Skip to content

Commit

Permalink
Try recovery from exceptions #6
Browse files Browse the repository at this point in the history
  • Loading branch information
donmendelson committed Feb 18, 2021
1 parent 4c5e7bf commit 7f10375
Show file tree
Hide file tree
Showing 10 changed files with 430 additions and 325 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ java io.fixprotocol.xml.XmlDiff <in-file1> <in-file2> [output-file]
```
If the output file is not provided, then results go to the console.

Optionally, argument `-e <event-filename>` can direct errors to a JSON file suitable for UI rendering.

Optionally, argument `-u` treats XML nodes as unordered so moves among children are not considered significant. Otherwise, a move will result in an add and a remove operation.

## Merge

The XmlMerge utility takes a base XML file and a difference file and merges the two to produce an new XML file.
Expand All @@ -31,9 +35,17 @@ To run the merge utility, run this command line:
```
java io.fixprotocol.xml.XmlMerge <base-xml-file> <diff-file> <output-xml-file>
```
Optionally, argument `-e <event-filename>` can direct errors to a JSON file suitable for UI rendering.

The merge utility attempts to continue even if errors occur. Examples of logged errors:

```
11:12:53.019 [main] ERROR io.fixprotocol.xml.XmlMerge - Target not found for add; /foo
11:12:53.035 [main] ERROR io.fixprotocol.xml.XmlMerge - Invalid XPath expression for remove; /aaa/fff[
```

## License
© Copyright 2017-2019 FIX Protocol Limited
© Copyright 2017-2021 FIX Protocol Limited

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<groupId>io.fixprotocol.xml</groupId>
<artifactId>diff-merge</artifactId>
<description>Diff/merge tool for XML files</description>
<version>1.4.1-SNAPSHOT</version>
<version>1.5.1-SNAPSHOT</version>
<name>${project.groupId}:${project.artifactId}</name>

<prerequisites>
Expand All @@ -17,6 +17,7 @@
<java.version>11</java.version>
<junit.version>5.7.1</junit.version>
<log4j.version>2.14.0</log4j.version>
<orchestra.version>1.6.10</orchestra.version>
<saxon.version>10.3</saxon.version>
<ignoreSigningInformation>true</ignoreSigningInformation>
</properties>
Expand Down Expand Up @@ -64,6 +65,11 @@
<artifactId>Saxon-HE</artifactId>
<version>${saxon.version}</version>
</dependency>
<dependency>
<groupId>io.fixprotocol.orchestra</groupId>
<artifactId>orchestra-common</artifactId>
<version>${orchestra.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
Expand Down
39 changes: 21 additions & 18 deletions src/main/java/io/fixprotocol/xml/CustomNamespaceContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,13 @@ public CustomNamespaceContext() {
namespaces.put("xml", XMLConstants.XML_NS_URI);
namespaces.put("xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
}

public void populate(Document doc) {
final Element baselineRoot = doc.getDocumentElement();
final NamedNodeMap rootAttributes = baselineRoot.getAttributes();

for (int i = 0; i < rootAttributes.getLength(); i++) {
final Attr attr = (Attr) rootAttributes.item(i);
final String prefix = attr.getPrefix();
if ("xmlns".equals(prefix)) {
register(attr.getLocalName(), attr.getValue());
} else if ("xmlns".equals(attr.getLocalName())) {
// default namespace
register(XMLConstants.DEFAULT_NS_PREFIX, attr.getValue());
}
}
}

@Override
public String getNamespaceURI(String prefix) {
if (prefix == null) {
throw new NullPointerException("Null prefix");
}
String uri = namespaces.get(prefix);
final String uri = namespaces.get(prefix);
if (uri != null) {
return uri;
} else {
Expand All @@ -62,17 +47,35 @@ public String getNamespaceURI(String prefix) {
}

// This method isn't necessary for XPath processing.
@Override
public String getPrefix(String uri) {
throw new UnsupportedOperationException();
}

// This method isn't necessary for XPath processing either.
@Override
public Iterator<String> getPrefixes(String uri) {
throw new UnsupportedOperationException();
}

public void populate(Document doc) {
final Element baselineRoot = doc.getDocumentElement();
final NamedNodeMap rootAttributes = baselineRoot.getAttributes();

for (int i = 0; i < rootAttributes.getLength(); i++) {
final Attr attr = (Attr) rootAttributes.item(i);
final String prefix = attr.getPrefix();
if ("xmlns".equals(prefix)) {
register(attr.getLocalName(), attr.getValue());
} else if ("xmlns".equals(attr.getLocalName())) {
// default namespace
register(XMLConstants.DEFAULT_NS_PREFIX, attr.getValue());
}
}
}

public void register(String prefix, String uri) {
namespaces.put(prefix, uri);
}

}
}
40 changes: 19 additions & 21 deletions src/main/java/io/fixprotocol/xml/PatchOpsListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
*/
package io.fixprotocol.xml;

import static io.fixprotocol.xml.XmlDiffListener.Event.Pos.append;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
Expand All @@ -29,18 +29,15 @@
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

import static io.fixprotocol.xml.XmlDiffListener.Event.Pos.*;

/**
* Writes XML diffs as patch operations specified by IETF RFC 5261
*
*
* @author Don Mendelson
* @see <a href="https://tools.ietf.org/html/rfc5261">An Extensible Markup Language (XML) Patch
* Operations Framework Utilizing XML Path Language (XPath) Selectors</a>
Expand All @@ -54,41 +51,42 @@ public class PatchOpsListener implements XmlDiffListener {

/**
* Constructs a listener with an output stream
*
* @throws IOException if an IO error occurs
* @throws ParserConfigurationException if a configuration error occurs
* @throws TransformerConfigurationException if a configuration error occurs
*
*
*/
public PatchOpsListener(OutputStream out)
throws IOException, ParserConfigurationException, TransformerConfigurationException {
writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);

DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
dbFactory.setNamespaceAware(true);
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
document = dBuilder.newDocument();
rootElement = document.createElement("diff");
document.appendChild(rootElement);
}

/*
* (non-Javadoc)
*
*
* @see java.util.function.Consumer#accept(java.lang.Object)
*/
@Override
public void accept(Event t) {

switch (t.getDifference()) {
case ADD:
Element addElement = document.createElement("add");
final Element addElement = document.createElement("add");
rootElement.appendChild(addElement);

if (t.getValue() instanceof Attr) {
// add attribute
addElement.setAttribute("sel", t.getXpath());
addElement.setAttribute("type", "@" + t.getValue().getNodeName());
Text textNode = document.createTextNode(t.getValue().getNodeValue());
final Text textNode = document.createTextNode(t.getValue().getNodeValue());
addElement.appendChild(textNode);
} else if (t.getValue() instanceof Element) {
// add element
Expand All @@ -97,30 +95,30 @@ public void accept(Event t) {
addElement.setAttribute("pos", t.getPos().toString());
}
// will import child text node if it exists (deep copy)
Element newValue = (Element) document.importNode(t.getValue(), true);
final Element newValue = (Element) document.importNode(t.getValue(), true);
addElement.appendChild(newValue);
}

break;
case REPLACE:
Element replaceElement = document.createElement("replace");
final Element replaceElement = document.createElement("replace");
rootElement.appendChild(replaceElement);

if (t.getValue() instanceof Attr) {
// replace attribute
replaceElement.setAttribute("sel", t.getXpath());
Text textNode = document.createTextNode(t.getValue().getNodeValue());
final Text textNode = document.createTextNode(t.getValue().getNodeValue());
replaceElement.appendChild(textNode);
} else {
// replace element
replaceElement.setAttribute("sel", t.getXpath());
// will import child text node if it exists
Node newValue = document.importNode(t.getValue(), true);
final Node newValue = document.importNode(t.getValue(), true);
replaceElement.appendChild(newValue);
}
break;
case REMOVE:
Element removeElement = document.createElement("remove");
final Element removeElement = document.createElement("remove");
rootElement.appendChild(removeElement);
removeElement.setAttribute("sel", t.getXpath());
break;
Expand All @@ -131,18 +129,18 @@ public void accept(Event t) {

/*
* (non-Javadoc)
*
*
* @see java.lang.AutoCloseable#close()
*/
@Override
public void close() throws Exception {
// Idempotent - only close once
if (isClosed.compareAndSet(false, true)) {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
final TransformerFactory transformerFactory = TransformerFactory.newInstance();
final Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
DOMSource source = new DOMSource(document);
StreamResult result = new StreamResult(writer);
final DOMSource source = new DOMSource(document);
final StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
writer.close();
}
Expand Down
Loading

0 comments on commit 7f10375

Please sign in to comment.