public function parse(ContextInterface $context, Cursor $cursor) { $inlineParserContext = new InlineParserContext($cursor); while (($character = $cursor->getCharacter()) !== null) { if ($matchingParsers = $this->environment->getInlineParsersForCharacter($character)) { foreach ($matchingParsers as $parser) { if ($parser->parse($context, $inlineParserContext)) { continue 2; } } } // We reach here if none of the parsers can handle the input // Attempt to match multiple non-special characters at once $text = $cursor->match($this->environment->getInlineParserCharacterRegex()); // This might fail if we're currently at a special character which wasn't parsed; if so, just add that character if ($text === null) { $cursor->advance(); $text = $character; } $lastInline = $inlineParserContext->getInlines()->last(); if ($lastInline instanceof Text && !isset($lastInline->data['delim'])) { $lastInline->append($text); } else { $inlineParserContext->getInlines()->add(new Text($text)); } } foreach ($this->environment->getInlineProcessors() as $inlineProcessor) { $inlineProcessor->processInlines($inlineParserContext->getInlines(), $inlineParserContext->getDelimiterStack()); } return $inlineParserContext->getInlines(); }
public function parse(ContextInterface $context, Cursor $cursor) { $document = $context->getDocument(); $tip = $context->getTip(); if (!$document->getLastChild() instanceof AttributesDocument) { $attributesDocument = new AttributesDocument(); foreach ($document->getChildren() as $child) { $document->removeChild($child); $attributesDocument->addChild($child); } $document->addChild($attributesDocument); if ($tip instanceof Document) { $context->setTip($attributesDocument); } } $state = $cursor->saveState(); $attributes = AttributesUtils::parse($cursor); if (empty($attributes)) { return false; } if (null !== $cursor->getFirstNonSpaceCharacter()) { $cursor->restoreState($state); return false; } $prepend = $tip instanceof Document || !$tip->getParent() instanceof Document && $context->getBlockCloser()->areAllClosed(); $context->addBlock(new Attributes($attributes, $prepend ? Attributes::PREPEND : Attributes::APPEND)); $context->setBlocksParsed(true); return true; }
/** * Attempt to parse link title (sans quotes) * * @param Cursor $cursor * * @return null|string The string, or null if no match */ public static function parseLinkTitle(Cursor $cursor) { if ($title = $cursor->match(RegexHelper::getInstance()->getLinkTitleRegex())) { // Chop off quotes from title and unescape return RegexHelper::unescape(substr($title, 1, strlen($title) - 2)); } }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { $tmpCursor = clone $cursor; $indent = $tmpCursor->advanceWhileMatches(' ', 3); $rest = $tmpCursor->getRemainder(); $data = new ListData(); if ($matches = RegexHelper::matchAll('/^[*+-]( +|$)/', $rest)) { $spacesAfterMarker = strlen($matches[1]); $data->type = ListBlock::TYPE_UNORDERED; $data->delimiter = null; $data->bulletChar = $matches[0][0]; } elseif ($matches = RegexHelper::matchAll('/^(\\d+)([.)])( +|$)/', $rest)) { $spacesAfterMarker = strlen($matches[3]); $data->type = ListBlock::TYPE_ORDERED; $data->start = intval($matches[1]); $data->delimiter = $matches[2]; $data->bulletChar = null; } else { return false; } $data->padding = $this->calculateListMarkerPadding($matches[0], $spacesAfterMarker, $rest); $cursor->advanceToFirstNonSpace(); $cursor->advanceBy($data->padding); // list item $data->markerOffset = $indent; // add the list if needed $container = $context->getContainer(); if (!$container || !$context->getContainer() instanceof ListBlock || !$data->equals($container->getListData())) { $context->addBlock(new ListBlock($data)); } // add the list item $context->addBlock(new ListItem($data)); return true; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { if ($cursor->getIndent() < IndentedCodeParser::CODE_INDENT_LEVEL) { return false; } $context->setBlocksParsed(true); return true; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { if (!$cursor->isIndented()) { return false; } $context->setBlocksParsed(true); return true; }
/** * @param ContextInterface $context * @param Cursor $cursor */ public function handleRemainingContents(ContextInterface $context, Cursor $cursor) { if ($cursor->isBlank()) { return; } $context->addBlock(new Paragraph()); $cursor->advanceToFirstNonSpace(); $context->getTip()->addLine($cursor->getRemainder()); }
public function matchesNextLine(Cursor $cursor) { if ($cursor->isBlank()) { $this->setLastLineBlank(true); } else { $this->setLastLineBlank(false); } return false; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { $match = RegexHelper::matchAt(RegexHelper::getInstance()->getHtmlBlockOpenRegex(), $cursor->getLine(), $cursor->getFirstNonSpacePosition()); if ($match === null) { return false; } $context->addBlock(new HtmlBlock()); $context->setBlocksParsed(true); return true; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return ArrayCollection */ public function parse(ContextInterface $context, Cursor $cursor) { $inlineParserContext = new InlineParserContext($cursor); while (($character = $cursor->getCharacter()) !== null) { if (!$this->parseCharacter($character, $context, $inlineParserContext)) { $this->addPlainText($character, $inlineParserContext); } } $this->processInlines($inlineParserContext); return $inlineParserContext->getInlines(); }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { $previousState = $cursor->saveState(); $indent = $cursor->advanceToFirstNonSpace(); $fence = $cursor->match('/^`{3,}(?!.*`)|^~{3,}(?!.*~)/'); if (!$fence) { $cursor->restoreState($previousState); return false; } // fenced code block $fenceLength = strlen($fence); $context->addBlock(new FencedCode($fenceLength, $fence[0], $indent)); return true; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { if ($cursor->isIndented()) { return false; } $match = RegexHelper::matchAt(RegexHelper::getInstance()->getThematicBreakRegex(), $cursor->getLine(), $cursor->getFirstNonSpacePosition()); if ($match === null) { return false; } // Advance to the end of the string, consuming the entire line (of the thematic break) $cursor->advanceToEnd(); $context->addBlock(new ThematicBreak()); $context->setBlocksParsed(true); return true; }
/** * @param \League\CommonMark\ContextInterface $context * @param \League\CommonMark\Cursor $cursor * * @return bool */ public function parse(\League\CommonMark\ContextInterface $context, \League\CommonMark\Cursor $cursor) { $line = $cursor->getLine(); //either a line starting with 'example:' (expected to have a set of links) //or a line starting with [example] (a single link) //remove potential markdown formatting (except what we need) $check = preg_replace('#[^a-z:\\[\\]]#', '', strtolower($line)); if (substr($check, 0, 8) != 'example:' and substr($check, 0, 9) != '[example]') { return false; } $context->addBlock(new ExampleElement($cursor->getLine(), $this->path)); $cursor->advanceBy(strlen($line)); $context->setBlocksParsed(true); return true; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { if (!$cursor->isIndented()) { return false; } if ($context->getTip() instanceof Paragraph) { return false; } if ($cursor->isBlank()) { return false; } $cursor->advanceBy(Cursor::INDENT_LEVEL, true); $context->addBlock(new IndentedCode()); return true; }
public function parse(ContextInterface $context, Cursor $cursor) { $state = $cursor->saveState(); $attributes = AttributesUtils::parse($cursor); if (empty($attributes)) { return false; } if (null !== $cursor->getFirstNonSpaceCharacter()) { $cursor->restoreState($state); return false; } $context->addBlock(new Attributes($attributes)); $context->setBlocksParsed(true); return true; }
/** * Attempt to parse a link reference, modifying the refmap. * * @param Cursor $cursor * * @return bool */ public function parse(Cursor $cursor) { if ($cursor->isAtEnd()) { return false; } $initialState = $cursor->saveState(); $matchChars = LinkParserHelper::parseLinkLabel($cursor); if ($matchChars === 0) { $cursor->restoreState($initialState); return false; } // We need to trim the opening and closing brackets from the previously-matched text $label = substr($cursor->getPreviousText(), 1, -1); if (preg_match('/[^\\s]/', $label) === 0) { $cursor->restoreState($initialState); return false; } if ($cursor->getCharacter() !== ':') { $cursor->restoreState($initialState); return false; } // Advance past the colon $cursor->advance(); // Link URL $cursor->advanceToFirstNonSpace(); $destination = LinkParserHelper::parseLinkDestination($cursor); if (empty($destination)) { $cursor->restoreState($initialState); return false; } $previousState = $cursor->saveState(); $cursor->advanceToFirstNonSpace(); $title = LinkParserHelper::parseLinkTitle($cursor); if ($title === null) { $title = ''; $cursor->restoreState($previousState); } // Make sure we're at line end: if ($cursor->match('/^ *(?:\\n|$)/') === null) { $cursor->restoreState($initialState); return false; } if (!$this->referenceMap->contains($label)) { $reference = new Reference($label, $destination, $title); $this->referenceMap->addReference($reference); } return true; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { if (!in_array($cursor->getFirstNonSpaceCharacter(), IconBlock::getIconBlockTypes()) || $cursor->getCharacter($cursor->getFirstNonSpacePosition() + 1) !== '>') { return false; } $type = $cursor->getFirstNonSpaceCharacter(); $cursor->advanceToFirstNonSpace(); if ($cursor->peek() === '>') { $cursor->advanceBy(2); if ($cursor->getCharacter() === ' ') { $cursor->advance(); } } $context->addBlock(new IconBlock($type)); return true; }
public function parse(ContextInterface $context, Cursor $cursor) { $container = $context->getContainer(); if (!$container instanceof Paragraph) { return false; } $lines = $container->getStrings(); if (count($lines) < 1) { return false; } $match = RegexHelper::matchAll(self::REGEXP_DEFINITION, $cursor->getLine(), $cursor->getFirstNonSpacePosition()); if (null === $match) { return false; } $columns = $this->parseColumns($match); $head = $this->parseRow(trim(array_pop($lines)), $columns, TableCell::TYPE_HEAD); if (null === $head) { return false; } $table = new Table(function (Cursor $cursor) use(&$table, $columns) { $row = $this->parseRow($cursor->getLine(), $columns); if (null === $row) { if (null !== $table->getCaption()) { return false; } if (null !== ($caption = $this->parseCaption($cursor->getLine()))) { $table->setCaption($caption); return true; } return false; } $table->getBody()->appendChild($row); return true; }); $table->getHead()->appendChild($head); if (count($lines) >= 1) { $paragraph = new Paragraph(); foreach ($lines as $line) { $paragraph->addLine($line); } $context->replaceContainerBlock($paragraph); $context->addBlock($table); } else { $context->replaceContainerBlock($table); } return true; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { if ($cursor->isIndented()) { return false; } if (!$context->getContainer() instanceof Paragraph) { return false; } $match = RegexHelper::matchAll('/^(?:=+|-+)[ \\t]*$/', $cursor->getLine(), $cursor->getFirstNonSpacePosition()); if ($match === null) { return false; } $level = $match[0][0] === '=' ? 1 : 2; $strings = $context->getContainer()->getStrings(); $context->replaceContainerBlock(new Heading($level, $strings)); return true; }
/** * @param $string * @param $expected * * @dataProvider dataForTestParse */ public function testParse($string, $expected) { $cursor = new Cursor($string); // Move to just before the first tilde pair $first_tilde_pos = mb_strpos($string, '~~', null, 'utf-8'); $cursor->advanceBy($first_tilde_pos); $inline_context = new InlineParserContext($cursor); $context_stub = $this->getMock('League\\CommonMark\\ContextInterface'); $parser = new StrikethroughParser(); $parser->parse($context_stub, $inline_context); $inlines = $inline_context->getInlines(); $this->assertCount(1, $inlines); $this->assertTrue($inlines->first() instanceof Strikethrough); /** @var Strikethrough $text */ $text = $inlines->first(); $this->assertEquals($expected, $text->getContent()); }
/** * @param Cursor $cursor * @param int $markerLength * * @return int */ private function calculateListMarkerPadding(Cursor $cursor, $markerLength) { $start = $cursor->saveState(); $spacesStartCol = $cursor->getColumn(); while ($cursor->getColumn() - $spacesStartCol < 5) { if (!$cursor->advanceBySpaceOrTab()) { break; } } $blankItem = $cursor->peek() === null; $spacesAfterMarker = $cursor->getColumn() - $spacesStartCol; if ($spacesAfterMarker >= 5 || $spacesAfterMarker < 1 || $blankItem) { $cursor->restoreState($start); $cursor->advanceBySpaceOrTab(); return $markerLength + 1; } return $markerLength + $spacesAfterMarker; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { if ($cursor->getFirstNonSpaceCharacter() !== 'A' || $cursor->getCharacter($cursor->getFirstNonSpacePosition() + 1) !== '>') { return false; } $cursor->advanceToFirstNonSpace(); if ($cursor->peek() === '>') { $cursor->advanceBy(2); if ($cursor->getCharacter() === ' ') { $cursor->advance(); } } $context->addBlock(new Aside()); return true; }
public function matchesNextLine(Cursor $cursor) { if ($cursor->getIndent() <= 3 && in_array($cursor->getFirstNonSpaceCharacter(), static::getIconBlockTypes())) { $cursor->advanceToFirstNonSpace(); if ($cursor->peek() === '>') { $cursor->advanceBy(2); if ($cursor->getCharacter() === ' ') { $cursor->advance(); } return true; } } return false; }
public function matchesNextLine(Cursor $cursor) { if (!$cursor->isIndented() && $cursor->getFirstNonSpaceCharacter() === '>') { $cursor->advanceToFirstNonSpace(); $cursor->advance(); if ($cursor->getCharacter() === ' ') { $cursor->advance(); } return true; } return false; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { if ($cursor->isIndented()) { return false; } if ($cursor->getFirstNonSpaceCharacter() !== '>') { return false; } $cursor->advanceToFirstNonSpace(); $cursor->advance(); if ($cursor->getCharacter() === ' ') { $cursor->advance(); } $context->addBlock(new BlockQuote()); return true; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { if ($cursor->isIndented()) { return false; } $previousState = $cursor->saveState(); $cursor->advanceToFirstNonSpace(); $fence = $cursor->match('/^\\[TOC\\]/'); if (is_null($fence)) { $cursor->restoreState($previousState); return false; } $context->addBlock(new TableOfContents()); return true; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { if ($cursor->isIndented()) { return false; } $match = RegexHelper::matchAt(RegexHelper::getInstance()->getHRuleRegex(), $cursor->getLine(), $cursor->getFirstNonSpacePosition()); if ($match === null) { return false; } // Advance to the end of the string, consuming the entire line (of the horizontal rule) $cursor->advanceBy(mb_strlen($cursor->getRemainder())); $context->addBlock(new HorizontalRule()); $context->setBlocksParsed(true); return true; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { if ($cursor->isIndented()) { return false; } $match = RegexHelper::matchAll('/^#{1,6}(?: +|$)/', $cursor->getLine(), $cursor->getFirstNonSpacePosition()); if (!$match) { return false; } $cursor->advanceToFirstNonSpace(); $cursor->advanceBy(strlen($match[0])); $level = strlen(trim($match[0])); $str = $cursor->getRemainder(); $str = preg_replace('/^ *#+ *$/', '', $str); $str = preg_replace('/ +#+ *$/', '', $str); $context->addBlock(new Heading($level, $str)); $context->setBlocksParsed(true); return true; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { if ($cursor->isIndented()) { return false; } if ($cursor->getFirstNonSpaceCharacter() !== '<') { return false; } $savedState = $cursor->saveState(); $cursor->advanceToFirstNonSpace(); $line = $cursor->getRemainder(); for ($blockType = 1; $blockType <= 7; $blockType++) { $match = RegexHelper::matchAt(RegexHelper::getHtmlBlockOpenRegex($blockType), $line); if ($match !== null && ($blockType < 7 || !$context->getContainer() instanceof Paragraph)) { $cursor->restoreState($savedState); $context->addBlock(new HtmlBlock($blockType)); $context->setBlocksParsed(true); return true; } } $cursor->restoreState($savedState); return false; }
/** * @param Cursor $cursor * @param int $markerLength * * @return int */ private function calculateListMarkerPadding(Cursor $cursor, $markerLength) { $start = $cursor->saveState(); $spacesStartCol = $cursor->getColumn(); do { $cursor->advanceBy(1, true); $nextChar = $cursor->getCharacter(); } while ($cursor->getColumn() - $spacesStartCol < 5 && ($nextChar === ' ' || $nextChar === "\t")); $blankItem = $cursor->peek() === null; $spacesAfterMarker = $cursor->getColumn() - $spacesStartCol; if ($spacesAfterMarker >= 5 || $spacesAfterMarker < 1 || $blankItem) { $cursor->restoreState($start); if ($cursor->peek() === ' ') { $cursor->advanceBy(1, true); } return $markerLength + 1; } return $markerLength + $spacesAfterMarker; }