有条件地将子节点移动到属性

时间:2015-02-10 22:59:40

标签: xml xslt xslt-grouping

我正在尝试转换这个文档,但对于xslt来说还是比较新的,并且试图将它做对。核心节点(为简单起见而截断)看起来像这样

<Product prod_id="6352">
    <brandId>221</brandId>
    <brand>Oscar Mayer</brand>
    <images>
       <smallimage>text</simage>
       <medimage>text</medimage>
       <largeimage>text</limage>
    </images>
    <nutrition>
        <nutritionShow>Y</nutritionShow>
        <servingSize>1 SLICE</servingSize>
        <servingsPerContainer>12</servingsPerContainer>
        <totalCalories>60</totalCalories>
        <fatCalories>35</fatCalories>
        <totalFat>4</totalFat>
        <totalFatPercent>6</totalFatPercent>
        <totalFatUnit>g</totalFatUnit>
        <saturatedFat>1.5</saturatedFat>
        <saturatedFatPercent>8</saturatedFatPercent>
        <saturatedFatUnit>g</saturatedFatUnit>
        <transFat>0</transFat>
        <transFatUnit>g</transFatUnit>
        <cholesterolUnit>mg</cholesterolUnit>
    </nutrition>
    <prodId>6352</prodId>
</Product>

最后,我希望逻辑上分组的子节点成为具有适当属性名称的单个节点。

最终结果应如下所示

<Product prod_id="6352">
<brandId>221</brandId>
<brand>Oscar Mayer</brand>
<images>
   <smallimage>text</smallimage>
   <medimage>text</medimage>
   <largeimage>text</largeimage>
</images>
<nutrition>
    <nutritionShow>Y</nutritionShow>
    <servingSize>1 SLICE</servingSize>
    <servingsPerContainer>12</servingsPerContainer>
    <totalCalories>60</totalCalories>
    <fatCalories>35</fatCalories>
    <totalFat amount="4" percent="6" unit="g" />
    <saturatedFat amount="1.5" percent="8" unit="g"/>
    <transFat amount="0" unit="g"</>
</nutrition>
<prodId>6352</prodId>

一些关键功能

  1. 对相似的属性进行分组(注意饱和脂肪和transFat ......略有不同)我有这些集合的离散列表。您可以根据关系使用列表或更动态的内容,但请注意方差。
  2. 留下其他(不可分组的)属性
  3. 忽略缺少金额属性的组/仅具有单位属性(通知胆固醇)
  4. 提前感谢您帮助我理解这一相当复杂的转变。

2 个答案:

答案 0 :(得分:1)

一种可能的解决方案是遵循XSLT:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" indent="yes" />
 <xsl:strip-space elements="*"/>
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
  <xsl:template match="nutrition/*">
    <xsl:variable name="cName" select="name()"/>
    <xsl:choose>
      <xsl:when test="following-sibling::node()[name()=concat($cName,'Unit')]">
        <xsl:copy>
          <xsl:attribute name="amount">
            <xsl:value-of select="."/>
          </xsl:attribute>
          <xsl:if test="following-sibling::node()[name()=concat($cName,'Percent')]">
            <xsl:attribute name="percent">
              <xsl:value-of select="following-sibling::node()[name()=concat($cName,'Percent')]"/>
            </xsl:attribute>
          </xsl:if>
          <xsl:attribute name="unit">
            <xsl:value-of select="following-sibling::node()[name()=concat($cName,'Unit')]"/>
          </xsl:attribute> 
        </xsl:copy>
      </xsl:when>
      <xsl:when test="contains(name() ,'Unit') or contains(name() ,'Percent')"/>
      <xsl:otherwise>
        <xsl:copy>
          <xsl:apply-templates />
        </xsl:copy>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

当应用于您的输入时,XML会产生输出

<Product prod_id="6352">
  <brandId>221</brandId>
  <brand>Oscar Mayer</brand>
  <images>
    <smallimage>text</smallimage>
    <medimage>text</medimage>
    <largeimage>text</largeimage>
  </images>
  <nutrition>
    <nutritionShow>Y</nutritionShow>
    <servingSize>1 SLICE</servingSize>
    <servingsPerContainer>12</servingsPerContainer>
    <totalCalories>60</totalCalories>
    <fatCalories>35</fatCalories>
    <totalFat amount="4" percent="6" unit="g"></totalFat>
    <saturatedFat amount="1.5" percent="8" unit="g"></saturatedFat>
    <transFat amount="0" unit="g"></transFat>
  </nutrition>
  <prodId>6352</prodId>
</Product>

第一个模板是Identity transform,无需任何更改即可复制所有节点和属性 第二个模板匹配nutrition的所有子元素/节点 如果当前元素具有以下兄弟,其本地名称与当前本地名称匹配,并以Unit结尾

<xsl:when test="following-sibling::node()[name()=concat($cName,'Unit')]">

当前节点必须是包含amount的节点 当前节点的值写为amount属性的值

<xsl:attribute name="amount">
    <xsl:value-of select="."/>
</xsl:attribute>

如果存在匹配Percent的以下兄弟

<xsl:if test="following-sibling::node()[name()=concat($cName,'Percent')]">

相应地写入Percent属性:

<xsl:attribute name="percent">
    <xsl:value-of select="following-sibling::node()[name()=concat($cName,'Percent')]"/>
  </xsl:attribute>

同样适用于Unit,而不事先检查是否存在匹配的Unit(必要时可以添加)。

<xsl:when test="contains(name() ,'Unit') or contains(name() ,'Percent')"/>

删除已作为属性写入的UnitPercent节点以及cholesterolUnit
最后,所有其他不可分组的nutrition元素都被复制:

<xsl:otherwise>
  <xsl:copy>
    <xsl:apply-templates/>
  </xsl:copy>
</xsl:otherwise> 

答案 1 :(得分:0)

继续发表评论......

  

该示例显示了3种类型的属性。其他人都是   人们会期待......你看到饱和脂肪也可以表达   不饱和和单不饱和和多不饱和有5-12个   每个类别。类别为1.金额,单位和百分比2.金额   和单位3.独立

就我个人而言,我更喜欢把它们拼出来,所以对于给定的例子:

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:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>


<!-- category #1: amount, unit and percent -->
<xsl:template match="totalFat">
    <totalFat amount="{.}" percent="{../totalFatPercent}" unit="{../totalFatUnit}" />
</xsl:template>

<xsl:template match="saturatedFat">
    <saturatedFat amount="{.}" percent="{../saturatedFatPercent}" unit="{../saturatedFatUnit}" />
</xsl:template>


<!-- category #2: amount and percent -->
<xsl:template match="transFat">
    <transFat amount="{.}" unit="{../transFatUnit}" />
</xsl:template>


<!-- suppress all units and percents -->
<xsl:template match="totalFatPercent | totalFatUnit | saturatedFatPercent | saturatedFatUnit | transFatUnit | cholesterolUnit | cholesterolPercent"/>

</xsl:stylesheet>

请注意,类别#3由身份转换模板处理,不需要例外。


另请注意,每件产品中已知的商品都不需要自己的模板;您可以在匹配nutrition的模板中将它们作为文字结果元素写出来,并将它们的名称添加到抑制空模板中。