【问题标题】:Dynamic grouping on element and attributes names元素和属性名称的动态分组
【发布时间】:2011-03-19 19:37:37
【问题描述】:

我想在标题下按元素名称(然后按相同的属性名称)对来自 XPath 的结果进行分类。注意:XML 数据可能不一致,某些同名元素可能具有不同的属性,因此它们需要不同的标题。

我似乎无法用语言写出我的问题,所以最好举个例子..

XML:

<pets>  
    <dog name="Frank" cute="yes" color"brown" type="Lab"/>
    <cat name="Fluffy" cute="yes" color="brown"/>
    <cat name="Lucy" cute="no" color="brown"/>
    <dog name="Spot" cute="no" color="brown"/>
    <dog name="Rover" cute="yes" color="brown"/>
    <dog name="Rupert" cute="yes" color="beige" type="Pug"/>
    <cat name="Simba" cute="yes" color="grey"/>
    <cat name="Princess" color="brown"/>

</pets>

XPath:

//*[@color='brown']

输出应该是什么样子(不同元素的标题不同):

ElementName  Color   Cute     Name     Type   
Dog          Brown   Yes      Frank    Lab


ElementName  Color   Cute     Name       
Dog          Brown   No       Spot    
Dog          Brown   Yes      Rover


ElementName  Color   Cute     Name     
Cat          Brown   Yes      Fluffy    
Cat          Brown   No       Lucy



ElementName  Color   Name     
Cat          Brown   Princess  

我目前拥有的 XSL(简化了!):

<xsl:apply-templates select="//*[@color='brown']" mode="result">
    <xsl:sort select="name()" order="ascending"/>
</xsl:apply-templates>


<xsl:template match="@*|node()" mode="result">
    <tr>
        <th align="left">Element</th>

        <xsl:for-each select="@*">
            <xsl:sort select="name()" order="ascending"/>
            <th align="left">
                <xsl:value-of select="name()"/>
            </th>
        </xsl:for-each>
    </tr>

    <tr>
        <td align="left">
            <xsl:value-of select="name()"/>
         </td>
         <xsl:for-each select="@*">
             <xsl:sort select="name()" order="ascending"/>
             <td align="left">
                 <xsl:value-of select="."/>
             </td>
         </xsl:for-each>
     </tr>
</xsl:template>

上面的 XSL 以我想要的方式正确地对它们进行排序.. 但现在我需要某种检查来查看哪些元素具有相同的名称,然后如果它们具有相同的名称,它们是否具有相同的属性。完成此检查后,我可以将一般“标题”放在具有匹配元素名称和属性的记录集上方。

我想我可以使用 xsl:choose xsl:when 并进行一些测试。我在想(在正确排序之后):

If element name != previous element name
    create headings
Else if all attributes != all previous element's attributes
    create headings

我想我最大的问题是,我不知道如何检查以前返回的数据集是什么...有人可以告诉我该怎么做吗?

或者如果我接近这个错误.. 引导我找到更好的解决方案?

希望一切都有意义!如果您需要澄清,请告诉我!

提前感谢您的耐心和回复! :)

【问题讨论】:

  • 好问题! (+1)。请参阅我的答案,了解通用且高效的 XSLT 1.0 解决方案,该解决方案适用于元素和属性的任何可能组合。 :)

标签: xslt sorting xpath grouping


【解决方案1】:

这个样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="ByName-AttNum" match="*/*[@color='brown']" use="concat(name(),'++',count(@*))"/>
    <xsl:template match="/">
        <html>
            <xsl:apply-templates/>
        </html>
    </xsl:template>
    <xsl:template match="*/*[generate-id(.) = generate-id(key('ByName-AttNum',concat(name(),'++',count(@*)))[1])]">
        <table>
            <tr>
                <th>ElementName</th>
                <xsl:apply-templates select="@*" mode="headers">
                    <xsl:sort select="name()"/>
                </xsl:apply-templates>
            </tr>
            <xsl:apply-templates select="key('ByName-AttNum',concat(name(),'++',count(@*)))" mode="list"/>
        </table>
    </xsl:template>
    <xsl:template match="*" mode="list">
        <tr>
            <td>
                <xsl:value-of select="name()"/>
            </td>
            <xsl:apply-templates select="@*" mode="list">
                <xsl:sort select="name()"/>
            </xsl:apply-templates>
        </tr>
    </xsl:template>
    <xsl:template match="@*" mode="headers">
        <th>
            <xsl:value-of select="name()"/>
        </th>
    </xsl:template>
    <xsl:template match="@*" mode="list">
        <td>
            <xsl:value-of select="."/>
        </td>
    </xsl:template>
</xsl:stylesheet>

结果:

<html>
    <table>
        <tr>
            <th>ElementName</th>
            <th>color</th>
            <th>cute</th>
            <th>name</th>
            <th>type</th>
        </tr>
        <tr>
            <td>dog</td>
            <td>brown</td>
            <td>yes</td>
            <td>Frank</td>
            <td>Lab</td>
        </tr>
    </table>
    <table>
        <tr>
            <th>ElementName</th>
            <th>color</th>
            <th>cute</th>
            <th>name</th>
        </tr>
        <tr>
            <td>cat</td>
            <td>brown</td>
            <td>yes</td>
            <td>Fluffy</td>
        </tr>
        <tr>
            <td>cat</td>
            <td>brown</td>
            <td>no</td>
            <td>Lucy</td>
        </tr>
    </table>
    <table>
        <tr>
            <th>ElementName</th>
            <th>color</th>
            <th>cute</th>
            <th>name</th>
        </tr>
        <tr>
            <td>dog</td>
            <td>brown</td>
            <td>no</td>
            <td>Spot</td>
        </tr>
        <tr>
            <td>dog</td>
            <td>brown</td>
            <td>yes</td>
            <td>Rover</td>
        </tr>
    </table>
    <table>
        <tr>
            <th>ElementName</th>
            <th>color</th>
            <th>name</th>
        </tr>
        <tr>
            <td>cat</td>
            <td>brown</td>
            <td>Princess</td>
        </tr>
    </table>
</html>

注意:这假定具有相同数量属性的所有元素也具有相同的属性名称(如您的输入示例中)。

编辑:更好的输出标记。

编辑 2:另一种解决方案:一个包含所有可能属性(如 CSV 模式)的标题和按属性计数和名称排序元素。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="attrByName" match="pets/*/@*" use="name()"/>
    <xsl:variable name="attr" select="/pets/*/@*[count(.|key('attrByName',name())[1])=1]"/>
    <xsl:template match="pets">
        <html>
            <table>
                <tr>
                    <th>ElementName</th>
                    <xsl:apply-templates select="$attr" mode="headers">
                        <xsl:sort select="name()"/>
                    </xsl:apply-templates>
                </tr>
                <xsl:apply-templates select="*[@color='brown']">
                    <xsl:sort select="count(@*)" order="descending"/>
                    <xsl:sort select="name()"/>
                </xsl:apply-templates>
            </table>
        </html>
    </xsl:template>
    <xsl:template match="pets/*">
        <tr>
            <td>
                <xsl:value-of select="name()"/>
            </td>
            <xsl:apply-templates select="$attr" mode="list">
                <xsl:sort select="name()"/>
                <xsl:with-param name="node" select="."/>
            </xsl:apply-templates>
        </tr>
    </xsl:template>
    <xsl:template match="@*" mode="headers">
        <th>
            <xsl:value-of select="name()"/>
        </th>
    </xsl:template>
    <xsl:template match="@*" mode="list">
        <xsl:param name="node"/>
        <td>
            <xsl:value-of select="$node/@*[name()=name(current())]"/>
        </td>
    </xsl:template>
</xsl:stylesheet>

结果:

<html>
    <table>
        <tr>
            <th>ElementName</th>
            <th>color</th>
            <th>cute</th>
            <th>name</th>
            <th>type</th>
        </tr>
        <tr>
            <td>dog</td>
            <td>brown</td>
            <td>yes</td>
            <td>Frank</td>
            <td>Lab</td>
        </tr>
        <tr>
            <td>cat</td>
            <td>brown</td>
            <td>yes</td>
            <td>Fluffy</td>
            <td></td>
        </tr>
        <tr>
            <td>cat</td>
            <td>brown</td>
            <td>no</td>
            <td>Lucy</td>
            <td></td>
        </tr>
        <tr>
            <td>dog</td>
            <td>brown</td>
            <td>no</td>
            <td>Spot</td>
            <td></td>
        </tr>
        <tr>
            <td>dog</td>
            <td>brown</td>
            <td>yes</td>
            <td>Rover</td>
            <td></td>
        </tr>
        <tr>
            <td>cat</td>
            <td>brown</td>
            <td></td>
            <td>Princess</td>
            <td></td>
        </tr>
    </table>
</html>

注意:这会在树中运行两次,但没有扩展。没有扩展的所需输出的精确匹配需要模仿这样的键机制:遍历树,将新键(元素名称加上属性名称)添加到参数,然后再次为每个键通过树过滤节点运行(可能是为非匹配元素保留节点集的一些优化......)。最坏的情况(每个具有不同键的节点)将通过一个节点:N(用于密钥构建)+(N + 1)* N / 2

【讨论】:

  • 查看没有任何假设的解决方案:)
【解决方案2】:

这种转换不会对具有相同数量属性的集合做出任何假设 -- 根本没有假设

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common"
 exclude-result-prefixes="ext">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:key name="kAnimalByProperties" match="animal"
  use="concat(@atype, .)"/>

 <xsl:variable name="vrtfNewDoc">
  <xsl:apply-templates select="/pets/*">
    <xsl:sort select="name()"/>
  </xsl:apply-templates>
 </xsl:variable>

 <xsl:template match="pets/*">
   <animal atype="{name()}">
     <xsl:copy-of select="@*"/>
     <xsl:for-each select="@*">
       <xsl:sort select="name()"/>
         <attrib>|<xsl:value-of select="name()"/>|</attrib>
     </xsl:for-each>
   </animal>
 </xsl:template>

 <xsl:template match="/">
   <xsl:for-each select="ext:node-set($vrtfNewDoc)">
     <xsl:for-each select=
     "*[generate-id()
       =generate-id(key('kAnimalByProperties',
                        concat(@atype, .)
                        )[1]
                    )
       ]">
        <table border="1">
          <tr>
            <td>Element Name</td>
            <xsl:for-each select="*">
              <td><xsl:value-of select="translate(.,'|','')"/></td>
            </xsl:for-each>
          </tr>
          <xsl:for-each select=
          "key('kAnimalByProperties', concat(@atype, .))">
            <xsl:variable name="vcurAnimal" select="."/>
            <tr>
              <td><xsl:value-of select="@atype"/></td>
              <xsl:for-each select="*">
                <td>
                  <xsl:value-of select=
                   "$vcurAnimal/@*[name()=translate(current(),'|','')]"/>
                </td>
              </xsl:for-each>
            </tr>
          </xsl:for-each>
        </table>
        <p/>
     </xsl:for-each>
   </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

应用于提供的 XML 文档时

<pets>
    <dog name="Frank" cute="yes" color="brown" type="Lab"/>
    <cat name="Fluffy" cute="yes" color="brown"/>
    <cat name="Lucy" cute="no" color="brown"/>
    <dog name="Spot" cute="no" color="brown"/>
    <dog name="Rover" cute="yes" color="brown"/>
    <dog name="Rupert" cute="yes" color="beige" type="Pug"/>
    <cat name="Simba" cute="yes" color="grey"/>
    <cat name="Princess" color="brown"/>
</pets>

产生想要的正确结果

<table border="1">
   <tr>
      <td>Element Name</td>
      <td>color</td>
      <td>cute</td>
      <td>name</td>
   </tr>
   <tr>
      <td>cat</td>
      <td>brown</td>
      <td>yes</td>
      <td>Fluffy</td>
   </tr>
   <tr>
      <td>cat</td>
      <td>brown</td>
      <td>no</td>
      <td>Lucy</td>
   </tr>
   <tr>
      <td>cat</td>
      <td>grey</td>
      <td>yes</td>
      <td>Simba</td>
   </tr>
</table>
<p/>
<table border="1">
   <tr>
      <td>Element Name</td>
      <td>color</td>
      <td>name</td>
   </tr>
   <tr>
      <td>cat</td>
      <td>brown</td>
      <td>Princess</td>
   </tr>
</table>
<p/>
<table border="1">
   <tr>
      <td>Element Name</td>
      <td>color</td>
      <td>cute</td>
      <td>name</td>
      <td>type</td>
   </tr>
   <tr>
      <td>dog</td>
      <td>brown</td>
      <td>yes</td>
      <td>Frank</td>
      <td>Lab</td>
   </tr>
   <tr>
      <td>dog</td>
      <td>beige</td>
      <td>yes</td>
      <td>Rupert</td>
      <td>Pug</td>
   </tr>
</table>
<p/>
<table border="1">
   <tr>
      <td>Element Name</td>
      <td>color</td>
      <td>cute</td>
      <td>name</td>
   </tr>
   <tr>
      <td>dog</td>
      <td>brown</td>
      <td>no</td>
      <td>Spot</td>
   </tr>
   <tr>
      <td>dog</td>
      <td>brown</td>
      <td>yes</td>
      <td>Rover</td>
   </tr>
</table>
<p/>

【讨论】:

  • @Dimitre:是的。这不会通过带有扩展功能的两遍转换做出任何假设。我认为可以通过生成一个标题(按名称将属性分组为 CVS 解决方案)来涵盖“引导我找到更好的解决方案”部分。另外,我认为您的输出标记更好!编辑我的。
  • @Alejandro:如果您使用 CSV,那么按顺序获取每个属性名称将非常困难——稍后当您需要时。
  • @Dimitre - 我需要调查原因,但我只得到标题,下面没有值(只显示元素的名称)。我将其复制并粘贴为新文件。我暂时无法研究它..希望今天的某个时候。 :S
  • @iHeartGreek:您使用的 XSLT 处理器可能没有实现 EXSLT。在这种情况下,使用它自己的 xxx:node-set() 扩展函数。例如,对于 MSXML,此扩展所属的命名空间为:“urn:schemas-microsoft-com:xslt”
  • @Dimitre:是的,我相信处理器没有实现 EXSLT。不过,我不确定您的上述评论对于我需要做些什么才能使其正常工作的真正含义.. :(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-06
  • 1970-01-01
  • 2010-10-13
  • 2021-12-30
  • 2012-08-21
相关资源
最近更新 更多