Skip to content

Commit c9da39b

Browse files
committed
#ODOC-85 Added handling for text nodes
1 parent 402f789 commit c9da39b

File tree

6 files changed

+67
-63
lines changed

6 files changed

+67
-63
lines changed

src/main/java/io/fixprotocol/xml/PatchOpsListener.java

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.w3c.dom.Element;
3535
import org.w3c.dom.Node;
3636
import org.w3c.dom.Text;
37+
import io.fixprotocol.orchestra.event.EventListener;
3738

3839
/**
3940
* Writes XML diffs as patch operations specified by IETF RFC 5261
@@ -45,28 +46,31 @@
4546
public class PatchOpsListener implements XmlDiffListener {
4647

4748
private final OutputStreamWriter writer;
48-
private final Document document;
49+
private final Document diffDocument;
4950
private final Element rootElement;
5051
private final AtomicBoolean isClosed = new AtomicBoolean();
52+
private final EventListener eventLogger;
5153

5254
/**
5355
* Constructs a listener with an output stream
56+
* @param eventListener
5457
*
5558
* @throws IOException if an IO error occurs
5659
* @throws ParserConfigurationException if a configuration error occurs
5760
* @throws TransformerConfigurationException if a configuration error occurs
5861
*
5962
*/
60-
public PatchOpsListener(OutputStream out)
63+
public PatchOpsListener(OutputStream out, EventListener eventListener)
6164
throws IOException, ParserConfigurationException, TransformerConfigurationException {
65+
this.eventLogger = eventListener;
6266
writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
6367

6468
final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
6569
dbFactory.setNamespaceAware(true);
6670
final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
67-
document = dBuilder.newDocument();
68-
rootElement = document.createElement("diff");
69-
document.appendChild(rootElement);
71+
diffDocument = dBuilder.newDocument();
72+
rootElement = diffDocument.createElement("diff");
73+
diffDocument.appendChild(rootElement);
7074
}
7175

