Can use extends before its parent

If `extends` is encountered before the parent ref, we reserve the
sequence number and leave a placeholder to record the link between
ref name and number.  This is necessary to render a list like,
"[1] [2.1] [2]", or to use subreferencing when the parent ref is
declared in the references tag.

When a placeholder is encountered during references section rendering,
it means that the parent was never declared.

Change-Id: I611cd1d73f775908926a803fae90d039ce122ab6
This commit is contained in:
Adam Wight 2019-11-29 12:50:07 +01:00
parent a27c33a2e7
commit 008526b3aa
4 changed files with 142 additions and 42 deletions

View file

@ -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 <ol> must be inside the parent's <li>
if ( preg_match( '#</li>\s*$#D', $parserInput, $matches, PREG_OFFSET_CAPTURE ) ) {
@ -125,10 +132,13 @@ class FootnoteBodyFormatter {
* @return string Wikitext, wrapped in a single <li> 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' ] ) ) {

View file

@ -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,
];
}
}

View file

@ -85,8 +85,6 @@ wgCiteBookReferencing=true
</ol></div>
!! end
# TODO:
# * Should render an error.
!! test
T236256 - Extending a reference that doesn't exist #1
!! config
@ -94,14 +92,14 @@ wgCiteBookReferencing=true
!! wikitext
<ref extends="a">page 1</ref>
!! html/php
<sup id="cite_ref-1" class="reference"><a href="#cite_note-1">&#91;1&#93;</a></sup><div class="mw-references-wrap"><ol class="references">
<li id="cite_note-1"><span class="mw-cite-backlink"><a href="#cite_ref-1">↑</a></span> <span class="reference-text">page 1</span>
<sup id="cite_ref-1" class="reference"><a href="#cite_note-1">&#91;1.1&#93;</a></sup><div class="mw-references-wrap"><ol class="references">
<li id="cite_note-a-"><span class="mw-cite-backlink">↑ </span> <span class="error mw-ext-cite-error" lang="en" dir="ltr">Cite error: Invalid <code>&lt;ref&gt;</code> tag;
no text was provided for refs named <code>a</code></span><ol class="mw-extended-references"><li id="cite_note-1"><span class="mw-cite-backlink"><a href="#cite_ref-1">↑</a></span> <span class="reference-text">page 1</span>
</li>
</ol></li>
</ol></div>
!! 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
<p><sup id="cite_ref-1" class="reference"><a href="#cite_note-1">&#91;1&#93;</a></sup>
</p>
<sup id="cite_ref-2" class="reference"><a href="#cite_note-2">&#91;2&#93;</a></sup><div class="mw-references-wrap"><ol class="references">
<sup id="cite_ref-2" class="reference"><a href="#cite_note-2">&#91;2.1&#93;</a></sup><div class="mw-references-wrap"><ol class="references">
<li id="cite_note-1"><span class="mw-cite-backlink"><a href="#cite_ref-1">↑</a></span> <span class="reference-text">book</span>
</li>
<li id="cite_note-2"><span class="mw-cite-backlink"><a href="#cite_ref-2">↑</a></span> <span class="reference-text">page 1</span>
<li id="cite_note-a-"><span class="mw-cite-backlink">↑ </span> <span class="error mw-ext-cite-error" lang="en" dir="ltr">Cite error: Invalid <code>&lt;ref&gt;</code> tag;
no text was provided for refs named <code>a</code></span><ol class="mw-extended-references"><li id="cite_note-2"><span class="mw-cite-backlink"><a href="#cite_ref-2">↑</a></span> <span class="reference-text">page 1</span>
</li>
</ol></li>
</ol></div>
!! end
@ -278,8 +278,6 @@ wgCiteBookReferencing=true
</ol></div>
!! 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
<references group="g2"/>
!! html/php
<p><sup id="cite_ref-a_1-0" class="reference"><a href="#cite_note-a-1">&#91;g1 1&#93;</a></sup>
<sup id="cite_ref-2" class="reference"><a href="#cite_note-2">&#91;g2 1&#93;</a></sup>
<sup id="cite_ref-2" class="reference"><a href="#cite_note-2">&#91;g2 1.1&#93;</a></sup>
</p>
<div class="mw-references-wrap"><ol class="references">
<li id="cite_note-a-1"><span class="mw-cite-backlink"><a href="#cite_ref-a_1-0">↑</a></span> <span class="reference-text">book</span>
</li>
</ol></div>
<div class="mw-references-wrap"><ol class="references">
<li id="cite_note-2"><span class="mw-cite-backlink"><a href="#cite_ref-2">↑</a></span> <span class="reference-text">page</span>
<li id="cite_note-a-"><span class="mw-cite-backlink">↑ </span> <span class="error mw-ext-cite-error" lang="en" dir="ltr">Cite error: Invalid <code>&lt;ref&gt;</code> tag;
no text was provided for refs named <code>a</code></span><ol class="mw-extended-references"><li id="cite_note-2"><span class="mw-cite-backlink"><a href="#cite_ref-2">↑</a></span> <span class="reference-text">page</span>
</li>
</ol></li>
</ol></div>
!! 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
<references group="g1"/>
!! html/php
<p><sup id="cite_ref-a_1-0" class="reference"><a href="#cite_note-a-1">&#91;g1 1&#93;</a></sup>
<sup id="cite_ref-2" class="reference"><a href="#cite_note-2">&#91;1&#93;</a></sup>
<sup id="cite_ref-2" class="reference"><a href="#cite_note-2">&#91;1.1&#93;</a></sup>
</p>
<div class="mw-references-wrap"><ol class="references">
<li id="cite_note-a-1"><span class="mw-cite-backlink"><a href="#cite_ref-a_1-0">↑</a></span> <span class="reference-text">book</span>
</li>
</ol></div><div class="mw-references-wrap"><ol class="references">
<li id="cite_note-2"><span class="mw-cite-backlink"><a href="#cite_ref-2">↑</a></span> <span class="reference-text">page</span>
<li id="cite_note-a-"><span class="mw-cite-backlink">↑ </span> <span class="error mw-ext-cite-error" lang="en" dir="ltr">Cite error: Invalid <code>&lt;ref&gt;</code> tag;
no text was provided for refs named <code>a</code></span><ol class="mw-extended-references"><li id="cite_note-2"><span class="mw-cite-backlink"><a href="#cite_ref-2">↑</a></span> <span class="reference-text">page</span>
</li>
</ol></li>
</ol></div>
!! 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
<references group="g1"/>
!! html/php
<p><sup id="cite_ref-a_1-0" class="reference"><a href="#cite_note-a-1">&#91;1&#93;</a></sup>
<sup id="cite_ref-2" class="reference"><a href="#cite_note-2">&#91;g1 1&#93;</a></sup>
<sup id="cite_ref-2" class="reference"><a href="#cite_note-2">&#91;g1 1.1&#93;</a></sup>
</p>
<div class="mw-references-wrap"><ol class="references">
<li id="cite_note-2"><span class="mw-cite-backlink"><a href="#cite_ref-2">↑</a></span> <span class="reference-text">page</span>
<li id="cite_note-a-"><span class="mw-cite-backlink">↑ </span> <span class="error mw-ext-cite-error" lang="en" dir="ltr">Cite error: Invalid <code>&lt;ref&gt;</code> tag;
no text was provided for refs named <code>a</code></span><ol class="mw-extended-references"><li id="cite_note-2"><span class="mw-cite-backlink"><a href="#cite_ref-2">↑</a></span> <span class="reference-text">page</span>
</li>
</ol></li>
</ol></div><div class="mw-references-wrap"><ol class="references">
<li id="cite_note-a-1"><span class="mw-cite-backlink"><a href="#cite_ref-a_1-0">↑</a></span> <span class="reference-text">book</span>
</li>
@ -439,6 +439,8 @@ invalid names, e.g. too many</span>
</ol></div>
!! end
# TODO
# * This should be invalid, there's no way to know that `footwo` should be subnumbered in a one-pass parse.
!! test
Extends <ref> defined in <references> called with #tag
!! wikitext
@ -460,6 +462,58 @@ Extends <ref> defined in <references> called with #tag
</ol></div>
!! end
!! test
Extends parent <ref> defined in <references> called with #tag
!! wikitext
<ref>BAR</ref>
<ref name="footwo" extends="foo">page 7</ref>
<ref name="foo" />
{{#tag:references|
<ref name="foo">book name</ref>
}}
!! html
<p><sup id="cite_ref-1" class="reference"><a href="#cite_note-1">&#91;1&#93;</a></sup>
<sup id="cite_ref-footwo_2-0" class="reference"><a href="#cite_note-footwo-2">&#91;2.1&#93;</a></sup>
<sup id="cite_ref-foo_3-0" class="reference"><a href="#cite_note-foo-3">&#91;2&#93;</a></sup>
</p>
<div class="mw-references-wrap"><ol class="references">
<li id="cite_note-1"><span class="mw-cite-backlink"><a href="#cite_ref-1">↑</a></span> <span class="reference-text">BAR</span>
</li>
<li id="cite_note-foo-3"><span class="mw-cite-backlink"><a href="#cite_ref-foo_3-0">↑</a></span> <span class="reference-text">book name</span>
<ol class="mw-extended-references"><li id="cite_note-footwo-2"><span class="mw-cite-backlink"><a href="#cite_ref-footwo_2-0">↑</a></span> <span class="reference-text">page 7</span>
</li>
</ol></li>
</ol></div>
!! end
!! test
Interleaved extends groups
!! wikitext
<ref name="a">text-a</ref>
<ref name="c">text-c</ref>
<ref extends="a">page b</ref>
<ref extends="c">page d</ref>
<references />
!! html
<p><sup id="cite_ref-a_1-0" class="reference"><a href="#cite_note-a-1">&#91;1&#93;</a></sup>
<sup id="cite_ref-c_2-0" class="reference"><a href="#cite_note-c-2">&#91;2&#93;</a></sup>
</p><p><sup id="cite_ref-3" class="reference"><a href="#cite_note-3">&#91;1.1&#93;</a></sup>
<sup id="cite_ref-4" class="reference"><a href="#cite_note-4">&#91;2.1&#93;</a></sup>
</p>
<div class="mw-references-wrap"><ol class="references">
<li id="cite_note-a-1"><span class="mw-cite-backlink"><a href="#cite_ref-a_1-0">↑</a></span> <span class="reference-text">text-a</span>
<ol class="mw-extended-references"><li id="cite_note-3"><span class="mw-cite-backlink"><a href="#cite_ref-3">↑</a></span> <span class="reference-text">page b</span>
</li>
</ol></li>
<li id="cite_note-c-2"><span class="mw-cite-backlink"><a href="#cite_ref-c_2-0">↑</a></span> <span class="reference-text">text-c</span>
<ol class="mw-extended-references"><li id="cite_note-4"><span class="mw-cite-backlink"><a href="#cite_ref-4">↑</a></span> <span class="reference-text">page d</span>
</li>
</ol></li>
</ol></div>
!! end
!! test
Multiple groups with extends
!! wikitext
@ -530,6 +584,7 @@ wgCiteBookReferencing=true
<ref name="a">book</ref>
<ref>another</ref>
<ref extends="a">page 2</ref>
<references />
!! html/php
<p><sup id="cite_ref-a_1-0" class="reference"><a href="#cite_note-a-1">&#91;1&#93;</a></sup>
@ -538,10 +593,10 @@ wgCiteBookReferencing=true
</p>
<div class="mw-references-wrap"><ol class="references">
<li id="cite_note-a-1"><span class="mw-cite-backlink"><a href="#cite_ref-a_1-0">↑</a></span> <span class="reference-text">book</span>
</li>
<li id="cite_note-2"><span class="mw-cite-backlink"><a href="#cite_ref-2">↑</a></span> <span class="reference-text">another</span>
<ol class="mw-extended-references"><li id="cite_note-3"><span class="mw-cite-backlink"><a href="#cite_ref-3">↑</a></span> <span class="reference-text">page 2</span>
</li>
</ol></li>
<li id="cite_note-2"><span class="mw-cite-backlink"><a href="#cite_ref-2">↑</a></span> <span class="reference-text">another</span>
</li>
</ol></div>
!! end

View file

@ -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 ],
]
],