JAXB - 使用不同的名称空间加载 XML 文件
JAXB - Load XML file with different namespaces
我需要加载一个 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>
目前,我通过使用 修改正在读取的数据,以一种次优的方式工作——但如果可能的话,我想将其移至 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=" */
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
在读取数据时过滤数据是正确的方法(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\"?>\r\n"
+ "<ns2:container xmlns:ns2=\"apple\" ns2:attribute=\"oranges\"><ns2:element>Orange Element</ns2:element>\r\n"
+ "</ns2:container>";
String appleXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n"
+ "<ns2:container xmlns:ns2=\"apple\" ns2:attribute=\"apples\"><ns2:element>Apple Element</ns2:element>\r\n"
+ "</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);
}
}
}
假设
pom.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 解决方案
我们抽象 (public) 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.");
}
}
}
- 单例 JAXBContext。
- (静态)初始化:
- 捕获异常并重新抛出 (runtime/unchecked)。
- 所有(已知)jaxb classes/packages/context(配置)。
打印我们:
com.example.jaxb.test.NamespaceTest$ContainerApple@4493d195
com.example.jaxb.test.NamespaceTest$ContainerPear@2781e022
我需要加载一个 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>
目前,我通过使用
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=" */
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
在读取数据时过滤数据是正确的方法(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\"?>\r\n"
+ "<ns2:container xmlns:ns2=\"apple\" ns2:attribute=\"oranges\"><ns2:element>Orange Element</ns2:element>\r\n"
+ "</ns2:container>";
String appleXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n"
+ "<ns2:container xmlns:ns2=\"apple\" ns2:attribute=\"apples\"><ns2:element>Apple Element</ns2:element>\r\n"
+ "</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);
}
}
}
假设
pom.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 解决方案
我们抽象 (public) 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.");
}
}
}
- 单例 JAXBContext。
- (静态)初始化:
- 捕获异常并重新抛出 (runtime/unchecked)。
- 所有(已知)jaxb classes/packages/context(配置)。
打印我们:
com.example.jaxb.test.NamespaceTest$ContainerApple@4493d195
com.example.jaxb.test.NamespaceTest$ContainerPear@2781e022