All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.rdf4j.testsuite.rio.rdfxml.RDFXMLParserTestCase Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *******************************************************************************/
package org.eclipse.rdf4j.testsuite.rio.rdfxml;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.xml.security.c14n.CanonicalizationException;
import org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.c14n.InvalidCanonicalizerException;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.base.CoreDatatype;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.util.Models;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.eclipse.rdf4j.repository.sail.SailRepository;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFParseException;
import org.eclipse.rdf4j.rio.RDFParser;
import org.eclipse.rdf4j.rio.helpers.BasicParserSettings;
import org.eclipse.rdf4j.rio.helpers.StatementCollector;
import org.eclipse.rdf4j.rio.ntriples.NTriplesParser;
import org.eclipse.rdf4j.sail.memory.MemoryStore;
import org.xml.sax.SAXException;

import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 * JUnit test for the RDF/XML parser that uses the test manifest that is available
 * online.
 */
public abstract class RDFXMLParserTestCase {

	/*-----------*
	 * Constants *
	 *-----------*/

	private static final String W3C_TESTS_DIR = "http://www.w3.org/2000/10/rdf-tests/rdfcore/";

	private static final String LOCAL_TESTS_DIR = "/testcases/rdfxml/";

	private static final String W3C_MANIFEST_FILE = W3C_TESTS_DIR + "Manifest.rdf";

	/*--------------------*
	 * Static initializer *
	 *--------------------*/

	public TestSuite createTestSuite() throws Exception {
		// Create an RDF repository for the manifest data
		Repository repository = new SailRepository(new MemoryStore());
		// Create test suite
		TestSuite suite = new TestSuite(RDFXMLParserTestCase.class.getName());

		try (RepositoryConnection con = repository.getConnection()) {

			// Add W3C's manifest
			URL w3cManifest = resolveURL(W3C_MANIFEST_FILE);
			con.add(w3cManifest, base(W3C_MANIFEST_FILE), RDFFormat.RDFXML);

			// Add all positive parser tests
			String query = ""
					+ "PREFIX test: "
					+ " SELECT ?TESTCASE ?INPUT ?OUTPUT "
					+ " WHERE { ?TESTCASE a test:PositiveParserTest; "
					+ "                test:inputDocument ?INPUT; "
					+ "                test:outputDocument ?OUTPUT; "
					+ "                test:status \"APPROVED\" .} ";

			try (var queryResult = con.prepareTupleQuery(query).evaluate()) {
				while (queryResult.hasNext()) {
					BindingSet bindingSet = queryResult.next();
					String caseURI = bindingSet.getValue("TESTCASE").toString();
					String inputURL = bindingSet.getValue("INPUT").toString();
					String outputURL = bindingSet.getValue("OUTPUT").toString();
					suite.addTest(new PositiveParserTest(caseURI, inputURL, outputURL));
				}
			}
			// Add all negative parser tests
			query = ""
					+ "PREFIX test: "
					+ " SELECT ?TESTCASE ?INPUT ?OUTPUT "
					+ " WHERE { ?TESTCASE a test:NegativeParserTest; "
					+ "                test:inputDocument ?INPUT; "
					+ "                test:outputDocument ?OUTPUT; "
					+ "                test:status \"APPROVED\" .} ";
			try (var queryResult = con.prepareTupleQuery(query).evaluate()) {
				while (queryResult.hasNext()) {
					BindingSet bindingSet = queryResult.next();
					String caseURI = bindingSet.getValue("TESTCASE").toString();
					String inputURL = bindingSet.getValue("INPUT").toString();
					suite.addTest(new NegativeParserTest(caseURI, inputURL));
				}
			}
		}
		repository.shutDown();

		return suite;
	}

	private static URL resolveURL(String urlString) throws MalformedURLException {
		if (urlString.startsWith(W3C_TESTS_DIR)) {
			// resolve to local copy
			urlString = LOCAL_TESTS_DIR + "w3c-approved/" + urlString.substring(W3C_TESTS_DIR.length());
		}

		if (urlString.startsWith("/")) {
			return RDFXMLParserTestCase.class.getResource(urlString);
		} else {
			return url(urlString);
		}
	}

	protected abstract RDFParser createRDFParser();

	/*--------------------------------*
	 * Inner class PositiveParserTest *
	 *--------------------------------*/

	private class PositiveParserTest extends TestCase {

		/*-----------*
		 * Variables *
		 *-----------*/

		private final String inputURL;

		private final String outputURL;

		/*--------------*
		 * Constructors *
		 *--------------*/

		public PositiveParserTest(String caseURI, String inputURL, String outputURL) {
			super(caseURI);
			this.inputURL = inputURL;
			this.outputURL = outputURL;
		}

		/*---------*
		 * Methods *
		 *---------*/

		@Override
		protected void runTest() throws Exception {
			// Parse input data
			RDFParser rdfxmlParser = createRDFParser();
			rdfxmlParser.setValueFactory(new CanonXMLValueFactory());
			rdfxmlParser.getParserConfig().addNonFatalError(BasicParserSettings.FAIL_ON_UNKNOWN_DATATYPES);
			rdfxmlParser.getParserConfig().addNonFatalError(BasicParserSettings.VERIFY_DATATYPE_VALUES);

			Set inputCollection = new LinkedHashSet<>();
			StatementCollector inputCollector = new StatementCollector(inputCollection);
			rdfxmlParser.setRDFHandler(inputCollector);

			try (InputStream in = resolveURL(inputURL).openStream()) {
				rdfxmlParser.parse(in, base(inputURL));
			}

			// Parse expected output data
			NTriplesParser ntriplesParser = new NTriplesParser();
			ntriplesParser.setValueFactory(new CanonXMLValueFactory());
			ntriplesParser.getParserConfig().addNonFatalError(BasicParserSettings.FAIL_ON_UNKNOWN_DATATYPES);
			ntriplesParser.getParserConfig().addNonFatalError(BasicParserSettings.VERIFY_DATATYPE_VALUES);

			Set outputCollection = new LinkedHashSet<>();
			StatementCollector outputCollector = new StatementCollector(outputCollection);
			ntriplesParser.setRDFHandler(outputCollector);

			try (InputStream in = resolveURL(outputURL).openStream()) {
				ntriplesParser.parse(in, base(inputURL));
			}

			// Check equality of the two models
			if (!Models.isomorphic(inputCollection, outputCollection)) {

				String expected = outputCollection.stream().map(Objects::toString).collect(Collectors.joining());
				String actual = inputCollection.stream().map(Objects::toString).collect(Collectors.joining("\n"));

				assertEquals(expected, actual);

			}
		}

	} // end inner class PositiveParserTest

	/*--------------------------------*
	 * Inner class NegativeParserTest *
	 *--------------------------------*/

	private class NegativeParserTest extends TestCase {

		/*-----------*
		 * Variables *
		 *-----------*/

		private final String inputURL;

		/*--------------*
		 * Constructors *
		 *--------------*/

		public NegativeParserTest(String caseURI, String inputURL) {
			super(caseURI);
			this.inputURL = inputURL;
		}

		/*---------*
		 * Methods *
		 *---------*/

		@Override
		protected void runTest() {
			try {
				// Try parsing the input; this should result in an error being
				// reported.
				RDFParser rdfxmlParser = createRDFParser();
				rdfxmlParser.getParserConfig().set(BasicParserSettings.FAIL_ON_UNKNOWN_DATATYPES, true);
				rdfxmlParser.getParserConfig().set(BasicParserSettings.VERIFY_DATATYPE_VALUES, true);
				rdfxmlParser.getParserConfig().set(BasicParserSettings.NORMALIZE_DATATYPE_VALUES, true);

				rdfxmlParser.setRDFHandler(new StatementCollector());

				InputStream in = resolveURL(inputURL).openStream();
				rdfxmlParser.parse(in, base(inputURL));
				in.close();

				fail("Parser parses erroneous data without reporting errors");
			} catch (RDFParseException e) {
				// This is expected as the input file is incorrect RDF
			} catch (Exception e) {
				fail("Error: " + e.getMessage());
			}
		}

	} // end inner class NegativeParserTest

	private static class CanonXMLValueFactory extends SimpleValueFactory {

		private final Canonicalizer c14n;

		public CanonXMLValueFactory() throws InvalidCanonicalizerException {
			org.apache.xml.security.Init.init();

			c14n = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
		}

		@Override
		public Literal createLiteral(String value, IRI datatype) {
			if (RDF.XMLLITERAL.equals(datatype)) {
				value = canonicalizeXmlLiteral(value);
			}

			return super.createLiteral(value, datatype);
		}

		@Override
		public Literal createLiteral(String value, CoreDatatype datatype) {
			if (datatype == CoreDatatype.RDF.XMLLITERAL) {
				value = canonicalizeXmlLiteral(value);
			}

			return super.createLiteral(value, datatype);
		}

		@Override
		public Literal createLiteral(String value, IRI datatype, CoreDatatype coreDatatype) {
			if (coreDatatype == CoreDatatype.RDF.XMLLITERAL) {
				assert RDF.XMLLITERAL.equals(datatype);
				value = canonicalizeXmlLiteral(value);
			} else {
				assert !RDF.XMLLITERAL.equals(datatype);
			}

			return super.createLiteral(value, datatype, coreDatatype);
		}

		private String canonicalizeXmlLiteral(String value) {
			// Canonicalize the literal value
			try {
				return new String(c14n.canonicalize(value.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
			} catch (ParserConfigurationException | IOException e) {
				throw new RuntimeException(e);
			} catch (CanonicalizationException | SAXException e) {
				return value;
			}
		}

	}

	private static URL url(String uri) throws MalformedURLException {
		return new URL(uri);
	}

	private static String base(String uri) {
		return uri;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy