![](/img/trans.png)
[英]How to unmarshal XML with multiple namespaces in different xml or ignore namespace in jaxb
[英]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));
}
}
}
}
給出 output:
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();
}
}
}
Output:
com.my.app.NameSpaceTest$Container@6ce139a4
com.my.app.NameSpaceTest$Container@18ce0030
在讀取數據時過濾數據是正確的方法(如果您必須處理詞匯表的版本和變體,JAXB 或一般的數據綁定不是理想的技術選擇)。 但是使用 SAX 過濾器過濾它,而不是在 stream 級別。
或者,在使用 JAXB 處理數據之前,使用 XSLT 轉換對數據進行標准化。
一種選擇是使用自定義org.xml.sax.ContentHandler
重寫命名空間的 sax 事件,然后將其委托給 jaxb 的“正常” Content Handler
程序。
這是一個自包含的示例:
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>
我們抽象(公共) 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.");
}
}
}
打印我們:
com.example.jaxb.test.NamespaceTest$ContainerApple@4493d195
com.example.jaxb.test.NamespaceTest$ContainerPear@2781e022
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.