commonmark for PHP
CommonMark for PHP is a PHP library to parse Markdown files to HTML. It supports extended syntax, and you can define your own.
You can install it using composer:
$ composer require league/commonmark
A basic example:
use League\CommonMark\CommonMarkConverter;
$converter = new CommonMarkConverter();
echo $converter->convert("**Hello, World**")->getContent();
β οΈ Code changed a lot between version 1.x
and 2.x
.
Custom Converter
To create a custom convert that you can tune however you want, you can use the MarkdownConverter
class.
// define your configuration
$config = [];
$environment = new Environment($config);
// define parsing rules
$environment->addExtension(new CommonMarkCoreExtension());
// define rendering rules
$environment->addRenderer(XXX::class, new XXXRenderer());
// convert
$converter = new MarkdownConverter($environment);
echo $converter->convert("**Hello, World**")->getContent();
Configuration
Refer to this section of the documentation.
Extensions
By default, you won't be able to parse any markdown. You can either load your own parsers, or use existing ones:
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addExtension(new TaskListExtension());
$environment->addExtension(new TableExtension());
All extensions are listed and explained here.
Renderers
Renderers are used to define how each element parsed is rendered.
For instance, you could define that all parsed links starting with HTTP will be red and those starting with HTTPS will be green.
β‘οΈ See also: HtmlDecorator
to only add HTML attributes.
Custom renderers
Add a custom renderer
To add a renderer, you need to define the parsed element that will be rendered using your custom renderer.
$environment->addRenderer(Heading::class, new HeadingRenderer());
See also: Link::class
, Image::class
, Table::class
...
Custom renderer class
Each renderer implements render
from NodeRendererInterface
.
class HeadingRenderer implements NodeRendererInterface {
public function render(Node $node, ChildNodeRendererInterface $childRenderer)
{
// ...
return new HtmlElement($tag, $attrs, $childRenderer->renderNodes($node->children()));
}
}
It returns a HtmlElement
which is the HTML element that will be displayed. For instance, for <h1 id="toto">...</h1>
, you would have:
$tag = "h1";
$attrs = ["id" => "toto"];
You should assert the type of node you're manipulating before using class-specific methods on it.
Heading::assertInstanceOf($node); // for Heading::class
Some generic methods:
// Get attributes
$attrs = $node->data->getData('attributes');
// Call render on all children nodes
$innerHtml = $childRenderer->renderNodes($node->children());
Heading
-
$node->getLevel()
: get the level, such as2
for##
Image
-
$node->getUrl()
: get the image URL
Link
-
$node->getUrl()
: get the link URL
FencedCode
-
$node->getLiteral()
: get the fenced code text -
$node->getInfoWords()
: get the fenced code language
Custom parser
You can create your own Markdown syntax and create a parser for it π. You'll have to create a renderer too πΌοΈ.
There are too categories of parsers: Inline and Block. The bold syntax **x**
would be inline, while a table ("<table>
") would be a block.
Block parsers are split in a StartParser
that checks if your parser can parse a block and a Parser
that actually parse it.
$environment->addBlockStartParser(new XXXStartParser());
β οΈ The order to add parsers to the environment is important. You may even have to add it before any extension.
StartParser
Return BlockStart::none()
if your parser is not supposed to parse this block. Otherwise, return BlockStart::of(...)
.
Use the cursor's methods to move around and find if your parser is supposed to parse this block.
class BootstrapVerticalTabStartParser implements BlockStartParserInterface
{
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
$cursor->isIndented();
$cursor->advance();
$char = $cursor->getNextNonSpaceCharacter();
$cursor->advance();
$cursor->advanceToNextNonSpaceOrTab();
return BlockStart::of(new XXXParser())->at($cursor);
}
}
Parser
The logic of a parser can be explained as follows:
-
tryContinue
is called for you to check if the current line is still within the block -
addLine
is then called for you to parse the Markdown -
closeBlock
when you parsed all of your lines
Then, parseInlines
is called. This method will allow you to parse inline symbols such as **bold**
that were inside of your block.
XXXParser sample code
class XXXParser extends AbstractBlockContinueParser implements BlockContinueParserWithInlinesInterface {
private XXX $block;
public function __construct(Environment $environment)
{
$this->block = new XXX();
}
public function getBlock(): AbstractBlock
{
return $this->block;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
// return either by using the cursor methods
// to find if you're done or not
return BlockContinue::finished(); // done
return BlockContinue::at($cursor); // continue
}
public function parseInlines(InlineParserEngineInterface $inlineParser): void
{
// Ex: store in a Paragraph the line
// After parsing its inline elements.
// Then store it as a child of our block
$xxx = "some content you **parsed**";
$p = new Paragraph();
$inlineParser->parse($xxx, $p);
$this->block->appendChild($p);
}
public function addLine(string $line): void
{
// each time you accept to continue
// parsing, this method is called
// with the line that you need to parse
$this->block->number_of_lines++; // example
}
public function closeBlock(): void
{
// for instance, you may have to
// process the last element here
}
public function canHaveLazyContinuationLines(): bool
{
return true;
}
}
As you'll create your own renderer, you can do anything you want, such as creating new attributes or methods in XXX
.
class XXX extends AbstractBlock
{
public int $number_of_lines = 0;
}
Here is how you can get started with the renderer:
class XXXRenderer implements NodeRendererInterface
{
public function render(Node $node, ChildNodeRendererInterface $childRenderer): ?HtmlElement {
XXX::assertInstanceOf($node);
var_dump($node->number_of_lines); // ok
return null; // todo
}
}