使用XSLT删除XML文件中的重复元素

使用XSLT删除XML文件中的重复元素
lookbook 翻译  (参与分:276,专家分:850)   发表:2003-4-16 下午9:42   更新:2003-4-17 上午9:49   版本:1.1   阅读:2458

[pre]    当进行XML数据转换的时候,我们经常会碰到XML数据文件中含有重复的元素。在这封技
术邮件中,我们将讨论一种解决该问题的方法。
问题:

    让我们先来看一下具体的问题描述。假使有如下的一个XML数据文件,它包含了如下的内容:[/pre]
<Order>
  <Item number="1">
    <SKU>12345</SKU>
    <Description>Standard Widget</Description>
  </Item>
  <Item number="2">
    <SKU>54321</SKU>
    <Description>Turbo Widget</Description>
  </Item>
  <Item number="3">
    <SKU>12345</SKU>
    <Description>Standard Widget</Description>
  </Item>
</Order> 

[pre]    在上面的XML数据文件中,每个Item元素都是单独显示的,请注意Item number1和Item number3
中的数据是一样的。但是,需求目标要求输出的XML数据文件中每个SKU元素个体不能重复,
并且要加上一个新的<Quantity>元素以显示每个Item元素数据的个数。需求目标的输出文件如下:[/pre]
<Order>
  <Item>
    <Quantity>2</Quantity>
    <SKU>12345</SKU>
    <Description>Standard Widget</Description>
  </Item>
  <Item>
    <Quantity>1</Quantity>
    <SKU>54321</SKU>
    <Description>Turbo Widget</Description>
  </Item>
</Order>

解决方法:

[pre]    提出的问题实际上有两个问题需要解决。第一,需要去除重复的SKU#12345元素个体(entry);
第二,需要提供一个新的<Quantity>元素以显示每个Item元素数据的个数。为了解决这些问
题,我们得使用一些XSLT的高级特性。
    为了解决第一个问题,我们将使用XSLT的following操作。following和preceding操作
分别指示在一个for-each循环中的以后和以前节点(node)。following操作判断以后节点
如果和当前节点一样,则去除当前重复的节点。
    为了解决第二个问题,我们需要得到每个Item元素的个数。幸运的是,XSLT提供计数(count)
功能。使用计数功能,我们可以对XML数据文件中出现的每个Item元素进行计数,并将这个
数值赋值给新建的<Quantity>元素。[/pre]
去除重复的元素个体:

[pre]    去除重复的元素个体需要一些小技巧。首先,将选择的节点放入一个for-each循环中,
但是这个循环中的select属性值需要一些小技巧。通常的做法,你会将所有的Item放入for-each
循环中,如下:[/pre]
<xsl:for-each select="//Order/Item">
. . .
</xsl:for-each> 

[pre]    但是,我们需要每个SKU元素数据都唯一。为了能达到这种转换,我们得在select的属
性值中加入额外的信息。这个额外的信息将会告诉转换处理器只对以后节点和当前节点不同
的当前节点取值。举个小例子,如果第一个节点是A,下一个节点是A,那么就忽略第一个节
点;如果第一个节点是A,下一个节点是A,再下一个节点是B,那么循环之后对第二个节点
取值,第一个节点会被忽略。下面是该方法在XML的表达式:[/pre]
<xsl:for-each select="//SKU[not(.=following::SKU)]">
. . .
</xsl:for-each> 

[pre]    在上面的XML表达式中,select的属性值决定了怎样循环取值选择的节点数据。它使得
我们只对以后节点和当前节点(用.表示)不同的当前节点取值。[/pre]
计数:

[pre]    需要对每个SKU元素进行计数并把它赋值给新建的<Quantity>元素,同样需要一些小技
巧。我们可以使用XSLT的计数(count)功能,但难题是告诉转换处理器需要对那些元素计数。
一个对所有SKU元素计数的简单例子如下:[/pre]
<xsl:value-of select="count(//SKU)"/> 

[pre]    上面的XML表达式中,只是简单的对所有符合//SKU模式的元素进行计数。但是,我们需
要的是对满足特殊条件的SKU元素计数。技巧之处在于满足特殊条件的SKU元素值在每个for-each
循环中可以得到,并且是用点号(.)标识。那么解决计数问题的关键就是计数(count)功
能也需要使用点号(.)标识。所以,我们可以在每个for-each循环中,使用一个新变量,
如下所示:[/pre]
<xsl:variable name="thesku" select="."/> 

[pre]    接着,我们就可以使用计数(count)功能来对每个SKU元素进行计数了,如下所示:[/pre]
<Quantity><xsl:value-of select="count(//SKU[.=$thesku])"/></Quantity> 

完整的解决方法:

[pre]    现在,我们可以把以上所述的所有内容合并起来,得到该问题的完整解决方法。下面的
完整代码使用select属性来对SKU进行唯一性选择;而<Quantity>元素是使用计数XSLT的(count)
功能得到的;最后,<Description>元素的值是从原XML数据文件取值而来。[/pre]
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <Order>
      <xsl:for-each select="//SKU[not(.=following::SKU)]">
        <xsl:variable name="thesku" select="."/>
        <Item>
          <Quantity><xsl:value-of select="count(//SKU[.=$thesku])"/></Quantity>
          <SKU><xsl:value-of select="." /></SKU>
          <Description><xsl:value-of select="../Description"/></Description>
        </Item>
      </xsl:for-each>
    </Order>
  </xsl:template>
</xsl:stylesheet> 

[pre]编者注释:由于技术错误,在先前的XML技术邮件"Tokenizing strings with Xalan-Java" 
(March 20, 2002)中,里面的代码有一个错误,特此更正,完整正确的代码如下:[/pre]
To use the tokenize function, we'll create a template that calls it, like the following: 
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="http://xml.apache.org/xalan">
  <xsl:template match="/">
    <xsl:for-each select="//CustomerAddress">
<Address><xsl:value-of select="Address1"/></Address>
<City><xsl:value-of select="xalan:tokenize(Address2, ' ,')[1]"/></City>
<State><xsl:value-of select="xalan:tokenize(Address2, ' ,')[2]"/></State>
<Zip><xsl:value-of select="xalan:tokenize(Address2, ' ,')[3]"/></Zip>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet> 

posted on 2004-09-10 20:15  笨笨  阅读(3406)  评论(0编辑  收藏  举报

导航