【问题标题】:XSLT 1.0 to sort and group an XMLXSLT 1.0 对 XML 进行排序和分组
【发布时间】:2016-01-08 03:54:34
【问题描述】:

我需要一个可以转换以下 XML 的 XSLT 1.0。

<Record>
  <Row>
    <Name>AAA</Name>
    <Surname>Surname1</Surname>
  </Row>
  <Row>
    <Name>BBB</Name>
    <Surname>Surname2</Surname>
  </Row>
  <Row>
    <Name>CCC</Name>
    <Surname>Surname1</Surname>
  </Row>
  <Row>
    <Name>DDD</Name>
    <Surname>Surname2</Surname>
  </Row>
  <Row>
    <Name>EEE</Name>
    <Surname>Surname1</Surname>
  </Row>
  <Row>
    <Name>FFF</Name>
    <Surname>Surname2</Surname>
  </Row>
  <Row>
    <Name>GGG</Name>
    <Surname>Surname1</Surname>
  </Row>
  <Row>
    <Name>HHH</Name>
    <Surname>Surname2</Surname>
  </Row>
</Record>

我期望的输出是:

<Output>
  <Recordset1>
    <Record>
      <Name>AAA</Name>
      <Surname>Surname1</Surname>
    </Record>
    <Record>
      <Name>CCC</Name>
      <Surname>Surname1</Surname>
    </Record>
  </Recordset1>
  <Recordset1>
    <Record>
      <Name>EEE</Name>
      <Surname>Surname1</Surname>
    </Record>
    <Record>
      <Name>GGG</Name>
      <Surname>Surname1</Surname>
    </Record>
  </Recordset1>
  <Recordset2>
    <Record>
      <Name>BBB</Name>
      <Surname>Surname2</Surname>
    </Record>
    <Record>
      <Name>DDD</Name>
      <Surname>Surname2</Surname>
    </Record>
  </Recordset2>
  <Recordset2>
    <Record>
      <Name>FFF</Name>
      <Surname>Surname2</Surname>
    </Record>
    <Record>
      <Name>HHH</Name>
      <Surname>Surname2</Surname>
    </Record>
  </Recordset2>
</Output>

条件是:

  1. Recordset1 应包含 Surname1 节点

  2. Recordset2 应包含 Surname2 节点

  3. 输出应按姓氏排序

  4. 每个记录集最多有 2 条记录。

