JAXB - 加载具有不同命名空间的 XML 文件



我需要加载一个XML文件,但是存在两种相同的文件格式,除了命名空间不同 - 在我的简化示例中,apple

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:container xmlns:ns2="apple">
</ns2:container>

pear

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:container xmlns:ns2="pear">
</ns2:container>

XmlRootElement引用特定的命名空间,因此我无法以相同的方式处理这两个文件:

public class NamespaceTest {
@XmlRootElement(namespace = "apple")
public static class Container {
}
public static void main(final String[] args) throws Exception {
// Correct namespace - works
unmarshall("""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:container xmlns:ns2="apple">
</ns2:container>
""");
// Incorrect namespace - doesn't work
unmarshall("""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:container xmlns:ns2="pear">
</ns2:container>
""");
}
private static void unmarshall(final String xml) throws Exception {
try (Reader reader = new StringReader(xml)) {
System.out.println(JAXBContext.newInstance(Container.class).createUnmarshaller().unmarshal(reader));
}
}
}
}

给出输出:

com.my.app.NameSpaceTest$Container@77167fb7
Exception in thread "main" javax.xml.bind.UnmarshalException: unexpected element (uri:"pear", local:"container"). Expected elements are <{apple}container>

目前,我已经通过使用 https://stackoverflow.com/a/50800021 修改读取数据来以次优的方式工作 - 但如果可能的话,我想将其移动到 JAXB 中。

public class NameSpaceTest {
@XmlRootElement(namespace = "apple")
public static class Container {
}
public static void main(final String[] args) throws Exception {
// Correct namespace
unmarshall("""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:container xmlns:ns2="apple">
</ns2:container>
""");
// Incorrect namespace
unmarshall("""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:container xmlns:ns2="pear">
</ns2:container>
""");
}
private static void unmarshall(final String xml) throws Exception {
try (Reader reader = new TranslatingReader(new BufferedReader(new StringReader(xml))) {
@Override
public String translate(final String line) {
return line.replace("pear", "apple");
}
}) {
System.out.println(JAXBContext.newInstance(Container.class).createUnmarshaller().unmarshal(reader));
}
}
/** @see <a href="https://stackoverflow.com/a/50800021">Source</a> */
private abstract static class TranslatingReader extends Reader {
private final BufferedReader input;
private StringReader output = new StringReader("");
public TranslatingReader(final BufferedReader input) {
this.input = input;
}
public abstract String translate(final String line);
@Override
public int read(final char[] cbuf, int off, int len) throws IOException {
int read = 0;
while (len > 0) {
final int nchars = output.read(cbuf, off, len);
if (nchars == -1) {
final String line = input.readLine();
if (line == null) {
break;
} else {
output = new StringReader(translate(line) + System.lineSeparator());
}
} else {
read += nchars;
off += nchars;
len -= nchars;
}
}
if (read == 0) {
read = -1;
}
return read;
}
@Override
public void close() throws IOException {
input.close();
output.close();
}
}
}

输出:

com.my.app.NameSpaceTest$Container@6ce139a4
com.my.app.NameSpaceTest$Container@18ce0030

假设

绒球.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>jaxb-test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency> 
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>3.0.2</version> <!-- latest, depends on jakarta.xml.bind:jakarta.xml.bind-api:3.0.1 -->
</dependency>
</dependencies>
</project>

OOP-解决方案

我们抽象(公共)Container,并使用正确的 qName 引入(私有或我们选择的可见性(,空))实现:

public class NamespaceTest {
public static interface Container {
}
@XmlRootElement(namespace = "apple", name = "container")
private static class ContainerApple implements Container {
}
@XmlRootElement(namespace = "pear", name = "container")
private static class ContainerPear implements Container {
}
...

..!

使用相同的main方法,unmarshall(仍然)看起来像:

...
private static void unmarshall(final String xml) throws Exception {
Unmarshaller umler = CTXT.createUnmarshaller();
try ( Reader reader = new StringReader(xml)) {
System.out.println(umler.unmarshal(reader)
);
}
}
private static final JAXBContext CTXT = initContext();
private static JAXBContext initContext() {
try {
return JAXBContext.newInstance(ContainerApple.class, ContainerPear.class);
} catch (JAXBException ex) {
throw new IllegalStateException("Could not initialize jaxb context.");
}
}
}
  • Singleton JAXBContext.
  • (静态)初始化方式:
    • 捕获异常并重新抛出(运行时/未选中)。
    • 所有(已知的)JAXB 类/包/上下文(配置)。

