Получить весь лист из сложного XML с атрибутами

У меня есть такой XML-файл

myxml.xml

<?xml version="1.0" encoding="utf-8"?> <products nb="2" type="new"> <product ean="12345677654321"> <sku>Product1</sku> <parameters> <short_desc> Short description of the product1 </short_desc> <price currency="USD">19.65</price> </parameters> </product> <product ean="12345644654321"> <sku>Product2</sku> <parameters> <long_desc> Long description of the product2 </long_desc> <price currency="USD">19.65</price> <vat>20</vat> </parameters> </product> </products> 

Я бы сделал такой массив

 /products/@nb /products/@type /products/product/@ean /products/product/sku /products/product/parameters/short_desc /products/product/parameters/long_desc /products/product/parameters/price /products/product/parameters/price/@currency /products/product/parameters/vat 

Я почти этот результат с этим кодом

getpath.xsl

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:variable name="vApos">'</xsl:variable> <xsl:template match="*[@* or not(*)] "> <xsl:if test="not(*)"> <xsl:apply-templates select="ancestor-or-self::*" mode="path"/> <xsl:text>
</xsl:text> </xsl:if> <xsl:apply-templates select="@*|*"/> </xsl:template> <xsl:template match="*" mode="path"> <xsl:value-of select="concat('/',name())"/> <xsl:variable name="vnumPrecSiblings" select= "count(preceding-sibling::*[name()=name(current())])"/> <xsl:if test="$vnumPrecSiblings"> <xsl:value-of select="concat('[', $vnumPrecSiblings +1, ']')"/> </xsl:if> </xsl:template> <xsl:template match="@*"> <xsl:apply-templates select="../ancestor-or-self::*" mode="path"/> <xsl:value-of select="concat('/@',name())"/> <xsl:text>
</xsl:text> </xsl:template> </xsl:stylesheet> $xslDoc = new \DOMDocument(); $xslDoc->substituteEntities = true; $xslDoc->load('getpath.xsl'); $xmlDoc = new \DOMDocument(); $xmlDoc->load('myxml.xml'); $proc = new \XSLTProcessor(); $proc->importStylesheet($xslDoc); $rest = $proc->transformToXML($xmlDoc); $res = preg_replace("/\\s/"," ", $rest); $path = explode(" ", $res); foreach ($path as $key => $value) { if(!empty($value) && !preg_match("/\[.*\]/", $value)) $fields[] = $value; } return $fields; 

Этот код дает мне

 /products/@nb /products/@type /products/product/@ean /products/product/sku /products/product/parameters/short_desc /products/product/parameters/price /products/product/parameters/price/@currency 

/ products / product / parameters / long_desc и / products / product / parameters / price / vat отсутствуют 🙁

Как я могу разобрать полный XML с помощью xslt? Или у вас есть решение без XSLT ???

Да, вы можете сделать это с помощью некоторого Xpath в PHP.

 $dom = new DOMDocument(); $dom->loadXml($xml); $xpath = new DOMXpath($dom); function getNodeExpression(DOMNode $node, array &$namespaces) { $name = $node->localName; $namespace = $node->namespaceURI; if ($namespace == '') { return ($node instanceOf DOMAttr ? '@' : '').$name; } elseif (isset($namespaces[$namespace])) { $prefix = $namespaces[$namespace]; } else { $xmlns = $prefix = ($node->prefix == '') ? 'ns' : $node->prefix; $i = 1; while (in_array($xmlns, $namespaces)) { $xmlns = $prefix.'-'.$i; $i++; } $namespaces[$namespace] = $prefix; } return ($node instanceOf DOMAttr ? '@' : '').$prefix.':'.$name; } $result = []; $namespaces= []; foreach ($xpath->evaluate('//*[count(*) = 0]|//@*') as $node) { $path = ''; foreach ($xpath->evaluate('ancestor::*', $node) as $parent) { $path = '/'.getNodeExpression($parent, $namespaces); } $path .= '/'.getNodeExpression($node, $namespaces); $result[$path] = TRUE; } 

Выход: https://eval.in/118054

 array(10) { [0]=> string(13) "/products/@nb" [1]=> string(15) "/products/@type" [2]=> string(13) "/product/@ean" [3]=> string(12) "/product/sku" [4]=> string(22) "/parameters/short_desc" [5]=> string(17) "/parameters/price" [6]=> string(16) "/price/@currency" [7]=> string(21) "/parameters/long_desc" [8]=> string(20) "/long_desc/@xml:lang" [9]=> string(15) "/parameters/vat" } array(1) { ["http://www.w3.org/XML/1998/namespace"]=> string(3) "xml" } 

Сложная часть в этом заключается в разрешении пространств имен и создании для них префиксов. Итак, давайте подробно рассмотрим:

Получите локальное имя (имя тега без префикса пространства имен) и пространство имен.

 $name = $node->localName; $namespace = $node->namespaceURI; 

Если пространство имен пустое, нам не нужен префикс, возвращающий выражение только с именем узла.

  if ($namespace == '') { return ($node instanceOf DOMAttr ? '@' : '').$name; 

В противном случае проверьте, было ли пространство имен уже использовано на другом узле и повторное использование этого префикса.

  } elseif (isset($namespaces[$namespace])) { $prefix = $namespaces[$namespace]; 

Если это неизвестное пространство имен, прочитайте префикс, используемый на этом узле. Если узел не использовал префикс, используйте строку «ns».

  } else { $xmlns = $prefix = ($node->prefix == '') ? 'ns' : $node->prefix; 

Убедитесь, что префикс еще не используется для другого пространства имен, добавьте число и увеличивайте его до тех пор, пока у нас не будет уникального префикса.

  $i = 1; while (in_array($xmlns, $namespaces)) { $xmlns = $prefix.'-'.$i; $i++; } 

Сохраните определение пространства имен => для следующего вызова.

  $namespaces[$namespace] = $prefix; 

Вернуть выражение, включая префикс.

  return ($node instanceOf DOMAttr ? '@' : '').$prefix.':'.$name; 

Массив пространства имен можно использовать для регистрации всего необходимого префикса пространства имен в объекте Xpath.