How to concatenate two or more xml files using xsl

2019-03-05 22:21发布

If I have this file: input file1.xml:

<schema>
    <sequence> 
        <nodeA id="a">
            <fruit id="small">
                <orange id="x" method="create">                    
                    <attributes>
                        <color>Orange</color>
                        <year>2000</year>
                    </attributes>
                </orange>                           
            </fruit>
            <fruit id="small">
                <apple id="x" method="create">                    
                    <attributes>
                        <color>Orange</color>
                        <year>2000</year>
                    </attributes>
                </apple>                           
            </fruit>
            <fruit id="medium">
                <orange id="x" method="create">                    
                    <attributes>
                        <color>Orange</color>
                        <year>2000</year>
                    </attributes>
                </orange>                           
            </fruit>
        </nodeA>
        <nodeB id="b">
            <dog id="large">
                <doberman id="x" method="create">
                    <condition>
                        <color>Black</color>
                    </condition>
                </doberman>
            </dog>
        </nodeB>
    </sequence>
</schema>

file2.xml:

<schema>
    <sequence>
        <nodeA id="a">
            <fruit id="small">
                <melon id="x" method="create">
                    <attributes>
                        <color>Orange</color>
                        <year>2000</year>
                    </attributes>
                </melon>
            </fruit>
        </nodeA>
        <nodeB id="b">
            <dog id="small">
                <poodle id="x" method="create">                    
                    <condition>
                        <color>White</color>
                    </condition>
                </poodle>  
            </dog>                
        </nodeB>
    </sequence>
</schema>

After concatenation: output: concate.xml

<schema>
    <sequence>
        <nodeA id="a">
            <fruit id="small">
                <orange id="x" method="create">                    
                    <attributes>
                        <color>Orange</color>
                        <year>2000</year>
                    </attributes>
                </orange>                        
            </fruit>
            <fruit id="small">
                <apple id="x" method="create">                    
                    <attributes>
                        <color>Orange</color>
                        <year>2000</year>
                    </attributes>
                </apple>                           
            </fruit>
            <fruit id="medium">
                <orange id="x" method="create">                    
                    <attributes>
                        <color>Orange</color>
                        <year>2000</year>
                    </attributes>
                </orange>                           
            </fruit>
            <fruit id="small">
                <melon id="x" method="create">
                    <attributes>
                        <color>Orange</color>
                        <year>2000</year>
                    </attributes>
                </melon>
            </fruit>
        </nodeA>
        <nodeB id="b">
            <dog id="large">
                <doberman id="x" method="create">
                    <condition>
                        <color>Black</color>
                    </condition>
                </doberman>
            </dog>
            <dog id="small">
                <poodle id="x" method="create">                    
                    <condition>
                        <color>White</color>
                    </condition>
                </poodle>  
            </dog>                
        </nodeB>
    </sequence>
</schema>

For the concate it will depend on the file order so the node in file2.xml will be placed under the node of file1.xml (as seen on the example). And I have up to 5 files. How is this achievable using xsl transformation only, i.e the xslt will input 5 files at the same time and outputting 1 file?

This is the document structure and the point where we do merge:

<schema>
    <sequence> 
        <nodeA id="a">
            <fruit id="small">
                <orange id="x" method="create">                    
                    ...
                </orange>                
            </fruit>
            <fruit id="small">
                ...                          
            </fruit>
            <fruit id="large"> 
                ...                          
            </fruit>

            <!-- we merge below this -->
        </nodeA>

        <nodeB id="b">
            <dog id="large">
                <doberman id="x" method="create">
                    ...
                </doberman>
            </dog>
            <dog id="small">
                <doberman id="x" method="create">
                    ...
                </doberman>
            </dog>
        <!-- we merge below this -->
        </nodeB>

        <somenode id="any">
            ...    
        </somenode>
    </sequence>
</schema>

Note: If not possible concatenating only two files input will be fine as it can always be repeated for the other files. Also there are various node name in the file (nodeA, nodeB, SomeNode, etc.)so something that can generalize this problem is needed.

we can use xsl1.0 or 2.0.

Thanks very much. John

标签: xml xslt
3条回答
Explosion°爆炸
2楼-- · 2019-03-05 22:46

Try:

 <?xml version="1.0"?>
 <xsl:stylesheet 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:fn="http://www.w3.org/2005/xpath-functions"
   xmlns:xs="http://www.w3.org/2001/XMLSchema"
   version="2.0"
   exclude-result-prefixes="xsl xs fn">

 <xsl:output indent="yes" encoding="UTF-8" />
 <xsl:param name="file2" /> <!-- input file1.xml -->
 <xsl:variable name="file2-doc" select="document($file2)" />

 <xsl:template  match="/">
  <schema>
   <sequence>
    <nodeA id="a">
     <xsl:apply-templates select="schema/sequence/nodeA/*" />
     <xsl:apply-templates select="$file2-doc/schema/sequence/nodeA/*" />
    </nodeA>
    <nodeB id="b">
     <xsl:apply-templates select="schema/sequence/nodeB/*" />
     <xsl:apply-templates select="$file2-doc/schema/sequence/nodeB/*" />
    </nodeB>
   </sequence>
  </schema>
 </xsl:template>

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

 <xsl:template match="attribute()|text()|comment()|processing-instruction()">
   <xsl:copy/>
 </xsl:template>

 </xsl:stylesheet>

