【问题标题】:Combining elements from 2 lists in XSLT在 XSLT 中组合来自 2 个列表的元素
【发布时间】:2010-12-13 09:27:31
【问题描述】:

假设我的 XML 看起来像这样(并假设我无法更改此 XML 的格式):

<biscuit>
 <name>Hobnobs</name>
 <price>1.49</price>
 <name>Digestives</name>
 <price>89.00</price>
</biscuit>


 <biscuitInfo name="Hobnobs">
  <nutritionalValue>
     <fat>6 grams</fat>
     <sugar>lots</sugar>
   </nutritionalValue>      
  </biscuitInfo>
  <biscuitInfo name="Digestives">
   <nutritionalValue>
     <fat>3 grams</fat>
     <sugar>5 grams</sugar>
   </nutritionalValue>
 </biscuitInfo>

我想使用 XSLT 将其变成如下所示:

<biscuit>
     <name>Hobnobs</name>
     <price>1.49</price>
     <fat>6 grams</fat>
     <sugar>lots</sugar>
</biscuit>

我将如何在 XSLT 中做这样的事情?我可以遍历第一个饼干列表(名称和价格)并从第二个列表(营养价值)中提取元素吗?

我对 XSL 不太了解,因此欢迎任何建议。

干杯,

法学博士。

