CommonMark
Markdown done right
Colin O’Dell
@colinodell
COLIN O’DELL
Creator & Maintainer of league/commonmark
LeadWeb Developer at UnleashedTechnologies
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
WHY IS IT NEEDED?
*I love Markdown*
<p><em>I love Markdown</em></p>
WHY IS IT NEEDED?
*I *love* Markdown*
WHY IS IT NEEDED? Source: http://johnmacfarlane.net/babelmark2/
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]!**');
INTEGRATIONS
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>
CONVERSION PROCESS
<https://tek.phparch.com>
Markdown Parse
<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
CONVERSION PROCESS
Markdown AST HTMLRenderParse
Add your own custom parser, processor, or renderer
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>!
public function getCharacters()
{
return ['<'];
}
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(
LeagueCommonMarkBlockElementThematicBreak::class,
new ImageHorizontalRuleRenderer()
);
$converter = new CommonMarkConverter($environment);
$html = $converter->convertToHtml("Hello World!nn-----");
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
Installation & Documentation:
http://github.com/thephpleague/commonmark
Learn More About CommonMark:
http://commonmark.org
Slides / Feedback:
https://joind.in/talk/96dc9
@colinodell

CommonMark: Markdown Done Right