打印我们:

com.example.jaxb.test.NamespaceTest$ContainerApple@4493d195
com.example.jaxb.test.NamespaceTest$ContainerPear@2781e022
<小时 />

在读取数据时过滤数据是正确的方法(如果必须处理词汇表的版本和变体,JAXB 或一般的数据绑定不是理想的技术选择)。但使用 SAX 筛选器对其进行筛选,而不是在流级别。

或者,在使用 JAXB 处理数据之前,使用 XSLT 转换对数据进行规范化。

一种选择是使用自定义org.xml.sax.ContentHandler,在委托给 jaxb 的"正常"Content Handler之前重写命名空间的 sax 事件。

这里有一个自包含的例子:

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
public class JaxbSaxRewriteNamespaceExample {
@XmlRootElement(name = "container", namespace = "apple")
@XmlAccessorType(XmlAccessType.NONE)
static class Container {
@XmlAttribute(namespace = "apple")
private String attribute;
@XmlElement(namespace = "apple")
private String element;
public String getAttribute() {
return attribute;
}
public String getElement() {
return element;
}
}
public static void main(String[] args) throws Exception {
String orangeXml = "<?xml version="1.0" encoding="UTF-8" standalone="yes"?>rn"
+ "<ns2:container xmlns:ns2="apple" ns2:attribute="oranges"><ns2:element>Orange Element</ns2:element>rn"
+ "</ns2:container>";
String appleXml = "<?xml version="1.0" encoding="UTF-8" standalone="yes"?>rn"
+ "<ns2:container xmlns:ns2="apple" ns2:attribute="apples"><ns2:element>Apple Element</ns2:element>rn"
+ "</ns2:container>";
JAXBContext jc = JAXBContext.newInstance(Container.class);
Container orange = read(jc, orangeXml, Collections.singletonMap("orange", "apple"));
Container apple = read(jc, appleXml, Collections.emptyMap());
System.out.println(orange.getAttribute());
System.out.println(orange.getElement());
System.out.println(apple.getAttribute());
System.out.println(apple.getElement());
}
private static Container read(JAXBContext jc, String xml, Map<String, String> namespaceMapping) throws Exception {
UnmarshallerHandler unmarshallerHandler = jc.createUnmarshaller().getUnmarshallerHandler();
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true); // Make sure sax parser is namespace aware
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
// Wrap the Jaxb ContentHandler with the custome NamespaceRenamer
xr.setContentHandler(new RenameNamespaceContentHandler(unmarshallerHandler, namespaceMapping));
// See javadoc of InputSource for more options to pass in data, e.g. InputStream
InputSource inputSource = new InputSource(new StringReader(xml)); //
xr.parse(inputSource);
return (Container) unmarshallerHandler.getResult();
}
public static class RenameNamespaceContentHandler implements ContentHandler {
private final ContentHandler delegate;
private final Map<String, String> namespaceMapping;
public RenameNamespaceContentHandler(ContentHandler delegate, Map<String, String> namespaceMapping) {
this.delegate = delegate;
this.namespaceMapping = namespaceMapping;
}
@Override
public void setDocumentLocator(Locator locator) {
delegate.setDocumentLocator(locator);
}
@Override
public void startDocument() throws SAXException {
delegate.startDocument();
}
@Override
public void endDocument() throws SAXException {
delegate.endDocument();
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
if (namespaceMapping.containsKey(uri)) {
delegate.startPrefixMapping(prefix, namespaceMapping.get(uri));
}
delegate.startPrefixMapping(prefix, uri);
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
delegate.endPrefixMapping(prefix);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
delegate.startElement(uri, localName, qName, atts);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
delegate.endElement(uri, localName, qName);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
delegate.characters(ch, start, length);
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
delegate.ignorableWhitespace(ch, start, length);
}
@Override
public void processingInstruction(String target, String data) throws SAXException {
delegate.processingInstruction(target, data);
}
@Override
public void skippedEntity(String name) throws SAXException {
delegate.skippedEntity(name);
}
}
}

最新更新