7276
/*
@@ -79,14 +83,14 @@ public void accept(Event t) {
7983

8084
switch (t.getDifference()) {
8185
case ADD:
82-
final Element addElement = document.createElement("add");
86+
final Element addElement = diffDocument.createElement("add");
8387
rootElement.appendChild(addElement);
8488

8589
if (t.getValue() instanceof Attr) {
8690
// add attribute
8791
addElement.setAttribute("sel", t.getXpath());
8892
addElement.setAttribute("type", "@" + t.getValue().getNodeName());
89-
final Text textNode = document.createTextNode(t.getValue().getNodeValue());
93+
final Text textNode = diffDocument.createTextNode(t.getValue().getNodeValue());
9094
addElement.appendChild(textNode);
9195
} else if (t.getValue() instanceof Element) {
9296
// add element
@@ -95,30 +99,38 @@ public void accept(Event t) {
9599
addElement.setAttribute("pos", t.getPos().toString());
96100
}
97101
// will import child text node if it exists (deep copy)
98-
final Element newValue = (Element) document.importNode(t.getValue(), true);
102+
final Element newValue = (Element) diffDocument.importNode(t.getValue(), true);
99103
addElement.appendChild(newValue);
104+
} else if (t.getValue() instanceof Text) {
105+
addElement.setAttribute("sel", t.getXpath());
106+
Node text = diffDocument.importNode(t.getValue(), false);
107+
addElement.appendChild(text);
108+
} else {
109+
eventLogger.error("Unhandled node type {0} for add", t.getValue().getNodeType());
100110
}
101111

102112
break;
103113
case REPLACE:
104-
final Element replaceElement = document.createElement("replace");
114+
final Element replaceElement = diffDocument.createElement("replace");
105115
rootElement.appendChild(replaceElement);
106116

107117
if (t.getValue() instanceof Attr) {
108118
// replace attribute
109119
replaceElement.setAttribute("sel", t.getXpath());
110-
final Text textNode = document.createTextNode(t.getValue().getNodeValue());
120+
final Text textNode = diffDocument.createTextNode(t.getValue().getNodeValue());
111121
replaceElement.appendChild(textNode);
112-
} else {
122+
} else if (t.getValue() instanceof Element || t.getValue() instanceof Text) {
113123
// replace element
114124
replaceElement.setAttribute("sel", t.getXpath());
115125
// will import child text node if it exists
116-
final Node newValue = document.importNode(t.getValue(), true);
126+
final Node newValue = diffDocument.importNode(t.getValue(), true);
117127
replaceElement.appendChild(newValue);
128+
} else {
129+
eventLogger.error("Unhandled node type {0} for replace", t.getValue().getNodeType());
118130
}
119131
break;
120132
case REMOVE:
121-
final Element removeElement = document.createElement("remove");
133+
final Element removeElement = diffDocument.createElement("remove");
122134
rootElement.appendChild(removeElement);
123135
removeElement.setAttribute("sel", t.getXpath());
124136
break;
@@ -139,7 +151,7 @@ public void close() throws Exception {
139151
final TransformerFactory transformerFactory = TransformerFactory.newInstance();
140152
final Transformer transformer = transformerFactory.newTransformer();
141153
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
142-
final DOMSource source = new DOMSource(document);
154+
final DOMSource source = new DOMSource(diffDocument);
143155
final StreamResult result = new StreamResult(writer);
144156
transformer.transform(source, result);
145157
writer.close();

src/main/java/io/fixprotocol/xml/XmlDiff.java

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,12 @@ public int compare(Element n1, Element n2) {
9191
*
9292
* @param args file names of two XML files to compare and optional name of difference file. If
9393
* diff file is not provided, then output goes to console.
94-
*
95-
* Optionally, argument '-u' treats XML nodes as unordered so moves among children are not considered significant. Otherwise,
96-
* a move will result in an add and a remove operation.
97-
*
98-
* Optionally, argument '-e <event-filename>' can direct errors to a JSON file suitable for UI rendering.
94+
*
95+
* Optionally, argument '-u' treats XML nodes as unordered so moves among children are not
96+
* considered significant. Otherwise, a move will result in an add and a remove operation.
97+
*
98+
* Optionally, argument '-e <event-filename>' can direct errors to a JSON file suitable for
99+
* UI rendering.
99100
*/
100101
public static void main(String[] args) {
101102
if (args.length < 2) {
@@ -123,11 +124,12 @@ public static void main(String[] args) {
123124
out = new FileOutputStream(args[i]);
124125
}
125126
}
126-
final PatchOpsListener aListener = new PatchOpsListener(out);
127-
tool.setListener(aListener);
127+
128128
tool.addEventLogger(eventFilename != null ? new FileOutputStream(eventFilename) : null);
129+
final PatchOpsListener aListener = new PatchOpsListener(out, tool.getEventLogger());
130+
tool.setListener(aListener);
129131
tool.diff(is1, is2);
130-
System.exit(0);
132+
tool.eventLogger.info("XmlDiff completed");
131133
} catch (final Exception e) {
132134
logger.fatal("XmlDiff failed", e);
133135
System.exit(1);
@@ -158,8 +160,8 @@ public XmlDiff() {
158160
final EventListener logEventLogger = factory.getInstance("LOG4J");
159161
try {
160162
logEventLogger.setResource(logger);
161-
} catch (final Exception e) {
162163
eventLogger.addEventListener(logEventLogger);
164+
} catch (final Exception e) {
163165
e.printStackTrace();
164166
}
165167
}
@@ -191,7 +193,6 @@ public void diff(InputStream is1, InputStream is2) throws Exception {
191193
System.exit(1);
192194
}
193195
listener.close();
194-
eventLogger.info("XmlDiff complete");
195196
}
196197
}
197198

