2

我在对 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 来更新这些值。

任何指针或建议?

4

1 回答 1

1

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

这真是一个不错的挑战!让我熬夜到凌晨 4:00。

以下 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>

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

于 2013-10-26T02:14:37.643 回答