1

当我从 SQL Server 查询数据 AS XML 时,它通常会生成重复的 XML 节点。通常我可以调整查询以消除这种情况,但并非总是如此。对于那些我不能的时候,我最终得到了这样的 XML:

<Xml>
<House houseId="3" address="123 Main">
    <Dog dogId="13" name="Rover">
        <Flea fleaId="17" name="Chester" />
    </Dog>
    <Dog dogId="13" name="Rover">
        <Flea fleaId="23" name="Poindexter" />            
    </Dog>
</House>
<House houseId="3" address="123 Main">
    <Human humanId="9" name="Mr. Johnson">
        <Child childId="11" name="Susie" />
    </Human>
    <Human humanId="9" name="Mr. Johnson">
        <Child childId="31" name="Sandy" />
    </Human>
</House>
<House houseId="5" address="987 Wall">
    <Dog dogId="13" name="Rover">
        <Flea fleaId="17" name="Chester" />
    </Dog>
    <Dog dogId="13" name="Rover">
        <Flea fleaId="19" name="Wilhelm" />            
    </Dog>
</House>
</Xml>

请注意,有两个彼此相邻的 <House> 节点,它们的属性相同。它们仅在子节点上有所不同。我正在尝试创建一个 XSLT,它将采用相同的兄弟节点,并将它们折叠成一个包含所有子节点超集的节点。在示例中,<House houseId="3"> 将包含 <Dog> 和 <Human> 节点。像这样:

<Xml>
<House houseId="3" address="123 Main">
    <Dog dogId="13" name="Rover">
        <Flea fleaId="17" name="Chester" />
        <Flea fleaId="23" name="Poindexter" />            
    </Dog>
    <Human humanId="9" name="Mr. Johnson">
        <Child childId="11" name="Susie" />
        <Child childId="31" name="Sandy" />
    </Human>
</House>
<House houseId="5" address="987 Wall">
    <Dog dogId="13" name="Rover">
        <Flea fleaId="17" name="Chester" />
        <Flea fleaId="19" name="Wilhelm" />            
    </Dog>
</House>
</Xml>

不仅合并了两个相同的 House 节点,还合并了重复的 Dog 和 Human 节点。但请注意,在两个不同的 <House> 节点下列出的 <Dog dogId='13' name='Rover'> 节点并未合并,因为它们不相同。(由于他们的祖先。)这就是我想要的:组合匹配的兄弟节点。

因为 XML 是由 SQL 生成的,所以 XSLT 将处理许多不同名称和排列的节点。因此,我无法对节点名称进行硬编码。但我可以放心,每个节点都会有一个相应的 id 属性,该属性将包含一个数值。例如:<House houseId="3">、<Dog dogId="17"> 和 <Flea fleaId="13">。
我也知道根节点没有属性,所以我可以开始处理根节点的子节点。

我的策略是为每个节点创建一个 xsl:key,其中节点的键值是其祖先节点与 id 值的串联。示例键值在下面的评论中

<Xml>
<House houseId="3" address="123 Main"><!--"houseId=3"-->
    <Dog dogId="13" name="Rover" ><!--"houseId=3;dogId=13"-->
        <Flea fleaId="17" name="Chester" /><!--"houseId=3;dogId=13;fleaId=17"-->
    </Dog>
    <Dog dogId="13" name="Rover" ><!--"houseId=3;dogId=13"-->
        <Flea fleaId="23" name="Poindexter" /><!--"houseId=3;dogId=13;fleaId=23"-->         
    </Dog>
</House>
<House houseId="3" address="123 Main" ><!--"houseId=3"-->
    <Human humanId="9" name="Mr. Johnson" ><!--"houseId=3;humanId=9"-->
        <Child childId="11" name="Susie" /><!--"houseId=3;humanId=9;childId=11"-->
    </Human>
    <Human humanId="9" name="Mr. Johnson"><!--"houseId=3;humanId=9"-->
        <Child childId="31" name="Sandy" /><!--"houseId=3;humanId=9;childId=31"-->
    </Human>
</House>
<House houseId="5" address="987 Wall" ><!--"houseId=5"-->
    <Dog dogId="13" name="Rover"><!--"houseId=5;dogId=13"-->
        <Flea fleaId="17" name="Chester" /><!--"houseId=5;dogId=13;fleaId=17"-->
    </Dog>
    <Dog dogId="13" name="Rover"><!--"houseId=5;dogId=13"-->
        <Flea fleaId="19" name="Wilhelm" /><!--"houseId=5;dogId=13;fleaId=19"-->           
    </Dog>
</House>
</Xml>

因此,两个看似匹配的 <Dog dogId='13' name='Rover'> 将通过它们的祖先来区分:

<Xml><House houseId="3"><Dog dogId='13' name='Rover'>

houseId=3;dogId=13

对比

<Xml><House houseId="5"><Dog dogId='13' name='Rover'>

houseId=5;dogId=13

这样,可以组合重复(兄弟)节点。不幸的是,我很难理解如何使用 XSL 和 xslt:key 来实现它。任何帮助将不胜感激。

4

1 回答 1

1

