1
+ /*
2
+ * Copyright 2017 Michael Stringer
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
1
17
package software .purpledragon .xml .compare
2
18
3
19
import software .purpledragon .xml .compare .options .DiffOption ._
4
20
import software .purpledragon .xml .compare .options .DiffOptions
5
21
6
22
import scala .xml .{Node , Text }
7
23
24
+ /**
25
+ * Utility for comparing XML documents.
26
+ */
8
27
object XmlCompare {
9
- private type Check = (Node , Node , DiffOptions ) => XmlDiff
28
+ private type Check = (Node , Node , DiffOptions , Seq [ String ] ) => XmlDiff
10
29
11
- val DefaultOptions : DiffOptions = Set (IgnorePrefix )
30
+ /**
31
+ * Default [[software.purpledragon.xml.compare.options.DiffOption.DiffOption DiffOption ]]s to use during XML comparison.
32
+ *
33
+ * Currently these are:
34
+ * - [[software.purpledragon.xml.compare.options.DiffOption.IgnoreNamespacePrefix IgnoreNamespacePrefix ]]
35
+ */
36
+ val DefaultOptions : DiffOptions = Set (IgnoreNamespacePrefix )
12
37
38
+ /**
39
+ * Compares two XML documents. This will perform a recursive scan of all the nodes in each document, checking each
40
+ * for node name, namespace and text.
41
+ *
42
+ * @param left the first XML document to compare.
43
+ * @param right the second XML document to compare.
44
+ * @param options configuration options to control the way the comparison is performed.
45
+ * @return results of the XML comparison.
46
+ */
13
47
def compare (left : Node , right : Node , options : DiffOptions = DefaultOptions ): XmlDiff = {
48
+ compareNodes(left, right, options, Nil )
49
+ }
50
+
51
+ private def compareNodes (left : Node , right : Node , options : DiffOptions , path : Seq [String ]): XmlDiff = {
14
52
val checks : Seq [Check ] = Seq (
15
53
compareNamespace,
16
54
compareText,
@@ -22,51 +60,55 @@ object XmlCompare {
22
60
// already failed
23
61
status
24
62
} else {
25
- check(left, right, options)
63
+ check(left, right, options, path )
26
64
}
27
65
}
28
66
}
29
67
30
- private def compareNamespace (left : Node , right : Node , options : DiffOptions ): XmlDiff = {
68
+ private def compareNamespace (left : Node , right : Node , options : DiffOptions , path : Seq [ String ] ): XmlDiff = {
31
69
if (left.label != right.label) {
32
- XmlDiffers (" different label" , left.label, right.label)
33
- } else if (left.namespace != right.namespace) {
34
- XmlDiffers (" different namespace" , left.namespace, right.namespace)
35
- } else if (left.prefix != right.prefix && ! options.contains(IgnorePrefix )) {
36
- XmlDiffers (" different prefix" , left.prefix, right.prefix)
70
+ XmlDiffers (" different label" , left.label, right.label, extendPath(path, left))
71
+ } else if (left.namespace != right.namespace && ! options.contains(IgnoreNamespace )) {
72
+ XmlDiffers (" different namespace" , left.namespace, right.namespace, extendPath(path, left))
73
+ } else if (left.prefix != right.prefix && ! options.contains(IgnoreNamespacePrefix ) &&
74
+ ! options.contains(IgnoreNamespace )) {
75
+ XmlDiffers (" different namespace prefix" , left.prefix, right.prefix, extendPath(path, left))
37
76
} else {
38
77
XmlEqual
39
78
}
40
79
}
41
80
42
- private def compareText (left : Node , right : Node , options : DiffOptions ): XmlDiff = {
43
- val leftText = left.child.collect({case t : Text => t}).map(_.text.trim).mkString
44
- val rightText = right.child.collect({case t : Text => t}).map(_.text.trim).mkString
81
+ private def compareText (left : Node , right : Node , options : DiffOptions , path : Seq [ String ] ): XmlDiff = {
82
+ val leftText = left.child.collect({ case t : Text => t }).map(_.text.trim).mkString
83
+ val rightText = right.child.collect({ case t : Text => t }).map(_.text.trim).mkString
45
84
46
85
if (leftText != rightText) {
47
- XmlDiffers (" different text" , leftText, rightText)
86
+ XmlDiffers (" different text" , leftText, rightText, extendPath(path, left) )
48
87
} else {
49
88
XmlEqual
50
89
}
51
90
}
52
91
53
- private def compareChildren (left : Node , right : Node , options : DiffOptions ): XmlDiff = {
92
+ private def compareChildren (left : Node , right : Node , options : DiffOptions , path : Seq [ String ] ): XmlDiff = {
54
93
val leftChildren = left.child.filterNot(_.isInstanceOf [Text ])
55
94
val rightChildren = right.child.filterNot(_.isInstanceOf [Text ])
56
95
57
96
if (leftChildren.size != rightChildren.size) {
58
- XmlDiffers (" child count" , leftChildren.size, rightChildren.size)
97
+ XmlDiffers (" different child count" , leftChildren.size, rightChildren.size, extendPath(path, left) )
59
98
} else {
60
- val matchedChildren = leftChildren.zip(rightChildren)
61
-
62
- matchedChildren.foldLeft[XmlDiff ](XmlEqual ) { case (status, (leftChild, rightChild)) =>
63
- if (! status.isEqual) {
64
- // already failed
65
- status
66
- } else {
67
- compare(leftChild, rightChild, options)
68
- }
99
+ leftChildren.zip(rightChildren).foldLeft[XmlDiff ](XmlEqual ) {
100
+ case (status, (leftChild, rightChild)) =>
101
+ if (! status.isEqual) {
102
+ // already failed
103
+ status
104
+ } else {
105
+ compareNodes(leftChild, rightChild, options, extendPath(path, left))
106
+ }
69
107
}
70
108
}
71
109
}
110
+
111
+ private def extendPath (path : Seq [String ], node : Node ): Seq [String ] = {
112
+ path :+ node.nameToString(new StringBuilder ()).toString
113
+ }
72
114
}
0 commit comments