diff --git a/src/FootnoteBodyFormatter.php b/src/FootnoteBodyFormatter.php index 8c67f2773..638766c86 100644 --- a/src/FootnoteBodyFormatter.php +++ b/src/FootnoteBodyFormatter.php @@ -69,9 +69,16 @@ class FootnoteBodyFormatter { // Note: This builds a string of wikitext, not html. $parserInput = "\n"; $indented = false; + // After sorting the list, we can assume that references are in the same order as their + // numbering. Subreferences will come immediately after their parent. + uasort( + $groupRefs, + function ( array $a, array $b ) : int { + return ( $a['number'] ?? '' ) <=> ( $b['number'] ?? '' ) ?: + ( $a['extendsIndex'] ?? '0' ) <=> ( $b['extendsIndex'] ?? '0' ); + } + ); foreach ( $groupRefs as $key => $value ) { - // FIXME: This assumes extended references appear immediately after their parent in the - // data structure. Reorder the refs according to their stored numbering. if ( !$indented && isset( $value['extends'] ) ) { // The nested
    must be inside the parent's
  1. if ( preg_match( '#
  2. \s*$#D', $parserInput, $matches, PREG_OFFSET_CAPTURE ) ) { @@ -125,10 +132,13 @@ class FootnoteBodyFormatter { * @return string Wikitext, wrapped in a single
  3. element */ private function referencesFormatEntry( $key, array $val, bool $isSectionPreview ) : string { - $text = $this->referenceText( $key, $val['text'], $isSectionPreview ); + $text = $this->referenceText( $key, ( $val['text'] ?? null ), $isSectionPreview ); $error = ''; $extraAttributes = ''; + // TODO: Show an error if isset( $val['__placeholder__'] ), this is caused by extends + // with a missing parent. + if ( isset( $val['dir'] ) ) { $dir = strtolower( $val['dir'] ); if ( in_array( $dir, [ 'ltr', 'rtl' ] ) ) { diff --git a/src/ReferenceStack.php b/src/ReferenceStack.php index e6d851b65..81693b657 100644 --- a/src/ReferenceStack.php +++ b/src/ReferenceStack.php @@ -169,6 +169,13 @@ class ReferenceStack { // This is an anonymous reference, which will be given a numeric index. $this->refs[$group][] = &$ref; $action = 'new'; + } elseif ( isset( $this->refs[$group][$name]['__placeholder__'] ) ) { + // Populate a placeholder. + unset( $this->refs[$group][$name]['__placeholder__'] ); + unset( $ref['number'] ); + $ref = array_merge( $ref, $this->refs[$group][$name] ); + $this->refs[$group][$name] =& $ref; + $action = 'new'; } elseif ( !isset( $this->refs[$group][$name] ) ) { // Valid key with first occurrence $this->refs[$group][$name] = &$ref; @@ -205,18 +212,22 @@ class ReferenceStack { $ref['number'] = $ref['number'] ?? ++$this->groupRefSequence[$group]; if ( $extends ) { - if ( isset( $this->refs[$group][$extends] ) ) { - $this->extendsCount[$group][$extends] = - ( $this->extendsCount[$group][$extends] ?? 0 ) + 1; + $this->extendsCount[$group][$extends] = + ( $this->extendsCount[$group][$extends] ?? 0 ) + 1; - $ref['extends'] = $extends; - $ref['extendsIndex'] = $this->extendsCount[$group][$extends]; + $ref['extends'] = $extends; + $ref['extendsIndex'] = $this->extendsCount[$group][$extends]; + if ( isset( $this->refs[$group][$extends]['number'] ) ) { + // Adopt the parent's number. + // TODO: Do we need to roll back the group ref sequence here? $ref['number'] = $this->refs[$group][$extends]['number']; } else { - // TODO: check parent existence in a second, pre-render stage of validation. - // This should be an error, not silent degradation. - $extends = null; + // Transfer my number to parent ref. + $this->refs[$group][$extends] = [ + 'number' => $ref['number'], + '__placeholder__' => true, + ]; } } diff --git a/tests/parser/bookReferencing.txt b/tests/parser/bookReferencing.txt index 44c5e6f56..cfb65b8ca 100644 --- a/tests/parser/bookReferencing.txt +++ b/tests/parser/bookReferencing.txt @@ -85,8 +85,6 @@ wgCiteBookReferencing=true
!! end -# TODO: -# * Should render an error. !! test T236256 - Extending a reference that doesn't exist #1 !! config @@ -94,14 +92,14 @@ wgCiteBookReferencing=true !! wikitext page 1 !! html/php -[1]
    -
  1. page 1 +[1.1]
      +
    1. Cite error: Invalid <ref> tag; +no text was provided for refs named a
      1. page 1
      2. +
    !! end -# TODO: -# * Should render an error at the second reference. !! test T236256 - Extending a reference that doesn't exist #2 !! config @@ -112,11 +110,13 @@ wgCiteBookReferencing=true !! html/php

    [1]

    -[2]
      +[2.1]
      1. book
      2. -
      3. page 1 +
      4. Cite error: Invalid <ref> tag; +no text was provided for refs named a
        1. page 1
        2. +
      !! end @@ -278,8 +278,6 @@ wgCiteBookReferencing=true
    !! end -# TODO: -# * Should render an error at the second reference. !! test T236256 - Base and book reference in different groups !! config @@ -291,20 +289,20 @@ wgCiteBookReferencing=true !! html/php

    [g1 1] -[g2 1] +[g2 1.1]

    1. book
      -
    1. page +
    2. Cite error: Invalid <ref> tag; +no text was provided for refs named a
      1. page
      2. +
    !! end -# TODO: -# * Should render an error at the second reference. !! test T236256 - Extending in the unnamed default group !! config @@ -315,19 +313,19 @@ wgCiteBookReferencing=true !! html/php

    [g1 1] -[1] +[1.1]

    1. book
      -
    1. page +
    2. Cite error: Invalid <ref> tag; +no text was provided for refs named a
      1. page
      2. +
    !! end -# TODO: -# * Should render an error at the second reference. !! test T236256 - Base in the unnamed default group !! config @@ -338,11 +336,13 @@ wgCiteBookReferencing=true !! html/php

    [1] -[g1 1] +[g1 1.1]

      -
    1. page +
    2. Cite error: Invalid <ref> tag; +no text was provided for refs named a
      1. page
      2. +
    1. book
    2. @@ -439,6 +439,8 @@ invalid names, e.g. too many
    !! end +# TODO +# * This should be invalid, there's no way to know that `footwo` should be subnumbered in a one-pass parse. !! test Extends defined in called with #tag !! wikitext @@ -460,6 +462,58 @@ Extends defined in called with #tag
!! end +!! test +Extends parent defined in called with #tag +!! wikitext +BAR +page 7 + + +{{#tag:references| +book name +}} +!! html +

[1] +[2.1] +[2] +

+
    +
  1. BAR +
  2. +
  3. book name +
    1. page 7 +
    2. +
  4. +
+!! end + +!! test +Interleaved extends groups +!! wikitext +text-a +text-c + +page b +page d + +!! html +

[1] +[2] +

[1.1] +[2.1] +

+
    +
  1. text-a +
    1. page b +
    2. +
  2. +
  3. text-c +
    1. page d +
    2. +
  4. +
+!! end + !! test Multiple groups with extends !! wikitext @@ -530,6 +584,7 @@ wgCiteBookReferencing=true book another page 2 + !! html/php

[1] @@ -538,10 +593,10 @@ wgCiteBookReferencing=true

  1. book -
  2. -
  3. another
    1. page 2
  4. +
  5. another +
!! end diff --git a/tests/phpunit/unit/ReferenceStackTest.php b/tests/phpunit/unit/ReferenceStackTest.php index c582d9d46..4d5e44a0e 100644 --- a/tests/phpunit/unit/ReferenceStackTest.php +++ b/tests/phpunit/unit/ReferenceStackTest.php @@ -423,6 +423,8 @@ class ReferenceStackTest extends MediaWikiUnitTestCase { 'name' => 'a', 'text' => 'text-a', 'number' => 1, + 'extends' => 'b', + 'extendsIndex' => 1, ], ], [ @@ -434,11 +436,17 @@ class ReferenceStackTest extends MediaWikiUnitTestCase { 'name' => 'a', 'text' => 'text-a', 'number' => 1, + 'extends' => 'b', + 'extendsIndex' => 1, ], + 'b' => [ + 'number' => 1, + '__placeholder__' => true, + ] ] ], [ - [ 'new', [], 'text-a', 'a', null, 'foo', 1 ], + [ 'new', [], 'text-a', 'a', 'b', 'foo', 1 ], ] ], 'Named extends before parent' => [ @@ -454,6 +462,8 @@ class ReferenceStackTest extends MediaWikiUnitTestCase { 'name' => 'a', 'text' => 'text-a', 'number' => 1, + 'extends' => 'b', + 'extendsIndex' => 1, ], [ 'count' => 0, @@ -461,7 +471,7 @@ class ReferenceStackTest extends MediaWikiUnitTestCase { 'key' => 2, 'name' => 'b', 'text' => 'text-b', - 'number' => 2, + 'number' => 1, ] ], [ @@ -473,6 +483,8 @@ class ReferenceStackTest extends MediaWikiUnitTestCase { 'name' => 'a', 'text' => 'text-a', 'number' => 1, + 'extends' => 'b', + 'extendsIndex' => 1, ], 'b' => [ 'count' => 0, @@ -480,12 +492,12 @@ class ReferenceStackTest extends MediaWikiUnitTestCase { 'key' => 2, 'name' => 'b', 'text' => 'text-b', - 'number' => 2, + 'number' => 1, ] ] ], [ - [ 'new', [], 'text-a', 'a', null, 'foo', 1 ], + [ 'new', [], 'text-a', 'a', 'b', 'foo', 1 ], [ 'new', [], 'text-b', 'b', null, 'foo', 2 ], ] ], @@ -553,6 +565,8 @@ class ReferenceStackTest extends MediaWikiUnitTestCase { 'name' => null, 'text' => 'text-a', 'number' => 1, + 'extends' => 'b', + 'extendsIndex' => 1, ] ], [ @@ -564,11 +578,17 @@ class ReferenceStackTest extends MediaWikiUnitTestCase { 'name' => null, 'text' => 'text-a', 'number' => 1, + 'extends' => 'b', + 'extendsIndex' => 1, ], - ] + 'b' => [ + 'number' => 1, + '__placeholder__' => true, + ] + ], ], [ - [ 'new', [], 'text-a', null, null, 'foo', 1 ], + [ 'new', [], 'text-a', null, 'b', 'foo', 1 ], ] ], 'Anonymous extends before parent' => [ @@ -584,6 +604,8 @@ class ReferenceStackTest extends MediaWikiUnitTestCase { 'name' => null, 'text' => 'text-a', 'number' => 1, + 'extends' => 'b', + 'extendsIndex' => 1, ], [ 'count' => 0, @@ -591,7 +613,7 @@ class ReferenceStackTest extends MediaWikiUnitTestCase { 'key' => 2, 'name' => 'b', 'text' => 'text-b', - 'number' => 2, + 'number' => 1, ] ], [ @@ -603,6 +625,8 @@ class ReferenceStackTest extends MediaWikiUnitTestCase { 'name' => null, 'text' => 'text-a', 'number' => 1, + 'extends' => 'b', + 'extendsIndex' => 1, ], 'b' => [ 'count' => 0, @@ -610,12 +634,12 @@ class ReferenceStackTest extends MediaWikiUnitTestCase { 'key' => 2, 'name' => 'b', 'text' => 'text-b', - 'number' => 2, + 'number' => 1, ] ] ], [ - [ 'new', [], 'text-a', null, null, 'foo', 1 ], + [ 'new', [], 'text-a', null, 'b', 'foo', 1 ], [ 'new', [], 'text-b', 'b', null, 'foo', 2 ], ] ],