使用 Xpath 表达式和 jaxb 解组 XML

Posted

技术标签:

【中文标题】使用 Xpath 表达式和 jaxb 解组 XML【英文标题】:Unmarshalling an XML using Xpath expression and jaxb 【发布时间】:2013-06-13 00:44:53 【问题描述】:

我是 JAXB 的新手,想知道是否有一种方法可以将 XML 解组到我的响应对象,但使用 xpath 表达式。问题是我正在调用第三方网络服务,而我收到的响应有很多细节。我不希望将 XML 中的所有细节都映射到我的响应对象。我只想从 xml 映射一些细节,我可以使用这些细节使用特定的 XPath 表达式并将它们映射到我的响应对象。是否有注释可以帮助我实现这一目标?

例如考虑以下响应

<root>
  <record>
    <id>1</id>
    <name>Ian</name>
    <AddressDetails>
      <street> M G Road </street>
    </AddressDetails>
  </record>  
</root>

我只对检索街道名称感兴趣,所以我想使用 xpath 表达式使用“root/record/AddressDetails/street”获取街道的值并将其映射到我的响应对象

public class Response
     // How do i map this in jaxb, I do not wish to map record,id or name elements
     String street; 

     //getter and setters
     ....
   

谢谢

【问题讨论】:

到目前为止你做了什么? 从我的发现看来我必须使用 eclipseLink MOXy 来实现这一目标 【参考方案1】:

注意:我是EclipseLink JAXB (MOXy) 领导,也是JAXB (JST-222) 专家组的成员。

对于这个用例,您可以使用 MOXy 的 @XmlPath 扩展。

回应

import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlRootElement(name="root")
@XmlAccessorType(XmlAccessType.FIELD)
public class Response
    @XmlPath("record/AddressDetails/street/text()")
    String street; 

    //getter and setters

jaxb.properties

要将 MOXy 用作您的 JAXB 提供程序,您需要在与域模型相同的包中包含一个名为 jaxb.properties 的文件,其中包含以下条目(请参阅:http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

演示

import java.io.File;
import javax.xml.bind.*;

public class Demo 

    public static void main(String[] args) throws Exception 
        JAXBContext jc = JAXBContext.newInstance(Response.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum17141154/input.xml");
        Response response = (Response) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(response, System.out);
    


输出

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <record>
      <AddressDetails>
         <street> M G Road </street>
      </AddressDetails>
   </record>
</root>

更多信息

http://blog.bdoughan.com/2010/07/xpath-based-mapping.html http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html

【讨论】:

blaise,我喜欢使用@XmlPath。是否有一个 JSR 注释可以做类似的事情但没有将我绑定到 org.eclipse。谢谢 MOXy 给 404 :(【参考方案2】:

如果您想要的只是街道名称,只需使用 XPath 表达式将其作为字符串获取,然后忘记 JAXB - 复杂的 JAXB 机制不会添加任何值。

import javax.xml.xpath.*;
import org.xml.sax.InputSource;

public class XPathDemo 

    public static void main(String[] args) throws Exception 
        XPathFactory xpf = XPathFactory.newInstance();
        XPath xpath = xpf.newXPath();

        InputSource xml = new InputSource("src/forum17141154/input.xml");
        String result = (String) xpath.evaluate("/root/record/AddressDetails/street", xml, XPathConstants.STRING);
        System.out.println(result);
    


【讨论】:

@Blaise 当我尝试使用上述代码时,我得到 java.net.MalformedURLException: no protocol: error。就在上面带有上述xml的代码。 “new InputSource()”的规范说提供的 SystemId 必须是绝对 URI,而不是本示例中提供的相对 URI。【参考方案3】:

如果您想映射到 XML 文档的中间,您可以执行以下操作:

演示代码

您可以使用 StAX 解析器前进到您希望解组的文档部分,然后从那里解组 XMLStreamReader

import javax.xml.bind.*;
import javax.xml.stream.*;
import javax.xml.transform.stream.StreamSource;

public class Demo 

    public static void main(String[] args) throws Exception 
        JAXBContext jc = JAXBContext.newInstance(Response.class);

        XMLInputFactory xif = XMLInputFactory.newFactory();
        StreamSource xml = new StreamSource("src/forum17141154/input.xml");
        XMLStreamReader xsr = xif.createXMLStreamReader(xml);
        while(!(xsr.isStartElement() && xsr.getLocalName().equals("AddressDetails"))) 
            xsr.next();
        

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        JAXBElement<Response> response = unmarshaller.unmarshal(xsr, Response.class);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(response, System.out);
    


回应

域对象上的映射是相对于您要映射到的 XML 文档的片段进行的。

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
public class Response

    String street; 

    //getter and setters

输出

<?xml version="1.0" encoding="UTF-8"?>
<AddressDetails>
   <street> M G Road </street>
</AddressDetails>

更多信息

http://blog.bdoughan.com/2012/08/handle-middle-of-xml-document-with-jaxb.html

【讨论】:

是否需要创建一个单独的 jaxb 域对象,或者我可以从完整的 xsd 重用通过 xjc 创建的那些(尤其是在提取完整文档树的较大部分时)? @MRalwasser - 您可以使用从 XML Schema 生成的那些。 谢谢,这就是我目前正在做的事情 - 你知道为什么我的解组器会创建所需模型的实例但没有将任何子元素映射到其中吗? @MRalwasser - 如果您将要解组的文档部分复制到其自己的 XML 文档中(根据需要修复命名空间声明),则可以正确解组。这将有助于确认您的映射是否正确。 我这样做了 - 似乎 xml 部分的***元素是“意外”元素。也许原因是它既不是根元素,也不是在 ObjectFactory 中定义为 @XmlElementDecl 注释的 create...() 方法。我有哪些选择?

以上是关于使用 Xpath 表达式和 jaxb 解组 XML的主要内容,如果未能解决你的问题,请参考以下文章

使用 DTD 文件导致的 JAXB 解组 XML 时出错

使用命名空间和前缀的 JAXB 解组

使用 JAXB 解组多次出现的 XML 元素

Java/JAXB:根据属性将 Xml 解组为特定子类

在将 XML 文件解组为对象后,如何让 JAXB 调用方法?

JAXB 解组未知 XML 内容的子集