Skip to content

Commit

Permalink
Merge pull request #70 from damianszczepanik/xml-support
Browse files Browse the repository at this point in the history
Added support for XMLProcessor
  • Loading branch information
damianszczepanik committed Jan 4, 2016
2 parents b5e78d9 + 0f19c95 commit c116554
Show file tree
Hide file tree
Showing 16 changed files with 492 additions and 10 deletions.
1 change: 0 additions & 1 deletion config/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@

<!-- Checks for common coding problems -->
<!-- See http://checkstyle.sf.net/config_coding.html -->
<module name="AvoidInlineConditionals"/>
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="IllegalInstantiation"/>
Expand Down
4 changes: 1 addition & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@
<url>[email protected]:damianszczepanik/silencio.git</url>
</scm>

<description>
Silencio is a tool for transforming and converting JSON files.
</description>
<description>Silencio is a tool for transforming and converting JSON, XML and Properties files.</description>

<licenses>
<license>
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/pl/szczepanik/silencio/core/Builder.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import pl.szczepanik.silencio.decisions.PositiveDecision;
import pl.szczepanik.silencio.processors.JSONProcessor;
import pl.szczepanik.silencio.processors.PropertiesProcessor;
import pl.szczepanik.silencio.processors.XMLProcessor;

/**
* Default implementation of class that holds processors.
Expand Down Expand Up @@ -110,6 +111,8 @@ public Processor build() {
Processor processor;
if (Format.JSON.equals(format)) {
processor = new JSONProcessor();
} else if (Format.XML.equals(format)) {
processor = new XMLProcessor();
} else if (Format.PROPERTIES.equals(format)) {
processor = new PropertiesProcessor();
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
import pl.szczepanik.silencio.api.Decision;
import pl.szczepanik.silencio.api.Processor;
import pl.szczepanik.silencio.converters.BlankConverter;
import pl.szczepanik.silencio.converters.GeoLocationConverter;
import pl.szczepanik.silencio.converters.WikipediaConverter;
import pl.szczepanik.silencio.core.Configuration;
import pl.szczepanik.silencio.core.Execution;
import pl.szczepanik.silencio.core.ProcessorException;
Expand Down
75 changes: 75 additions & 0 deletions src/main/java/pl/szczepanik/silencio/processors/XMLProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package pl.szczepanik.silencio.processors;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import pl.szczepanik.silencio.api.Format;
import pl.szczepanik.silencio.core.ProcessorException;
import pl.szczepanik.silencio.processors.visitors.XMLVisitor;

/**
* Provides processor that supports XML format.
*
* @author Damian Szczepanik (damianszczepanik@github)
*/
public class XMLProcessor extends AbstractProcessor {

private final XMLVisitor visitor = new XMLVisitor();

private Document document = null;

/** Creates new processor for XML file. */
public XMLProcessor() {
super(Format.XML);
}

@Override
public void realLoad(Reader reader) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.parse(new InputSource(reader));
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new ProcessorException(e);
}
}

@Override
public void realProcess() {
visitor.setConfiguration(configuration);
Element rootElement = document.getDocumentElement();
// optional, but recommended
// http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-how-does-it-work
rootElement.normalize();

visitor.processXML(rootElement);
}