@@ -229,6 +230,10 @@ public void diff(InputStream is1, String xpathString1, InputStream is2, String x
229230
}
230231
}
231232

233+
public EventListener getEventLogger() {
234+
return eventLogger;
235+
}
236+
232237
/**
233238
* Compare XML elements with regard to order of children.
234239
*
@@ -413,17 +418,18 @@ private boolean diffText(Element element1, Element element2) {
413418
if (child2 == null || Node.TEXT_NODE != child2.getNodeType()) {
414419
listener.accept(Event.remove(XpathUtil.getFullXPath(child1)));
415420
} else {
416-
final int valueCompare =
417-
child1.getNodeValue().trim().compareTo(child2.getNodeValue().trim());
421+
final int valueCompare = stripWhitespace(child1.getNodeValue())
422+
.compareTo(stripWhitespace(child2.getNodeValue()));
418423

419424
if (valueCompare != 0) {
420-
listener.accept(Event.replace(XpathUtil.getFullXPath(element1), child2, child1));
425+
listener.accept(Event.replace(XpathUtil.getFullXPath(child1), child2, child1));
421426
} else {
422427
return true;
423428
}
424429
}
425-
} else if (child2 != null && Node.TEXT_NODE == child2.getNodeType()) {
426-
listener.accept(Event.add(XpathUtil.getFullXPath(element2), child2, append));
430+
} else if (child2 != null && Node.TEXT_NODE == child2.getNodeType()
431+
&& stripWhitespace(child2.getNodeValue()).length() > 0) {
432+
listener.accept(Event.add(XpathUtil.getFullXPath(child2), child2, append));
427433
}
428434
return false;
429435
}
@@ -466,4 +472,8 @@ private ArrayList<Attr> sortAttributes(NamedNodeMap attributes, ArrayList<Attr>
466472
return nodeArray;
467473
}
468474

475+
private String stripWhitespace(String str) {
476+
return str.replaceAll("\\s", "");
477+
}
478+
469479
}

src/main/java/io/fixprotocol/xml/XmlDiffListener.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ enum Pos {
5353
* @param pos position to add or insert new Node
5454
*/
5555
static Event add(String xpath, Node value, Pos pos) {
56-
return new Event(Difference.ADD, xpath, Objects.requireNonNull(value, "Node to add missing"),
57-
null, pos);
56+
return new Event(Difference.ADD,
57+
Objects.requireNonNull(xpath, "XPath target for add missing"),
58+
Objects.requireNonNull(value, "Node to add missing"), null, pos);
5859
}
5960

6061
/**

src/main/java/io/fixprotocol/xml/XmlMerge.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ private void add(Document doc, XPath xpathEvaluator, Element patchOpElement) {
236236
}
237237
} catch (final XPathExpressionException e) {
238238
errors++;
239-
eventLogger.error("Invalid XPath expression for add; {1}", xpathExpression);
239+
eventLogger.error("Invalid XPath expression for add; {0}", xpathExpression);
240240
}
241241
}
242242

@@ -305,6 +305,7 @@ private void replace(final Document doc, XPath xpathEvaluator, Element patchOpEl
305305
siteNode.appendChild(text);
306306
break;
307307
case Node.ATTRIBUTE_NODE:
308+
case Node.TEXT_NODE:
308309
siteNode.setNodeValue(value);
309310
break;
310311
}

src/main/java/io/fixprotocol/xml/XpathUtil.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ public static String getFullXPath(Node n) {
125125
break;
126126
case Node.ELEMENT_NODE:
127127
case Node.DOCUMENT_NODE:
128+
case Node.TEXT_NODE:
128129
parent = n.getParentNode();
129130
break;
130131
default:
@@ -192,6 +193,9 @@ public static String getFullXPath(Node n) {
192193
buffer.append("/@");
193194
buffer.append(node.getNodeName());
194195
break;
196+
case Node.TEXT_NODE:
197+
buffer.append("/text()[1]");
198+
break;
195199
}
196200
}
197201
// return buffer

src/test/java/io/fixprotocol/xml/XmlDiffTest.java

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ public void removeContext(org.apache.logging.log4j.spi.LoggerContext context) {
7272

7373
public class XmlDiffTest {
7474

75-
private XmlDiff xmlDiff;
7675
private XmlMerge xmlMerge;
7776

7877
@BeforeAll
@@ -86,25 +85,16 @@ public static void setupOnce() throws Exception {
8685
*/
8786
@BeforeEach
8887
public void setUp() throws Exception {
89-
xmlDiff = new XmlDiff();
90-
91-
xmlMerge = new XmlMerge();
88+
xmlMerge = new XmlMerge();
9289
}
9390

