Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4138649
DOMDataUtils.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
23 KB
Referenced Files
None
Subscribers
None
DOMDataUtils.php
View Options
<?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
);
}
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Wed, Aug 19, 14:44 (3 w, 5 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
72/3c/32b0baba8bec42ef695011c5a28c
Default Alt Text
DOMDataUtils.php (23 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment