使用xml和xslt中的多个属性进行分组

时间:2011-09-14 01:36:04

标签: xml xslt xslt-1.0

我有以下xml

<smses>
  <sms address="87654321" type="1" body="Some text" readable_date="3/09/2011 2:16:52 PM" contact_name="Person1" />
  <sms address="87654321" type="2" body="Some text" readable_date="3/09/2011 2:36:41 PM" contact_name="Person1" />
  <sms address="87654321" type="1" body="Some text" readable_date="3/09/2011 2:16:52 PM" contact_name="Person1" />
  <sms address="123" type="2" body="Some text" readable_date="3/09/2011 10:56:24 AM" contact_name="Person2" />
  <sms address="123" type="1" body="Some text" readable_date="3/09/2011 10:57:52 AM" contact_name="Person2" />
  <sms address="123" type="2" body="Some text" readable_date="3/09/2011 10:56:24 AM" contact_name="Person2" />
  <sms address="12345678" type="1" body="Some text" readable_date="3/09/2011 11:21:16 AM" contact_name="Person3" />
  <sms address="12345678" type="2" body="Some text" readable_date="3/09/2011 11:37:21 AM" contact_name="Person3" />

  <sms address="12345" type="2" body="Some text" readable_date="28/01/2011 7:24:50 PM" contact_name="(Unknown)" />
  <sms address="233" type="1" body="Some text" readable_date="30/12/2010 1:13:41 PM" contact_name="(Unknown)" />
</smses>

我想获得这样的输出(例如xml)

<sms contact_name="person1">
    <message type="1">{@body}</message>
    <message type="2">{@body}</message>
    <message type="1">{@body}</message>
</sms>
<sms contact_name="person2">
    <message type="2">{@body}</message>
    <message type="1">{@body}</message>
</sms>
<sms contact_name="person3">
    <message type="2">{@body}</message>
    <message type="1">{@body}</message>
</sms>
<sms contact_name="(Unknown)">
    <message type="2">{@body}</message>
    <message type="1">{@body}</message>
</sms>
<sms contact_name="(Unknown)">
    <message type="2">{@body}</message>   
</sms>

e.g。 HTML

<div>
  <h1>Person: @contact_name (@address)</h1>
  <p>message @type: @body</p>
</div>

我已经设法使用以下XSLT代码执行此操作(请原谅下面的代码并不完全反映html,输出是所需的结果!)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes" />
    <xsl:key name="txt" match="sms" use="@contact_name" />
    <xsl:template match="smses">
        <xsl:apply-templates select="sms[generate-id(.)=generate-id(key('txt', @contact_name)[1])]">
            <xsl:sort select="@address" order="ascending" />
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="sms">
        <h4><xsl:value-of select="@contact_name"  /></h4>
            <xsl:for-each select="key('txt', @contact_name)">
                    <br />
                    <xsl:value-of select="@body" />
            </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>

我遇到的问题是,或者更确切地说是我要问的问题。我有一个带有@contact_name属性的sms元素是“(未知)”,但@address在两个元素之间是唯一的,即它们不应该组合在一起,因为sms消息来自不同的数字/ person(即使联系人姓名相同,也无关紧要)。 我是否应该尝试重新排序/更改XML数据,或者是否有办法让XSLT识别未知的组,如果@address相同,则应检查@contact_name是否不同。

编辑:

我没有提及(或者更确切地说忘记)虽然有一些短信具有相同的@contact_name和唯一@address,但也有一些@address字段存在轻微差异的情况他们没有国家代码在数字前面,例如

<sms contact_name="jared" address="12345" />
<sms contact_name="jared" address="+64112345" />

但是他们的意思是分组,因为他们 来自同一个人/号码。

编辑:

在我的情况下,只有3个字符(例如+64)国家代码加2位数网络代码(例如21)的差异。基本上结果应该是,如果@contact_name =相同且@address完全不同 即。

 <sms contact_name="jared" address="12345" />
 <sms contact_name="jared" address="5433467" />

然后它们应该是单独的元素,因为它们来自不同的人/数字。

如果@contact_name =相同且@address仅因国家/地区和网络代码而异 即。

 <sms contact_name="jared" address="02112345" />
 <sms contact_name="jared" address="+642112345" />

然后应将它们分组,因为它们来自同一个人/数字

编辑:

国家/地区代码:+64(3个字符)

网络代码:021(3个字符,通常是最后一个字符根据网络而变化)

数字(@address)按{+ 1}}保存为+ 64-21-12345(不包括短划线)或021-12345(不包括破折号)。

1 个答案:

答案 0 :(得分:10)

