[FEATURE] Keep tags when stripping empty tags in HtmlParser 07/45107/5
authorAlexander Stehlik <alexander.stehlik@gmail.com>
Sun, 20 Dec 2015 08:31:27 +0000 (09:31 +0100)
committerBenni Mack <benni@typo3.org>
Sat, 23 Jan 2016 11:17:08 +0000 (12:17 +0100)
A new option for the HTMLparser.stripEmptyTags configuration
is added:

HTMLparser.stripEmptyTags.keepTags = tr,td

This will make the HTMLparser remove all empty tags except the
configured ones. If this setting is used the .tags configuration
will have no effect any more.

Resolves: #72045
Releases: master
Change-Id: I7137db597e442460aa7add9d99bdf73db0d5cbd5
Reviewed-on: https://review.typo3.org/45107
Reviewed-by: Frank Nägler <frank.naegler@typo3.org>
Tested-by: Frank Nägler <frank.naegler@typo3.org>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
typo3/sysext/core/Classes/Html/HtmlParser.php
typo3/sysext/core/Documentation/Changelog/master/Feature-72045-KeepTagsInHtmlParserWhenStrippingEmptyTags.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Html/HtmlParserTest.php

index 15aab06..8053d1d 100644 (file)
@@ -986,19 +986,24 @@ class HtmlParser
      * @param string $tagList The comma separated list of tags to be stripped.
      *                        If empty, all empty tags will be stripped
      * @param bool $treatNonBreakingSpaceAsEmpty If TRUE tags containing only &nbsp; entities will be treated as empty.
+     * @param bool $keepTags If true, the provided tags will be kept instead of stripped.
      * @return string the stripped content
      */
-    public function stripEmptyTags($content, $tagList = null, $treatNonBreakingSpaceAsEmpty = false)
+    public function stripEmptyTags($content, $tagList = '', $treatNonBreakingSpaceAsEmpty = false, $keepTags = false)
     {
-        $tagRegEx = '[^ >]+'; // all characters until you reach a > or space;
-        if ($tagList) {
-            $tags = preg_split('/,/', $tagList);
-            $tagRegEx = preg_replace('/ */', '', join('|', $tags));
+        if (!empty($tagList)) {
+            $tagRegEx = join('|', GeneralUtility::trimExplode(',', $tagList, true));
+            if ($keepTags) {
+                $tagRegEx = '(?!' . $tagRegEx . ')[^ >]+';
+            }
+        } else {
+            $tagRegEx = '[^ >]+'; // all characters until you reach a > or space;
         }
         $count = 1;
         $nbspRegex = $treatNonBreakingSpaceAsEmpty ? '|(&nbsp;)' : '';
-        while ($count != 0) {
-            $content = preg_replace(sprintf('/<(%s)[^>]*>( %s)*<\/\\1[^>]*>/i', $tagRegEx, $nbspRegex), '', $content, -1, $count);
+        $finalRegex = sprintf('/<(%s)[^>]*>( %s)*<\/\\1[^>]*>/i', $tagRegEx, $nbspRegex);
+        while ($count !== 0) {
+            $content = preg_replace($finalRegex, '', $content, -1, $count);
         }
         return $content;
     }
@@ -1012,26 +1017,21 @@ class HtmlParser
      */
     protected function stripEmptyTagsIfConfigured($value, $configuration)
     {
-        if (isset($configuration['stripEmptyTags']) && $configuration['stripEmptyTags']) {
-            $tags = null;
-            if (
-                isset($configuration['stripEmptyTags.']['tags'])
-                && $configuration['stripEmptyTags.']['tags'] !== ''
-            ) {
-                $tags = $configuration['stripEmptyTags.']['tags'];
-            }
-
-            $treatNonBreakingSpaceAsEmpty = false;
-            if (
-                isset($configuration['stripEmptyTags.']['treatNonBreakingSpaceAsEmpty'])
-                && $configuration['stripEmptyTags.']['treatNonBreakingSpaceAsEmpty']
-            ) {
-                $treatNonBreakingSpaceAsEmpty = (bool)$configuration['stripEmptyTags.']['treatNonBreakingSpaceAsEmpty'];
-            }
+        if (empty($configuration['stripEmptyTags'])) {
+            return $value;
+        }
 
-            $value = $this->stripEmptyTags($value, $tags, $treatNonBreakingSpaceAsEmpty);
+        $tags = null;
+        $keepTags = false;
+        if (!empty($configuration['stripEmptyTags.']['keepTags'])) {
+            $tags = $configuration['stripEmptyTags.']['keepTags'];
+            $keepTags = true;
+        } elseif (!empty($configuration['stripEmptyTags.']['tags'])) {
+            $tags = $configuration['stripEmptyTags.']['tags'];
         }
 
-        return $value;
+        $treatNonBreakingSpaceAsEmpty = !empty($configuration['stripEmptyTags.']['treatNonBreakingSpaceAsEmpty']);
+
+        return $this->stripEmptyTags($value, $tags, $treatNonBreakingSpaceAsEmpty, $keepTags);
     }
 }
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-72045-KeepTagsInHtmlParserWhenStrippingEmptyTags.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-72045-KeepTagsInHtmlParserWhenStrippingEmptyTags.rst
new file mode 100644 (file)
index 0000000..77571fb
--- /dev/null
@@ -0,0 +1,28 @@
+====================================================
+Feature: #72045 - HTMLparser.stripEmptyTags.keepTags
+====================================================
+
+Description
+===========
+
+A new option for the HTMLparser.stripEmptyTags configuration is added.
+It allows keeping configured tags. Before this change only a list of tags
+could be provided that should be removed.
+
+The following example will strip all empty tags **except** ``tr`` and ``td`` tags.
+
+::
+
+    HTMLparser.stripEmptyTags = 1
+    HTMLparser.stripEmptyTags.keepTags = tr,td
+
+
+**Important!** If this setting is used the stripEmptyTags.tags configuration will
+have no effect any more. You can only use one option at a time.
+
+
+Impact
+======
+
+Unless the configuration of the HTMLparser is changed, the stripEmptyTags
+feature will work as before.
\ No newline at end of file
index eaa7eb7..01711fc 100644 (file)
@@ -294,23 +294,38 @@ class HtmlParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
     public function emptyTagsDataProvider()
     {
         return array(
-            array(0 , null, false, '<h1></h1>', '<h1></h1>'),
-            array(1 , null, false, '<h1></h1>', ''),
-            array(1 , null, false, '<h1>hallo</h1>', '<h1>hallo</h1>'),
-            array(1 , null, false, '<h1 class="something"></h1>', ''),
-            array(1 , null, false, '<h1 class="something"></h1><h2></h2>', ''),
-            array(1 , 'h2', false, '<h1 class="something"></h1><h2></h2>', '<h1 class="something"></h1>'),
-            array(1 , 'h2, h1', false, '<h1 class="something"></h1><h2></h2>', ''),
-            array(1 , null, false, '<div><p></p></div>', ''),
-            array(1 , null, false, '<div><p>&nbsp;</p></div>', '<div><p>&nbsp;</p></div>'),
-            array(1 , null, true, '<div><p>&nbsp;&nbsp;</p></div>', ''),
-            array(1 , null, true, '<div>&nbsp;&nbsp;<p></p></div>', ''),
-            array(1 , null, false, '<div>Some content<p></p></div>', '<div>Some content</div>'),
-            array(1 , null, true, '<div>Some content<p></p></div>', '<div>Some content</div>'),
-            array(1 , null, false, '<div>Some content</div>', '<div>Some content</div>'),
-            array(1 , null, true, '<div>Some content</div>', '<div>Some content</div>'),
-            array(1 , null, false, '<a href="#skiplinks">Skiplinks </a><b></b>', '<a href="#skiplinks">Skiplinks </a>'),
-            array(1 , null, true, '<a href="#skiplinks">Skiplinks </a><b></b>', '<a href="#skiplinks">Skiplinks </a>'),
+            array(0, null, false, '<h1></h1>', '<h1></h1>'),
+            array(1, null, false, '<h1></h1>', ''),
+            array(1, null, false, '<h1>hallo</h1>', '<h1>hallo</h1>'),
+            array(1, null, false, '<h1 class="something"></h1>', ''),
+            array(1, null, false, '<h1 class="something"></h1><h2></h2>', ''),
+            array(1, 'h2', false, '<h1 class="something"></h1><h2></h2>', '<h1 class="something"></h1>'),
+            array(1, 'h2, h1', false, '<h1 class="something"></h1><h2></h2>', ''),
+            array(1, null, false, '<div><p></p></div>', ''),
+            array(1, null, false, '<div><p>&nbsp;</p></div>', '<div><p>&nbsp;</p></div>'),
+            array(1, null, true, '<div><p>&nbsp;&nbsp;</p></div>', ''),
+            array(1, null, true, '<div>&nbsp;&nbsp;<p></p></div>', ''),
+            array(1, null, false, '<div>Some content<p></p></div>', '<div>Some content</div>'),
+            array(1, null, true, '<div>Some content<p></p></div>', '<div>Some content</div>'),
+            array(1, null, false, '<div>Some content</div>', '<div>Some content</div>'),
+            array(1, null, true, '<div>Some content</div>', '<div>Some content</div>'),
+            array(1, null, false, '<a href="#skiplinks">Skiplinks </a><b></b>', '<a href="#skiplinks">Skiplinks </a>'),
+            array(1, null, true, '<a href="#skiplinks">Skiplinks </a><b></b>', '<a href="#skiplinks">Skiplinks </a>'),
+            array(0, '', false, '<h1></h1>', '<h1></h1>'),
+            array(1, '', false, '<h1></h1>', ''),
+            array(1, '', false, '<h1>hallo</h1>', '<h1>hallo</h1>'),
+            array(1, '', false, '<h1 class="something"></h1>', ''),
+            array(1, '', false, '<h1 class="something"></h1><h2></h2>', ''),
+            array(1, '', false, '<div><p></p></div>', ''),
+            array(1, '', false, '<div><p>&nbsp;</p></div>', '<div><p>&nbsp;</p></div>'),
+            array(1, '', true, '<div><p>&nbsp;&nbsp;</p></div>', ''),
+            array(1, '', true, '<div>&nbsp;&nbsp;<p></p></div>', ''),
+            array(1, '', false, '<div>Some content<p></p></div>', '<div>Some content</div>'),
+            array(1, '', true, '<div>Some content<p></p></div>', '<div>Some content</div>'),
+            array(1, '', false, '<div>Some content</div>', '<div>Some content</div>'),
+            array(1, '', true, '<div>Some content</div>', '<div>Some content</div>'),
+            array(1, '', false, '<a href="#skiplinks">Skiplinks </a><b></b>', '<a href="#skiplinks">Skiplinks </a>'),
+            array(1, '', true, '<a href="#skiplinks">Skiplinks </a><b></b>', '<a href="#skiplinks">Skiplinks </a>'),
         );
     }
 
@@ -339,6 +354,48 @@ class HtmlParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
     }
 
     /**
+     * @return array
+     */
+    public function stripEmptyTagsKeepsConfiguredTagsDataProvider() {
+        return [
+            array(
+                'tr,td',
+                false,
+                '<div><p><tr><td></td></tr></p></div><div class="test"></div><tr></tr><p></p><td></td><i></i>',
+                '<div><p><tr><td></td></tr></p></div><tr></tr><td></td>'
+            ),
+            array(
+                'tr,td',
+                true,
+                '<div><p><tr><td></td></tr></p></div><p class="test"> &nbsp; </p><tr></tr><p></p><td></td><i></i>',
+                '<div><p><tr><td></td></tr></p></div><tr></tr><td></td>'
+            ),
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider stripEmptyTagsKeepsConfiguredTagsDataProvider
+     * @param string $tagList List of tags that should be kept, event if they are empty.
+     * @param bool $treatNonBreakingSpaceAsEmpty If true &nbsp; will be considered empty.
+     * @param string $content The HTML content that should be parsed.
+     * @param string $expectedResult The expected HTML code result.
+     */
+    public function stripEmptyTagsKeepsConfiguredTags($tagList, $treatNonBreakingSpaceAsEmpty, $content, $expectedResult) {
+        $tsConfig = array(
+            'keepNonMatchedTags' => 1,
+            'stripEmptyTags' => 1,
+            'stripEmptyTags.' => array(
+                'keepTags' => $tagList,
+                'treatNonBreakingSpaceAsEmpty' => $treatNonBreakingSpaceAsEmpty
+            ),
+        );
+
+        $result = $this->parseConfigAndCleanHtml($tsConfig, $content);
+        $this->assertEquals($expectedResult, $result);
+    }
+
+    /**
      * Calls HTMLparserConfig() and passes the generated config to the HTMLcleaner() method on the current subject.
      *
      * @param array $tsConfig The TypoScript that should be used to generate the HTML parser config.