From 005176a355bd4f96067012755a6b82c6f75fc2fa Mon Sep 17 00:00:00 2001 From: Pavel Astakhov Date: Sun, 26 May 2019 04:24:47 +0600 Subject: [PATCH] Port Cite extension * All wt2wt, html2wt, and all but one html2html tests pass in hybrid mode when entire html2wt code is run in PHP Set "Serializer: true" in the html2wt section of phpconfig.yaml * The single failing html2html test is a test which is presumably related to the unported extension code, but not sure. Not investigating it now. * Update Parsoid Extension API to provide access to extension source without exposing internals. Change-Id: I6d6e21ad2324acfc4306b32c9055d6c088708c48 --- src/Parsoid/{index.php => Cite.php} | 47 ++- src/Parsoid/Ref.php | 222 ++++++++------ src/Parsoid/RefGroup.php | 127 +++++--- src/Parsoid/RefProcessor.php | 28 +- src/Parsoid/References.php | 442 +++++++++++++++++----------- src/Parsoid/ReferencesData.php | 125 +++++--- 6 files changed, 601 insertions(+), 390 deletions(-) rename src/Parsoid/{index.php => Cite.php} (50%) diff --git a/src/Parsoid/index.php b/src/Parsoid/Cite.php similarity index 50% rename from src/Parsoid/index.php rename to src/Parsoid/Cite.php index 1bea44ff6..2af51a953 100644 --- a/src/Parsoid/index.php +++ b/src/Parsoid/Cite.php @@ -1,48 +1,37 @@ ` and `` extension tag handling - * natively in Parsoid. - * @module ext/Cite - */ +declare( strict_types = 1 ); -namespace Parsoid; +namespace Parsoid\Ext\Cite; -use Parsoid\Ref as Ref; -use Parsoid\References as References; -use Parsoid\RefProcessor as RefProcessor; +use DOMNode; +use Parsoid\Config\Env; /** * Native Parsoid implementation of the Cite extension * that ties together `` and ``. */ class Cite { - public function __construct() { - $this->config = [ + /** @return array */ + public function getConfig(): array { + return [ 'name' => 'cite', 'domProcessors' => [ 'wt2htmlPostProcessor' => RefProcessor::class, - 'html2wtPreProcessor' => function ( ...$args ) {return $this->_html2wtPreProcessor( ...$args ); - } + 'html2wtPreProcessor' => function ( ...$args ) { + return self::html2wtPreProcessor( ...$args ); + } ], 'tags' => [ [ 'name' => 'ref', - 'toDOM' => Ref::toDOM, + 'class' => Ref::class, 'fragmentOptions' => [ 'sealFragment' => true ], - 'serialHandler' => Ref::serialHandler, // FIXME: Rename to toWikitext - 'lintHandler' => Ref::lintHandler - ] - , // FIXME: Do we need (a) domDiffHandler (b) ... others ... + ], [ 'name' => 'references', - 'toDOM' => References::toDOM, - 'serialHandler' => References::serialHandler, - 'lintHandler' => References::lintHandler + 'class' => References::class, ] ], 'styles' => [ @@ -51,7 +40,6 @@ class Cite { ] ]; } - public $config; /** * html -> wt DOM PreProcessor @@ -64,12 +52,11 @@ class Cite { * - for inserted refs, we might want to de-duplicate refs. * - for deleted refs, if the primary ref was deleted, we have to transfer * the primary ref designation to another instance of the named ref. + * + * @param Env $env + * @param DOMNode $body */ - public function _html2wtPreProcessor( $env, $body ) { + private static function html2wtPreProcessor( Env $env, DOMNode $body ) { // TODO } } - -if ( gettype( $module ) === 'object' ) { - $module->exports = $Cite; -} diff --git a/src/Parsoid/Ref.php b/src/Parsoid/Ref.php index 67d6a6254..51ace18d6 100644 --- a/src/Parsoid/Ref.php +++ b/src/Parsoid/Ref.php @@ -1,26 +1,32 @@ -= 99.9 -// phpcs:ignoreFile -// phpcs:disable Generic.Files.LineLength.TooLong -/* REMOVE THIS COMMENT AFTER PORTING */ -namespace Parsoid; +parent->parent->require( './extapi.js' )->versionCheck( '^0.10.0' ); -$temp0 = $ParsoidExtApi; -$ContentUtils = $temp0::ContentUtils; -$DOMDataUtils = $temp0::DOMDataUtils; -$WTUtils = $temp0::WTUtils; -$Promise = $temp0::Promise; +namespace Parsoid\Ext\Cite; + +use DOMDocument; +use DOMElement; +use DOMNode; +use Exception; +use Parsoid\Config\ParsoidExtensionAPI; +use Parsoid\Ext\ExtensionTag; +use Parsoid\Ext\SerialHandler; +use Parsoid\Html2Wt\SerializerState; +use Parsoid\Utils\ContentUtils; +use Parsoid\Utils\DOMCompat; +use Parsoid\Utils\DOMDataUtils; +use Parsoid\Utils\DOMUtils; +use Parsoid\Utils\WTUtils; /** * Simple token transform version of the Ref extension tag. - * - * @class */ -class Ref { - public static function toDOM( $state, $content, $args ) { +class Ref implements ExtensionTag, SerialHandler { + + /** @inheritDoc */ + public function toDOM( ParsoidExtensionAPI $extApi, string $txt, array $extExtArgs ): DOMDocument { // Drop nested refs entirely, unless we've explicitly allowed them - if ( $state->parseContext->extTag === 'ref' -&& !( $state->parseContext->extTagOpts && $state->parseContext->extTagOpts->allowNestedRef ) + if ( ( $extApi->parseContext['extTag'] ?? null ) === 'ref' && + empty( $extApi->parseContext['extTagOpts']['allowNestedRef'] ) ) { return null; } @@ -29,120 +35,140 @@ class Ref { // function. However, we're overly permissive here since we can't // distinguish when that's nested in another template. // The php preprocessor did our expansion. - $allowNestedRef = $state->parseContext->inTemplate && $state->parseContext->extTag !== 'ref'; + $allowNestedRef = !empty( $extApi->parseContext['inTemplate'] ) && + ( $extApi->parseContext['extTag'] ?? null ) !== 'ref'; - return ParsoidExtApi::parseTokenContentsToDOM( $state, $args, '', $content, [ + return $extApi->parseTokenContentsToDOM( + $extExtArgs, + '', + $txt, + [ // NOTE: sup's content model requires it only contain phrasing // content, not flow content. However, since we are building an // in-memory DOM which is simply a tree data structure, we can // nest flow content in a tag. 'wrapperTag' => 'sup', - 'inTemplate' => $state->parseContext->inTemplate, + 'inTemplate' => $extApi->parseContext['inTemplate'] ?? null, 'extTag' => 'ref', - 'extTagOpts' => [ - 'allowNestedRef' => (bool)$allowNestedRef - ], + 'extTagOpts' => [ 'allowNestedRef' => $allowNestedRef ], // FIXME: One-off PHP parser state leak. // This needs a better solution. - 'inPHPBlock' => true + 'inPHPBlock' => true, ] ); } - public static function lintHandler( $ref, $env, $tplInfo, $domLinter ) { + /** @inheritDoc */ + public function hasLintHandler(): bool { + return true; + } + + /** @inheritDoc */ + public function lintHandler( + ParsoidExtensionAPI $extApi, DOMElement $ref, callable $defaultHandler + ): ?DOMNode { // Don't lint the content of ref in ref, since it can lead to cycles // using named refs - if ( WTUtils::fromExtensionContent( $ref, 'references' ) ) { return $ref->nextSibling; - } + if ( WTUtils::fromExtensionContent( $ref, 'references' ) ) { + return $ref->nextSibling; + } - $linkBackId = preg_replace( '/[^#]*#/', '', $ref->firstChild->getAttribute( 'href' ), 1 ); + $refFirstChild = $ref->firstChild; + DOMUtils::assertElt( $refFirstChild ); + $linkBackId = preg_replace( '/[^#]*#/', '', $refFirstChild->getAttribute( 'href' ), 1 ); $refNode = $ref->ownerDocument->getElementById( $linkBackId ); if ( $refNode ) { // Ex: Buggy input wikitext without ref content - $domLinter( $refNode->lastChild, $env, ( $tplInfo->isTemplated ) ? $tplInfo : null ); + $defaultHandler( $refNode->lastChild ); } return $ref->nextSibling; } -} -Ref::serialHandler = [ - 'handle' => /* async */function ( $node, $state, $wrapperUnmodified ) use ( &$DOMDataUtils, &$ContentUtils ) { - $startTagSrc = /* await */ $state->serializer->serializeExtensionStartTag( $node, $state ); + /** @inheritDoc */ + public function fromHTML( + DOMElement $node, SerializerState $state, bool $wrapperUnmodified + ): string { + $startTagSrc = $state->serializer->serializeExtensionStartTag( $node, $state ); $dataMw = DOMDataUtils::getDataMw( $node ); - $env = $state->env; + $env = $state->getEnv(); $html = null; - if ( !$dataMw->body ) { + if ( empty( $dataMw->body ) ) { return $startTagSrc; // We self-closed this already. } else { // We self-closed this already. - if ( gettype( $dataMw->body->html ) === 'string' ) { - // First look for the extension's content in data-mw.body.html - $html = $dataMw->body->html; - } elseif ( gettype( $dataMw->body->id ) === 'string' ) { - // If the body isn't contained in data-mw.body.html, look if - // there's an element pointed to by body.id. - $bodyElt = $node->ownerDocument->getElementById( $dataMw->body->id ); - if ( !$bodyElt && $env->page->editedDoc ) { - // Try to get to it from the main page. - // This can happen when the is inside another - // extension, most commonly inside a . - // The recursive call to serializeDOM puts us inside - // inside a new document. - $bodyElt = $env->page->editedDoc->getElementById( $dataMw->body->id ); - } - if ( $bodyElt ) { - // n.b. this is going to drop any diff markers but since - // the dom differ doesn't traverse into extension content - // none should exist anyways. - DOMDataUtils::visitAndStoreDataAttribs( $bodyElt ); - $html = ContentUtils::toXML( $bodyElt, [ 'innerXML' => true ] ); - DOMDataUtils::visitAndLoadDataAttribs( $bodyElt ); - } else { - // Some extra debugging for VisualEditor - $extraDebug = ''; - $firstA = $node->querySelector( 'a[href]' ); - if ( $firstA && preg_match( '/^#/', $firstA->getAttribute( 'href' ) || '' ) ) { - $href = $firstA->getAttribute( 'href' ) || ''; - try { - $ref = $node->ownerDocument->querySelector( $href ); - if ( $ref ) { - $extraDebug += ' [own doc: ' . $ref->outerHTML . ']'; - } - $ref = $env->page->editedDoc->querySelector( $href ); - if ( $ref ) { - $extraDebug += ' [main doc: ' . $ref->outerHTML . ']'; - } - } catch ( Exception $e ) { - }// eslint-disable-line - // eslint-disable-line - if ( !$extraDebug ) { - $extraDebug = ' [reference ' . $href . ' not found]'; - } + if ( is_string( $dataMw->body->html ?? null ) ) { + // First look for the extension's content in data-mw.body.html + $html = $dataMw->body->html; + } 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. + $bodyElt = DOMCompat::getElementById( $node->ownerDocument, $dataMw->body->id ); + $editedDoc = $env->getPageConfig()->editedDoc ?? null; + if ( !$bodyElt && $editedDoc ) { + // Try to get to it from the main page. + // This can happen when the is inside another + // extension, most commonly inside a . + // The recursive call to serializeDOM puts us inside + // inside a new document. + $bodyElt = DOMCompat::getElementById( $editedDoc, $dataMw->body->id ); } - $env->log( 'error/' . $dataMw->name, - 'extension src id ' . $dataMw->body->id -. ' points to non-existent element for:', $node->outerHTML, - '. More debug info: ', $extraDebug - ); + if ( $bodyElt ) { + // n.b. this is going to drop any diff markers but since + // the dom differ doesn't traverse into extension content + // none should exist anyways. + DOMDataUtils::visitAndStoreDataAttribs( $bodyElt ); + $html = ContentUtils::toXML( $bodyElt, [ 'innerXML' => true ] ); + DOMDataUtils::visitAndLoadDataAttribs( $bodyElt ); + } else { + // Some extra debugging for VisualEditor + $extraDebug = ''; + $firstA = DOMCompat::querySelector( $node, 'a[href]' ); + $href = $firstA->getAttribute( 'href' ); + if ( $firstA && preg_match( '/^#/', $href ) ) { + try { + $ref = DOMCompat::querySelector( $node->ownerDocument, $href ); + if ( $ref ) { + $extraDebug .= ' [own doc: ' . DOMCompat::getOuterHTML( $ref ) . ']'; + } + $ref = DOMCompat::querySelector( $env->getPageConfig()->editedDoc, $href ); + if ( $ref ) { + $extraDebug .= ' [main doc: ' . DOMCompat::getOuterHTML( $ref ) . ']'; + } + } catch ( Exception $e ) { + }// eslint-disable-line + // eslint-disable-line + if ( !$extraDebug ) { + $extraDebug = ' [reference ' . $href . ' not found]'; + } + } + $env->log( + 'error/' . $dataMw->name, + 'extension src id ' . $dataMw->body->id . ' points to non-existent element for:', + DOMCompat::getOuterHTML( $node ), + '. More debug info: ', + $extraDebug + ); + return ''; // Drop it! + } + } else { // Drop it! + $env->log( 'error', 'Ref body unavailable for: ' . DOMCompat::getOuterHTML( $node ) ); return ''; // Drop it! - } - } else { // Drop it! - - $env->log( 'error', 'Ref body unavailable for: ' . $node->outerHTML ); - return ''; // Drop it! - }// Drop it! + } // Drop it! } - $src = /* await */ $state->serializer->serializeHTML( [ - 'env' => $state->env, - 'extName' => $dataMw->name, - // FIXME: One-off PHP parser state leak. + $src = $state->serializer->serializeHTML( + [ + 'env' => $state->getEnv(), + 'extName' => $dataMw->name, // FIXME: One-off PHP parser state leak. // This needs a better solution. 'inPHPBlock' => true - ], $html + ], + $html ); - return $startTagSrc + $src . 'name . '>'; + return $startTagSrc . $src . 'name . '>'; } -]; - -$module->exports = $Ref; + /** @inheritDoc */ + public function before( DOMElement $node, DOMNode $otherNode, SerializerState $state ): ?array { + return null; + } +} diff --git a/src/Parsoid/RefGroup.php b/src/Parsoid/RefGroup.php index 6c364676e..de89f790b 100644 --- a/src/Parsoid/RefGroup.php +++ b/src/Parsoid/RefGroup.php @@ -1,69 +1,113 @@ parent->parent->parent->parent->require( './extapi.js' )->versionCheck( '^0.10.0' ); -$temp0 = $ParsoidExtApi; -$DOMDataUtils = $temp0::DOMDataUtils; -$DOMUtils = $temp0::DOMUtils; +namespace Parsoid\Ext\Cite; + +use DOMDocument; +use DOMElement; +use Parsoid\Config\Env; +use Parsoid\Utils\DOMDataUtils; +use Parsoid\Utils\DOMUtils; +use Parsoid\Utils\Title; +use stdClass; /** * Helper class used by `` implementation. - * @class */ class RefGroup { - public function __construct( $group ) { - $this->name = $group || ''; - $this->refs = []; - $this->indexByName = new Map(); - } + + /** + * @var string + */ public $name; + + /** + * @var stdClass[] + */ public $refs; + + /** + * @var stdClass[] + */ public $indexByName; - public function renderLine( $env, $refsList, $ref ) { + /** + * RefGroup constructor. + * @param string $group + */ + public function __construct( string $group = '' ) { + $this->name = $group; + $this->refs = []; + $this->indexByName = []; + } + + /** + * Generate leading linkbacks + * @param string $href + * @param string|null $group + * @param string $text + * @param DOMDocument $ownerDoc + * @param Env $env + * @return DOMElement + */ + private static function createLinkback( + string $href, ?string $group, string $text, DOMDocument $ownerDoc, Env $env + ): DOMElement { + $a = $ownerDoc->createElement( 'a' ); + $s = $ownerDoc->createElement( 'span' ); + $textNode = $ownerDoc->createTextNode( $text . ' ' ); + $title = Title::newFromText( + $env->getPageConfig()->getTitle() . '#' . $href, + $env->getSiteConfig() + ); + $a->setAttribute( 'href', $env->makeLink( $title ) ); + $s->setAttribute( 'class', 'mw-linkback-text' ); + if ( $group ) { + $a->setAttribute( 'data-mw-group', $group ); + } + $s->appendChild( $textNode ); + $a->appendChild( $s ); + return $a; + } + + /** + * @param Env $env + * @param DOMElement $refsList + * @param stdClass $ref + */ + public function renderLine( Env $env, DOMElement $refsList, stdClass $ref ): void { $ownerDoc = $refsList->ownerDocument; // Generate the li and set ref content first, so the HTML gets parsed. // We then append the rest of the ref nodes before the first node $li = $ownerDoc->createElement( 'li' ); + $refDir = $ref->dir; + $refTarget = $ref->target; + $refContent = $ref->content; + $refGroup = $ref->group; DOMDataUtils::addAttributes( $li, [ - 'about' => '#' . $ref->target, - 'id' => $ref->target, - 'class' => ( [ 'rtl', 'ltr' ]->includes( $ref->dir ) ) ? 'mw-cite-dir-' . $ref->dir : null + 'about' => '#' . $refTarget, + 'id' => $refTarget, + 'class' => ( $refDir === 'rtl' || $refDir === 'ltr' ) ? 'mw-cite-dir-' . $refDir : null ] ); $reftextSpan = $ownerDoc->createElement( 'span' ); - DOMDataUtils::addAttributes( $reftextSpan, [ - 'id' => 'mw-reference-text-' . $ref->target, - 'class' => 'mw-reference-text' + DOMDataUtils::addAttributes( + $reftextSpan, + [ + 'id' => 'mw-reference-text-' . $refTarget, + 'class' => 'mw-reference-text', ] ); - if ( $ref->content ) { - $content = $env->fragmentMap->get( $ref->content )[ 0 ]; + if ( $refContent ) { + $content = $env->getFragment( $refContent )[0]; DOMUtils::migrateChildrenBetweenDocs( $content, $reftextSpan ); DOMDataUtils::visitAndLoadDataAttribs( $reftextSpan ); } $li->appendChild( $reftextSpan ); - // Generate leading linkbacks - $createLinkback = function ( $href, $group, $text ) use ( &$ownerDoc, &$env ) { - $a = $ownerDoc->createElement( 'a' ); - $s = $ownerDoc->createElement( 'span' ); - $textNode = $ownerDoc->createTextNode( $text . ' ' ); - $a->setAttribute( 'href', $env->page->titleURI . '#' . $href ); - $s->setAttribute( 'class', 'mw-linkback-text' ); - if ( $group ) { - $a->setAttribute( 'data-mw-group', $group ); - } - $s->appendChild( $textNode ); - $a->appendChild( $s ); - return $a; - }; if ( count( $ref->linkbacks ) === 1 ) { - $linkback = $createLinkback( $ref->id, $ref->group, "↑" ); + $linkback = self::createLinkback( $ref->id, $refGroup, "↑", $ownerDoc, $env ); $linkback->setAttribute( 'rel', 'mw:referencedBy' ); $li->insertBefore( $linkback, $reftextSpan ); } else { @@ -72,10 +116,9 @@ class RefGroup { $span->setAttribute( 'rel', 'mw:referencedBy' ); $li->insertBefore( $span, $reftextSpan ); - $ref->linkbacks->forEach( function ( $lb, $i ) use ( &$span, &$createLinkback, &$ref ) { - $span->appendChild( $createLinkback( $lb, $ref->group, $i + 1 ) ); + foreach ( $ref->linkbacks as $i => $lb ) { + $span->appendChild( self::createLinkback( $lb, $refGroup, $i + 1, $ownerDoc, $env ) ); } - ); } // Space before content node @@ -85,5 +128,3 @@ class RefGroup { $refsList->appendChild( $li ); } } - -$module->exports = $RefGroup; diff --git a/src/Parsoid/RefProcessor.php b/src/Parsoid/RefProcessor.php index eb0eb1aed..53aa02862 100644 --- a/src/Parsoid/RefProcessor.php +++ b/src/Parsoid/RefProcessor.php @@ -1,25 +1,29 @@ html DOM PostProcessor - * - * @class */ class RefProcessor { - public function run( $body, $env, $options, $atTopLevel ) { + + /** + * @param DOMElement $body + * @param Env $env + * @param array $options + * @param bool $atTopLevel + */ + public function run( + DOMElement $body, Env $env, array $options = [], bool $atTopLevel = false + ): void { if ( $atTopLevel ) { $refsData = new ReferencesData( $env ); - References::_processRefs( $env, $refsData, $body ); + References::processRefs( $env, $refsData, $body ); References::insertMissingReferencesIntoDOM( $refsData, $body ); } } } - -$module->exports = $RefProcessor; diff --git a/src/Parsoid/References.php b/src/Parsoid/References.php index 8ba217db1..20379749d 100644 --- a/src/Parsoid/References.php +++ b/src/Parsoid/References.php @@ -1,23 +1,31 @@ -= 99.9 -// phpcs:ignoreFile -// phpcs:disable Generic.Files.LineLength.TooLong -/* REMOVE THIS COMMENT AFTER PORTING */ -namespace Parsoid; +parent->parent->require( './extapi.js' )->versionCheck( '^0.10.0' ); -$temp0 = $ParsoidExtApi; -$ContentUtils = $temp0::ContentUtils; -$DOMDataUtils = $temp0::DOMDataUtils; -$DOMUtils = $temp0::DOMUtils; -$TokenUtils = $temp0::TokenUtils; -$WTUtils = $temp0::WTUtils; -$Promise = $temp0::Promise; +namespace Parsoid\Ext\Cite; -/** - * @class - */ -class References { - public static function hasRef( $node ) { +use DOMDocument; +use DOMElement; +use DOMNode; +use Parsoid\Config\Env; +use Parsoid\Config\ParsoidExtensionAPI; +use Parsoid\Ext\ExtensionTag; +use Parsoid\Ext\SerialHandler; +use Parsoid\Html2Wt\SerializerState; +use Parsoid\Utils\ContentUtils; +use Parsoid\Utils\DOMCompat; +use Parsoid\Utils\DOMDataUtils; +use Parsoid\Utils\DOMUtils; +use Parsoid\Utils\Title; +use Parsoid\Utils\TokenUtils; +use Parsoid\Utils\WTUtils; +use Wikimedia\Assert\Assert; + +class References implements ExtensionTag, SerialHandler { + /** + * @param DOMNode $node + * @return bool + */ + private static function hasRef( DOMNode $node ): bool { $c = $node->firstChild; while ( $c ) { if ( DOMUtils::isElt( $c ) ) { @@ -33,52 +41,37 @@ class References { return false; } - public static function toDOM( $state, $content, $args ) { - return ParsoidExtApi::parseTokenContentsToDOM( $state, $args, '', $content, [ - 'wrapperTag' => 'div', - 'extTag' => 'references', - 'inTemplate' => $state->parseContext->inTemplate - ] - )->then( function ( $doc ) use ( &$TokenUtils, &$args, &$state ) { - $refsOpts = Object::assign( [ - 'group' => null, - 'responsive' => null - ], TokenUtils::kvToHash( $args, true ) - ); - - $frag = References::createReferences( $state->env, $doc, $doc->body, $refsOpts, function ( $dp ) use ( &$state ) { - $dp->src = ( $state->extToken->hasAttribute( 'source' ) ) ? $state->extToken->getAttribute( 'source' ) : null; - // Redundant - also present on doc.body.firstChild, but feels cumbersome to use - $dp->selfClose = $state->extToken->dataAttribs->selfClose; - } - ); - $doc->body->appendChild( $frag ); - - return $doc; - } - ); - } - - public static function createReferences( $env, $doc, $body, $refsOpts, $modifyDp, $autoGenerated ) { + /** + * @param Env $env + * @param DOMDocument $doc + * @param DOMNode|null $body + * @param array $refsOpts + * @param callable|null $modifyDp + * @param bool $autoGenerated + * @return DOMElement + */ + private static function createReferences( + Env $env, DOMDocument $doc, ?DOMNode $body, array $refsOpts, + ?callable $modifyDp, bool $autoGenerated = false + ): DOMElement { $ol = $doc->createElement( 'ol' ); - $ol->classList->add( 'mw-references' ); - $ol->classList->add( 'references' ); + DOMCompat::getClassList( $ol )->add( 'mw-references' ); + DOMCompat::getClassList( $ol )->add( 'references' ); if ( $body ) { DOMUtils::migrateChildren( $body, $ol ); } // Support the `responsive` parameter - $rrOpts = $env->conf->wiki->responsiveReferences; - $responsiveWrap = $rrOpts->enabled; - if ( $refsOpts->responsive !== null ) { - $responsiveWrap = $refsOpts->responsive !== '0'; + $rrOpts = $env->getSiteConfig()->responsiveReferences(); + $responsiveWrap = !empty( $rrOpts['enabled'] ); + if ( $refsOpts['responsive'] !== null ) { + $responsiveWrap = $refsOpts['responsive'] !== '0'; } - $frag = null; if ( $responsiveWrap ) { $div = $doc->createElement( 'div' ); - $div->classList->add( 'mw-references-wrap' ); + DOMCompat::getClassList( $div )->add( 'mw-references-wrap' ); $div->appendChild( $ol ); $frag = $div; } else { @@ -94,64 +87,70 @@ class References { } $dp = DOMDataUtils::getDataParsoid( $frag ); - if ( $refsOpts->group ) { // No group for the empty string either - $dp->group = $refsOpts->group; - $ol->setAttribute( 'data-mw-group', $refsOpts->group ); + if ( $refsOpts['group'] ) { // No group for the empty string either + $dp->group = $refsOpts['group']; + $ol->setAttribute( 'data-mw-group', $refsOpts['group'] ); } - if ( gettype( $modifyDp ) === 'function' ) { + if ( $modifyDp ) { $modifyDp( $dp ); } return $frag; } - public static function extractRefFromNode( $node, $refsData, $referencesAboutId, $referencesGroup, $nestedRefsHTML ) { - $env = $refsData->env; + private static function extractRefFromNode( + DOMElement $node, ReferencesData $refsData, ?string $referencesAboutId = null, + ?string $referencesGroup = '', array $nestedRefsHTML = [] + ): void { + $env = $refsData->getEnv(); $doc = $node->ownerDocument; $nestedInReferences = $referencesAboutId !== null; // This is data-parsoid from the dom fragment node that's gone through // dsr computation and template wrapping. $nodeDp = DOMDataUtils::getDataParsoid( $node ); - $typeOf = $node->getAttribute( 'typeof' ) || ''; + $typeOf = $node->getAttribute( 'typeof' ); $isTplWrapper = preg_match( '/\bmw:Transclusion\b/', $typeOf ); $nodeType = preg_replace( '/mw:DOMFragment\/sealed\/ref/', '', $typeOf, 1 ); $content = $nodeDp->html; - $tplDmw = ( $isTplWrapper ) ? DOMDataUtils::getDataMw( $node ) : null; + $tplDmw = $isTplWrapper ? DOMDataUtils::getDataMw( $node ) : null; // This is the that's the meat of the sealed fragment - $c = $env->fragmentMap->get( $content )[ 0 ]; + /** @var DOMElement $c */ + $c = $env->getFragment( $content )[0]; + DOMUtils::assertElt( $c ); // All the actions that require loaded data-attributes on `c` are done // here so that we can quickly store those away for later. DOMDataUtils::visitAndLoadDataAttribs( $c ); $cDp = DOMDataUtils::getDataParsoid( $c ); $refDmw = DOMDataUtils::getDataMw( $c ); - if ( !$cDp->empty && self::hasRef( $c ) ) { // nested ref-in-ref - self::_processRefs( $env, $refsData, $c ); + if ( empty( $cDp->empty ) && self::hasRef( $c ) ) { // nested ref-in-ref + self::processRefs( $env, $refsData, $c ); } DOMDataUtils::visitAndStoreDataAttribs( $c ); // Use the about attribute on the wrapper with priority, since it's // only added when the wrapper is a template sibling. - $about = ( $node->hasAttribute( 'about' ) ) ? $node->getAttribute( 'about' ) : - ( $c->hasAttribute( 'about' ) ) ? $c->getAttribute( 'about' ) : ''; + $about = $node->hasAttribute( 'about' ) + ? $node->getAttribute( 'about' ) + : $c->getAttribute( 'about' ); // FIXME(SSS): Need to clarify semantics here. // If both the containing elt as well as the nested // elt has a group attribute, what takes precedence? - $group = $refDmw->attrs->group || $referencesGroup || ''; - $refName = $refDmw->attrs->name || ''; + $group = $refDmw->attrs['group'] ?? $referencesGroup ?? null; + $refName = $refDmw->attrs['name'] ?? ''; $ref = $refsData->add( $env, $group, $refName, $about, $nestedInReferences ); // Add ref-index linkback $linkBack = $doc->createElement( 'sup' ); // FIXME: Lot of useless work for an edge case - if ( $cDp->empty ) { + if ( !empty( $cDp->empty ) ) { // Discard wrapper if there was no input wikitext $content = null; - if ( $cDp->selfClose ) { - $refDmw->body = null; + if ( !empty( $cDp->selfClose ) ) { + unset( $refDmw->body ); } else { $refDmw->body = [ 'html' => '' ]; } @@ -178,14 +177,15 @@ class References { DOMDataUtils::addAttributes( $linkBack, [ 'about' => $about, 'class' => 'mw-ref', - 'id' => ( $nestedInReferences ) ? null : - ( ( $ref->name ) ? $ref->linkbacks[ count( $ref->linkbacks ) - 1 ] : $ref->id ), + 'id' => $nestedInReferences + ? null + : ( $ref->name ? end( $ref->linkbacks ) : $ref->id ), 'rel' => 'dc:references', 'typeof' => $nodeType ] ); DOMDataUtils::addTypeOf( $linkBack, 'mw:Extension/ref' ); - $dataParsoid = [ + $dataParsoid = (object)[ 'src' => $nodeDp->src, 'dsr' => $nodeDp->dsr, 'pi' => $nodeDp->pi @@ -199,9 +199,13 @@ class References { // refLink is the link to the citation $refLink = $doc->createElement( 'a' ); + $title = Title::newFromText( + $env->getPageConfig()->getTitle() . '#' . $ref->target, + $env->getSiteConfig() + ); DOMDataUtils::addAttributes( $refLink, [ - 'href' => $env->page->titleURI . '#' . $ref->target, - 'style' => 'counter-reset: mw-Ref ' . $ref->groupIndex . ';' + 'href' => $env->makeLink( $title ), + 'style' => 'counter-reset: mw-Ref ' . $ref->groupIndex . ';', ] ); if ( $ref->group ) { @@ -212,8 +216,8 @@ class References { // for browsers that don't support counters $refLinkSpan = $doc->createElement( 'span' ); $refLinkSpan->setAttribute( 'class', 'mw-reflink-text' ); - $refLinkSpan->appendChild( $doc->createTextNode( '[' -. ( ( $ref->group ) ? $ref->group . ' ' : '' ) . $ref->groupIndex . ']' + $refLinkSpan->appendChild( $doc->createTextNode( + '[' . ( $ref->group ? $ref->group . ' ' : '' ) . $ref->groupIndex . ']' ) ); $refLink->appendChild( $refLinkSpan ); @@ -230,27 +234,36 @@ class References { // Keep the first content to compare multiple s with the same name. if ( !$ref->content ) { $ref->content = $content; - $ref->dir = strtolower( $refDmw->attrs->dir || '' ); + $ref->dir = strtolower( $refDmw->attrs['dir'] ?? '' ); } } - public static function insertReferencesIntoDOM( $refsNode, $refsData, $nestedRefsHTML, $autoGenerated ) { - $env = $refsData->env; - $isTplWrapper = preg_match( '/\bmw:Transclusion\b/', $refsNode->getAttribute( 'typeof' ) || '' ); + /** + * @param DOMElement $refsNode + * @param ReferencesData $refsData + * @param array $nestedRefsHTML + * @param bool $autoGenerated + */ + private static function insertReferencesIntoDOM( + DOMElement $refsNode, ReferencesData $refsData, array $nestedRefsHTML, bool $autoGenerated = false + ): void { + $env = $refsData->getEnv(); + $isTplWrapper = preg_match( '/\bmw:Transclusion\b/', $refsNode->getAttribute( 'typeof' ) ); $dp = DOMDataUtils::getDataParsoid( $refsNode ); - $group = $dp->group || ''; + $group = $dp->group ?? ''; if ( !$isTplWrapper ) { $dataMw = DOMDataUtils::getDataMw( $refsNode ); - if ( !count( Object::keys( $dataMw ) ) ) { + if ( !count( (array)$dataMw ) ) { // FIXME: This can be moved to `insertMissingReferencesIntoDOM` - Assert::invariant( $autoGenerated ); - $dataMw = [ + Assert::invariant( $autoGenerated, 'Expected non empty $dataMw or $autoGenerated is true' ); + $dataMw = (object)[ 'name' => 'references', - 'attrs' => [ - 'group' => $group || null - ] - ]; // Dont emit empty keys - + 'attrs' => [], + ]; + // Dont emit empty keys + if ( $group ) { + $dataMw->attrs['group'] = $group; + } DOMDataUtils::setDataMw( $refsNode, $dataMw ); } @@ -259,22 +272,23 @@ class References { if ( $autoGenerated ) { $dataMw->autoGenerated = true; } elseif ( count( $nestedRefsHTML ) > 0 ) { - $dataMw->body = [ 'html' => "\n" . implode( '', $nestedRefsHTML ) ]; - } elseif ( !$dp->selfClose ) { - $dataMw->body = [ 'html' => '' ]; + $dataMw->body = (object)[ 'html' => "\n" . implode( $nestedRefsHTML ) ]; + } elseif ( empty( $dp->selfClose ) ) { + $dataMw->body = (object)[ 'html' => '' ]; } else { - $dataMw->body = null; + unset( $dataMw->body ); } - $dp->selfClose = null; + // @phan-suppress-next-line PhanTypeObjectUnsetDeclaredProperty + unset( $dp->selfClose ); } $refGroup = $refsData->getRefGroup( $group ); // Deal with responsive wrapper - if ( $refsNode->classList->contains( 'mw-references-wrap' ) ) { - $rrOpts = $env->conf->wiki->responsiveReferences; - if ( $refGroup && count( $refGroup->refs ) > $rrOpts->threshold ) { - $refsNode->classList->add( 'mw-references-columns' ); + if ( DOMCompat::getClassList( $refsNode )->contains( 'mw-references-wrap' ) ) { + $rrOpts = $env->getSiteConfig()->responsiveReferences(); + if ( $refGroup && count( $refGroup->refs ) > $rrOpts['threshold'] ) { + DOMCompat::getClassList( $refsNode )->add( 'mw-references-columns' ); } $refsNode = $refsNode->firstChild; } @@ -289,8 +303,9 @@ class References { } if ( $refGroup ) { - $refGroup->refs->forEach( function ( $ref ) use ( &$refGroup, &$env, &$refsNode ) {return $refGroup->renderLine( $env, $refsNode, $ref ); - } ); + foreach ( $refGroup->refs as $ref ) { + $refGroup->renderLine( $env, $refsNode, $ref ); + } } // Remove the group from refsData @@ -301,20 +316,32 @@ class References { * Process ``s left behind after the DOM is fully processed. * We process them as if there was an implicit `` tag at * the end of the DOM. + * + * @param ReferencesData $refsData + * @param DOMNode $node */ - public static function insertMissingReferencesIntoDOM( $refsData, $node ) { - $env = $refsData->env; + public static function insertMissingReferencesIntoDOM( + ReferencesData $refsData, DOMNode $node + ): void { + $env = $refsData->getEnv(); $doc = $node->ownerDocument; - $refsData->refGroups->forEach( function ( $refsValue, $refsGroup ) use ( &$env, &$doc, &$node, &$refsData ) { - $frag = References::createReferences( $env, $doc, null, [ + foreach ( $refsData->getRefGroups() as $refsGroup ) { + $frag = self::createReferences( + $env, + $doc, + null, + [ 'group' => $refsGroup, - 'responsive' => null - ], function ( $dp ) { + 'responsive' => null, + ], + function ( $dp ) use ( $env ) { // The new references come out of "nowhere", so to make selser work - // propertly, add a zero-sized DSR pointing to the end of the document. - $dp->dsr = [ count( $env->page->src ), count( $env->page->src ), 0, 0 ]; - }, true + // property, add a zero-sized DSR pointing to the end of the document. + $contentLength = mb_strlen( $env->getPageMainContent() ); + $dp->dsr = [ $contentLength, $contentLength, 0, 0 ]; + }, + true ); // Add a \n before the
    so that when serialized to wikitext, @@ -322,39 +349,38 @@ class References { $node->appendChild( $doc->createTextNode( "\n" ) ); $node->appendChild( $frag ); - References::insertReferencesIntoDOM( $frag, $refsData, [ '' ], true ); + self::insertReferencesIntoDOM( $frag, $refsData, [ '' ], true ); } - ); } - public static function lintHandler( $refs, $env, $tplInfo, $domLinter ) { - // Nothing to do - // - // FIXME: Not entirely true for scenarios where the tags - // are defined in the references section that is itself templated. - // - // {{1x|\nfoo\n}} - // - // In this example, the references tag has the right tplInfo and - // when the tag is processed in the body of the article where - // it is accessed, there is no relevant template or dsr info available. - // - // Ignoring for now. - return $refs->nextSibling; - } - - public static function _processRefs( $env, $refsData, $node ) { + /** + * @param Env $env + * @param ReferencesData $refsData + * @param DOMElement $node + */ + public static function processRefs( Env $env, ReferencesData $refsData, DOMElement $node ): void { $child = $node->firstChild; while ( $child !== null ) { $nextChild = $child->nextSibling; - if ( DOMUtils::isElt( $child ) ) { + if ( $child instanceof DOMElement ) { if ( WTUtils::isSealedFragmentOfType( $child, 'ref' ) ) { self::extractRefFromNode( $child, $refsData ); - } elseif ( preg_match( ( '/(?:^|\s)mw:Extension\/references(?=$|\s)/' ), $child->getAttribute( 'typeof' ) || '' ) ) { + } elseif ( + preg_match( + '#(?:^|\s)mw:Extension/references(?=$|\s)#', + $child->getAttribute( 'typeof' ) ?? '' + ) + ) { $referencesId = $child->getAttribute( 'about' ) || ''; - $referencesGroup = DOMDataUtils::getDataParsoid( $child )->group; + $referencesGroup = DOMDataUtils::getDataParsoid( $child )->group ?? null; $nestedRefsHTML = []; - self::_processRefsInReferences( $refsData, $child, $referencesId, $referencesGroup, $nestedRefsHTML ); + self::processRefsInReferences( + $refsData, + $child, + $referencesId, + $referencesGroup, + $nestedRefsHTML + ); self::insertReferencesIntoDOM( $child, $refsData, $nestedRefsHTML ); } else { // inline media -- look inside the data-mw attribute @@ -385,17 +411,17 @@ class References { * See T214994 * ----------------------------------------------------------------- */ $dmw = DOMDataUtils::getDataMw( $child ); - $caption = $dmw->caption; + $caption = $dmw->caption ?? null; if ( $caption ) { // Extract the caption HTML, build the DOM, process refs, // serialize to HTML, update the caption HTML. $captionDOM = ContentUtils::ppToDOM( $env, $caption ); - self::_processRefs( $env, $refsData, $captionDOM ); + self::processRefs( $env, $refsData, $captionDOM ); $dmw->caption = ContentUtils::ppToXML( $captionDOM, [ 'innerXML' => true ] ); } } if ( $child->hasChildNodes() ) { - self::_processRefs( $env, $refsData, $child ); + self::processRefs( $env, $refsData, $child ); } } } @@ -409,56 +435,116 @@ class References { * foo * bar * ``` - * @private + * + * @param ReferencesData $refsData + * @param DOMElement $node + * @param string $referencesId + * @param string|null $referencesGroup + * @param array $nestedRefsHTML */ - public static function _processRefsInReferences( $refsData, $node, $referencesId, $referencesGroup, $nestedRefsHTML ) { + private static function processRefsInReferences( + ReferencesData $refsData, DOMElement $node, string $referencesId, + ?string $referencesGroup, array $nestedRefsHTML + ): void { $child = $node->firstChild; while ( $child !== null ) { $nextChild = $child->nextSibling; - if ( DOMUtils::isElt( $child ) ) { + if ( $child instanceof DOMElement ) { if ( WTUtils::isSealedFragmentOfType( $child, 'ref' ) ) { - self::extractRefFromNode( $child, $refsData, $referencesId, $referencesGroup, $nestedRefsHTML ); + self::extractRefFromNode( + $child, + $refsData, + $referencesId, + $referencesGroup, + $nestedRefsHTML + ); } elseif ( $child->hasChildNodes() ) { - self::_processRefsInReferences( $refsData, $child, $referencesId, $referencesGroup, $nestedRefsHTML ); + self::processRefsInReferences( + $refsData, + $child, + $referencesId, + $referencesGroup, + $nestedRefsHTML + ); } } $child = $nextChild; } } -} -References::serialHandler = [ - 'handle' => /* async */function ( $node, $state, $wrapperUnmodified ) use ( &$DOMDataUtils ) { + /** @inheritDoc */ + public function toDOM( ParsoidExtensionAPI $extApi, string $txt, array $extArgs ): DOMDocument { + $doc = $extApi->parseTokenContentsToDOM( + $extArgs, + '', + $txt, + [ + 'wrapperTag' => 'div', + 'extTag' => 'references', + 'inTemplate' => $extApi->parseContext['inTemplate'] ?? null, + ] + ); + + $refsOpts = TokenUtils::kvToHash( $extArgs, true ) + [ + 'group' => null, + 'responsive' => null, + ]; + + $docBody = DOMCompat::getBody( $doc ); + $frag = self::createReferences( + $extApi->getEnv(), + $doc, + $docBody, + $refsOpts, + function ( $dp ) use ( $extApi, $docBody ) { + $dp->src = $extApi->getExtSource(); + // Setting redundant info on fragment. + // $docBody->firstChild info feels cumbersome to use downstream. + $wrapperNode = $docBody->firstChild; + DOMUtils::assertElt( $wrapperNode ); + if ( !empty( DOMDataUtils::getDataParsoid( $wrapperNode )->selfClose ) ) { + $dp->selfClose = true; + } + } + ); + DOMCompat::getBody( $doc )->appendChild( $frag ); + return $doc; + } + + /** @inheritDoc */ + public function fromHTML( + DOMElement $node, SerializerState $state, bool $wrapperUnmodified + ): string { $dataMw = DOMDataUtils::getDataMw( $node ); - if ( $dataMw->autoGenerated && $state->rtTestMode ) { + if ( !empty( $dataMw->autoGenerated ) && $state->rtTestMode ) { // Eliminate auto-inserted noise in rt-testing return ''; } else { - $startTagSrc = /* await */ $state->serializer->serializeExtensionStartTag( $node, $state ); - if ( !$dataMw->body ) { + $startTagSrc = $state->serializer->serializeExtensionStartTag( $node, $state ); + if ( empty( $dataMw->body ) ) { return $startTagSrc; // We self-closed this already. } else { // We self-closed this already. - if ( gettype( $dataMw->body->html ) === 'string' ) { - $src = /* await */ $state->serializer->serializeHTML( [ - 'env' => $state->env, - 'extName' => $dataMw->name - ], $dataMw->body->html - ); - return $startTagSrc + $src . 'name . '>'; - } else { - $state->env->log( 'error', - 'References body unavailable for: ' . $node->outerHTML - ); - return ''; // Drop it! - } + if ( is_string( $dataMw->body->html ) ) { + $src = $state->serializer->serializeHTML( + [ + 'env' => $state->getEnv(), + 'extName' => $dataMw->name, + ], + $dataMw->body->html + ); + return $startTagSrc . $src . 'name . '>'; + } else { + $state->getEnv()->log( 'error', + 'References body unavailable for: ' . DOMCompat::getOuterHTML( $node ) + ); + return ''; // Drop it! + } } } } - , // Drop it! - - // FIXME: LEAKY -- Should we expose newline constraints to extensions? - 'before' => function ( $node, $otherNode, $state ) use ( &$WTUtils ) { + /** @inheritDoc */ + public function before( DOMElement $node, DOMNode $otherNode, SerializerState $state ): ?array { // Serialize new references tags on a new line. if ( WTUtils::isNewElt( $node ) ) { return [ 'min' => 1, 'max' => 2 ]; @@ -466,6 +552,28 @@ References::serialHandler = [ return null; } } -]; -$module->exports = $References; + /** @inheritDoc */ + public function hasLintHandler(): bool { + return true; + } + + /** @inheritDoc */ + public function lintHandler( + ParsoidExtensionAPI $extApi, DOMElement $refs, callable $defaultHandler + ): ?DOMNode { + // Nothing to do + // + // FIXME: Not entirely true for scenarios where the tags + // are defined in the references section that is itself templated. + // + // {{1x|\nfoo\n}} + // + // In this example, the references tag has the right tplInfo and + // when the tag is processed in the body of the article where + // it is accessed, there is no relevant template or dsr info available. + // + // Ignoring for now. + return $refs->nextSibling; + } +} diff --git a/src/Parsoid/ReferencesData.php b/src/Parsoid/ReferencesData.php index 7e34f68ef..b8185a01c 100644 --- a/src/Parsoid/ReferencesData.php +++ b/src/Parsoid/ReferencesData.php @@ -1,31 +1,45 @@ parent->parent->parent->require( './extapi.js' )->versionCheck( '^0.10.0' ); -$temp0 = $ParsoidExtApi; -$ContentUtils = $temp0::ContentUtils; -$Sanitizer = $temp0::Sanitizer; +namespace Parsoid\Ext\Cite; -$RefGroup = require './RefGroup.js'; +use Parsoid\Config\Env; +use Parsoid\Utils\ContentUtils; +use Parsoid\Wt2Html\TT\Sanitizer; +use stdClass; -/** - * @class - */ class ReferencesData { - public function __construct( $env ) { + + /** + * @var Env + */ + private $env; + + /** + * @var int + */ + private $index; + + /** + * @var RefGroup[] + */ + private $refGroups; + + /** + * ReferencesData constructor. + * @param Env $env + */ + public function __construct( Env $env ) { $this->index = 0; $this->env = $env; - $this->refGroups = new Map(); + $this->refGroups = []; } - public $env; - public $index; - public $refGroups; - - public function makeValidIdAttr( $val ) { + /** + * @param string $val + * @return bool|string + */ + public function makeValidIdAttr( string $val ) { // 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. @@ -36,33 +50,52 @@ class ReferencesData { return Sanitizer::escapeIdForAttribute( $val ); } - public function getRefGroup( $groupName, $allocIfMissing ) { - $groupName = $groupName || ''; - if ( !$this->refGroups->has( $groupName ) && $allocIfMissing ) { - $this->refGroups->set( $groupName, new RefGroup( $groupName ) ); + /** + * @param string $groupName + * @param bool $allocIfMissing + * @return RefGroup|null + */ + public function getRefGroup( string $groupName = '', bool $allocIfMissing = false ): ?RefGroup { + if ( !isset( $this->refGroups[$groupName] ) && $allocIfMissing ) { + $this->refGroups[$groupName] = new RefGroup( $groupName ); } - return $this->refGroups->get( $groupName ); + return $this->refGroups[$groupName] ?? null; } - public function removeRefGroup( $groupName ) { - if ( $groupName !== null && $groupName !== null ) { + /** + * @param string|null $groupName + */ + public function removeRefGroup( ?string $groupName = null ): void { + if ( $groupName !== null ) { // '' is a valid group (the default group) - $this->refGroups->delete( $groupName ); + unset( $this->refGroups[$groupName] ); } } - public function add( $env, $groupName, $refName, $about, $skipLinkback ) { + /** + * @param Env $env + * @param string $groupName + * @param string $refName + * @param string $about + * @param bool $skipLinkback + * @return stdClass + */ + public function add( + Env $env, string $groupName, string $refName, string $about, bool $skipLinkback + ): stdClass { $group = $this->getRefGroup( $groupName, true ); $refName = $this->makeValidIdAttr( $refName ); - $ref = null; - if ( $refName && $group->indexByName->has( $refName ) ) { - $ref = $group->indexByName->get( $refName ); + if ( $refName && isset( $group->indexByName[$refName] ) ) { + $ref = $group->indexByName[$refName]; if ( $ref->content && !$ref->hasMultiples ) { $ref->hasMultiples = true; // Use the non-pp version here since we've already stored attribs // before putting them in the map. - $ref->cachedHtml = ContentUtils::toXML( $env->fragmentMap->get( $ref->content )[ 0 ], [ 'innerXML' => true ] ); + $ref->cachedHtml = ContentUtils::toXML( + $env->getFragment( $ref->content )[0], + [ 'innerXML' => true ] + ); } } else { // The ids produced Cite.php have some particulars: @@ -71,14 +104,14 @@ class ReferencesData { // Notes (references) whose ref doesn't have a name are 'cite_note-' + index // Notes whose ref has a name are 'cite_note-' + name + '-' + index $n = $this->index; - $refKey = ( 1 + $n ) . ''; - $refIdBase = 'cite_ref-' . ( ( $refName ) ? $refName . '_' . $refKey : $refKey ); - $noteId = 'cite_note-' . ( ( $refName ) ? $refName . '-' . $refKey : $refKey ); + $refKey = strval( 1 + $n ); + $refIdBase = 'cite_ref-' . ( $refName ? $refName . '_' . $refKey : $refKey ); + $noteId = 'cite_note-' . ( $refName ? $refName . '-' . $refKey : $refKey ); // bump index $this->index += 1; - $ref = [ + $ref = (object)[ 'about' => $about, 'content' => null, 'dir' => '', @@ -86,7 +119,7 @@ class ReferencesData { 'groupIndex' => count( $group->refs ) + 1, 'index' => $n, 'key' => $refIdBase, - 'id' => ( ( $refName ) ? $refIdBase . '-0' : $refIdBase ), + 'id' => $refName ? $refIdBase . '-0' : $refIdBase, 'linkbacks' => [], 'name' => $refName, 'target' => $noteId, @@ -96,7 +129,7 @@ class ReferencesData { ]; $group->refs[] = $ref; if ( $refName ) { - $group->indexByName->set( $refName, $ref ); + $group->indexByName[$refName] = $ref; } } @@ -105,6 +138,18 @@ class ReferencesData { } return $ref; } -} -$module->exports = $ReferencesData; + /** + * @return Env + */ + public function getEnv(): Env { + return $this->env; + } + + /** + * @return RefGroup[] + */ + public function getRefGroups(): array { + return $this->refGroups; + } +}