ApiMarkdown.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\apidoc\helpers;
  8. use cebe\markdown\GithubMarkdown;
  9. use DomainException;
  10. use Highlight\Highlighter;
  11. use yii\apidoc\models\TypeDoc;
  12. use yii\apidoc\renderers\BaseRenderer;
  13. use yii\helpers\Html;
  14. use yii\helpers\Inflector;
  15. use yii\helpers\Markdown;
  16. /**
  17. * A Markdown helper with support for class reference links.
  18. *
  19. * @author Carsten Brandt <mail@cebe.cc>
  20. * @since 2.0
  21. */
  22. class ApiMarkdown extends GithubMarkdown
  23. {
  24. use ApiMarkdownTrait;
  25. /**
  26. * @var BaseRenderer
  27. */
  28. public static $renderer;
  29. /**
  30. * @var array translation for guide block types
  31. * @since 2.0.5
  32. */
  33. public static $blockTranslations = [];
  34. protected $renderingContext;
  35. protected $headings = [];
  36. /**
  37. * @return array the headlines of this document
  38. * @since 2.0.5
  39. */
  40. public function getHeadings()
  41. {
  42. return $this->headings;
  43. }
  44. /**
  45. * @inheritDoc
  46. */
  47. protected function prepare()
  48. {
  49. parent::prepare();
  50. $this->headings = [];
  51. }
  52. public function parse($text)
  53. {
  54. $markup = parent::parse($text);
  55. $markup = $this->applyToc($markup);
  56. return $markup;
  57. }
  58. /**
  59. * @since 2.0.5
  60. */
  61. protected function applyToc($content)
  62. {
  63. // generate TOC
  64. if (!empty($this->headings)) {
  65. $toc = [];
  66. foreach ($this->headings as $heading)
  67. $toc[] = '<li>' . Html::a(strip_tags($heading['title']), '#' . $heading['id']) . '</li>';
  68. $toc = '<div class="toc"><ol>' . implode("\n", $toc) . "</ol></div>\n";
  69. if (strpos($content, '</h1>') !== false)
  70. $content = str_replace('</h1>', "</h1>\n" . $toc, $content);
  71. else
  72. $content = $toc . $content;
  73. }
  74. return $content;
  75. }
  76. /**
  77. * @var Highlighter
  78. */
  79. private static $highlighter;
  80. /**
  81. * @inheritdoc
  82. */
  83. protected function renderCode($block)
  84. {
  85. if (self::$highlighter === null) {
  86. self::$highlighter = new Highlighter();
  87. self::$highlighter->setAutodetectLanguages([
  88. 'apache', 'nginx',
  89. 'bash', 'dockerfile', 'http',
  90. 'css', 'less', 'scss',
  91. 'javascript', 'json', 'markdown',
  92. 'php', 'sql', 'twig', 'xml',
  93. ]);
  94. }
  95. try {
  96. if (isset($block['language'])) {
  97. $result = self::$highlighter->highlight($block['language'], $block['content'] . "\n");
  98. return "<pre><code class=\"hljs {$result->language} language-{$block['language']}\">{$result->value}</code></pre>\n";
  99. } else {
  100. $result = self::$highlighter->highlightAuto($block['content'] . "\n");
  101. return "<pre><code class=\"hljs {$result->language}\">{$result->value}</code></pre>\n";
  102. }
  103. } catch (DomainException $e) {
  104. echo $e;
  105. return parent::renderCode($block);
  106. }
  107. }
  108. /**
  109. * Highlights code
  110. *
  111. * @param string $code code to highlight
  112. * @param string $language language of the code to highlight
  113. * @return string HTML of highlighted code
  114. * @deprecated since 2.0.5 this method is not used anymore, highlight.php is used for highlighting
  115. */
  116. public static function highlight($code, $language)
  117. {
  118. if ($language !== 'php') {
  119. return htmlspecialchars($code, ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8');
  120. }
  121. if (strncmp($code, '<?php', 5) === 0) {
  122. $text = @highlight_string(trim($code), true);
  123. } else {
  124. $text = highlight_string("<?php ".trim($code), true);
  125. $text = str_replace('&lt;?php', '', $text);
  126. if (($pos = strpos($text, '&nbsp;')) !== false) {
  127. $text = substr($text, 0, $pos) . substr($text, $pos + 6);
  128. }
  129. }
  130. // remove <code><span style="color: #000000">\n and </span>tags added by php
  131. $text = substr(trim($text), 36, -16);
  132. return $text;
  133. }
  134. /**
  135. * @inheritDoc
  136. */
  137. protected function renderHeadline($block)
  138. {
  139. $content = $this->renderAbsy($block['content']);
  140. if (preg_match('~<span id="(.*?)"></span>~', $content, $matches)) {
  141. $hash = $matches[1];
  142. $content = preg_replace('~<span id=".*?"></span>~', '', $content);
  143. } else {
  144. $hash = Inflector::slug(strip_tags($content));
  145. }
  146. $hashLink = "<span id=\"$hash\"></span><a href=\"#$hash\" class=\"hashlink\">&para;</a>";
  147. if ($block['level'] == 2) {
  148. $this->headings[] = [
  149. 'title' => trim($content),
  150. 'id' => $hash,
  151. ];
  152. } elseif ($block['level'] > 2) {
  153. if (end($this->headings)) {
  154. $this->headings[key($this->headings)]['sub'][] = [
  155. 'title' => trim($content),
  156. 'id' => $hash,
  157. ];
  158. }
  159. }
  160. $tag = 'h' . $block['level'];
  161. return "<$tag>$content $hashLink</$tag>";
  162. }
  163. /**
  164. * @inheritdoc
  165. */
  166. protected function renderLink($block)
  167. {
  168. $result = parent::renderLink($block);
  169. // add special syntax for linking to the guide
  170. $result = preg_replace_callback('/href="guide:([A-z0-9-.#]+)"/i', function($match) {
  171. return 'href="' . static::$renderer->generateGuideUrl($match[1]) . '"';
  172. }, $result, 1);
  173. return $result;
  174. }
  175. /**
  176. * @inheritdoc
  177. * @since 2.0.5
  178. */
  179. protected function translateBlockType($type)
  180. {
  181. $key = ucfirst($type) . ':';
  182. if (isset(static::$blockTranslations[$key])) {
  183. $translation = static::$blockTranslations[$key];
  184. } else {
  185. $translation = $key;
  186. }
  187. return "$translation ";
  188. }
  189. /**
  190. * Converts markdown into HTML
  191. *
  192. * @param string $content
  193. * @param TypeDoc $context
  194. * @param bool $paragraph
  195. * @return string
  196. */
  197. public static function process($content, $context = null, $paragraph = false)
  198. {
  199. if (!isset(Markdown::$flavors['api'])) {
  200. Markdown::$flavors['api'] = new static;
  201. }
  202. if (is_string($context)) {
  203. $context = static::$renderer->apiContext->getType($context);
  204. }
  205. Markdown::$flavors['api']->renderingContext = $context;
  206. if ($paragraph) {
  207. return Markdown::processParagraph($content, 'api');
  208. } else {
  209. return Markdown::process($content, 'api');
  210. }
  211. }
  212. /**
  213. * Add bootstrap classes to tables.
  214. * @inheritdoc
  215. */
  216. public function renderTable($block)
  217. {
  218. return str_replace('<table>', '<table class="table table-bordered table-striped">', parent::renderTable($block));
  219. }
  220. }