123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Framework\Simplexml;
- /**
- * Extends SimpleXML to add valuable functionality to \SimpleXMLElement class
- *
- * @api
- * @since 100.0.2
- */
- class Element extends \SimpleXMLElement
- {
- /**
- * Would keep reference to parent node
- *
- * If \SimpleXMLElement would support complicated attributes
- *
- * @todo make use of spl_object_hash to keep global array of simplexml elements
- * to emulate complicated attributes
- * @var \Magento\Framework\Simplexml\Element
- */
- protected $_parent = null;
- /**
- * For future use
- *
- * @param \Magento\Framework\Simplexml\Element $element
- * @return void
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
- */
- public function setParent($element)
- {
- //$this->_parent = $element;
- }
- /**
- * Returns parent node for the element
- *
- * Currently using xpath
- *
- * @throws \InvalidArgumentException
- * @return \Magento\Framework\Simplexml\Element
- */
- public function getParent()
- {
- if (!empty($this->_parent)) {
- $parent = $this->_parent;
- } else {
- $arr = $this->xpath('..');
- if (!isset($arr[0])) {
- throw new \InvalidArgumentException('Root node could not be unset.');
- }
- $parent = $arr[0];
- }
- return $parent;
- }
- /**
- * Enter description here...
- *
- * @return boolean
- * @SuppressWarnings(PHPMD.UnusedLocalVariable)
- */
- public function hasChildren()
- {
- if (!$this->children()) {
- return false;
- }
- // simplexml bug: @attributes is in children() but invisible in foreach
- foreach ($this->children() as $k => $child) {
- return true;
- }
- return false;
- }
- /**
- * Returns attribute value by attribute name
- *
- * @param string $name
- * @return string|null
- */
- public function getAttribute($name)
- {
- $attrs = $this->attributes();
- return isset($attrs[$name]) ? (string)$attrs[$name] : null;
- }
- /**
- * Find a descendant of a node by path
- *
- * @todo Do we need to make it xpath look-a-like?
- * @todo Check if we still need all this and revert to plain XPath if this makes any sense
- * @todo param string $path Subset of xpath. Example: "child/grand[@attrName='attrValue']/subGrand"
- * @param string $path Example: "child/grand@attrName=attrValue/subGrand" (to make it faster without regex)
- * @return \Magento\Framework\Simplexml\Element
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- */
- public function descend($path)
- {
- # $node = $this->xpath($path);
- # return $node[0];
- if (is_array($path)) {
- $pathArr = $path;
- } else {
- // Simple exploding by / does not suffice,
- // as an attribute value may contain a / inside
- // Note that there are three matches for different kinds of attribute values specification
- if (strpos($path, "@") === false) {
- $pathArr = explode('/', $path);
- } else {
- $regex = "#([^@/\\\"]+(?:@[^=/]+=(?:\\\"[^\\\"]*\\\"|[^/]*))?)/?#";
- $pathArr = $pathMatches = [];
- if (preg_match_all($regex, $path, $pathMatches)) {
- $pathArr = $pathMatches[1];
- }
- }
- }
- $desc = $this;
- foreach ($pathArr as $nodeName) {
- if (strpos($nodeName, '@') !== false) {
- $a = explode('@', $nodeName);
- $b = explode('=', $a[1]);
- $nodeName = $a[0];
- $attributeName = $b[0];
- $attributeValue = $b[1];
- //
- // Does a very simplistic trimming of attribute value.
- //
- $attributeValue = trim($attributeValue, '"');
- $found = false;
- foreach ($desc->{$nodeName} as $subdesc) {
- if ((string)$subdesc[$attributeName] === $attributeValue) {
- $found = true;
- $desc = $subdesc;
- break;
- }
- }
- if (!$found) {
- $desc = false;
- }
- } else {
- $desc = $desc->{$nodeName};
- }
- if (!$desc) {
- return false;
- }
- }
- return $desc;
- }
- /**
- * Create attribute if it does not exists and set value to it
- *
- * @param string $name
- * @param string $value
- * @return void
- */
- public function setAttribute($name, $value)
- {
- if (!isset($this->attributes()[$name])) {
- $this->addAttribute($name, $value);
- }
- $this->attributes()[$name] = $value;
- }
- /**
- * Returns the node and children as an array
- *
- * @return array|string
- */
- public function asArray()
- {
- return $this->_asArray();
- }
- /**
- * asArray() analog, but without attributes
- * @return array|string
- */
- public function asCanonicalArray()
- {
- return $this->_asArray(true);
- }
- /**
- * Returns the node and children as an array
- *
- * @param bool $isCanonical - whether to ignore attributes
- * @return array|string
- */
- protected function _asArray($isCanonical = false)
- {
- $result = [];
- if (!$isCanonical) {
- // add attributes
- foreach ($this->attributes() as $attributeName => $attribute) {
- if ($attribute) {
- $result['@'][$attributeName] = (string)$attribute;
- }
- }
- }
- // add children values
- if ($this->hasChildren()) {
- foreach ($this->children() as $childName => $child) {
- $result[$childName] = $child->_asArray($isCanonical);
- }
- } else {
- if (empty($result)) {
- // return as string, if nothing was found
- $result = (string)$this;
- } else {
- // value has zero key element
- $result[0] = (string)$this;
- }
- }
- return $result;
- }
- /**
- * Makes nicely formatted XML from the node
- *
- * @param string $filename
- * @param int|boolean $level if false
- * @return string
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- */
- public function asNiceXml($filename = '', $level = 0)
- {
- if (is_numeric($level)) {
- $pad = str_pad('', $level * 3, ' ', STR_PAD_LEFT);
- $nl = "\n";
- } else {
- $pad = '';
- $nl = '';
- }
- $out = $pad . '<' . $this->getName();
- $attributes = $this->attributes();
- if ($attributes) {
- foreach ($attributes as $key => $value) {
- $out .= ' ' . $key . '="' . str_replace('"', '\"', (string)$value) . '"';
- }
- }
- $attributes = $this->attributes('xsi', true);
- if ($attributes) {
- foreach ($attributes as $key => $value) {
- $out .= ' xsi:' . $key . '="' . str_replace('"', '\"', (string)$value) . '"';
- }
- }
- if ($this->hasChildren()) {
- $out .= '>';
- $value = trim((string)$this);
- if (strlen($value)) {
- $out .= $this->xmlentities($value);
- }
- $out .= $nl;
- foreach ($this->children() as $child) {
- $out .= $child->asNiceXml('', is_numeric($level) ? $level + 1 : true);
- }
- $out .= $pad . '</' . $this->getName() . '>' . $nl;
- } else {
- $value = (string)$this;
- if (strlen($value)) {
- $out .= '>' . $this->xmlentities($value) . '</' . $this->getName() . '>' . $nl;
- } else {
- $out .= '/>' . $nl;
- }
- }
- if ((0 === $level || false === $level) && !empty($filename)) {
- file_put_contents($filename, $out);
- }
- return $out;
- }
- /**
- * Enter description here...
- *
- * @param int $level
- * @return string
- */
- public function innerXml($level = 0)
- {
- $out = '';
- foreach ($this->children() as $child) {
- $out .= $child->asNiceXml($level);
- }
- return $out;
- }
- /**
- * Converts meaningful xml characters to xml entities
- *
- * @param string $value
- * @return string
- */
- public function xmlentities($value = null)
- {
- if ($value === null) {
- $value = $this;
- }
- $value = (string)$value;
- $value = str_replace(
- ['&', '"', "'", '<', '>'],
- ['&', '"', ''', '<', '>'],
- $value
- );
- return $value;
- }
- /**
- * Appends $source to current node
- *
- * @param \Magento\Framework\Simplexml\Element $source
- * @return $this
- */
- public function appendChild($source)
- {
- if ($source->count()) {
- $child = $this->addChild($source->getName());
- } else {
- $child = $this->addChild($source->getName(), $this->xmlentities($source));
- }
- $child->setParent($this);
- $attributes = $source->attributes();
- foreach ($attributes as $key => $value) {
- $child->addAttribute($key, $this->xmlentities($value));
- }
- foreach ($source->children() as $sourceChild) {
- $child->appendChild($sourceChild);
- }
- return $this;
- }
- /**
- * Extends current node with xml from $source
- *
- * If $overwrite is false will merge only missing nodes
- * Otherwise will overwrite existing nodes
- *
- * @param \Magento\Framework\Simplexml\Element $source
- * @param boolean $overwrite
- * @return $this
- */
- public function extend($source, $overwrite = false)
- {
- if (!$source instanceof \Magento\Framework\Simplexml\Element) {
- return $this;
- }
- foreach ($source->children() as $child) {
- $this->extendChild($child, $overwrite);
- }
- return $this;
- }
- /**
- * Extends one node
- *
- * @param \Magento\Framework\Simplexml\Element $source
- * @param boolean $overwrite
- * @return $this
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.UnusedLocalVariable)
- */
- public function extendChild($source, $overwrite = false)
- {
- // this will be our new target node
- $targetChild = null;
- // name of the source node
- $sourceName = $source->getName();
- // here we have children of our source node
- $sourceChildren = $source->children();
- if (!$source->hasChildren()) {
- // handle string node
- if (isset($this->{$sourceName})) {
- // if target already has children return without regard
- if ($this->{$sourceName}->hasChildren()) {
- return $this;
- }
- if ($overwrite) {
- unset($this->{$sourceName});
- } else {
- return $this;
- }
- }
- $targetChild = $this->addChild($sourceName, $source->xmlentities());
- $targetChild->setParent($this);
- foreach ($source->attributes() as $key => $value) {
- $targetChild->addAttribute($key, $this->xmlentities($value));
- }
- return $this;
- }
- if (isset($this->{$sourceName})) {
- $targetChild = $this->{$sourceName};
- }
- if ($targetChild === null) {
- // if child target is not found create new and descend
- $targetChild = $this->addChild($sourceName);
- $targetChild->setParent($this);
- foreach ($source->attributes() as $key => $value) {
- $targetChild->addAttribute($key, $this->xmlentities($value));
- }
- }
- // finally add our source node children to resulting new target node
- foreach ($sourceChildren as $childKey => $childNode) {
- $targetChild->extendChild($childNode, $overwrite);
- }
- return $this;
- }
- /**
- * Set node
- *
- * @param string $path
- * @param string $value
- * @param bool $overwrite
- * @return $this
- */
- public function setNode($path, $value, $overwrite = true)
- {
- $arr1 = explode('/', $path);
- $arr = [];
- foreach ($arr1 as $v) {
- if (!empty($v)) {
- $arr[] = $v;
- }
- }
- $last = sizeof($arr) - 1;
- $node = $this;
- foreach ($arr as $i => $nodeName) {
- if ($last === $i) {
- if (!isset($node->{$nodeName}) || $overwrite) {
- $node->{$nodeName} = $value;
- }
- } else {
- if (!isset($node->{$nodeName})) {
- $node = $node->addChild($nodeName);
- } else {
- $node = $node->{$nodeName};
- }
- }
- }
- return $this;
- }
- /**
- * Unset self from the XML-node tree
- *
- * Note: trying to refer this object as a variable after "unsetting" like this will result in E_WARNING
- * @return void
- */
- public function unsetSelf()
- {
- $uniqueId = uniqid();
- $this['_unique_id'] = $uniqueId;
- $children = $this->getParent()->xpath('*');
- for ($i = count($children); $i > 0; $i--) {
- if ($children[$i - 1][0]['_unique_id'] == $uniqueId) {
- unset($children[$i - 1][0]);
- return;
- }
- }
- }
- }
|