Added support for table bbcode
authorAlexander Ebert <ebert@woltlab.com>
Tue, 12 Jul 2016 16:49:15 +0000 (18:49 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 12 Jul 2016 16:49:23 +0000 (18:49 +0200)
com.woltlab.wcf/bbcode.xml
wcfsetup/install/files/lib/system/bbcode/TableBBCode.class.php
wcfsetup/install/files/lib/system/bbcode/TdBBCode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/TrBBCode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/html/metacode/converter/ListMetacodeConverter.class.php
wcfsetup/install/files/lib/system/html/metacode/converter/TableMetacodeConverter.class.php [new file with mode: 0644]

index 90931ec09fcb23507c5bdd2d59a5431a33b37417..a6e58cd98de38e25842ec171dcffa35ac58279ec 100644 (file)
                        </attributes>
                        <isBlockElement>1</isBlockElement>
                </bbcode>
+               
                <bbcode name="table">
                        <classname><![CDATA[wcf\system\bbcode\TableBBCode]]></classname>
                        <isBlockElement>1</isBlockElement>
                </bbcode>
+               <bbcode name="tr">
+                       <classname><![CDATA[wcf\system\bbcode\TrBBCode]]></classname>
+                       <isBlockElement>1</isBlockElement>
+               </bbcode>
+               <bbcode name="td">
+                       <classname><![CDATA[wcf\system\bbcode\TdBBcode]]></classname>
+                       <isBlockElement>1</isBlockElement>
+               </bbcode>
+               
                <bbcode name="media">
                        <classname><![CDATA[wcf\system\bbcode\MediaBBCode]]></classname>
                        <sourcecode>1</sourcecode>
index 60a0b596889b835dcb41c8d753cbba0ef56ce3ac..b283b8fe5681addf850fcdcf25a0b2380f715aff 100644 (file)
@@ -1,11 +1,10 @@
 <?php
 namespace wcf\system\bbcode;
-use wcf\system\Regex;
 
 /**
  * Parses the [table] bbcode tag.
  * 
- * @author     Tim Duesterhus, Marcel Werk
+ * @author     Alexander Ebert
  * @copyright  2001-2016 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
@@ -15,75 +14,7 @@ class TableBBCode extends AbstractBBCode {
         * @inheritDoc
         */
        public function getParsedTag(array $openingTag, $content, array $closingTag, BBCodeParser $parser) {
-               if ($parser->getOutputType() == 'text/html') {
-                       $parsedContent = Regex::compile('(?:\s|<br>)*(\[tr\].*\[/tr\])(?:\s|<br>)*', Regex::CASE_INSENSITIVE | Regex::DOT_ALL)->replace($content, '\\1');
-                       
-                       // check syntax
-                       $regex = new Regex('\[/?t[rd]\]', Regex::CASE_INSENSITIVE);
-                       if ($regex->match($parsedContent, true)) {
-                               $matches = $regex->getMatches();
-                               
-                               $openTags = [];
-                               $openTDs = 0;
-                               $firstRowTDs = 0;
-                               
-                               // parse tags
-                               foreach ($matches[0] as $match) {
-                                       switch ($match) {
-                                               case '[td]':
-                                                       if (end($openTags) !== '[tr]') return '';
-                                                       $openTags[] = $match;
-                                                       $openTDs++;
-                                               break;
-                                               case '[/td]':
-                                                       if (end($openTags) !== '[td]') return '';
-                                                       array_pop($openTags);
-                                               break;
-                                               case '[tr]':
-                                                       if (!empty($openTags)) return '';
-                                                       $openTags[] = $match;
-                                               break;
-                                               case '[/tr]':
-                                                       if (end($openTags) !== '[tr]') return '';
-                                                       
-                                                       array_pop($openTags);
-                                                       
-                                                       // check that every row has got the same number of tds
-                                                       if ($firstRowTDs === 0) $firstRowTDs = $openTDs;
-                                                       if ($openTDs !== $firstRowTDs) return '';
-                                                       
-                                                       $openTDs = 0;
-                                               break;
-                                       }
-                               }
-                               
-                               if (!empty($openTags)) return '';
-                       }
-                       else {
-                               return '';
-                       }
-                       
-                       // tr
-                       $parsedContent = Regex::compile('\[tr\](?:\s|<br>)*', Regex::CASE_INSENSITIVE)->replace($parsedContent, '<tr>');
-                       // td
-                       $parsedContent = str_ireplace('[td]', '<td>', $parsedContent);
-                       // /td
-                       $parsedContent = Regex::compile('\[/td\](?:\s|<br>)*', Regex::CASE_INSENSITIVE)->replace($parsedContent, '</td>');
-                       // /tr
-                       $parsedContent = Regex::compile('\[/tr\](?:\s|<br>)*', Regex::CASE_INSENSITIVE)->replace($parsedContent, '</tr>');
-                       
-                       return '<div class="container bbcodeTable"><table class="table responsiveTable"><tbody>'.$parsedContent.'</tbody></table></div>';
-               }
-               else if ($parser->getOutputType() == 'text/simplified-html') {
-                       // remove table tags
-                       $content = str_ireplace('[td]', '* ', $content);
-                       $content = str_ireplace('[/td]', ' ', $content);
-                       $content = str_ireplace('[tr]', '', $content);
-                       $content = str_ireplace('[/tr]', '', $content);
-                       
-                       return $content;
-               }
-               
-               return '';
+               // fake output
+               return '[table]' . $content . '[/table]';
        }
 }
