Файловый менеджер - Редактировать - /var/www/html/mediawiki-1.43.1/vendor/wikimedia/parsoid/src/Utils/DOMDataUtils.php
Ðазад
<?php declare( strict_types = 1 ); namespace Wikimedia\Parsoid\Utils; use Composer\Semver\Semver; use stdClass; use Wikimedia\Assert\Assert; use Wikimedia\Assert\UnreachableException; use Wikimedia\JsonCodec\Hint; use Wikimedia\JsonCodec\JsonCodec; use Wikimedia\Parsoid\Core\PageBundle; use Wikimedia\Parsoid\DOM\Document; use Wikimedia\Parsoid\DOM\Element; use Wikimedia\Parsoid\DOM\Node; use Wikimedia\Parsoid\NodeData\DataBag; use Wikimedia\Parsoid\NodeData\DataMw; use Wikimedia\Parsoid\NodeData\DataMwI18n; use Wikimedia\Parsoid\NodeData\DataParsoid; use Wikimedia\Parsoid\NodeData\I18nInfo; use Wikimedia\Parsoid\NodeData\NodeData; use Wikimedia\Parsoid\NodeData\TempData; /** * 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; } /** * Return the JsonCodec used for rich attributes in a Document. * @param Document $doc * @return JsonCodec */ public static function getCodec( Document $doc ): JsonCodec { // 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->codec; } public static function prepareDoc( Document $doc ): void { // `bag` is a deliberate dynamic property; see DOMDataUtils::getBag() // @phan-suppress-next-line PhanUndeclaredProperty dynamic property $doc->bag = new DataBag(); // `codec` is a deliberate dynamic property; see DOMDataUtils::getCodec() // @phan-suppress-next-line PhanUndeclaredProperty dynamic property $doc->codec = new JsonCodec(); // 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; // @phan-suppress-next-line PhanUndeclaredProperty dynamic property $childDoc->codec = $topLevelDoc->codec; } /** * 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; } $nodeId = DOMCompat::getAttribute( $node, self::DATA_OBJECT_ATTR_NAME ); if ( $nodeId !== null ) { $dataObject = self::getBag( $node->ownerDocument )->getObject( (int)$nodeId ); } else { $dataObject = null; // Make phan happy } Assert::invariant( isset( $dataObject ), 'Bogus nodeId given!' ); if ( isset( $dataObject->storedId ) ) { throw new UnreachableException( '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: ' . $nodeId . '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 { $nodeId = self::stashObjectInDoc( $node->ownerDocument, $data ); $node->setAttribute( self::DATA_OBJECT_ATTR_NAME, (string)$nodeId ); } /** * Get data parsoid info from a node. * * @param Element $node node * @return DataParsoid */ public static function getDataParsoid( Element $node ): DataParsoid { $data = self::getNodeData( $node ); $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; } /** * Returns the i18n information of a node. This is in private access because it shouldn't * typically be used directly; instead getDataNodeI18n and getDataAttrI18n should be used. * @param Element $node * @return DataMwI18n|null */ private static function getDataMwI18n( Element $node ): ?DataMwI18n { $data = self::getNodeData( $node ); // We won't set a default value for this property return $data->i18n ?? null; } /** * Sets the i18n information of a node. This is in private access because it shouldn't * typically be used directly; instead setDataNodeI18n and setDataAttrI18n should be used. */ private static function setDataMwI18n( Element $node, DataMwI18n $i18n ) { $data = self::getNodeData( $node ); $data->i18n = $i18n; } /** * Retrieves internationalization (i18n) information of a node (typically for localization) * @param Element $node * @return ?I18nInfo */ public static function getDataNodeI18n( Element $node ): ?I18nInfo { $data = self::getNodeData( $node ); // We won't set a default value for this property if ( !isset( $data->i18n ) ) { return null; } return $data->i18n->getSpanInfo(); } /** * Sets internationalization (i18n) information of a node, used for later localization */ public static function setDataNodeI18n( Element $node, I18nInfo $i18n ) { $data = self::getNodeData( $node ); $data->i18n ??= new DataMwI18n(); $data->i18n->setSpanInfo( $i18n ); } /** * Retrieves internationalization (i18n) information of an attribute value (typically for * localization) * @param Element $node * @param string $name * @return ?I18nInfo */ public static function getDataAttrI18n( Element $node, string $name ): ?I18nInfo { $data = self::getNodeData( $node ); // We won't set a default value for this property if ( !isset( $data->i18n ) ) { return null; } return $data->i18n->getAttributeInfo( $name ); } /** * Sets internationalization (i18n) information of a attribute value, used for later * localization * @param Element $node * @param string $name * @param I18nInfo $i18n */ public static function setDataAttrI18n( Element $node, string $name, I18nInfo $i18n ) { $data = self::getNodeData( $node ); $data->i18n ??= new DataMwI18n(); $data->i18n->setAttributeInfo( $name, $i18n ); } /** * @param Element $node * @return array */ public static function getDataAttrI18nNames( Element $node ): array { $data = self::getNodeData( $node ); // We won't set a default value for this property if ( !isset( $data->i18n ) ) { return []; } return $data->i18n->getAttributeNames(); } /** * 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 DataMw */ public static function getDataMw( Element $node ): DataMw { $data = self::getNodeData( $node ); $data->mw ??= new DataMw; return $data->mw; } /** * Set data meta wiki info from a node. * * @param Element $node node * @param ?DataMw $dmw data-mw */ public static function setDataMw( Element $node, ?DataMw $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 ) !== []; } /** * Check if there is i18n info on a node (for the node or its attributes) * @param Element $node * @return bool */ public static function validDataMwI18n( Element $node ): bool { return self::getDataMwI18n( $node ) !== null; } /** * 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 ) { $attVal = DOMCompat::getAttribute( $node, $name ); if ( $attVal === null ) { return $defaultVal; } $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 ); $dp->a ??= []; $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 ); $dp->a ??= []; $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 PageBundle */ public static function getPageBundle( Document $doc ): PageBundle { 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. * * TODO: Note that $data is effective a partial PageBundle containing * only the 'parsoid' and 'mw' properties. * * @param Element $node node * @param stdClass $data data * @param array $idIndex Index of used id attributes in the DOM */ public static function storeInPageBundle( Element $node, stdClass $data, array $idIndex ): void { $hints = self::getCodecHints(); $uid = DOMCompat::getAttribute( $node, 'id' ); $document = $node->ownerDocument; $pb = self::getPageBundle( $document ); $codec = self::getCodec( $document ); $docDp = &$pb->parsoid; $origId = $uid; if ( $uid !== null && array_key_exists( $uid, $docDp['ids'] ) ) { $uid = null; } if ( $uid === '' ) { $uid = null; } if ( $uid === null ) { 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 ); } // Convert from DataParsoid/DataMw objects to associative array $docDp['ids'][$uid] = $codec->toJsonArray( $data->parsoid, $hints['data-parsoid'] ); if ( isset( $data->mw ) ) { $pb->mw['ids'][$uid] = $codec->toJsonArray( $data->mw, $hints['data-mw'] ); } } /** * Helper function to create static Hint objects for JsonCodec. * @return array<Hint> */ public static function getCodecHints(): array { static $hints = null; if ( $hints === null ) { $hints = [ 'data-parsoid' => Hint::build( DataParsoid::class, Hint::ALLOW_OBJECT ), 'data-mw' => Hint::build( DataMw::class, Hint::ALLOW_OBJECT ), ]; } return $hints; } /** * @param Document $doc doc * @param PageBundle $pb object */ public static function injectPageBundle( Document $doc, PageBundle $pb ): void { $script = DOMUtils::appendToHead( $doc, 'script', [ 'id' => 'mw-pagebundle', 'type' => 'application/x-mw-pagebundle', ] ); $script->appendChild( $doc->createTextNode( $pb->encodeForHeadElement() ) ); } /** * @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 ); // we actually want arrays in the page bundle rather than stdClasses; but we still // want to access the object properties $pb = (object)PHPUtils::jsonDecode( $dpScriptElt->textContent ); } 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 ); } /** * 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 ); $codec = self::getCodec( $node->ownerDocument ); $dataParsoidAttr = DOMCompat::getAttribute( $node, 'data-parsoid' ); $dp = $codec->newFromJsonString( $dataParsoidAttr ?? '{}', self::getCodecHints()['data-parsoid'] ); if ( !empty( $options['markNew'] ) ) { $dp->setTempFlag( TempData::IS_NEW, $dataParsoidAttr === null ); } self::setDataParsoid( $node, $dp ); $node->removeAttribute( 'data-parsoid' ); $dataMwAttr = DOMCompat::getAttribute( $node, 'data-mw' ); $dmw = $dataMwAttr === null ? null : $codec->newFromJsonString( $dataMwAttr, self::getCodecHints()['data-mw'] ); 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' ); $dataI18n = DOMCompat::getAttribute( $node, 'data-mw-i18n' ); if ( $dataI18n !== null ) { $i18n = DataMwI18n::fromJson( PHPUtils::jsonDecode( $dataI18n, true ) ); self::setDataMwI18n( $node, $i18n ); $node->removeAttribute( 'data-mw-i18n' ); } } /** * 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 ) { $id = DOMCompat::getAttribute( $n, 'id' ); if ( $id !== null ) { $index[$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 { $hints = self::getCodecHints(); $options ??= []; if ( !( $node instanceof Element ) ) { return; } Assert::invariant( empty( $options['discardDataParsoid'] ) || empty( $options['keepTmp'] ), 'Conflicting options: discardDataParsoid and keepTmp are both enabled.' ); $codec = self::getCodec( $node->ownerDocument ); $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 ??= new stdClass; $data->parsoid = $dp; } else { $node->setAttribute( 'data-parsoid', PHPUtils::jsonEncode( $codec->toJsonArray( $dp, $hints['data-parsoid'] ) ) ); } } // 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 ??= new stdClass; $data->mw = self::getDataMw( $node ); } else { $node->setAttribute( 'data-mw', PHPUtils::jsonEncode( $codec->toJsonArray( self::getDataMw( $node ), $hints['data-mw'] ) ) ); } } if ( self::validDataMwI18n( $node ) ) { self::setJSONAttribute( $node, 'data-mw-i18n', self::getDataMwI18n( $node ) ); } // Store pagebundle if ( $data !== null ) { self::storeInPageBundle( $node, $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 ); $id = DOMCompat::getAttribute( $node, self::DATA_OBJECT_ATTR_NAME ); $nd->storedId = $id !== null ? intval( $id ) : null; $node->removeAttribute( self::DATA_OBJECT_ATTR_NAME ); } /** * Clones a node and its data bag * @param Element $elt * @param bool $deep * @return Element */ public static function cloneNode( Element $elt, bool $deep ): Element { $clone = $elt->cloneNode( $deep ); '@phan-var Element $clone'; // @var Element $clone // We do not need to worry about $deep because a shallow clone does not have child nodes, // so it's always cloning data on the cloned tree (which may be empty). self::fixClonedData( $clone ); return $clone; } /** * Recursively fixes cloned data from $elt: to avoid conflicts of element IDs, we clone the * data and set it in the node with a new element ID (which setNodeData does). * @param Element $elt */ private static function fixClonedData( Element $elt ) { if ( $elt->hasAttribute( self::DATA_OBJECT_ATTR_NAME ) ) { self::setNodeData( $elt, self::getNodeData( $elt )->cloneNodeData() ); } foreach ( $elt->childNodes as $child ) { if ( $child instanceof Element ) { self::fixClonedData( $child ); } } } }
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка