2

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

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

我要解决的问题是每个表可以有多个行,这些行作为同名的兄弟节点输出到 XML。任何表中都可能有无限行,因此我尝试使用带有表名 XPATH 的 for-each 来处理每一行。这可行,但是当我尝试将文档函数与 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 和发布问题的新手。

4

3 回答 3

2

目的是从使用 DBunit 的数据库导出的平面 XML 重新创建分层 XML。关联可以由 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父项和子项中的值。

于 2014-05-14T11:44:19.110 回答
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>
于 2014-05-14T09:06:44.220 回答
-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 个键功能。如果您希望进行多条件测试,则必须使用选择 when 语句,其中多个 if 语句相互连接。例如:

这是我的高级代码中的一个片段,它在彼此内部有多个 for-each 循环,并选择 when 语句通过其标识符确定 XML 元素是否是父元素的子元素,这些标识符是示例 XML 中父元素的子元素我的问题。

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

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

于 2014-05-22T14:29:29.587 回答