Как получить пространства имен в файлах XML с помощью Xpath - PullRequest
37 голосов
/ 23 сентября 2008

У меня есть XML-файл, который начинается так:

<Elements name="Entities" xmlns="XS-GenerationToolElements">

Мне придется открыть много этих файлов. У каждого из них есть свое пространство имен, но одновременно будет только одно пространство имен (я никогда не найду двух пространств имен, определенных в одном файле XML).

Используя XPath, я хотел бы иметь автоматический способ добавления заданного пространства имен в диспетчер пространства имен. До сих пор я мог получить пространство имен только при разборе файла xml, но у меня есть экземпляр XPathNavigator, и у него должен быть хороший и чистый способ получить пространства имен, верно?

- ИЛИ -

Учитывая, что у меня есть только одно пространство имен, каким-то образом заставьте XPath использовать только одно, присутствующее в xml, что позволит избежать загромождения кода, всегда добавляя пространство имен.

Ответы [ 3 ]

83 голосов
/ 23 сентября 2008

Есть несколько техник, которые вы можете попробовать; то, что вы будете использовать, будет зависеть от того, какую именно информацию вам нужно получить из документа, насколько строгой вы хотите быть и насколько соответствует используемая вами реализация 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 с помощью регулярного выражения, но вы могли бы сделать что-то более сложное, если бы вам нужно было одновременно выполнять другие операции.

10 голосов
/ 14 ноября 2008

Это 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>
4 голосов
/ 23 сентября 2008

К сожалению, в XPath нет понятия «пространство имен по умолчанию». Вам необходимо зарегистрировать пространства имен с префиксами в контексте XPath, а затем использовать эти префиксы в выражениях XPath. Это означает очень многословный xpath, но это основной недостаток XPath 1. Очевидно, XPath 2 решит эту проблему, но сейчас это бесполезно.

Я предлагаю вам программно проверить документ XML на наличие пространства имен, связать это пространство имен с префиксом в контексте XPath, а затем использовать префикс в выражениях xpath.

...