@Override
public void realWrite(Writer writer) {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
try {
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(document);
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
} catch (TransformerException e) {
throw new ProcessorException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package pl.szczepanik.silencio.processors.visitors;

import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import pl.szczepanik.silencio.core.Key;
import pl.szczepanik.silencio.core.ProcessorException;
import pl.szczepanik.silencio.core.Value;

/**
* Iterates over XML nodes and calls {@link #processValue(Key, Object)} for each basic node.
*
* @author Damian Szczepanik (damianszczepanik@github)
*/
public class XMLVisitor extends AbstractVisitor {

static final String EXCEPTION_MESSAGE_NODE_TYPE_UNSUPPORTED = "Node with type %d, name '%s' and value '%s' is not supported";

/**
* Process passed JSON map and iterates over each node.
*
* @param rootElement
* XML root element to iterate over
*/
public void processXML(Element rootElement) {
processAttributes(rootElement.getAttributes());
processNodes(rootElement.getChildNodes());
}

private void processNodes(NodeList nodeList) {
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
processNode(node);

processAttributes(node.getAttributes());
processNodes(node.getChildNodes());
}
}

private void processAttributes(NamedNodeMap attributes) {
int attributeLength = attributes == null ? 0 : attributes.getLength();
if (attributeLength != 0) {
for (int i = 0; i < attributeLength; i++) {
processNode(attributes.item(i));
}
}
}

/**
* Process the node with its attributes but without its children.
*
* @param node
* node to process
*/
private void processNode(Node node) {
node.normalize();

short type = node.getNodeType();
switch (type) {
case Node.ELEMENT_NODE:
case Node.ATTRIBUTE_NODE:
convertNodeIfNeeded(node, node.getNodeName());
break;
case Node.TEXT_NODE:
convertNodeIfNeeded(node, node.getParentNode().getNodeName());
break;

default:
// looks like new type of node is present so there must be done additional
// switch-case with support or ignore such situation
// if you get this, raise the issue
throw new ProcessorException(String.format(EXCEPTION_MESSAGE_NODE_TYPE_UNSUPPORTED,
type, node.getNodeName(), node.getNodeValue()));
}
}

private void convertNodeIfNeeded(Node node, String key) {
if (shouldConvert(node)) {
final String pureValue = StringUtils.trim(node.getNodeValue());
Value newValue = processValue(new Key(key), pureValue);
node.setNodeValue(newValue.getValue().toString());
}
}

private boolean shouldConvert(Node node) {
return StringUtils.isNotBlank(StringUtils.trim(node.getNodeValue()));
}
}
2 changes: 1 addition & 1 deletion src/test/java/pl/szczepanik/silencio/core/BuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public void shouldFailWhenPassingInvalidFormat() {
public void shouldClearExecutions() {

// given
Builder builder = new Builder(Format.PROPERTIES);
Builder builder = new Builder(Format.XML);
builder.with(new NegativeDecision(), Builder.BLANK);

// when
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package pl.szczepanik.silencio.integration;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.StringWriter;

import org.junit.Test;

import pl.szczepanik.silencio.GenericTest;
import pl.szczepanik.silencio.api.Format;
import pl.szczepanik.silencio.api.Processor;
import pl.szczepanik.silencio.core.Builder;
import pl.szczepanik.silencio.diagnostics.ProcessorSmokeChecker;
import pl.szczepanik.silencio.processors.XMLProcessor;
import pl.szczepanik.silencio.utils.ResourceLoader;

/**
* @author Damian Szczepanik (damianszczepanik@github)
*/
public class XMLProcessorTestInt extends GenericTest {

@Test
public void shouldProcessXMLFile() {

// given
input = ResourceLoader.loadXmlAsReader("suv.xml");
output = new StringWriter();
Processor processor = new Builder(Format.XML).with(Builder.NUMBER_SEQUENCE).build();
processor.load(input);

// when
processor.process();

// then
processor.write(output);
String reference = ResourceLoader.loadXmlAsString("suv_Positive_NumberSequence.xml");
assertThat(output.toString()).isEqualTo(reference);
}

@Test
public void shouldNotCrashOnDiagnosticTests() {

// given
String content = ResourceLoader.loadXmlAsString("suv.xml");
ProcessorSmokeChecker checker = new ProcessorSmokeChecker(new XMLProcessor());

// when
checker.validateWithAllCombinations(content);

// then
// no crash
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class JSONProcessorTest extends GenericTest {
public void shouldReturnPassedFormat() {

// given
JSONProcessor processor = new JSONProcessor();
Processor processor = new JSONProcessor();

// when
Format format = processor.getFormat();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package pl.szczepanik.silencio.processors;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.core.StringContains.containsString;

import java.io.StringWriter;

import org.junit.Test;

import pl.szczepanik.silencio.GenericTest;
import pl.szczepanik.silencio.api.Format;
import pl.szczepanik.silencio.api.Processor;
import pl.szczepanik.silencio.core.Builder;
import pl.szczepanik.silencio.core.Configuration;
import pl.szczepanik.silencio.core.Execution;
import pl.szczepanik.silencio.core.ProcessorException;
import pl.szczepanik.silencio.decisions.PositiveDecision;
import pl.szczepanik.silencio.mocks.WriterCrashOnWrite;
import pl.szczepanik.silencio.utils.ResourceLoader;

/**
* @author Damian Szczepanik (damianszczepanik@github)
*/
public class XMLProcessorTest extends GenericTest {

@Test
public void shouldReturnPassedFormat() {

// given
XMLProcessor processor = new XMLProcessor();

// when
Format format = processor.getFormat();

// then
assertThat(format).isEqualTo(Format.XML);
}

@Test
public void shouldLoadXMLFileOnRealLoad() {

// given
XMLProcessor processor = new XMLProcessor();
input = ResourceLoader.loadXmlAsReader("suv.xml");
String refInput = ResourceLoader.loadXmlAsString("suv_tranformed.xml");
output = new StringWriter();

// when
processor.load(input);

// then
processor.realWrite(output);
assertThat(refInput).isEqualTo(output.toString());
}

@Test
public void shouldFailWhenLoadingInvalidJSONFile() {

// given
Processor processor = new XMLProcessor();
Execution execution = new Execution(new PositiveDecision(), Builder.BLANK);
input = ResourceLoader.loadXmlAsReader("corrupted.xml");

// when
processor.setConfiguration(new Configuration(execution));

// then
thrown.expect(ProcessorException.class);
thrown.expectMessage(containsString("XML document structures must start and end within the same entity"));
processor.load(input);
}

@Test
public void shouldFailWhenWrittingToInvalidWriter() {

final String errorMessage = "Don't write into this writter!";

// given
XMLProcessor processor = new XMLProcessor();
Execution execution = new Execution(new PositiveDecision(), Builder.BLANK);
processor.setConfiguration(new Configuration(execution));
input = ResourceLoader.loadXmlAsReader("suv.xml");
output = new WriterCrashOnWrite(errorMessage);

// when
processor.load(input);
processor.realProcess();

// then
thrown.expect(ProcessorException.class);
thrown.expectMessage(errorMessage);
processor.realWrite(output);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public class JSONVisitorTest extends GenericTest {
public void shouldReportExceptionOnUnsupportedModel() throws Exception {

// when
String key = "myKey";
Object value = new Object();
final String key = "myKey";
final Object value = new Object();
JSONVisitor parserr = new JSONVisitor();

// then
Expand Down
Loading

0 comments on commit c116554

Please sign in to comment.