A fork of potaka/bbcode to handle future maintenance
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

171 lines
5.2KB

  1. <?php
  2. namespace Knockout\BbCode\Tokenizer;
  3. /**
  4. * Transform text into tokens for bbcode
  5. *
  6. * @author po_taka <angel.koilov@gmail.com>
  7. */
  8. class Tokenizer
  9. {
  10. private $rootTag;
  11. public function tokenize($text)
  12. {
  13. $this->rootTag = new Tag(null);
  14. $curentElement = 0;
  15. $textAsArray = preg_split('//u', $text);
  16. $textLenght = count($textAsArray);
  17. $bufferText = '';
  18. $currentTag = $this->rootTag;
  19. while ($curentElement < $textLenght) {
  20. $currentChar = $textAsArray[$curentElement];
  21. if ($currentChar === '[' && ($curentElement+1 < $textLenght) && $textAsArray[$curentElement+1] !== ']') {
  22. // get the close bracket
  23. $closeTagFound = false;
  24. // [tag=argumen]
  25. $argumentFound = false;
  26. $argumentValue = '';
  27. $tmpPosion = $curentElement;
  28. $tagText = '';
  29. $tmpPosion++;
  30. while ($tmpPosion < $textLenght) {
  31. if ($textAsArray[$tmpPosion] === ']') {
  32. $closeTagFound = true;
  33. break;
  34. // for the time being we support only 1 argument
  35. } elseif ($textAsArray[$tmpPosion] === '=' && $argumentFound === false) {
  36. $argumentFound = true;
  37. } else {
  38. if ($argumentFound) {
  39. $argumentValue .= $textAsArray[$tmpPosion];
  40. } else {
  41. $tagText .= $textAsArray[$tmpPosion];
  42. }
  43. }
  44. $tmpPosion++;
  45. }
  46. if (false === $closeTagFound) {
  47. $bufferText .= $currentChar . $tagText;
  48. $curentElement = $tmpPosion;
  49. continue;
  50. }
  51. $curentElement = $tmpPosion;
  52. if ($tagText[0] === '/') {
  53. $tagName = mb_strcut($tagText, 1);
  54. if ($currentTag->getType() === $tagName) {
  55. if ($bufferText !== '') {
  56. $tmpTag = new Tag(null);
  57. $tmpTag->setText($bufferText);
  58. $currentTag->addTag($tmpTag);
  59. }
  60. $currentTag = $currentTag->getParent();
  61. } else {
  62. $bufferText .= "[{$tagText}]";
  63. $curentElement = $tmpPosion + 1;
  64. continue;
  65. }
  66. } else {
  67. $tmpTag = new Tag(null);
  68. $tmpTag->setText($bufferText);
  69. $currentTag->addTag($tmpTag);
  70. $tmpTag = new Tag($tagText);
  71. if ($argumentFound) {
  72. $tmpTag->setArgumen($argumentValue);
  73. }
  74. $currentTag->addTag($tmpTag);
  75. $currentTag = $tmpTag;
  76. }
  77. $bufferText = '';
  78. } else {
  79. $bufferText .= $currentChar;
  80. }
  81. $curentElement++;
  82. }
  83. if ($bufferText) {
  84. $tag = new Tag(null);
  85. $tag->setText($bufferText);
  86. $currentTag->addTag($tag);
  87. }
  88. $this->handleNotClosedTags($currentTag);
  89. return $this->rootTag;
  90. }
  91. private function handleNotClosedTags(Tag $tag)
  92. {
  93. while ($tag->getParent() !== null) {
  94. $parent = $tag->getParent();
  95. $tagCode = "[{$tag->getType()}";
  96. if ($tag->getArgument()) {
  97. $tagCode .= "={$tag->getArgument()}";
  98. }
  99. $tagCode .= "]";
  100. $parent->removeTag($tag);
  101. // if parent last tag is text -> append text
  102. $parentTags = $parent->getTags();
  103. end($parentTags);
  104. $lastParentTag = current($parentTags);
  105. if ($lastParentTag->getType() === null) {
  106. $lastParentTag->setText("{$lastParentTag->getText()}{$tagCode}");
  107. } else {
  108. $curretntTagAsTextTag = new Tag(null);
  109. $curretntTagAsTextTag->setText($tagCode);
  110. $parent->addTag($curretntTagAsTextTag);
  111. }
  112. foreach ($tag->getTags() as $tagToMove) {
  113. $parent->addTag($tagToMove);
  114. }
  115. $tag = $tag->getParent();
  116. if ($tag !== null) {
  117. $this->mergeLastTextTags($tag);
  118. }
  119. }
  120. }
  121. /**
  122. * @param Tag $tag
  123. * @return boolean
  124. */
  125. private function mergeLastTextTags(Tag $tag)
  126. {
  127. $tags = $tag->getTags();
  128. if (count($tags) < 2) {
  129. return true;
  130. }
  131. end($tags);
  132. $lastTag = current($tags);
  133. if ($lastTag->getType() !== null) {
  134. return true;
  135. }
  136. prev($tags);
  137. $postLastTag = current($tags);
  138. if ($postLastTag->getType() !== null) {
  139. return true;
  140. }
  141. $postLastTag->setText("{$postLastTag->getText()}{$lastTag->getText()}");
  142. $tag->removeTag($lastTag);
  143. return true;
  144. }
  145. }