Follow up to "follow" functionality for Cite

These refs get a `style="display: none;"` since they're
not intended to be user visible.

Follow refs with errors conform to the proposed spec in T251842

Bug: T51538
Change-Id: Ie4ea28e7f9afde24614874bb4b8e07c5cabafa12
This commit is contained in:
Arlo Breault 2020-07-08 15:48:02 -07:00
parent 467b82701b
commit bb34d30839
3 changed files with 93 additions and 145 deletions

View file

@ -94,28 +94,9 @@ class Ref extends ExtensionTagHandler {
'inPHPBlock' => true
];
// ref follow nodes should already have their content spans stored in followContent
if ( is_string( $dataMw->body->html ?? null ) ) {
// First look for the extension's content in data-mw.body.html
$src = $extApi->htmlToWikitext( $html2wtOpts, $dataMw->body->html );
} elseif ( isset( $dataMw->attrs->follow ) ) {
$src = '';
if ( $node->hasAttribute( 'about' ) ) {
$about = $node->getAttribute( 'about' );
$bodyElt = [];
$followNode = DOMCompat::getElementById( $node->ownerDocument, $dataMw->body->id )
?? null;
if ( $followNode != null ) {
$bodyElt = DOMCompat::querySelectorAll( $followNode,
"span[typeof~='mw:Cite/Follow'][about='" . $about . "']" );
}
// ensure that $bodyElt was found
if ( count( $bodyElt ) > 0 ) {
$src = $extApi->domToWikitext( $html2wtOpts, $bodyElt[0], true );
$src = ltrim( $src, ' ' );
}
}
} elseif ( is_string( $dataMw->body->id ?? null ) ) {
// If the body isn't contained in data-mw.body.html, look if
// there's an element pointed to by body.id.
@ -164,15 +145,35 @@ class Ref extends ExtensionTagHandler {
return ''; // Drop it!
}
// Search for spans with follow content
if ( isset( $bodyElt->firstChild->nextSibling ) &&
DOMUtils::hasTypeOf( $bodyElt->firstChild->nextSibling, 'mw:Cite/Follow' ) ) {
$clonedNode = $bodyElt->cloneNode();
$clonedNode->appendChild( $bodyElt->firstChild );
$bodyElt = $clonedNode;
}
$hasRefName = strlen( $dataMw->attrs->name ?? '' ) > 0;
$hasFollow = strlen( $dataMw->attrs->follow ?? '' ) > 0;
$src = $extApi->domToWikitext( $html2wtOpts, $bodyElt, true );
if ( $hasFollow && !DOMUtils::hasTypeOf( $node, 'mw:Error' ) ) {
$about = $node->getAttribute( 'about' );
$followNode = DOMCompat::querySelector(
$bodyElt, "span[typeof~='mw:Cite/Follow'][about='{$about}']"
);
if ( $followNode ) {
$src = $extApi->domToWikitext( $html2wtOpts, $followNode, true );
$src = ltrim( $src, ' ' );
} else {
$src = '';
}
} else {
if ( $hasRefName ) {
// Follow content may have been added as spans, so drop it
if ( DOMCompat::querySelector( $bodyElt, "span[typeof~='mw:Cite/Follow']" ) ) {
$bodyElt = $bodyElt->cloneNode( true );
foreach ( $bodyElt->childNodes as $child ) {
if ( DOMUtils::hasTypeOf( $child, 'mw:Cite/Follow' ) ) {
DOMCompat::remove( $child );
}
}
}
}
$src = $extApi->domToWikitext( $html2wtOpts, $bodyElt, true );
}
} else {
$extApi->log( 'error', 'Ref body unavailable for: ' . DOMCompat::getOuterHTML( $node ) );
return ''; // Drop it!

View file

@ -149,83 +149,77 @@ class References extends ExtensionTagHandler {
// NOTE: This will have been trimmed in Utils::getExtArgInfo()'s call
// to TokenUtils::kvToHash() and ExtensionHandler::normalizeExtOptions()
$refName = $refDmw->attrs->name ?? '';
$follow = $refDmw->attrs->follow ?? '';
$followName = $refDmw->attrs->follow ?? '';
$hasRefName = strlen( $refName ) > 0;
$hasFollow = strlen( $follow ) > 0;
// Looks like Cite.php doesn't try to fix ids that already have
// a "_" in them. Ex: name="a b" and name="a_b" are considered
// identical. Not sure if this is a feature or a bug.
// It also considers entities equal to their encoding
// (i.e. '&' === '&'), which is done:
// in PHP: Sanitizer#decodeTagAttributes and
// in Parsoid: ExtensionHandler#normalizeExtOptions
$refName = $extApi->sanitizeHTMLId( $refName );
$followName = $extApi->sanitizeHTMLId( $followName );
// Add ref-index linkback
$linkBack = $doc->createElement( 'sup' );
// prechecking various combinations of un-named refs, named refs, follow and error cases to
// properly determine how to proceed with adding a ref or not and generating appropriate code
if ( $hasRefName ) {
if ( $hasFollow ) {
// having both a ref name and follow is an error, the ref should be represented
// and the follow present, but not causing any effect, but add and
// verify follow round trips anyway
$debugging = 0;
$ref = null;
$errs = [];
$hasRefName = strlen( $refName ) > 0;
$hasFollow = strlen( $followName ) > 0;
if ( $hasFollow ) {
if ( $hasRefName ) {
$errs[] = [ 'key' => 'cite_error_ref_too_many_keys' ];
} else {
// normal named ref, so add normally
$debugging = 1;
}
} else {
// Either an un-named ref or a follow
if ( $hasFollow ) {
// Is a follow ref, so check if a named ref has already been defined
$group = $refsData->getRefGroup( $groupName, true );
if ( isset( $group->indexByName[$follow] ) ) {
// Yes the named ref is defined, so lets check if it has defined content
$ref = $group->indexByName[$follow];
// This is a follows ref, so check that a named ref has already
// been defined
$group = $refsData->getRefGroup( $groupName );
if ( isset( $group->indexByName[$followName] ) ) {
$ref = $group->indexByName[$followName];
$span = $c->ownerDocument->createElement( 'span' );
DOMUtils::addTypeOf( $span, 'mw:Cite/Follow' );
$span->setAttribute( 'about', $about );
$span->appendChild(
$c->ownerDocument->createTextNode( ' ' )
);
DOMUtils::migrateChildren( $c, $span );
$c->appendChild( $span );
if ( $ref->contentId ) {
// The named ref did define content, so attach follow content normally
$debugging = 2;
$refContent = $extApi->getContentDOM( $ref->contentId );
ParsoidExtensionAPI::migrateChildrenBetweenDocs(
$c, $refContent, false
);
} else {
// The follow content follows a named ref that did not define content
// and therefore the follow content cannot be appended and must be
// handled differently
$debugging = 3;
// Otherwise, we have a follow that comes after a named
// ref without content so use the follow fragment as
// the content
$ref->contentId = $contentId;
}
} else {
// this follow precedes a named ref being defined or possibly never being defined
// which is a type of error but still must round trip, so we must handle this case
// with special code and not call the add function.
$debugging = 4;
$html = $extApi->domToHtml( $c, true, true );
$refDmw->body = (object)[ 'html' => $html ];
DOMDataUtils::setDataMw( $linkBack, $refDmw );
DOMUtils::addTypeOf( $linkBack, 'mw:Extension/ref mw:Error' );
$node->parentNode->replaceChild( $linkBack, $node );
return;
// do not define a ref
$errs[] = [ 'key' => 'cite_error_references_missing_key' ];
}
// Is an unnamed ref and not a follow, so add the ref normally
} else {
// normal un-named ref, so add normally
$debugging = 5;
}
}
$ref = $refsData->add(
$extApi, $groupName, $refName, $follow, $contentId, $about, $nestedInReferences,
$linkBack
);
$errs = [];
if ( !$ref ) {
$ref = $refsData->add(
$extApi, $groupName, $refName, $about, $nestedInReferences,
$linkBack
);
}
// Check for missing content, added ?? '' to fix T259676 crasher
// FIXME: See T260082 for a more complete description of cause and deeper fix
$missingContent = ( !empty( $cDp->empty ) || trim( $refDmw->body->extsrc ?? '' ) === '' );
if ( $refName !== '' && strlen( $follow ) > 0 ) {
$errs[] = [ 'key' => 'cite_error_ref_too_many_keys' ];
}
if ( $missingContent ) {
// Check for missing name and content to generate error code
if ( $refName === '' ) {
if ( !$hasRefName ) {
if ( !empty( $cDp->selfClose ) ) {
$errs[] = [ 'key' => 'cite_error_ref_no_key' ];
} else {
@ -246,6 +240,8 @@ class References extends ExtensionTagHandler {
$html = '';
$contentDiffers = false;
if ( $ref->hasMultiples ) {
// FIXME: Strip the mw:Cite/Follow wrappers
// See the test, "Forward-referenced ref with magical follow edge case"
$html = $extApi->domToHtml( $c, true, true );
$c = null; // $c is being release in the call above
$contentDiffers = $html !== $ref->cachedHtml;
@ -263,10 +259,6 @@ class References extends ExtensionTagHandler {
}
$lastLinkback = $ref->linkbacks[count( $ref->linkbacks ) - 1] ?? null;
if ( strlen( $follow ) > 0 ) {
$linkBack->setAttribute( 'style', 'display: none;' );
}
DOMUtils::addAttributes( $linkBack, [
'about' => $about,
'class' => 'mw-ref',
@ -279,6 +271,7 @@ class References extends ExtensionTagHandler {
if ( count( $errs ) > 0 ) {
DOMUtils::addTypeOf( $linkBack, 'mw:Error' );
}
$dataParsoid = new stdClass;
if ( isset( $nodeDp->src ) ) {
$dataParsoid->src = $nodeDp->src;
@ -300,13 +293,16 @@ class References extends ExtensionTagHandler {
}
DOMDataUtils::setDataMw( $linkBack, $dmw );
if ( $hasFollow && count( $errs ) === 0 ) {
$linkBack->setAttribute( 'style', 'display: none;' );
}
// refLink is the link to the citation
$refLink = $doc->createElement( 'a' );
DOMUtils::addAttributes( $refLink, [
'href' => $extApi->getPageUri() . '#' . $ref->target,
'style' => 'counter-reset: mw-Ref ' . $ref->groupIndex . ';',
]
);
'href' => $extApi->getPageUri() . '#' . $ref->target,
'style' => 'counter-reset: mw-Ref ' . $ref->groupIndex . ';',
] );
if ( $ref->group ) {
$refLink->setAttribute( 'data-mw-group', $ref->group );
}
@ -317,13 +313,10 @@ class References extends ExtensionTagHandler {
$refLinkSpan->setAttribute( 'class', 'mw-reflink-text' );
$refLinkSpan->appendChild( $doc->createTextNode(
'[' . ( $ref->group ? $ref->group . ' ' : '' ) . $ref->groupIndex . ']'
)
);
) );
if ( strlen( $follow ) == 0 || count( $errs ) > 0 ) {
$refLink->appendChild( $refLinkSpan );
$linkBack->appendChild( $refLink );
}
$refLink->appendChild( $refLinkSpan );
$linkBack->appendChild( $refLink );
$node->parentNode->replaceChild( $linkBack, $node );

View file

@ -5,7 +5,6 @@ namespace Wikimedia\Parsoid\Ext\Cite;
use DOMElement;
use stdClass;
use Wikimedia\Parsoid\Ext\DOMUtils;
use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI;
class ReferencesData {
@ -54,62 +53,17 @@ class ReferencesData {
* @param ParsoidExtensionAPI $extApi
* @param string $groupName
* @param string $refName
* @param string $follow
* @param string $contentId
* @param string $about
* @param bool $skipLinkback
* @param DOMElement $linkBack
* @return stdClass
*/
public function add(
ParsoidExtensionAPI $extApi, string $groupName, string $refName, string $follow,
string $contentId, string $about, bool $skipLinkback, DOMElement $linkBack
ParsoidExtensionAPI $extApi, string $groupName, string $refName,
string $about, bool $skipLinkback, DOMElement $linkBack
): stdClass {
$group = $this->getRefGroup( $groupName, true );
// Looks like Cite.php doesn't try to fix ids that already have
// a "_" in them. Ex: name="a b" and name="a_b" are considered
// identical. Not sure if this is a feature or a bug.
// It also considers entities equal to their encoding
// (i.e. '&' === '&'), which is done:
// in PHP: Sanitizer#decodeTagAttributes and
// in Parsoid: ExtensionHandler#normalizeExtOptions
$refName = $extApi->sanitizeHTMLId( $refName );
$hasRefName = strlen( $refName ) > 0;
$hasFollow = strlen( $follow ) > 0;
// Is this a follow ref that has been preceeded by a named ref which defined
// content or did not define content, or a self closed named ref without content
if ( !$hasRefName && $hasFollow && isset( $group->indexByName[$follow] ) ) {
$ref = $group->indexByName[$follow];
$contentSup = $extApi->getContentDOM( $contentId );
$ownerDoc = $contentSup->ownerDocument;
$span = $ownerDoc->createElement( 'span' );
DOMUtils::addTypeOf( $span, 'mw:Cite/Follow' );
$span->setAttribute( 'about', $about );
$spaceNode = $ownerDoc->createTextNode( ' ' );
$span->appendChild( $spaceNode );
DOMUtils::migrateChildren( $contentSup, $span );
// contentSup is now empty and can be used as a container for migrate children
$contentSup->appendChild( $span );
// If the named ref has defined content
if ( $ref->contentId ) {
$refContent = $extApi->getContentDOM( $ref->contentId );
ParsoidExtensionAPI::migrateChildrenBetweenDocs( $contentSup, $refContent, false );
} else {
// Otherwise we have a follow that comes after named ref without content
// So create a sup in a fragment, set that into the environment and migrate
// the follow content into the fragment
$ref->contentId = $contentId;
}
return $ref;
}
// Must check for error case where $hasRefName and $hasFollow are both present
// which still needs to create the ref, but also flag it with an error due to
// [ 'key' => 'cite_error_ref_too_many_keys' ]; as trapped in References.php
// but which needs to preserve the extra key for round tripping
if ( $hasRefName && isset( $group->indexByName[$refName] ) ) {
$ref = $group->indexByName[$refName];