XSLT - 棘手的转型

时间:2013-09-26 15:04:56

标签: xml xslt

我遇到了XML片段的XSLT转换问题。源XML看起来像这样:

<XXX>
    <Name>Sample</Name>
    <MMM>
        <AAA ID="A"/>
        <MMM>
            <BBB ID="B"/>
            <MMM>
                <AA ID="C"/>
                <BB ID="D"/>
            </MMM>
        </MMM>
    </MMM>
</XXX>

但它需要转变为:

<XXX>
    <Name>Sample</Name>
    <MMM>
        <MMM>
            <MMM>
                <AAA ID="A"/>
                <BBB ID="B"/>
            </MMM>
            <AA ID="C"/>            
        </MMM>
        <BB ID="D"/>
    </MMM>
</XXX>

规则很简单,MMM元素只能有两个子元素节点。如果这些节点中只有一个碰巧是另一个MMM,则需要占据第一个位置。

使用代码很容易,但这些XML片段是SQL数据库中XML列的值,我想使用SQL和XSLT来更新这些值。

任何指针或建议?

1 个答案:

答案 0 :(得分:1)

解决此问题的一种方法是首先提取MMM树,将另一方面其他节点提取到单独的结构中,然后再将它们合并。

这真是一个很好的挑战!让我熬夜到凌晨4点。

以下XSLT(差不多!见下文)完成了这项工作:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common" version="1.0" exclude-result-prefixes="exslt">
   <xsl:output method="xml" encoding="ISO-8859-1" />
   <!-- handling of MMM extraction -->
   <xsl:template match="MMM" mode="extract_mmm">
      <MMM>
         <xsl:apply-templates mode="extract_mmm" />
      </MMM>
   </xsl:template>
   <xsl:template match="*" mode="extract_mmm" />
   <!-- handling of extraction of other nodes -->
   <xsl:template match="MMM" mode="extract_other">
      <xsl:apply-templates mode="extract_other" />
   </xsl:template>
   <xsl:template match="*" mode="extract_other">
      <xsl:copy-of select="." />
   </xsl:template>
   <!-- handling of merging the two partial result sets -->
   <xsl:template match="MMM" mode="dump">
      <xsl:param name="others" />
      <xsl:choose>
         <!-- this handles the case of an MMM being a leaf node -->
         <xsl:when test="count(MMM) = 0">
            <xsl:variable name="nodes_in_next_sibling" select="2*count(following-sibling::MMM)" />
            <MMM>
               <xsl:copy-of select="$others[count($others) - $nodes_in_next_sibling - 1]" />
               <xsl:copy-of select="$others[count($others) - $nodes_in_next_sibling]" />
            </MMM>
         </xsl:when>
         <!-- this handles the case of an inner MMM with a sibling -->
         <xsl:when test="count(../MMM) = 2">
            <xsl:variable name="free_positions_in_second_child" select="count(MMM[position() = 2 and count(MMM) = 0])*2 + count(MMM[2]//MMM[count(MMM) = 0])*2 + count(MMM[position() = 2 and count(MMM) = 1]) + count(MMM[2]//MMM[count(MMM) = 1])" />
            <MMM>
               <xsl:apply-templates mode="dump" select="MMM[1]">
                  <xsl:with-param name="others" select="$others[position() &lt; count($others)- $free_positions_in_second_child + 1]" />
               </xsl:apply-templates>
               <xsl:apply-templates mode="dump" select="MMM[2]">
                  <xsl:with-param name="others" select="$others[position() &gt;= count($others) - $free_positions_in_second_child + 1]" />
               </xsl:apply-templates>
            </MMM>
         </xsl:when>
         <!-- this handles the case of an inner MMM without sibling -->
         <xsl:when test="count(../MMM) = 1">
            <MMM>
               <xsl:apply-templates mode="dump">
                  <xsl:with-param name="others" select="$others[position() &lt; count($others)]" />
               </xsl:apply-templates>
            </MMM>
            <xsl:copy-of select="$others[position() = count($others)]" />
         </xsl:when>
      </xsl:choose>
   </xsl:template>
   <xsl:template match="XXX">
      <XXX>
         <xsl:copy-of select="Name" />
         <xsl:variable name="mmm_structure">
            <xsl:apply-templates mode="extract_mmm" select="MMM" />
         </xsl:variable>
         <xsl:variable name="other_structure_tmp">
            <xsl:apply-templates mode="extract_other" select="MMM" />
         </xsl:variable>
         <!-- http://stackoverflow.com/questions/4610921/how-to-concatenate-two-node-sets-such-that-order-is-respected -->
         <!-- http://www.exslt.org/exsl/ -->
         <xsl:variable name="other_structure" select="exslt:node-set($other_structure_tmp/*)" />
         <xsl:apply-templates select="$mmm_structure" mode="dump">
            <xsl:with-param name="others" select="$other_structure" />
         </xsl:apply-templates>
      </XXX>
   </xsl:template>
</xsl:stylesheet>

注意:

  • 这仍然是带有EXSLT扩展的XSLT 1.0。
  • 还处理了具有两个孩子的
  • MMM个节点。放置其他节点的规则是从底部到顶部填充位置。
  • 在此版本中,其他节点尽可能向下移动,而不会违反2个子最大规则。因此,输出并不完全与问题中的显示相同,而是如下所示。

这是输出:

<?xml version="1.0" encoding="ISO-8859-1"?>
<XXX>
  <Name>Sample</Name>
  <MMM>
    <MMM>
      <MMM>
        <AAA ID="A"/>
        <AAA ID="B"/>
      </MMM>
    </MMM>
    <BBB ID="C"/>
  </MMM>
  <BBB ID="D"/>
</XXX>

如果这是一个问题,请告诉我。