diff --git a/docs/reference/enhancements.md b/docs/reference/enhancements.md index 6156312e..24c301f2 100644 --- a/docs/reference/enhancements.md +++ b/docs/reference/enhancements.md @@ -441,6 +441,12 @@ Works with all list types: block attribute for that following block; the list and item are not terminated. +**Stripped attribute names:** `start`, `type`, and `reversed` are HTML +attributes valid only on `
    `. When authored on a list item they are +silently dropped from the rendered `
  1. ` to keep output HTML valid. +To set `
      `, use a standard block-attribute line **before** +the list: `{start=5}` followed by `1. ...`. + --- ### Table Row and Cell Attributes @@ -749,6 +755,9 @@ Attributes can be attached to individual `
      `, `
      `, and `
      ` elements: - `{...}` on line after term → applies to that `
      ` - `{...}` as last line in definition block → applies to that `
      ` (consistent with list items) +**Stripped attribute names:** the same `start`, `type`, `reversed` +strip applies to `
      ` output. + --- ### Table Multi-line Cells, Rowspan, and Colspan diff --git a/src/Renderer/HtmlRenderer.php b/src/Renderer/HtmlRenderer.php index 2667a713..b068e3b9 100644 --- a/src/Renderer/HtmlRenderer.php +++ b/src/Renderer/HtmlRenderer.php @@ -88,6 +88,15 @@ class HtmlRenderer implements RendererInterface */ protected array $nodeRenderers = []; + /** + * Attribute names valid on
        but not on
      1. /
        . Stripped from + *
      2. /
        output to avoid emitting invalid HTML when authors put + * these names on a list item (e.g. as a postfix {start=5} line). + * + * @var array + */ + protected const OL_ONLY_ATTRIBUTES = ['start', 'type', 'reversed']; + public function __construct(protected bool $xhtml = false) { $this->sharedRenderContext = new RenderContext(); @@ -741,7 +750,7 @@ protected function renderList(ListBlock $node): string protected function renderListItem(ListItem $node, bool $tight = true): string { - $attrs = $this->renderAttributes($node); + $attrs = $this->renderAttributesExcluding($node, self::OL_ONLY_ATTRIBUTES); $content = $this->renderChildren($node); if ($tight) { @@ -1239,7 +1248,7 @@ protected function renderDefinitionTerm(DefinitionTerm $node): string protected function renderDefinitionDescription(DefinitionDescription $node): string { - $attrs = $this->renderAttributes($node); + $attrs = $this->renderAttributesExcluding($node, self::OL_ONLY_ATTRIBUTES); $content = $this->renderChildren($node); // Content goes on separate lines diff --git a/tests/TestCase/DjotConverterTest.php b/tests/TestCase/DjotConverterTest.php index 144cde52..25d74eb1 100644 --- a/tests/TestCase/DjotConverterTest.php +++ b/tests/TestCase/DjotConverterTest.php @@ -1104,6 +1104,80 @@ public function testListItemAttributesLooseListUnchanged(): void $this->assertStringContainsString('
        ', $result); } + public function testListItemAttributeStartIsStrippedFromLi(): void + { + // G3: `start` is an HTML attribute valid only on
          . It must + // be stripped from
        1. output, never feed
            , and never + // appear in HTML. + $djot = "1. item\n {start=5}\n"; + + $result = $this->converter->convert($djot); + + $this->assertStringContainsString('
          1. ', $result); + $this->assertStringNotContainsString('
          2. . Stripped + // from
          3. ; never overrides marker-derived
              . + $djot = "(a) x\n {type=i}\n"; + + $result = $this->converter->convert($djot); + + $this->assertStringContainsString('
                ', $result); + $this->assertStringNotContainsString('
              1. -only. Stripped from
              2. . + $djot = "1. item\n {reversed}\n"; + + $result = $this->converter->convert($djot); + + $this->assertStringContainsString('
              3. ', $result); + $this->assertStringNotContainsString('
              4. assertStringNotContainsString('reversed=""', $result); + } + + public function testListItemOtherAttributesUnchanged(): void + { + // G3 regression guard: only start/type/reversed are stripped. + // class, id, data-* pass through unchanged on
              5. . + $djot = "1. item\n {#anchor .step data-step=\"1\"}\n"; + + $result = $this->converter->convert($djot); + + $this->assertStringContainsString('id="anchor"', $result); + $this->assertStringContainsString('class="step"', $result); + $this->assertStringContainsString('data-step="1"', $result); + } + + public function testOlStartFromBlockAttrUnchanged(): void + { + // G3 regression guard: a block-attribute line BEFORE a list + // applies to the
                  and must still emit start/type as before. + $djot = "{start=5}\n1. item\n2. next\n"; + + $result = $this->converter->convert($djot); + + $this->assertStringContainsString('
                    . + $djot = ": term\n\n definition\n {start=5}\n"; + + $result = $this->converter->convert($djot); + + $this->assertStringContainsString('
                    ', $result); + $this->assertStringNotContainsString('