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+
117package software .purpledragon .xml .compare
218
319import software .purpledragon .xml .compare .options .DiffOption ._
420import software .purpledragon .xml .compare .options .DiffOptions
521
622import scala .xml .{Node , Text }
723
24+ /**
25+ * Utility for comparing XML documents.
26+ */
827object XmlCompare {
9- private type Check = (Node , Node , DiffOptions ) => XmlDiff
28+ private type Check = (Node , Node , DiffOptions , Seq [ String ] ) => XmlDiff
1029
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 )
1237
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+ */
1347 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 = {
1452 val checks : Seq [Check ] = Seq (
1553 compareNamespace,
1654 compareText,
@@ -22,51 +60,55 @@ object XmlCompare {
2260 // already failed
2361 status
2462 } else {
25- check(left, right, options)
63+ check(left, right, options, path )
2664 }
2765 }
2866 }
2967
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 = {
3169 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))
3776 } else {
3877 XmlEqual
3978 }
4079 }
4180
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
4584
4685 if (leftText != rightText) {
47- XmlDiffers (" different text" , leftText, rightText)
86+ XmlDiffers (" different text" , leftText, rightText, extendPath(path, left) )
4887 } else {
4988 XmlEqual
5089 }
5190 }
5291
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 = {
5493 val leftChildren = left.child.filterNot(_.isInstanceOf [Text ])
5594 val rightChildren = right.child.filterNot(_.isInstanceOf [Text ])
5695
5796 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) )
5998 } 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+ }
69107 }
70108 }
71109 }
110+
111+ private def extendPath (path : Seq [String ], node : Node ): Seq [String ] = {
112+ path :+ node.nameToString(new StringBuilder ()).toString
113+ }
72114}
0 commit comments