Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] xml catalog for XSD #479

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions schema/xmlcatalog.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0"?>
<!--
CycloneDX Software Bill-of-Material (SBoM) Specification

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!--
This XML catalog provides mappings for CycloneDX schemas.
The catalog maps schema URLs to local XSD files to facilitate schema
validation without needing internet access.
Namespace: urn:oasis:names:tc:entity:xmlns:xml:catalog
-->
<!-- to prevent unintendedn notwork access, we do not set a DTD/XSD in this XML -->
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">

<!-- SPDX BOM Schema -->
<uri name="http://cyclonedx.org/schema/spdx" uri="spdx.xsd"/>

<!-- CycloneDX BOM Schemas -->
<uri name="http://cyclonedx.org/schema/bom/1.0" uri="bom-1.0.xsd"/>
<uri name="http://cyclonedx.org/schema/bom/1.1" uri="bom-1.1.xsd"/>
<uri name="http://cyclonedx.org/schema/bom/1.2" uri="bom-1.2.xsd"/>
<uri name="http://cyclonedx.org/schema/bom/1.3" uri="bom-1.3.xsd"/>
<uri name="http://cyclonedx.org/schema/bom/1.4" uri="bom-1.4.xsd"/>
<uri name="http://cyclonedx.org/schema/bom/1.5" uri="bom-1.5.xsd"/>
<uri name="http://cyclonedx.org/schema/bom/1.6" uri="bom-1.6.xsd"/>

<!-- Placeholder for future schemas, where 1.x is the next CycloneDX Spec Version -->
<!-- <uri name="http://cyclonedx.org/schema/bom/1.x" uri="bom-1.x.xsd"/> -->

</catalog>
8 changes: 8 additions & 0 deletions tools/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,13 @@
<version>3.2.5</version>
</plugin>
</plugins>
<testResources>
<testResource>
<directory>${basedir}/../schema</directory>
</testResource>
<testResource>
<directory>src/test/resources/</directory>
</testResource>
</testResources>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package org.cyclonedx.schema;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import static org.junit.jupiter.api.DynamicTest.dynamicTest;

public class XmlCatalogVerificationTest {

/**
* Tests the XML catalog by parsing the xmlcatalog.xml file and verifying if the XML namespaces
* in the referenced XSD schema files match the XML namespaces defined in the xmlcatalog.xml file.
*
* @return a list of dynamic tests for each URI in the xmlcatalog.xml file
* @throws IOException if an I/O error occurs while reading the XML catalog file
* @throws ParserConfigurationException if a parser configuration error occurs
* @throws SAXException if a SAX error occurs while parsing the XML catalog file
* @throws XPathExpressionException if an XPath expression error occurs
*/
@TestFactory
public List<DynamicTest> testXmlCatalog() throws IOException, ParserConfigurationException, SAXException, XPathExpressionException {
// Define the path to the XML catalog file. This is relative to "${basedir}/../schema" in the pom.xml.
String xmlCatalogFilename = "xmlcatalog.xml";

// Load the XML catalog file from the classpath
ClassLoader classLoader = getClass().getClassLoader();
InputStream xmlCatalogStream = classLoader.getResourceAsStream(xmlCatalogFilename);

// Ensure the XML catalog file is found
Assertions.assertNotNull(xmlCatalogStream, "XML catalog file not found");

// Parse the xmlcatalog.xml file
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true); // Make the factory namespace-aware
DocumentBuilder builder = factory.newDocumentBuilder();
Document xmlCatalogDocument = builder.parse(new InputSource(xmlCatalogStream));

// Get the XML catalog elements
NodeList xmlCatalogElements = xmlCatalogDocument.getDocumentElement().getChildNodes();

// List to hold dynamic tests
List<DynamicTest> dynamicTests = new ArrayList<>();

// Iterate through the XML catalog elements
for (int i = 0; i < xmlCatalogElements.getLength(); i++) {
Node xmlCatalogElement = xmlCatalogElements.item(i);

jkowalleck marked this conversation as resolved.
Show resolved Hide resolved
// Check if the element is a <uri> element, continue if it is not
if (!xmlCatalogElement.getNodeName().equals("uri")) {
continue;
}

// Get the URI name and the local filename of the XSD schema
String uriNameXmlCtlg = xmlCatalogElement.getAttributes().getNamedItem("name").getTextContent();
String xsdLocalFilename = xmlCatalogElement.getAttributes().getNamedItem("uri").getTextContent();

// Load the XSD schema local file from the classpath
InputStream xsdSchemaFileStream = classLoader.getResourceAsStream(xsdLocalFilename);
Assertions.assertNotNull(xsdSchemaFileStream, "The following file is missing: " + xsdLocalFilename);

// Parse the XSD schema local file
DocumentBuilderFactory factoryXsd = DocumentBuilderFactory.newInstance();
factoryXsd.setNamespaceAware(true); // Make the factory namespace-aware
DocumentBuilder builderXsd = factoryXsd.newDocumentBuilder();
Document xsdDocument = builderXsd.parse(new InputSource(xsdSchemaFileStream));

// Create an XPath instance to evaluate the targetNamespace field found in the XSD local schema file
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
// Define the namespace URI for the xs prefix
if ("xs".equals(prefix)) {
return "http://www.w3.org/2001/XMLSchema";
}
return null;
}

@Override
public String getPrefix(String namespaceURI) {
// Define the prefix for the namespace URI
if ("http://www.w3.org/2001/XMLSchema".equals(namespaceURI)) {
return "xs";
}
return null;
}

@Override
public Iterator<String> getPrefixes(String namespaceURI) {
return null;
}
});

// Evaluate the targetNamespace attribute from the XSD document
String targetNamespace = (String) xPath.evaluate("/xs:schema/@targetNamespace", xsdDocument, XPathConstants.STRING);

// Create a dynamic test for each URI
dynamicTests.add(dynamicTest("Testing if URI namespace from the XML catalog: " + uriNameXmlCtlg + " matches the URI namespace from the local XSD file: " + targetNamespace, () -> {
// Assert if the targetNamespace from the XSD file matches the uriNameXmlCtlg from the XML catalog
Assertions.assertEquals(uriNameXmlCtlg, targetNamespace, "The namespace " + uriNameXmlCtlg + " does not match the targetNamespace in file " + xsdLocalFilename);
}));
}

// Return the list of dynamic tests
return dynamicTests;
}
}