XSL按元素分组xml

时间:2010-10-12 09:48:32

标签: xml xslt

我正在使用XSLT1.0(处理器无法处理2.0)并且在尝试对xml结构的输出进行分组时遇到问题:

<排>
<排序>
<文本>一些订单文本1
< /文本>
  < /排序>
< /排>

<排>
<支付>
<文本>一些付款文本1
< /文本>
  < /支付>
< /排>

<排>
<排序>
<文本>一些订单文本2
< /文本>
< /排序>
< /排>

<排>
<接触>
<文本>一些联系方式1 < /文本>
< /接触>
< /排>

<排>
<接触>
<文本>一些联系方式2
< /文本>
< /接触>
< /排>

今天我们选择所有行并为每个行调用apply模板(每个类型都有自己的模板写出它的主体),这样就创建了一个输出:

订单:一些订单text1
订单:一些订单text2
付款方式:一些付款文本1 联系方式:部分联系方式1 联系方式:部分联系方式2

但我想要的是(在XSLT 1.0中)将输出分组以便:

顺序

  1. 某些订单text1
  2. 某些订单text2
  3. 付款

    1. 一些付款文字1
    2. 联系

      1. 一些联系方式1
      2. 一些联系方式2
      3. 显然,此处涉及的订单,付款和联系等许多其他元素类型,因此选择显式元素名称不是解决方案。

        修改

        Ty,一些很好的答案,如果我有一个结构说明,Muenchian分组解决方案将如何改变

        <customers>
          <person>
            <row>....</row> (row is same as above)
            <row>....</row>
          </person>
          <person>
            <row>....</row>
            <row>....</row>
            <row>....</row>
          </person>
        

        然后是关键:

          <xsl:key name="type" match="row/*" use="local-name()"/>
        

        将选择所有人的所有行,这不是我想要的。感谢您的回复。

4 个答案:

答案 0 :(得分:4)

在XSLT 1.0中执行此操作需要使用Muenchian grouping,但在XSLT 2.0中使用xsl:for-each-group更容易(在我看来)。

以下XSLT 1.0样式表将按您的要求执行,关键是使用一个键(doh!),它允许您对节点本地名称进行分组。

<强>输入:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <row>
    <order>
      <text>some order text 1</text>
    </order>
  </row>

  <row>
    <payment>
      <text>some payment text 1</text>
    </payment>
  </row>

  <row>
    <order>
      <text>some order text 2</text>
    </order>
  </row>

  <row>
    <contact>
      <text>some contact details 1</text>
    </contact>
  </row>

  <row>
    <contact>
      <text>some contact details 2</text>
    </contact>
  </row>
</root>

<强> XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="text"/>
  <xsl:key name="type" match="row/*" use="local-name()"/>

  <xsl:template match="root">
    <xsl:for-each select="row/*[
      generate-id() = generate-id(key('type', local-name())[1])]">
      <xsl:value-of select="local-name()"/>
      <xsl:text>&#x0a;</xsl:text>
      <xsl:for-each select="key('type', local-name())">
        <xsl:value-of select="concat('  ', position(), '. ')"/>
        <xsl:apply-templates select="text"/>
        <xsl:text>&#x0a;</xsl:text>
      </xsl:for-each>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

<强>输出:

order
  1. some order text 1
  2. some order text 2
payment
  1. some payment text 1
contact
  1. some contact details 1
  2. some contact details 2

答案 1 :(得分:1)

尝试:

<xsl:template match="(parent element-whatever contains the 'row' elements)">
  <xsl:apply-templates>
    <xsl:sort select="name(*)" />
  </xsl:apply-templates>
</xsl:template>

这会按第一个子项的名称对行元素进行排序。

此模板添加标题:

<xsl:template match="row">
    <xsl:copy>
    <xsl:if test="not(preceding-sibling::*[name(*) = name(current()/*)])">
      <!-- Output header here -->
      <xsl:value-of select="name(*)" />
    </xsl:if>
    <xsl:apply-templates select="@* | node()"/>
  </xsl:copy>
</xsl:template>

测试基本上是说'如果之前没有同名的兄弟姐妹那就输出这个'。

答案 2 :(得分:1)

以@Flynn的回答为基础......

如果您有父模板(未在示例中显示):

<xsl:template match="row-parent">
  <xsl:apply-templates select="row">
    <xsl:sort select="name(*[1])" />
  </xsl:apply-templates>
</xsl:template>

请注意,通过选择“row”而不是默认值(所有子节点,包括文本节点),我们避免选择包含空格的文本节点,这对于我们的输出是不合需要的。

然后,为了添加章节标题,处理子句的模板使用条件来查看这是否是其部分的第一行:

<xsl:template match="row">
   <xsl:variable name="childName" select="name(*[1])"/>
   <!-- if this is the first row with an element child of this name -->
   <xsl:if test="not(preceding-sibling::row[name(*[1]) = $childName])">
      <xsl:value-of select="concat('&#10;',
         translate(substring($childName, 1, 1), $lower, $upper),
         substring($childName, 2), '&#10;&#10;')"/>
   </xsl:if>

然后输出该组每行的数据,并使用您想要的格式:

   <xsl:number level="any" count="row[name(*[1]) = $childName]" format=" 1. "
      from="row-parent"/>
   <xsl:value-of select="normalize-space(*[1])"/>
   <xsl:text>&#10;</xsl:text>
</xsl:template>

像往常一样,$ lower和$ upper在模板(或样式表)的顶部定义为

<xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>

并使样式表使用'text'输出方法:

<xsl:output method="text"/>

输入上的上述样式表的输出(在<row-parent>包装器中)是:

Contact

 1. some contact details 1
 2. some contact details 2

Order

 1. some order text 1
 2. some order text 2

Payment

 1. some payment text 1

或者,更健壮,您可以使用Muenchian grouping:首先按子元素名称对行进行分组,然后(输出每个组的标题并处理)组中的所有行。

答案 3 :(得分:1)

除了分组方法的好答案,这个样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:variable name="vSort" select="'|order|payment|contact|'"/>
    <xsl:template match="/">
        <xsl:apply-templates select="*/row">
            <xsl:sort select="string-length(
                                 substring-before($vSort,
                                                  concat('|',
                                                         name(),
                                                         '|')))"/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="row/*">
        <xsl:variable name="vName" select="name()"/>
        <xsl:variable name="vNumber">
            <xsl:number level="any" count="*[name()=$vName]" from="/"/>
        </xsl:variable>
        <xsl:if test="$vNumber = 1">
            <xsl:value-of select="concat(translate(substring(name(),1,1),
                                                       'opc',
                                                       'OPC'),
                                             substring(name(),2),
                                             '&#xA;')"/>
        </xsl:if>
        <xsl:value-of select="concat($vNumber,'. ',text,'&#xA;')"/>
    </xsl:template>
</xsl:stylesheet>

输出(输入格式良好):

Order
1.  some order text 1
Payment
1.  some payment text 1
2.  some order text 2
Contact
1.  some contact details 1
2.  some contact details 2