簡體   English   中英

使用xslt在xml中合並兩個或更多節點

[英]merge two or more node in xml using 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解決方案的建議。 謝謝。

親切的問候,約翰

與我在這里給您提供的解決方案相比,這是一種不同口味的解決方案。

我認為有必要逐步進行。 我假設@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

<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而不是緊跟其后的另一個@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>

我希望這很清楚。 您可以擴展這個概念,並為自己創建一個漂亮的庫,包含這些可重用函數,然后將它們用作簡單謂詞以一種方式或另一種方式來合並節點。 不可能是完成工作的最有效方法,但至少是表達解決方案的干凈方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM