Index: trunk/phase3/tests/phpunit/languages/LanguageTest.php |
— | — | @@ -112,7 +112,109 @@ |
113 | 113 | ); |
114 | 114 | } |
115 | 115 | |
| 116 | + function testTruncate() { |
| 117 | + $this->assertEquals( |
| 118 | + "XXX", |
| 119 | + $this->lang->truncate( "1234567890", 0, 'XXX' ), |
| 120 | + 'truncate prefix, len 0, small ellipsis' |
| 121 | + ); |
| 122 | + |
| 123 | + $this->assertEquals( |
| 124 | + "12345XXX", |
| 125 | + $this->lang->truncate( "1234567890", 8, 'XXX' ), |
| 126 | + 'truncate prefix, small ellipsis' |
| 127 | + ); |
| 128 | + |
| 129 | + $this->assertEquals( |
| 130 | + "123456789", |
| 131 | + $this->lang->truncate( "123456789", 5, 'XXXXXXXXXXXXXXX' ), |
| 132 | + 'truncate prefix, large ellipsis' |
| 133 | + ); |
| 134 | + |
| 135 | + $this->assertEquals( |
| 136 | + "XXX67890", |
| 137 | + $this->lang->truncate( "1234567890", -8, 'XXX' ), |
| 138 | + 'truncate suffix, small ellipsis' |
| 139 | + ); |
| 140 | + |
| 141 | + $this->assertEquals( |
| 142 | + "123456789", |
| 143 | + $this->lang->truncate( "123456789", -5, 'XXXXXXXXXXXXXXX' ), |
| 144 | + 'truncate suffix, large ellipsis' |
| 145 | + ); |
| 146 | + } |
| 147 | + |
116 | 148 | /** |
| 149 | + * @dataProvider provideHTMLTruncateData() |
| 150 | + */ |
| 151 | + function testTruncateHTML( $len, $ellipsis, $input, $expected ) { |
| 152 | + // Actual HTML... |
| 153 | + $this->assertEquals( |
| 154 | + $expected, |
| 155 | + $this->lang->truncateHTML( $input, $len, $ellipsis ) |
| 156 | + ); |
| 157 | + } |
| 158 | + |
| 159 | + /** |
| 160 | + * Array format is ($len, $ellipsis, $input, $expected) |
| 161 | + */ |
| 162 | + function provideHTMLTruncateData() { |
| 163 | + return array( |
| 164 | + array( 0, 'XXX', "1234567890", "XXX" ), |
| 165 | + array( 8, 'XXX', "1234567890", "12345XXX" ), |
| 166 | + array( 5, 'XXXXXXXXXXXXXXX', '1234567890', "1234567890" ), |
| 167 | + array( 2, '***', |
| 168 | + '<p><span style="font-weight:bold;"></span></p>', |
| 169 | + '<p><span style="font-weight:bold;"></span></p>', |
| 170 | + ), |
| 171 | + array( 2, '***', |
| 172 | + '<p><span style="font-weight:bold;">123456789</span></p>', |
| 173 | + '<p><span style="font-weight:bold;">***</span></p>', |
| 174 | + ), |
| 175 | + array( 2, '***', |
| 176 | + '<p><span style="font-weight:bold;"> 23456789</span></p>', |
| 177 | + '<p><span style="font-weight:bold;">***</span></p>', |
| 178 | + ), |
| 179 | + array( 3, '***', |
| 180 | + '<p><span style="font-weight:bold;">123456789</span></p>', |
| 181 | + '<p><span style="font-weight:bold;">***</span></p>', |
| 182 | + ), |
| 183 | + array( 4, '***', |
| 184 | + '<p><span style="font-weight:bold;">123456789</span></p>', |
| 185 | + '<p><span style="font-weight:bold;">1***</span></p>', |
| 186 | + ), |
| 187 | + array( 5, '***', |
| 188 | + '<tt><span style="font-weight:bold;">123456789</span></tt>', |
| 189 | + '<tt><span style="font-weight:bold;">12***</span></tt>', |
| 190 | + ), |
| 191 | + array( 6, '***', |
| 192 | + '<p><a href="www.mediawiki.org">123456789</a></p>', |
| 193 | + '<p><a href="www.mediawiki.org">123***</a></p>', |
| 194 | + ), |
| 195 | + array( 6, '***', |
| 196 | + '<p><a href="www.mediawiki.org">12 456789</a></p>', |
| 197 | + '<p><a href="www.mediawiki.org">12 ***</a></p>', |
| 198 | + ), |
| 199 | + array( 7, '***', |
| 200 | + '<small><span style="font-weight:bold;">123<p id="#moo">456</p>789</span></small>', |
| 201 | + '<small><span style="font-weight:bold;">123<p id="#moo">4***</p></span></small>', |
| 202 | + ), |
| 203 | + array( 8, '***', |
| 204 | + '<div><span style="font-weight:bold;">123<span>4</span>56789</span></div>', |
| 205 | + '<div><span style="font-weight:bold;">123<span>4</span>5***</span></div>', |
| 206 | + ), |
| 207 | + array( 9, '***', |
| 208 | + '<p><table style="font-weight:bold;"><tr><td>123456789</td></tr></table></p>', |
| 209 | + '<p><table style="font-weight:bold;"><tr><td>123456789</td></tr></table></p>', |
| 210 | + ), |
| 211 | + array( 10, '***', |
| 212 | + '<p><font style="font-weight:bold;">123456789</font></p>', |
| 213 | + '<p><font style="font-weight:bold;">123456789</font></p>', |
| 214 | + ), |
| 215 | + ); |
| 216 | + } |
| 217 | + |
| 218 | + /** |
117 | 219 | * Test Language::isValidBuiltInCode() |
118 | 220 | * @dataProvider provideLanguageCodes |
119 | 221 | */ |
Index: trunk/phase3/languages/Language.php |
— | — | @@ -2770,31 +2770,33 @@ |
2771 | 2771 | return $text; // string short enough even *with* HTML (short-circuit) |
2772 | 2772 | } |
2773 | 2773 | |
2774 | | - $displayLen = 0; // innerHTML legth so far |
| 2774 | + $dispLen = 0; // innerHTML legth so far |
2775 | 2775 | $testingEllipsis = false; // checking if ellipses will make string longer/equal? |
2776 | 2776 | $tagType = 0; // 0-open, 1-close |
2777 | 2777 | $bracketState = 0; // 1-tag start, 2-tag name, 0-neither |
2778 | 2778 | $entityState = 0; // 0-not entity, 1-entity |
2779 | | - $tag = $ret = $pRet = ''; // accumulated tag name, accumulated result string |
| 2779 | + $tag = $ret = ''; // accumulated tag name, accumulated result string |
2780 | 2780 | $openTags = array(); // open tag stack |
2781 | | - $pOpenTags = array(); |
| 2781 | + $maybeState = null; // possible truncation state |
2782 | 2782 | |
2783 | 2783 | $textLen = strlen( $text ); |
2784 | 2784 | $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated |
2785 | 2785 | for ( $pos = 0; true; ++$pos ) { |
2786 | 2786 | # Consider truncation once the display length has reached the maximim. |
| 2787 | + # We check if $dispLen > 0 to grab tags for the $neLength = 0 case. |
2787 | 2788 | # Check that we're not in the middle of a bracket/entity... |
2788 | | - if ( $displayLen >= $neLength && $bracketState == 0 && $entityState == 0 ) { |
| 2789 | + if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) { |
2789 | 2790 | if ( !$testingEllipsis ) { |
2790 | 2791 | $testingEllipsis = true; |
2791 | 2792 | # Save where we are; we will truncate here unless there turn out to |
2792 | 2793 | # be so few remaining characters that truncation is not necessary. |
2793 | | - $pOpenTags = $openTags; // save state |
2794 | | - $pRet = $ret; // save state |
2795 | | - } elseif ( $displayLen > $length && $displayLen > strlen( $ellipsis ) ) { |
| 2794 | + if ( !$maybeState ) { // already saved? ($neLength = 0 case) |
| 2795 | + $maybeState = array( $ret, $openTags ); // save state |
| 2796 | + } |
| 2797 | + } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) { |
2796 | 2798 | # String in fact does need truncation, the truncation point was OK. |
2797 | | - $openTags = $pOpenTags; // reload state |
2798 | | - $ret = $this->removeBadCharLast( $pRet ); // reload state, multi-byte char fix |
| 2799 | + list( $ret, $openTags ) = $maybeState; // reload state |
| 2800 | + $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix |
2799 | 2801 | $ret .= $ellipsis; // add ellipsis |
2800 | 2802 | break; |
2801 | 2803 | } |
— | — | @@ -2832,25 +2834,27 @@ |
2833 | 2835 | if ( $entityState ) { |
2834 | 2836 | if ( $ch == ';' ) { |
2835 | 2837 | $entityState = 0; |
2836 | | - $displayLen++; // entity is one displayed char |
| 2838 | + $dispLen++; // entity is one displayed char |
2837 | 2839 | } |
2838 | 2840 | } else { |
| 2841 | + if ( $neLength == 0 && !$maybeState ) { |
| 2842 | + // Save state without $ch. We want to *hit* the first |
| 2843 | + // display char (to get tags) but not *use* it if truncating. |
| 2844 | + $maybeState = array( substr( $ret, 0, -1 ), $openTags ); |
| 2845 | + } |
2839 | 2846 | if ( $ch == '&' ) { |
2840 | 2847 | $entityState = 1; // entity found, (e.g. " ") |
2841 | 2848 | } else { |
2842 | | - $displayLen++; // this char is displayed |
| 2849 | + $dispLen++; // this char is displayed |
2843 | 2850 | // Add the next $max display text chars after this in one swoop... |
2844 | | - $max = ( $testingEllipsis ? $length : $neLength ) - $displayLen; |
| 2851 | + $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen; |
2845 | 2852 | $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max ); |
2846 | | - $displayLen += $skipped; |
| 2853 | + $dispLen += $skipped; |
2847 | 2854 | $pos += $skipped; |
2848 | 2855 | } |
2849 | 2856 | } |
2850 | 2857 | } |
2851 | 2858 | } |
2852 | | - if ( $displayLen == 0 ) { |
2853 | | - return ''; // no text shown, nothing to format |
2854 | | - } |
2855 | 2859 | // Close the last tag if left unclosed by bad HTML |
2856 | 2860 | $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags ); |
2857 | 2861 | while ( count( $openTags ) > 0 ) { |