/** * 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; }
/** * Returns the position of the next non-space character * * @return int */ public function getFirstNonSpacePosition() { if ($this->firstNonSpaceCache === null) { $match = RegexHelper::matchAt('/[^ ]/', $this->line, $this->currentPosition); $this->firstNonSpaceCache = $match === null ? $this->length : $match; } return $this->firstNonSpaceCache; }
/** * @param ContextInterface $context * @param InlineParserContext $inlineContext * * @return bool */ public function parse(ContextInterface $context, InlineParserContext $inlineContext) { $cursor = $inlineContext->getCursor(); if ($m = $cursor->match(RegexHelper::getInstance()->getHtmlTagRegex())) { $inlineContext->getInlines()->add(new Html($m)); return true; } return false; }
/** * @param InlineParserContext $inlineContext * * @return bool */ public function parse(InlineParserContext $inlineContext) { $cursor = $inlineContext->getCursor(); if ($m = $cursor->match(RegexHelper::getInstance()->getHtmlTagRegex())) { $inlineContext->getContainer()->appendChild(new HtmlInline($m)); return true; } 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; }
public static function parse(Cursor $cursor) { if (null === self::$regexp) { $regex = RegexHelper::getInstance(); self::$regexp = sprintf('/^\\s*([.#][_a-z0-9-]+|%s%s)(?<!})\\s*/i', $regex->getPartialRegex(RegexHelper::ATTRIBUTENAME), $regex->getPartialRegex(RegexHelper::ATTRIBUTEVALUESPEC)); } $state = $cursor->saveState(); $cursor->advanceToFirstNonSpace(); if ('{' !== $cursor->getCharacter()) { $cursor->restoreState($state); return []; } $cursor->advanceBy(1); if (':' === $cursor->getCharacter()) { $cursor->advanceBy(1); } $attributes = []; while ($attribute = trim($cursor->match(self::$regexp))) { if ('#' === $attribute[0]) { $attributes['id'] = substr($attribute, 1); continue; } if ('.' === $attribute[0]) { $attributes['class'][] = substr($attribute, 1); continue; } list($name, $value) = explode('=', $attribute, 2); $first = $value[0]; $last = substr($value, -1); if (('"' === $first && '"' === $last || "'" === $first && "'" === $last) && strlen($value) > 1) { $value = substr($value, 1, -1); } if ('class' === strtolower(trim($name))) { foreach (array_filter(explode(' ', trim($value))) as $class) { $attributes['class'][] = $class; } } else { $attributes[trim($name)] = trim($value); } } if (0 === $cursor->advanceWhileMatches('}')) { $cursor->restoreState($state); return []; } if (isset($attributes['class'])) { $attributes['class'] = implode(' ', $attributes['class']); } return $attributes; }
/** * @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->advanceBy(mb_strlen($cursor->getRemainder())); $context->addBlock(new ThematicBreak()); $context->setBlocksParsed(true); return true; }
private function parseRow($line, array $columns, $type = TableCell::TYPE_BODY) { $cells = RegexHelper::matchAll(self::REGEXP_CELLS, $line); if (null === $cells || !is_array($cells[0])) { return; } $row = new TableRow(); foreach ($cells[0] as $i => $cell) { $row->addChild(new TableCell(trim($cell), $type, isset($columns[$i]) ? $columns[$i] : null)); } for ($j = count($columns) - 1; $j > $i; --$j) { $row->addChild(new TableCell('', $type, null)); } return $row; }
/** * @param ContextInterface $context * @param Cursor $cursor * * @return bool */ public function parse(ContextInterface $context, Cursor $cursor) { $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 Header($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() && !$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 Link $inline * @param ElementRendererInterface $htmlRenderer * * @return HtmlElement */ public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer) { if (!$inline instanceof Link) { throw new \InvalidArgumentException('Incompatible inline type: ' . get_class($inline)); } $attrs = []; foreach ($inline->getData('attributes', []) as $key => $value) { $attrs[$key] = $htmlRenderer->escape($value, true); } if (!($this->config->getConfig('safe') && RegexHelper::isLinkPotentiallyUnsafe($inline->getUrl()))) { $attrs['href'] = $htmlRenderer->escape($inline->getUrl(), true); } if (isset($inline->data['title'])) { $attrs['title'] = $htmlRenderer->escape($inline->data['title'], true); } return new HtmlElement('a', $attrs, $htmlRenderer->renderInlines($inline->children())); }
/** * @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; }
private function buildLink($inline, $htmlRenderer) { $attrs = []; foreach ($inline->getData('attributes', []) as $key => $value) { $attrs[$key] = $htmlRenderer->escape($value, true); } if (!($this->config->getConfig('safe') && RegexHelper::isLinkPotentiallyUnsafe($inline->getUrl()))) { $attrs['href'] = $htmlRenderer->escape($inline->getUrl(), true); } if (isset($inline->data['title'])) { $attrs['title'] = $htmlRenderer->escape($inline->data['title'], true); } if ($this->isExternalUrl($inline->getUrl())) { $attrs['rel'] = 'nofollow'; $attrs['target'] = '_blank'; } return new HtmlElement('a', $attrs, $htmlRenderer->renderInlines($inline->children())); }
/** * @param Image $inline * @param ElementRendererInterface $htmlRenderer * * @return HtmlElement */ public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer) { if (!$inline instanceof Image) { throw new \InvalidArgumentException('Incompatible inline type: ' . get_class($inline)); } $attrs = []; foreach ($inline->getData('attributes', []) as $key => $value) { $attrs[$key] = $htmlRenderer->escape($value, true); } if ($this->config->getConfig('safe') && RegexHelper::isLinkPotentiallyUnsafe($inline->getUrl())) { $attrs['src'] = ''; } else { $attrs['src'] = $htmlRenderer->escape($inline->getUrl(), true); } $alt = $htmlRenderer->renderInlines($inline->children()); $alt = preg_replace('/\\<[^>]*alt="([^"]*)"[^>]*\\>/', '$1', $alt); $attrs['alt'] = preg_replace('/\\<[^>]*\\>/', '', $alt); if (isset($inline->data['title'])) { $attrs['title'] = $htmlRenderer->escape($inline->data['title'], true); } return new HtmlElement('img', $attrs, '', 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; }
private function parseCaption($line) { $caption = RegexHelper::matchAll(self::REGEXP_CAPTION, $line); if (null === $caption) { return; } return new TableCaption($caption[1], $caption[2]); }
/** * @param ContextInterface $context * @param Cursor $cursor */ public function handleRemainingContents(ContextInterface $context, Cursor $cursor) { $context->getTip()->addLine($cursor->getRemainder()); // Check for end condition if ($this->type >= self::TYPE_1_CODE_CONTAINER && $this->type <= self::TYPE_5_CDATA) { if ($cursor->match(RegexHelper::getHtmlBlockCloseRegex($this->type)) !== null) { $this->finalize($context, $context->getLineNumber()); } } }
/** * @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()); }