Index: trunk/extensions/CodeReview/backend/CodeCommentLinker.php |
— | — | @@ -41,13 +41,15 @@ |
42 | 42 | $displayLen = 0; // innerHTML legth so far |
43 | 43 | $doTruncate = true; // truncated string plus '...' shorter than original? |
44 | 44 | $tagType = 0; // 0-open, 1-close |
45 | | - $bracketState = 0; // 1-tag start, 2-tag name, 3-tag params, 0-neither |
| 45 | + $bracketState = 0; // 1-tag start, 2-tag name, 0-neither |
46 | 46 | $entityState = 0; // 0-not entity, 1-entity |
47 | | - $tag = $ret = $ch = $lastCh = ''; |
| 47 | + $tag = $ret = $ch = ''; |
48 | 48 | $openTags = array(); |
49 | | - for( $pos = 0; $pos < strlen($text); $pos++ ) { |
50 | | - $lastCh = $ch; |
| 49 | + $textLen = strlen($text); |
| 50 | + for( $pos = 0; $pos < $textLen; ++$pos ) { |
51 | 51 | $ch = $text[$pos]; |
| 52 | + $lastCh = $pos ? $text[$pos-1] : ''; |
| 53 | + $ret .= $ch; // add to result string |
52 | 54 | if ( $ch == '<' ) { |
53 | 55 | self::onEndBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML |
54 | 56 | $entityState = 0; // for bad HTML |
— | — | @@ -58,9 +60,9 @@ |
59 | 61 | $bracketState = 0; // out of brackets |
60 | 62 | } elseif ( $bracketState == 1 ) { |
61 | 63 | if ( $ch == '/' ) { |
62 | | - $tagType = 1; // close tag |
| 64 | + $tagType = 1; // close tag (e.g. "</span>") |
63 | 65 | } else { |
64 | | - $tagType = 0; // open tag |
| 66 | + $tagType = 0; // open tag (e.g. "<span>") |
65 | 67 | $tag .= $ch; |
66 | 68 | } |
67 | 69 | $bracketState = 2; // building tag name |
— | — | @@ -68,28 +70,35 @@ |
69 | 71 | if ( $ch != ' ' ) { |
70 | 72 | $tag .= $ch; |
71 | 73 | } else { |
72 | | - $bracketState = 3; // name found (e.g. "<a href=...") |
| 74 | + // Name found (e.g. "<a href=..."), add on tag attributes... |
| 75 | + $pos += self::skipAndAppend( $ret, $text, "<>", $pos + 1 ); |
73 | 76 | } |
74 | 77 | } elseif ( $bracketState == 0 ) { |
75 | 78 | if ( $entityState ) { |
76 | 79 | if ( $ch == ';' ) { |
77 | 80 | $entityState = 0; |
78 | | - $displayLen++; // entity is one char |
| 81 | + $displayLen++; // entity is one displayed char |
79 | 82 | } |
80 | | - } elseif ( $ch == '&' ) { |
81 | | - $entityState = 1; // entity found, (e.g. " ") |
82 | 83 | } else { |
83 | | - $displayLen++; // not in brackets |
| 84 | + if ( $ch == '&' ) { |
| 85 | + $entityState = 1; // entity found, (e.g. " ") |
| 86 | + } else { |
| 87 | + $displayLen++; // this char is displayed |
| 88 | + // Add on the other display text after this... |
| 89 | + $skipped = self::skipAndAppend( |
| 90 | + $ret, $text, "<>&", $pos + 1, $maxLen - $displayLen ); |
| 91 | + $displayLen += $skipped; |
| 92 | + $pos += $skipped; |
| 93 | + } |
84 | 94 | } |
85 | 95 | } |
86 | | - $ret .= $ch; |
87 | 96 | if( !$doTruncate ) continue; |
88 | 97 | # Truncate if not in the middle of a bracket/entity... |
89 | 98 | if ( $bracketState == 0 && $entityState == 0 && $displayLen >= $maxLen ) { |
90 | | - $left = substr( $text, $pos + 1 ); // remaining string |
91 | | - $left = StringUtils::delimiterReplace( '<', '>', '', $left ); // rm tags |
92 | | - $left = StringUtils::delimiterReplace( '&', ';', '', $left ); // rm entities |
93 | | - $doTruncate = ( strlen($left) > strlen($ellipsis) ); |
| 99 | + $remaining = substr( $text, $pos + 1 ); // remaining string |
| 100 | + $remaining = StringUtils::delimiterReplace( '<', '>', '', $remaining ); // rm tags |
| 101 | + $remaining = StringUtils::delimiterReplace( '&', ';', '', $remaining ); // rm entities |
| 102 | + $doTruncate = ( strlen($remaining) > strlen($ellipsis) ); |
94 | 103 | if ( $doTruncate ) { |
95 | 104 | # Hack: go one char over so truncate() will handle multi-byte chars |
96 | 105 | $ret = $wgLang->truncate( $ret . 'x', strlen($ret), '' ) . $ellipsis; |
— | — | @@ -100,14 +109,26 @@ |
101 | 110 | if( $displayLen == 0 ) { |
102 | 111 | return ''; // no text shown, nothing to format |
103 | 112 | } |
104 | | - self::onEndBracket( $tag, $lastCh, $tagType, $openTags ); // for bad HTML |
| 113 | + self::onEndBracket( $tag, $text[$textLen-1], $tagType, $openTags ); // for bad HTML |
105 | 114 | while ( count( $openTags ) > 0 ) { |
106 | | - $ret .= '</' . array_pop($openTags) . '>'; // close open tags |
| 115 | + $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags |
107 | 116 | } |
108 | 117 | return $ret; |
109 | 118 | } |
110 | 119 | |
111 | | - protected function onEndBracket( &$tag, $tagType, $lastCh, &$openTags ) { |
| 120 | + // like strcspn() but adds the skipped chars to $ret |
| 121 | + private function skipAndAppend( &$ret, $text, $search, $start, $len = NULL ) { |
| 122 | + $skipCount = 0; |
| 123 | + if( $start < strlen($text) ) { |
| 124 | + $skipCount = strcspn( $text, $search, $start, $len ); |
| 125 | + $ret .= substr( $text, $start, $skipCount ); |
| 126 | + } |
| 127 | + return $skipCount; |
| 128 | + } |
| 129 | + |
| 130 | + // (a) push or pop $tag from $openTags as needed |
| 131 | + // (b) clear $tag value |
| 132 | + private function onEndBracket( &$tag, $tagType, $lastCh, &$openTags ) { |
112 | 133 | $tag = ltrim( $tag ); |
113 | 134 | if( $tag != '' ) { |
114 | 135 | if( $tagType == 0 && $lastCh != '/' ) { |