最新服务器上的版本,以后用这个
wangzhenxin
2023-11-19 bc164b8bdbfbdf1d8229a5ced6b08d7cb8db7361
commit | author | age
2207d6 1 A super fast, highly extensible markdown parser for PHP
W 2 =======================================================
3
4 [![Latest Stable Version](https://poser.pugx.org/cebe/markdown/v/stable.png)](https://packagist.org/packages/cebe/markdown)
5 [![Total Downloads](https://poser.pugx.org/cebe/markdown/downloads.png)](https://packagist.org/packages/cebe/markdown)
6 [![Build Status](https://travis-ci.org/cebe/markdown.svg?branch=master)](http://travis-ci.org/cebe/markdown)
7 [![Code Coverage](https://scrutinizer-ci.com/g/cebe/markdown/badges/coverage.png?s=db6af342d55bea649307ef311fbd536abb9bab76)](https://scrutinizer-ci.com/g/cebe/markdown/)
8 [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/cebe/markdown/badges/quality-score.png?s=17448ca4d140429fd687c58ff747baeb6568d528)](https://scrutinizer-ci.com/g/cebe/markdown/)
9
10 What is this? <a name="what"></a>
11 -------------
12
13 A set of [PHP][] classes, each representing a [Markdown][] flavor, and a command line tool
14 for converting markdown files to HTML files.
15
16 The implementation focus is to be **fast** (see [benchmark][]) and **extensible**.
17 Parsing Markdown to HTML is as simple as calling a single method (see [Usage](#usage)) providing a solid implementation
18 that gives most expected results even in non-trivial edge cases.
19
20 Extending the Markdown language with new elements is as simple as adding a new method to the class that converts the
21 markdown text to the expected output in HTML. This is possible without dealing with complex and error prone regular expressions.
22 It is also possible to hook into the markdown structure and add elements or read meta information using the internal representation
23 of the Markdown text as an abstract syntax tree (see [Extending the language](#extend)).
24
25 Currently the following markdown flavors are supported:
26
27 - **Traditional Markdown** according to <http://daringfireball.net/projects/markdown/syntax> ([try it!](http://markdown.cebe.cc/try?flavor=default)).
28 - **Github flavored Markdown** according to <https://help.github.com/articles/github-flavored-markdown> ([try it!](http://markdown.cebe.cc/try?flavor=gfm)).
29 - **Markdown Extra** according to <http://michelf.ca/projects/php-markdown/extra/> (currently not fully supported WIP see [#25][], [try it!](http://markdown.cebe.cc/try?flavor=extra))
30 - Any mixed Markdown flavor you like because of its highly extensible structure (See documentation below).
31
32 Future plans are to support:
33
34 - Smarty Pants <http://daringfireball.net/projects/smartypants/>
35 - ... (Feel free to [suggest](https://github.com/cebe/markdown/issues/new) further additions!)
36
37 [PHP]: http://php.net/ "PHP is a popular general-purpose scripting language that is especially suited to web development."
38 [Markdown]: http://en.wikipedia.org/wiki/Markdown "Markdown on Wikipedia"
39 [#25]: https://github.com/cebe/markdown/issues/25 "issue #25"
40 [benchmark]: https://github.com/kzykhys/Markbench#readme "kzykhys/Markbench on github"
41
42 ### Who is using it?
43
44 - It powers the [API-docs and the definitive guide](http://www.yiiframework.com/doc-2.0/) for the [Yii Framework][] [2.0](https://github.com/yiisoft/yii2).
45
46 [Yii Framework]: http://www.yiiframework.com/ "The Yii PHP Framework"
47
48
49 Installation <a name="installation"></a>
50 ------------
51
52 [PHP 5.4 or higher](http://www.php.net/downloads.php) is required to use it.
53 It will also run on facebook's [hhvm](http://hhvm.com/).
54
55 The library uses PHPDoc annotations to determine the markdown elements that should be parsed.
56 So in case you are using PHP `opcache`, make sure 
57 [it does not strip comments](http://php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments).
58
59 Installation is recommended to be done via [composer][] by running:
60
61     composer require cebe/markdown "~1.2.0"
62
63 Alternatively you can add the following to the `require` section in your `composer.json` manually:
64
65 ```json
66 "cebe/markdown": "~1.2.0"
67 ```
68
69 Run `composer update` afterwards.
70
71 [composer]: https://getcomposer.org/ "The PHP package manager"
72
73 > Note: If you have configured PHP with opcache you need to enable the
74 > [opcache.save_comments](http://php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments) option because inline element parsing relies on PHPdoc annotations to find declared elements.
75
76 Usage <a name="usage"></a>
77 -----
78
79 ### In your PHP project
80
81 To parse your markdown you need only two lines of code. The first one is to choose the markdown flavor as
82 one of the following:
83
84 - Traditional Markdown: `$parser = new \cebe\markdown\Markdown();`
85 - Github Flavored Markdown: `$parser = new \cebe\markdown\GithubMarkdown();`
86 - Markdown Extra: `$parser = new \cebe\markdown\MarkdownExtra();`
87
88 The next step is to call the `parse()`-method for parsing the text using the full markdown language
89 or calling the `parseParagraph()`-method to parse only inline elements.
90
91 Here are some examples:
92
93 ```php
94 // traditional markdown and parse full text
95 $parser = new \cebe\markdown\Markdown();
96 echo $parser->parse($markdown);
97
98 // use github markdown
99 $parser = new \cebe\markdown\GithubMarkdown();
100 echo $parser->parse($markdown);
101
102 // use markdown extra
103 $parser = new \cebe\markdown\MarkdownExtra();
104 echo $parser->parse($markdown);
105
106 // parse only inline elements (useful for one-line descriptions)
107 $parser = new \cebe\markdown\GithubMarkdown();
108 echo $parser->parseParagraph($markdown);
109 ```
110
111 You may optionally set one of the following options on the parser object:
112
113 For all Markdown Flavors:
114
115 - `$parser->html5 = true` to enable HTML5 output instead of HTML4.
116 - `$parser->keepListStartNumber = true` to enable keeping the numbers of ordered lists as specified in the markdown.
117   The default behavior is to always start from 1 and increment by one regardless of the number in markdown.
118
119 For GithubMarkdown:
120
121 - `$parser->enableNewlines = true` to convert all newlines to `<br/>`-tags. By default only newlines with two preceding spaces are converted to `<br/>`-tags. 
122
123 It is recommended to use UTF-8 encoding for the input strings. Other encodings may work, but are currently untested.
124
125 ### The command line script
126
127 You can use it to render this readme:
128
129     bin/markdown README.md > README.html
130
131 Using github flavored markdown:
132
133     bin/markdown --flavor=gfm README.md > README.html
134
135 or convert the original markdown description to html using the unix pipe:
136
137     curl http://daringfireball.net/projects/markdown/syntax.text | bin/markdown > md.html
138
139 Here is the full Help output you will see when running `bin/markdown --help`:
140
141     PHP Markdown to HTML converter
142     ------------------------------
143     
144     by Carsten Brandt <mail@cebe.cc>
145     
146     Usage:
147         bin/markdown [--flavor=<flavor>] [--full] [file.md]
148     
149         --flavor  specifies the markdown flavor to use. If omitted the original markdown by John Gruber [1] will be used.
150                   Available flavors:
151     
152                   gfm   - Github flavored markdown [2]
153                   extra - Markdown Extra [3]
154
155         --full    ouput a full HTML page with head and body. If not given, only the parsed markdown will be output.
156
157         --help    shows this usage information.
158
159         If no file is specified input will be read from STDIN.
160
161     Examples:
162
163         Render a file with original markdown:
164
165             bin/markdown README.md > README.html
166
167         Render a file using gihtub flavored markdown:
168
169             bin/markdown --flavor=gfm README.md > README.html
170
171         Convert the original markdown description to html using STDIN:
172
173             curl http://daringfireball.net/projects/markdown/syntax.text | bin/markdown > md.html
174
175     
176     [1] http://daringfireball.net/projects/markdown/syntax
177     [2] https://help.github.com/articles/github-flavored-markdown
178     [3] http://michelf.ca/projects/php-markdown/extra/
179
180
181 Extensions
182 ----------
183
184 Here are some extensions to this library:
185
186 - [Bogardo/markdown-codepen](https://github.com/Bogardo/markdown-codepen) - shortcode to embed codepens from http://codepen.io/ in markdown.
187 - [kartik-v/yii2-markdown](https://github.com/kartik-v/yii2-markdown) - Advanced Markdown editing and conversion utilities for Yii Framework 2.0.
188 - [cebe/markdown-latex](https://github.com/cebe/markdown-latex) - Convert Markdown to LaTeX and PDF
189 - [softark/creole](https://github.com/softark/creole) - A creole markup parser
190 - [hyn/frontmatter](https://github.com/hyn/frontmatter) - Frontmatter Metadata Support (JSON, TOML, YAML)
191 - ... [add yours!](https://github.com/cebe/markdown/edit/master/README.md#L186)
192
193
194 Extending the language <a name="extend"></a>
195 ----------------------
196
197 Markdown consists of two types of language elements, I'll call them block and inline elements simlar to what you have in
198 HTML with `<div>` and `<span>`. Block elements are normally spreads over several lines and are separated by blank lines.
199 The most basic block element is a paragraph (`<p>`).
200 Inline elements are elements that are added inside of block elements i.e. inside of text.
201
202 This markdown parser allows you to extend the markdown language by changing existing elements behavior and also adding
203 new block and inline elements. You do this by extending from the parser class and adding/overriding class methods and
204 properties. For the different element types there are different ways to extend them as you will see in the following sections.
205
206 ### Adding block elements
207
208 The markdown is parsed line by line to identify each non-empty line as one of the block element types.
209 To identify a line as the beginning of a block element it calls all protected class methods who's name begins with `identify`.
210 An identify function returns true if it has identified the block element it is responsible for or false if not.
211 In the following example we will implement support for [fenced code blocks][] which are part of the github flavored markdown.
212
213 [fenced code blocks]: https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks
214                       "Fenced code block feature of github flavored markdown"
215
216 ```php
217 <?php
218
219 class MyMarkdown extends \cebe\markdown\Markdown
220 {
221     protected function identifyFencedCode($line, $lines, $current)
222     {
223         // if a line starts with at least 3 backticks it is identified as a fenced code block
224         if (strncmp($line, '```', 3) === 0) {
225             return true;
226         }
227         return false;
228     }
229
230     // ...
231 }
232 ```
233
234 In the above, `$line` is a string containing the content of the current line and is equal to `$lines[$current]`.
235 You may use `$lines` and `$current` to check other lines than the current line. In most cases you can ignore these parameters.
236
237 Parsing of a block element is done in two steps:
238
239 1. **Consuming** all the lines belonging to it. In most cases this is iterating over the lines starting from the identified
240    line until a blank line occurs. This step is implemented by a method named `consume{blockName}()` where `{blockName}`
241    is the same name as used for the identify function above. The consume method also takes the lines array
242    and the number of the current line. It will return two arguments: an array representing the block element in the abstract syntax tree
243    of the markdown document and the line number to parse next. In the abstract syntax array the first element refers to the name of
244    the element, all other array elements can be freely defined by yourself.
245    In our example we will implement it like this:
246
247    ```php
248     protected function consumeFencedCode($lines, $current)
249     {
250         // create block array
251         $block = [
252             'fencedCode',
253             'content' => [],
254         ];
255         $line = rtrim($lines[$current]);
256
257         // detect language and fence length (can be more than 3 backticks)
258         $fence = substr($line, 0, $pos = strrpos($line, '`') + 1);
259         $language = substr($line, $pos);
260         if (!empty($language)) {
261             $block['language'] = $language;
262         }
263
264         // consume all lines until ```
265         for($i = $current + 1, $count = count($lines); $i < $count; $i++) {
266             if (rtrim($line = $lines[$i]) !== $fence) {
267                 $block['content'][] = $line;
268             } else {
269                 // stop consuming when code block is over
270                 break;
271             }
272         }
273         return [$block, $i];
274     }
275     ```
276
277 2. **Rendering** the element. After all blocks have been consumed, they are being rendered using the
278    `render{elementName}()`-method where `elementName` refers to the name of the element in the abstract syntax tree:
279
280    ```php
281     protected function renderFencedCode($block)
282     {
283         $class = isset($block['language']) ? ' class="language-' . $block['language'] . '"' : '';
284         return "<pre><code$class>" . htmlspecialchars(implode("\n", $block['content']) . "\n", ENT_NOQUOTES, 'UTF-8') . '</code></pre>';
285     }
286    ```
287
288    You may also add code highlighting here. In general it would also be possible to render ouput in a different language than
289    HTML for example LaTeX.
290
291
292 ### Adding inline elements
293
294 Adding inline elements is different from block elements as they are parsed using markers in the text.
295 An inline element is identified by a marker that marks the beginning of an inline element (e.g. `[` will mark a possible
296 beginning of a link or `` ` `` will mark inline code).
297
298 Parsing methods for inline elements are also protected and identified by the prefix `parse`. Additionally a `@marker` annotation
299 in PHPDoc is needed to register the parse function for one or multiple markers.
300 The method will then be called when a marker is found in the text. As an argument it takes the text starting at the position of the marker.
301 The parser method will return an array containing the element of the abstract sytnax tree and an offset of text it has
302 parsed from the input markdown. All text up to this offset will be removed from the markdown before the next marker will be searched.
303
304 As an example, we will add support for the [strikethrough][] feature of github flavored markdown:
305
306 [strikethrough]: https://help.github.com/articles/github-flavored-markdown#strikethrough "Strikethrough feature of github flavored markdown"
307
308 ```php
309 <?php
310
311 class MyMarkdown extends \cebe\markdown\Markdown
312 {
313     /**
314      * @marker ~~
315      */
316     protected function parseStrike($markdown)
317     {
318         // check whether the marker really represents a strikethrough (i.e. there is a closing ~~)
319         if (preg_match('/^~~(.+?)~~/', $markdown, $matches)) {
320             return [
321                 // return the parsed tag as an element of the abstract syntax tree and call `parseInline()` to allow
322                 // other inline markdown elements inside this tag
323                 ['strike', $this->parseInline($matches[1])],
324                 // return the offset of the parsed text
325                 strlen($matches[0])
326             ];
327         }
328         // in case we did not find a closing ~~ we just return the marker and skip 2 characters
329         return [['text', '~~'], 2];
330     }
331
332     // rendering is the same as for block elements, we turn the abstract syntax array into a string.
333     protected function renderStrike($element)
334     {
335         return '<del>' . $this->renderAbsy($element[1]) . '</del>';
336     }
337 }
338 ```
339
340 ### Composing your own Markdown flavor
341
342 This markdown library is composed of traits so it is very easy to create your own markdown flavor by adding and/or removing
343 the single feature traits.
344
345 Designing your Markdown flavor consists of four steps:
346
347 1. Select a base class
348 2. Select language feature traits
349 3. Define escapeable characters
350 4. Optionally add custom rendering behavior
351
352 #### Select a base class
353
354 If you want to extend from a flavor and only add features you can use one of the existing classes
355 (`Markdown`, `GithubMarkdown` or `MarkdownExtra`) as your flavors base class.
356
357 If you want to define a subset of the markdown language, i.e. remove some of the features, you have to
358 extend your class from `Parser`.
359
360 #### Select language feature traits
361
362 The following shows the trait selection for traditional Markdown.
363
364 ```php
365 class MyMarkdown extends Parser
366 {
367     // include block element parsing using traits
368     use block\CodeTrait;
369     use block\HeadlineTrait;
370     use block\HtmlTrait {
371         parseInlineHtml as private;
372     }
373     use block\ListTrait {
374         // Check Ul List before headline
375         identifyUl as protected identifyBUl;
376         consumeUl as protected consumeBUl;
377     }
378     use block\QuoteTrait;
379     use block\RuleTrait {
380         // Check Hr before checking lists
381         identifyHr as protected identifyAHr;
382         consumeHr as protected consumeAHr;
383     }
384     // include inline element parsing using traits
385     use inline\CodeTrait;
386     use inline\EmphStrongTrait;
387     use inline\LinkTrait;
388
389     /**
390      * @var boolean whether to format markup according to HTML5 spec.
391      * Defaults to `false` which means that markup is formatted as HTML4.
392      */
393     public $html5 = false;
394
395     protected function prepare()
396     {
397         // reset references
398         $this->references = [];
399     }
400
401     // ...
402 }
403 ```
404
405 In general, just adding the trait with `use` is enough, however in some cases some fine tuning is desired
406 to get most expected parsing results. Elements are detected in alphabetical order of their identification
407 function. This means that if a line starting with `-` could be a list or a horizontal rule, the preference has to be set
408 by renaming the identification function. This is what is done with renaming `identifyHr` to `identifyAHr`
409 and `identifyBUl` to `identifyBUl`. The consume function always has to have the same name as the identification function
410 so this has to be renamed too.
411
412 There is also a conflict for parsing of the `<` character. This could either be a link/email enclosed in `<` and `>`
413 or an inline HTML tag. In order to resolve this conflict when adding the `LinkTrait`, we need to hide the `parseInlineHtml`
414 method of the `HtmlTrait`.
415
416 If you use any trait that uses the `$html5` property to adjust its output you also need to define this property.
417
418 If you use the link trait it may be useful to implement `prepare()` as shown above to reset references before
419 parsing to ensure you get a reusable object.
420
421 #### Define escapeable characters
422
423 Depending on the language features you have chosen there is a different set of characters that can be escaped
424 using `\`. The following is the set of escapeable characters for traditional markdown, you can copy it to your class
425 as is.
426
427 ```php
428     /**
429      * @var array these are "escapeable" characters. When using one of these prefixed with a
430      * backslash, the character will be outputted without the backslash and is not interpreted
431      * as markdown.
432      */
433     protected $escapeCharacters = [
434         '\\', // backslash
435         '`', // backtick
436         '*', // asterisk
437         '_', // underscore
438         '{', '}', // curly braces
439         '[', ']', // square brackets
440         '(', ')', // parentheses
441         '#', // hash mark
442         '+', // plus sign
443         '-', // minus sign (hyphen)
444         '.', // dot
445         '!', // exclamation mark
446         '<', '>',
447     ];
448 ```
449
450 #### Add custom rendering behavior
451
452 Optionally you may also want to adjust rendering behavior by overriding some methods.
453 You may refer to the `consumeParagraph()` method of the `Markdown` and `GithubMarkdown` classes for some inspiration
454 which define different rules for which elements are allowed to interrupt a paragraph.
455
456
457 Acknowledgements <a name="ack"></a>
458 ----------------
459
460 I'd like to thank [@erusev][] for creating [Parsedown][] which heavily influenced this work and provided
461 the idea of the line based parsing approach.
462
463 [@erusev]: https://github.com/erusev "Emanuil Rusev"
464 [Parsedown]: http://parsedown.org/ "The Parsedown PHP Markdown parser"
465
466 FAQ <a name="faq"></a>
467 ---
468
469 ### Why another markdown parser?
470
471 While reviewing PHP markdown parsers for choosing one to use bundled with the [Yii framework 2.0][]
472 I found that most of the implementations use regex to replace patterns instead
473 of doing real parsing. This way extending them with new language elements is quite hard
474 as you have to come up with a complex regex, that matches your addition but does not mess
475 with other elements. Such additions are very common as you see on github which supports referencing
476 issues, users and commits in the comments.
477 A [real parser][] should use context aware methods that walk trough the text and
478 parse the tokens as they find them. The only implentation that I have found that uses
479 this approach is [Parsedown][] which also shows that this implementation is [much faster][benchmark]
480 than the regex way. Parsedown however is an implementation that focuses on speed and implements
481 its own flavor (mainly github flavored markdown) in one class and at the time of this writing was
482 not easily extensible.
483
484 Given the situation above I decided to start my own implementation using the parsing approach
485 from Parsedown and making it extensible creating a class for each markdown flavor that extend each
486 other in the way that also the markdown languages extend each other.
487 This allows you to choose between markdown language flavors and also provides a way to compose your
488 own flavor picking the best things from all.
489 I chose this approach as it is easier to implement and also more intuitive approach compared
490 to using callbacks to inject functionallity into the parser.
491
492 [real parser]: http://en.wikipedia.org/wiki/Parsing#Types_of_parser
493
494 [Parsedown]: http://parsedown.org/ "The Parsedown PHP Markdown parser"
495
496 [Yii framework 2.0]: https://github.com/yiisoft/yii2
497
498 ### Where do I report bugs or rendering issues?
499
500 Just [open an issue][] on github, post your markdown code and describe the problem. You may also attach screenshots of the rendered HTML result to describe your problem.
501
502 [open an issue]: https://github.com/cebe/markdown/issues/new
503
504 ### How can I contribute to this library?
505
506 Check the [CONTRIBUTING.md](CONTRIBUTING.md) file for more info.
507
508
509 ### Am I free to use this?
510
511 This library is open source and licensed under the [MIT License][]. This means that you can do whatever you want
512 with it as long as you mention my name and include the [license file][license]. Check the [license][] for details.
513
514 [MIT License]: http://opensource.org/licenses/MIT
515
516 [license]: https://github.com/cebe/markdown/blob/master/LICENSE
517
518 Contact
519 -------
520
521 Feel free to contact me using [email](mailto:mail@cebe.cc) or [twitter](https://twitter.com/cebe_cc).