Это лучшее, что я смог придумать до сих пор:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" version="1.0"/>
<xsl:template match="/root">
<xsl:copy>
<xsl:for-each select="*">
<xsl:call-template name="check-if-allowed">
<xsl:with-param name="path" select="local-name(.)"/>
</xsl:call-template>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template name="check-if-allowed">
<xsl:param name="path"/>
<xsl:copy>
<xsl:if test="$path = document('filter.xml')//RequiredElements/elementName/text()">
<xsl:attribute name="flagged-by-filter">true</xsl:attribute>
</xsl:if>
<xsl:choose>
<xsl:when test="*">
<xsl:for-each select="*">
<xsl:call-template name="check-if-allowed">
<xsl:with-param name="path" select="concat($path, '/', local-name(.))"/>
</xsl:call-template>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="text()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Давайте рассмотрим это: первый шаблон соответствует вашему элементу /root
. Он сделает поверхностную копию, а затем вызовет шаблон check-if-allowed
для каждого дочернего элемента, передав локальное имя этого дочернего элемента в качестве параметра path
.
Шаблон check-if-allowed
принимает параметр с именем path
. Он делает поверхностную копию своего узла, а затем проверяет, содержится ли параметр path
в выборе, сделанном из документа filter.xml
. Это должен быть путь к вашему второму документу, который содержит список разрешенных путей. Если тест прошел успешно (т. е. если параметр path
отображается как текстовое содержимое elementName
в filter.xml
), то он также добавит атрибут с именем flagged-by-filter
и значением true
.
После этого xsl:choose
сделает одно из двух. Если для текущего есть какие-либо дочерние элементы, он будет вызывать для них тот же шаблон check-if-allowed
, но каждый раз с параметром path
, к которому было добавлено локальное имя этого элемента. Если нет дочерних элементов, он просто скопирует любой текст, который мог быть в текущем элементе.
Имейте в виду, что это очень неполное решение. Он игнорирует атрибуты и не работает для смешанного содержимого (то есть текста, смешанного с элементами).
Эту вторую таблицу стилей можно применить к результату первой для фактической фильтрации:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:copy><xsl:apply-templates select="*"/></xsl:copy>
</xsl:template>
<xsl:template match="*[//*[@flagged-by-filter='true']]">
<xsl:copy><xsl:apply-templates select="*"/></xsl:copy>
</xsl:template>
<xsl:template match="*[* and not(//*[@flagged-by-filter='true']) and @flagged-by-filter='true']">
<xsl:copy></xsl:copy>
</xsl:template>
<xsl:template match="*[not(*) and @flagged-by-filter='true']">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="*[not(*) and not(@flagged-by-filter='true')]"/>
</xsl:stylesheet>
Опять же, здесь очень простая работа. Он не обрабатывает атрибуты, и все еще есть проблема, поскольку elem4
по какой-то причине всегда проходит. Не знаю почему, отладчик показывает мне, что он всегда соответствует второму шаблону, но я не могу представить, как это сделать.
Это более обычный декларативный стиль XSLT. Первый шаблон соответствует корню. Он просто копирует его, а затем применяет шаблоны к своим дочерним элементам. Второй шаблон соответствует любому элементу, у которого есть потомок с атрибутом flagged-by-filter="true"
. Третий шаблон соответствует любому элементу, который имеет хотя бы один дочерний элемент, имеет атрибут, отмеченный фильтром, но не имеет потомков с указанным атрибутом. Четвертый шаблон соответствует любому элементу, у которого нет дочернего элемента, но который сам помечен. Окончательный шаблон соответствует любому элементу, который не имеет дочернего элемента и не помечен сам по себе.
Хотя это не полное решение вашей проблемы, я надеюсь, что этого достаточно, чтобы вы могли двигаться дальше. Если вы не можете применить два последовательных преобразования XSLT, вам нужно будет найти способ применить материал из первого XSLT по мере необходимости. Я не могу придумать, как это можно сделать, но, возможно, у кого-то есть хорошая идея.
Тем не менее, для такой проблемы либо не будет использоваться XSLT, либо будет просто сгенерирована таблица стилей программно на основе XML-файла вашего фильтра. Вышеприведенное будет очень плохо с точки зрения производительности, поскольку мы постоянно применяем выражения XPath к дополнительному документу. Мало того, его придется каждый раз полностью разбирать. У меня когда-то была похожая установка, и я обнаружил, что производительность очень низкая. Поэтому я изменил доступ ко второму документу на функцию расширения, которая будет вызывать метод Java с использованием предварительно загруженных данных.
XSLT отлично подходит для некоторых вещей, но когда вы сталкиваетесь с такой сложностью, я думаю, что лучше всего использовать его в сочетании с другим языком.
person
G_H
schedule
30.10.2011