/** * Parse the given content. * * Any newly created nodes should be appended to the given target. Any remaining content should be passed to the * next parser in the chain. * * @param Text $content * @param InlineNodeAcceptorInterface $target * @return void */ public function parseInline(Text $content, InlineNodeAcceptorInterface $target) { $content->handle('{ (?<!\\\\) ( \\<[A-Z][A-Z0-9]* # an opening HTML tag with (?: # multiple attributes... \\s+ [A-Z_:][A-Z0-9_:.\\-]* (?: \\s*=\\s* (?: [^ "\'=<>`]+| # unquoted attribute values \'[^\']*\'| # single-quoted attribute values "[^"]*" # double-quoted attribute values ) )? )* \\s*/?\\>| # ...or \\</[A-Z][A-Z0-9]*\\s*\\>| # a closing HTML tag, or \\<!--(-(?!-)|[^\\-])*?--\\>| # a HTML comment, or \\<\\?.*?\\?\\>| # a processing instruction, or \\<![A-Z]+\\s+[^>]+\\>| # an element type declaration, or \\<!\\[CDATA\\[.*?\\]\\]\\> # a CDATA section ) }isx', function (Text $content) use($target) { $target->addInline(new RawHTML($content)); }, function (Text $part) use($target) { $this->next->parseInline($part, $target); }); }
/** * Parse the given content. * * Any newly created nodes should be pushed to the stack. Any remaining content should be passed to the next parser * in the chain. * * @param Text $content * @param Container $target * @return void */ public function parseBlock(Text $content, Container $target) { $content->handle('/ ( (?: # We are either at the beginning of the match ^|\\n # or at a newline (for multi-line quotes) ) (?: [ ]{0,3}\\>[ ]* # either a blank line | [ ]{0,3}\\>[ ]?[`~]{3,}.* # or a code fence | [ ]{0,3}\\> # or non-blank lines... [ ]*[^\\n\\ ][^\\n]*[ ]* ( # ...with lazy continuation \\n [ ]{0,3} [^>\\-+*=\\ \\n][^\\n]* )* ) )+ $ /mx', function (Text $content) use($target) { // Remove block quote marker plus surrounding whitespace on each line $content->replace('/^[ ]{0,3}\\>[ ]?/m', ''); $blockquote = new Blockquote(); $target->addChild($blockquote); $this->first->parseBlock($content, $blockquote); }, function (Text $part) use($target) { $this->next->parseBlock($part, $target); }); }
protected function parseEmail(Text $content, InlineNodeAcceptorInterface $target) { if ($content->contains('@')) { $content->handle('{ < (?:mailto:)? ( [a-zA-Z0-9.!#$%&\'*+/=?^_`{|}~-]+ \\@ [a-zA-Z0-9] (?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])? (?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)* ) > }ix', function (Text $w, Text $email) use($target) { $text = $email->copy(); $link = new Link($email->prepend('mailto:')); $link->addInline(new String($text)); $target->addInline($link); }, function (Text $part) use($target) { $this->next->parseInline($part, $target); }); } else { $this->next->parseInline($content, $target); } }
/** * Parse the given content. * * Any newly created nodes should be pushed to the stack. Any remaining content should be passed to the next parser * in the chain. * * @param Text $content * @param Container $target * @return void */ public function parseBlock(Text $content, Container $target) { $content->handle('{ ^(?: ([ ]{0,3}) #1 Initial indentation ( #2 Fence ([`~]) #3 The fence character (` or ~) \\3{2,} # At least two remaining fence characters ) ([^`\\n]*?)? #4 Code language [optional] (?: \\n(.*?) #5 Code block )? (?:(?<=\\n)([ ]{0,3}\\2\\3*[ ]*)|\\z) # Closing fence or end of document )$ }msx', function (Text $whole, Text $whitespace, Text $fence, Text $fenceChar, Text $lang, Text $code = null) use($target) { $leading = $whitespace->getLength(); $code = $code ?: new Text(); $language = new Text(explode(' ', $lang->trim())[0]); $language->decodeEntities(); // Remove all leading whitespace from content lines if ($leading > 0) { $code->replace("/^[ ]{0,{$leading}}/m", ''); } $target->addChild(new CodeBlock($code, $language)); }, function (Text $part) use($target) { $this->next->parseBlock($part, $target); }); }
/** * Parse the given content. * * Any newly created nodes should be pushed to the stack. Any remaining content should be passed to the next parser * in the chain. * * @param Text $content * @param Container $target * @return void */ public function parseBlock(Text $content, Container $target) { $content->handle('{ (?: # Ensure blank line before (or beginning of subject) \\A\\s*\\n?| \\n\\s*\\n ) [ ]{4} # four leading spaces .+ (?: # optionally more spaces (?: # blank lines in between are okay \\n[ ]* )* \\n [ ]{4} .+ )* $ }mx', function (Text $code) use($target) { // Remove leading blank lines $code->replace('/^(\\s*\\n)*/', ''); // Remove indent $code->replace('/^[ ]{1,4}/m', ''); $code->append("\n"); $target->addChild(new CodeBlock($code)); }, function (Text $part) use($target) { $this->next->parseBlock($part, $target); }); }
/** * Parse the given content. * * Any newly created nodes should be appended to the given target. Any remaining content should be passed to the * next parser in the chain. * * @param Text $content * @param InlineNodeAcceptorInterface $target * @return void */ public function parseInline(Text $content, InlineNodeAcceptorInterface $target) { $content->handle('/(\\\\\\n)|( {2,}\\n)/', function () use($target) { $target->addInline(new HardBreak()); }, function (Text $part) use($target) { $this->next->parseInline($part, $target); }); }
protected function parseUnderscores(Text $content, InlineNodeAcceptorInterface $target) { $content->handle('{ (?<![A-Za-z0-9]) (__) (?![\\s_]) (.+) (?<![\\s_]) \\1 (?![A-Za-z0-9]) }sx', function (Text $w, Text $a, Text $inner) use($target) { $emphasis = new StrongEmphasis($inner); $this->context->queue($emphasis->getContent(), $emphasis); $target->addInline($emphasis); }, function (Text $part) use($target) { $this->next->parseInline($part, $target); }); }
/** * Parse the given content. * * Any newly created nodes should be pushed to the stack. Any remaining content should be passed to the next parser * in the chain. * * @param Text $content * @param Container $target * @return void */ public function parseBlock(Text $content, Container $target) { $content->handle('/^\\s*$/m', function (Text $line) use($target) { $target->addChild(new BlankLine($line)); }, function (Text $part) use($target) { $paragraph = new Paragraph($part); $target->addChild($paragraph); $this->inlineParser->queue($paragraph->getText(), $paragraph); }); }
/** * Preprocess the text. * * This unifies line endings and replaces tabs by spaces. * * @param Text $text * @return void */ protected function prepare(Text $text) { // Unify line endings $text->replaceString("\r\n", "\n"); $text->replaceString("\r", "\n"); // Replace tabs by spaces $text->replace('/(.*?)\\t/', function (Text $whole, Text $string) { $tabWidth = 4; return $string . str_repeat(' ', $tabWidth - $string->getLength() % $tabWidth); }); }
/** * Parse the given content. * * Any newly created nodes should be pushed to the stack. Any remaining content should be passed to the next parser * in the chain. * * @param Text $content * @param Container $target * @return void */ public function parseBlock(Text $content, Container $target) { $content->handle('{ ^ [ ]{0,3}\\[(.+)\\]: # id = $1 [ \\t]* \\n? # maybe *one* newline [ \\t]* (?| # url = $2 ([^\\s<>]+) # either without spaces | <([^<>\\n]*)> # or enclosed in angle brackets ) (?: [ \\t]* \\n? # maybe one newline [ \\t]* (?<=\\s) # lookbehind for whitespace ["\'(] (.+?) # title = $3 ["\')] [ \\t]* )? # title is optional $ }xm', function (Text $whole, Text $id, Text $url, Text $title = null) use($target) { if (!$this->queue->isEmpty()) { $lastPart = $this->queue->dequeue(); // If the previous line was not empty, we should not parse this as a link reference if (!$lastPart->match('/(^|\\n *)\\n$/')) { $lastPart->append($whole); $this->queue->enqueue($lastPart); return; } $this->parsePart($lastPart, $target); } $id = $id->lower()->getString(); // Throw away duplicate reference definitions if (!$this->links->exists($id)) { $url->decodeEntities(); // Replace special characters in the URL $url->encodeUrl(); $this->links->set($id, $url); if ($title) { $title->decodeEntities(); $this->titles->set($id, $title); } } }, function (Text $part) { if (!$this->queue->isEmpty()) { $lastPart = $this->queue->dequeue(); $part->prepend($lastPart); } $this->queue->enqueue($part); }); if (!$this->queue->isEmpty()) { $lastPart = $this->queue->dequeue(); $this->parsePart($lastPart, $target); } }
/** * Parse the given content. * * Any newly created nodes should be pushed to the stack. Any remaining content should be passed to the next parser * in the chain. * * @param Text $content * @param Container $target * @return void */ public function parseBlock(Text $content, Container $target) { $content->handle('{ ^[ ]{0,3} # Optional leading spaces (\\#{1,6}) # $1 = string of #\'s (([ ].+?)??) # $2 = Header text ([ ]\\#*[ ]*)? # optional closing #\'s (not counted) $ }mx', function (Text $whole, Text $marks, Text $content) use($target) { $level = $marks->getLength(); $heading = new Heading($content->trim(), $level); $target->addChild($heading); $this->inlineParser->queue($heading->getText(), $heading); }, function (Text $part) use($target) { $this->next->parseBlock($part, $target); }); }
protected function parseCodeSpan(Text $content, InlineNodeAcceptorInterface $target) { $content->handle('{ (?<![`\\\\]) (`+) # $1 = Opening run of ` (?!`) (.+?) # $2 = The code block (?<!`) \\1 # Matching closer (?!`) }sx', function (Text $whole, Text $b, Text $code) use($target) { // Replace multiple whitespace characters in a row with a single space $code->trim()->replaceString("\n", ' ')->replace('/[\\s]{2,}/', ' '); $target->addInline(new Code($code->escapeHtml())); }, function (Text $part) use($target) { $this->next->parseInline($part, $target); }); }
/** * Parse the given content. * * Any newly created nodes should be pushed to the stack. Any remaining content should be passed to the next parser * in the chain. * * @param Text $content * @param Container $target * @return void */ public function parseBlock(Text $content, Container $target) { $content->handle('{ ^ [ ]{0,3} ([^>\\-*=\\ \\n].*) [ ]* \\n [ ]{0,3} (=+|-+) [ ]* \\n* $ }mx', function (Text $whole, Text $content, Text $mark) use($target) { $level = substr($mark, 0, 1) == '=' ? 1 : 2; $heading = new Heading($content->trim(), $level); $target->addChild($heading); $this->inlineParser->queue($heading->getText(), $heading); }, function (Text $part) use($target) { $this->next->parseBlock($part, $target); }); }
/** * Parse the given content. * * Any newly created nodes should be pushed to the stack. Any remaining content should be passed to the next parser * in the chain. * * @param Text $content * @param Container $target * @return void */ public function parseBlock(Text $content, Container $target) { $tags = implode('|', $this->getValidTags()); $content->handle('{ ^ # starts at the beginning or with a newline [ ]{0,3} # up to 3 leading spaces allowed (?: # start with one of the following... \\<(?:' . $tags . ')\\s*/?\\>?| # an opening HTML tag, or \\</(?:' . $tags . ')\\s*\\>| # a closing HTML tag, or \\<!--(-(?!-)|[^\\-])*?--\\>| # a HTML comment, or \\<\\?.*?\\?\\>| # a processing instruction, or \\<![A-Z]+\\s+[^>]+\\>| # an element type declaration, or \\<!\\[CDATA\\[.*?\\]\\]\\> # a CDATA section ) .*? # match everything until... ((?=\\n[ ]*\\n)|\\Z) # we encounter an empty line or the end }imsx', function (Text $content) use($target) { $block = new HTMLBlock($content); $target->addChild($block); }, function (Text $part) use($target) { $this->next->parseBlock($part, $target); }); }
protected function parseReferenceLink(Text $content, InlineNodeAcceptorInterface $target) { $references = implode('|', array_map(function ($reference) { return str_replace(' ', '[ ]', preg_quote($reference)); }, $this->context->getReferences())); $content->handle('/ (?<!\\\\) (?| () \\[ (' . $references . ') \\] [\\n\\ ]* \\[\\] | (?: \\[ (' . $this->getNestedBrackets() . ') # link text = $1 \\] [ \\t\\n]* )? \\[ (' . $references . ') # label = $2 \\] ) /iux', function (Text $whole, Text $linkText, Text $label) use($target) { $reference = $label->copy()->lower(); $url = $this->context->getReferenceUrl($reference); $title = $this->context->getReferenceTitle($reference); if ($linkText->isEmpty()) { $linkText = $label; } $link = new Link($url, $linkText, $title); $target->addInline($link); $this->context->queue($link->getContent(), $link); }, function (Text $part) use($target) { $this->next->parseInline($part, $target); }); }
protected function parseReferenceImage(Text $content, InlineNodeAcceptorInterface $target) { $references = implode('|', array_map(function ($reference) { return str_replace(' ', '[ ]', preg_quote($reference)); }, $this->context->getReferences())); $content->handle('{ (?<!\\\\)! (?| () \\[ (' . $references . ') \\] [\\n\\ ]* \\[\\] | (?: \\[ (.*?) # alt text = $1 \\] [ \\t\\n]* )? \\[ (' . $references . ') \\] ) }iux', function (Text $whole, Text $alt, Text $label) use($target) { $reference = $label->copy()->lower(); $url = $this->context->getReferenceUrl($reference); $title = $this->context->getReferenceTitle($reference); if ($alt->isEmpty()) { $alt = $label; } $image = new Image($url, $alt, $title); $target->addInline($image); }, function (Text $part) use($target) { $this->next->parseInline($part, $target); }); }
protected function parseOrderedLists(Text $content, Container $target) { $content->handle('{ ^ ([ ]{0,3}) # $1 - initial indent ([0-9]+)([.)]) # $2 - list marker; $3 - punctuation (?| ([ ]{1,4}) # $4 - list indent [^ ].* | () # ... which can also be empty ) ( \\n\\n? \\1[ ]{2}\\4 .* )* ( (?: \\n \\1[0-9]+\\3\\4 [^ ].* | # empty items \\n \\1\\2 | # Lazy continuation lines \\n [ ]* (?: [^0-9>\\-+*=\\ \\n] | [0-9]+[^.)] ) [^\\n]* ) ( \\n\\n? \\1[ ]{2}\\4 .* )* )* $ }mx', function (Text $content, Text $i, Text $start, Text $punctuation, Text $indent) use($target) { $isTerse = !$content->match('/^[ ]*$/m'); $lines = explode("\n", $content->getString()); $start = $start->getString(); $indentLength = $i->getLength() + $indent->getLength() + 2; $list = new ListBlock('ol', $isTerse, $start); // Go through all the lines to assemble the list items $curItem = substr(array_shift($lines), $indentLength) . "\n"; foreach ($lines as $line) { if (preg_match('/^[0-9]+' . preg_quote($punctuation) . '/', $line)) { $this->addItemToList($curItem, $list); $curItem = substr($line, $indentLength) . "\n"; } else { $curItem .= $this->unindentLine($line, $indentLength) . "\n"; } } $this->addItemToList($curItem, $list); $target->addChild($list); }, function (Text $part) use($target) { $this->next->parseBlock($part, $target); }); }
public function handleBackslashes(Text $content) { $content->replaceString(['\\\\', '\\!', '\\"', '\\#', '\\$', '\\%', '\\&', '\\\'', '\\(', '\\)', '\\*', '\\+', '\\,', '\\-', '\\.', '\\/', '\\:', '\\;', '\\<', '\\=', '\\>', '\\?', '\\@', '\\[', '\\]', '\\^', '\\_', '\\`', '\\{', '\\|', '\\}', '\\~'], ['\\', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~']); }
public function visitHardBreak(HardBreak $softBreak) { $this->buffer->append(Tag::inline('br'))->append("\n"); }
public function hasLanguage() { return !$this->language->isEmpty(); }
/** * @return Text */ public function getContent() { return $this->content->copy(); }
protected function handle(Text $content, $mark, Container $target, callable $next) { $content->handle($this->getPattern($mark), function () use($target) { $target->addChild(new HorizontalRule()); }, $next); }
protected function makeLines(Text $text) { return $text->trim()->split('/\\n/')->apply(function (Text $line) { return $line->copy()->ltrim(); }); }