Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
3.45% |
1 / 29 |
CRAP | |
9.39% |
20 / 213 |
DOMDataUtils | |
0.00% |
0 / 1 |
|
3.45% |
1 / 29 |
6952.08 | |
9.39% |
20 / 213 |
getBag | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
prepareDoc | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 4 |
|||
prepareChildDoc | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
stashObjectInDoc | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
noAttrs | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 5 |
|||
getNodeData | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 14 |
|||
setNodeData | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
getDataParsoid | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
setDataParsoid | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
getDataParsoidDiff | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 2 |
|||
setDataParsoidDiff | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
getDataMw | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
setDataMw | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
validDataMw | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getJSONAttribute | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 9 |
|||
setJSONAttribute | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
setShadowInfo | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 7 |
|||
setShadowInfoIfModified | |
0.00% |
0 / 1 |
72 | |
0.00% |
0 / 11 |
|||
addNormalizedAttribute | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 5 |
|||
getPageBundle | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
storeInPageBundle | |
0.00% |
0 / 1 |
6.06 | |
88.24% |
15 / 17 |
|||
injectPageBundle | |
100.00% |
1 / 1 |
1 | |
100.00% |
5 / 5 |
|||
extractPageBundle | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 6 |
|||
visitAndLoadDataAttribs | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
massageLoadedDataParsoid | |
0.00% |
0 / 1 |
380 | |
0.00% |
0 / 42 |
|||
loadDataAttribs | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 15 |
|||
usedIdIndex | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 7 |
|||
visitAndStoreDataAttribs | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
storeDataAttribs | |
0.00% |
0 / 1 |
210 | |
0.00% |
0 / 28 |
<?php | |
declare( strict_types = 1 ); | |
namespace Wikimedia\Parsoid\Utils; | |
use Composer\Semver\Semver; | |
use stdClass; | |
use Wikimedia\Assert\Assert; | |
use Wikimedia\Parsoid\Config\Env; | |
use Wikimedia\Parsoid\Core\DomSourceRange; | |
use Wikimedia\Parsoid\DOM\Document; | |
use Wikimedia\Parsoid\DOM\Element; | |
use Wikimedia\Parsoid\DOM\Node; | |
use Wikimedia\Parsoid\NodeData\DataBag; | |
use Wikimedia\Parsoid\NodeData\DataParsoid; | |
use Wikimedia\Parsoid\NodeData\NodeData; | |
use Wikimedia\Parsoid\NodeData\ParamInfo; | |
use Wikimedia\Parsoid\NodeData\TempData; | |
use Wikimedia\Parsoid\Tokens\SourceRange; | |
/** | |
* These helpers pertain to HTML and data attributes of a node. | |
*/ | |
class DOMDataUtils { | |
public const DATA_OBJECT_ATTR_NAME = 'data-object-id'; | |
/** | |
* Return the dynamic "bag" property of a Document. | |
* @param Document $doc | |
* @return DataBag | |
*/ | |
public static function getBag( Document $doc ): DataBag { | |
// This is a dynamic property; it is not declared. | |
// All references go through here so we can suppress phan's complaint. | |
// @phan-suppress-next-line PhanUndeclaredProperty | |
return $doc->bag; | |
} | |
/** | |
* @param Document $doc | |
*/ | |
public static function prepareDoc( Document $doc ) { | |
// `bag` is a deliberate dynamic property; see DOMDataUtils::getBag() | |
// @phan-suppress-next-line PhanUndeclaredProperty dynamic property | |
$doc->bag = new DataBag(); | |
// Cache the head and body. | |
DOMCompat::getHead( $doc ); | |
DOMCompat::getBody( $doc ); | |
} | |
/** | |
* @param Document $topLevelDoc | |
* @param Document $childDoc | |
*/ | |
public static function prepareChildDoc( Document $topLevelDoc, Document $childDoc ) { | |
// @phan-suppress-next-line PhanUndeclaredProperty dynamic property | |
Assert::invariant( $topLevelDoc->bag instanceof DataBag, 'doc bag not set' ); | |
// @phan-suppress-next-line PhanUndeclaredProperty dynamic property | |
$childDoc->bag = $topLevelDoc->bag; | |
} | |
/** | |
* Stash $obj in $doc and return an id for later retrieval | |
* @param Document $doc | |
* @param NodeData $obj | |
* @return int | |
*/ | |
public static function stashObjectInDoc( Document $doc, NodeData $obj ): int { | |
return self::getBag( $doc )->stashObject( $obj ); | |
} | |
/** | |
* Does this node have any attributes? | |
* @param Element $node | |
* @return bool | |
*/ | |
public static function noAttrs( Element $node ): bool { | |
// The 'xmlns' attribute is "invisible" T235295 | |
if ( $node->hasAttribute( 'xmlns' ) ) { | |
return false; | |
} | |
$numAttrs = count( $node->attributes ); | |
return $numAttrs === 0 || | |
( $numAttrs === 1 && $node->hasAttribute( self::DATA_OBJECT_ATTR_NAME ) ); | |
} | |
/** | |
* Get data object from a node. | |
* | |
* @param Element $node node | |
* @return NodeData | |
*/ | |
public static function getNodeData( Element $node ): NodeData { | |
if ( !$node->hasAttribute( self::DATA_OBJECT_ATTR_NAME ) ) { | |
// Initialized on first request | |
$dataObject = new NodeData; | |
self::setNodeData( $node, $dataObject ); | |
return $dataObject; | |
} | |
$docId = $node->getAttribute( self::DATA_OBJECT_ATTR_NAME ); | |
if ( $docId !== '' ) { | |
$dataObject = self::getBag( $node->ownerDocument )->getObject( (int)$docId ); | |
} else { | |
$dataObject = null; // Make phan happy | |
} | |
Assert::invariant( isset( $dataObject ), 'Bogus docId given!' ); | |
if ( isset( $dataObject->storedId ) ) { | |
PHPUtils::unreachable( | |
'Trying to fetch node data without loading!' . | |
// If this node's data-object id is different from storedId, | |
// it will indicate that the data-parsoid object was shared | |
// between nodes without getting cloned. Useful for debugging. | |
'Node id: ' . $node->getAttribute( self::DATA_OBJECT_ATTR_NAME ) . | |
'Stored data: ' . PHPUtils::jsonEncode( $dataObject ) | |
); | |
} | |
return $dataObject; | |
} | |
/** | |
* Set node data. | |
* | |
* @param Element $node node | |
* @param NodeData $data data | |
*/ | |
public static function setNodeData( Element $node, NodeData $data ): void { | |
$docId = self::stashObjectInDoc( $node->ownerDocument, $data ); | |
$node->setAttribute( self::DATA_OBJECT_ATTR_NAME, (string)$docId ); | |
} | |
/** | |
* Get data parsoid info from a node. | |
* | |
* @param Element $node node | |
* @return DataParsoid | |
*/ | |
public static function getDataParsoid( Element $node ): DataParsoid { | |
$data = self::getNodeData( $node ); | |
if ( !isset( $data->parsoid ) ) { | |
$data->parsoid = new DataParsoid; | |
} | |
return $data->parsoid; | |
} | |
/** | |
* Set data parsoid info on a node. | |
* | |
* @param Element $node node | |
* @param DataParsoid $dp data-parsoid | |
*/ | |
public static function setDataParsoid( Element $node, DataParsoid $dp ): void { | |
$data = self::getNodeData( $node ); | |
$data->parsoid = $dp; | |
} | |
/** | |
* Get data diff info from a node. | |
* | |
* @param Element $node node | |
* @return ?stdClass | |
*/ | |
public static function getDataParsoidDiff( Element $node ): ?stdClass { | |
$data = self::getNodeData( $node ); | |
// We won't set a default value for this property | |
return $data->parsoid_diff ?? null; | |
} | |
/** | |
* Set data diff info on a node. | |
* | |
* @param Element $node node | |
* @param ?stdClass $diffObj data-parsoid-diff object | |
*/ | |
public static function setDataParsoidDiff( Element $node, ?stdClass $diffObj ): void { | |
$data = self::getNodeData( $node ); | |
$data->parsoid_diff = $diffObj; | |
} | |
/** | |
* Get data meta wiki info from a node. | |
* | |
* @param Element $node node | |
* @return stdClass | |
*/ | |
public static function getDataMw( Element $node ): stdClass { | |
$data = self::getNodeData( $node ); | |
if ( !isset( $data->mw ) ) { | |
$data->mw = new stdClass; | |
} | |
return $data->mw; | |
} | |
/** | |
* Set data meta wiki info from a node. | |
* | |
* @param Element $node node | |
* @param ?stdClass $dmw data-mw | |
*/ | |
public static function setDataMw( Element $node, ?stdClass $dmw ): void { | |
$data = self::getNodeData( $node ); | |
$data->mw = $dmw; | |
} | |
/** | |
* Check if there is meta wiki info in a node. | |
* | |
* @param Element $node node | |
* @return bool | |
*/ | |
public static function validDataMw( Element $node ): bool { | |
return (array)self::getDataMw( $node ) !== []; | |
} | |
/** | |
* Get an object from a JSON-encoded XML attribute on a node. | |
* | |
* @param Element $node node | |
* @param string $name name | |
* @param mixed $defaultVal | |
* @return mixed | |
*/ | |
public static function getJSONAttribute( Element $node, string $name, $defaultVal ) { | |
if ( !$node->hasAttribute( $name ) ) { | |
return $defaultVal; | |
} | |
$attVal = $node->getAttribute( $name ); | |
$decoded = PHPUtils::jsonDecode( $attVal, false ); | |
if ( $decoded !== null ) { | |
return $decoded; | |
} else { | |
error_log( 'ERROR: Could not decode attribute-val ' . $attVal . | |
' for ' . $name . ' on node ' . DOMCompat::nodeName( $node ) ); | |
return $defaultVal; | |
} | |
} | |
/** | |
* Set a attribute on a node with a JSON-encoded object. | |
* | |
* @param Element $node node | |
* @param string $name Name of the attribute. | |
* @param mixed $obj value of the attribute to | |
*/ | |
public static function setJSONAttribute( Element $node, string $name, $obj ): void { | |
$val = $obj === [] ? '{}' : PHPUtils::jsonEncode( $obj ); | |
$node->setAttribute( $name, $val ); | |
} | |
/** | |
* Set shadow info on a node; similar to the method on tokens. | |
* Records a key = value pair in data-parsoid['a'] property. | |
* | |
* This is effectively a call of 'setShadowInfoIfModified' except | |
* there is no original value, so by definition, $val is modified. | |
* | |
* @param Element $node node | |
* @param string $name Name of the attribute. | |
* @param mixed $val val | |
*/ | |
public static function setShadowInfo( Element $node, string $name, $val ): void { | |
$dp = self::getDataParsoid( $node ); | |
if ( !isset( $dp->a ) ) { | |
$dp->a = []; | |
} | |
if ( !isset( $dp->sa ) ) { | |
$dp->sa = []; | |
} | |
$dp->a[$name] = $val; | |
} | |
/** | |
* Set shadow info on a node; similar to the method on tokens. | |
* | |
* If the new value ($val) for the key ($name) is different from the | |
* original value ($origVal): | |
* - the new value is recorded in data-parsoid->a and | |
* - the original value is recorded in data-parsoid->sa | |
* | |
* @param Element $node node | |
* @param string $name Name of the attribute. | |
* @param mixed $val val | |
* @param mixed $origVal original value (null is a valid value) | |
* @param bool $skipOrig | |
*/ | |
public static function setShadowInfoIfModified( | |
Element $node, string $name, $val, $origVal, bool $skipOrig = false | |
): void { | |
if ( !$skipOrig && ( $val === $origVal || $origVal === null ) ) { | |
return; | |
} | |
$dp = self::getDataParsoid( $node ); | |
if ( !isset( $dp->a ) ) { | |
$dp->a = []; | |
} | |
if ( !isset( $dp->sa ) ) { | |
$dp->sa = []; | |
} | |
// FIXME: This is a hack to not overwrite already shadowed info. | |
// We should either fix the call site that depends on this | |
// behaviour to do an explicit check, or double down on this | |
// by porting it to the token method as well. | |
if ( !$skipOrig && !array_key_exists( $name, $dp->a ) ) { | |
$dp->sa[$name] = $origVal; | |
} | |
$dp->a[$name] = $val; | |
} | |
/** | |
* Set an attribute and shadow info to a node. | |
* Similar to the method on tokens | |
* | |
* @param Element $node node | |
* @param string $name Name of the attribute. | |
* @param mixed $val value | |
* @param mixed $origVal original value | |
* @param bool $skipOrig | |
*/ | |
public static function addNormalizedAttribute( | |
Element $node, string $name, $val, $origVal, bool $skipOrig = false | |
): void { | |
if ( $name === 'id' ) { | |
DOMCompat::setIdAttribute( $node, $val ); | |
} else { | |
$node->setAttribute( $name, $val ); | |
} | |
self::setShadowInfoIfModified( $node, $name, $val, $origVal, $skipOrig ); | |
} | |
/** | |
* Get this document's pagebundle object | |
* @param Document $doc | |
* @return stdClass | |
*/ | |
public static function getPageBundle( Document $doc ): stdClass { | |
return self::getBag( $doc )->getPageBundle(); | |
} | |
/** | |
* Removes the `data-*` attribute from a node, and migrates the data to the | |
* document's JSON store. Generates a unique id with the following format: | |
* ``` | |
* mw<base64-encoded counter> | |
* ``` | |
* but attempts to keep user defined ids. | |
* | |
* @param Element $node node | |
* @param Env $env environment | |
* @param stdClass $data data | |
* @param array $idIndex Index of used id attributes in the DOM | |
*/ | |
public static function storeInPageBundle( | |
Element $node, Env $env, stdClass $data, array $idIndex | |
): void { | |
$uid = $node->getAttribute( 'id' ) ?? ''; | |
$document = $node->ownerDocument; | |
$pb = self::getPageBundle( $document ); | |
$docDp = $pb->parsoid; | |
$origId = $uid ?: null; | |
if ( array_key_exists( $uid, $docDp->ids ) ) { | |
$uid = null; | |
// FIXME: Protect mw ids while tokenizing to avoid false positives. | |
$env->log( 'info', 'Wikitext for this page has duplicate ids: ' . $origId ); | |
} | |
if ( !$uid ) { | |
do { | |
$docDp->counter += 1; | |
// PORT-FIXME: NOTE that we aren't updating the idIndex here because | |
// we are generating unique ids that will not conflict. In any case, | |
// the idIndex is a workaround for the PHP DOM's issues and we might | |
// switch out of this in the future anyway. | |
$uid = 'mw' . PHPUtils::counterToBase64( $docDp->counter ); | |
} while ( isset( $idIndex[$uid] ) ); | |
self::addNormalizedAttribute( $node, 'id', $uid, $origId ); | |
} | |
$docDp->ids[$uid] = $data->parsoid; | |
if ( isset( $data->mw ) ) { | |
$pb->mw->ids[$uid] = $data->mw; | |
} | |
} | |
/** | |
* @param Document $doc doc | |
* @param stdClass $obj object | |
*/ | |
public static function injectPageBundle( Document $doc, stdClass $obj ): void { | |
$pb = PHPUtils::jsonEncode( $obj ); | |
$script = DOMUtils::appendToHead( $doc, 'script', [ | |
'id' => 'mw-pagebundle', | |
'type' => 'application/x-mw-pagebundle', | |
] ); | |
$script->appendChild( $doc->createTextNode( $pb ) ); | |
} | |
/** | |
* @param Document $doc doc | |
* @return stdClass|null | |
*/ | |
public static function extractPageBundle( Document $doc ): ?stdClass { | |
$pb = null; | |
$dpScriptElt = DOMCompat::getElementById( $doc, 'mw-pagebundle' ); | |
if ( $dpScriptElt ) { | |
$dpScriptElt->parentNode->removeChild( $dpScriptElt ); | |
$pb = PHPUtils::jsonDecode( $dpScriptElt->textContent, false ); | |
} | |
return $pb; | |
} | |
/** | |
* Walk DOM from node downward calling loadDataAttribs | |
* | |
* @param Node $node node | |
* @param array $options options | |
*/ | |
public static function visitAndLoadDataAttribs( Node $node, array $options = [] ): void { | |
DOMUtils::visitDOM( $node, [ self::class, 'loadDataAttribs' ], $options ); | |
} | |
/** | |
* Massage the data parsoid object loaded from a node attribute | |
* into expected shape. | |
* | |
* @param stdClass $stdDP | |
* @param array $options | |
* @param ?Element $node | |
* @return DataParsoid | |
*/ | |
public static function massageLoadedDataParsoid( | |
stdClass $stdDP, array $options = [], ?Element $node = null | |
): DataParsoid { | |
$dp = new DataParsoid; | |
foreach ( $stdDP as $key => $value ) { | |
switch ( $key ) { | |
case 'a': | |
case 'sa': | |
$dp->$key = (array)$value; | |
break; | |
case 'dsr': | |
case 'extTagOffsets': | |
if ( $value !== null ) { | |
$dp->$key = DomSourceRange::fromArray( $value ); | |
} | |
break; | |
case 'tsr': | |
case 'extLinkContentOffsets': | |
if ( $value !== null ) { | |
$dp->$key = SourceRange::fromArray( $value ); | |
} | |
break; | |
case 'optList': | |
$optList = []; | |
foreach ( $value as $item ) { | |
$optList[] = (array)$item; | |
} | |
$dp->optList = $optList; | |
break; | |
case 'pi': | |
$pi = []; | |
foreach ( $value as $item ) { | |
$pi2 = []; | |
foreach ( $item as $item2 ) { | |
$pi2[] = ParamInfo::newFromJson( $item2 ); | |
} | |
$pi[] = $pi2; | |
} | |
$dp->pi = $pi; | |
break; | |
case 'tmp': | |
$tmp = new TempData; | |
foreach ( $value as $key2 => $value2 ) { | |
$tmp->$key2 = $value2; | |
} | |
$dp->tmp = $tmp; | |
break; | |
default: | |
$dp->$key = $value; | |
} | |
} | |
if ( !empty( $options['markNew'] ) ) { | |
$dp->setTempFlag( TempData::IS_NEW, !$node->hasAttribute( 'data-parsoid' ) ); | |
} | |
return $dp; | |
} | |
/** | |
* These are intended be used on a document after post-processing, so that | |
* the underlying .dataobject is transparently applied (in the store case) | |
* and reloaded (in the load case), rather than worrying about keeping | |
* the attributes up-to-date throughout that phase. For the most part, | |
* using this.ppTo* should be sufficient and using these directly should be | |
* avoided. | |
* | |
* @param Node $node node | |
* @param array $options options | |
*/ | |
public static function loadDataAttribs( Node $node, array $options ): void { | |
if ( !( $node instanceof Element ) ) { | |
return; | |
} | |
// Reset the node data object's stored state, since we're reloading it | |
self::setNodeData( $node, new NodeData ); | |
$dp = self::massageLoadedDataParsoid( | |
self::getJSONAttribute( $node, 'data-parsoid', new stdClass ), | |
$options, $node ); | |
self::setDataParsoid( $node, $dp ); | |
$node->removeAttribute( 'data-parsoid' ); | |
$dmw = self::getJSONAttribute( $node, 'data-mw', null ); | |
self::setDataMw( $node, $dmw ); | |
$node->removeAttribute( 'data-mw' ); | |
$dpd = self::getJSONAttribute( $node, 'data-parsoid-diff', null ); | |
self::setDataParsoidDiff( $node, $dpd ); | |
$node->removeAttribute( 'data-parsoid-diff' ); | |
} | |
/** | |
* Builds an index of id attributes seen in the DOM | |
* @param Node $node | |
* @return array | |
*/ | |
public static function usedIdIndex( Node $node ): array { | |
$index = []; | |
DOMUtils::visitDOM( DOMCompat::getBody( $node->ownerDocument ), | |
static function ( Node $n, ?array $options = null ) use ( &$index ) { | |
if ( $n instanceof Element && $n->hasAttribute( 'id' ) ) { | |
$index[$n->getAttribute( 'id' )] = true; | |
} | |
}, | |
[] | |
); | |
return $index; | |
} | |
/** | |
* Walk DOM from node downward calling storeDataAttribs | |
* | |
* @param Node $node node | |
* @param array $options options | |
*/ | |
public static function visitAndStoreDataAttribs( Node $node, array $options = [] ): void { | |
// PORT-FIXME: storeDataAttribs calls storeInPageBundle which calls getElementById. | |
// PHP's `getElementById` implementation is broken, and we work around that by | |
// using Zest which uses XPath. So, getElementById call can be O(n) and calling it | |
// on on every element of the DOM via vistDOM here makes it O(n^2) instead of O(n). | |
// So, we work around that by building an index and avoiding getElementById entirely | |
// in storeInPageBundle. | |
if ( !empty( $options['storeInPageBundle'] ) ) { | |
$options['idIndex'] = self::usedIdIndex( $node ); | |
} | |
DOMUtils::visitDOM( $node, [ self::class, 'storeDataAttribs' ], $options ); | |
} | |
/** | |
* Copy data attributes from the bag to either JSON-encoded attributes on | |
* each node, or the page bundle, erasing the data-object-id attributes. | |
* | |
* @param Node $node node | |
* @param ?array $options options | |
* - discardDataParsoid: Discard DataParsoid objects instead of storing them | |
* - keepTmp: Preserve DataParsoid::$tmp | |
* - storeInPageBundle: If true, data will be stored in the page bundle | |
* instead of data-parsoid and data-mw. | |
* - env: The Env object required for various features | |
* - idIndex: Array of used ID attributes | |
*/ | |
public static function storeDataAttribs( Node $node, ?array $options = null ): void { | |
$options = $options ?? []; | |
if ( !( $node instanceof Element ) ) { | |
return; | |
} | |
Assert::invariant( empty( $options['discardDataParsoid'] ) || empty( $options['keepTmp'] ), | |
'Conflicting options: discardDataParsoid and keepTmp are both enabled.' ); | |
$dp = self::getDataParsoid( $node ); | |
$discardDataParsoid = !empty( $options['discardDataParsoid'] ); | |
if ( $dp->getTempFlag( TempData::IS_NEW ) ) { | |
// Only necessary to support the cite extension's getById, | |
// that's already been loaded once. | |
// | |
// This is basically a hack to ensure that DOMUtils.isNewElt | |
// continues to work since we effectively rely on the absence | |
// of data-parsoid to identify new elements. But, loadDataAttribs | |
// creates an empty {} if one doesn't exist. So, this hack | |
// ensures that a loadDataAttribs + storeDataAttribs pair don't | |
// dirty the node by introducing an empty data-parsoid attribute | |
// where one didn't exist before. | |
// | |
// Ideally, we'll find a better solution for this edge case later. | |
$discardDataParsoid = true; | |
} | |
$data = null; | |
if ( !$discardDataParsoid ) { | |
if ( empty( $options['keepTmp'] ) ) { | |
// @phan-suppress-next-line PhanTypeObjectUnsetDeclaredProperty | |
unset( $dp->tmp ); | |
} | |
if ( !empty( $options['storeInPageBundle'] ) ) { | |
$data = (object)[ 'parsoid' => $dp ]; | |
} else { | |
self::setJSONAttribute( $node, 'data-parsoid', $dp ); | |
} | |
} | |
// Strip invalid data-mw attributes | |
if ( self::validDataMw( $node ) ) { | |
if ( | |
!empty( $options['storeInPageBundle'] ) && isset( $options['env'] ) && | |
// The pagebundle didn't have data-mw before 999.x | |
Semver::satisfies( $options['env']->getOutputContentVersion(), '^999.0.0' ) | |
) { | |
$data = $data ?: new stdClass; | |
$data->mw = self::getDataMw( $node ); | |
} else { | |
self::setJSONAttribute( $node, 'data-mw', self::getDataMw( $node ) ); | |
} | |
} | |
// Store pagebundle | |
if ( $data !== null ) { | |
self::storeInPageBundle( $node, $options['env'], $data, $options['idIndex'] ); | |
} | |
// Indicate that this node's data has been stored so that if we try | |
// to access it after the fact we're aware and remove the attribute | |
// since it's no longer needed. | |
$nd = self::getNodeData( $node ); | |
$nd->storedId = $node->getAttribute( self::DATA_OBJECT_ATTR_NAME ); | |
$node->removeAttribute( self::DATA_OBJECT_ATTR_NAME ); | |
} | |
} |