首页 文章

解组具有DTD相对路径的文档时的JAXB SAXParseException

提问于
浏览
8

我有一个类从第三方源解组xml(我无法控制内容) . 这是解组的片段:

JAXBContext jContext = JAXBContext.newInstance("com.optimumlightpath.it.aspenoss.xsd"); 
Unmarshaller unmarshaller = jContext.createUnmarshaller() ;
StringReader xmlStr = new StringReader(str.value);
Connections conns = (Connections) unmarshaller.unmarshal(xmlStr);

Connections 是使用xjc生成的类dtd-> xsd->类 . 包 com.optimumlightpath.it.aspenoss.xsd 包含所有这些类 .

我收到的xml包含DOCTYPE中的相对路径 . 基本上 str.value 以上包含:

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<!DOCTYPE Connections SYSTEM "./dtd/Connections.dtd">
<Connections>
...
</Connections>

这作为java 1.5应用程序成功运行 . 为了避免上面的错误,我不得不在项目根目录下创建一个./dtd目录并包含所有dtd文件(不知道为什么我必须这样做但我们会这样做) .

我已经在Tomcat5.5上创建了一个使用上述类的Web服务 . 我在unmarshal线上得到 [org.xml.sax.SAXParseException: Relative URI "./dtd/Connections.dtd"; can not be resolved without a document URI.] . 我尝试在每个relavant文件夹(项目根目录,WebContent,WEB-INF,tomcat工作目录等)中创建./dtd无济于事 .

问题1:我在哪里可以找到./dtd,以便在作为tomcat webservice运行时,类可以找到它?我需要做什么tomcat或服务配置才能识别目录?

问题2:为什么该类甚至首先需要dtd文件?它是否具有在dtd-> xsd->类的注释中解组所需的所有信息?我已经阅读了很多关于禁用验证,设置EntityResource和其他解决方案的帖子,但是这个类并不总是作为Web服务部署,我不想有两个代码序列 .

3 回答

  • 8

    当从InputStream或Reader解组时,解析器不知道文档的systemId(uri / location),因此它无法解析相对路径 . 似乎解析器尝试使用当前工作目录来解析引用,该目录仅在从ide或命令行运行时才有效 . 为了覆盖这种行为并自己解决,你需要实现一个 EntityResolver ,正如Blaise Doughan所提到的那样 .

    经过一些实验,我找到了一种标准的方法 . 你需要从 SAXSource 解组,而 SAXSource 又由 XMLReaderInputSource 构成 . 在此示例中,dtd位于带注释的类旁边,因此可以在类路径中找到 .

    Main.java

    public class Main {
        private static final String FEATURE_NAMESPACES = "http://xml.org/sax/features/namespaces";
        private static final String FEATURE_NAMESPACE_PREFIXES = "http://xml.org/sax/features/namespace-prefixes";
    
        public static void main(String[] args) throws JAXBException, IOException, SAXException {
            JAXBContext ctx = JAXBContext.newInstance(Root.class);
            Unmarshaller unmarshaller = ctx.createUnmarshaller();
    
            XMLReader xmlreader = XMLReaderFactory.createXMLReader();
            xmlreader.setFeature(FEATURE_NAMESPACES, true);
            xmlreader.setFeature(FEATURE_NAMESPACE_PREFIXES, true);
            xmlreader.setEntityResolver(new EntityResolver() {
                public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
                    // TODO: Check if systemId really references root.dtd
                    return new InputSource(Root.class.getResourceAsStream("root.dtd"));
                }
            });
    
            String xml = "<!DOCTYPE root SYSTEM './root.dtd'><root><element>test</element></root>";
            InputSource input = new InputSource(new StringReader(xml));
            Source source = new SAXSource(xmlreader, input);
    
            Root root = (Root)unmarshaller.unmarshal(source);
            System.out.println(root.getElement());
        }
    }
    

    Root.java

    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Root {
        @XmlElement
        private String element;
    
        public String getElement() {
            return element;
        }
    
        public void setElement(String element) {
            this.element = element;
        }
    }
    

    root.dtd

    <?xml version="1.0" encoding="UTF-8"?>
    <!ELEMENT root (element)>
    <!ELEMENT element (#PCDATA)>
    
  • 0

    问题2:为什么该类甚至首先需要dtd文件?

    它不是寻找DTD的JAXB实现,而是底层解析器 .

    问题#1:我在哪里可以找到./dtd,以便该类在作为tomcat webservice运行时可以找到它?

    我将使用MOXy JAXB实现(我是技术主管)演示一种可以在多种环境中工作的方法 .

    Proposed Solution

    创建一个EntityResolver,从类路径加载DTD . 通过这种方式,您可以将DTD与应用程序打包在一起,无论部署环境如何,您都可以随时了解DTD的位置 .

    public class DtdEntityResolver implements EntityResolver {
    
        public InputSource resolveEntity(String publicId, String systemId)
                throws SAXException, IOException {
            InputStream dtd = getClass().getClassLoader().getResourceAsStream("dtd/Connections.dtd");
            return new InputSource(dtd);
        }
    
    }
    

    然后使用MOXy JAXB实现,您可以转换为底层实现并设置EntityResolver .

    import org.eclipse.persistence.jaxb.JAXBHelper;
    ...
    JAXBContext jContext = JAXBContext.newInstance("com.optimumlightpath.it.aspenoss.xsd");
    Unmarshaller unmarshaller = jContext.createUnmarshaller() ;
    JAXBHelper.getUnmarshaller(unmarshaller).getXMLUnmarshaller().setEntityResolver(new DtdEntityResolver());
    StringReader xmlStr = new StringReader(str.value);
    Connections conns =(Connections) unmarshaller.unmarshal(xmlStr);
    
  • 2

    这是使用 EntityResolver 界面给出的答案的另一个变体 . 我的情况是将相对外部XML实体从一个XML文件解析到文件夹层次结构中的另一个XML文件 . 下面的构造函数的参数是XML "working"文件夹,而不是进程的工作目录 .

    public class FileEntityResolver implements EntityResolver {
        private static final URI USER_DIR = SystemUtils.getUserDir().toURI();
    
        private URI root;
    
        public FileEntityResolver(File root) {
            this.root = root.toURI();
        }
    
        @Override @SuppressWarnings("resource")
        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
            URI systemURI;
            try {
                systemURI = new URI(systemId);
            } catch (URISyntaxException e) {
                return null;
            }
    
            URI relative = USER_DIR.relativize(systemURI);
            URI resolved = root.resolve(relative);
    
            File f = new File(resolved);
            FileReader fr = new FileReader(f);
            // SAX will close the file reader for us
            return new InputSource(fr);
        }
    }
    

相关问题