【问题讨论】:

    标签: xml xslt


    【解决方案1】:

    两个例子。此样式表使用 clasic 全递归:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:key name="kNutChildByBisName" match="nutritionalValue/*"
                 use="../../@name"/>
        <xsl:key name="kElemByPrecedingName" match="biscuit/*[not(self::name)]"
                 use="preceding-sibling::name[1]"/>
        <xsl:template match="node()|@*" name="identity">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*"/>
            </xsl:copy>
        </xsl:template>
        <xsl:template match="name" mode="group">
            <bisquit>
                <xsl:apply-templates select=".|key('kNutChildByBisName',.)|
                                             key('kElemByPrecedingName',.)"/>
            </bisquit>
        </xsl:template>
        <xsl:template match="biscuit">
            <xsl:apply-templates mode="group"/>
        </xsl:template>
        <xsl:template match="biscuitInfo"/>
        <xsl:template match="node()" mode="group"/>
    </xsl:stylesheet>
    

    而且这个样式表使用细粒度遍历:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:strip-space elements="*"/>
        <xsl:key name="kNutByBisName" match="nutritionalValue"
                     use="../@name"/>
        <xsl:template match="node()|@*" name="identity">
            <xsl:copy>
                <xsl:apply-templates select="node()[1]|@*"/>
            </xsl:copy>
            <xsl:apply-templates select="following-sibling::node()[1]"/>
        </xsl:template>
        <xsl:template match="biscuitInfo"/>
        <xsl:template match="biscuit">
            <xsl:apply-templates select="node()[1]|following-sibling::node()[1]"/>
        </xsl:template>
        <xsl:template match="name[1]" name="group">
            <bisquit>
                <xsl:call-template name="identity"/>
                <xsl:apply-templates select="key('kNutByBisName',.)/node()[1]"/>
            </bisquit>
            <xsl:apply-templates select="following-sibling::name[1]" mode="group"/>
        </xsl:template>
        <xsl:template match="name"/>
        <xsl:template match="name" mode="group">
            <xsl:call-template name="group"/>
        </xsl:template>
    </xsl:stylesheet>
    

    有了这个输入:

    <root>
        <biscuit>
            <name>Hobnobs</name>
            <price>1.49</price>
            <name>Digestives</name>
            <price>89.00</price>
        </biscuit>
        <biscuitInfo name="Hobnobs">
            <nutritionalValue>
                <fat>6 grams</fat>
                <sugar>lots</sugar>
            </nutritionalValue>
        </biscuitInfo>
        <biscuitInfo name="Digestives">
            <nutritionalValue>
                <fat>3 grams</fat>
                <sugar>5 grams</sugar>
            </nutritionalValue>
        </biscuitInfo>
    </root>
    

    两个输出:

    <root>
        <bisquit>
            <name>Hobnobs</name>
            <price>1.49</price>
            <fat>6 grams</fat>
            <sugar>lots</sugar>
        </bisquit>
        <bisquit>
            <name>Digestives</name>
            <price>89.00</price>
            <fat>3 grams</fat>
            <sugar>5 grams</sugar>
        </bisquit>
    </root>
    

    注意:您正在执行两项任务:分组和交叉引用。

    编辑:更好的细粒度遍历,以防组中只有name

    【讨论】:

    • 这里没有冒犯,但我真的很感兴趣,为什么要在某些情况下使用身份转换,它会生成更多代码并且恕我直言,可读性较差?
    • @Flack:重用和语义:分组只是用apply-templates而不是copy-of分组,那么你也可以转换这些元素。此外,唯一的假设是:bisquit 孩子被分组,组以 name 开始。除此之外,模式可以在不更改样式表的情况下增长。
    • @Alejandro +1 谢谢。我明白你的意思。由于 90% 的时间使用 xml-html 转换,这可能只是我的坏习惯。
    【解决方案2】:

    几个模板覆盖的传统身份规则。没有键,没有细粒度遍历:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>
    
     <xsl:template match="node()|@*" name="identity">
      <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
     </xsl:template>
    
     <xsl:template match="biscuit/name">
      <biscuit>
        <xsl:call-template name="identity"/>
        <xsl:apply-templates select=
        "following-sibling::price[1]
        |
          ../../biscuitInfo[@name = current()]/*"/>
      </biscuit>
     </xsl:template>
    
     <xsl:template match="biscuit">
      <xsl:apply-templates select="name"/>
     </xsl:template>
    
     <xsl:template match="nutritionalValue">
       <xsl:apply-templates/>
     </xsl:template>
     <xsl:template match="biscuitInfo"/>
    </xsl:stylesheet>
    

    在此源 XML 上应用时(本质上是提供的,但包装在单个顶部元素中):

    <product>
        <biscuit>
            <name>Hobnobs</name>
            <price>1.49</price>
            <name>Digestives</name>
            <price>89.00</price>
        </biscuit>
        <biscuitInfo name="Hobnobs">
            <nutritionalValue>
                <fat>6 grams</fat>
                <sugar>lots</sugar>
            </nutritionalValue>
        </biscuitInfo>
        <biscuitInfo name="Digestives">
            <nutritionalValue>
                <fat>3 grams</fat>
                <sugar>5 grams</sugar>
            </nutritionalValue>
        </biscuitInfo>
    </product>
    

    产生想要的正确结果

    <product>
       <biscuit>
          <name>Hobnobs</name>
          <price>1.49</price>
          <fat>6 grams</fat>
          <sugar>lots</sugar>
       </biscuit>
       <biscuit>
          <name>Digestives</name>
          <price>89.00</price>
          <fat>3 grams</fat>
          <sugar>5 grams</sugar>
       </biscuit>
    </product>
    

    请注意

    1. 使用和覆盖标识规则是基本的 XSLT 设计模式,它的使用有助于编写几乎任何转换。

    2. 使用&lt;xsl:apply-templates/&gt; 代替&lt;xsl:apply-templates select="node()[1]"/&gt; 允许并行执行,这在不久的将来可能会变得越来越重要。

    3. 键可以用作优化,但对于像提供的那样的小型 XML 文档,这不是必需的,并且键的使用与本文的中心思想无关解决这个问题。

    【讨论】:

      【解决方案3】:

      您可以做的是在第二部分使用 XPath。在第一个列表上执行循环,一旦有了名称,就可以使用 XPath 在第二个列表中查询您想要的确切内容。

      【讨论】:

      • 这是否需要在 xslt 中创建一个变量来表示循环当前关注的值?或者我可以直接在 Xpath 查询中使用它吗?
      • 我认为这两个选项都有效。创建一个变量会给你更短的表达式,但我认为你可以直接在 xpath 查询中使用你的值。
      • 太好了,谢谢。我不认为您有任何显示这种技术的好链接吗?只是为了救我挣扎。干杯。
      【解决方案4】:

      这个 XSLT:

      <?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" indent="yes"/>
      
      <xsl:key name="bisquit-info" match="/root/biscuitInfo" use="@name"/>
      
      <xsl:template match="root">
          <xsl:copy>
              <xsl:apply-templates select="biscuit/name"/>
          </xsl:copy>
      </xsl:template>
      
      <xsl:template match="name">
          <bisquit>
              <xsl:copy-of select="."/>
              <xsl:copy-of select="following-sibling::price[1]"/>
              <xsl:copy-of select="key('bisquit-info', .)/nutritionalValue/*"/>
          </bisquit>
      </xsl:template> 
      </xsl:stylesheet>
      

      使用这样的 XML 输入(只是添加了根节点以确保格式正确)

      <root>
      <biscuit>
          <name>Hobnobs</name>
          <price>1.49</price>
          <name>Digestives</name>
          <price>89.00</price>
      </biscuit>
      
      <biscuitInfo name="Hobnobs">
          <nutritionalValue>
          <fat>6 grams</fat>
          <sugar>lots</sugar>
          </nutritionalValue>      
      </biscuitInfo>
      <biscuitInfo name="Digestives">
          <nutritionalValue>
          <fat>3 grams</fat>
          <sugar>5 grams</sugar>
          </nutritionalValue>
      </biscuitInfo>
      </root>
      

      将提供此结果:

      <root>
      <bisquit>
          <name>Hobnobs</name>
          <price>1.49</price>
          <fat>6 grams</fat>
          <sugar>lots</sugar>
      </bisquit>
      <bisquit>
          <name>Digestives</name>
          <price>89.00</price>
          <fat>3 grams</fat>
          <sugar>5 grams</sugar>
      </bisquit>
      </root>
      

      设计并不理想,但这会奏效。我使用了键,假设输入树可以很大。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-10-29
        • 1970-01-01
        • 2020-10-26
        • 1970-01-01
        • 1970-01-01
        • 2021-09-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多