diff --git a/plugins/serendipity_event_nl2br/serendipity_event_nl2br.php b/plugins/serendipity_event_nl2br/serendipity_event_nl2br.php index 29ad482f..8b2d26f6 100644 --- a/plugins/serendipity_event_nl2br/serendipity_event_nl2br.php +++ b/plugins/serendipity_event_nl2br/serendipity_event_nl2br.php @@ -269,16 +269,23 @@ class serendipity_event_nl2br extends serendipity_event $element = $temp['element']; if ($p_tags) { + // NL2P OPERATION + $this->isolationtags = $isolate; + $text = $eventData[$element]; if (!empty($text)) { //Standardize line endings to Unix $text = str_replace(array("\r\n", "\r"), "\n", $text); - if ($element == 'body' && isset($eventData['extended'])) { - //move newlines from body to extended + + //move newlines from body to extended + if ($element == 'body' && isset($eventData['extended'])) + { $eventData['extended'] = str_repeat("\n",strspn($text,"\n",-1)) . $eventData['extended']; + $text = rtrim($text,"\n"); } - $eventData[$element] = $this->nl2p($text, $element); + $eventData[$element] = $this->nl2p($text); } + // NL2BR OPERATION } else if ($isolate) { $eventData[$element] = $this->isolate($eventData[$element], '~[<\[](' . implode('|', $isolate) . ').*?[>\]].*?[<\[]/\1[>\]]~si'); $eventData[$element] = nl2br($eventData[$element]); @@ -325,17 +332,17 @@ class serendipity_event_nl2br extends serendipity_event /* nl2br plugin start */ -p.whitelinebottom { +p.wl_bottom { margin-top: 0em; margin-bottom: 1em; } -p.whitelinetop { +p.wl_top { margin-top: 1em; margin-bottom: 0em; } -p.whitelinetopbottom { +p.wl_top_bottom { margin-top: 1em; margin-bottom: 1em; } @@ -387,215 +394,427 @@ p.break { } + /** ==================================== + * NL2P OPERATION + * ==================================== + */ - /** - * Insert
'; - $small_p = '
'; + var $nested_block_elements = array('div','table','blockquote'); + + var $singleton_elements = array('area', 'br', 'col', 'command', 'embed', 'hr', + 'img', 'input', 'keygen', 'link', 'param', 'source', + 'track', 'wbr', '!--' + ); - $i = count($text) - 1; - $whiteline = false; - - //main operation: convert \n to big_p and small_p - while ($i > 0) { - //search next /n enclosing text, starting at $i-1 - $i = $this->next_nl_block($i, $text); - if ($i == 0) { //no newlines left - break; - } elseif ($whiteline == true) { - $text[$i] = '
' . $big_p; - } else { - $text[$i] = '' . $small_p; - } - //look ahead for next paragraph class - if ($text[$i-1] === "\n") { - $whiteline = true; - $i--; - } else { - $whiteline = false; - } - } - if ($whiteline && $startnl) { - $start_tag = ''; - } elseif ($startnl) { - $start_tag = '
'; - } elseif ($whiteline) { - $start_tag = $big_p; - } else { - $start_tag = $small_p; - } - if ($complex) { - $textstring = $this->tidy_block_elements($text); - $textstring = $this->formate_block_elements($textstring); - $textstring = $this->isolate_block_elements($textstring); - $textstring = $start_tag . $textstring . '
'; - return $this->clean_code($textstring); - } - return $start_tag . implode($text) . ''; - } + var $allowed_p_parents = array('blockquote', 'td', 'div', 'article', 'aside', 'dd', + 'details', 'dl', 'dt', 'footer', 'header', 'summary' + ); - /** - * Remove unnecessary paragraphs - * Unnecessary are those which start and end immediately. - * They only get created by isolate_block_elements - * @param mixed text - * @return string - */ - function clean_code ($text) - { - if (is_array($text)) { - $text = implode($text); - } - return str_replace(array('','','','',''),"", $text); - } + //paragraphs aren't allowed in these inline elements -> p closes these elements: + var $inline_elements = array('b', 'big', 'i', 'small', 'tt', 'abbr', + 'acronym', 'cite', 'code', 'dfn', 'em', 'kbd', 'strong', + 'samp', 'var', 'a', 'bdo', 'br', 'map', 'object', + 'q', 'script', 'span', 'sub', 'sup', 'button', + 'label', 'select', 'textarea' + ); - function purge_p($text) - { - $text = str_replace('', "", $text); - return str_replace(array('','
', '
', '
'),"\n", $text); - } + const P_TOP = ''; + const P_BOTTOM = '
'; + const P_TOP_BOTTOM = '
'; + const P_BREAK = '
'; + const P_END = '
'; - /** - * Use nl2p on text within blockelements, useful e.g. with blockquotes - * @param array text - * @return string - */ - function formate_block_elements($textstring) - { - $block_elements = array('end_tags($start_tag); - //first see if block-element really exists - $start_tag_position = strpos($textstring, $start_tag); - while ($start_tag_position !== false) { - $start_tag_end = strpos($textstring, '>', $start_tag_position)+1; - $blocktext = $this->get_string_till($textstring, $end_tag, $start_tag_end); - $blocktext_length = strlen($blocktext); - $formatted_blocktext = $this->nl2p($blocktext); - //insert formatted_blocktext into old blockelement - $textstring = substr_replace($textstring, $formatted_blocktext, $start_tag_end, $blocktext_length); + function nl2p($text) + { + //homogenize tags + $text = $this->tag_clean($text); + + //delete isolation tags from other arrays + if ($this->isolationtags) + { + $this->block_elements = array_diff($this->block_elements,$this->isolationtags); + $this->allowed_p_parents = array_diff($this->allowed_p_parents,$this->isolationtags); + $this->nested_block_elements = array_diff($this->nested_block_elements,$this->isolationtags); + $this->inline_elements = array_diff($this->inline_elements,$this->isolationtags); + $this->singleton_elements = array_diff($this->singleton_elements,$this->isolationtags); + } + else { $this->isolationtags = array(); } - //next blockelement - $start_tag_position = strpos($textstring, $start_tag, $start_tag_end+strlen($formatted_blocktext)); - } - } - return $textstring; - } + return $this->blocktag_nl2p($text); + } - /** - * Make sure none of these block_elements are within a- * @param string text - * @return string - */ - function isolate_block_elements($textstring) - { - $block_elements = array('
end_tags($start_tag); - $textstring = str_replace("$start_tag", "$start_tag", $textstring); - $textstring = str_replace("$end_tag", "$end_tag
", $textstring); - } - } - return $textstring; - } + for ($i = 0; $i < count($text); $i++) + { + if ($text[$i] == '<' && !strpos($textstring,'>',$i+1) ) + { $text[$i] = '<'; } + elseif ($text[$i] == '>' && !($tagstart !== false || $tagdef || $tagstyle) ) + { $text[$i] = '>'; } + elseif ($text[$i] == '<') + { $tagstart = $i; } + elseif ($text[$i] == ' ' && $tagstart !== false ) + { $text[$i] = ''; } + elseif ($text[$i] == '>' && $tagstart !== false ) + { + $text[$tagstart] = '<'; + $text[$i] = '>'; + } + elseif ($text[$i] == ' ' && $tagdef) + { + //check if it is a real tag + $tag = substr($textstring,$tagdef,$i-$tagdef); + + if ( !(in_array($tag,$this->block_elements) + || in_array($tag,$this->singleton_elements) + || in_array($tag,$this->inline_elements) + || in_array($tag,$this->allowed_p_parents) + || in_array($tag,$this->isolationtags) + || in_array($tag,$this->nested_block_elements) )) + { + $text[$tagstart_b] = '<'; + $text[strpos($textstring,'>',$i+1)] = '>'; + } else + { + $tagstyle = true; + $tagdef = false; + } + } + elseif ($text[$i] == '/' && $tagstart !== false) + { $endtag = true; } + elseif ($text[$i] == ' ' && $endtag) + { $text[$i] = ''; } + elseif (($tagstart !== false || $tagdef || $tagstyle) && $text[$i] == "\n") + { $text[$i] = ''; } + elseif ($text[$i] == '>' && ($tagdef || $tagstyle) ) + { + $tagstart = false; + $tagdef = false; + $tagstyle = false; + $endtag = false; + } + elseif ($tagstart !== false) + { + $tagdef = $i; + $tagstart_b = $tagstart; + $tagstart = false; + $text[$i] = strtolower($text[$i]); + } + elseif ($tagdef) + { $text[$i] = strtolower($text[$i]); } + } + return implode($text); + } - /** - * Remove all
-tags from block-elements - * Note: Walking from left to right - * @param array text - * @return string - */ - function tidy_block_elements($text) - { - $remove = false; - $textstring = implode($text); - $block_elements = array('
end_tags($start_tag); - //first see if block-element really exists - $start_tag_position = strpos($textstring, $start_tag); - while ($start_tag_position !== false) { - $start_tag_end = strpos($textstring, '>', $start_tag_position)+1; - $blocktext = $this->get_string_till($textstring, $end_tag, $start_tag_end); - $blocktext_length = strlen($blocktext); - $formatted_blocktext = $this->purge_p($blocktext); - //insert formatted_blocktext into old blockelement - $textstring = substr_replace($textstring, $formatted_blocktext, $start_tag_end, $blocktext_length); + /* + * sophisticated nl to p - blocktag stage + * handles content with blocktags, apply nl2p to the block elements if tag allows it + * works also for ommitted closing tags and singleton tags + * Insert P_BOTTOM class at paragraphs ending with two newlines + * Insert P_BREAK class at paragraphs ending with one newline + * Insert P_TOP class at the first paragraph if starting with a nl + * Insert P_TOP_BOTTOM class if the first paragraph is ending with two newlines + * @param: text + * return string + */ + function blocktag_nl2p($text) + { + //explode string into array of tags and contents + $textarray = $this->explode_along_tags($text); + $content = ""; + $start = 0; + $tagstack = array(); + $isolation_flag = false; - //next blockelement - $start_tag_position = strpos($textstring, $start_tag, $start_tag_end+strlen($formatted_blocktext)); - } - } - return $textstring; - } + for ($i=0; $i < count($textarray); $i++) + { - function get_string_till($text, $end_tag, $offset=0) - { - if (strpos($text, $end_tag, $offset) === false) { - return ""; - } - $len = strpos($text, $end_tag, $offset) - $offset; - return substr($text, $offset, $len); - } + //get tag or false if none + $tag = $this->extract_tag($textarray[$i]); - /** - * Return corresponding end-tag:
- */ - function end_tags($start_tag) - { - return str_replace("<", "", $start_tag).">"; - } + //new blocktag - e.g.
+ if ($tag && $this->is_starttag($textarray[$i]) + && (in_array($tag, $this->block_elements) || in_array($tag, $this->nested_block_elements) )) + { + //merge previous content, apply nl2p if needed and concatenate + if (!$isolation_flag && ( empty($tagstack) || in_array($tagstack[0], $this->allowed_p_parents) )) + { + $content .= $this->nl2pblock(implode(array_slice($textarray,$start,$i-$start))); + } else + { + $content .= implode(array_slice($textarray,$start,$i-$start)); + } + // clear stack of block elements and insert + if (in_array($tag, $this->block_elements) ) + { + $tagstack = array_diff($tagstack, $this->block_elements); + } + // concatenate tag + $content .= $textarray[$i] . "\n"; + + if (!in_array($tag, $this->singleton_elements) ) + { + array_unshift($tagstack, $tag); + } + $start = $i+1; + } + //new tag which can contain paragraphs and can be inside a blocktag - e.g.
or + elseif($tag && !$this->is_starttag($textarray[$i]) && !empty($tagstack) && $tag == $tagstack[0]) + { + //content, apply nl2p if needed + if ($i != $start) + { + if (!$isolation_flag && in_array($tagstack[0], $this->allowed_p_parents) ) + { + $content .= $this->nl2pblock(implode(array_slice($textarray,$start,$i-$start))); + } else + { + $content .= implode(array_slice($textarray,$start,$i-$start)); + } + } + //closing tag + $content .= $textarray[$i] . "\n"; + + $start = $i+1; + array_shift($tagstack); + } + } + + //merge remainder + if (!$isolation_flag && ( empty($tagstack) || in_array($tagstack[0], $this->allowed_p_parents) )) + { + $content .= $this->nl2pblock(implode(array_slice($textarray,$start,$i-$start))); + } else + { + $content .= implode(array_slice($textarray,$start,$i-$start)); + } + return $content; + } + + /* + * sophisticated nl to p for content which is already + * purged from block elements by blocktag_nl2p + * explode content along \n + * check for following \n + * explode along (inline) tags, get active tags across newlines + * build every paragraph: p class | reopen active tags | content ... | new open tags | closing p tag + * @param string text + * @return string + */ + function nl2pblock($textstring) + { + + //check for empty content + if (empty(trim($textstring))) { return $textstring; } + + //check for start/end newlines + $startnl = ( strspn($textstring,"\n") ) ? true : false; + $endnl = ( strspn($textstring,"\n",-1 ) ) ? true : false; + $whiteline = false; + $textstring = trim($textstring,"\n"); + if (empty($textstring)) { return ''; } + + //explode in paragraphs + $textarray = explode("\n",$textstring); + $tagstack = array(); + $tagstack_prev = array(); + $textline = array(); + + $content = ''; + + for($i=0; $i+ elseif ($tag && $this->is_starttag($textarray[$i]) && in_array($tag, $this->allowed_p_parents)) + { + //merge previous content, apply nl2p if needed and concatenate + if (!$isolation_flag && ( empty($tagstack) || in_array($tagstack[0], $this->allowed_p_parents) )) + { + $content .= $this->nl2pblock(implode(array_slice($textarray,$start,$i-$start))); + } else + { + $content .= implode(array_slice($textarray,$start,$i-$start)); + } + //insert tag into the stack and concatenate + array_unshift($tagstack, $tag); + $content .= $textarray[$i]; + $start = $i+1; + } + //isolation tag + elseif($tag && $this->is_starttag($textarray[$i]) && in_array($tag, $this->isolationtags) ) + { + //merge previous content, apply nl2p if needed and concatenate + if (!$isolation_flag && empty($tagstack) ) + { + $content .= $this->nl2pblock(implode(array_slice($textarray,$start,$i-$start))); + } elseif (!$isolation_flag && in_array($tagstack[0], $this->allowed_p_parents) ) + { + $content .= $textarray[$start] + . $this->nl2pblock(implode(array_slice($textarray,$start+1,$i-$start-1))); + } else + { + $content .= implode(array_slice($textarray,$start,$i-$start)); + } + $isolation_flag = true; + $start = $i+1; + } + //closing isolation tag + elseif($tag && !$this->is_starttag($textarray[$i]) && in_array($tag, $this->isolationtags) ) + { + if ($isolation_flag) + { + //content, no nl2p + $content .= implode(array_slice($textarray,$start,$i-$start-1)); + $isolation_flag = false; + $start = $i+1; + } + } + //closing blocktag or p parent - e.g. explode_along_tags($textarray[$i]); + //save active tags + $tagstack_prev = $tagstack; + + //iterate trough the tags in the paragraph + for ($j=0; $j extract_tag($textline[$j]); + + // put or remove tag from stack + if ($tag && $this->is_starttag($textline[$j]) && !in_array($tag,$this->singleton_elements) ) + { + array_unshift($tagstack, $textline[$j]); + } + elseif($tag && !$this->is_starttag($textline[$j]) && !empty($tagstack) && $tag == $this->extract_tag($tagstack[0])) + { + array_shift($tagstack); + } + } + + //build content + //paragraph class + if ($i == 0 && $startnl && ( $whiteline || ($i == count($textarray)-1 && $endnl) ) ) + { + $content .= self::P_TOP_BOTTOM; + } + elseif ($i == 0 && $startnl) + { + $content .= self::P_TOP; + } + elseif ($whiteline || ($i == count($textarray)-1 && $endnl)) + { + $content .= self::P_BOTTOM; + } else + { + $content .= self::P_BREAK; + } + + //reopen active tags + foreach($tagstack_prev as $ins_tag) + { + $content .= $ins_tag; + } + + //content paragraph + $content .= $textarray[$i]; + + //close open tags + foreach($tagstack as $ins_tag) + { + $content .= $this->html_end_tag($this->extract_tag($ins_tag)); + } + //paragraph closing tag + $content .= self::P_END . "\n"; + $whiteline = false; + } + + return $content; + } + + /** explode textstring into array of substrings + * array element can be tag or content + * @param text + * $return array of tags and contents + */ + function explode_along_tags($text) + { + $startpos = 0; + $endpos = 0; + $textarray = array(); + do + { + //find tag start + $endpos = strpos($text,'<',$startpos); + if ($endpos === false) + { + //no more tags, copy remainder to array + $endpos = strlen($text); + if ($endpos - $startpos > 0) + { $textarray[] = substr($text,$startpos,$endpos - $startpos); } + return $textarray; + } + elseif (($endpos - $startpos) > 0) + { + //copy preliminary text to array + $textarray[] = substr($text,$startpos,$endpos - $startpos); + } + $startpos = $endpos; + //find tag end + $endpos = strpos($text,'>',$startpos); + if ($endpos === false) { return false; } + elseif (($endpos - $startpos) > 1) + { + //copy tag to array + $textarray[] = substr($text,$startpos,$endpos - $startpos + 1); + $startpos = $endpos + 1; + } + else { return false; } + } while (1); + return false; + } + + function extract_tag($text) + { + if ($text[0] != '<') { return false; } + $n = strcspn($text,' >'); + return ltrim(substr($text,0,$n),''); + } + + function html_end_tag($text) { return '' . $text . '>'; } + function html_start_tag($text) { return '<' . $text . '>'; } + function is_starttag($text) { return ($text[1] == "/") ? false : true; } - /** - * Find next newline separated by text from current position - * @param int start - * $param array text - */ - function next_nl_block($i, $text) - { - $skipped = false; - for ($i--; $i>0; $i-- ) { - if (!$skipped){ - //see if you skipped over a non-newline (heading to the next block) - if (strpos($text[$i], "\n") === false) { - $skipped = true; - } - } else if (strpos($text[$i], "\n") !== false) { - break; - } - } - return $i; - } }