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 <gallery> test which is
  presumably related to the unported <gallery> 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
This commit is contained in:
Pavel Astakhov 2019-05-26 04:24:47 +06:00 committed by Subramanya Sastry
parent 4e334fa727
commit 005176a355
6 changed files with 601 additions and 390 deletions

View file

@ -1,48 +1,37 @@
<?php
// phpcs:ignoreFile
// phpcs:disable Generic.Files.LineLength.TooLong
/* REMOVE THIS COMMENT AFTER PORTING */
/**
* This module implements `<ref>` and `<references>` 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 `<ref>` and `<references>`.
*/
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;
}

View file

@ -1,26 +1,32 @@
<?php // lint >= 99.9
// phpcs:ignoreFile
// phpcs:disable Generic.Files.LineLength.TooLong
/* REMOVE THIS COMMENT AFTER PORTING */
namespace Parsoid;
<?php
declare( strict_types = 1 );
$ParsoidExtApi = $module->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 <sup> 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 <ref> is inside another
// extension, most commonly inside a <references>.
// 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 <ref> is inside another
// extension, most commonly inside a <references>.
// 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 . '</' . $dataMw->name . '>';
return $startTagSrc . $src . '</' . $dataMw->name . '>';
}
];
$module->exports = $Ref;
/** @inheritDoc */
public function before( DOMElement $node, DOMNode $otherNode, SerializerState $state ): ?array {
return null;
}
}

View file

@ -1,69 +1,113 @@
<?php
// phpcs:ignoreFile
// phpcs:disable Generic.Files.LineLength.TooLong
/* REMOVE THIS COMMENT AFTER PORTING */
namespace Parsoid;
declare( strict_types = 1 );
$ParsoidExtApi = $module->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 `<references>` 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;

View file

@ -1,25 +1,29 @@
<?php
// phpcs:ignoreFile
// phpcs:disable Generic.Files.LineLength.TooLong
/* REMOVE THIS COMMENT AFTER PORTING */
namespace Parsoid;
declare( strict_types = 1 );
use Parsoid\References as References;
use Parsoid\ReferencesData as ReferencesData;
namespace Parsoid\Ext\Cite;
use DOMElement;
use Parsoid\Config\Env;
/**
* wt -> 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;

View file

@ -1,23 +1,31 @@
<?php // lint >= 99.9
// phpcs:ignoreFile
// phpcs:disable Generic.Files.LineLength.TooLong
/* REMOVE THIS COMMENT AFTER PORTING */
namespace Parsoid;
<?php
declare( strict_types = 1 );
$ParsoidExtApi = $module->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 <sup> 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 <references> elt as well as the nested <ref>
// 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 <ref>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 `<ref>`s left behind after the DOM is fully processed.
* We process them as if there was an implicit `<references />` 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 <ol> 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 <ref> tags
// are defined in the references section that is itself templated.
//
// {{1x|<references>\n<ref name='x'><b>foo</ref>\n</references>}}
//
// In this example, the references tag has the right tplInfo and
// when the <ref> 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 {
* <references> <ref>foo</ref> </references>
* <references> <ref>bar</ref> </references>
* ```
* @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 <references /> 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 . '</' . $dataMw->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 . '</' . $dataMw->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 <ref> tags
// are defined in the references section that is itself templated.
//
// {{1x|<references>\n<ref name='x'><b>foo</ref>\n</references>}}
//
// In this example, the references tag has the right tplInfo and
// when the <ref> 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;
}
}

View file

@ -1,31 +1,45 @@
<?php
// phpcs:ignoreFile
// phpcs:disable Generic.Files.LineLength.TooLong
/* REMOVE THIS COMMENT AFTER PORTING */
namespace Parsoid;
declare( strict_types = 1 );
$ParsoidExtApi = $module->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;
}
}