Xml 按每个属性分组的xslt
我有多种类型的xml消息需要“压缩”,方法是将多个节点分组到同一个父节点下(同一个父节点意味着它们共享同一个节点名,并且声明的每个属性也相等)。例如:Xml 按每个属性分组的xslt,xml,xslt,composite-key,xslt-grouping,Xml,Xslt,Composite Key,Xslt Grouping,我有多种类型的xml消息需要“压缩”,方法是将多个节点分组到同一个父节点下(同一个父节点意味着它们共享同一个节点名,并且声明的每个属性也相等)。例如: <TopLevel CodeTL="Something"> <Ratings> <Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012"> <RatingByNumber Code="X" Rating=
<TopLevel CodeTL="Something">
<Ratings>
<Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
<RatingByNumber Code="X" Rating="10" Number="1">
<RatingByNumber Code="X" Rating="19" Number="2">
</Rating>
</Ratings>
</TopLevel>
<TopLevel CodeTL="Something">
<Ratings>
<Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1">
<RatingByNumber Code="X" Rating="19" Number="2">
</Rating>
</Ratings>
</TopLevel>
<TopLevel CodeTL="Something">
<Ratings>
<Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1">
<RatingByNumber Code="X" Rating="19" Number="2">
</Rating>
</Ratings>
</TopLevel>
<TopLevel CodeTL="Something">
<Ratings>
<Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="30" Number="3">
<RatingByNumber Code="X" Rating="39" Number="4">
</Rating>
</Ratings>
</TopLevel>
请注意,它们都共享相同的CodeTL属性,最后两个共享相同的CodeA、Start和End属性,所以我需要的是使用xslt生成以下输出
<TopLevel CodeTL="Something">
<Ratings>
<Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
<RatingByNumber Code="X" Rating="10" Number="1">
<RatingByNumber Code="X" Rating="19" Number="2">
</Rating>
<Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1">
<RatingByNumber Code="X" Rating="19" Number="2">
</Rating>
<Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1">
<RatingByNumber Code="X" Rating="19" Number="2">
<RatingByNumber Code="X" Rating="30" Number="3">
<RatingByNumber Code="X" Rating="39" Number="4">
</Rating>
</Ratings>
</TopLevel>
这会更干净,而且根据使用它的应用程序的不同,它可能会节省处理时间和空间
我遇到的问题是,我有不同类型的xml消息,它们具有不同的节点名称和属性(以及属性数量),但它们都共享我在这里展示的相同结构。
这将是一种处理所有这些问题的通用方法,但我希望XSLT能够转换我提供的示例,这样我就可以为我需要发送的每个xml消息创建自定义代码。此XSLT 1.0样式表产生所需的结果:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="byCodeTL" match="TopLevel" use="@CodeTL"/>
<xsl:key name="byAttrs" match="Rating"
use="concat(../../@CodeTL, '|', @CodeA, '|', @Start, '|', @End)"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="TopLevel[generate-id()=
generate-id(key('byCodeTL', @CodeTL)[1])]">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<Ratings>
<xsl:apply-templates
select="key('byCodeTL', @CodeTL)/Ratings/*"/>
</Ratings>
</xsl:copy>
</xsl:template>
<xsl:template match="Rating[generate-id()=
generate-id(key('byAttrs',
concat(../../@CodeTL, '|', @CodeA, '|', @Start, '|', @End))[1])]">
<xsl:copy>
<xsl:apply-templates select="@*|key('byAttrs',
concat(../../@CodeTL, '|', @CodeA, '|', @Start, '|', @End))/*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="TopLevel"/>
<xsl:template match="Rating"/>
</xsl:stylesheet>
所有
TopLevel
元素都按其CodeTL
属性分组。所有Rating
元素根据其属性和相应TopLevel
的CodeTL
属性的组合进行分组此通用XSLT 2.0转换:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my" exclude-result-prefixes="xs my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<t>
<xsl:sequence select="my:grouping(*)"/>
</t>
</xsl:template>
<xsl:function name="my:grouping" as="node()*">
<xsl:param name="pElems" as="element()*"/>
<xsl:if test="$pElems">
<xsl:for-each-group select="$pElems" group-by="my:signature(.)">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:sequence select="my:grouping(current-group()/*)"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:if>
</xsl:function>
<xsl:function name="my:signature" as="xs:string">
<xsl:param name="pElem" as="element()"/>
<xsl:variable name="vsignAttribs" as="xs:string*">
<xsl:for-each select="$pElem/@*">
<xsl:sort select="name()"/>
<xsl:value-of select="concat(name(), '=', .,'|')"/>
</xsl:for-each>
</xsl:variable>
<xsl:sequence select=
"concat(name($pElem), '|', string-join($vsignAttribs, ''))"/>
</xsl:function>
</xsl:stylesheet>
<t>
<TopLevel CodeTL="Something">
<Ratings>
<Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
<RatingByNumber Code="X" Rating="30" Number="3"/>
<RatingByNumber Code="X" Rating="39" Number="4"/>
</Rating>
</Ratings>
</TopLevel>
</t>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:my="my:my" exclude-result-prefixes="my ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vrtfPass1">
<xsl:apply-templates select="/*"/>
</xsl:variable>
<xsl:variable name="vPass1" select="ext:node-set($vrtfPass1)"/>
<xsl:template match="/">
<xsl:apply-templates select="$vPass1/*" mode="pass2"/>
</xsl:template>
<xsl:template match="/*" mode="pass2">
<xsl:copy>
<xsl:call-template name="my:grouping">
<xsl:with-param name="pElems" select="*"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="my:grouping">
<xsl:param name="pElems" select="/.."/>
<xsl:if test="$pElems">
<xsl:for-each select="$pElems">
<xsl:variable name="vPos" select="position()"/>
<xsl:if test=
"not(current()/@my:sign
= $pElems[not(position() >= $vPos)]/@my:sign
)">
<xsl:element name="{name()}">
<xsl:copy-of select="namespace::*[not(. = 'my:my')]"/>
<xsl:copy-of select="@*[not(name()='my:sign')]"/>
<xsl:call-template name="my:grouping">
<xsl:with-param name="pElems" select=
"$pElems[@my:sign = current()/@my:sign]/*"/>
</xsl:call-template>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*/*">
<xsl:variable name="vSignature">
<xsl:call-template name="signature"/>
</xsl:variable>
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:attribute name="my:sign">
<xsl:value-of select="$vSignature"/>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template name="signature">
<xsl:variable name="vsignAttribs">
<xsl:for-each select="@*">
<xsl:sort select="name()"/>
<xsl:value-of select="concat(name(), '=', .,'|')"/>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select=
"concat(name(), '|', $vsignAttribs)"/>
</xsl:template>
</xsl:stylesheet>
<t>
<TopLevel>
<Ratings>
<Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
<RatingByNumber Code="X" Rating="30" Number="3"/>
<RatingByNumber Code="X" Rating="39" Number="4"/>
</Rating>
</Ratings>
</TopLevel>
</t>
当此转换应用于相同的XML文档(如上)时,同样会产生相同的正确结果:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my" exclude-result-prefixes="xs my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<t>
<xsl:sequence select="my:grouping(*)"/>
</t>
</xsl:template>
<xsl:function name="my:grouping" as="node()*">
<xsl:param name="pElems" as="element()*"/>
<xsl:if test="$pElems">
<xsl:for-each-group select="$pElems" group-by="my:signature(.)">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:sequence select="my:grouping(current-group()/*)"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:if>
</xsl:function>
<xsl:function name="my:signature" as="xs:string">
<xsl:param name="pElem" as="element()"/>
<xsl:variable name="vsignAttribs" as="xs:string*">
<xsl:for-each select="$pElem/@*">
<xsl:sort select="name()"/>
<xsl:value-of select="concat(name(), '=', .,'|')"/>
</xsl:for-each>
</xsl:variable>
<xsl:sequence select=
"concat(name($pElem), '|', string-join($vsignAttribs, ''))"/>
</xsl:function>
</xsl:stylesheet>
<t>
<TopLevel CodeTL="Something">
<Ratings>
<Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
<RatingByNumber Code="X" Rating="30" Number="3"/>
<RatingByNumber Code="X" Rating="39" Number="4"/>
</Rating>
</Ratings>
</TopLevel>
</t>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:my="my:my" exclude-result-prefixes="my ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vrtfPass1">
<xsl:apply-templates select="/*"/>
</xsl:variable>
<xsl:variable name="vPass1" select="ext:node-set($vrtfPass1)"/>
<xsl:template match="/">
<xsl:apply-templates select="$vPass1/*" mode="pass2"/>
</xsl:template>
<xsl:template match="/*" mode="pass2">
<xsl:copy>
<xsl:call-template name="my:grouping">
<xsl:with-param name="pElems" select="*"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="my:grouping">
<xsl:param name="pElems" select="/.."/>
<xsl:if test="$pElems">
<xsl:for-each select="$pElems">
<xsl:variable name="vPos" select="position()"/>
<xsl:if test=
"not(current()/@my:sign
= $pElems[not(position() >= $vPos)]/@my:sign
)">
<xsl:element name="{name()}">
<xsl:copy-of select="namespace::*[not(. = 'my:my')]"/>
<xsl:copy-of select="@*[not(name()='my:sign')]"/>
<xsl:call-template name="my:grouping">
<xsl:with-param name="pElems" select=
"$pElems[@my:sign = current()/@my:sign]/*"/>
</xsl:call-template>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*/*">
<xsl:variable name="vSignature">
<xsl:call-template name="signature"/>
</xsl:variable>
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:attribute name="my:sign">
<xsl:value-of select="$vSignature"/>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template name="signature">
<xsl:variable name="vsignAttribs">
<xsl:for-each select="@*">
<xsl:sort select="name()"/>
<xsl:value-of select="concat(name(), '=', .,'|')"/>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select=
"concat(name(), '|', $vsignAttribs)"/>
</xsl:template>
</xsl:stylesheet>
<t>
<TopLevel>
<Ratings>
<Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
<RatingByNumber Code="X" Rating="30" Number="3"/>
<RatingByNumber Code="X" Rating="39" Number="4"/>
</Rating>
</Ratings>
</TopLevel>
</t>
说明:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my" exclude-result-prefixes="xs my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<t>
<xsl:sequence select="my:grouping(*)"/>
</t>
</xsl:template>
<xsl:function name="my:grouping" as="node()*">
<xsl:param name="pElems" as="element()*"/>
<xsl:if test="$pElems">
<xsl:for-each-group select="$pElems" group-by="my:signature(.)">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:sequence select="my:grouping(current-group()/*)"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:if>
</xsl:function>
<xsl:function name="my:signature" as="xs:string">
<xsl:param name="pElem" as="element()"/>
<xsl:variable name="vsignAttribs" as="xs:string*">
<xsl:for-each select="$pElem/@*">
<xsl:sort select="name()"/>
<xsl:value-of select="concat(name(), '=', .,'|')"/>
</xsl:for-each>
</xsl:variable>
<xsl:sequence select=
"concat(name($pElem), '|', string-join($vsignAttribs, ''))"/>
</xsl:function>
</xsl:stylesheet>
<t>
<TopLevel CodeTL="Something">
<Ratings>
<Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
<RatingByNumber Code="X" Rating="30" Number="3"/>
<RatingByNumber Code="X" Rating="39" Number="4"/>
</Rating>
</Ratings>
</TopLevel>
</t>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:my="my:my" exclude-result-prefixes="my ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vrtfPass1">
<xsl:apply-templates select="/*"/>
</xsl:variable>
<xsl:variable name="vPass1" select="ext:node-set($vrtfPass1)"/>
<xsl:template match="/">
<xsl:apply-templates select="$vPass1/*" mode="pass2"/>
</xsl:template>
<xsl:template match="/*" mode="pass2">
<xsl:copy>
<xsl:call-template name="my:grouping">
<xsl:with-param name="pElems" select="*"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="my:grouping">
<xsl:param name="pElems" select="/.."/>
<xsl:if test="$pElems">
<xsl:for-each select="$pElems">
<xsl:variable name="vPos" select="position()"/>
<xsl:if test=
"not(current()/@my:sign
= $pElems[not(position() >= $vPos)]/@my:sign
)">
<xsl:element name="{name()}">
<xsl:copy-of select="namespace::*[not(. = 'my:my')]"/>
<xsl:copy-of select="@*[not(name()='my:sign')]"/>
<xsl:call-template name="my:grouping">
<xsl:with-param name="pElems" select=
"$pElems[@my:sign = current()/@my:sign]/*"/>
</xsl:call-template>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*/*">
<xsl:variable name="vSignature">
<xsl:call-template name="signature"/>
</xsl:variable>
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:attribute name="my:sign">
<xsl:value-of select="$vSignature"/>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template name="signature">
<xsl:variable name="vsignAttribs">
<xsl:for-each select="@*">
<xsl:sort select="name()"/>
<xsl:value-of select="concat(name(), '=', .,'|')"/>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select=
"concat(name(), '|', $vsignAttribs)"/>
</xsl:template>
</xsl:stylesheet>
<t>
<TopLevel>
<Ratings>
<Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
<RatingByNumber Code="X" Rating="30" Number="3"/>
<RatingByNumber Code="X" Rating="39" Number="4"/>
</Rating>
</Ratings>
</TopLevel>
</t>
my:sign
这似乎可行,但如果有两个顶级节点具有不同的代码,但具有相同的子节点(它们被分组在文件中出现的第一个节点下),则会失败。例如@EdFox-很好的观点。我们应该将祖父母
@CodeTL
包含在评级
组键中。看我的编辑。这似乎真的是我想要的,除了我被1.0绊倒了。我看看能不能做点什么。谢谢您提供的详细答案。@EdFox:在XSLT 1.0中使用了相同的思想,但在两次转换中,第一次转换将创建每个元素的副本,并添加一个包含签名的特殊新元素(或属性)。在第二步中,我们对这个特殊的元素/属性进行简单的Muenchian分组。您能将1.0版本添加到您的帖子中吗?很抱歉,xslt仍然让我有些不知所措,所以我没有信心将您的解释翻译成实际代码。@EdFox:很高兴。我很快就要开始工作了,因此我将能够在10小时后开始使用类似的XSLT 1.0解决方案——因此,请耐心等待。@EdFox:Done——有关等效的通用XSLT 1.0转换,请参阅本答案的第二部分。