/BBDumper.class.php

https://github.com/Ralle/BBCode-Parser · PHP · 298 lines · 222 code · 27 blank · 49 comment · 36 complexity · 799180e39058dc88178e785e5ba47ccd MD5 · raw file

  1. <?php
  2. require_once __DIR__ . '/BBCode.class.php';
  3. require_once __DIR__ . '/BBCodeReplace.class.php';
  4. require_once __DIR__ . '/BBCodeDefault.class.php';
  5. require_once __DIR__ . '/BBCodeRoot.class.php';
  6. require_once __DIR__ . '/BBCodeCallback.class.php';
  7. /*
  8. The BBDumper is a class used to output a BBNode tree as HTML or text.
  9. A BBNode tree consists of a series of instances of BBText and BBTag inside of a BBRoot node. In order to output any node, the dumper needs handlers. A handler is an instance of the BBCode class.
  10. You will need to set the default handler for nodes that have no handler.
  11. You will also need to set the root handler to boostrap the dumping.
  12. The handlers each have a context type and a list of context types that are allowed inside of it. The root handler will contain the list of context types allowed in the root.
  13. */
  14. class BBDumper {
  15. // enable debugging to output what happens when the dumping occurs.
  16. public $debug = false;
  17. // contains the array of handlers with their tag name as key
  18. private $handlers = array();
  19. // the default handler handles BBText and unhandled BBTags
  20. private $defaultHandler = null;
  21. // the root handler as described above
  22. private $rootHandler = null;
  23. // set the default handler for this dumper
  24. public function setDefaultHandler(BBCode $handler)
  25. {
  26. // every handler needs a reference to their dumper
  27. $handler->dumper = $this;
  28. $this->defaultHandler = $handler;
  29. }
  30. // set the root handler for this dumper
  31. public function setRootHandler(BBCode $handler)
  32. {
  33. $handler->dumper = $this;
  34. $this->rootHandler = $handler;
  35. }
  36. // to add a handler to the dumper, you will use this function. Optionally you can change the tagName for which the handler will be added.
  37. // this could for example be used to add both a [b] and a [bold] tag with the same handler.
  38. public function addHandler(BBCode $handler, $otherName = '')
  39. {
  40. $handler->dumper = $this;
  41. $tagName = $otherName ? $otherName : $handler->tagName;
  42. $this->handlers[$tagName] = $handler;
  43. }
  44. // add multiple handlers as an array.
  45. public function addHandlers(array $handlers)
  46. {
  47. foreach ($handlers as $handler)
  48. {
  49. $this->addHandler($handler);
  50. }
  51. }
  52. // get the immediate handler for a BBNode.
  53. public function getImmediateHandler(BBNode $node)
  54. {
  55. // if we have a BBTag and a handler with that tag name, return it, otherwise return null.
  56. if ($node instanceof BBTag && isset($this->handlers[$node->tagName]))
  57. {
  58. return $this->handlers[$node->tagName];
  59. }
  60. return null;
  61. }
  62. // get the absolute handler for a BBNode. Here we check whether or not the handler is allowed in the context of the node. This is the one we will be using.
  63. public function getHandler(BBNode $node)
  64. {
  65. if ($node->handler)
  66. {
  67. return $node->handler;
  68. }
  69. $this->d('Get handler for ' . get_class($node) . ' with tag name: ' . ($node instanceof BBTag ? $node->tagName : 'Non-tag'));
  70. switch (get_class($node))
  71. {
  72. case 'BBRoot':
  73. $handler = $this->rootHandler;
  74. break;
  75. case 'BBTag':
  76. $parentHandler = $node->parent->handler;
  77. // get what types the parent can contain, skip over default handler
  78. $tempNode = $node->parent;
  79. while ($tempNode->handler == $this->defaultHandler) {
  80. $tempNode = $tempNode->parent;
  81. }
  82. $parentCanContain = $tempNode->handler->canContain;
  83. $iHandler = $this->getImmediateHandler($node);
  84. // check to see if the node has a handler and if the parent permits this context type
  85. if ($iHandler && $node->hasEndTag)
  86. {
  87. if (in_array($iHandler->type, $parentCanContain))
  88. {
  89. $handler = $iHandler;
  90. }
  91. else
  92. {
  93. // else use the default handler
  94. $handler = $this->defaultHandler;
  95. $this->d('The handler ' . $iHandler->tagName . ' is not allowed in this context.');
  96. }
  97. }
  98. else
  99. {
  100. // there exists no handler to this tag, use the default one
  101. $this->d('There is no handler for this tag.');
  102. $handler = $this->defaultHandler;
  103. }
  104. break;
  105. case 'BBText':
  106. // just use the parent's handler
  107. $handler = $node->parent->handler;
  108. break;
  109. default:
  110. // this case should never be used. In any case, just use the default
  111. $handler = $this->defaultHandler;
  112. break;
  113. }
  114. $node->handler = $handler;
  115. return $handler;
  116. }
  117. // this function is the whole purpose of the dumper. Depending on which kind of BBNode we wish to dump, we call different dump methods.
  118. public function dump(BBNode $node)
  119. {
  120. if (!$this->defaultHandler) {
  121. throw new Exception('Missing defaultHandler');
  122. }
  123. if (!$this->rootHandler) {
  124. throw new Exception('Missing rootHandler');
  125. }
  126. switch (get_class($node))
  127. {
  128. case 'BBRoot':
  129. return $this->dumpBBRoot($node);
  130. case 'BBTag':
  131. return $this->dumpBBTag($node);
  132. case 'BBEndTag':
  133. return $this->dumpBBEndTag($node);
  134. case 'BBText':
  135. return $this->dumpBBText($node);
  136. default:
  137. throw new Exception('Unknown BBNode subclass.');
  138. }
  139. }
  140. // dump the BBRoot
  141. public function dumpBBRoot(BBRoot $node)
  142. {
  143. $handler = $this->getHandler($node);
  144. return $handler->dump($node);
  145. }
  146. // dump the BBTag and escape the attributes if requested by the handler
  147. public function dumpBBTag(BBTag $node)
  148. {
  149. $handler = $this->getHandler($node);
  150. if ($handler->escapeAttributes)
  151. {
  152. $node->attributes = array_map('htmlspecialchars', $node->attributes);
  153. }
  154. return $handler->dump($node);
  155. }
  156. // dump an end tag. This will only occur if we have an orphan end tag
  157. public function dumpBBEndTag(BBEndTag $node)
  158. {
  159. return '[/' . $node->tagName . ']';
  160. }
  161. // dump BBText and escape it if requested
  162. public function dumpBBText(BBText $node)
  163. {
  164. $handler = $this->getHandler($node);
  165. $text = $node->text;
  166. // escape the text
  167. if ($handler->escapeText)
  168. {
  169. $text = htmlspecialchars($text);
  170. }
  171. // make linebreaks
  172. if ($handler->replaceNewlines)
  173. {
  174. $text = nl2br($text);
  175. }
  176. return $text;
  177. }
  178. // if the first child is a BBText, call ltrim on it.
  179. public function trimInsideLeft(BBNode $node)
  180. {
  181. $first = reset($node->children);
  182. if ($first !== false && $first instanceof BBText)
  183. {
  184. $first->text = ltrim($first->text);
  185. }
  186. }
  187. // if the last child is a BBText, call rtrim on it.
  188. public function trimInsideRight(BBNode $node)
  189. {
  190. $last = end($node->children);
  191. if ($last !== false && $last instanceof BBText)
  192. {
  193. $last->text = rtrim($last->text);
  194. }
  195. }
  196. // if the first child is a BBText, remove the first linebreak in it.
  197. public function removeFirstLinebreak(BBNode $node)
  198. {
  199. $first = reset($node->children);
  200. if ($first !== false && $first instanceof BBText)
  201. {
  202. $first->text = preg_replace('#^(\r\n|\r|\n)#', '', $first->text);
  203. }
  204. }
  205. // if the last child is a BBText, remove the last linebreak in it.
  206. public function removeLastLinebreak(BBNode $node)
  207. {
  208. $last = end($node->children);
  209. if ($last !== false && $last instanceof BBText)
  210. {
  211. $last->text = preg_replace('#(\r\n|\r|\n)$#', '', $last->text);
  212. }
  213. }
  214. // dump the children of a BBNode. Only BBTag and BBRoot can have children. All the different trim functions get called
  215. public function dumpChildren(BBNode $node)
  216. {
  217. $ret = '';
  218. if ($node->handler->trimInsideLeft)
  219. {
  220. $this->trimInsideLeft($node);
  221. }
  222. if ($node->handler->removeFirstLinebreak)
  223. {
  224. $this->removeFirstLinebreak($node);
  225. }
  226. if ($node->handler->removeLastLinebreak)
  227. {
  228. $this->removeLastLinebreak($node);
  229. }
  230. if ($node->handler->trimInsideRight)
  231. {
  232. $this->trimInsideRight($node);
  233. }
  234. // loop through all the children and dump them
  235. for ($i = 0; $i < count($node->children); $i++)
  236. {
  237. $child = $node->children[$i];
  238. // get the handler
  239. $childHandler = $this->getHandler($child);
  240. // get the next sibling
  241. $nextSibling = array_key_exists($i+1, $node->children) ? $node->children[$i+1] : null;
  242. // get the next sibling's handler
  243. $nextSiblingHandler = $nextSibling !== null ? $this->getHandler($nextSibling) : null;
  244. // if the child is a text and the next sibling is a tag which has removeLinebreaksBefore, remove the last linebreak of the child.
  245. if ($child instanceof BBText && $nextSibling instanceof BBTag && $nextSiblingHandler->removeLinebreakBefore)
  246. {
  247. // remove the childs last linebreak.
  248. $child->text = preg_replace('#(\r\n|\r|\n)$#', '', $child->text);
  249. }
  250. // if the child is a tag and the next sibling is a text which has removeLinebreaksAfter, remove the first linebreak of the next sibling.
  251. else if ($child instanceof BBTag && $nextSibling instanceof BBText && $childHandler->removeLinebreakAfter)
  252. {
  253. // remove the next siblings first linebreak.
  254. $nextSibling->text = preg_replace('#^(\r\n|\r|\n)#', '', $nextSibling->text);
  255. }
  256. // dump the child
  257. $ret .= $this->dump($child);
  258. }
  259. return $ret;
  260. }
  261. // the debug function. Give it a string and it gets printed if debugging is enabled.
  262. public function d($m)
  263. {
  264. if ($this->debug)
  265. {
  266. echo $m, "\r\n";
  267. }
  268. }
  269. }
  270. ?>