/** * @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; }
private function incorporateLine(ContextInterface $context) { $cursor = new Cursor($context->getLine()); $context->getBlockCloser()->resetTip(); $context->setBlocksParsed(false); $context->setContainer($context->getDocument()); while ($context->getContainer()->hasChildren()) { $lastChild = $context->getContainer()->getLastChild(); if (!$lastChild->isOpen()) { break; } $context->setContainer($lastChild); if (!$context->getContainer()->matchesNextLine($cursor)) { $context->setContainer($context->getContainer()->getParent()); // back up to the last matching block break; } } $context->getBlockCloser()->setLastMatchedContainer($context->getContainer()); // Check to see if we've hit 2nd blank line; if so break out of list: if ($cursor->isBlank() && $context->getContainer()->endsWithBlankLine()) { $this->breakOutOfLists($context, $context->getContainer()); } while (!$context->getContainer()->isCode() && !$context->getBlocksParsed()) { $parsed = false; foreach ($this->environment->getBlockParsers() as $parser) { if ($parser->parse($context, $cursor)) { $parsed = true; break; } } if (!$parsed || $context->getContainer()->acceptsLines()) { $context->setBlocksParsed(true); } } // What remains at the offset is a text line. Add the text to the appropriate container. // First check for a lazy paragraph continuation: if (!$context->getBlockCloser()->areAllClosed() && !$cursor->isBlank() && $context->getTip() instanceof Paragraph && count($context->getTip()->getStrings()) > 0) { // lazy paragraph continuation $context->getTip()->addLine($cursor->getRemainder()); } else { // not a lazy continuation // finalize any blocks not matched $context->getBlockCloser()->closeUnmatchedBlocks(); // Determine whether the last line is blank, updating parents as needed $context->getContainer()->setLastLineBlank($cursor, $context->getLineNumber()); // Handle any remaining cursor contents if ($context->getContainer()->isOpen()) { $context->getContainer()->handleRemainingContents($context, $cursor); } elseif (!$cursor->isBlank()) { // Create paragraph container for line $context->addBlock(new Paragraph()); $cursor->advanceToFirstNonSpace(); $context->getTip()->addLine($cursor->getRemainder()); } } }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { if ($cursor->isIndented() && !$context->getContainer() instanceof ListBlock) { return false; } $tmpCursor = clone $cursor; $tmpCursor->advanceToFirstNonSpace(); $rest = $tmpCursor->getRemainder(); $data = new ListData(); $data->markerOffset = $cursor->getIndent(); if ($matches = RegexHelper::matchAll('/^[*+-]/', $rest)) { $data->type = ListBlock::TYPE_UNORDERED; $data->delimiter = null; $data->bulletChar = $matches[0][0]; } elseif (($matches = RegexHelper::matchAll('/^(\\d{1,9})([.)])/', $rest)) && (!$context->getContainer() instanceof Paragraph || $matches[1] === '1')) { $data->type = ListBlock::TYPE_ORDERED; $data->start = intval($matches[1]); $data->delimiter = $matches[2]; $data->bulletChar = null; } else { return false; } $markerLength = strlen($matches[0]); // Make sure we have spaces after $nextChar = $tmpCursor->peek($markerLength); if (!($nextChar === null || $nextChar === "\t" || $nextChar === ' ')) { return false; } // If it interrupts paragraph, make sure first line isn't blank if ($context->getContainer() instanceof Paragraph && !RegexHelper::matchAt(RegexHelper::REGEX_NON_SPACE, $rest, $markerLength)) { return false; } // We've got a match! Advance offset and calculate padding $cursor->advanceToFirstNonSpace(); // to start of marker $cursor->advanceBy($markerLength, true); // to end of marker $data->padding = $this->calculateListMarkerPadding($cursor, $markerLength); // 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->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; }
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 ($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 ContextInterface $context * @param Cursor $cursor */ public function handleRemainingContents(ContextInterface $context, Cursor $cursor) { /** @var FencedCode $container */ $container = $context->getContainer(); // check for closing code fence if ($cursor->getIndent() <= 3 && $cursor->getFirstNonSpaceCharacter() == $container->getChar()) { $match = RegexHelper::matchAll('/^(?:`{3,}|~{3,})(?= *$)/', $cursor->getLine(), $cursor->getFirstNonSpacePosition()); if (strlen($match[0]) >= $container->getLength()) { // don't add closing fence to container; instead, close it: $this->setLength(-1); // -1 means we've passed closer return; } } $context->getTip()->addLine($cursor->getRemainder()); }
/** * Parse blocks * * @param ContextInterface $context * @param Cursor $cursor */ private function parseBlocks(ContextInterface $context, Cursor $cursor) { while (!$context->getContainer()->isCode() && !$context->getBlocksParsed()) { $parsed = false; foreach ($this->environment->getBlockParsers() as $parser) { if ($parser->parse($context, $cursor)) { $parsed = true; break; } } if (!$parsed || $context->getContainer()->acceptsLines()) { $context->setBlocksParsed(true); } } }
/** * @param ContextInterface $context * @param Cursor $cursor */ private function setAndPropagateLastLineBlank(ContextInterface $context, $cursor) { if ($cursor->isBlank() && ($lastChild = $context->getContainer()->lastChild())) { if ($lastChild instanceof AbstractBlock) { $lastChild->setLastLineBlank(true); } } $container = $context->getContainer(); $lastLineBlank = $container->shouldLastLineBeBlank($cursor, $context->getLineNumber()); // Propagate lastLineBlank up through parents: while ($container) { $container->setLastLineBlank($lastLineBlank); $container = $container->parent(); } }