【问题标题】:merge two or more node in xml using xslt使用 xslt 合并 xml 中的两个或多个节点
【发布时间】:2012-05-08 03:59:01
【问题描述】:

这个问题有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 解决方案。 谢谢。

亲切的问候, 约翰

【问题讨论】:

  • 不清楚“创建”、“更改”和“删除”的所有“有效”序列是什么。如果没有先前的“创建”,序列如何以“删除”开头? “删除”是删除对象,还是只删除包含的属性?请编辑问题并解释。我不得不猜测重复“改变”动作的语义,在我看来,你还没有给我们一个完全有代表性的例子。在示例中,只有最后一个“更改”中包含的属性应与初始属性合并——猜想中间“更改”属性也必须合并。
  • @DimitreNovaatchev 我已经添加了更多解释,希望现在可以清除。这是创建、更改和删除订单的唯一可能方案。非常感谢。
  • @DimitreNovaatchev 嗨,你有没有可能解决这个问题?谢谢。
  • 当我理解了问题——只有这样我才能回答。您发布的问题仍然没有完全定义,花时间解决这些问题是没有意义的。
  • @John,与您并行提出的问题相比,这个问题与构建这些属性的超集有些不同:stackoverflow.com/questions/10419095/…。您对我发布到该解决方案的解决方案是否满意,或者您需要同时实现这两种风格?

标签: xml xslt


【解决方案1】:

与我给你的here 相比,这里有一个不同风格的解决方案。

我认为循序渐进是值得的。我假设@actions 以逻辑顺序出现——首先是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(.))]"/>

同时忽略creates 后跟change 以及所有creates 和change 后跟remove

<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 会让我们忽略它时,我们会做一件简单的事情 - 收集具有相同@ids 的元素的所有属性,忽略@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>

以及“原始”matches 函数顶部的 preceded-byfollowed-by 语法糖:

<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>

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

【讨论】:

  • 感谢您提供如此详尽的解释。真的很感激。
猜你喜欢
  • 2019-01-29
  • 1970-01-01
  • 2019-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-02
相关资源
最近更新 更多