commonmark: markdown done right
TRANSCRIPT
COLIN O’DELL
Creator & Maintainer of league/commonmark
Lead Web Developer at Unleashed Technologies
Author of PHP 7 Migration Guide e-book
@colinodell
www.colinodell.com
LEAGUE/COMMONMARK
A well-written, super-configurable Markdown
parser for PHP based on the CommonMark
spec.
COMMONMARK IS…
A strongly defined, highly compatible specification of Markdown.
Written by people from Github, StackOverflow, Reddit, and others.
Spec includes:
Strict rules (precedence, parsing order, handling edge cases)
Specific definitions (ex: “whitespace”, “punctuation”)
616 examples
30%
WHY IS IT NEEDED?
*I *love* Markdown*
<p><em>I <em>love</em> Markdown</em></p>
*I *love* Markdown*
<p><em>I </em>love<em> Markdown</em></p>
*I *love* Markdown*
<p><em>I *love</em> Markdown*</p>
15%
33%
Source: http://johnmacfarlane.net/babelmark2/
FEATURES
100% compliance with the CommonMark spec
Easy to implement
Easy to customize
Well-tested
Decent performance
(Relatively) stable
FEATURES
100% compliance with the CommonMark spec
Easy to implement
Easy to customize
Well-tested
Decent performance
(Relatively) stable
ADDING LEAGUE/COMMONMARK
$ composer require league/commonmark:^0.13
<?php$converter = new CommonMarkConverter();echo $converter->convertToHtml('Hello **php[tek]!**');
FEATURES
100% compliance with the CommonMark spec
Easy to implement
Easy to customize
Well-tested
Decent performance
(Relatively) stable
FEATURES
100% compliance with the CommonMark spec
Easy to implement
Easy to customize
Well-tested
Decent performance
(Relatively) stable
CONVERSION PROCESS
<https://tek.phparch.com>
<a href="https://tek.phparch.com">https://tek.phparch.com
</a>
<document><paragraph><link destination="https://tek.phparch.com">
<text>https://tek.phparch.com</text></link>
</paragraph></document>
CONVERSION PROCESS
Markdown AST RenderParse
CONVERSION PROCESS
<a href="https://tek.phparch.com">https://tek.phparch.com
</a>
Markdown AST HTMLRenderParse
EXAMPLE 1: CUSTOM PARSER
<https://tek.phparch.com>
<a href="https://tek.phparch.com">https://tek.phparch.com
</a>
<@colinodell>
<a href="https://twitter.com/colinodell">@colinodell
</a>
class TwitterUsernameAutolinkParser extends AbstractInlineParser {public function getCharacters() {
return ['<'];}
public function parse(InlineParserContext $inlineContext) {$cursor = $inlineContext->getCursor();
}}
CURSOR
Learning CommonMark with <@colinodell>!
getCharacter()
getFirstNonSpaceCharacter()
getRemainder()
getLine()
getPosition()
advance()
advanceBy($count)
advanceBySpaceOrTab()
advanceWhileMatches($char)
advanceToFirstNonSpace()
isIndented()
isAtEnd()
match($regex)
peek($count)
CURSOR
Learning CommonMark with <@colinodell>!
getCharacter()
getFirstNonSpaceCharacter()
getRemainder()
getLine()
getPosition()
advance()
advanceBy($count)
advanceBySpaceOrTab()
advanceWhileMatches($char)
advanceToFirstNonSpace()
isIndented()
isAtEnd()
match($regex)
peek($count)
CURSOR
Learning CommonMark with <@colinodell>!
getCharacter()
getFirstNonSpaceCharacter()
getRemainder()
getLine()
getPosition()
advance()
advanceBy($count)
advanceBySpaceOrTab()
advanceWhileMatches($char)
advanceToFirstNonSpace()
isIndented()
isAtEnd()
match($regex)
peek($count)
/^<@[A-Za-z0-9_]+>/
CUSTOMIZING LEAGUE/COMMONMARK
class TwitterUsernameAutolinkParser extends AbstractInlineParser {public function getCharacters() {
return ['<'];}
public function parse(InlineParserContext $inlineContext) {$cursor = $inlineContext->getCursor();if ($match = $cursor->match('/^<@[A-Za-z0-9_]+>/')) {
// Remove the starting '<@' and ending '>' that were matched$username = substr($match, 2, -1);
$profileUrl = 'https://twitter.com/' . $username;$link = new Link($profileUrl, '@'.$username);$inlineContext->getContainer()->appendChild($link);
return true;}
return false;}
}
$environment = Environment::createCommonMarkEnvironment();$environment->addInlineParser(
new TwitterHandleParser());
$converter = new CommonMarkConverter($environment);
$html = $converter->convertToHtml("Follow <@colinodell> on Twitter!"
);
EXAMPLE 2: CUSTOM AST PROCESSOR
<document><paragraph><link destination="https://tek.phparch.com">
<text>https://tek.phparch.com</text></link>
</paragraph></document>
<document><paragraph><link destination="https://bit.ly/foo">
<text>https://tek.phparch.com</text></link>
</paragraph></document>
class ShortenLinkProcessor implements DocumentProcessorInterface {
public function processDocument(Document $document) {
$walker = $document->walker();
while ($event = $walker->next()) {
if ($event->isEntering() && $event->getNode() instanceof Link) {
/** @var Link $linkNode */
$linkNode = $event->getNode();
$originalUrl = $linkNode->getUrl();
$shortUrl = $this->bitly->shorten($originalUrl);
$linkNode->setUrl($shortUrl);
}
}
}
}
$environment = Environment::createCommonMarkEnvironment();$environment->addDocumentProcessor(
new ShortenLinkProcessor());
$converter = new CommonMarkConverter($environment);
$html = $converter->convertToHtml("Schedule: <https://tek.phparch.com/schedule>"
);
EXAMPLE 2: CUSTOM AST PROCESSOR
EXAMPLE 3: CUSTOM RENDERER
<document><paragraph><text>Hello World!</text>
</paragraph><thematic_break />
</document>
<p>Hello World!</p><hr />
<p>Hello World!</p><img src="hr.png" />
EXAMPLE 3: CUSTOM RENDERER
class ImageHorizontalRuleRenderer implements BlockRendererInterface {
public function render(...) {
return new HtmlElement('img', ['src' => 'hr.png']);
}
}
$environment = Environment::createCommonMarkEnvironment();$environment->addBlockRenderer(
League\CommonMark\Block\Element\ThematicBreak::class,new ImageHorizontalRuleRenderer()
);
$converter = new CommonMarkConverter($environment);
$html = $converter->convertToHtml("Hello World!\n\n-----");
EXAMPLE 3: CUSTOM RENDERER
FEATURES
100% compliance with the CommonMark spec
Easy to implement
Easy to customize
Well-tested
Decent performance
(Relatively) stable
FEATURES
100% compliance with the CommonMark spec
Easy to implement
Easy to customize
Well-tested
Decent performance
(Relatively) stable
WELL-TESTED
94% code coverage
Functional tests
All 616 spec examples
Library of regression tests
Unit tests
Cursor
Environment
Utility classes
FEATURES
100% compliance with the CommonMark spec
Easy to implement
Easy to customize
Well-tested
Decent performance
(Relatively) stable
FEATURES
100% compliance with the CommonMark spec
Easy to implement
Easy to customize
Well-tested
Decent performance
(Relatively) stable
PERFORMANCE
0 20 40 60 80 100
Parsedown
cebe/markdown
PHP Markdown Extra
league/commonmark
Time (ms)
league/commonmark is ~35-40ms slower
PHP 5.6 PHP 7.0
Tips:
• Choose library based on your needs
• Cache rendered HTML (100% boost)
• Use PHP 7 (50-80% boost)
• Optimize custom functionality
FEATURES
100% compliance with the CommonMark spec
Easy to implement
Easy to customize
Well-tested
Decent performance
(Relatively) stable
FEATURES
100% compliance with the CommonMark spec
Easy to implement
Easy to customize
Well-tested
Decent performance
(Relatively) stable
STABILITY
Current version: 0.13.3
Conforms to CommonMark spec 0.25
1.0.0 will be released once CommonMark spec is 1.0
No major stability issues
Backwards Compatibility Promise:
No BC breaks to CommonMarkConverter class in 0.x
Other BC breaks will be documented
FEATURES
100% compliance with the CommonMark spec
Easy to implement
Easy to customize
Well-tested
Decent performance
(Relatively) stable