首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何在fasterXml Jackson XmlMapper中保留xml属性?

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

Stack Overflow用户
提问于 2022-03-21 12:43:36
回答 1查看 700关注 0票数 0

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

代码语言:javascript
运行
复制
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):

代码语言:javascript
运行
复制
<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>

我的代码看起来如下(简化):

代码语言:javascript
运行
复制
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如下所示:

代码语言:javascript
运行
复制
            <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元素的属性?

向你问好,大卫

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-06-01 14:27:40

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

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

附加依赖(在Spring 2.6.x时似乎以传递依赖的形式存在):

代码语言:javascript
运行
复制
<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:

代码语言:javascript
运行
复制
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 (为我工作,您可以更改您的首选项):

代码语言:javascript
运行
复制
<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:

代码语言:javascript
运行
复制
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:

代码语言:javascript
运行
复制
<test>
    <testcase1>
        <result>
            <myData>
                ...
            </myData>
        </result>
    </testcase1>
</test>

向你问好,大卫

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71557824

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档