此转换使用具有复合键的Muenchian分组

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

 <xsl:key name="kContactByNameAddress" match="sms"
          use="concat(@contact_name,'+',@address)"/>

 <xsl:template match=
    "sms[generate-id()
        =
         generate-id(key('kContactByNameAddress',
                         concat(@contact_name,'+',@address)
                        )
                         [1]
                     )
        ]
    ">
     <sms contact_name="{@contact_name}">
       <xsl:apply-templates mode="inGroup"
       select="key('kContactByNameAddress',
                 concat(@contact_name,'+',@address)
                )"/>
     </sms>
 </xsl:template>

 <xsl:template match="sms" mode="inGroup">
       <message type="{@type}">
         <xsl:value-of select="@body"/>
       </message>
 </xsl:template>
 <xsl:template match="sms"/>
</xsl:stylesheet>

应用于提供的XML文档

<smses>
    <sms address="87654321" type="1" body="Some text"
    readable_date="3/09/2011 2:16:52 PM" contact_name="Person1" />
    <sms address="87654321" type="2" body="Some text"
    readable_date="3/09/2011 2:36:41 PM" contact_name="Person1" />
    <sms address="87654321" type="1" body="Some text"
    readable_date="3/09/2011 2:16:52 PM" contact_name="Person1" />
    <sms address="123" type="2" body="Some text"
    readable_date="3/09/2011 10:56:24 AM" contact_name="Person2" />
    <sms address="123" type="1" body="Some text"
    readable_date="3/09/2011 10:57:52 AM" contact_name="Person2" />
    <sms address="123" type="2" body="Some text"
    readable_date="3/09/2011 10:56:24 AM" contact_name="Person2" />
    <sms address="12345678" type="1" body="Some text"
    readable_date="3/09/2011 11:21:16 AM" contact_name="Person3" />
    <sms address="12345678" type="2" body="Some text"
    readable_date="3/09/2011 11:37:21 AM" contact_name="Person3" />
    <sms address="12345" type="2" body="Some text"
    readable_date="28/01/2011 7:24:50 PM" contact_name="(Unknown)" />
    <sms address="233" type="1" body="Some text"
    readable_date="30/12/2010 1:13:41 PM" contact_name="(Unknown)" />
</smses>

产生了想要的正确结果

<sms contact_name="Person1">
   <message type="1">Some text</message>
   <message type="2">Some text</message>
   <message type="1">Some text</message>
</sms>
<sms contact_name="Person2">
   <message type="2">Some text</message>
   <message type="1">Some text</message>
   <message type="2">Some text</message>
</sms>
<sms contact_name="Person3">
   <message type="1">Some text</message>
   <message type="2">Some text</message>
</sms>
<sms contact_name="(Unknown)">
   <message type="2">Some text</message>
</sms>
<sms contact_name="(Unknown)">
   <message type="1">Some text</message>
</sms>

更新:OP已编辑了他的问题,并发布了address属性可能或可能不以国家/地区代码开头的新要求。如果国家代码之后的子字符串等于另一个地址,则两个地址,一个具有contry代码,另一个没有国家代码,“相同”。在这种情况下,这两个元素应该组合在一起。

这是解决方案(在XSLT 2.0中写入是微不足道的,但在XSLT 1.0中一次通过这样做非常棘手.Anultipass解决方案更容易,但一般来说需要xxx:node-set()扩展功能,因此会失去可移植性):

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

 <xsl:key name="kContactByNameAddress" match="sms"
  use="concat(@contact_name,'+',
              concat(substring(@address,
                               4 div starts-with(@address,'+')),
                     substring(@address,
                               1 div not(starts-with(@address,'+'))
                              )
                     )
              )"/>

 <xsl:template match=
    "sms[generate-id()
        =
         generate-id(key('kContactByNameAddress',
                         concat(@contact_name,'+',
                                concat(substring(@address,
                                                 4 div starts-with(@address,'+')),
                                       substring(@address,
                                                 1 div not(starts-with(@address,'+'))
                                                 )
                                       )
                                 )
                         )
                         [1]
                     )
        ]
    ">
     <sms contact_name="{@contact_name}">
       <xsl:apply-templates mode="inGroup"
       select="key('kContactByNameAddress',
                 concat(@contact_name,'+',
                        concat(substring(@address,
                                         4 div starts-with(@address,'+')),
                               substring(@address,
                                         1 div not(starts-with(@address,'+'))
                                         )
                                )
                        )
                  )
      "/>
     </sms>
 </xsl:template>

 <xsl:template match="sms" mode="inGroup">
       <message type="{@type}">
         <xsl:value-of select="@body"/>
       </message>
 </xsl:template>
 <xsl:template match="sms"/>
</xsl:stylesheet>

将此转换应用于以下XML文档(前一个+添加了三个sms元素contact_name="Jared",其中两个具有“相同”的地址,新发布的规则):

