簡體   English   中英

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));
            }
        }
    }
}

給出 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>

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 JAXB上下文。
  • (靜態)初始化:
    • 捕獲異常並重新拋出(運行時/未檢查)。
    • 所有(已知)jaxb 類/包/上下文(配置)。

打印我們:

com.example.jaxb.test.NamespaceTest$ContainerApple@4493d195
com.example.jaxb.test.NamespaceTest$ContainerPear@2781e022

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM