我需要通过BizTalk处理一段可怕的XML,我已设法将其规范化为下面的示例。我不是XSLT忍者,但是在网络和VS2010调试器之间,我可以找到解决XSL问题的方法。
我现在需要一个聪明的XSLT来“清除”重复的元素,只保留最新的元素,由 ValidFromDate 属性中的日期决定。
ValidFromDate属性属于XSD:日期类型。
<SomeData>
<A ValidFromDate="2011-12-01">A_1</A>
<A ValidFromDate="2012-01-19">A_2</A>
<B CalidFromDate="2011-12-03">B_1</B>
<B ValidFromDate="2012-01-17">B_2</B>
<B ValidFromDate="2012-01-19">B_3</B>
<C ValidFromDate="2012-01-20">C_1</C>
<C ValidFromDate="2011-01-20">C_2</C>
</SomeData>
转型后,我只想保留这些内容:
<SomeData>
<A ValidFromDate="2012-01-19">A_2</A>
<B ValidFromDate="2012-01-19">B_3</B>
<C ValidFromDate="2012-01-20">C_1</C>
</SomeData>
关于如何将XSL放在一起的任何线索?我已经清空互联网试图找到一个解决方案,我已经尝试了很多聪明的XSL排序脚本,但没有一个让我觉得把我拉向了正确的方向。
答案 0 :(得分:3)
Xslt 1.0解决这个问题的最佳方法是使用Muenchian分组。 (鉴于元素已经按ValidFromDate属性排序),以下样式表应该可以解决这个问题:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="element-key" match="/SomeData/*" use="name()" />
<xsl:template match="/SomeData">
<xsl:copy>
<xsl:for-each select="*[generate-id() = generate-id(key('element-key', name()))]">
<xsl:copy-of select="(. | following-sibling::*[name() = name(current())])[last()]" />
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
以下是我针对您的示例Xml运行时获得的结果:
<?xml version="1.0" encoding="utf-8"?>
<SomeData>
<A ValidFromDate="2012-01-19">A_2</A>
<B ValidFromDate="2012-01-19">B_3</B>
<C ValidFromDate="2011-01-20">C_2</C>
</SomeData>
答案 1 :(得分:2)
以下样式表生成正确的结果,而不依赖于输入顺序:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="byName" match="/SomeData/*" use="name()"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="SomeData">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:for-each select="*[generate-id()=
generate-id(key('byName', name())[1])]">
<xsl:apply-templates select="key('byName', name())" mode="out">
<xsl:sort select="translate(@ValidFromDate, '-', '')"
data-type="number" order="descending"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="SomeData/*" mode="out">
<xsl:if test="position()=1">
<xsl:apply-templates select="."/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
输出:
<SomeData>
<A ValidFromDate="2012-01-19">A_2</A>
<B ValidFromDate="2012-01-19">B_3</B>
<C ValidFromDate="2012-01-20">C_1</C>
</SomeData>
请注意,结果略微与您列出的所需输出不同,因为C_1
实际上是最新的C
元素(即输入是不已经排序)。通过依赖初始排序顺序(并盲目地遵循列出的预期输出),现有答案实际上是不正确的。
<强>解释强>
xsl:key
/SomeData/*
所有name()
分组for-each
@ValidFromDate
选择每个组中的第一个项目答案 2 :(得分:2)
基于@ValidFromDate
订单:
XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="k" match="*" use="name()"/>
<xsl:template match="SomeData">
<xsl:copy>
<xsl:apply-templates select="*[generate-id() =
generate-id(key('k', name()))]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:apply-templates select="key('k', name())" mode="a">
<xsl:sort select="@ValidFromDate" order="descending"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*" mode="a">
<xsl:if test="position() = 1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
申请:
<SomeData>
<A ValidFromDate="2011-12-01">A_1</A>
<A ValidFromDate="2012-01-19">A_2</A>
<B CalidFromDate="2011-12-03">B_1</B>
<B ValidFromDate="2012-01-17">B_2</B>
<B ValidFromDate="2012-01-19">B_3</B>
<C ValidFromDate="2012-01-20">C_1</C>
<C ValidFromDate="2011-01-20">C_2</C>
</SomeData>
产生
<SomeData>
<A ValidFromDate="2012-01-19">A_2</A>
<B ValidFromDate="2012-01-19">B_3</B>
<C ValidFromDate="2012-01-20">C_1</C>
</SomeData>
答案 3 :(得分:2)
比@lwburk 更简单,更简洁的XSLT 1.0解决方案:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kName" match="*/*" use="name()"/>
<xsl:template match="/">
<xsl:apply-templates select=
"*/*[generate-id()
=
generate-id(key('kName', name())[1])
]
"/>
</xsl:template>
<xsl:template match="*/*">
<xsl:for-each select="key('kName', name())">
<xsl:sort select="@ValidFromDate" order="descending"/>
<xsl:if test="position() = 1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
将此转换应用于提供的XML文档:
<SomeData>
<A ValidFromDate="2011-12-01">A_1</A>
<A ValidFromDate="2012-01-19">A_2</A>
<B CalidFromDate="2011-12-03">B_1</B>
<B ValidFromDate="2012-01-17">B_2</B>
<B ValidFromDate="2012-01-19">B_3</B>
<C ValidFromDate="2012-01-20">C_1</C>
<C ValidFromDate="2011-01-20">C_2</C>
</SomeData>
产生了想要的正确结果:
<A ValidFromDate="2012-01-19">A_2</A>
<B ValidFromDate="2012-01-19">B_3</B>
<C ValidFromDate="2012-01-20">C_1</C>
答案 4 :(得分:1)
基于Pawel's answer,我做了以下修改,产生了相同的结果:
<xsl:template match="/SomeData">
<xsl:copy>
<xsl:copy-of select="*[generate-id() = generate-id(key('element-key', name())[last()])]"/>
</xsl:copy>
</xsl:template>
如果他们每次产生相同的结果,我喜欢这个,因为它有点清洁。
答案 5 :(得分:1)
XSLT 2.0解决方案,不依赖于输入顺序。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<SomeData>
<xsl:for-each-group select="/SomeData/*" group-by="name()">
<xsl:for-each select="current-group()">
<xsl:sort select="number(substring(attribute(),1,4))" order="descending" data-type="number"/> <!-- year-->
<xsl:sort select="number(substring(attribute(),6,2))" order="descending" data-type="number"/> <!-- month-->
<xsl:sort select="number(substring(attribute(),9,2))" order="descending" data-type="number"/> <!-- date-->
<xsl:if test="position()=1">
<xsl:sequence select="."/>
</xsl:if>
</xsl:for-each>
</xsl:for-each-group>
</SomeData>
</xsl:template>
</xsl:stylesheet>