每个for-each迭代的XSL谓词增加

时间:2014-05-14 08:49:30

标签: xslt xpath predicates

快速问题:有没有办法通过使用变量来增加XPATH的谓词,比如迭代C中的数组?例如/ XPATH / element [i]

我正在尝试使用XSL使用XPATHS从XML访问数据。 XML是数据库的输出,其中父节点是表名,其子节点是列。 XSL必须能够将子项的文本值转换为具有表名元素的列名的属性。

我试图解决的问题是每个表可以有多行,这些行作为具有相同名称的兄弟节点输出到XML。任何表中都可能存在无限行,因此我尝试使用for-each和表名的XPATH来处理每一行。这有效,但是当我尝试使用带有XPATH的文档函数和第一个XPATH的谓词然后是下一个XPATH然后是下一个XPATH时,我不知道该怎么做。我只能访问第一个XPATH。我想要一种能够在for-each的每次迭代中访问下一个XPATH的方法。有没有什么可以增加每个循环,谓词和用于指向下一个XPATH?

下面的XML代码是我用于测试的示例,它被称为DB.xml:

<?xml version="1.0"?>
<dataset>
 <rtbp>
  <cfmtype>dog</cfmtype>
  <cfmid>1</cfmid>
 </rtbp>
 <rtbp>
  <cfmtype>cat</cfmtype>
  <cfmid>2</cfmid>
 </rtbp>
 <FunctionSet> 
  <FUNCTIONSET__IDENTIFIER>1</FUNCTIONSET__IDENTIFIER>
  <RTBP__CFMID>1</RTBP__CFMID>
 </FunctionSet>
 <FunctionSet> 
  <FUNCTIONSET__IDENTIFIER>2</FUNCTIONSET__IDENTIFIER>
  <RTBP__CFMID>2</RTBP__CFMID>
 </FunctionSet>
</dataset>

以下是我正在使用的XSL:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>

 <xsl:template match="/">
  <xsl:for-each select="dataset/rtbp">
   <xsl:element name="RTBP">
    <xsl:attribute name="CFMtype">
     <xsl:value-of select="document('DB.xml')/dataset/rtbp[1]/cfmtype" />
    </xsl:attribute>
    <xsl:attribute name="CFMid">
     <xsl:value-of select="document('DB.xml')/dataset/rtbp[1]/cfmid" />
    </xsl:attribute>
    <xsl:text>&#xa;</xsl:text>

    <xsl:for-each select="/dataset/FunctionSet">
     <xsl:element name="FunctionSet">
      <xsl:attribute name="RTBP__CFMid">
       <xsl:value-of select="document('DB.xml')/dataset/FunctionSet[1]/FUNCTIONSET__IDENTIFIER" />
      </xsl:attribute>
      <xsl:attribute name="RTBP_FunctionSet">
       <xsl:value-of select="document('DB.xml')/dataset/FunctionSet[1]/RTBP__CFMID" />
      </xsl:attribute>
     </xsl:element>
     <xsl:text>&#xa;</xsl:text>
    </xsl:for-each>

   </xsl:element>
   <xsl:text>&#xa;</xsl:text>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

此时谓词设置为1但我希望它是一个在每个循环上迭代的变量,因此XPATH会更改为表名的下一个出现。

预期结果如下:

<?xml version="1.0"?>
<RTBP CFMtype="dog" CFMid="1">
  <FunctionSet RTBP__CFMid="1" RTBP_FunctionSet="1"/>
</RTBP>
<RTBP CFMtype="cat" CFMid="2">
  <FunctionSet RTBP__CFMid="2" RTBP_FunctionSet="2"/>
</RTBP>

因为您可以告诉第二个表(FunctionSet)是第一个(RTBP)的子节点,因此for-each中的for-each。我需要一个方法,将FunctionSet的第一行放入RTBP的第一行,同样放入第二行。

我是XML,XSL和发布问题的新手。

3 个答案:

答案 0 :(得分:2)

  

目的是从平面XML重新创建分层XML   使用DBunit从数据库导出。这种联系可以完成   通过cmfid

您绝对应该使用基于匹配cfmid值的 - 尤其是在您需要大量行的情况下。尝试:

XSLT 1.0

<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:key name="func" match="FunctionSet" use="RTBP__CFMID" />

<xsl:template match="/">
    <root>
        <xsl:for-each select="dataset/rtbp">
            <RTBP CFMtype="{cfmtype}" CFMid="{cfmid}">
                <xsl:for-each select="key('func', cfmid)">
                    <FunctionSet RTBP__CFMid="{RTBP__CFMID}" RTBP_FunctionSet="{FUNCTIONSET__IDENTIFIER}"/>
                </xsl:for-each>
            </RTBP>
        </xsl:for-each>
    </root>
</xsl:template>

</xsl:stylesheet>

如果将以上内容应用于以下测试输入

<?xml version="1.0"?>
<dataset>
 <rtbp>
  <cfmtype>dog</cfmtype>
  <cfmid>124</cfmid>
 </rtbp>
 <rtbp>
  <cfmtype>cat</cfmtype>
  <cfmid>256</cfmid>
 </rtbp>
 <FunctionSet> 
  <FUNCTIONSET__IDENTIFIER>Canine</FUNCTIONSET__IDENTIFIER>
  <RTBP__CFMID>124</RTBP__CFMID>
 </FunctionSet>
 <FunctionSet> 
  <FUNCTIONSET__IDENTIFIER>Feline</FUNCTIONSET__IDENTIFIER>
  <RTBP__CFMID>256</RTBP__CFMID>
 </FunctionSet>
 <FunctionSet> 
  <FUNCTIONSET__IDENTIFIER>Hound</FUNCTIONSET__IDENTIFIER>
  <RTBP__CFMID>124</RTBP__CFMID>
 </FunctionSet>
</dataset>

结果是:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <RTBP CFMtype="dog" CFMid="124">
    <FunctionSet RTBP__CFMid="124" RTBP_FunctionSet="Canine"/>
    <FunctionSet RTBP__CFMid="124" RTBP_FunctionSet="Hound"/>
  </RTBP>
  <RTBP CFMtype="cat" CFMid="256">
    <FunctionSet RTBP__CFMid="256" RTBP_FunctionSet="Feline"/>
  </RTBP>
</root>

请注意,您请求的输出格式会不必要地复制父级和子级中的cfmid值。

答案 1 :(得分:1)

我认为你正在寻找类似的东西(在更新后更新):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
        <xsl:output indent="yes"/>
        <xsl:template match="node()|@*">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*"/>
            </xsl:copy>
        </xsl:template>
        <xsl:template match="rtbp">
            <xsl:copy>
                <xsl:for-each select="*">
                    <xsl:attribute name="{local-name()}" select="."/>
                </xsl:for-each>
                <xsl:apply-templates 
                             select="//FunctionSet[RTBP__CFMID = current()/cfmid]"
                     mode="insertFunctionSet"/>
            </xsl:copy>
        </xsl:template>
        <xsl:template match="FunctionSet"/>
        <xsl:template match="FunctionSet" mode="insertFunctionSet">
            <xsl:copy>
                <xsl:for-each select="*">
                    <xsl:attribute name="{local-name()}" select="."/>
                </xsl:for-each>
            </xsl:copy>
        </xsl:template>
    </xsl:stylesheet>

这里的想法是在FunctionSet元素的上下文中以不同的方式处理元素rtbp

当你递归遍历整个树时,它不应该是输出的一部分(这是<xsl:template match="FunctionSet"/>的目标)。

但它应该在rtbp元素内部处理,因此我们此时以特定模式在相关FunctionSet上应用模板。这是<xsl:template match="FunctionSet" mode="insertFunctionSet">...</xsl:template>

的目标

输入您的信息:

<?xml version="1.0"?>
<dataset>
 <rtbp>
  <cfmtype>dog</cfmtype>
  <cfmid>1</cfmid>
 </rtbp>
 <rtbp>
  <cfmtype>cat</cfmtype>
  <cfmid>2</cfmid>
 </rtbp>
 <FunctionSet> 
  <FUNCTIONSET__IDENTIFIER>1</FUNCTIONSET__IDENTIFIER>
  <RTBP__CFMID>1</RTBP__CFMID>
 </FunctionSet>
 <FunctionSet> 
  <FUNCTIONSET__IDENTIFIER>2</FUNCTIONSET__IDENTIFIER>
  <RTBP__CFMID>2</RTBP__CFMID>
 </FunctionSet>
</dataset>

结果是:

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
   <rtbp cfmtype="dog" cfmid="1">
      <FunctionSet FUNCTIONSET__IDENTIFIER="1" RTBP__CFMID="1"/>
   </rtbp>
   <rtbp cfmtype="cat" cfmid="2">
      <FunctionSet FUNCTIONSET__IDENTIFIER="2" RTBP__CFMID="2"/>
   </rtbp>
</dataset>

答案 2 :(得分:-1)

对于那些在我发布这个问题的时候和我一样知识渊博的人,并且希望在这里找到相同信息的人是我对问题的解决方案。简短回答快速问题'你可以增加一个变量'。没有!但您可以设置变量并使用以下代码段移动位置:

<xsl:for-each select="/dataset/rtbp">
   <xsl:variable name="i" select="position()" />
 </xsl:for-each> 

此代码段循环遍历源XML中的rtbp表,并在每个循环中将位置移动一个位置。这将创建一个对象,您可以在XPath中使用该对象来测试具有相同URI路径的每个Xpath出现的条件。如:

<xsl:for-each select="/dataset/rtbp">
    <xsl:variable name="i" select="position()" />
    <xsl:if test="/dataset/FunctionSet[$i]/cfmid = /dataset/rtbp[$n]/cfmid">
       <!--code if condition is true-->
 </xsl:for-each> 

[$variable name]是指导XPath到元素名称出现的方式。因此,当i = 1时,它会在XPath中查找元素名称的第一个出现,然后当i = 2时,它会在XPath中查找元素名称的第二次出现。

Key函数是在模板中搜索关键条件的好工具。但是我每个模板只能使用1个键功能。如果您希望进行多条件测试,则必须使用select when语句,其中多个if语句与eeach other一起使用。例如:                                 

这是来自我的高级代码的片段,其中每个循环都有多个for-each循环,并选择when语句来判断XML元素是否是父元素的子元素,通过其标识符是父元素的子元素。我的问题中的示例XML。

使用position函数和XPath谓词条目结合选择何时使用和可以构建复杂XSL的语句,可以将数据库的平面XML表列表重新创建为分层XML表单。

文森特的关键函数答案适用于这个问题的小复杂性,但这个答案包含了关于XPath谓词的答案,所以我认为它与问题的答案更相关。请查看Vincent的答案并考虑使用Key Functions作为解决方案,因为它非常有用