Как получить пространства имен в файлах XML, используя Xpath
У меня есть XML-файл, который начинается так:
<Elements name="Entities" xmlns="XS-GenerationToolElements">
Мне придется открыть много этих файлов. Каждое из них имеет свое пространство имен, но одновременно будет иметь только одно пространство имен (я никогда не найду двух пространств имен, определенных в одном файле XML).
Используя XPath, я хотел бы иметь автоматический способ добавления заданного пространства имен в диспетчер пространства имен. До сих пор я мог получить пространство имен только при разборе xml-файла, но у меня есть экземпляр XPathNavigator, и у него должен быть хороший и чистый способ получить пространства имен, верно?
-- ИЛИ ЖЕ --
Учитывая, что у меня есть только одно пространство имен, каким-то образом заставьте XPath использовать только одно, присутствующее в xml, таким образом избегая загромождения кода, всегда добавляя пространство имен.
3 ответа
Есть несколько методов, которые вы можете попробовать; то, что вы будете использовать, будет зависеть от того, какую именно информацию вам нужно получить из документа, насколько строгой вы хотите быть и насколько соответствует используемая вами реализация XPath.
Одним из способов получения URI пространства имен, связанного с конкретным префиксом, является использование namespace::
ось. Это даст вам узел пространства имен, именем которого является префикс, а значением - URI пространства имен. Например, вы можете получить URI пространства имен по умолчанию для элемента документа, используя путь:
/*/namespace::*[name()='']
Возможно, вы сможете использовать это для настройки ассоциаций пространства имен для вашего XPathNavigator. Имейте в виду, однако, что namespace::
Ось является одним из тех углов XPath 1.0, который не всегда реализуется.
Второй способ получить этот URI пространства имен - использовать namespace-uri()
функция для элемента документа (который, как вы сказали, всегда будет в этом пространстве имен). Выражение:
namespace-uri(/*)
даст вам это пространство имен.
В качестве альтернативы можно забыть о связи префикса с этим пространством имен и просто освободить путь к пространству имен. Вы можете сделать это с помощью local-name()
функционировать всякий раз, когда вам нужно обратиться к элементу, пространство имен которого вы не знаете. Например:
//*[local-name() = 'Element']
Вы можете пойти еще дальше и проверить URI пространства имен элемента по сравнению с URI элемента документа, если вы действительно этого хотите:
//*[local-name() = 'Element' and namespace-uri() = namespace-uri(/*)]
Последний вариант, учитывая, что пространство имен, кажется, ничего не значит для вас, - это запустить ваш XML через фильтр, который удаляет пространства имен. Тогда вам не придется беспокоиться о них в вашем XPath. Самый простой способ сделать это - просто удалить xmlns
атрибут с регулярным выражением, но вы могли бы сделать что-то более сложное, если вам нужно было сделать другие операции в то же время.
Это 40-строчное xslt-преобразование предоставляет всю полезную информацию о пространствах имен в данном XML-документе:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="ext"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kNsByNsUri" match="ns" use="@uri"/>
<xsl:variable name="vXmlNS"
select="'http://www.w3.org/XML/1998/namespace'"/>
<xsl:template match="/">
<xsl:variable name="vrtfNamespaces">
<xsl:for-each select=
"//namespace::*
[not(. = $vXmlNS)
and
. = namespace-uri(..)
]">
<ns element="{name(..)}"
prefix="{name()}" uri="{.}"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="vNamespaces"
select="ext:node-set($vrtfNamespaces)/*"/>
<namespaces>
<xsl:for-each select=
"$vNamespaces[generate-id()
=
generate-id(key('kNsByNsUri',@uri)[1])
]">
<namespace uri="{@uri}">
<xsl:for-each select="key('kNsByNsUri',@uri)/@element">
<element name="{.}" prefix="{../@prefix}"/>
</xsl:for-each>
</namespace>
</xsl:for-each>
</namespaces>
</xsl:template>
При применении к следующему документу XML:
<a xmlns="my:def1" xmlns:n1="my:n1"
xmlns:n2="my:n2" xmlns:n3="my:n3">
<b>
<n1:d/>
</b>
<n1:c>
<n2:e>
<f/>
</n2:e>
</n1:c>
<n2:g/>
</a>
желаемый результат получается:
<namespaces>
<namespace uri="my:def1">
<element name="a" prefix=""/>
<element name="b" prefix=""/>
<element name="f" prefix=""/>
</namespace>
<namespace uri="my:n1">
<element name="n1:d" prefix="n1"/>
<element name="n1:c" prefix="n1"/>
</namespace>
<namespace uri="my:n2">
<element name="n2:e" prefix="n2"/>
<element name="n2:g" prefix="n2"/>
</namespace>
</namespaces>
К сожалению, в XPath нет понятия "пространство имен по умолчанию". Вам необходимо зарегистрировать пространства имен с префиксами в контексте XPath, а затем использовать эти префиксы в выражениях XPath. Это означает очень многословный xpath, но это основной недостаток XPath 1. Очевидно, XPath 2 решит эту проблему, но сейчас это бесполезно.
Я предлагаю вам программно проверить ваш XML-документ на предмет пространства имен, связать это пространство имен с префиксом в контексте XPath, а затем использовать префикс в выражениях xpath.