Skip to content

Commit

Permalink
[plugin-xml] Add step to check if XML document is well formed
Browse files Browse the repository at this point in the history
  • Loading branch information
uarlouski committed Jan 6, 2025
1 parent 7f092b8 commit bf7bbce
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 294 deletions.
25 changes: 25 additions & 0 deletions docs/modules/plugins/pages/plugin-xml.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,28 @@ Then XML `
</xs:schema>
`
----

=== Validate well formed XML document

Validates if the provided XML document is well formed.

[source,gherkin]
----
Then XML `$xml` is well formed
----

* `$xml` - The {xml} document.

.Check a message is well formed XML document
[source,gherkin]
----
Then XML `
<?xml version="1.0" encoding="UTF-8"?>
<message>
<to>Bob</to>
<from>Alice</from>
<heading>Reminder</heading>
<body>Don't forget to fill TJ gaps for this week</body>
</message>
` is well formed
----
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -78,9 +78,11 @@ public void saveTransformedXml(String xml, String xslt, Set<VariableScope> scope
* Checks if xml contains element by XPath
* @param xml XML
* @param xpath XPath
* @throws IOException If an I/O error has occurred
* @throws SAXException If an XML processing error has occurred
*/
@Then("XML `$xml` contains element by xpath `$xpath`")
public void doesElementExistByXpath(String xml, String xpath)
public void doesElementExistByXpath(String xml, String xpath) throws SAXException, IOException
{
Document doc = XmlUtils.convertToDocument(xml);
softAssert.assertThat("XML has element with XPath: " + xpath, doc, hasXPath(xpath));
Expand Down Expand Up @@ -129,4 +131,22 @@ public void validateXmlAgainstXsd(String xml, String xsd)
softAssert.recordFailedAssertion(e);
}
}

/**
* Validates if the XML document is well formed
*
* @param xml The XML document
*/
@Then("XML `$xml` is well formed")
public void validateXmlIsWellFormed(String xml)
{
try
{
XmlUtils.convertToDocument(xml);
}
catch (SAXException | IOException e)
{
softAssert.recordFailedAssertion(e);
}
}
}
68 changes: 22 additions & 46 deletions vividus-plugin-xml/src/main/java/org/vividus/util/xml/XmlUtils.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 the original author or authors.
* Copyright 2019-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -41,10 +41,6 @@
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.vividus.util.pool.UnsafeGenericObjectPool;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
Expand All @@ -59,12 +55,10 @@ public final class XmlUtils
private static final String EXTERNAL_PARAMETER_ENTITIES = "http://xml.org/sax/features/external-parameter-entities";
private static final String EXTERNAL_GENERAL_ENTITIES = "http://xml.org/sax/features/external-general-entities";

private static final UnsafeGenericObjectPool<XPathFactory> XPATH_FACTORY = new UnsafeGenericObjectPool<>(
XPathFactory::newInstance);
private static final UnsafeGenericObjectPool<TransformerFactory> TRANSFORMER_FACTORY =
new UnsafeGenericObjectPool<>(TransformerFactory::newInstance);
private static final XPathFactory XPATH_FACTORY = XPathFactory.newInstance();
private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
private static final UnsafeGenericObjectPool<DocumentBuilder> DOCUMENT_BUILDER;
private static final DocumentBuilder DOCUMENT_BUILDER;

static
{
Expand All @@ -74,44 +68,24 @@ public final class XmlUtils
DOCUMENT_BUILDER_FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
DOCUMENT_BUILDER_FACTORY.setFeature(EXTERNAL_GENERAL_ENTITIES, false);
DOCUMENT_BUILDER_FACTORY.setFeature(EXTERNAL_PARAMETER_ENTITIES, false);
DOCUMENT_BUILDER = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
}
catch (ParserConfigurationException e)
{
throw new IllegalStateException(e);
}
DOCUMENT_BUILDER = new UnsafeGenericObjectPool<>(new BasePooledObjectFactory<>()
{
@Override
public DocumentBuilder create() throws ParserConfigurationException
{
return DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
}

@Override
public PooledObject<DocumentBuilder> wrap(DocumentBuilder obj)
{
return new DefaultPooledObject<>(obj);
}
});
}

private XmlUtils()
{
}

public static Document convertToDocument(String xml)
public static Document convertToDocument(String xml) throws SAXException, IOException
{
return DOCUMENT_BUILDER.apply(documentBuilder ->
synchronized (DOCUMENT_BUILDER)
{
try
{
return documentBuilder.parse(createInputSource(xml));
}
catch (SAXException | IOException e)
{
throw new IllegalStateException(e.getMessage(), e);
}
});
return DOCUMENT_BUILDER.parse(createInputSource(xml));
}
}