Make file1 your main document input. Pass the filename for file2 as parameter "file2". Similarly extend for multiple input files.

查看更多
仙女界的扛把子
3楼-- · 2019-03-05 23:06

@John, here's a more generic solution:

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

   <xsl:variable name="to-merge" select="document('input2.xml') | document('input3.xml')"/>

   <xsl:function name="a:id">
      <xsl:param name="ctx"/>
      <xsl:value-of select="concat($ctx/local-name(), $ctx/@id)"/>
   </xsl:function>   

   <xsl:key name="match" match="/schema/sequence/*" use="a:id(.)"/>

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

   <xsl:template match="*[count(. | key('match', a:id(.))) = count(key('match', a:id(.)))]">
    <xsl:copy>
           <xsl:apply-templates select="@* | node()"/>

           <xsl:variable name="id" select="a:id(.)"/>
           <xsl:for-each select="$to-merge">
              <xsl:apply-templates select="key('match', $id)/*"/>
           </xsl:for-each>
    </xsl:copy>
   </xsl:template>

</xsl:stylesheet>

You define the merge point in the key and you define the merge match function in the a:id. You can fallback to XSLT 1.0 by just taking the a:id function into your predicates.

My assumptions:

  • you run the transformation on the "leading" document and sequence your merges in that to-merge variable
  • you have a single match point that is located at the same spot in each document to be merged. it shouldn't be hard to customize the solution to merge from different points in each document.
  • the nodes match by local-name() and @id
查看更多
Explosion°爆炸
4楼-- · 2019-03-05 23:09

Here is another answer. This joins at the schema/sequence/* level, rather than just nodeA and NodeB.

 <?xml version="1.0"?>
 <xsl:stylesheet 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:fn="http://www.w3.org/2005/xpath-functions"
   xmlns:xs="http://www.w3.org/2001/XMLSchema"
   version="2.0"
   exclude-result-prefixes="xsl xs fn">

 <xsl:output indent="yes" encoding="UTF-8" />
 <xsl:param name="file2" /> <!-- input file1.xml -->
 <xsl:variable name="file1-doc" select="root()" />
 <xsl:variable name="file2-doc" select="document($file2)" />


 <xsl:template  match="/">
  <schema>
   <sequence>
    <xsl:call-template name="union-point">
     <xsl:with-param name="value" select="schema/sequence/*"/>
    </xsl:call-template>
    <xsl:call-template name="union-point">
     <!-- The following predicate excludes all the node names that we
             have already processed in the first call-template.    -->
     <xsl:with-param name="value" select="$file2-doc/schema/sequence/*
      [not (fn:exists($file1-doc/schema/sequence/name()))]
      "/>
    </xsl:call-template>
   </sequence>
  </schema>
 </xsl:template>

 <xsl:template name="union-point">
   <xsl:param name="value"/>
   <xsl:for-each select="$value/name()" >
    <xsl:variable name="node-name" select="."/>
    <xsl:element name="{.}">
 <xsl:attribute name="id">
  <xsl:value-of select="($file1-doc/schema/sequence/*[name()=$node-name]/@id |
                         $file2-doc/schema/sequence/*[name()=$node-name]/@id  )[1]" />
 </xsl:attribute>
     <xsl:apply-templates select="$file1-doc/schema/sequence/*[name()=$node-name]/*" />
     <xsl:apply-templates select="$file2-doc/schema/sequence/*[name()=$node-name]/*" />
    </xsl:element>
   </xsl:for-each>
 </xsl:template>

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

 <xsl:template match="attribute()|text()|comment()|processing-instruction()">
   <xsl:copy/>
 </xsl:template>

 </xsl:stylesheet>

As a solution, its probably a bit clumsy and awkward, but it basically works. Hopefully an expert like Dimitre Novatchev will come along and offer a tidier alternative. This is about the limits of my ability.

*UPDATE 1 * I added the id attribute to the etc.

UPDATE 2 Here is the resultant output:

 <?xml version="1.0" encoding="UTF-8"?>
 <schema>
    <sequence>
       <nodeA id="a">
          <fruit id="small">
                 <orange id="x" method="create">                    
                     <attributes>
                         <color>Orange</color>
                         <year>2000</year>
                     </attributes>
                 </orange>                           
             </fruit>
               <fruit id="small">
                 <apple id="x" method="create">                    
                     <attributes>
                         <color>Orange</color>
                         <year>2000</year>
                     </attributes>
                 </apple>                           
             </fruit>
          <fruit id="medium">
                 <orange id="x" method="create">                    
                     <attributes>
                         <color>Orange</color>
                         <year>2000</year>
                     </attributes>
                 </orange>                           
             </fruit>
          <fruit id="small">
                 <melon id="x" method="create">
                     <attributes>
                         <color>Orange</color>
                         <year>2000</year>
                     </attributes>
                 </melon>
             </fruit>
       </nodeA>
       <nodeB id="b">
          <dog id="large">
                 <doberman id="x" method="create">
                     <condition>
                         <color>Black</color>
                     </condition>
                 </doberman>
             </dog>
          <dog id="small">
                 <poodle id="x" method="create">                    
                     <condition>
                         <color>White</color>
                     </condition>
                 </poodle>  
             </dog>
       </nodeB>
    </sequence>
 </schema>
查看更多
登录 后发表回答