Rollback all, then redo all, when fixing out-of-order tags; not one-by-one

Imagine the following wikitext:

  <ref name=r/>
  <references>
  <ref name=r>A</ref>
  <ref name=r>B</ref>
  </references>

This is simple. Cite would see these as the following operations, in
order:

1. Use only:       <ref name=r/>
2. References block
3. Define only:    <ref name=r>A</ref>
4. Define only:    <ref name=r>B</ref>

<ref name=r> is defined twice with different content and we get an
error message.

Now, imagine the following wikitext:

  <ref name=r/>
  {{#tag:references|
  <ref name=r>A</ref>
  <ref name=r>B</ref>
  }}

Cite would see these as the following operations, in order:
1. Use only:       <ref name=r/>
2. Use and define: <ref name=r>A</ref>
3. Use and define: <ref name=r>B</ref>
4. References block

When the 'references' block appears, Cite notices that the tag has
parsed content, and deduces that it was called with #tag. We need to
undo the last operations to update internal bookkeeping, as the last
two 'ref' tags do not actually represent ref usages, as we assumed,
but only definitions.

5. Undo:          <ref name=r> reused
6. Define only:   <ref name=r>B</ref>
7. Undo:          <ref name=r> defined
(Right now, it appears to Cite that <ref name=r> was never defined!)
8. Define only: <ref name=r>A</ref>

Thus we get no errors, although we should.

This patch changes the order of the rollback operations:

5. Undo:          <ref name=r> reused
6. Undo:          <ref name=r> defined
7. Define only:   <ref name=r>A</ref>
8. Define only:   <ref name=r>B</ref>

Aha! <ref name=r> is defined twice with different content! We get an
error correctly.

Bug: T124227
Change-Id: I61766c4104856323987cca9a5e4ff85a76b3618b
This commit is contained in:
Bartosz Dziewoński 2016-01-20 21:38:59 +01:00
parent f42e769174
commit e116699480
2 changed files with 39 additions and 12 deletions

View file

@ -604,30 +604,39 @@ class Cite {
if ( strval( $str ) !== '' ) {
$this->mReferencesGroup = $group;
# Detect whether we were sent already rendered <ref>s
# Mostly a side effect of using #tag to call references
# Detect whether we were sent already rendered <ref>s.
# Mostly a side effect of using #tag to call references.
# The following assumes that the parsed <ref>s sent within
# the <references> block were the most recent calls to
# <ref>. This assumption is true for all known use cases,
# but not strictly enforced by the parser. It is possible
# that some unusual combination of #tag, <references> and
# conditional parser functions could be created that would
# lead to malformed references here.
$count = substr_count( $str, Parser::MARKER_PREFIX . "-ref-" );
$redoStack = array();
# Undo effects of calling <ref> while unaware of containing <references>
for ( $i = 1; $i <= $count; $i++ ) {
if ( !$this->mRefCallStack ) {
break;
}
# The following assumes that the parsed <ref>s sent within
# the <references> block were the most recent calls to
# <ref>. This assumption is true for all known use cases,
# but not strictly enforced by the parser. It is possible
# that some unusual combination of #tag, <references> and
# conditional parser functions could be created that would
# lead to malformed references here.
$call = array_pop( $this->mRefCallStack );
$redoStack[] = $call;
if ( $call !== false ) {
list( $type, $ref_argv, $ref_str,
$ref_key, $ref_group, $ref_index ) = $call;
# Undo effects of calling <ref> while unaware of containing <references>
$this->rollbackRef( $type, $ref_key, $ref_group, $ref_index );
}
}
# Rerun <ref> call now that mInReferences is set.
# Rerun <ref> call now that mInReferences is set.
for ( $i = count( $redoStack ) - 1; $i >= 0; $i-- ) {
$call = $redoStack[$i];
if ( $call !== false ) {
list( $type, $ref_argv, $ref_str,
$ref_key, $ref_group, $ref_index ) = $call;
$this->guardedRef( $ref_str, $ref_argv, $parser );
}
}

View file

@ -584,3 +584,21 @@ Multiple definition (mixed outside/inside)
</ol>
!! end
!! test
Multiple definition (inside {{#tag:references}})
!! input
<ref name=a />
{{#tag:references|
<ref name=a>abc</ref>
<ref name=a>def</ref>
}}
!! result
<p><sup id="cite_ref-a_1-0" class="reference"><a href="#cite_note-a-1">[1]</a></sup>
</p>
<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">abc <strong class="error mw-ext-cite-error">Cite error: Invalid <code>&lt;ref&gt;</code> tag; name "a" defined multiple times with different content</strong></span>
</li>
</ol>
!! end