<smses>
    <sms address="87654321" type="1" body="Some text"
        readable_date="3/09/2011 2:16:52 PM" contact_name="Person1" />
    <sms address="87654321" type="2" body="Some text"
        readable_date="3/09/2011 2:36:41 PM" contact_name="Person1" />
    <sms address="87654321" type="1" body="Some text"
        readable_date="3/09/2011 2:16:52 PM" contact_name="Person1" />
    <sms address="123" type="2" body="Some text"
        readable_date="3/09/2011 10:56:24 AM" contact_name="Person2" />
    <sms address="123" type="1" body="Some text"
        readable_date="3/09/2011 10:57:52 AM" contact_name="Person2" />
    <sms address="123" type="2" body="Some text"
        readable_date="3/09/2011 10:56:24 AM" contact_name="Person2" />
    <sms address="12345678" type="1" body="Some text"
        readable_date="3/09/2011 11:21:16 AM" contact_name="Person3" />
  <sms contact_name="jared" address="12345" type="2" body="Some text"/>
  <sms contact_name="jared" address="56789" type="1" body="Some text"/>
  <sms contact_name="jared" address="+6412345" type="2" body="Some text"/>
    <sms address="12345678" type="2" body="Some text"
        readable_date="3/09/2011 11:37:21 AM" contact_name="Person3" />
    <sms address="12345" type="2" body="Some text"
        readable_date="28/01/2011 7:24:50 PM" contact_name="(Unknown)" />
    <sms address="233" type="1" body="Some text"
        readable_date="30/12/2010 1:13:41 PM" contact_name="(Unknown)" />
</smses>

产生了想要的正确结果

<sms contact_name="Person1">
   <message type="1">Some text</message>
   <message type="2">Some text</message>
   <message type="1">Some text</message>
</sms>
<sms contact_name="Person2">
   <message type="2">Some text</message>
   <message type="1">Some text</message>
   <message type="2">Some text</message>
</sms>
<sms contact_name="Person3">
   <message type="1">Some text</message>
   <message type="2">Some text</message>
</sms>
<sms contact_name="jared">
   <message type="2">Some text</message>
   <message type="2">Some text</message>
</sms>
<sms contact_name="jared">
   <message type="1">Some text</message>
</sms>
<sms contact_name="(Unknown)">
   <message type="2">Some text</message>
</sms>
<sms contact_name="(Unknown)">
   <message type="1">Some text</message>
</sms>

详细解释

这个问题的主要困难来自于XPath 1.0中没有“if ... then ... else”运算符,但是我们必须指定一个XPath表达式。 use指令的xsl:key属性,选择address属性(当它不以“+”开头)或其子串在国家代码后(如果其字符串值开始)用“+”)。

我在这里使用这个穷人的实施

if($condition)
  then $string1
  else $string2

以下XPath表达式在评估时等同于上面的

concat(substring($string1, 1 div $condition),
       substring($string2, 1 div not($condition))
      )

这种等同性来自1 div true()1 div 1相同且11 div false()1 div 0相同的事实是数字(正数)Infinity

此外,对于任何字符串$ssubstring($s, Infinity)的值只是空字符串。当然,对于任何字符串$ssubstring($s, 1)的值只是字符串$s本身。

<强> II。 XSLT 2.0解决方案:

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

 <xsl:template match="/*">
  <xsl:for-each-group select="sms" group-by=
   "concat(@contact_name,'+',
           if(starts-with(@address,'+'))
             then substring(@address, 4)
             else @address
           )">
     <sms contact_name="{@contact_name}">
      <xsl:apply-templates select="current-group()"/>
     </sms>

  </xsl:for-each-group>
 </xsl:template>

 <xsl:template match="sms">
       <message type="{@type}">
         <xsl:value-of select="@body"/>
       </message>
 </xsl:template>
</xsl:stylesheet>

当这个(更简单!)XSLT 2.0转换应用于同一个XML文档(上图)时,会生成相同的正确输出

<sms contact_name="Person1">
   <message type="1">Some text</message>
   <message type="2">Some text</message>
   <message type="1">Some text</message>
</sms>
<sms contact_name="Person2">
   <message type="2">Some text</message>
   <message type="1">Some text</message>
   <message type="2">Some text</message>
</sms>
<sms contact_name="Person3">
   <message type="1">Some text</message>
   <message type="2">Some text</message>
</sms>
<sms contact_name="jared">
   <message type="2">Some text</message>
   <message type="2">Some text</message>
</sms>
<sms contact_name="jared">
   <message type="1">Some text</message>
</sms>
<sms contact_name="(Unknown)">
   <message type="2">Some text</message>
</sms>
<sms contact_name="(Unknown)">
   <message type="1">Some text</message>
</sms>