【问题讨论】:

    标签: xml sorting xslt xslt-1.0 grouping


    【解决方案1】:

    这有一些技巧:

    1. 选择不同的姓氏。
    2. 对姓氏记录进行分组。
    3. 将姓氏记录分成 2 个一组。
    4. 根据不同的姓氏索引创建动态元素名称。

    这个样式表解决了这些问题:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
      <xsl:template match="/Record">
        <Output>
          <xsl:apply-templates select="//Row[not(./Surname=preceding::Surname)]/Surname" mode="group">
            <xsl:sort select="Surname" />
          </xsl:apply-templates>
        </Output>
      </xsl:template>
    
      <xsl:template match="Surname" mode="group">
        <xsl:variable name="surname" select="./text()" />
        <xsl:variable name="surnameIndex" select="position()" />
        <xsl:variable name="surnameGroup" select="/Record/Row[Surname/text()=$surname]" />
        <xsl:for-each select="$surnameGroup">
          <xsl:variable name="index" select="position()" />
          <!-- only create a new record set on the first (or only) member of the output pair -->
          <xsl:if test="$index mod 2=1"> <!-- positions are 1 based, not 0 based -->
            <xsl:element name="Recordset{$surnameIndex}">
              <!-- alternatively, use xsl:copy-of -->
              <xsl:apply-templates select="$surnameGroup[$index]" mode="record" />
              <xsl:apply-templates select="$surnameGroup[$index+1]" mode="record" />
            </xsl:element>
          </xsl:if>
        </xsl:for-each>
      </xsl:template>
    
      <xsl:template match="Row" mode="record">
        <Record>
          <Name><xsl:value-of select="Name" /></Name>
          <Surname><xsl:value-of select="Surname" /></Surname>
        </Record>
      </xsl:template>
    
    </xsl:stylesheet>
    

    将此样式表应用于您的输入文档的输出是:

    <?xml version="1.0" encoding="UTF-8" ?>
    <Output>
      <Recordset1>
        <Record>
          <Name>AAA</Name>
          <Surname>Surname1</Surname>
        </Record>
        <Record>
          <Name>CCC</Name>
          <Surname>Surname1</Surname>
        </Record>
      </Recordset1>
      <Recordset1>
        <Record>
          <Name>EEE</Name>
          <Surname>Surname1</Surname>
        </Record>
        <Record>
          <Name>GGG</Name>
          <Surname>Surname1</Surname>
        </Record>
      </Recordset1>
      <Recordset2>
        <Record>
          <Name>BBB</Name>
          <Surname>Surname2</Surname>
        </Record>
        <Record>
          <Name>DDD</Name>
          <Surname>Surname2</Surname>
        </Record>
      </Recordset2>
      <Recordset2>
        <Record>
          <Name>FFF</Name>
          <Surname>Surname2</Surname>
        </Record>
        <Record>
          <Name>HHH</Name>
          <Surname>Surname2</Surname>
        </Record>
      </Recordset2>
    </Output>
    

    【讨论】:

    • 这不是一个好的答案 - 在这里查看原因:jenitennison.com/xslt/grouping/muenchian.html
    • @michael.hor257k 你是对的,这是一个很好的答案,因为它解决了给定的问题,同时划分了@Roland 遇到的不同问题。如果您担心使用 preceding 来确定不同值的效率太低,那么可以在第一个模板中轻松替换另一种方法。
    【解决方案2】:

    这可能会对你有所帮助。

    <xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxml="urn:schemas-microsoft-com:xslt"
    exclude-result-prefixes="msxml">
    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    
    <xsl:key match="Row" name="rows" use="Surname"/>
    
    <xsl:template match="Record">
      <Output>
        <xsl:for-each select="Row[generate-id(.) = generate-id(key('rows', Surname)[1])]">
          <xsl:sort select="Surname" data-type="text" order="ascending"/>
    
          <xsl:variable name="Recordset" select="concat('Recordset',position())"/>
    
          <xsl:variable name="Rows">
            <xsl:copy-of select="key('rows', Surname)"/>
          </xsl:variable>
    
          <xsl:variable name="RowList" select="msxml:node-set($Rows)"/>
    
          <xsl:for-each select="$RowList/Row[position() mod 2 = 1]">
            <xsl:element name="{$Recordset}">
              <Record>
                <xsl:copy-of select="./*"/>
              </Record>
              <xsl:if test="following-sibling::Row[1]/*">
                <Record>
                  <xsl:copy-of select="following-sibling::Row[1]/*"/>
                </Record>
              </xsl:if>
            </xsl:element>
          </xsl:for-each>
    
        </xsl:for-each>
      </Output>
    </xsl:template>
    </xsl:stylesheet>
    

    【讨论】:

    • 你觉得这到底是怎么回事:&lt;xsl:for-each select="Row[. = key('rows', Surname)[1]]"&gt;?
    • 这是您之前向我指出的 Muenchian 方法。我只是将它的 for-each 部分简化如下:对于每个 Row Surname,找到具有该姓氏的第一个 Row 实例。所以,如果当前行是第一个实例,那么做一些事情。好问题。顺便说一句,这段代码运行良好。
    • "为每个 Row Surname 找到具有该姓氏的第一个 Row 实例。" 这不是您的“简化”所做的。它查找 整个文本值 等于具有该姓氏的第一个 Row 实例的文本值的每一行。这很容易导致重复。
    • 我在 for-each 中添加了 generate-id。不会有潜在的问题吧。还有其他改进建议吗?
    • 我没有阅读超过这一点。这个问题是在 2016 年 1 月提出的,OP 已经一年多没有访问了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-16
    • 2013-03-07
    • 2014-06-10
    • 1970-01-01
    • 2016-01-25
    • 2023-02-10
    • 1970-01-01
    相关资源
    最近更新 更多