gl-website-deployer/vendor/doctrine/rst-parser/lib/Parser/LineDataParser.php

235 lines
8.0 KiB
PHP
Raw Normal View History

2024-11-19 08:02:04 +01:00
<?php
declare(strict_types=1);
namespace Doctrine\RST\Parser;
use Doctrine\Common\EventManager;
use Doctrine\RST\Event\OnLinkParsedEvent;
use Doctrine\RST\Nodes\ParagraphNode;
use Doctrine\RST\Nodes\SpanNode;
use Doctrine\RST\Parser;
use function array_map;
use function array_shift;
use function count;
use function explode;
use function ltrim;
use function mb_strlen;
use function preg_match;
use function strlen;
use function substr;
use function trim;
final class LineDataParser
{
/** @var Parser */
private $parser;
/** @var EventManager */
private $eventManager;
public function __construct(Parser $parser, EventManager $eventManager)
{
$this->parser = $parser;
$this->eventManager = $eventManager;
}
public function parseLink(string $line): ?Link
{
// Links
if (preg_match('/^\.\. _`(.+)`: (.+)$/mUsi', $line, $match) > 0) {
return $this->createLink($match[1], $match[2], Link::TYPE_LINK);
}
// anonymous links
if (preg_match('/^\.\. _(.+): (.+)$/mUsi', $line, $match) > 0) {
return $this->createLink($match[1], $match[2], Link::TYPE_LINK);
}
// Short anonymous links
if (preg_match('/^__ (.+)$/mUsi', trim($line), $match) > 0) {
$url = $match[1];
return $this->createLink('_', $url, Link::TYPE_LINK);
}
// Anchor links - ".. _`anchor-link`:"
if (preg_match('/^\.\. _`(.+)`:$/mUsi', trim($line), $match) > 0) {
$anchor = $match[1];
return new Link($anchor, '#' . $anchor, Link::TYPE_ANCHOR);
}
if (preg_match('/^\.\. _(.+):$/mUsi', trim($line), $match) > 0) {
$anchor = $match[1];
return $this->createLink($anchor, '#' . $anchor, Link::TYPE_ANCHOR);
}
return null;
}
private function createLink(string $name, string $url, string $type): Link
{
$this->eventManager->dispatchEvent(
OnLinkParsedEvent::ON_LINK_PARSED,
new OnLinkParsedEvent($url, $type, $this->parser->getEnvironment()->getCurrentFileName())
);
return new Link($name, $url, $type);
}
public function parseDirectiveOption(string $line): ?DirectiveOption
{
if (preg_match('/^(\s+):(.+): (.*)$/mUsi', $line, $match) > 0) {
return new DirectiveOption($match[2], trim($match[3]));
}
if (preg_match('/^(\s+):(.+):(\s*)$/mUsi', $line, $match) > 0) {
$value = trim($match[3]);
return new DirectiveOption($match[2], true);
}
return null;
}
public function parseDirective(string $line): ?Directive
{
if (preg_match('/^\.\. (\|(.+)\| |)([^\s]+)::( (.*)|)$/mUsi', $line, $match) > 0) {
return new Directive(
$match[2],
$match[3],
trim($match[4])
);
}
return null;
}
/**
* @param string[] $lines
*
* @return ListItem[]
*/
public function parseList(array $lines): array
{
$list = [];
$currentItem = null;
$currentPrefix = null;
$currentOffset = 0;
$createListItem = function (string $item, string $prefix): ListItem {
// parse any markup in the list item (e.g. sublists, directives)
$nodes = $this->parser->getSubParser()->parseLocal($item)->getNodes();
if (count($nodes) === 1 && $nodes[0] instanceof ParagraphNode) {
// if there is only one paragraph node, the value is put directly in the <li> element
$nodes = [$nodes[0]->getValue()];
}
return new ListItem($prefix, mb_strlen($prefix) > 1, $nodes);
};
foreach ($lines as $line) {
if (preg_match(LineChecker::LIST_MARKER, $line, $m) > 0) {
// a list marker indicates the start of a new list item,
// complete the previous one and start a new one
if ($currentItem !== null) {
$list[] = $createListItem($currentItem, $currentPrefix);
}
$currentOffset = strlen($m[0]);
$currentPrefix = $m[1];
$currentItem = substr($line, $currentOffset) . "\n";
continue;
}
// the list item offset is determined by the offset of the first text
if (trim($currentItem) === '') {
$currentOffset = strlen($line) - strlen(ltrim($line));
}
$currentItem .= substr($line, $currentOffset) . "\n";
}
if ($currentItem !== null) {
$list[] = $createListItem($currentItem, $currentPrefix);
}
return $list;
}
/** @param string[] $lines */
public function parseDefinitionList(array $lines): DefinitionList
{
/** @var array{term: SpanNode, classifiers: list<SpanNode>, definition: string}|null $definitionListTerm */
$definitionListTerm = null;
$definitionList = [];
$createDefinitionTerm = function (array $definitionListTerm): DefinitionListTerm {
// parse any markup in the definition (e.g. lists, directives)
$definitionNodes = $this->parser->getSubParser()->parseLocal($definitionListTerm['definition'])->getNodes();
if (count($definitionNodes) === 1 && $definitionNodes[0] instanceof ParagraphNode) {
// if there is only one paragraph node, the value is put directly in the <dd> element
$definitionNodes = [$definitionNodes[0]->getValue()];
} else {
// otherwise, .first and .last are added to the first and last nodes of the definition
$definitionNodes[0]->setClasses($definitionNodes[0]->getClasses() + ['first']);
$definitionNodes[count($definitionNodes) - 1]->setClasses($definitionNodes[count($definitionNodes) - 1]->getClasses() + ['last']);
}
return new DefinitionListTerm(
$definitionListTerm['term'],
$definitionListTerm['classifiers'],
$definitionNodes
);
};
$currentOffset = 0;
foreach ($lines as $key => $line) {
// indent or empty line = term definition line
if ($definitionListTerm !== null && (trim($line) === '') || $line[0] === ' ') {
if ($currentOffset === 0) {
// first line of a definition determines the indentation offset
$definition = ltrim($line);
$currentOffset = strlen($line) - strlen($definition);
} else {
$definition = substr($line, $currentOffset);
}
$definitionListTerm['definition'] .= $definition . "\n";
// non empty string at the start of the line = definition term
} elseif (trim($line) !== '') {
// we are starting a new term so if we have an existing
// term with definitions, add it to the definition list
if ($definitionListTerm !== null) {
$definitionList[] = $createDefinitionTerm($definitionListTerm);
}
$parts = explode(' : ', trim($line));
$term = array_shift($parts);
$classifiers = array_map(function (string $classifier): SpanNode {
return $this->parser->createSpanNode($classifier);
}, array_map('trim', $parts));
$currentOffset = 0;
$definitionListTerm = [
'term' => $this->parser->createSpanNode($term),
'classifiers' => $classifiers,
'definition' => '',
];
}
}
// append the last definition of the list
if ($definitionListTerm !== null) {
$definitionList[] = $createDefinitionTerm($definitionListTerm);
}
return new DefinitionList($definitionList);
}
}