From d988326a107472aed36a7594fce6dd7d714078f5 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 15 Aug 2024 09:41:30 +0400 Subject: [PATCH 1/3] Location detection for links with inline nodes. --- .../src/Markdown/Nodes/Locator/Parser.php | 102 ++++++++++++------ .../Nodes/Locator/ParserTest~document.md | 2 +- .../Nodes/Locator/ParserTest~expected.xml | 99 +++++++++-------- .../src/Markdown/Nodes/RendererWrapper.php | 21 ++-- 4 files changed, 137 insertions(+), 87 deletions(-) diff --git a/packages/documentator/src/Markdown/Nodes/Locator/Parser.php b/packages/documentator/src/Markdown/Nodes/Locator/Parser.php index 177f86182..e6440f7ef 100644 --- a/packages/documentator/src/Markdown/Nodes/Locator/Parser.php +++ b/packages/documentator/src/Markdown/Nodes/Locator/Parser.php @@ -7,16 +7,15 @@ use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Location; use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Offset; use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Padding; -use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Coordinate; use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Locator; use LastDragon_ru\LaraASP\Documentator\Markdown\Nodes\Aware; use LastDragon_ru\LaraASP\Documentator\Markdown\Utils; use LastDragon_ru\LaraASP\Documentator\Utils\Text; use League\CommonMark\Delimiter\DelimiterInterface; -use League\CommonMark\Delimiter\DelimiterStack; use League\CommonMark\Environment\Environment; use League\CommonMark\Environment\EnvironmentAwareInterface; use League\CommonMark\Extension\CommonMark\Parser\Inline\CloseBracketParser; +use League\CommonMark\Extension\Footnote\Node\FootnoteRef; use League\CommonMark\Extension\Table\TableCell; use League\CommonMark\Node\Node; use League\CommonMark\Parser\Inline\InlineParserInterface; @@ -24,7 +23,6 @@ use League\CommonMark\Parser\InlineParserContext; use League\Config\ConfigurationAwareInterface; use Override; -use ReflectionProperty; use WeakMap; use function array_slice; @@ -49,7 +47,8 @@ class Parser implements InlineParserInterface, EnvironmentAwareInterface, Config use Aware; /** - * @var WeakMap + * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingTraversableTypeHintSpecification https://github.com/slevomat/coding-standard/issues/1692 + * @var WeakMap */ private WeakMap $incomplete; @@ -74,30 +73,34 @@ public function parse(InlineParserContext $inlineContext): bool { // The `$cursor->getPosition()` depends on delimiters length, we need to // find it. Not sure that this is the best way... $cursor = $inlineContext->getCursor(); - $offset = $cursor->getPosition(); + $origin = $cursor->getPosition() + 1; + $offset = match (true) { + $this->parser instanceof CloseBracketParser => $this->getDelimiterOffset($inlineContext, ['[', '!']), + default => $cursor->getPosition(), + }; - if ($this->parser instanceof CloseBracketParser) { - $offset = $offset - - $this->getDelimiterStackLength($inlineContext->getDelimiterStack()) // delimiters length - - mb_strlen($cursor->getPreviousText()); // text after delimiter - } - - // Parse + // Parsed? $parsed = $this->parser->parse($inlineContext); if (!$parsed) { return false; } - // Detect Location + // Child? $container = $inlineContext->getContainer(); + $child = $container->lastChild(); + + if (!$child) { + return true; + } + + // Detect Location $startLine = $container->getStartLine(); $endLine = $container->getEndLine(); $length = $cursor->getPosition() - $offset; - $child = $container->lastChild(); $line = $cursor->getLine(); - if ($child !== null && $startLine !== null && $endLine !== null) { + if ($startLine !== null && $endLine !== null) { $start = $line; if ($startLine !== $endLine) { @@ -112,7 +115,9 @@ public function parse(InlineParserContext $inlineContext): bool { $start = (end($beforeLines) ?: '').(reset($inlineLines) ?: ''); if ($beforeLinesCount) { - $offset -= (mb_strlen(implode("\n", array_slice($beforeLines, 0, -1))) + 1); + $correction = (mb_strlen(implode("\n", array_slice($beforeLines, 0, -1))) + 1); + $offset -= $correction; + $origin -= $correction; } if ($startLine !== $endLine) { @@ -123,17 +128,26 @@ public function parse(InlineParserContext $inlineContext): bool { $padding = Utils::getPadding($child, $startLine, $start); if ($padding !== null) { - Data::set($child, new Location(new Locator($startLine, $endLine, $offset, $length, $padding))); + $this->save($child, $startLine, $endLine, $offset, $length, $padding, $origin - $offset); } - } elseif ($child !== null && $container instanceof TableCell) { + } elseif ($container instanceof TableCell) { // The properties of the `TableCell` is not known yet (v2.4.2), we // should wait until parsing is complete. // // Also, escaped `|` passed down to inline parsing as an unescaped // pipe character. It leads to invalid `$offset`/`$length`. + $origin += mb_substr_count(mb_substr($line, 0, $origin), '|'); $offset += mb_substr_count(mb_substr($line, 0, $offset), '|'); $length += mb_substr_count(mb_substr($line, $offset, $length), '|'); - $this->incomplete[$child] = new Coordinate(-1, $offset, $length); + $this->incomplete[$child] = new class($origin - $offset, $offset, $length) { + public function __construct( + public readonly int $origin, + public readonly int $offset, + public readonly int $length, + ) { + // empty + } + }; } else { // empty } @@ -170,17 +184,14 @@ public function finalize(): void { } // Set - Data::set( + $this->save( $node, - new Location( - new Locator( - $blockStartLine, - $blockEndLine, - $coordinate->offset + $offset, - $coordinate->length, - $blockPadding + $cellPadding, - ), - ), + $blockStartLine, + $blockEndLine, + $coordinate->offset + $offset, + $coordinate->length, + $blockPadding + $cellPadding, + $coordinate->origin, ); } @@ -188,12 +199,35 @@ public function finalize(): void { $this->incomplete = new WeakMap(); } - private function getDelimiterStackLength(DelimiterStack $stack): int { - $delimiter = (new ReflectionProperty($stack, 'top'))->getValue($stack); - $length = $delimiter instanceof DelimiterInterface - ? mb_strlen($delimiter->getInlineNode()->getLiteral()) - : 0; + /** + * @param list $characters + */ + private function getDelimiterOffset(InlineParserContext $context, array $characters): int { + $delimiter = $context->getDelimiterStack()->searchByCharacter($characters); + $length = 0; + + if ($delimiter instanceof DelimiterInterface && $delimiter->isActive()) { + // We do not use `$delimiter->getLength()` here because `$delimiter->getIndex()` + // seems incorrect for some delimiters e.g. for `![`. + $length = (int) $delimiter->getIndex() - mb_strlen($delimiter->getInlineNode()->getLiteral()); + } return $length; } + + private function save( + Node $child, + int $startLine, + int $endLine, + int $offset, + ?int $length, + int $padding, + int $origin, + ): void { + Data::set($child, new Location(new Locator($startLine, $endLine, $offset, $length, $padding))); + + if (!($child instanceof FootnoteRef)) { + Data::set($child, new Offset($origin)); + } + } } diff --git a/packages/documentator/src/Markdown/Nodes/Locator/ParserTest~document.md b/packages/documentator/src/Markdown/Nodes/Locator/ParserTest~document.md index b46e3a3c0..fc4bbec36 100644 --- a/packages/documentator/src/Markdown/Nodes/Locator/ParserTest~document.md +++ b/packages/documentator/src/Markdown/Nodes/Locator/ParserTest~document.md @@ -2,7 +2,7 @@ -Text text _**[link](https://example.com/)**_. +Text text _**[**_`link`_**](https://example.com/)**_ text [**_`link`](https://example.com/). Text text [link](https://example.com/)[^1] text [link](https://example.com/ "title") text[^1] text text text text [link][link] text text [link](https://example.com/) text text text text text text diff --git a/packages/documentator/src/Markdown/Nodes/Locator/ParserTest~expected.xml b/packages/documentator/src/Markdown/Nodes/Locator/ParserTest~expected.xml index 4d793ac98..642b26155 100644 --- a/packages/documentator/src/Markdown/Nodes/Locator/ParserTest~expected.xml +++ b/packages/documentator/src/Markdown/Nodes/Locator/ParserTest~expected.xml @@ -8,32 +8,41 @@ - - link + + + + link + + + + + **_ + link + . - + link - + link - + link - + link @@ -44,7 +53,7 @@ - + link @@ -59,7 +68,7 @@ - + link . @@ -68,7 +77,7 @@ - + link . @@ -77,7 +86,7 @@ - + link . @@ -92,7 +101,7 @@ - + link . @@ -101,7 +110,7 @@ Quote quote quote quote quote quote quote quote quote quote quote quote quote quote quote quote quote - + link @@ -111,7 +120,7 @@ - + link . @@ -124,12 +133,12 @@ - + Header - + Header ( - + link ) @@ -138,28 +147,28 @@ - + - + link - + Cell - + Cell - + - + link - + link . @@ -171,25 +180,25 @@
- + Header - + Header - + | - + link - + Cell @@ -201,20 +210,20 @@ - + image - + image - + image - + image @@ -223,25 +232,25 @@ - + image - + image - + image - + image @@ -249,22 +258,22 @@
- + Header - + Header - - + + image - + Cell @@ -291,14 +300,14 @@ - + link - + link @@ -308,12 +317,12 @@ - + link - + link . diff --git a/packages/documentator/src/Markdown/Nodes/RendererWrapper.php b/packages/documentator/src/Markdown/Nodes/RendererWrapper.php index 76d6e19c3..320b54d89 100644 --- a/packages/documentator/src/Markdown/Nodes/RendererWrapper.php +++ b/packages/documentator/src/Markdown/Nodes/RendererWrapper.php @@ -4,10 +4,11 @@ use LastDragon_ru\LaraASP\Documentator\Markdown\Data\BlockPadding; use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Data; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Length; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Offset; use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Padding; use LastDragon_ru\LaraASP\Documentator\Markdown\Utils; use League\CommonMark\Environment\EnvironmentAwareInterface; -use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; @@ -97,12 +98,18 @@ protected function location(Node $node): ?string { * @return array */ private function getXmlAdditionalAttributes(Node $node): array { - $attributes = []; - $attributes['location'] = $this->location($node); - $attributes['padding'] = Data::get($node, Padding::class); - - if ($node instanceof AbstractBlock) { - $attributes['blockPadding'] = Data::get($node, BlockPadding::class); + $attributes = [ + 'location' => $this->location($node), + ]; + $data = [ + 'offset' => Offset::class, + 'length' => Length::class, + 'padding' => Padding::class, + 'blockPadding' => BlockPadding::class, + ]; + + foreach ($data as $key => $class) { + $attributes[$key] = Data::get($node, $class); } return array_filter($attributes, static fn ($v) => $v !== null); From 063d1b3dd4a8baaf16f380bc8a674d9947c82bbf Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:39:06 +0400 Subject: [PATCH 2/3] `Locator` renamed to `Location` + will support internal padding. --- .../documentator/src/Markdown/Document.php | 5 +- packages/documentator/src/Markdown/Editor.php | 16 ++--- .../documentator/src/Markdown/EditorTest.php | 60 +++++++++---------- .../src/Markdown/Location/Coordinate.php | 1 + .../src/Markdown/Location/Location.php | 57 +++++++++++++++++- .../src/Markdown/Location/LocationTest.php | 51 ++++++++++++++++ .../src/Markdown/Location/Locator.php | 46 -------------- .../Markdown/Mutations/FootnotesPrefix.php | 3 +- .../Markdown/Mutations/ReferencesPrefix.php | 4 +- .../src/Markdown/Nodes/Locator/Parser.php | 20 +++---- .../Nodes/Reference/ParserContinue.php | 10 ++-- packages/documentator/src/Markdown/Utils.php | 31 +++++----- 12 files changed, 179 insertions(+), 125 deletions(-) create mode 100644 packages/documentator/src/Markdown/Location/LocationTest.php delete mode 100644 packages/documentator/src/Markdown/Location/Locator.php diff --git a/packages/documentator/src/Markdown/Document.php b/packages/documentator/src/Markdown/Document.php index bec21132f..031137266 100644 --- a/packages/documentator/src/Markdown/Document.php +++ b/packages/documentator/src/Markdown/Document.php @@ -9,7 +9,6 @@ use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Lines; use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Coordinate; use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location; -use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Locator; use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\FootnotesPrefix; use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\FootnotesRemove; use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\ReferencesInline; @@ -93,7 +92,7 @@ public function getBody(): ?string { $start = $summary?->getEndLine(); $end = array_key_last($this->getLines()); $body = $start !== null && is_int($end) - ? $this->getText(new Locator($start + 1, $end)) + ? $this->getText(new Location($start + 1, $end)) : null; $body = trim((string) $body) ?: null; @@ -234,7 +233,7 @@ private function getFirstNode(string $class, ?Closure $filter = null, ?Closure $ private function getBlockText(?AbstractBlock $node): ?string { $location = $node?->getStartLine() !== null && $node->getEndLine() !== null - ? new Locator($node->getStartLine(), $node->getEndLine()) + ? new Location($node->getStartLine(), $node->getEndLine()) : null; $text = $location ? $this->getText($location) diff --git a/packages/documentator/src/Markdown/Editor.php b/packages/documentator/src/Markdown/Editor.php index 80f20f138..76421abff 100644 --- a/packages/documentator/src/Markdown/Editor.php +++ b/packages/documentator/src/Markdown/Editor.php @@ -84,14 +84,14 @@ public function mutate(array $changes): static { $paddings = []; foreach ($changes as $change) { - [$coordinate, $padding, $text] = $change; - $line = $lines[$coordinate->line] ?? ''; - $prefix = mb_substr($line, 0, $coordinate->offset); - $suffix = $coordinate->length + [$coordinate, $text] = $change; + $line = $lines[$coordinate->line] ?? ''; + $prefix = mb_substr($line, 0, $coordinate->offset); + $suffix = $coordinate->length ? mb_substr($line, $coordinate->offset + $coordinate->length) : ''; - $lines[$coordinate->line] = $prefix.$text.$suffix; - $paddings[$coordinate->line] = $padding; + $lines[$coordinate->line] = $prefix.$text.$suffix; + $paddings[$coordinate->line] = $coordinate->padding; if ($text === null && !$suffix) { $lines[$coordinate->line] = trim($prefix); @@ -136,7 +136,7 @@ public function mutate(array $changes): static { /** * @param array $changes * - * @return list + * @return list */ protected function expand(array $changes): array { $expanded = []; @@ -153,7 +153,7 @@ protected function expand(array $changes): array { usort($coordinates, $sort); foreach ($coordinates as $coordinate) { - $expanded[] = [$coordinate, $location->getPadding(), $text[$line++] ?? null]; + $expanded[] = [$coordinate, $text[$line++] ?? null]; } // If `$text` contains more lines than `$coordinates` that means diff --git a/packages/documentator/src/Markdown/EditorTest.php b/packages/documentator/src/Markdown/EditorTest.php index 28b90c039..59799615d 100644 --- a/packages/documentator/src/Markdown/EditorTest.php +++ b/packages/documentator/src/Markdown/EditorTest.php @@ -3,7 +3,7 @@ namespace LastDragon_ru\LaraASP\Documentator\Markdown; use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Coordinate; -use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Locator; +use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location; use LastDragon_ru\LaraASP\Documentator\Testing\Package\TestCase; use Override; use PHPUnit\Framework\Attributes\CoversClass; @@ -33,11 +33,11 @@ public function testMutate(): void { ]; $editor = new Editor($lines); $changes = [ - [new Locator(1, 1, 2, 3), '123'], - [new Locator(2, 4, 4, 4), '123'], - [new Locator(6, 8, 4, 4), "123\n345"], - [new Locator(11, 12, 4, 3, 2), "123\n345"], - [new Locator(14, 15, 4, 3, 2), '123'], + [new Location(1, 1, 2, 3), '123'], + [new Location(2, 4, 4, 4), '123'], + [new Location(6, 8, 4, 4), "123\n345"], + [new Location(11, 12, 4, 3, 2), "123\n345"], + [new Location(14, 15, 4, 3, 2), '123'], ]; $actual = $editor->mutate($changes); $expected = [ @@ -73,19 +73,19 @@ public function removeOverlaps(array $changes): array { } }; $changes = [ - 0 => [new Locator(10, 10, 15, 10), 'a'], - 1 => [new Locator(10, 10, 10, null), 'b'], - 2 => [new Locator(12, 15, 5, 10), 'c'], - 3 => [new Locator(14, 15, 5, 10), 'd'], - 4 => [new Locator(17, 17, 5, 10), 'e'], - 5 => [new Locator(17, 17, 11, 10), 'f'], - 6 => [new Locator(18, 18, 5, 10), 'g'], + 0 => [new Location(10, 10, 15, 10), 'a'], + 1 => [new Location(10, 10, 10, null), 'b'], + 2 => [new Location(12, 15, 5, 10), 'c'], + 3 => [new Location(14, 15, 5, 10), 'd'], + 4 => [new Location(17, 17, 5, 10), 'e'], + 5 => [new Location(17, 17, 11, 10), 'f'], + 6 => [new Location(18, 18, 5, 10), 'g'], ]; $expected = [ - 1 => [new Locator(10, 10, 10, null), 'b'], - 3 => [new Locator(14, 15, 5, 10), 'd'], - 5 => [new Locator(17, 17, 11, 10), 'f'], - 6 => [new Locator(18, 18, 5, 10), 'g'], + 1 => [new Location(10, 10, 10, null), 'b'], + 3 => [new Location(14, 15, 5, 10), 'd'], + 5 => [new Location(17, 17, 11, 10), 'f'], + 6 => [new Location(18, 18, 5, 10), 'g'], ]; self::assertEquals($expected, $editor->removeOverlaps($changes)); @@ -102,18 +102,18 @@ public function expand(array $changes): array { } }; $changes = [ - [new Locator(1, 1, 5, 10), 'text'], - [new Locator(2, 3, 5, null), 'text'], - [new Locator(4, 5, 5, 5, 1), "text a\ntext b"], - [new Locator(6, 6, 5, 10, 2), "text a\ntext b"], + [new Location(1, 1, 5, 10), 'text'], + [new Location(2, 3, 5, null), 'text'], + [new Location(4, 5, 5, 5, 1), "text a\ntext b"], + [new Location(6, 6, 5, 10, 2), "text a\ntext b"], ]; $expected = [ - [new Coordinate(6, 7, 10), 2, 'text a'], - [new Coordinate(5, 1, 5), 1, 'text b'], - [new Coordinate(4, 6, null), 1, 'text a'], - [new Coordinate(3, 0, null), 0, null], - [new Coordinate(2, 5, null), 0, 'text'], - [new Coordinate(1, 5, 10), 0, 'text'], + [new Coordinate(6, 7, 10, 2), 'text a'], + [new Coordinate(5, 1, 5, 1), 'text b'], + [new Coordinate(4, 6, null, 1), 'text a'], + [new Coordinate(3, 0, null, 0), null], + [new Coordinate(2, 5, null, 0), 'text'], + [new Coordinate(1, 5, 10, 0), 'text'], ]; self::assertEquals($expected, $editor->expand($changes)); @@ -130,8 +130,8 @@ public function testGetText(): void { 6 => 'u v w x', ]); - self::assertNull($editor->getText(new Locator(25, 25, 0))); - self::assertEquals('f g', $editor->getText(new Locator(1, 1, 2, 3))); + self::assertNull($editor->getText(new Location(25, 25, 0))); + self::assertEquals('f g', $editor->getText(new Location(1, 1, 2, 3))); self::assertEquals( <<<'TEXT' k l @@ -139,7 +139,7 @@ public function testGetText(): void { q r s TEXT, - $editor->getText(new Locator(2, 5, 4, 5)), + $editor->getText(new Location(2, 5, 4, 5)), ); self::assertEquals( <<<'TEXT' diff --git a/packages/documentator/src/Markdown/Location/Coordinate.php b/packages/documentator/src/Markdown/Location/Coordinate.php index 59ab46b2e..fda1f33d8 100644 --- a/packages/documentator/src/Markdown/Location/Coordinate.php +++ b/packages/documentator/src/Markdown/Location/Coordinate.php @@ -7,6 +7,7 @@ public function __construct( public int $line, public int $offset, public ?int $length, + public int $padding = 0, ) { // empty } diff --git a/packages/documentator/src/Markdown/Location/Location.php b/packages/documentator/src/Markdown/Location/Location.php index 8a6ec02df..ec56b41e2 100644 --- a/packages/documentator/src/Markdown/Location/Location.php +++ b/packages/documentator/src/Markdown/Location/Location.php @@ -3,10 +3,61 @@ namespace LastDragon_ru\LaraASP\Documentator\Markdown\Location; use IteratorAggregate; +use Override; +use Traversable; /** - * @extends IteratorAggregate + * @implements IteratorAggregate */ -interface Location extends IteratorAggregate { - public function getPadding(): int; +readonly class Location implements IteratorAggregate { + public function __construct( + private int $startLine, + private int $endLine, + private int $offset = 0, + private ?int $length = null, + private int $startLinePadding = 0, + private ?int $internalPadding = null, + ) { + // empty + } + + /** + * @return Traversable + */ + #[Override] + public function getIterator(): Traversable { + if ($this->startLine === $this->endLine) { + yield new Coordinate( + $this->startLine, + $this->startLinePadding + $this->offset, + $this->length, + $this->startLinePadding, + ); + } else { + for ($line = $this->startLine; $line <= $this->endLine; $line++) { + yield match (true) { + $line === $this->startLine => new Coordinate( + $line, + $this->startLinePadding + $this->offset, + null, + $this->startLinePadding, + ), + $line === $this->endLine => new Coordinate( + $line, + $this->internalPadding ?? $this->startLinePadding, + $this->length, + $this->internalPadding ?? $this->startLinePadding, + ), + default => new Coordinate( + $line, + $this->internalPadding ?? $this->startLinePadding, + null, + $this->internalPadding ?? $this->startLinePadding, + ), + }; + } + } + + yield from []; + } } diff --git a/packages/documentator/src/Markdown/Location/LocationTest.php b/packages/documentator/src/Markdown/Location/LocationTest.php new file mode 100644 index 000000000..da3e9f122 --- /dev/null +++ b/packages/documentator/src/Markdown/Location/LocationTest.php @@ -0,0 +1,51 @@ +padding; - } - - /** - * @return Traversable - */ - #[Override] - public function getIterator(): Traversable { - if ($this->startLine === $this->endLine) { - yield new Coordinate($this->startLine, $this->padding + $this->offset, $this->length); - } else { - for ($line = $this->startLine; $line <= $this->endLine; $line++) { - yield match (true) { - $line === $this->startLine => new Coordinate($line, $this->padding + $this->offset, null), - $line === $this->endLine => new Coordinate($line, $this->padding, $this->length), - default => new Coordinate($line, $this->padding, null), - }; - } - } - - yield from []; - } -} diff --git a/packages/documentator/src/Markdown/Mutations/FootnotesPrefix.php b/packages/documentator/src/Markdown/Mutations/FootnotesPrefix.php index 0a5e9bb92..bbb3069ed 100644 --- a/packages/documentator/src/Markdown/Mutations/FootnotesPrefix.php +++ b/packages/documentator/src/Markdown/Mutations/FootnotesPrefix.php @@ -5,7 +5,6 @@ use LastDragon_ru\LaraASP\Documentator\Markdown\Contracts\Mutation; use LastDragon_ru\LaraASP\Documentator\Markdown\Document; use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location; -use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Locator; use LastDragon_ru\LaraASP\Documentator\Markdown\Utils; use League\CommonMark\Extension\Footnote\Node\Footnote; use League\CommonMark\Extension\Footnote\Node\FootnoteRef; @@ -93,6 +92,6 @@ private function getLabelLocation(Footnote|FootnoteRef $footnote, string $label) $offset = $coordinate->offset + 2; $length = mb_strlen($label); - return new Locator($startLine, $endLine, $offset, $length); + return new Location($startLine, $endLine, $offset, $length); } } diff --git a/packages/documentator/src/Markdown/Mutations/ReferencesPrefix.php b/packages/documentator/src/Markdown/Mutations/ReferencesPrefix.php index 451aac9d2..89d63bc4e 100644 --- a/packages/documentator/src/Markdown/Mutations/ReferencesPrefix.php +++ b/packages/documentator/src/Markdown/Mutations/ReferencesPrefix.php @@ -4,7 +4,7 @@ use LastDragon_ru\LaraASP\Documentator\Markdown\Contracts\Mutation; use LastDragon_ru\LaraASP\Documentator\Markdown\Document; -use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Locator; +use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location; use LastDragon_ru\LaraASP\Documentator\Markdown\Nodes\Reference\Block as Reference; use LastDragon_ru\LaraASP\Documentator\Markdown\Utils; use League\CommonMark\Extension\CommonMark\Node\Inline\AbstractWebResource; @@ -84,7 +84,7 @@ public function __invoke(Document $document, DocumentNode $node): array { $offset = $coordinate->offset + 1; $length = mb_strlen($reference->getLabel()); $text = "{$prefix}-{$reference->getLabel()}"; - $location = new Locator($startLine, $endLine, $offset, $length); + $location = new Location($startLine, $endLine, $offset, $length); } } else { // skipped diff --git a/packages/documentator/src/Markdown/Nodes/Locator/Parser.php b/packages/documentator/src/Markdown/Nodes/Locator/Parser.php index e6440f7ef..0ff3914c5 100644 --- a/packages/documentator/src/Markdown/Nodes/Locator/Parser.php +++ b/packages/documentator/src/Markdown/Nodes/Locator/Parser.php @@ -2,12 +2,12 @@ namespace LastDragon_ru\LaraASP\Documentator\Markdown\Nodes\Locator; -use LastDragon_ru\LaraASP\Documentator\Markdown\Data\BlockPadding; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\BlockPadding as DataBlockPadding; use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Data; -use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Location; -use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Offset; -use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Padding; -use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Locator; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Location as DataLocation; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Offset as DataOffset; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Padding as DataPadding; +use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location; use LastDragon_ru\LaraASP\Documentator\Markdown\Nodes\Aware; use LastDragon_ru\LaraASP\Documentator\Markdown\Utils; use LastDragon_ru\LaraASP\Documentator\Utils\Text; @@ -169,9 +169,9 @@ public function finalize(): void { // Detected? $blockStartLine = $container->getStartLine(); $blockEndLine = $container->getEndLine(); - $blockPadding = Data::get($container, BlockPadding::class); - $cellPadding = Data::get($container, Padding::class); - $offset = Data::get($container, Offset::class); + $blockPadding = Data::get($container, DataBlockPadding::class); + $cellPadding = Data::get($container, DataPadding::class); + $offset = Data::get($container, DataOffset::class); if ( $blockStartLine === null @@ -224,10 +224,10 @@ private function save( int $padding, int $origin, ): void { - Data::set($child, new Location(new Locator($startLine, $endLine, $offset, $length, $padding))); + Data::set($child, new DataLocation(new Location($startLine, $endLine, $offset, $length, $padding))); if (!($child instanceof FootnoteRef)) { - Data::set($child, new Offset($origin)); + Data::set($child, new DataOffset($origin)); } } } diff --git a/packages/documentator/src/Markdown/Nodes/Reference/ParserContinue.php b/packages/documentator/src/Markdown/Nodes/Reference/ParserContinue.php index e211c004f..3d999169d 100644 --- a/packages/documentator/src/Markdown/Nodes/Reference/ParserContinue.php +++ b/packages/documentator/src/Markdown/Nodes/Reference/ParserContinue.php @@ -2,10 +2,10 @@ namespace LastDragon_ru\LaraASP\Documentator\Markdown\Nodes\Reference; -use LastDragon_ru\LaraASP\Documentator\Markdown\Data\BlockPadding; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\BlockPadding as DataBlockPadding; use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Data; -use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Location; -use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Locator; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Location as DataLocation; +use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; @@ -82,13 +82,13 @@ public function closeBlock(): void { } // Data - Data::set($this->block, new BlockPadding($this->padding)); + Data::set($this->block, new DataBlockPadding($this->padding)); $start = $this->block->getStartLine(); $end = $this->block->getEndLine(); if ($start !== null && $end !== null) { - Data::set($this->block, new Location(new Locator($start, $end, 0, null, $this->padding))); + Data::set($this->block, new DataLocation(new Location($start, $end, 0, null, $this->padding))); } } diff --git a/packages/documentator/src/Markdown/Utils.php b/packages/documentator/src/Markdown/Utils.php index e2f0556f4..7d92eed50 100644 --- a/packages/documentator/src/Markdown/Utils.php +++ b/packages/documentator/src/Markdown/Utils.php @@ -2,15 +2,14 @@ namespace LastDragon_ru\LaraASP\Documentator\Markdown; -use LastDragon_ru\LaraASP\Documentator\Markdown\Data\BlockPadding; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\BlockPadding as DataBlockPadding; use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Data; -use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Length; -use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Lines; -use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Location; -use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Offset; -use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Padding; -use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location as LocationContract; -use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Locator; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Length as DataLength; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Lines as DataLines; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Location as DataLocation; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Offset as DataOffset; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Padding as DataPadding; +use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location; use League\CommonMark\Extension\CommonMark\Node\Inline\AbstractWebResource; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\Block\Document; @@ -81,8 +80,8 @@ public static function getPadding(Node $node, ?int $line, ?string $start): ?int // Known? $type = $line === null || $line === $container->getStartLine() - ? BlockPadding::class - : Padding::class; + ? DataBlockPadding::class + : DataPadding::class; $padding = Data::get($container, $type); if ($padding !== null) { @@ -116,20 +115,20 @@ public static function getPadding(Node $node, ?int $line, ?string $start): ?int } public static function getLine(Document $document, int $line): ?string { - $lines = Data::get($document, Lines::class) ?? []; + $lines = Data::get($document, DataLines::class) ?? []; $line = $lines[$line] ?? null; return $line; } - public static function getLocation(Node $node): ?LocationContract { - $location = Data::get($node, Location::class); + public static function getLocation(Node $node): ?Location { + $location = Data::get($node, DataLocation::class); if ($location === null && $node instanceof AbstractBlock) { $start = $node->getStartLine(); $end = $node->getEndLine(); - $offset = Data::get($node, Offset::class) ?? 0; - $length = Data::get($node, Length::class); + $offset = Data::get($node, DataOffset::class) ?? 0; + $length = Data::get($node, DataLength::class); $padding = self::getPadding($node, null, null); if ($padding === null && $node->parent() instanceof Document) { @@ -137,7 +136,7 @@ public static function getLocation(Node $node): ?LocationContract { } if ($start !== null && $end !== null && $padding !== null) { - $location = new Locator($start, $end, $offset, $length, $padding); + $location = new Location($start, $end, $offset, $length, $padding); } } From 4b8b192b48f1df54f3c96f5860b0d15654df53b9 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 15 Aug 2024 11:33:40 +0400 Subject: [PATCH 3/3] Mutations will not touch links label. --- .../src/Markdown/Location/Location.php | 12 ++-- .../src/Markdown/Mutations/Move.php | 58 +++++++++---------- .../src/Markdown/Mutations/MoveTest.php | 4 +- .../Markdown/Mutations/ReferencesInline.php | 28 +++------ .../Mutations/ReferencesInlineTest.php | 4 +- .../Markdown/Mutations/ReferencesPrefix.php | 28 ++++----- .../Mutations/ReferencesPrefixTest.php | 6 +- packages/documentator/src/Markdown/Utils.php | 53 +++++++++-------- 8 files changed, 88 insertions(+), 105 deletions(-) diff --git a/packages/documentator/src/Markdown/Location/Location.php b/packages/documentator/src/Markdown/Location/Location.php index ec56b41e2..1d1efbe86 100644 --- a/packages/documentator/src/Markdown/Location/Location.php +++ b/packages/documentator/src/Markdown/Location/Location.php @@ -11,12 +11,12 @@ */ readonly class Location implements IteratorAggregate { public function __construct( - private int $startLine, - private int $endLine, - private int $offset = 0, - private ?int $length = null, - private int $startLinePadding = 0, - private ?int $internalPadding = null, + public int $startLine, + public int $endLine, + public int $offset = 0, + public ?int $length = null, + public int $startLinePadding = 0, + public ?int $internalPadding = null, ) { // empty } diff --git a/packages/documentator/src/Markdown/Mutations/Move.php b/packages/documentator/src/Markdown/Mutations/Move.php index fbdac694d..ac5bab3be 100644 --- a/packages/documentator/src/Markdown/Mutations/Move.php +++ b/packages/documentator/src/Markdown/Mutations/Move.php @@ -4,25 +4,25 @@ use LastDragon_ru\LaraASP\Core\Utils\Path; use LastDragon_ru\LaraASP\Documentator\Markdown\Contracts\Mutation; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Data; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Offset; use LastDragon_ru\LaraASP\Documentator\Markdown\Document; use LastDragon_ru\LaraASP\Documentator\Markdown\Nodes\Reference\Block as Reference; use LastDragon_ru\LaraASP\Documentator\Markdown\Utils; use League\CommonMark\Extension\CommonMark\Node\Inline\AbstractWebResource; use League\CommonMark\Extension\CommonMark\Node\Inline\Image; use League\CommonMark\Extension\CommonMark\Node\Inline\Link; -use League\CommonMark\Extension\Table\TableCell; use League\CommonMark\Node\Block\Document as DocumentNode; -use League\CommonMark\Node\Inline\Text; use Override; use function dirname; use function filter_var; +use function ltrim; use function mb_substr; use function preg_match; use function preg_quote; use function rawurldecode; use function rtrim; -use function str_replace; use function str_starts_with; use function trim; @@ -78,45 +78,41 @@ public function __invoke(Document $document, DocumentNode $node): array { } // Changes - $text = null; - $origin = trim((string) $document->getText($location)); + $text = null; if ($resource instanceof Link || $resource instanceof Image) { - $title = (string) $resource->getTitle(); - $titleWrapper = mb_substr(rtrim(mb_substr($origin, 0, -1)), -1, 1); - $label = (string) Utils::getChild($resource, Text::class)?->getLiteral(); - $target = rawurldecode($resource->getUrl()); - $target = Path::getPath($docDirectory, $target); - $target = Path::getRelativePath($newDirectory, $target); - $targetWrap = (bool) preg_match('/^!?\['.preg_quote($label, '/').']\(\s*getText($location)); + $titleValue = (string) $resource->getTitle(); + $titleWrapper = mb_substr(rtrim(mb_substr($origin, 0, -1)), -1, 1); + $title = Utils::getLinkTitle($resource, $titleValue, $titleWrapper); + $targetValue = rawurldecode($resource->getUrl()); + $targetValue = Path::getPath($docDirectory, $targetValue); + $targetValue = Path::getRelativePath($newDirectory, $targetValue); + $targetWrap = mb_substr(ltrim(ltrim($origin, '(')), 0, 1) === '<'; + $target = Utils::getLinkTarget($resource, $targetValue, $targetWrap); + $text = $title ? "({$target} {$title})" : "({$target})"; } } elseif ($resource instanceof Reference) { + $origin = trim((string) $document->getText($location)); $label = $resource->getLabel(); - $title = $resource->getTitle(); + $titleValue = $resource->getTitle(); $titleWrapper = mb_substr($origin, -1, 1); - $target = rawurldecode($resource->getDestination()); - $target = Path::getPath($docDirectory, $target); - $target = Path::getRelativePath($newDirectory, $target); + $title = Utils::getLinkTitle($resource, $titleValue, $titleWrapper); + $targetValue = rawurldecode($resource->getDestination()); + $targetValue = Path::getPath($docDirectory, $targetValue); + $targetValue = Path::getRelativePath($newDirectory, $targetValue); $targetWrap = (bool) preg_match('/^\['.preg_quote($resource->getLabel(), '/').']:\s+ ') text [title](<../from/file/a> (title)) + text [_`link`_](../from/file/b ' ') text [title](<../from/file/a> (title)) [mailto](mailto:mail@example.com) text [absolute](/path/to/file 'title') text [external](https://example.com/). @@ -203,7 +203,7 @@ public static function dataProviderInvoke(): array { # General Text text [tel](tel:+70000000000 "title") text [link](./file/a) - text [link](file/b ' <title> ') text [title](<./file/a> (title)) + text [_`link`_](file/b ' <title> ') text [title](<./file/a> (title)) [mailto](mailto:mail@example.com) text [absolute](/path/to/file 'title') text [external](https://example.com/). diff --git a/packages/documentator/src/Markdown/Mutations/ReferencesInline.php b/packages/documentator/src/Markdown/Mutations/ReferencesInline.php index b9b0db4c5..507d99fe9 100644 --- a/packages/documentator/src/Markdown/Mutations/ReferencesInline.php +++ b/packages/documentator/src/Markdown/Mutations/ReferencesInline.php @@ -3,19 +3,18 @@ namespace LastDragon_ru\LaraASP\Documentator\Markdown\Mutations; use LastDragon_ru\LaraASP\Documentator\Markdown\Contracts\Mutation; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Data; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Offset; use LastDragon_ru\LaraASP\Documentator\Markdown\Document; use LastDragon_ru\LaraASP\Documentator\Markdown\Nodes\Reference\Block as Reference; use LastDragon_ru\LaraASP\Documentator\Markdown\Utils; use League\CommonMark\Extension\CommonMark\Node\Inline\AbstractWebResource; use League\CommonMark\Extension\CommonMark\Node\Inline\Image; use League\CommonMark\Extension\CommonMark\Node\Inline\Link; -use League\CommonMark\Extension\Table\TableCell; use League\CommonMark\Node\Block\Document as DocumentNode; -use League\CommonMark\Node\Inline\Text; use Override; use function rawurldecode; -use function str_replace; /** * Inlines all references. @@ -45,22 +44,13 @@ public function __invoke(Document $document, DocumentNode $node): array { $text = null; if ($reference instanceof Link || $reference instanceof Image) { - $title = (string) $reference->getTitle(); - $label = (string) Utils::getChild($reference, Text::class)?->getLiteral(); - $target = rawurldecode($reference->getUrl()); + $offset = Data::get($reference, Offset::class); + $location = $offset !== null ? Utils::getOffsetLocation($location, $offset) : null; - if (Utils::getContainer($reference) instanceof TableCell) { - $title = str_replace('|', '\\|', $title); - $label = str_replace('|', '\\|', $label); - $target = str_replace('|', '\\|', $target); - } - - $text = $title - ? Utils::getLink('[%s](%s %s)', $label, $target, $title, null, null) - : Utils::getLink('[%s](%s)', $label, $target, '', null, null); - - if ($reference instanceof Image) { - $text = "!{$text}"; + if ($location !== null) { + $title = Utils::getLinkTitle($reference, (string) $reference->getTitle()); + $target = Utils::getLinkTarget($reference, rawurldecode($reference->getUrl())); + $text = $title ? "({$target} {$title})" : "({$target})"; } } elseif ($reference instanceof Reference) { $text = ''; @@ -68,7 +58,7 @@ public function __invoke(Document $document, DocumentNode $node): array { // skipped } - if ($text !== null) { + if ($location !== null && $text !== null) { $changes[] = [$location, $text ?: null]; } } diff --git a/packages/documentator/src/Markdown/Mutations/ReferencesInlineTest.php b/packages/documentator/src/Markdown/Mutations/ReferencesInlineTest.php index bf9d8d481..16241b106 100644 --- a/packages/documentator/src/Markdown/Mutations/ReferencesInlineTest.php +++ b/packages/documentator/src/Markdown/Mutations/ReferencesInlineTest.php @@ -22,7 +22,7 @@ public function testInvoke(): void { <<<'MARKDOWN' # Header - Text text [link](https://example.com) text text [link][link] text + Text text [link](https://example.com) text text [`link`][link] text text text ![image][image] text text. ![image][image] @@ -55,7 +55,7 @@ public function testInvoke(): void { <<<'MARKDOWN' # Header - Text text [link](https://example.com) text text [link](https://example.com) text + Text text [link](https://example.com) text text [`link`](https://example.com) text text text ![image](https://example.com "image") text text. ![image](https://example.com "image") diff --git a/packages/documentator/src/Markdown/Mutations/ReferencesPrefix.php b/packages/documentator/src/Markdown/Mutations/ReferencesPrefix.php index 89d63bc4e..288bdf143 100644 --- a/packages/documentator/src/Markdown/Mutations/ReferencesPrefix.php +++ b/packages/documentator/src/Markdown/Mutations/ReferencesPrefix.php @@ -3,6 +3,8 @@ namespace LastDragon_ru\LaraASP\Documentator\Markdown\Mutations; use LastDragon_ru\LaraASP\Documentator\Markdown\Contracts\Mutation; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Data; +use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Offset; use LastDragon_ru\LaraASP\Documentator\Markdown\Document; use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location; use LastDragon_ru\LaraASP\Documentator\Markdown\Nodes\Reference\Block as Reference; @@ -10,14 +12,11 @@ use League\CommonMark\Extension\CommonMark\Node\Inline\AbstractWebResource; use League\CommonMark\Extension\CommonMark\Node\Inline\Image; use League\CommonMark\Extension\CommonMark\Node\Inline\Link; -use League\CommonMark\Extension\Table\TableCell; use League\CommonMark\Node\Block\Document as DocumentNode; -use League\CommonMark\Node\Inline\Text; use Override; use function hash; use function mb_strlen; -use function str_replace; use function uniqid; /** @@ -56,19 +55,14 @@ public function __invoke(Document $document, DocumentNode $node): array { $text = null; if ($reference instanceof Link || $reference instanceof Image) { - $label = (string) Utils::getChild($reference, Text::class)?->getLiteral(); - $target = Utils::getReference($reference)?->getLabel(); - $target = "{$prefix}-{$target}"; - - if (Utils::getContainer($reference) instanceof TableCell) { - $label = str_replace('|', '\\|', $label); - $target = str_replace('|', '\\|', $target); - } - - $text = Utils::getLink('[%s][%s]', $label, $target, '', null, null); - - if ($reference instanceof Image) { - $text = "!{$text}"; + $offset = Data::get($reference, Offset::class); + $location = $offset !== null ? Utils::getOffsetLocation($location, $offset) : null; + + if ($location !== null) { + $target = Utils::getReference($reference)?->getLabel(); + $target = "{$prefix}-{$target}"; + $target = Utils::escapeTextInTableCell($reference, $target); + $text = "[{$target}]"; } } elseif ($reference instanceof Reference) { $coordinate = null; @@ -90,7 +84,7 @@ public function __invoke(Document $document, DocumentNode $node): array { // skipped } - if ($text !== null) { + if ($location !== null && $text !== null) { $changes[] = [$location, $text]; } } diff --git a/packages/documentator/src/Markdown/Mutations/ReferencesPrefixTest.php b/packages/documentator/src/Markdown/Mutations/ReferencesPrefixTest.php index f6d9d0629..e781cbfec 100644 --- a/packages/documentator/src/Markdown/Mutations/ReferencesPrefixTest.php +++ b/packages/documentator/src/Markdown/Mutations/ReferencesPrefixTest.php @@ -20,7 +20,7 @@ final class ReferencesPrefixTest extends TestCase { private const Markdown = <<<'MARKDOWN' # Header - Text text [link](https://example.com) text text [link][link] text + Text text [link](https://example.com) text text [`link`][link] text text text ![image][image] text text. ![image][image] @@ -54,7 +54,7 @@ public function testInvoke(): void { <<<'MARKDOWN' # Header - Text text [link](https://example.com) text text [link][a282e9c32e7eee65-link] text + Text text [link](https://example.com) text text [`link`][a282e9c32e7eee65-link] text text text ![image][a282e9c32e7eee65-image] text text. ![image][a282e9c32e7eee65-image] @@ -91,7 +91,7 @@ public function testInvokeExplicit(): void { <<<'MARKDOWN' # Header - Text text [link](https://example.com) text text [link][prefix-link] text + Text text [link](https://example.com) text text [`link`][prefix-link] text text text ![image][prefix-image] text text. ![image][prefix-image] diff --git a/packages/documentator/src/Markdown/Utils.php b/packages/documentator/src/Markdown/Utils.php index 7d92eed50..3566bbfd3 100644 --- a/packages/documentator/src/Markdown/Utils.php +++ b/packages/documentator/src/Markdown/Utils.php @@ -11,6 +11,7 @@ use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Padding as DataPadding; use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location; use League\CommonMark\Extension\CommonMark\Node\Inline\AbstractWebResource; +use League\CommonMark\Extension\Table\TableCell; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\Block\Document; use League\CommonMark\Node\Node; @@ -19,10 +20,9 @@ use function mb_strpos; use function preg_match; -use function sprintf; use function str_contains; +use function str_replace; use function strtr; -use function trim; /** * @internal @@ -143,6 +143,17 @@ public static function getLocation(Node $node): ?Location { return $location; } + public static function getOffsetLocation(Location $location, int $offset): Location { + return new Location( + $location->startLine, + $location->endLine, + $location->offset + $offset, + $location->length !== null ? $location->length - $offset : $location->length, + $location->startLinePadding, + $location->internalPadding, + ); + } + public static function isReference(AbstractWebResource $node): bool { return self::getReference($node) !== null; } @@ -154,33 +165,16 @@ public static function getReference(AbstractWebResource $node): ?ReferenceInterf return $reference; } - public static function getLink( - string $format, - string $label, - string $target, - string $title, - ?bool $wrapTarget, - ?string $titleWrapper, - ): string { - $label = self::getLinkLabel($label); - $title = self::getLinkTitle($title, $titleWrapper); - $target = self::getLinkTarget($target, $wrapTarget); - $link = trim(sprintf($format, $label, $target, $title)); - - return $link; - } - - private static function getLinkLabel(string $label): string { - return strtr($label, ['[' => '\\\\[', ']' => '\\\\]']); - } - - private static function getLinkTarget(string $target, ?bool $wrap): string { - return ($wrap ?? preg_match('/\s/u', $target)) + public static function getLinkTarget(Node $container, string $target, ?bool $wrap = null): string { + $target = ($wrap ?? preg_match('/\s/u', $target)) ? '<'.strtr($target, ['<' => '\\<', '>' => '\\>']).'>' : UrlEncoder::unescapeAndEncode($target); + $target = self::escapeTextInTableCell($container, $target); + + return $target; } - private static function getLinkTitle(string $title, ?string $wrapper = null): string { + public static function getLinkTitle(Node $container, string $title, ?string $wrapper = null): string { if (!$title) { return ''; } @@ -201,10 +195,19 @@ private static function getLinkTitle(string $title, ?string $wrapper = null): st "'" => "'".strtr($title, $wrappers['"'])."'", default => '('.strtr($title, $wrappers[')']).')', }; + $title = self::escapeTextInTableCell($container, $title); return $title; } + public static function escapeTextInTableCell(Node $container, string $text): string { + if (self::getContainer($container) instanceof TableCell) { + $text = str_replace('|', '\\|', $text); + } + + return $text; + } + /** * @template T of object *