这种转变

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:my="my:my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

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

 <xsl:template match="/*" priority="3">
  <xsl:sequence select="my:grouping(., *)"/>
 </xsl:template>

 <xsl:function name="my:grouping" as="element()*">
  <xsl:param name="pElem" as="element()"/>
  <xsl:param name="pChildren" as="element()*"/>

  <xsl:element name="{name($pElem)}" namespace="{namespace-uri($pElem)}">
   <xsl:apply-templates select="$pElem/@*"/>
   <xsl:for-each-group select="$pChildren" group-by="my:signature(.)">
     <xsl:copy>
       <xsl:apply-templates select="@*|node()[not(self::*)]"/>
       <xsl:apply-templates select=
           "my:grouping(., current-group()/*)/*"/>
     </xsl:copy>
   </xsl:for-each-group>
  </xsl:element>
 </xsl:function>

 <xsl:function name="my:signature" as="xs:string">
  <xsl:param name="pElem" as="element()"/>

  <xsl:variable name="vAttibs" as="xs:string*">
   <xsl:perform-sort select="$pElem/@*">
     <xsl:sort select="name()"/>
   </xsl:perform-sort>
  </xsl:variable>
  <xsl:sequence select=
   "string-join((name($pElem)
                 ,for $at in $vAttibs
                   return concat($at, '+', $pElem/@*[name()=$at])
                 )
                  ,'|')"/>
 </xsl:function>
</xsl:stylesheet>

应用于提供的 XML 文档时:

<Xml>
    <House houseId="3" address="123 Main">
        <Dog dogId="13" name="Rover">
            <Flea fleaId="17" name="Chester" />
        </Dog>
        <Dog dogId="13" name="Rover">
            <Flea fleaId="23" name="Poindexter" />
        </Dog>
    </House>
    <House houseId="3" address="123 Main">
        <Human humanId="9" name="Mr. Johnson">
            <Child childId="11" name="Susie" />
        </Human>
        <Human humanId="9" name="Mr. Johnson">
            <Child childId="31" name="Sandy" />
        </Human>
    </House>
    <House houseId="5" address="987 Wall">
        <Dog dogId="13" name="Rover">
            <Flea fleaId="17" name="Chester" />
        </Dog>
        <Dog dogId="13" name="Rover">
            <Flea fleaId="19" name="Wilhelm" />
        </Dog>
    </House>
</Xml>

产生想要的正确结果:

<Xml>
   <House houseId="3" address="123 Main">
      <Dog dogId="13" name="Rover">
         <Flea fleaId="17" name="Chester"/>
         <Flea fleaId="23" name="Poindexter"/>
      </Dog>
      <Human humanId="9" name="Mr. Johnson">
         <Child childId="11" name="Susie"/>
         <Child childId="31" name="Sandy"/>
      </Human>
   </House>
   <House houseId="5" address="987 Wall">
      <Dog dogId="13" name="Rover">
         <Flea fleaId="17" name="Chester"/>
         <Flea fleaId="19" name="Wilhelm"/>
      </Dog>
   </House>
</Xml>

并使用此扩展 XML 文档(添加了文本节点):

<Xml>
    <House houseId="3" address="123 Main">
        <Dog dogId="13" name="Rover">
          Dog named Rover
            <Flea fleaId="17" name="Chester">Regular dog flee</Flea>
        </Dog>
        <Dog dogId="13" name="Rover">
            <Flea fleaId="23" name="Poindexter">Flea named Poindexter</Flea>
        </Dog>
    </House>
    <House houseId="3" address="123 Main">
        <Human humanId="9" name="Mr. Johnson">
            <Child childId="11" name="Susie">Susan Johnson</Child>
        </Human>
        <Human humanId="9" name="Mr. Johnson">
            <Child childId="31" name="Sandy">Sandy Johnson</Child>
        </Human>
    </House>
    <House houseId="5" address="987 Wall">
        <Dog dogId="13" name="Rover">
            <Flea fleaId="17" name="Chester" />
        </Dog>
        <Dog dogId="13" name="Rover">
            <Flea fleaId="19" name="Wilhelm" />
        </Dog>
    </House>
</Xml>

再次产生正确的结果

<Xml>
   <House houseId="3" address="123 Main">
      <Dog dogId="13" name="Rover">
          Dog named Rover
            <Flea fleaId="17" name="Chester">Regular dog flee</Flea>
         <Flea fleaId="23" name="Poindexter">Flea named Poindexter</Flea>
      </Dog>
      <Human humanId="9" name="Mr. Johnson">
         <Child childId="11" name="Susie">Susan Johnson</Child>
         <Child childId="31" name="Sandy">Sandy Johnson</Child>
      </Human>
   </House>
   <House houseId="5" address="987 Wall">
      <Dog dogId="13" name="Rover">
         <Flea fleaId="17" name="Chester"/>
         <Flea fleaId="19" name="Wilhelm"/>
      </Dog>
   </House>
</Xml>

说明

我们使用两个函数:my:signature()my:grouping()

  1. my:signature()为每个元素创建一个签名——这是元素名称和所有 attrName+value 对的管道分隔字符串,按 attrName 排序。

  2. my:grouping()用于my:signature()进行正确的分组。它有第二个参数,包含要分组的元素。

于 2013-01-02T03:41:01.307 回答