У меня есть XML-документ с прикрепленным к нему пространством имен по умолчанию, например
<foo xmlns="http://www.example.com/ns/1.0"> ... </foo>
На самом деле это сложный XML-документ, который соответствует сложной схеме. Моя задача – разобрать некоторые данные. Чтобы помочь мне, у меня есть таблица XPath. XPath довольно глубоко вложен, например
level1/level2/level3[@foo="bar"]/level4[@foo="bar"]/level5/level6[2]
Человек, который генерирует XPath, является экспертом в схеме, поэтому я исхожу из предположения, что я не могу его упростить или использовать ярлыки обхода объектов.
Я использую SimpleXML, чтобы разобрать все. Моя проблема связана с тем, как обрабатывается пространство имен по умолчанию.
Поскольку в корневом элементе есть пространство имен по умолчанию, я не могу просто сделать
$xml = simplexml_load_file($somepath); $node = $xml->xpath('level1/level2/level3[@foo="bar"]/level4[@foo="bar"]/level5/level6[2]');
Я должен зарегистрировать пространство имен , назначить его префиксу, а затем использовать префикс в моем XPath, например
$xml = simplexml_load_file($somepath); $xml->registerXPathNamespace('myns', 'http://www.example.com/ns/1.0'); $node = $xml->xpath('myns:level1/myns:level2/myns:level3[@foo="bar"]/myns:level4[@foo="bar"]/myns:level5/myns:level6[2]');
Добавление префиксов в долгосрочной перспективе не поддается управлению.
Существует ли подходящий способ обработки пространств имен по умолчанию без использования префиксов с XPath?
Использование пустого префикса не работает ( $xml->registerXPathNamespace('', 'http://www.example.com/ns/1.0');
). Я могу выровнять пространство имен по умолчанию, например
$xml = file_get_contents($somepath); $xml = str_replace('xmlns="http://www.example.com/ns/1.0"', '', $xml); $xml = simplexml_load_string($xml);
но это обходит проблему.
Из небольшого количества чтения в Интернете это не ограничивается какой-либо конкретной PHP или другой библиотекой, но для самого XPath – по крайней мере, в версии XPath 1.0
XPath 1.0 не содержит понятия пространства имен по умолчанию, поэтому независимо от того, как имена элементов отображаются в источнике XML, если у них есть связанное с ними пространство имен, селектора для них должны быть префиксными в базовых селекторах XPath формы ns:name
. Обратите внимание, что ns
– префикс, определенный в процессоре XPath, а не обрабатываемый документ, поэтому не имеет отношения к тому, как атрибуты xmlns
используются в представлении XML.
См., Например, эту страницу «Общие ошибки XSLT» , говоря о тесно связанном XSLT 1.0:
Чтобы получить доступ к элементам с именами в XPath, вы должны определить префикс для своего пространства имен. […] К сожалению, XSLT версии 1.0 не имеет понятия, похожего на пространство имен по умолчанию; поэтому вы должны снова и снова повторять префиксы пространства имен.
Согласно ответу на аналогичный вопрос , XPath 2.0 включает понятие «пространство имен по умолчанию», а связанная выше ссылка XSLT упоминает это также в контексте XSLT 2.0.
К сожалению, все встроенные расширения XML в PHP построены поверх библиотек libxml2 и libxslt , которые поддерживают только версию 1.0 XPath и XSLT.
Поэтому, кроме предварительной обработки документа, чтобы не использовать пространства имен, единственным вариантом было бы найти процессор XPath 2.0, который вы могли бы подключить к PHP.
(В стороне, стоит отметить, что если у вас есть несвязанные атрибуты в вашем XML-документе, они не являются технически в пространстве имен по умолчанию, а вообще не имеют пространства имен, см. XML-пространства имен и атрибуты Unprefixed для обсуждения этой странности пространства имен спецификации).
Существует ли подходящий способ обработки пространств имен по умолчанию без использования префиксов с XPath?
Нет. Правильный способ обработки любого пространства имен – связать некоторое значение (префикс) с этим пространством имен, чтобы он мог быть явно выбран в выражении XPath. Пространство имен по умолчанию не отличается.
Подумайте об этом так: элемент в каком-то пространстве имен и другой элемент с тем же именем в другом пространстве имен (или вообще без пространства имен) – это разные элементы. Они могут означать (т.е. представлять) разные вещи. В этом весь смысл. Вам нужно указать XPath, который вы хотите выбрать. Без этого XPath не знает, о чем вы просите.
Добавление префиксов в долгосрочной перспективе не поддается управлению.
Я действительно не понимаю, почему. Независимо от того, что создает выражение XPath, должно быть возможно указать правильное выражение XPath (или это сломанный инструмент).
Возможно, вы думаете: « Почему я не могу просто игнорировать пространство имен и получить все элементы, соответствующие этому имени? » Есть действительно хакерские способы сделать это (например, уже на основе XSLT-ответа), но они разбиты по дизайну. Элемент в XML идентифицируется комбинацией своего пространства имен и локального имени, так же как ваш дом может быть идентифицирован с номером улицы (локальное имя) в каком-либо городе и государстве (пространство имен). Если я скажу вам, что я живу на главной улице 422, тогда вы до сих пор не знаете, где я живу, пока я не скажу вам, какой город и штат.
Вы все еще можете подумать: « Достаточно с глупыми аналогами, я действительно очень хочу это сделать ». Вы можете выбирать элементы с заданным именем во всех пространствах имен, сопоставляя только локальную часть имени элемента, например:
*[local-name()='level1']/*[local-name()='level2'] /*[local-name()='level3' and @foo="bar"]/*[local-name()='level4' and @foo="bar"]/*[local-name()='level5']/*[local-name()='level6'][2]');
Обратите внимание, что это не ограничивает пространство имен по умолчанию. Он полностью игнорирует пространства имен. Это уродливо, и я не рекомендую его, но иногда вы просто хотите игнорировать то, что лучше, и что-то сделать.
Кстати, это не ошибка PHP. Это то, что требует спецификация XPath. Вы должны указать префикс для выбора узла в пространстве имен. Если бы PHP позволял вам делать это каким-то другим способом, то, как бы они ни назывались, это уже не было XPath (согласно спецификации).
Чтобы избежать хаков, таких как str_replace
которые у вас есть (и я бы рекомендовал это избегать), вы можете запускать файлы XML через XSLT, чтобы исключить пространство имен:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:myns="http://www.example.com/ns/1.0"> <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()" /> </xsl:copy> </xsl:template> <xsl:template match="myns:*"> <xsl:element name="{local-name()}"> <xsl:apply-templates select="@* | node()" /> </xsl:element> </xsl:template> </xsl:stylesheet>
При запуске на любом из этих входов:
<foo xmlns="http://www.example.com/ns/1.0"> <a> <child attr="5"></child> </a> </foo> <ex:foo xmlns:ex="http://www.example.com/ns/1.0"> <ex:a> <ex:child attr="5"></ex:child> </ex:a> </ex:foo>
Результат такой же:
<foo> <a> <child attr="5" /> </a> </foo>
Это позволит вам использовать ваши XPaths без префикса для результата.