通过xslt从另一个xml结构获取xml信息

时间:2011-10-30 08:46:07

标签: xml xslt

我正在使用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(没有其他编程语言)中提出一些东西来获得我需要的结构,但却无法实现。

我应该做什么的想法或指示?

感谢您的帮助。

2 个答案:

答案 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>

<强>解释

  1. identity rule“按原样”复制每个节点。

  2. 有一个覆盖模板匹配其父元素的任何元素在此模板中,完成以下操作:

  3. 为当前(匹配)节点生成一个字符串,它是一个相对XPath表达式,上下文节点是文档的顶部元素。

  4. 如果此相对路径是作为参数传递的文档中指定的表达式之一的前缀,则我们对此元素执行标识转换。

答案 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非常适合某些东西,但是当你遇到这样的复杂性时,我认为它最好与另一种语言结合使用。