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.

169 lines
3.8KB

  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2014 Carsten Brandt
  4. * @license https://github.com/cebe/markdown/blob/master/LICENSE
  5. * @link https://github.com/cebe/markdown#readme
  6. */
  7. namespace cebe\markdown\block;
  8. /**
  9. * Adds inline and block HTML support
  10. */
  11. trait HtmlTrait
  12. {
  13. /**
  14. * @var array HTML elements considered as inline elements.
  15. * @see http://www.w3.org/wiki/HTML/Elements#Text-level_semantics
  16. */
  17. protected $inlineHtmlElements = [
  18. 'a', 'abbr', 'acronym',
  19. 'b', 'basefont', 'bdo', 'big', 'br', 'button', 'blink',
  20. 'cite', 'code',
  21. 'del', 'dfn',
  22. 'em',
  23. 'font',
  24. 'i', 'img', 'ins', 'input', 'iframe',
  25. 'kbd',
  26. 'label', 'listing',
  27. 'map', 'mark',
  28. 'nobr',
  29. 'object',
  30. 'q',
  31. 'rp', 'rt', 'ruby',
  32. 's', 'samp', 'script', 'select', 'small', 'spacer', 'span', 'strong', 'sub', 'sup',
  33. 'tt', 'var',
  34. 'u',
  35. 'wbr',
  36. 'time',
  37. ];
  38. /**
  39. * @var array HTML elements known to be self-closing.
  40. */
  41. protected $selfClosingHtmlElements = [
  42. 'br', 'hr', 'img', 'input', 'nobr',
  43. ];
  44. /**
  45. * identify a line as the beginning of a HTML block.
  46. */
  47. protected function identifyHtml($line, $lines, $current)
  48. {
  49. if ($line[0] !== '<' || isset($line[1]) && $line[1] == ' ') {
  50. return false; // no html tag
  51. }
  52. if (strncmp($line, '<!--', 4) === 0) {
  53. return true; // a html comment
  54. }
  55. $gtPos = strpos($lines[$current], '>');
  56. $spacePos = strpos($lines[$current], ' ');
  57. if ($gtPos === false && $spacePos === false) {
  58. return false; // no html tag
  59. } elseif ($spacePos === false) {
  60. $tag = rtrim(substr($line, 1, $gtPos - 1), '/');
  61. } else {
  62. $tag = rtrim(substr($line, 1, min($gtPos, $spacePos) - 1), '/');
  63. }
  64. if (!ctype_alnum($tag) || in_array(strtolower($tag), $this->inlineHtmlElements)) {
  65. return false; // no html tag or inline html tag
  66. }
  67. return true;
  68. }
  69. /**
  70. * Consume lines for an HTML block
  71. */
  72. protected function consumeHtml($lines, $current)
  73. {
  74. $content = [];
  75. if (strncmp($lines[$current], '<!--', 4) === 0) { // html comment
  76. for ($i = $current, $count = count($lines); $i < $count; $i++) {
  77. $line = $lines[$i];
  78. $content[] = $line;
  79. if (strpos($line, '-->') !== false) {
  80. break;
  81. }
  82. }
  83. } else {
  84. $tag = rtrim(substr($lines[$current], 1, min(strpos($lines[$current], '>'), strpos($lines[$current] . ' ', ' ')) - 1), '/');
  85. $level = 0;
  86. if (in_array($tag, $this->selfClosingHtmlElements)) {
  87. $level--;
  88. }
  89. for ($i = $current, $count = count($lines); $i < $count; $i++) {
  90. $line = $lines[$i];
  91. $content[] = $line;
  92. $level += substr_count($line, "<$tag") - substr_count($line, "</$tag>") - substr_count($line, "/>");
  93. if ($level <= 0) {
  94. break;
  95. }
  96. }
  97. }
  98. $block = [
  99. 'html',
  100. 'content' => implode("\n", $content),
  101. ];
  102. return [$block, $i];
  103. }
  104. /**
  105. * Renders an HTML block
  106. */
  107. protected function renderHtml($block)
  108. {
  109. return $block['content'] . "\n";
  110. }
  111. /**
  112. * Parses an & or a html entity definition.
  113. * @marker &
  114. */
  115. protected function parseEntity($text)
  116. {
  117. // html entities e.g. &copy; &#169; &#x00A9;
  118. if (preg_match('/^&#?[\w\d]+;/', $text, $matches)) {
  119. return [['inlineHtml', $matches[0]], strlen($matches[0])];
  120. } else {
  121. return [['text', '&amp;'], 1];
  122. }
  123. }
  124. /**
  125. * renders a html entity.
  126. */
  127. protected function renderInlineHtml($block)
  128. {
  129. return $block[1];
  130. }
  131. /**
  132. * Parses inline HTML.
  133. * @marker <
  134. */
  135. protected function parseInlineHtml($text)
  136. {
  137. if (strpos($text, '>') !== false) {
  138. if (preg_match('~^</?(\w+\d?)( .*?)?>~s', $text, $matches)) {
  139. // HTML tags
  140. return [['inlineHtml', $matches[0]], strlen($matches[0])];
  141. } elseif (preg_match('~^<!--.*?-->~s', $text, $matches)) {
  142. // HTML comments
  143. return [['inlineHtml', $matches[0]], strlen($matches[0])];
  144. }
  145. }
  146. return [['text', '&lt;'], 1];
  147. }
  148. /**
  149. * Escapes `>` characters.
  150. * @marker >
  151. */
  152. protected function parseGt($text)
  153. {
  154. return [['text', '&gt;'], 1];
  155. }
  156. }