Adding "follow" functionality to the Cite extension

* Interim state commit with experimental code.

 * Updates to citeParserTests.txt to check now valid follow
   functionality and newly passing tests.

 * Added to follow refs, <sup style="display: none;" about=...
   to suppress display of hidden sups needed for VE to use
   in editing follow refs.

 * Added code to implemented follow functionality and catch
   invalid usage.

Bug: T51538
Change-Id: Ic3ac8237fd2c490cfaf2fe799759742f72f10686
This commit is contained in:
sbailey 2020-07-08 15:48:02 -07:00 committed by Arlo Breault
parent 46a9900f69
commit 467b82701b
3 changed files with 138 additions and 7 deletions

View file

@ -94,9 +94,28 @@ class Ref extends ExtensionTagHandler {
'inPHPBlock' => true 'inPHPBlock' => true
]; ];
// ref follow nodes should already have their content spans stored in followContent
if ( is_string( $dataMw->body->html ?? null ) ) { if ( is_string( $dataMw->body->html ?? null ) ) {
// First look for the extension's content in data-mw.body.html // First look for the extension's content in data-mw.body.html
$src = $extApi->htmlToWikitext( $html2wtOpts, $dataMw->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 ) ) { } elseif ( is_string( $dataMw->body->id ?? null ) ) {
// If the body isn't contained in data-mw.body.html, look if // If the body isn't contained in data-mw.body.html, look if
// there's an element pointed to by body.id. // there's an element pointed to by body.id.
@ -145,6 +164,14 @@ class Ref extends ExtensionTagHandler {
return ''; // Drop it! 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;
}
$src = $extApi->domToWikitext( $html2wtOpts, $bodyElt, true ); $src = $extApi->domToWikitext( $html2wtOpts, $bodyElt, true );
} else { } else {
$extApi->log( 'error', 'Ref body unavailable for: ' . DOMCompat::getOuterHTML( $node ) ); $extApi->log( 'error', 'Ref body unavailable for: ' . DOMCompat::getOuterHTML( $node ) );

View file

@ -144,17 +144,73 @@ class References extends ExtensionTagHandler {
// FIXME(SSS): Need to clarify semantics here. // FIXME(SSS): Need to clarify semantics here.
// If both the containing <references> elt as well as the nested <ref> // If both the containing <references> elt as well as the nested <ref>
// elt has a group attribute, what takes precedence? // elt has a group attribute, what takes precedence?
$group = $refDmw->attrs->group ?? $referencesGroup ?? ''; $groupName = $refDmw->attrs->group ?? $referencesGroup ?? '';
// NOTE: This will have been trimmed in Utils::getExtArgInfo()'s call // NOTE: This will have been trimmed in Utils::getExtArgInfo()'s call
// to TokenUtils::kvToHash() and ExtensionHandler::normalizeExtOptions() // to TokenUtils::kvToHash() and ExtensionHandler::normalizeExtOptions()
$refName = $refDmw->attrs->name ?? ''; $refName = $refDmw->attrs->name ?? '';
$follow = $refDmw->attrs->follow ?? '';
$hasRefName = strlen( $refName ) > 0;
$hasFollow = strlen( $follow ) > 0;
// Add ref-index linkback // Add ref-index linkback
$linkBack = $doc->createElement( 'sup' ); $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;
} 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];
if ( $ref->contentId ) {
// The named ref did define content, so attach follow content normally
$debugging = 2;
} 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;
}
} 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
}
// 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( $ref = $refsData->add(
$extApi, $group, $refName, $about, $nestedInReferences, $linkBack $extApi, $groupName, $refName, $follow, $contentId, $about, $nestedInReferences,
$linkBack
); );
$errs = []; $errs = [];
@ -163,6 +219,10 @@ class References extends ExtensionTagHandler {
// FIXME: See T260082 for a more complete description of cause and deeper fix // FIXME: See T260082 for a more complete description of cause and deeper fix
$missingContent = ( !empty( $cDp->empty ) || trim( $refDmw->body->extsrc ?? '' ) === '' ); $missingContent = ( !empty( $cDp->empty ) || trim( $refDmw->body->extsrc ?? '' ) === '' );
if ( $refName !== '' && strlen( $follow ) > 0 ) {
$errs[] = [ 'key' => 'cite_error_ref_too_many_keys' ];
}
if ( $missingContent ) { if ( $missingContent ) {
// Check for missing name and content to generate error code // Check for missing name and content to generate error code
if ( $refName === '' ) { if ( $refName === '' ) {
@ -203,6 +263,10 @@ class References extends ExtensionTagHandler {
} }
$lastLinkback = $ref->linkbacks[count( $ref->linkbacks ) - 1] ?? null; $lastLinkback = $ref->linkbacks[count( $ref->linkbacks ) - 1] ?? null;
if ( strlen( $follow ) > 0 ) {
$linkBack->setAttribute( 'style', 'display: none;' );
}
DOMUtils::addAttributes( $linkBack, [ DOMUtils::addAttributes( $linkBack, [
'about' => $about, 'about' => $about,
'class' => 'mw-ref', 'class' => 'mw-ref',
@ -215,7 +279,6 @@ class References extends ExtensionTagHandler {
if ( count( $errs ) > 0 ) { if ( count( $errs ) > 0 ) {
DOMUtils::addTypeOf( $linkBack, 'mw:Error' ); DOMUtils::addTypeOf( $linkBack, 'mw:Error' );
} }
$dataParsoid = new stdClass; $dataParsoid = new stdClass;
if ( isset( $nodeDp->src ) ) { if ( isset( $nodeDp->src ) ) {
$dataParsoid->src = $nodeDp->src; $dataParsoid->src = $nodeDp->src;
@ -256,8 +319,11 @@ class References extends ExtensionTagHandler {
'[' . ( $ref->group ? $ref->group . ' ' : '' ) . $ref->groupIndex . ']' '[' . ( $ref->group ? $ref->group . ' ' : '' ) . $ref->groupIndex . ']'
) )
); );
if ( strlen( $follow ) == 0 || count( $errs ) > 0 ) {
$refLink->appendChild( $refLinkSpan ); $refLink->appendChild( $refLinkSpan );
$linkBack->appendChild( $refLink ); $linkBack->appendChild( $refLink );
}
$node->parentNode->replaceChild( $linkBack, $node ); $node->parentNode->replaceChild( $linkBack, $node );

View file

@ -5,6 +5,7 @@ namespace Wikimedia\Parsoid\Ext\Cite;
use DOMElement; use DOMElement;
use stdClass; use stdClass;
use Wikimedia\Parsoid\Ext\DOMUtils;
use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI; use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI;
class ReferencesData { class ReferencesData {
@ -53,14 +54,16 @@ class ReferencesData {
* @param ParsoidExtensionAPI $extApi * @param ParsoidExtensionAPI $extApi
* @param string $groupName * @param string $groupName
* @param string $refName * @param string $refName
* @param string $follow
* @param string $contentId
* @param string $about * @param string $about
* @param bool $skipLinkback * @param bool $skipLinkback
* @param DOMElement $linkBack * @param DOMElement $linkBack
* @return stdClass * @return stdClass
*/ */
public function add( public function add(
ParsoidExtensionAPI $extApi, string $groupName, string $refName, ParsoidExtensionAPI $extApi, string $groupName, string $refName, string $follow,
string $about, bool $skipLinkback, DOMElement $linkBack string $contentId, string $about, bool $skipLinkback, DOMElement $linkBack
): stdClass { ): stdClass {
$group = $this->getRefGroup( $groupName, true ); $group = $this->getRefGroup( $groupName, true );
// Looks like Cite.php doesn't try to fix ids that already have // Looks like Cite.php doesn't try to fix ids that already have
@ -72,6 +75,41 @@ class ReferencesData {
// in Parsoid: ExtensionHandler#normalizeExtOptions // in Parsoid: ExtensionHandler#normalizeExtOptions
$refName = $extApi->sanitizeHTMLId( $refName ); $refName = $extApi->sanitizeHTMLId( $refName );
$hasRefName = strlen( $refName ) > 0; $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] ) ) { if ( $hasRefName && isset( $group->indexByName[$refName] ) ) {
$ref = $group->indexByName[$refName]; $ref = $group->indexByName[$refName];