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 -
if ( preg_match( '#
\s*$#D', $parserInput, $matches, PREG_OFFSET_CAPTURE ) ) {
@@ -125,10 +132,13 @@ class FootnoteBodyFormatter {
* @return string Wikitext, wrapped in a single - 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]
-- ↑ page 1
+[1.1]
+- ↑ Cite error: Invalid
<ref>
tag;
+no text was provided for refs named a
- ↑ page 1
+
!! 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]
- ↑ book
-- ↑ page 1
+
- ↑ Cite error: Invalid
<ref>
tag;
+no text was provided for refs named a
- ↑ page 1
+
!! 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]
-- ↑ page
+
- ↑ Cite error: Invalid
<ref>
tag;
+no text was provided for refs named a
- ↑ page
+
!! 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]
-- ↑ page
+
- ↑ Cite error: Invalid
<ref>
tag;
+no text was provided for refs named a
- ↑ page
+
!! 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]
-- ↑ page
+
- ↑ Cite error: Invalid
<ref>
tag;
+no text was provided for refs named a
- ↑ page
+
- ↑ book
@@ -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]
+
+
+- ↑ BAR
+
+- ↑ book name
+
+
+
+!! end
+
+!! test
+Interleaved extends groups
+!! wikitext
+[text-a]
+[text-c]
+
+[page b]
+[page d]
+
+!! html
+[1]
+[2]
+
[1.1]
+[2.1]
+
+
+- ↑ text-a
+
- ↑ page b
+
+
+- ↑ text-c
+
- ↑ page d
+
+
+
+!! 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
- ↑ book
-
-- ↑ another
- ↑ page 2
+- ↑ 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 ],
]
],