/**
Expand All @@ -122,11 +96,12 @@ public static Document convertToDocument(String xml)
*/
public static Optional<String> getXmlByXpath(String xml, String xpath)
{
return XPATH_FACTORY.apply(xPathFactory -> {
synchronized (XPATH_FACTORY)
{
try
{
InputSource source = createInputSource(xml);
NodeList nodeList = (NodeList) xPathFactory.newXPath().evaluate(xpath, source, XPathConstants.NODESET);
NodeList nodeList = (NodeList) XPATH_FACTORY.newXPath().evaluate(xpath, source, XPathConstants.NODESET);
Node singleNode = nodeList.item(0);
Properties outputProperties = new Properties();
outputProperties.setProperty(OutputKeys.OMIT_XML_DECLARATION, YES);
Expand All @@ -136,7 +111,7 @@ public static Optional<String> getXmlByXpath(String xml, String xpath)
{
throw new IllegalStateException(e.getMessage(), e);
}
});
}
}

public static void validateXmlAgainstXsd(String xml, String xsd) throws SAXException, IOException
Expand All @@ -150,21 +125,21 @@ public static void validateXmlAgainstXsd(String xml, String xsd) throws SAXExcep
public static void transform(String xml, String xslt, Consumer<String> transformedXmlConsumer,
Consumer<TransformerException> transformerExceptionConsumer)
{
StreamSource xmlSource = createStreamSource(xml);
StreamSource xsltSource = createStreamSource(xslt);
TRANSFORMER_FACTORY.accept(transformerFactory ->
synchronized (TRANSFORMER_FACTORY)
{
StreamSource xmlSource = createStreamSource(xml);
StreamSource xsltSource = createStreamSource(xslt);
try
{
Transformer transformer = transformerFactory.newTransformer(xsltSource);
Transformer transformer = TRANSFORMER_FACTORY.newTransformer(xsltSource);
String transformedXml = transform(xmlSource, transformer);
transformedXmlConsumer.accept(transformedXml);
}
catch (TransformerException e)
{
transformerExceptionConsumer.accept(e);
}
});
}
}

/**
Expand Down Expand Up @@ -192,10 +167,11 @@ public static Optional<String> format(String xml)

private static Optional<String> transform(Source xmlSource, Properties outputProperties)
{
return TRANSFORMER_FACTORY.apply(transformerFactory -> {
synchronized (TRANSFORMER_FACTORY)
{
try
{
Transformer transformer = transformerFactory.newTransformer();
Transformer transformer = TRANSFORMER_FACTORY.newTransformer();
transformer.setOutputProperties(outputProperties);
String result = transform(xmlSource, transformer);
return Optional.of(result);
Expand All @@ -204,7 +180,7 @@ private static Optional<String> transform(Source xmlSource, Properties outputPro
{
return Optional.empty();
}
});
}
}

private static String transform(Source xmlSource, Transformer transformer) throws TransformerException
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;

import java.io.IOException;
import java.util.Set;

import javax.xml.transform.TransformerException;
Expand All @@ -37,6 +38,7 @@
import org.vividus.variable.VariableScope;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

@ExtendWith(MockitoExtension.class)
class XmlStepsTests
Expand Down Expand Up @@ -64,7 +66,7 @@ void shouldSaveDataByXpathIntoScopeVariable()

@Test
@SuppressWarnings("unchecked")
void shouldValidateXmlElementExistenceByXpath()
void shouldValidateXmlElementExistenceByXpath() throws SAXException, IOException
{
Document doc = XmlUtils.convertToDocument(XML);
softAssert.assertThat(eq("XML has element with XPath: " + XPATH), eq(doc), any(Matcher.class));
Expand Down Expand Up @@ -114,6 +116,13 @@ void shouldRecordFailedAssertionOnTransformationException()
verify(softAssert).recordFailedAssertion(any(TransformerException.class));
}

@Test
void shouldRecordFailedAssertionIfXmlIsNotWellFormed() throws Exception
{
xmlValidationSteps.validateXmlIsWellFormed("<test>xslt test<test>");
verify(softAssert).recordFailedAssertion(any(SAXParseException.class));
}

private String loadXsd()
{
return loadResource("test.xsd");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 the original author or authors.
* Copyright 2019-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -36,6 +36,7 @@
import org.vividus.util.ResourceUtils;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

class XmlUtilsTests
{
Expand All @@ -59,15 +60,15 @@ void shouldThrowExceptionInCaseOfInvalidXpath()
}

@Test
void shouldConvertXmlStringToDocument()
void shouldConvertXmlStringToDocument() throws SAXException, IOException
{
assertThat(XmlUtils.convertToDocument(XML), instanceOf(Document.class));
}

@Test
void shouldThrowExceptionInCaseOfInvalidXmlOnConversion()
{
assertThrows(IllegalStateException.class, () -> XmlUtils.convertToDocument("<invalidXml>"));
assertThrows(SAXParseException.class, () -> XmlUtils.convertToDocument("<invalidXml>"));
}

@Test
Expand Down

This file was deleted.

Loading

0 comments on commit bf7bbce

Please sign in to comment.