使用xslt合并xml中的两个或多个节点

时间:2012-04-30 05:06:24

标签: xml xslt

此问题有3种情况:

第一种可能性: 输入:

<root>
    <node id="N1">
        <fruit id="1" action="aaa">
            <orange id="x" action="create">
                <attribute>
                    <color>Orange</color>
                    <year>2012</year>
                </attribute>
            </orange>
            <orange id="x" action="change">
                <attribute>
                    <color>Red</color>
                </attribute>
            </orange>
            <orange id="x" action="change">
                <attribute>
                    <color>Blue</color>
                    <condition>good</condition>
                </attribute>
            </orange>
        </fruit>
    </node>
</root>

预期产出:

<root>
    <node id="N1">
        <fruit id="1" action="aaa">
            <orange id="x" action="create">
                <attribute>
                    <color>Blue</color>
                    <year>2012</year>
                    <condition>good</condition>
                </attribute>
            </orange>
        </fruit>
    </node>
</root>

第二种可能性: 输入:

<root>
    <node id="N1">
        <car id="1">
            <bmw id="i" action="change">
                <attribute>
                    <color>Blue</color>
                    <owner>a</owner>
                </attribute>
            </bmw>
            <bmw id="i" action="change">
                <attribute>
                    <color>Yellow</color>
                    <status>avaailable</status>
                </attribute>
            </bmw>
        </car>
    </node>
</root>

预期产出:

<root>
    <node id="N1">
        <car id="1">
            <bmw id="i" action="change">
                <attribute>
                    <color>Yellow</color>
                    <owner>a</owner>
                    <status>available</status>
                </attribute>
            </bmw>
        </car>
    </node>
</root>

第三种情景:

<root>
    <node id="N1">
        <car id="1">
            <bmw id="j" action="delete">
                <attribute>
                    <color>Blue</color>
                    <year>2000</year>
                </attribute>
            </bmw>
            <bmw id="j" action="delete">
                <attribute>
                    <color>Pink</color>
                    <status>available</status>
                </attribute>
            </bmw>
        </car>
    </node>
</root>

预期产出:

<root>
    <node id="N1">
        <car id="1">
            <bmw id="j" action="delete">
                <attribute>
                    <color>Pink</color>
                    <year>2000</year>
                    <status>available</status>
                </attribute>
            </bmw>            
        </car>
    </node>
</root>

关于第二和第三种情况的说明:

  • 带有'action = change'的两个或多个节点将合并到一个节点,其中'action = change'
  • 使用'action = delete'将两个或多个带有'action = delete'的节点合并到一个节点中
  • 在合并时,我们更新只保留最后一个节点的值,保留初始节点并添加任何新的附加节点。

我希望解释清楚。

请告诉我有关此问题的XSLT解决方案。 谢谢。

亲切的问候, 约翰

1 个答案:

答案 0 :(得分:1)

与我给你的here相比,这是一种不同风味的解决方案。

我认为值得一步一步走。我假设@action出现在逻辑顺序中 - 首先create,然后是change,最后是remove。同一@action可能会出现多次,但不会是随机的。现在我们准备好了解主要逻辑:

<xsl:template match="@* | node()">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
</xsl:template>

我们宣布身份转换,然后在几个地方拦截它。我们只停留在具有相同@id,父@id@action的节点的唯一匹配位置:

<xsl:template match="node/*/*[a:is-primary(.)]" priority="1">
    <xsl:copy>
        <xsl:apply-templates select="@*"/>
        <xsl:apply-templates select="attribute" mode="consolidate-most-recent"/>
    </xsl:copy>
</xsl:template>

我们忽略了“重复”:

<xsl:template match="node/*/*[not(a:is-primary(.))]"/>

并忽略create跟随change以及所有createchange后跟remove。{/ p>

<xsl:template match="node/*/*[@action = 'change'][a:preceded-by(., 'create')]" priority="2"/>
<xsl:template match="node/*/*[@action = 'create' or action='change'][a:followed-by(., 'remove')]" priority="2"/>

当唯一的@action没有跟随另一个会让我们忽略它的@action被捕获时,我们做了一件简单的事情 - 收集具有相同@id个忽略的元素的所有属性@action并使用他们最“最近”的值(文档顺序中最后出现的值)。

<xsl:template match="attribute" mode="consolidate-most-recent">
    <xsl:copy>
        <xsl:for-each-group 
                    select="/root/node/*/*[a:matches(current()/parent::*, ., 'any')]/attribute/*" 
                    group-by="local-name()">
            <!-- take the last in the document order -->
            <xsl:apply-templates select="current-group()[last()]"/>
        </xsl:for-each-group>
    </xsl:copy>
</xsl:template>

就是这样。现在让我们看一下使其工作的函数:

我们有一个key来简化查找

<xsl:key name="entity" match="/root/node/*/*" use="concat(parent::*/@id, '_', @id, '_', @action)"/>

检查节点是否是唯一出现的函数(我们可以将它直接添加到模板match谓词中,但是因为我们开始使用这些函数,所以让我们保持相同):

<xsl:function name="a:is-primary" as="xs:boolean">
    <xsl:param name="ctx"/>
    <!-- need to establish "focus"(context) for the key() function to work -->
    <xsl:for-each select="$ctx">
        <xsl:sequence select="generate-id($ctx) = generate-id(key('entity', concat($ctx/parent::*/@id, '_', $ctx/@id, '_', $ctx/@action))[1])"/>
    </xsl:for-each>
</xsl:function> 

一个matches函数,它将为我们进行所有类型的比较(同样,可以将它全部放在谓词中,但这样我们将在真实模板中保持良好和干净):

<xsl:function name="a:matches" as="xs:boolean">
    <xsl:param name="src"/>
    <xsl:param name="target"/>
    <!-- can be one of the following:
        'any' - only match the @id(s) and ignore @action
        'same' - match by @id(s) and expect $src/@action to match $target/@action
         a certain value - match by @id(s) and expect @action to match this value
     -->
    <xsl:param name="action"/>

    <xsl:value-of select="
                  ($src/local-name() = $target/local-name()) and
                  ($src/parent::*/@id = $target/parent::*/@id) and 
                  ($src/@id = $target/@id) and 
                  (if ($action = 'any') 
                      then true()
                      else if ($action = 'same')
                          then ($target/@action = $src/@action)
                          else ($target/@action = $action))"/>  
</xsl:function>

preceded-byfollowed-by语法糖位于“原始”matches函数之上:

<xsl:function name="a:preceded-by" as="xs:boolean">
    <xsl:param name="ctx"/>
    <xsl:param name="action"/>

    <xsl:value-of select="count($ctx/preceding::*[a:matches($ctx, ., $action)]) > 0"/>
</xsl:function>

<xsl:function name="a:followed-by" as="xs:boolean">
    <xsl:param name="ctx"/>
    <xsl:param name="action"/>

    <xsl:value-of select="count($ctx/following::*[a:matches($ctx, ., $action)]) > 0"/>
</xsl:function>

<强>概要

这是一个完整的转变:

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

    <xsl:key name="entity" match="/root/node/*/*" use="concat(parent::*/@id, '_', @id, '_', @action)"/>

    <xsl:function name="a:is-primary" as="xs:boolean">
        <xsl:param name="ctx"/>
        <!-- need to establish "focus"(context) for the key() function to work -->
        <xsl:for-each select="$ctx">
            <xsl:sequence select="generate-id($ctx) = generate-id(key('entity', concat($ctx/parent::*/@id, '_', $ctx/@id, '_', $ctx/@action))[1])"/>
        </xsl:for-each>
    </xsl:function> 

    <xsl:function name="a:preceded-by" as="xs:boolean">
        <xsl:param name="ctx"/>
        <xsl:param name="action"/>

        <xsl:value-of select="count($ctx/preceding::*[a:matches($ctx, ., $action)]) > 0"/>
    </xsl:function>

    <xsl:function name="a:followed-by" as="xs:boolean">
        <xsl:param name="ctx"/>
        <xsl:param name="action"/>

        <xsl:value-of select="count($ctx/following::*[a:matches($ctx, ., $action)]) > 0"/>
    </xsl:function>

    <xsl:function name="a:matches" as="xs:boolean">
        <xsl:param name="src"/>
        <xsl:param name="target"/>
        <!-- can be one of the following:
            'any' - only match the @id(s) and ignore @action
            'same' - match by @id(s) and expect $src/@action to match $target/@action
             a certain value - match by @id(s) and expect @action to match this value
         -->
        <xsl:param name="action"/>

        <xsl:value-of select="
                      ($src/local-name() = $target/local-name()) and
                      ($src/parent::*/@id = $target/parent::*/@id) and 
                      ($src/@id = $target/@id) and 
                      (if ($action = 'any') 
                          then true()
                          else if ($action = 'same')
                              then ($target/@action = $src/@action)
                              else ($target/@action = $action))"/>  
    </xsl:function>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="node/*/*[a:is-primary(.)]" priority="1">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:apply-templates select="attribute" mode="consolidate-most-recent"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="attribute" mode="consolidate-most-recent">
        <xsl:copy>
            <xsl:for-each-group 
                        select="/root/node/*/*[a:matches(current()/parent::*, ., 'any')]/attribute/*" 
                        group-by="local-name()">
                <!-- take the last in the document order -->
                <xsl:apply-templates select="current-group()[last()]"/>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="node/*/*[not(a:is-primary(.))]"/>

    <!-- assume a remove is never followed by a change or create -->
    <xsl:template match="node/*/*[@action = 'change'][a:preceded-by(., 'create')]" priority="2"/>
    <xsl:template match="node/*/*[@action = 'create' or action='change'][a:followed-by(., 'remove')]" priority="2"/>
</xsl:stylesheet>

应用于文档时:

<root>
    <node id="N1">
        <fruit id="1" action="aaa">
            <orange id="x" action="create">
                <attribute>
                    <color>Orange</color>
                    <year>2012</year>
                </attribute>
            </orange>
            <orange id="x" action="change">
                <attribute>
                    <color>Red</color>
                    <something>!!</something>
                </attribute>
            </orange>
            <orange id="x" action="change">
                <attribute>
                    <color>Blue</color>
                    <condition>good</condition>
                </attribute>
            </orange>
            <orange id="x" action="remove">
                <attribute>
                    <condition>awesome</condition>
                </attribute>
            </orange>
        </fruit>
    </node>
</root>

产生以下结果:

<root>
   <node id="N1">
      <fruit id="1" action="aaa">
         <orange id="x" action="remove">
            <attribute>
               <color>Blue</color>
               <year>2012</year>
               <something>!!</something>
               <condition>awesome</condition>
            </attribute>
         </orange>
      </fruit>
   </node>
</root>

我希望它很清楚。您可以扩展这个概念,并为自己构建一个很好的可重用函数库,然后将其用作简单谓词以某种方式合并节点。不太可能是最有效的工作方式,但至少是一种表达解决方案的简洁方式。