diff --git a/wcfsetup/install/files/lib/system/bbcode/TdBBCode.class.php b/wcfsetup/install/files/lib/system/bbcode/TdBBCode.class.php
new file mode 100644 (file)
index 0000000..3145cbd
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\system\bbcode;
+
+/**
+ * Parses the [td] bbcode tag.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Bbcode
+ */
+class TdBBCode extends AbstractBBCode {
+       /**
+        * @inheritDoc
+        */
+       public function getParsedTag(array $openingTag, $content, array $closingTag, BBCodeParser $parser) {
+               // ignore these tags as they occur outside of a table
+               return '[td]' . $content . '[/td]';
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/TrBBCode.class.php b/wcfsetup/install/files/lib/system/bbcode/TrBBCode.class.php
new file mode 100644 (file)
index 0000000..6877256
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\system\bbcode;
+
+/**
+ * Parses the [tr] bbcode tag.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Bbcode
+ */
+class TrBBCode extends AbstractBBCode {
+       /**
+        * @inheritDoc
+        */
+       public function getParsedTag(array $openingTag, $content, array $closingTag, BBCodeParser $parser) {
+               // ignore these tags as they occur outside of a table
+               return '[tr]' . $content . '[/tr]';
+       }
+}
index 56a30a4f98e2839e479291fd8e687fc664b80392..7ae0263554ea83f0c34c894fbaff10d2dddefa92 100644 (file)
@@ -170,13 +170,6 @@ class ListMetacodeConverter extends AbstractMetacodeConverter {
                return $element;
        }
        
-       /**
-        * @inheritDoc
-        */
-       public function validateAttributes(array $attributes) {
-               return true;
-       }
-       
        /**
         * Returns true if provided node is within another list, prevents issues
         * with nested lists handled in the wrong order.
diff --git a/wcfsetup/install/files/lib/system/html/metacode/converter/TableMetacodeConverter.class.php b/wcfsetup/install/files/lib/system/html/metacode/converter/TableMetacodeConverter.class.php
new file mode 100644 (file)
index 0000000..05b8cf8
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+namespace wcf\system\html\metacode\converter;
+use wcf\util\DOMUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Converts table bbcodes into `<table>`, `<tr>` and `<td>`.
+ * 
+ * @author      Alexander Ebert
+ * @copyright   2001-2016 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package     WoltLabSuite\Core\System\Html\Metacode\Converter
+ * @since       3.0
+ */
+class TableMetacodeConverter extends AbstractMetacodeConverter {
+       /**
+        * @inheritDoc
+        */
+       public function convert(\DOMDocumentFragment $fragment, array $attributes) {
+               $element = $fragment->ownerDocument->createElement('table');
+               $tbody = $fragment->ownerDocument->createElement('tbody');
+               $element->appendChild($tbody);
+               $tbody->appendChild($fragment);
+               
+               // get all table rows
+               $rows = [];
+               $nodes = $tbody->getElementsByTagName('woltlab-metacode');
+               /** @var \DOMElement $node */
+               foreach ($nodes as $node) {
+                       if ($node->getAttribute('data-name') === 'tr' && !$this->isInsideTable($node)) {
+                               $rows[] = $node;
+                       }
+               }
+               
+               // fix markup for table rows
+               /** @var \DOMElement $row */
+               foreach ($rows as $row) {
+                       if ($row->parentNode !== $tbody) {
+                               $parent = DOMUtil::getParentBefore($row, $tbody);
+                               $tbody->insertBefore($row, $parent);
+                       }
+                       
+                       DOMUtil::replaceElement($row, $row->ownerDocument->createElement('tr'));
+               }
+               
+               // drop everything except for <tr> elements
+               $childNodes = DOMUtil::getChildNodes($tbody);
+               foreach ($childNodes as $childNode) {
+                       if ($childNode->nodeType === XML_ELEMENT_NODE && $childNode->nodeName === 'tr') {
+                               continue;
+                       }
+                       
+                       DOMUtil::removeNode($childNode);
+               }
+               
+               // get columns for each tr
+               /** @var \DOMElement $childNode */
+               foreach ($tbody->childNodes as $childNode) {
+                       $this->handleRow($childNode);
+               }
+               
+               return $element;
+       }
+       
+       protected function handleRow(\DOMElement $row) {
+               // get all table columns
+               $cols = [];
+               $nodes = $row->getElementsByTagName('woltlab-metacode');
+               /** @var \DOMElement $node */
+               foreach ($nodes as $node) {
+                       if ($node->getAttribute('data-name') === 'td' && !$this->isInsideTable($node)) {
+                               $cols[] = $node;
+                       }
+               }
+               
+               // move tds
+               /** @var \DOMElement $col */
+               foreach ($cols as $col) {
+                       if (false && $col->parentNode !== $row) {
+                               $parent = DOMUtil::getParentBefore($col, $row);
+                               $row->insertBefore($col, $parent);
+                       }
+                       
+                       DOMUtil::replaceElement($col, $row->ownerDocument->createElement('td'));
+               }
+               
+               // drop everything except for <td> elements and removing
+               // <p> inside columns
+               $childNodes = DOMUtil::getChildNodes($row);
+               /** @var \DOMElement $childNode */
+               foreach ($childNodes as $childNode) {
+                       if ($childNode->nodeType === XML_ELEMENT_NODE && $childNode->nodeName === 'td') {
+                               // convert <p>...</p> to ...<br><br>
+                               $nodes = DOMUtil::getChildNodes($childNode);
+                               /** @var \DOMElement $node */
+                               foreach ($nodes as $node) {
+                                       if ($node->nodeName === 'p') {
+                                               for ($i = 0; $i < 2; $i++) {
+                                                       DOMUtil::insertAfter($node->ownerDocument->createElement('br'), $node);
+                                               }
+                                               
+                                               DOMUtil::removeNode($node, true);
+                                       }
+                               }
+                               
+                               // removing leading whitespace / <br>
+                               $nodes = DOMUtil::getChildNodes($childNode);
+                               foreach ($nodes as $node) {
+                                       if ($node->nodeType === XML_TEXT_NODE) {
+                                               if (StringUtil::trim($node->textContent) !== '') {
+                                                       break;
+                                               }
+                                       }
+                                       else if ($node->nodeType === XML_ELEMENT_NODE && $node->nodeName !== 'br') {
+                                               break;
+                                       }
+                                       
+                                       DOMUtil::removeNode($node);
+                               }
+                               
+                               // removing trailing whitespace / <br>
+                               $nodes = DOMUtil::getChildNodes($childNode);
+                               $i = count($nodes);
+                               while ($i--) {
+                                       $node = $nodes[$i];
+                                       if ($node->nodeType === XML_TEXT_NODE) {
+                                               if (StringUtil::trim($node->textContent) !== '') {
+                                                       break;
+                                               }
+                                       }
+                                       else if ($node->nodeType === XML_ELEMENT_NODE && $node->nodeName !== 'br') {
+                                               break;
+                                       }
+                                       
+                                       DOMUtil::removeNode($node);
+                               }
+                               
+                               continue;
+                       }
+                       
+                       DOMUtil::removeNode($childNode);
+               }
+       }
+       
+       /**
+        * Returns true if provided node is within another table, prevents issues
+        * with nested tables handled in the wrong order.
+        *
+        * @param       \DOMNode        $node           target node
+        * @return      boolean         true if provided node is within another table
+        */
+       protected function isInsideTable(\DOMNode $node) {
+               /** @var \DOMElement $parent */
+               $parent = $node;
+               while ($parent = $parent->parentNode) {
+                       if ($parent->nodeName === 'woltlab-metacode' && $parent->getAttribute('data-name') === 'table') {
+                               return true;
+                       }
+               }
+               
+               return false;
+       }
+}