到目前为止,这是我能想到的最好方案:
<?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
参数出现为filter.xml
中elementName
的文本内容),则还会添加一个名为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在某些方面非常好,但是当您涉及到这样的复杂性时,我认为最好与另一种语言结合使用。
xsl:variable
中的元素构建变量。在正确的人手中,XSLT可以如此优雅。 - G_H