XSLT для удаления тегов/атрибутов, принадлежащих пространству имен

Я пытаюсь использовать XSLT для удаления тегов/атрибутов, принадлежащих пространству имен. Трудность состоит в том, что теги из разных пространств имен могут быть встроены друг в друга.

Пример:

<?xml version="1.0" encoding="utf-8"?>
<Collection xmlns="http://s0" xmlns:ns1="http://s1">
  <Identifier Name="CollectionX"
          ns1:GlobalID="{E436833B-B0A6-4E0D-804B-60052B767AE3}"
          ns1:LocalID="{0130C866-7A91-4544-A82B-E0C0F2E3BCB2}"  />

  <Properties>
    <ns1:Collectible>1982</ns1:Collectible>
    <Displayed>Reserved</Displayed>
    <Picture>Reserved.jpeg</Picture>
  </Properties>

  <WeakLinks>
    <Link Type="resource" Language="en-us"/>
  </WeakLinks>

</Collection>

Я хочу отфильтровать все теги/свойства, которые не принадлежат ns1, если у них нет никаких дочерних элементов ns1.

Таким образом, результат должен быть:

<?xml version="1.0" encoding="utf-8"?>
<Collection xmlns="http://s0" xmlns:ns1="http://s1">
  <Identifier 
      ns1:GlobalID="{E436833B-B0A6-4E0D-804B-60052B767AE3}"
      ns1:LocalID="{0130C866-7A91-4544-A82B-E0C0F2E3BCB2}"  />

  <Properties>
    <ns1:Collectible>1982</ns1:Collectible>
  </Properties>

</Collection>

Как я могу использовать XSLT? Любая помощь?

Ответ 1

Это преобразование:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ns1="http://s1">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match=
 "*[not(attribute::ns1:*)
  and
    not(descendant-or-self::ns1:*)
   ]
 |
  @*[not(namespace-uri()='http://s1')]
 "/>
</xsl:stylesheet>

при применении к предоставленному XML-документу:

<Collection xmlns="http://s0" xmlns:ns1="http://s1">
    <Identifier Name="CollectionX"
      ns1:GlobalID="{E436833B-B0A6-4E0D-804B-60052B767AE3}"
      ns1:LocalID="{0130C866-7A91-4544-A82B-E0C0F2E3BCB2}"  />
    <Properties>
        <ns1:Collectible>1982</ns1:Collectible>
        <Displayed>Reserved</Displayed>
        <Picture>Reserved.jpeg</Picture>
    </Properties>
    <WeakLinks>
        <Link Type="resource" Language="en-us"/>
    </WeakLinks>
</Collection>

создает желаемый, правильный результат:

<Collection xmlns="http://s0" xmlns:ns1="http://s1">
   <Identifier ns1:GlobalID="{E436833B-B0A6-4E0D-804B-60052B767AE3}"
   ns1:LocalID="{0130C866-7A91-4544-A82B-E0C0F2E3BCB2}"/>
   <Properties>
      <ns1:Collectible>1982</ns1:Collectible>
   </Properties>
</Collection>

Объяснение

  • Правило идентичности (шаблон) копирует каждый node "as-is".

  • Существует только один шаблон, переопределяющий правило идентификации. Эти шаблоны не имеют тела - это означает, что он эффективно фильтрует (удаляет) любой сопоставленный node от копирования на выход. Соответствующие узлы - это те, которые должны быть отфильтрованы: 1) любой элемент, который не имеет атрибутов, принадлежащих пространству имен, к которому привязан префикс ns1:, а также сам не принадлежит к этому пространству имен, а также имеет нет узлов-потомков, принадлежащих этому пространству имен. И 2) любой атрибут, который не принадлежит этому пространству имен.

Помните. Переопределение правила идентификации является наиболее фундаментальным и мощным шаблоном проектирования XSLT. Подробнее об этом шаблоне проектирования можно найти здесь.

Ответ 2

Вы можете выбрать элементы с пространством имен ns1, используя ns1:*.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:ns1="http://s1">
  <xsl:output method="xml" indent="yes"/>
  <xsl:strip-space elements="*"/>
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@ns1:* | 
                                   node()[attribute::ns1:* | 
                                          descendant-or-self::ns1:*] | 
                                   text() | comment() | processing-instruction()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Обновление

Я обновил XPath для соответствия атрибутов @ns1:* только для захвата атрибутов с нужным пространством имен. Я также исправил поддержку комментариев и обработки инструкций в моем тестировании. Учитывая следующий XML

<?xml version="1.0" encoding="utf-8"?>
<Collection xmlns="http://s0" xmlns:ns1="http://s1">
  <Identifier Name="CollectionX"
          ns1:GlobalID="{E436833B-B0A6-4E0D-804B-60052B767AE3}"
          ns1:LocalID="{0130C866-7A91-4544-A82B-E0C0F2E3BCB2}"  />
<!-- comment -->
  <Properties>
    <ns1:Collectible>1982</ns1:Collectible>
    <Displayed>Reserved</Displayed>
    <Picture>Reserved</Picture>
  </Properties>

  <WeakLinks>
    <Link Type="resource" Language="en-us"/>
  </WeakLinks>

</Collection>

XSL выше производит этот вывод (тестируется с Saxon и MSXML).

<?xml version="1.0" encoding="UTF-8"?>
<Collection xmlns="http://s0" xmlns:ns1="http://s1">
  <Identifier ns1:GlobalID="{E436833B-B0A6-4E0D-804B-60052B767AE3}" 
              ns1:LocalID="{0130C866-7A91-4544-A82B-E0C0F2E3BCB2}"/>
  <!-- comment -->
  <Properties>
    <ns1:Collectible>1982</ns1:Collectible>
  </Properties>
</Collection>

Обновление 2

Я удалил свою предыдущую ссылку на атрибуты без пространства имен. В соответствии с спецификацией XPath, которая очень хорошо представлена ​​здесь @Dimitre.Novatchev, атрибут без префикса пространства имен относится к "no-namespace", а не к пространству имен по умолчанию или пространству имен родительского node. Если вы хотите сопоставить их, добавьте @*[parent::ns1:* and namespace-uri()=''] | в выражение соответствия в <apply-templates ...>. Это применило бы такую ​​ситуацию, как <ns1:Collectible WhatIsMyNamespace="no-namespace">, где вы хотите сопоставить WhatIsMyNamespace="no-namespace".