Commit 352726ff authored by Pakulin's avatar Pakulin
Browse files

Modified XML diff utility to correctly handle XML files without schema namespace prefix

parent b2b0305e
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
package org.etsi.mts.ttcn.part9.xmldiff;

public class FeatureSpec {
	private String name;
	private boolean value;
	public FeatureSpec(String name, boolean value) {
		super();
		this.name = name;
		this.value = value;
	}
	public String getName() {
		return name;
	}
	public boolean getValue() {
		return value;
	}
}
 
 No newline at end of file
+39 −0
Original line number Diff line number Diff line
package org.etsi.mts.ttcn.part9.xmldiff;

import java.util.ArrayList;
import java.util.List;

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class ParserErrorHandler implements ErrorHandler {
	private List<SAXParseException> errors = new ArrayList<>();
	private List<SAXParseException> warnings = new ArrayList<>();
	
	@Override
	public void error(SAXParseException err) throws SAXException {
		errors.add(err);
		throw err;
	}

	@Override
	public void fatalError(SAXParseException err) throws SAXException {
		errors.add(err);
		throw err;
	}

	@Override
	public void warning(SAXParseException warn) throws SAXException {
		warnings.add(warn);
	}

	public List<SAXParseException> getErrors() {
		return errors;
	}

	public List<SAXParseException> getWarnings() {
		return warnings;
	}
	
}
 No newline at end of file
+152 −2
Original line number Diff line number Diff line
@@ -9,12 +9,27 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.custommonkey.xmlunit.DetailedDiff;
import org.custommonkey.xmlunit.Diff;
import org.custommonkey.xmlunit.Difference;
import org.custommonkey.xmlunit.DifferenceEngine;
import org.custommonkey.xmlunit.DifferenceListener;
import org.custommonkey.xmlunit.XMLUnit;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/** This class implements an algorithm to compare two XML files.
@@ -24,7 +39,28 @@ import org.xml.sax.SAXException;
 *
 */
public class XmlDiff {
	public static class IgnoreSchemePrefix implements DifferenceListener {
		@Override
	    public int differenceFound(Difference difference) {
	    	int id = difference.getId();
			if (id == DifferenceEngine.NAMESPACE_PREFIX_ID /*||
				id == DifferenceEngine.NAMESPACE_URI_ID */
					) {
	    		return RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
	    	}
	    	return RETURN_ACCEPT_DIFFERENCE;
	    }
	    

		@Override
		public void skippedComparison(Node control, Node test) {
		}
	}

	protected File referenceXmlFile;
	private List<File> xsdFiles;
	private Schema schemes = null;
	private DocumentBuilderFactory xmlParserFactory;

