У меня есть такой 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.