В этом преобразовании используется мюнхенская группировка с составными ключами :
<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>
Обновление : ОП отредактировал свой вопрос и опубликовал новые требования, согласно которым атрибут address
может начинаться или не начинаться с кода страны. Два адреса, один с кодом страны, а другой без кода страны, являются «одинаковыми», если подстрока после кода страны равна другому адресу. В этом случае два элемента должны быть сгруппированы вместе.
Вот решение (было бы тривиально написать в XSLT 2.0, но в XSLT 1.0 сделать это за один проход довольно сложно. Решение с несколькими проходами более простое, но обычно требует 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
, и это 1
, тогда как 1 div false()
совпадает с 1 div 0
, и это число (положительное) Infinity
.
Также для любой строки $s
значение substring($s, Infinity)
является просто пустой строкой. И, конечно же, для любой строки $s
значение substring($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>