|
| 1 | +<?xml version="1.0"?> |
| 2 | + |
| 3 | +<!-- |
| 4 | + Merging two XML files |
| 5 | + Version 1.6 |
| 6 | + LGPL (c) Oliver Becker, 2002-07-05 |
| 7 | + obecker@informatik.hu-berlin.de |
| 8 | +--> |
| 9 | + |
| 10 | +<xslt:transform version="1.0" |
| 11 | + xmlns:xslt="http://www.w3.org/1999/XSL/Transform" |
| 12 | + xmlns:m="http://informatik.hu-berlin.de/merge" |
| 13 | + exclude-result-prefixes="m"> |
| 14 | + |
| 15 | + |
| 16 | +<!-- Normalize the contents of text, comment, and processing-instruction |
| 17 | + nodes before comparing? |
| 18 | + Default: yes --> |
| 19 | +<xslt:param name="normalize" select="'yes'" /> |
| 20 | + |
| 21 | +<!-- Don't merge elements with this (qualified) name --> |
| 22 | +<xslt:param name="dontmerge" /> |
| 23 | + |
| 24 | +<!-- If set to true, text nodes in file1 will be replaced --> |
| 25 | +<xslt:param name="replace" select="false()" /> |
| 26 | + |
| 27 | +<!-- Variant 1: Source document looks like |
| 28 | + <?xml version="1.0"?> |
| 29 | + <merge xmlns="http://informatik.hu-berlin.de/merge"> |
| 30 | + <file1>file1.xml</file1> |
| 31 | + <file2>file2.xml</file2> |
| 32 | + </merge> |
| 33 | + The transformation sheet merges file1.xml and file2.xml. |
| 34 | +--> |
| 35 | +<xslt:template match="m:merge" > |
| 36 | + <xslt:variable name="file1" select="string(m:file1)" /> |
| 37 | + <xslt:variable name="file2" select="string(m:file2)" /> |
| 38 | + <xslt:message> |
| 39 | + <xslt:text />Merging '<xslt:value-of select="$file1" /> |
| 40 | + <xslt:text />' and '<xslt:value-of select="$file2"/>'<xslt:text /> |
| 41 | + </xslt:message> |
| 42 | + <xslt:if test="$file1='' or $file2=''"> |
| 43 | + <xslt:message terminate="yes"> |
| 44 | + <xslt:text>No files to merge specified</xslt:text> |
| 45 | + </xslt:message> |
| 46 | + </xslt:if> |
| 47 | + <xslt:call-template name="m:merge"> |
| 48 | + <xslt:with-param name="nodes1" select="document($file1,/*)/node()" /> |
| 49 | + <xslt:with-param name="nodes2" select="document($file2,/*)/node()" /> |
| 50 | + </xslt:call-template> |
| 51 | +</xslt:template> |
| 52 | + |
| 53 | + |
| 54 | +<!-- Variant 2: |
| 55 | + The transformation sheet merges the source document with the |
| 56 | + document provided by the parameter "with". |
| 57 | +--> |
| 58 | +<xslt:param name="with" /> |
| 59 | + |
| 60 | +<xslt:template match="*"> |
| 61 | + <xslt:message> |
| 62 | + <xslt:text />Merging input with '<xslt:value-of select="$with"/> |
| 63 | + <xslt:text>'</xslt:text> |
| 64 | + </xslt:message> |
| 65 | + <xslt:if test="string($with)=''"> |
| 66 | + <xslt:message terminate="yes"> |
| 67 | + <xslt:text>No input file specified (parameter 'with')</xslt:text> |
| 68 | + </xslt:message> |
| 69 | + </xslt:if> |
| 70 | + |
| 71 | + <xslt:call-template name="m:merge"> |
| 72 | + <xslt:with-param name="nodes1" select="/node()" /> |
| 73 | + <xslt:with-param name="nodes2" select="document($with,/*)/node()" /> |
| 74 | + </xslt:call-template> |
| 75 | +</xslt:template> |
| 76 | + |
| 77 | + |
| 78 | +<!-- ============================================================== --> |
| 79 | + |
| 80 | +<!-- The "merge" template --> |
| 81 | +<xslt:template name="m:merge"> |
| 82 | + <xslt:param name="nodes1" /> |
| 83 | + <xslt:param name="nodes2" /> |
| 84 | + |
| 85 | + <xslt:choose> |
| 86 | + <!-- Is $nodes1 resp. $nodes2 empty? --> |
| 87 | + <xslt:when test="count($nodes1)=0"> |
| 88 | + <xslt:copy-of select="$nodes2" /> |
| 89 | + </xslt:when> |
| 90 | + <xslt:when test="count($nodes2)=0"> |
| 91 | + <xslt:copy-of select="$nodes1" /> |
| 92 | + </xslt:when> |
| 93 | + |
| 94 | + <xslt:otherwise> |
| 95 | + <!-- Split $nodes1 and $nodes2 --> |
| 96 | + <xslt:variable name="first1" select="$nodes1[1]" /> |
| 97 | + <xslt:variable name="rest1" select="$nodes1[position()!=1]" /> |
| 98 | + <xslt:variable name="first2" select="$nodes2[1]" /> |
| 99 | + <xslt:variable name="rest2" select="$nodes2[position()!=1]" /> |
| 100 | + <!-- Determine type of node $first1 --> |
| 101 | + <xslt:variable name="type1"> |
| 102 | + <xslt:apply-templates mode="m:detect-type" select="$first1" /> |
| 103 | + </xslt:variable> |
| 104 | + |
| 105 | + <!-- Compare $first1 and $first2 --> |
| 106 | + <xslt:variable name="diff-first"> |
| 107 | + <xslt:call-template name="m:compare-nodes"> |
| 108 | + <xslt:with-param name="node1" select="$first1" /> |
| 109 | + <xslt:with-param name="node2" select="$first2" /> |
| 110 | + </xslt:call-template> |
| 111 | + </xslt:variable> |
| 112 | + |
| 113 | + <xslt:choose> |
| 114 | + <!-- $first1 != $first2 --> |
| 115 | + <xslt:when test="$diff-first='!'"> |
| 116 | + <!-- Compare $first1 and $rest2 --> |
| 117 | + <xslt:variable name="diff-rest"> |
| 118 | + <xslt:for-each select="$rest2"> |
| 119 | + <xslt:call-template name="m:compare-nodes"> |
| 120 | + <xslt:with-param name="node1" select="$first1" /> |
| 121 | + <xslt:with-param name="node2" select="." /> |
| 122 | + </xslt:call-template> |
| 123 | + </xslt:for-each> |
| 124 | + </xslt:variable> |
| 125 | + |
| 126 | + <xslt:choose> |
| 127 | + <!-- $first1 is in $rest2 and |
| 128 | + $first1 is *not* an empty text node --> |
| 129 | + <xslt:when test="contains($diff-rest,'=') and |
| 130 | + not($type1='text' and |
| 131 | + normalize-space($first1)='')"> |
| 132 | + <!-- determine position of $first1 in $nodes2 |
| 133 | + and copy all preceding nodes of $nodes2 --> |
| 134 | + <xslt:variable name="pos" |
| 135 | + select="string-length(substring-before( |
| 136 | + $diff-rest,'=')) + 2" /> |
| 137 | + <xslt:copy-of |
| 138 | + select="$nodes2[position() < $pos]" /> |
| 139 | + <!-- merge $first1 with its equivalent node --> |
| 140 | + <xslt:choose> |
| 141 | + <!-- Elements: merge --> |
| 142 | + <xslt:when test="$type1='element'"> |
| 143 | + <xslt:element name="{name($first1)}" |
| 144 | + namespace="{namespace-uri($first1)}"> |
| 145 | + <xslt:copy-of select="$first1/namespace::*" /> |
| 146 | + <xslt:copy-of select="$first2/namespace::*" /> |
| 147 | + <xslt:copy-of select="$first1/@*" /> |
| 148 | + <xslt:call-template name="m:merge"> |
| 149 | + <xslt:with-param name="nodes1" |
| 150 | + select="$first1/node()" /> |
| 151 | + <xslt:with-param name="nodes2" |
| 152 | + select="$nodes2[position()=$pos]/node()" /> |
| 153 | + </xslt:call-template> |
| 154 | + </xslt:element> |
| 155 | + </xslt:when> |
| 156 | + <!-- Other: copy --> |
| 157 | + <xslt:otherwise> |
| 158 | + <xslt:copy-of select="$first1" /> |
| 159 | + </xslt:otherwise> |
| 160 | + </xslt:choose> |
| 161 | + |
| 162 | + <!-- Merge $rest1 and rest of $nodes2 --> |
| 163 | + <xslt:call-template name="m:merge"> |
| 164 | + <xslt:with-param name="nodes1" select="$rest1" /> |
| 165 | + <xslt:with-param name="nodes2" |
| 166 | + select="$nodes2[position() > $pos]" /> |
| 167 | + </xslt:call-template> |
| 168 | + </xslt:when> |
| 169 | + |
| 170 | + <!-- $first1 is a text node and replace mode was |
| 171 | + activated --> |
| 172 | + <xslt:when test="$type1='text' and $replace"> |
| 173 | + <xslt:call-template name="m:merge"> |
| 174 | + <xslt:with-param name="nodes1" select="$rest1" /> |
| 175 | + <xslt:with-param name="nodes2" select="$nodes2" /> |
| 176 | + </xslt:call-template> |
| 177 | + </xslt:when> |
| 178 | + |
| 179 | + <!-- else: $first1 is not in $rest2 or |
| 180 | + $first1 is an empty text node --> |
| 181 | + <xslt:otherwise> |
| 182 | + <xslt:copy-of select="$first1" /> |
| 183 | + <xslt:call-template name="m:merge"> |
| 184 | + <xslt:with-param name="nodes1" select="$rest1" /> |
| 185 | + <xslt:with-param name="nodes2" select="$nodes2" /> |
| 186 | + </xslt:call-template> |
| 187 | + </xslt:otherwise> |
| 188 | + </xslt:choose> |
| 189 | + </xslt:when> |
| 190 | + |
| 191 | + <!-- else: $first1 = $first2 --> |
| 192 | + <xslt:otherwise> |
| 193 | + <xslt:choose> |
| 194 | + <!-- Elements: merge --> |
| 195 | + <xslt:when test="$type1='element'"> |
| 196 | + <xslt:element name="{name($first1)}" |
| 197 | + namespace="{namespace-uri($first1)}"> |
| 198 | + <xslt:copy-of select="$first1/namespace::*" /> |
| 199 | + <xslt:copy-of select="$first2/namespace::*" /> |
| 200 | + <xslt:copy-of select="$first1/@*" /> |
| 201 | + <xslt:call-template name="m:merge"> |
| 202 | + <xslt:with-param name="nodes1" |
| 203 | + select="$first1/node()" /> |
| 204 | + <xslt:with-param name="nodes2" |
| 205 | + select="$first2/node()" /> |
| 206 | + </xslt:call-template> |
| 207 | + </xslt:element> |
| 208 | + </xslt:when> |
| 209 | + <!-- Other: copy --> |
| 210 | + <xslt:otherwise> |
| 211 | + <xslt:copy-of select="$first1" /> |
| 212 | + </xslt:otherwise> |
| 213 | + </xslt:choose> |
| 214 | + |
| 215 | + <!-- Merge $rest1 and $rest2 --> |
| 216 | + <xslt:call-template name="m:merge"> |
| 217 | + <xslt:with-param name="nodes1" select="$rest1" /> |
| 218 | + <xslt:with-param name="nodes2" select="$rest2" /> |
| 219 | + </xslt:call-template> |
| 220 | + </xslt:otherwise> |
| 221 | + </xslt:choose> |
| 222 | + </xslt:otherwise> |
| 223 | + </xslt:choose> |
| 224 | +</xslt:template> |
| 225 | + |
| 226 | + |
| 227 | +<!-- Comparing single nodes: |
| 228 | + if $node1 and $node2 are equivalent then the template creates a |
| 229 | + text node "=" otherwise a text node "!" --> |
| 230 | +<xslt:template name="m:compare-nodes"> |
| 231 | + <xslt:param name="node1" /> |
| 232 | + <xslt:param name="node2" /> |
| 233 | + <xslt:variable name="type1"> |
| 234 | + <xslt:apply-templates mode="m:detect-type" select="$node1" /> |
| 235 | + </xslt:variable> |
| 236 | + <xslt:variable name="type2"> |
| 237 | + <xslt:apply-templates mode="m:detect-type" select="$node2" /> |
| 238 | + </xslt:variable> |
| 239 | + |
| 240 | + <xslt:choose> |
| 241 | + <!-- Are $node1 and $node2 element nodes with the same name? --> |
| 242 | + <xslt:when test="$type1='element' and $type2='element' and |
| 243 | + local-name($node1)=local-name($node2) and |
| 244 | + namespace-uri($node1)=namespace-uri($node2) and |
| 245 | + name($node1)!=$dontmerge and name($node2)!=$dontmerge"> |
| 246 | + <!-- Comparing the attributes --> |
| 247 | + <xslt:variable name="diff-att"> |
| 248 | + <!-- same number ... --> |
| 249 | + <xslt:if test="count($node1/@*)!=count($node2/@*)">.</xslt:if> |
| 250 | + <!-- ... and same name/content --> |
| 251 | + <xslt:for-each select="$node1/@*"> |
| 252 | + <xslt:if test="not($node2/@* |
| 253 | + [local-name()=local-name(current()) and |
| 254 | + namespace-uri()=namespace-uri(current()) and |
| 255 | + .=current()])">.</xslt:if> |
| 256 | + </xslt:for-each> |
| 257 | + </xslt:variable> |
| 258 | + <xslt:choose> |
| 259 | + <xslt:when test="string-length($diff-att)!=0">!</xslt:when> |
| 260 | + <xslt:otherwise>=</xslt:otherwise> |
| 261 | + </xslt:choose> |
| 262 | + </xslt:when> |
| 263 | + |
| 264 | + <!-- Other nodes: test for the same type and content --> |
| 265 | + <xslt:when test="$type1!='element' and $type1=$type2 and |
| 266 | + name($node1)=name($node2) and |
| 267 | + ($node1=$node2 or |
| 268 | + ($normalize='yes' and |
| 269 | + normalize-space($node1)= |
| 270 | + normalize-space($node2)))">=</xslt:when> |
| 271 | + |
| 272 | + <!-- Otherwise: different node types or different name/content --> |
| 273 | + <xslt:otherwise>!</xslt:otherwise> |
| 274 | + </xslt:choose> |
| 275 | +</xslt:template> |
| 276 | + |
| 277 | + |
| 278 | +<!-- Type detection, thanks to M. H. Kay --> |
| 279 | +<xslt:template match="*" mode="m:detect-type">element</xslt:template> |
| 280 | +<xslt:template match="text()" mode="m:detect-type">text</xslt:template> |
| 281 | +<xslt:template match="comment()" mode="m:detect-type">comment</xslt:template> |
| 282 | +<xslt:template match="processing-instruction()" mode="m:detect-type">pi</xslt:template> |
| 283 | + |
| 284 | +</xslt:transform> |
0 commit comments