我正在使用XSLT 1.0,我想做以下事情:
我有1.xml文件:
<root>
<elem1>value1</elem1>
<elem2>
<elem3>
<param1>value2</param1>
<param2>value3</param2>
</elem3>
</elem2>
<elem4>
<param3>value4</param3>
</elem4>
</root>
现在一个客户端传递给我另一个xml文件,该文件告诉我他希望我给他什么元素(可以在客户端之间改变),即:
<root>
<RequiredElements>
<elementName>elem1</elementName>
<elementName>elem2/elem3/param1</elementName>
</RequiredElements>
</root>
这意味着在这种情况下,我应该创建另一个xml文件,具有以下结构:
<root>
<elem1>value1</elem1>
<elem2>
<elem3>
<param1>value2</param1>
</elem3>
</elem2>
</root>
我试图在xslt(没有其他编程语言)中提出一些东西来获得我需要的结构,但却无法实现。
我应该做什么的想法或指示?
感谢您的帮助。
答案 0 :(得分:3)
这个简单的转换(如果没有内联参数-d,则少于30行):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:paramDoc>
<root>
<RequiredElements>
<elementName>elem1</elementName>
<elementName>elem2/elem3/param1</elementName>
</RequiredElements>
</root>
</my:paramDoc>
<xsl:variable name="vPaths" select=
"document('')/*/my:paramDoc/*/*/*"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*/*">
<xsl:variable name="vPath">
<xsl:for-each select=
"ancestor-or-self::*[not(position()=last())]">
<xsl:value-of select="concat(name(), '/')"/>
</xsl:for-each>
</xsl:variable>
<xsl:if test=
"$vPaths[starts-with(concat(.,'/'), $vPath)]">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
应用于提供的XML文档:
<root>
<elem1>value1</elem1>
<elem2>
<elem3>
<param1>value2</param1>
<param2>value3</param2>
</elem3>
</elem2>
<elem4>
<param3>value4</param3>
</elem4>
</root>
生成想要的正确结果:
<root>
<elem1>value1</elem1>
<elem2>
<elem3>
<param1>value2</param1>
</elem3>
</elem2>
</root>
<强>解释强>:
identity rule“按原样”复制每个节点。
有一个覆盖模板匹配其父元素的任何元素在此模板中,完成以下操作:
为当前(匹配)节点生成一个字符串,它是一个相对XPath表达式,上下文节点是文档的顶部元素。
如果此相对路径是作为参数传递的文档中指定的表达式之一的前缀,则我们对此元素执行标识转换。
答案 1 :(得分:1)
这是迄今为止我能够提出的最好的:
<?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"
属性的后代的任何元素。第三个模板匹配具有至少一个子元素的任何元素,具有flagged-by-filter属性但没有具有所述属性的后代。第四个模板匹配任何没有子元素但本身标记的元素。最终模板匹配任何没有子元素的元素,也不标记自己。
虽然这不是解决问题的完整解决方案,但我希望这足以让您继续前行。如果您不能自由地应用两个连续的XSLT转换,则需要找到一种方法来根据需要应用第一个XSLT中的内容。我无法想象如何做到这一点,但也许其他人有一个好主意。
这样说和完成,对于这样的问题要么不使用XSLT,要么只是基于过滤器XML以编程方式生成样式表。以上在性能方面会非常糟糕,因为我们经常在其他文档上应用XPath表达式。不仅如此,它还必须每次都完全解析。我曾经有类似的设置,发现性能非常差。因此,我将对第二个文档的访问权限更改为扩展函数,该函数将使用预加载的数据调用Java方法。
XSLT非常适合某些东西,但是当你遇到这样的复杂性时,我认为它最好与另一种语言结合使用。