9491
@Test
9592
public void unordered() throws Exception {
9693

9794
final String mergedFilename = "target/test/unorderedmerged.xml";
9895
final String diffFilename = "target/test/unordereddiff.xml";
99-
try (
100-
final FileInputStream is1 = new FileInputStream(
101-
Thread.currentThread().getContextClassLoader().getResource("DiffTest1.xml").getFile());
102-
final FileInputStream is2 = new FileInputStream(Thread.currentThread()
103-
.getContextClassLoader().getResource("DiffTest2.xml").getFile())) {
104-
xmlDiff.setListener(new PatchOpsListener(new FileOutputStream(diffFilename)));
105-
xmlDiff.setAreElementsOrdered(false);
106-
xmlDiff.diff(is1, is2);
107-
}
96+
XmlDiff.main(new String[] {"src/test/resources/DiffTest1.xml", "src/test/resources/DiffTest2.xml", diffFilename, "-u"});
97+
10898

10999
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
110100
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
@@ -135,15 +125,8 @@ public void unordered() throws Exception {
135125
public void ordered() throws Exception {
136126
final String mergedFilename = "target/test/orderedmerged.xml";
137127
final String diffFilename = "target/test/ordereddiff.xml";
138-
try (
139-
final FileInputStream is1 = new FileInputStream(
140-
Thread.currentThread().getContextClassLoader().getResource("DiffTest1.xml").getFile());
141-
final FileInputStream is2 = new FileInputStream(Thread.currentThread()
142-
.getContextClassLoader().getResource("DiffTest2.xml").getFile())) {
143-
xmlDiff.setListener(new PatchOpsListener(new FileOutputStream(diffFilename)));
144-
xmlDiff.setAreElementsOrdered(true);
145-
xmlDiff.diff(is1, is2);
146-
}
128+
XmlDiff.main(new String[] {"src/test/resources/DiffTest1.xml", "src/test/resources/DiffTest2.xml", diffFilename});
129+
147130

148131
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
149132
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
@@ -165,15 +148,8 @@ public void ordered() throws Exception {
165148
@Test
166149
public void epDiff() throws Exception {
167150
final String mergedFilename = "target/test/roundtripmerged.xml";
168-
final String diffFilename = "target/test/roundtripdiff.xml";
169-
try (
170-
final FileInputStream is1 = new FileInputStream(Thread.currentThread()
171-
.getContextClassLoader().getResource("FixRepository2016EP215.xml").getFile());
172-
final FileInputStream is2 = new FileInputStream(Thread.currentThread()
173-
.getContextClassLoader().getResource("FixRepository2016EP216.xml").getFile())) {
174-
xmlDiff.setListener(new PatchOpsListener(new FileOutputStream(diffFilename)));
175-
xmlDiff.diff(is1, is2);
176-
}
151+
final String diffFilename = "target/test/roundtripdiff.xml";
152+
XmlDiff.main(new String[] {"src/test/resources/FixRepository2016EP215.xml", "src/test/resources/FixRepository2016EP216.xml", diffFilename});
177153

178154
try (
179155
final FileInputStream is1Baseline = new FileInputStream(Thread.currentThread()

0 commit comments

Comments
 (0)