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"
.