繁体   English   中英

如何在fastXml Jackson XmlMapper中保留xml属性?

[英]How to keep xml attribute in fasterXml Jackson XmlMapper?

我正在编写测试生成的 xml 结构的测试用例。 我通过 xml 文件提供 xml 结构。 我目前正在使用 FasterXMLs Jackson XmlMapper 来读取和测试预期的 xml。

Java:            adoptopenjdk 11
Maven:           3.6.3
JUnit (Jupiter): 5.7.1 (JUnit Jupiter)
Mapper:          com.fasterxml.jackson.dataformat.xml.XmlMapper
Dependency:      <dependency>
                     <groupId>com.fasterxml.jackson.dataformat</groupId>
                     <artifactId>jackson-dataformat-xml</artifactId>
                     <version>2.11.4</version>
                 </dependency>

我有一个包含预期 xml 的 xml 文件(例如:/test/testcases.xml:

<testcases>
    <testcase1>
        <response>
            <sizegroup-list>
                <sizeGroup id="1">
                <sizes>
                    <size>
                        <technicalSize>38</technicalSize>
                        <textSize>38</textSize>
                    <size>
                    <size>
                        <technicalSize>705</technicalSize>
                        <textSize>110cm</textSize>
                    <size>
                </sizes>
            </sizeGroup-list>
        </response>
    </testcase1>
</testcases>

我的代码看起来像这样(简化):

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;

import java.io.FileInputStream;
import java.io.InputStream;

import static org.junit.jupiter.api.Assertions.assertEquals;

class Testcases {
    private static final String OBJECT_NODE_START_TAG = "<ObjectNode>";
    private static final String OBJECT_NODE_CLOSE_TAG = "</ObjectNode>";
    private static final String TESTCASES_XML = "/test/testcases.xml";
    private static final XmlMapper XML_MAPPER = new XmlMapper();

    @Test
    void testcase1() throws Exception {
        final String nodePtr = "/testcase1/response";
        try (InputStream inputStream = new FileInputStream(TESTCASES_XML)) {
            JsonNode rootNode = XML_MAPPER.readTree(inputStream);
            JsonNode subNode = rootNode.at(nodePtr);

            if (subNode.isMissingNode()) {
                throw new IllegalArgumentException(
                        "Node '" + nodePtr + "' not found in file " + TESTCASES_XML);
            }

            String expectedXml = XML_MAPPER.writeValueAsString(subNode);
            expectedXml = unwrapObjectNode(expectedXml);

            // Testcalls, e.g. someService.generateXmlData()
            String generatedXml = "...";

            assertEquals(expectedXml, generatedXml);
        };
    }

    // FIXME: Ugly: Tell XmlMapper to unwrap ObjectNode automatically
    private String unwrapObjectNode(String xmlString) {
        if(StringUtils.isBlank(xmlString)) {
            return xmlString;
        }

        if(xmlString.startsWith(OBJECT_NODE_START_TAG)) {
            xmlString = xmlString.substring(OBJECT_NODE_START_TAG.length());
            if(xmlString.endsWith(OBJECT_NODE_CLOSE_TAG)) {
                xmlString = xmlString.substring(0, xmlString.length() - OBJECT_NODE_CLOSE_TAG.length());
            }
        }

        return xmlString;
    }

}

但返回的预期 xml 如下所示:

            <sizegroup-list>
                <sizeGroup>
                <id>1</id>
                <sizes>
                    <size>
                        <technicalSize>38</technicalSize>
                        <textSize>38</textSize>
                    <size>
                    <size>
                        <technicalSize>705</technicalSize>
                        <textSize>110cm</textSize>
                    <size>
                </sizes>
            </sizeGroup-list>

元素 sizeGroup 的前一个属性 id 被映射为子元素并且未通过我的测试。 如何告诉 XmlMapper 保留 xml 元素的属性?

最好的问候,大卫

我无法告诉 XmlMapper 从加载的 xml 文件中保留 xml 标记的属性。 但是我通过使用 xPath 表达式解析 xml 测试数据找到了另一种方法。

一个简单的 String.equals(...) 证明是不可靠的,如果预期和实际 xml 包含不同的空格或 xml 标记顺序。 幸运的是,有一个用于比较 xml 的库。 xml单元!

附加依赖项(从 Spring Boot 2.6.x 开始似乎作为传递依赖项存在):

<dependency>
    <groupId>org.xmlunit</groupId>
    <artifactId>xmlunit-core</artifactId>
    <!-- version transitive in spring-boot-starter-parent 2.6.7 -->
    <version>2.8.4</version>
    <scope>test</test>
</dependency>

ResourceUtil.java:

import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
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.io.StringWriter;
import java.net.URL;

public class ResourceUtil {
    private static final DocumentBuilderFactory XML_DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
    private static final XPathFactory X_PATH_FACTORY = XPathFactory.newInstance();

    private ResourceUtil() {}

    /** Reads an xml file named after the testcase class (e.g. MyTestcase.class
      * -> MyTestcase.xml) and parses the data at the supplied xPath expression. */
    public static String xmlData(Class<?> testClass, String xPathExpression) {
        return getXmlDocumentAsString(testClass, testClass.getSimpleName() + ".xml", xPathExpression);
    }

    /** Reads the specified xml file and parses the data at the supplied xPath
      * expression. The xml file is expected in the same package/directory as
      * the testcase class. */
    private static String getXmlDocumentAsString(Class<?> ctxtClass, String fileName, String xPathExpression) {
        Document xmlDocument = getXmlDocument(ctxtClass, fileName);
        XPath xPath = X_PATH_FACTORY.newXPath();

        try {
            Node subNode = (Node)xPath.compile(xPathExpression).evaluate(xmlDocument, XPathConstants.NODE);
            return nodeToString(subNode.getChildNodes());
        } catch (TransformerException | XPathExpressionException var6) {
            throw new IllegalArgumentException("Unable to read value of '" + xPathExpression + "' from file " + fileName, var6);
        }
    }

    /** Reads the specified xml file and returns a Document instance of the
      * xml data. The xml file is expected in the same package/directory as
      * the testcase class. */
    private static Document getXmlDocument(Class<?> ctxtClass, String xmlFileName) {
        InputStream inputStream = getResourceFile(ctxtClass, xmlFileName);

        try {
            DocumentBuilder builder = XML_DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
            return builder.parse(inputStream);
        } catch (SAXException | IOException | ParserConfigurationException var4) {
            throw new IllegalStateException("Unable to read xml content from file '" + xmlFileName + "'.", var4);
        }
    }

    /** Returns an InputStream of the specified xml file. The xml file is
      * expected in the same package/directory as the testcase class. */
    private static InputStream getResourceFile(Class<?> ctxtClass, String fileName) {
      String pkgPath = StringUtils.replaceChars(ctxtClass.getPackage().getName(), ".", "/");
      String filePath = "/" + pkgPath + "/" + fileName;
      URL url = ctxtClass.getResource(filePath);
      if (url == null) {
          throw new IllegalArgumentException("Resource file not found: " + filePath);
      }
      return ResourceTestUtil.class.getResourceAsStream(filePath);
    }

    /** Deserializes a NodeList to a String with (formatted) xml. */
    private static String nodeToString(NodeList nodeList) throws TransformerException {
        StringWriter buf = new StringWriter();
        Transformer xform = TransformerFactory.newInstance().newTransformer(getXsltAsResource());
        xform.setOutputProperty("omit-xml-declaration", "yes");
        xform.setOutputProperty("indent", "no");

        for(int i = 0; i < nodeList.getLength(); ++i) {
            xform.transform(new DOMSource(nodeList.item(i)), new StreamResult(buf));
        }

        return buf.toString().trim();
    }

    /** Returns a Source of an XSLT file for formatting xml data */
    private static Source getXsltAsResource() {
        return new StreamSource(ResourceTestUtil.class.getResourceAsStream("xmlstylesheet.xslt"));
    }

xmlstylesheet.xslt(对我有用,您可以根据自己的喜好进行更改):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:strip-space elements="*"/>
    <xsl:output method="xml" encoding="UTF-8"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

MyTestcase.java:

import org.xmlunit.builder.DiffBuilder;
import org.xmlunit.diff.DefaultNodeMatcher;
import org.xmlunit.diff.Diff;
import org.xmlunit.diff.ElementSelectors;

import static ResourceUtil.xmldata;

public class MyTestcase {
    @Test
    void testcase1() {
        // Execute logic to generate xml
        String xml = ...
       
        assertXmlEquals(xmlData(getClass(), "/test/testcase1/result"), xml);
    }

    /** Compare xml using XmlUnit assertion. Expected and actual xml need
      * to be equal in content (ignoring whitespace and xml tag order) */
    void assertXmlEquals(String expectedXml, String testXml) {
        Diff diff = DiffBuilder.compare(expectedXml)
                .withTest(testXml)
                .ignoreWhitespace()
                .checkForSimilar()
                .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText, ElementSelectors.byName))
                .build();
        assertFalse(diff.fullDescription(), diff.hasDifferences());
    }

}

MyTestcase.xml:

<test>
    <testcase1>
        <result>
            <myData>
                ...
            </myData>
        </result>
    </testcase1>
</test>

最好的问候,大卫

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM