Inline Parsing

There are two ways to implement custom inline syntax:

The difference between normal inlines and delimiter-run-based inlines is subtle but important to understand. In a nutshell, delimiter-run-based inlines:

An example of this would be emphasis:

This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.

If your syntax looks like that, consider using a delimiter processor instead. Otherwise, an inline parser is your best bet.

Implementing Inline Parsers

Inline parsers should implement InlineParserInterface and the following two methods:

getMatchDefinition()

This method should return an instance of InlineParserMatch which defines the text the parser is looking for. Examples of this might be something like:

use League\CommonMark\Parser\Inline\InlineParserMatch;

InlineParserMatch::string('@');                  // Match any '@' characters found in the text
InlineParserMatch::string('foo');                // Match the text 'foo' (case insensitive)

InlineParserMatch::oneOf('@', '!');              // Match either character
InlineParserMatch::oneOf('http://', 'https://'); // Match either string

InlineParserMatch::regex('\d+');                 // Match the regular expression (omit the regex delimiters and any flags)

Once a match is found, the parse() method below may be called.

parse()

This method will be called if both conditions are met:

  1. The engine has found at a matching string in the current line; and,
  2. No other inline parsers with a higher priority have successfully parsed the text at this point in the line

Parameters

InlineParserContext

This class has several useful methods:

Return value

parse() should return false if it’s unable to handle the text at the current position for any reason. Other parsers will then have a chance to try parsing that text. If all registered parsers return false, the text will be added as plain text.

Returning true tells the engine that you’ve successfully parsed the character (and related ones after it). It is your responsibility to:

  1. Advance the cursor to the end of the parsed/matched text
  2. Add the parsed inline to the container ($inlineContext->getContainer()->appendChild(...))

Inline Parser Examples

Example 1 - Twitter Handles

Let’s say you wanted to autolink Twitter handles without using the link syntax. This could be accomplished by registering a new inline parser to handle the @ character:

use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;

class TwitterHandleParser implements InlineParserInterface
{
    public function getMatchDefinition(): InlineParserMatch
    {
        return InlineParserMatch::regex('@([A-Za-z0-9_]{1,15}(?!\w))');
    }

    public function parse(InlineParserContext $inlineContext): bool
    {
        $cursor = $inlineContext->getCursor();
        // The @ symbol must not have any other characters immediately prior
        $previousChar = $cursor->peek(-1);
        if ($previousChar !== null && $previousChar !== ' ') {
            // peek() doesn't modify the cursor, so no need to restore state first
            return false;
        }

        // This seems to be a valid match
        // Advance the cursor to the end of the match
        $cursor->advanceBy($inlineContext->getFullMatchLength());

        // Grab the Twitter handle
        [$handle] = $inlineContext->getSubMatches();
        $profileUrl = 'https://twitter.com/' . $handle;
        $inlineContext->getContainer()->appendChild(new Link($profileUrl, '@' . $handle));
        return true;
    }
}

// And here's how to hook it up:

$environment = new Environment();
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addInlineParser(new TwitterHandleParser());

Example 2 - Emoticons

Let’s say you want to automatically convert smilies (or “frownies”) to emoticon images. This is incredibly easy with an inline parser:

use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\Node\Inline\Image;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;

class SmilieParser implements InlineParserInterface
{
    public function getMatchDefinition(): InlineParserMatch
    {
        return InlineParserMatch::oneOf(':)', ':(');
    }

    public function parse(InlineParserContext $inlineContext): bool
    {
        $cursor = $inlineContext->getCursor();

        // Advance the cursor past the 2 matched chars since we're able to parse them successfully
        $cursor->advanceBy(2);

        // Add the corresponding image
        if ($inlineContext->getFullMatch() === ':)') {
            $inlineContext->getContainer()->appendChild(new Image('/img/happy.png'));
        } elseif ($inlineContext->getFullMatch() === ':(') {
            $inlineContext->getContainer()->appendChild(new Image('/img/sad.png'));
        }

        return true;
    }
}

$environment = new Environment();
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addInlineParser(new SmilieParserParser());

Tips


Edit this page