XPath в SimpleXML для пространств имен по умолчанию без префиксов

У меня есть 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 без префикса для результата.