Skip to content

Commit 400870f

Browse files
committed
Official specs Coverage.
1 parent d9c8281 commit 400870f

File tree

3 files changed

+49
-24
lines changed

3 files changed

+49
-24
lines changed

src/Parser/BlockParser.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -341,13 +341,13 @@ protected function parseInlineAttributes(string $attrStr): array
341341
} elseif (!empty($match[2])) {
342342
// ID attribute
343343
$attrs['id'] = $match[2];
344-
} elseif (!empty($match[3])) {
344+
} elseif (($match[3] ?? '') !== '') {
345345
// key="double quoted value"
346346
$attrs[$match[3]] = $match[4] ?? '';
347-
} elseif (isset($match[5])) {
347+
} elseif (($match[5] ?? '') !== '') {
348348
// key='single quoted value'
349349
$attrs[$match[5]] = $match[6] ?? '';
350-
} elseif (isset($match[7])) {
350+
} elseif (($match[7] ?? '') !== '') {
351351
// key=unquoted
352352
$attrs[$match[7]] = $match[8] ?? '';
353353
}
@@ -512,6 +512,19 @@ protected function tryParseBlockAttributes(array $lines, int $start): ?int
512512
if (!preg_match('/^[.#a-zA-Z%]/', $attrStr)) {
513513
return null;
514514
}
515+
516+
// Check if attributes precede a reference definition - if so, skip storing them
517+
// (they were already applied during extractReferences)
518+
$count = count($lines);
519+
$nextIdx = $start + 1;
520+
while ($nextIdx < $count && $this->isBlankLine($lines[$nextIdx])) {
521+
$nextIdx++;
522+
}
523+
if ($nextIdx < $count && preg_match('/^\[([^\]]+)\]:/', $lines[$nextIdx])) {
524+
// Attributes precede a reference definition, don't store them as block attrs
525+
return 1;
526+
}
527+
515528
$this->parseAttributeString($attrStr);
516529

517530
return 1;
@@ -1494,9 +1507,10 @@ protected function disambiguateListStyle(array $listInfo, array $lines, int $sta
14941507
$hasNonRomanLetter = false;
14951508
$allSameLetter = true;
14961509
$romanChars = 'ivxlcdm';
1510+
$lineCount = count($lines);
14971511

14981512
// Look ahead at subsequent items
1499-
for ($i = $start + 1; $i < count($lines); $i++) {
1513+
for ($i = $start + 1; $i < $lineCount; $i++) {
15001514
$line = $lines[$i];
15011515

15021516
// Stop at blank lines or non-list content
@@ -1567,7 +1581,7 @@ protected function disambiguateListStyle(array $listInfo, array $lines, int $sta
15671581
}
15681582

15691583
/**
1570-
* @return array{type: string, marker: string, content: string, start?: int, checked?: bool, style?: string, marker_indent?: int}|null
1584+
* @return array{type: string, marker: string, content: string, start?: int, checked?: bool, style?: string, marker_indent?: int, ambiguous?: bool, alpha_start?: int, alpha_style?: string}|null
15711585
*/
15721586
protected function parseListItemMarker(string $line): ?array
15731587
{

src/Parser/InlineParser.php

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -277,17 +277,22 @@ protected function parseInlines(Node $parent, string $text): void
277277
$textBuffer = '';
278278
$result = $this->parseLink($text, $pos);
279279
if ($result !== null) {
280-
// Check if this returned literal text (for unclosed link)
281-
if (isset($result['literal'])) {
282-
$textBuffer .= $result['literal'];
283-
$pos = $result['pos'];
280+
// Check if this is an unclosed link (special handling)
281+
if (isset($result['unclosed_link'])) {
282+
// Output [ then parse linkText in isolation then output ](
283+
$parent->appendChild(new Text('['));
284+
$this->parseInlines($parent, $result['link_text']);
285+
$parent->appendChild(new Text(']('));
286+
$pos = $result['continue_pos'];
284287

285288
continue;
286289
}
287-
$parent->appendChild($result['node']);
288-
$pos = $result['pos'];
290+
if (isset($result['node'])) {
291+
$parent->appendChild($result['node']);
292+
$pos = $result['pos'];
289293

290-
continue;
294+
continue;
295+
}
291296
}
292297
}
293298

@@ -544,7 +549,7 @@ protected function parseCodeSpan(string $text, int $pos): ?array
544549
}
545550

546551
/**
547-
* @return array{node: \Djot\Node\Inline\Link|\Djot\Node\Inline\Span, pos: int}|null
552+
* @return array{node: \Djot\Node\Inline\Link|\Djot\Node\Inline\Span, pos: int}|array{unclosed_link: true, link_text: string, continue_pos: int}|null
548553
*/
549554
protected function parseLink(string $text, int $pos): ?array
550555
{
@@ -620,12 +625,13 @@ protected function parseLink(string $text, int $pos): ?array
620625
];
621626
}
622627

623-
// Unclosed parenthesis - the entire [text](... is literal text
624-
// In djot, emphasis openers in [..] can't match closers in (..)
625-
// so we return the whole thing as literal text to prevent cross-boundary emphasis
628+
// Unclosed parenthesis - not a valid link
629+
// Parse [text] as isolated inline content, then continue from after (
630+
// This prevents emphasis from crossing the [text]( boundary
626631
return [
627-
'literal' => substr($text, $pos, $length - $pos),
628-
'pos' => $length,
632+
'unclosed_link' => true,
633+
'link_text' => $linkText,
634+
'continue_pos' => $urlStart, // Position after (
629635
];
630636
}
631637

@@ -641,7 +647,7 @@ protected function parseLink(string $text, int $pos): ?array
641647
$ref = $this->normalizeReferenceLabel($linkText);
642648
} else {
643649
// Explicit reference [text][ref] - only normalize whitespace, keep formatting chars
644-
$ref = preg_replace('/\s+/', ' ', trim($ref));
650+
$ref = preg_replace('/\s+/', ' ', trim($ref)) ?? $ref;
645651
}
646652

647653
$refDef = $this->blockParser->getReference($ref);
@@ -731,6 +737,11 @@ protected function parseImage(string $text, int $pos): ?array
731737
return null;
732738
}
733739

740+
// Unclosed links can't be images, and we need node/pos to exist
741+
if (isset($result['unclosed_link']) || !isset($result['node'])) {
742+
return null;
743+
}
744+
734745
$link = $result['node'];
735746
if (!$link instanceof Link) {
736747
return null;
@@ -1618,10 +1629,10 @@ protected function normalizeReferenceLabel(string $label): string
16181629
{
16191630
// Strip inline formatting markers: _ * ~ ^ + = { }
16201631
// But keep the content between them
1621-
$label = preg_replace('/[_*~^+={}]/', '', $label);
1632+
$label = preg_replace('/[_*~^+={}]/', '', $label) ?? $label;
16221633

16231634
// Normalize whitespace: collapse multiple spaces/newlines to single space
1624-
$label = preg_replace('/\s+/', ' ', $label);
1635+
$label = preg_replace('/\s+/', ' ', $label) ?? $label;
16251636

16261637
// Trim
16271638
return trim($label);

src/Renderer/HtmlRenderer.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ protected function renderAttributes(Node $node): string
500500
return '';
501501
}
502502

503-
// Sort attributes: id first, then others (preserving insertion order), then class last
503+
// Sort attributes: id first, class second, then others in source order
504504
uksort($attrs, function (string $a, string $b): int {
505505
if ($a === 'id') {
506506
return -1;
@@ -509,10 +509,10 @@ protected function renderAttributes(Node $node): string
509509
return 1;
510510
}
511511
if ($a === 'class') {
512-
return 1; // class goes last
512+
return -1; // class after id
513513
}
514514
if ($b === 'class') {
515-
return -1; // class goes last
515+
return 1;
516516
}
517517

518518
return 0; // preserve order for other attributes

0 commit comments

Comments
 (0)