	/** Initialize the diff engine.
	 * 
@@ -42,7 +78,19 @@ public class XmlDiff {
		this(new File(referenceXmlFile), xsdFileNames, xsdSearchPath);
	}
	
	public XmlDiff(File file, Object xsdFileNames, Object xsdSearchPath) {
	/** Initialize the diff engine.
	 * 
	 * If {@code xsdFileNames} is <code>null</code> then the value of {@code xsdSearchPath}
	 * is ignored.
	 * 
	 * @param file path to the reference XML file
	 * @param xsdFileNames optional list of XSD files relevant for the reference XML file.
	 * 	May be <code>null</code>.
	 * @param xsdSearchPath optional list of folder names and/or URIs where to look for XSD files. 
	 * 	May be <code>null</code>.
	 * 
	 */
	public XmlDiff(File file, String[] xsdFileNames, String[] xsdSearchPath) {
		this.referenceXmlFile = file;
		if (!this.referenceXmlFile.exists()) {
			throw new IllegalArgumentException("No such file: " + this.referenceXmlFile.getAbsolutePath());
@@ -51,6 +99,22 @@ public class XmlDiff {
			throw new IllegalArgumentException("Can't read: " + this.referenceXmlFile.getAbsolutePath());
		}

		xmlParserFactory = DocumentBuilderFactory.newInstance();
		xmlParserFactory.setIgnoringComments(true);
		xmlParserFactory.setCoalescing(true);
		xmlParserFactory.setIgnoringElementContentWhitespace(true);
		
		xsdFiles = new ArrayList<File>();
		List<String> missing;
		
		missing = findXsdFiles(xsdFileNames, xsdSearchPath);
		if (missing != null) {
			throw new IllegalArgumentException("Missing XSD files " + missing + " in search path " + Arrays.toString(xsdSearchPath));
		}
		
		if (xsdFiles.size() > 0) {
			addXmlSchemas();
		}
	}

	/** Compare an XML document against the reference one.
@@ -82,11 +146,20 @@ public class XmlDiff {
		return diff(rd, input, diffDetails);
	}

	/***********************************************************************
	 * 
	 * Private methods
	 *  
	 ***********************************************************************/
	
	
	private boolean diff(Reader expected, Reader actual,
			StringBuilder diffDetails) throws XmlDiffError {
		
		Diff differ = null;
		try {
			differ = new Diff(expected, actual);
//			differ = new Diff(expected, actual);
			differ = createDiffer(expected, actual);
		} catch (SAXException e) {
			throw new XmlDiffError("Failed to parse XML", e);
		} catch (IOException e) {
@@ -113,4 +186,81 @@ public class XmlDiff {
		}
		return result;
	}
	
	private void addXmlSchemas() {
		SchemaFactory schemaFactory = 
			    SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
		Source[] schemaSources = new Source[xsdFiles.size()];
		for (int i = 0; i < schemaSources.length; i++) {
			schemaSources[i] = new StreamSource(xsdFiles.get(i));
		}
		try {
			schemes  = schemaFactory.newSchema(schemaSources);
		} catch (SAXException e) {
			throw new IllegalArgumentException("Failed to parse a schema file", e);
		}
		if (schemes != null) {
			xmlParserFactory.setSchema(schemes);
			xmlParserFactory.setValidating(false);
			xmlParserFactory.setNamespaceAware(true);
		}
	}

	private List<String> findXsdFiles(String[] xsdFileNames, String[] xsdSearchPath) {
		if (xsdFileNames == null || xsdFileNames.length == 0) {
			return null;
		}
		List<String> missing = new ArrayList<String>();
		if (xsdSearchPath == null || xsdSearchPath.length == 0) {
			for (String name : xsdFileNames) {
				File guess = new File(name);
				if (guess.exists()) {
					xsdFiles.add(guess);
				} else {
					missing.add(name);
				}
			}
		} else {
			for (String fileName : xsdFileNames) {
				boolean found = false;
				for (String path: xsdSearchPath) {
					File guess = new File(path, fileName);
					if (guess.exists()) {
						xsdFiles.add(guess);
						found = true;
						break;
					}
				}
				if (!found) {
					missing.add(fileName);
				}
			}
		}
		return (missing.size() > 0) ? missing : null; 
	}

	private Document parseXml(Reader inReader, String kind) throws SAXException, IOException, XmlDiffError {
		ParserErrorHandler handler = new ParserErrorHandler();
		DocumentBuilder parser;
		try {
			parser = xmlParserFactory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			throw new RuntimeException("Internal error: failed to create an XML parser", e);
		}
		parser.setErrorHandler(handler);
		InputSource input = new InputSource(inReader);
		Document result = parser.parse(input);
		if (handler.getErrors().size() > 0) {
			throw new XmlDiffError("Failed to parse " + kind + ": " + handler.getErrors());
		}
		return result;
	}
	
	private Diff createDiffer(Reader control, Reader test) throws SAXException, IOException, XmlDiffError {
		Document controlDoc = parseXml(control, "sample XML file");
		Document testDoc = parseXml(test, "generated XML document");
		Diff result = new Diff(controlDoc, testDoc);
		result.overrideDifferenceListener(new IgnoreSchemePrefix());
		return result;
	}
}
+5 −5
Original line number Diff line number Diff line
package org.etsi.mts.ttcn.part9.xmldiff;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
@@ -9,9 +11,7 @@ import java.io.Reader;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

public class TestDiff2 {
public class TestDiff_002 {
	public static String FILE_base = "Pos_070401_AttributeElementDefinition_001_01_base.xml";
	public static String FILE_whitespace = "Pos_070401_AttributeElementDefinition_001_02_whitespace.xml";
	public static String FILE_comment = "Pos_070401_AttributeElementDefinition_001_03_comment.xml";
@@ -29,7 +29,7 @@ public class TestDiff2 {
	@Before
	public void setUp() throws IOException {
		root = new File("xml/002");
		differ = new XmlDiff(new File(root, FILE_base), null, null);
		differ = new XmlDiff(new File(root, FILE_base), new String[]{FILE_xsd}, new String[]{"xml/002"});
		errors = new StringBuilder();
	}
	
+26 −4
Original line number Diff line number Diff line
package org.etsi.mts.ttcn.part9.xmldiff;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
@@ -11,8 +10,11 @@ import org.junit.Test;

import static org.junit.Assert.*;

public class TestDiff3 {
	public static String FILE_base = "Pos_07060101_extending_simple_content_001.xml";
public class TestDiff_003 {
	public static String XSD_FILE = "003_grammar.xsd";
	public static String FILE_without_ns = "xml_without_ns.xml";
	public static String FILE_with_ns = "xml_with_ns.xml";
	public static String FILE_base = FILE_without_ns;
	public static String FILE_ttwb = "generated_ttwb.xml";
	public static String FILE_tc = "generated_tc.xml";
	
@@ -24,10 +26,20 @@ public class TestDiff3 {
	@Before
	public void setUp() throws IOException {
		root = new File("xml/003");
		differ = new XmlDiff(new File(root, FILE_base), null, null);
		differ = new XmlDiff(new File(root, FILE_base), new String[]{XSD_FILE},
				new String[]{"xml/003"});
		errors = new StringBuilder();
	}
	
	@Test
	public void test_self() throws IOException, XmlDiffError {
		actual = new FileReader(new File(root, FILE_base));
		
		boolean v = differ.diff(actual, errors);
		assertEquals("", errors.toString());
		assertTrue(v);
	}
	
	@Test
	public void test_ttwb() throws IOException, XmlDiffError {
		actual = new FileReader(new File(root, FILE_ttwb));
@@ -46,4 +58,14 @@ public class TestDiff3 {
		assertFalse(v);
		assertTrue(errors.toString().contains("attribute"));
	}

	@Test
	public void test_nons_vs_withns() throws IOException, XmlDiffError {
		actual = new FileReader(new File(root, FILE_without_ns));
		
		boolean v = differ.diff(actual, errors);
		
		assertEquals("", errors.toString());
		assertTrue(v);
	}
}
Loading