Я попытался понять ваши требования и части реализации Оливера Беккера, объясненные в http://web.archive.org/web/20160502222322/http://www2.informatik.hu-berlin.de/~obecker/XSLT/#merge и показанные в http://web.archive.org/web/20160502194427/http://www2.informatik.hu-berlin.de/~obecker/XSLT/merge/merge.xslt.html путем адаптации сравнения узлов элементов (элементы без дочерних элементов считаются эквивалентными, если имя, пространство имен и соответствие содержимого, элементы со сложным содержимым (то есть дочерние элементы) считаются эквивалентными, если они имеют одинаковые имя и пространство имен), и адаптируя объединение элементов для копирования атрибутов из второго документа ($node2/@*
копируются сначала как Я думаю, вы хотите, чтобы атрибуты в первом документе имели предпочтения).
Я протестировал это в режиме онлайн на https://xsltfiddle.liberty -development.net / nc4NzQq с Saxon 9.8 HE, но только для того, чтобы иметь возможность делиться кодом и результатом в исполняемом виде как XSLT 2 или 3 процессора легко позволяют мне вставить второй документ. Итак, у меня есть
<!--
Merging two XML files
Version 1.6
LGPL (c) Oliver Becker, 2002-07-05
obecker@informatik.hu-berlin.de
-->
<xslt:transform xmlns:xslt="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://informatik.hu-berlin.de/merge" version="1.0" exclude-result-prefixes="m">
<!-- Normalize the contents of text, comment, and processing-instruction
nodes before comparing?
Default: yes -->
<xslt:param name="normalize" select="'yes'" />
<!-- Don't merge elements with this (qualified) name -->
<xslt:param name="dontmerge" />
<!-- If set to true, text nodes in file1 will be replaced -->
<xslt:param name="replace" select="false()" />
<!-- Variant 1: Source document looks like
<?xml version="1.0"?>
<merge xmlns="http://informatik.hu-berlin.de/merge">
<file1>file1.xml</file1>
<file2>file2.xml</file2>
</merge>
The transformation sheet merges file1.xml and file2.xml.
-->
<xslt:template match="m:merge">
<xslt:variable name="file1" select="string(m:file1)" />
<xslt:variable name="file2" select="string(m:file2)" />
<xslt:message>
<xslt:text />Merging '<xslt:value-of select="$file1" />
<xslt:text />' and '<xslt:value-of select="$file2" />'<xslt:text />
</xslt:message>
<xslt:if test="$file1='' or $file2=''">
<xslt:message terminate="yes">
<xslt:text>No files to merge specified</xslt:text>
</xslt:message>
</xslt:if>
<xslt:call-template name="m:merge">
<xslt:with-param name="nodes1" select="document($file1,/*)/node()" />
<xslt:with-param name="nodes2" select="document($file2,/*)/node()" />
</xslt:call-template>
</xslt:template>
<!-- Variant 2:
The transformation sheet merges the source document with the
document provided by the parameter "with".
-->
<xslt:param name="with" />
<!-- for testing I inline the second document -->
<xslt:param name="with-doc">
<first y="2">
<second param="123" second="false">
<third>asd</third>
<third>def</third>
</second>
<fourth>
<fifth y="2">tuv</fifth>
<fifth>wxy</fifth>
</fourth>
<sixth>678</sixth>
<sixth>910</sixth>
</first>
</xslt:param>
<xslt:template match="*">
<xslt:message>
<xslt:text />Merging input with '<xslt:value-of select="$with" />
<xslt:text>'</xslt:text>
</xslt:message>
<!--<xslt:if test="string($with)=''">
<xslt:message terminate="yes">
<xslt:text>No input file specified (parameter 'with')</xslt:text>
</xslt:message>
</xslt:if>-->
<xslt:call-template name="m:merge">
<xslt:with-param name="nodes1" select="/node()" />
<!-- <xslt:with-param name="nodes2" select="document($with,/*)/node()" /> -->
<xslt:with-param name="nodes2" select="$with-doc/node()" />
</xslt:call-template>
</xslt:template>
<!-- ============================================================== -->
<!-- The "merge" template -->
<xslt:template name="m:merge">
<xslt:param name="nodes1" />
<xslt:param name="nodes2" />
<xslt:choose>
<!-- Is $nodes1 resp. $nodes2 empty? -->
<xslt:when test="count($nodes1)=0">
<xslt:copy-of select="$nodes2" />
</xslt:when>
<xslt:when test="count($nodes2)=0">
<xslt:copy-of select="$nodes1" />
</xslt:when>
<xslt:otherwise>
<!-- Split $nodes1 and $nodes2 -->
<xslt:variable name="first1" select="$nodes1[1]" />
<xslt:variable name="rest1" select="$nodes1[position()!=1]" />
<xslt:variable name="first2" select="$nodes2[1]" />
<xslt:variable name="rest2" select="$nodes2[position()!=1]" />
<!-- Determine type of node $first1 -->
<xslt:variable name="type1">
<xslt:apply-templates mode="m:detect-type" select="$first1" />
</xslt:variable>
<!-- Compare $first1 and $first2 -->
<xslt:variable name="diff-first">
<xslt:call-template name="m:compare-nodes">
<xslt:with-param name="node1" select="$first1" />
<xslt:with-param name="node2" select="$first2" />
</xslt:call-template>
</xslt:variable>
<xslt:choose>
<!-- $first1 != $first2 -->
<xslt:when test="$diff-first='!'">
<!-- Compare $first1 and $rest2 -->
<xslt:variable name="diff-rest">
<xslt:for-each select="$rest2">
<xslt:call-template name="m:compare-nodes">
<xslt:with-param name="node1" select="$first1" />
<xslt:with-param name="node2" select="." />
</xslt:call-template>
</xslt:for-each>
</xslt:variable>
<xslt:choose>
<!-- $first1 is in $rest2 and
$first1 is *not* an empty text node -->
<xslt:when test="contains($diff-rest,'=') and not($type1='text' and normalize-space($first1)='')">
<!-- determine position of $first1 in $nodes2
and copy all preceding nodes of $nodes2 -->
<xslt:variable name="pos" select="string-length(substring-before( $diff-rest,'=')) + 2" />
<xslt:copy-of select="$nodes2[position() < $pos]" />
<!-- merge $first1 with its equivalent node -->
<xslt:choose>
<!-- Elements: merge -->
<xslt:when test="$type1='element'">
<xslt:element name="{name($first1)}" namespace="{namespace-uri($first1)}">
<xslt:copy-of select="$first1/namespace::*" />
<xslt:copy-of select="$first2/namespace::*" />
<xslt:copy-of select="$first2/@*"/>
<xslt:copy-of select="$first1/@*" />
<xslt:call-template name="m:merge">
<xslt:with-param name="nodes1" select="$first1/node()" />
<xslt:with-param name="nodes2" select="$nodes2[position()=$pos]/node()" />
</xslt:call-template>
</xslt:element>
</xslt:when>
<!-- Other: copy -->
<xslt:otherwise>
<xslt:copy-of select="$first1" />
</xslt:otherwise>
</xslt:choose>
<!-- Merge $rest1 and rest of $nodes2 -->
<xslt:call-template name="m:merge">
<xslt:with-param name="nodes1" select="$rest1" />
<xslt:with-param name="nodes2" select="$nodes2[position() > $pos]" />
</xslt:call-template>
</xslt:when>
<!-- $first1 is a text node and replace mode was
activated -->
<xslt:when test="$type1='text' and $replace">
<xslt:call-template name="m:merge">
<xslt:with-param name="nodes1" select="$rest1" />
<xslt:with-param name="nodes2" select="$nodes2" />
</xslt:call-template>
</xslt:when>
<!-- else: $first1 is not in $rest2 or
$first1 is an empty text node -->
<xslt:otherwise>
<xslt:copy-of select="$first1" />
<xslt:call-template name="m:merge">
<xslt:with-param name="nodes1" select="$rest1" />
<xslt:with-param name="nodes2" select="$nodes2" />
</xslt:call-template>
</xslt:otherwise>
</xslt:choose>
</xslt:when>
<!-- else: $first1 = $first2 -->
<xslt:otherwise>
<xslt:choose>
<!-- Elements: merge -->
<xslt:when test="$type1='element'">
<xslt:element name="{name($first1)}" namespace="{namespace-uri($first1)}">
<xslt:copy-of select="$first2/namespace::*" />
<xslt:copy-of select="$first1/namespace::*" />
<xslt:copy-of select="$first2/@*" />
<xslt:copy-of select="$first1/@*" />
<xslt:call-template name="m:merge">
<xslt:with-param name="nodes1" select="$first1/node()" />
<xslt:with-param name="nodes2" select="$first2/node()" />
</xslt:call-template>
</xslt:element>
</xslt:when>
<!-- Other: copy -->
<xslt:otherwise>
<xslt:copy-of select="$first1" />
</xslt:otherwise>
</xslt:choose>
<!-- Merge $rest1 and $rest2 -->
<xslt:call-template name="m:merge">
<xslt:with-param name="nodes1" select="$rest1" />
<xslt:with-param name="nodes2" select="$rest2" />
</xslt:call-template>
</xslt:otherwise>
</xslt:choose>
</xslt:otherwise>
</xslt:choose>
</xslt:template>
<!-- Comparing single nodes:
if $node1 and $node2 are equivalent then the template creates a
text node "=" otherwise a text node "!" -->
<xslt:template name="m:compare-nodes">
<xslt:param name="node1" />
<xslt:param name="node2" />
<xslt:variable name="type1">
<xslt:apply-templates mode="m:detect-type" select="$node1" />
</xslt:variable>
<xslt:variable name="type2">
<xslt:apply-templates mode="m:detect-type" select="$node2" />
</xslt:variable>
<xslt:choose>
<!-- Are $node1 and $node2 complex element nodes with the same name? -->
<xslt:when test="$type1='element' and $type2='element' and $node1/* and $node2/* and local-name($node1)=local-name($node2) and namespace-uri($node1)=namespace-uri($node2) and name($node1)!=$dontmerge and name($node2)!=$dontmerge">
<xslt:text>=</xslt:text>
</xslt:when>
<!-- Are $node1 and $node2 simple elements with the same name and same content -->
<xslt:when test="$type1='element' and $type2='element' and not($node1/*) and not($node2/*) and local-name($node1)=local-name($node2) and namespace-uri($node1)=namespace-uri($node2) and $node1 = $node2 and name($node1)!=$dontmerge and name($node2)!=$dontmerge">
<xslt:text>=</xslt:text>
</xslt:when>
<!-- Other nodes: test for the same type and content -->
<xslt:when test="$type1!='element' and $type1=$type2 and name($node1)=name($node2) and ($node1=$node2 or ($normalize='yes' and normalize-space($node1)= normalize-space($node2)))">=</xslt:when>
<!-- Otherwise: different node types or different name/content -->
<xslt:otherwise>!</xslt:otherwise>
</xslt:choose>
</xslt:template>
<!-- Type detection, thanks to M. H. Kay -->
<xslt:template match="*" mode="m:detect-type">element</xslt:template>
<xslt:template match="text()" mode="m:detect-type">text</xslt:template>
<xslt:template match="comment()" mode="m:detect-type">comment</xslt:template>
<xslt:template match="processing-instruction()" mode="m:detect-type">pi</xslt:template>
</xslt:transform>
, который затем дает результат
<first y="2" x="1">
<second param="wt" second="true">
<third>abc</third>
<third>asd</third><third>def</third>
</second>
<fourth>
<fifth x="1">hij</fifth>
<fifth>klm</fifth>
<fifth y="2">tuv</fifth><fifth>wxy</fifth></fourth>
<sixth>qrs</sixth>
<sixth>678</sixth><sixth>910</sixth></first>
, который близок к вашему желаемому результату, я не уверен, что вы можете иметь смешанное содержимое (т. Е. Смешать текст и дочерние элементы), если нет, я думаю, используя xsl:strip-space
и xsl:output indent="yes"
, как это было сделано в https://xsltfiddle.liberty -development.net / nc4NzQq / 1 , чистый результат
<first y="2" x="1">
<second param="wt" second="true">
<third>abc</third>
<third>asd</third>
<third>def</third>
</second>
<fourth>
<fifth x="1">hij</fifth>
<fifth>klm</fifth>
<fifth y="2">tuv</fifth>
<fifth>wxy</fifth>
</fourth>
<sixth>qrs</sixth>
<sixth>678</sixth>
<sixth>910</sixth>
</first>
С другой стороны, поскольку у меня был второй встроенный образец, где пропущены пробелы, может быть достаточно предположить, что этого не происходит в обычном случае использования функции document
, а затем моделировать ее в https://xsltfiddle.liberty -development.net / nc4NzQq / 2 с xml:space="preserve"
на встроенном образце, результат
<first y="2" xml:space="preserve" x="1">
<second param="wt" second="true">
<third>abc</third>
<third>asd</third>
<third>def</third>
</second>
<fourth>
<fifth x="1">hij</fifth>
<fifth>klm</fifth>
<fifth y="2">tuv</fifth>
<fifth>wxy</fifth>
</fourth>
<sixth>qrs</sixth>
<sixth>678</sixth>
<sixth>910</sixth>
</first>
также выглядит многообещающе. Поэтому попытайтесь соответственно изменить его, чтобы использовать параметр with
и функцию document
, как в оригинале, тогда вы можете получить желаемый результат, по крайней мере для двух показанных вами образцов. Трудно сказать, является ли это универсальным решением, так как я думаю, что сама идея объединения во многом зависит от четкой спецификации того, как точно сравнивать узлы.