【问题标题】:Parsing Xml leaf node element values using JAXB使用 JAXB 解析 Xml 叶节点元素值
【发布时间】:2013-12-20 13:09:30
【问题描述】:

我有一个 xsd 说 request.xsd 和相应的 jaxb 生成的类。现在我得到了一个 xml 文件 request.xml,我可以解组并创建“请求”对象。

我在 xml 中有很多元素标签,其中一些标签可以多次使用。我需要创建一个应该包含所有叶节点值的 java.util.List。

例如:

下面是我的 request.xml :

<Request>
  <Operation>manual</Operation>
  <Work>
     <WorkModule>
          <Name>AXN</Name>
     </WorkModule>
  </Work>
  <Identifier>
     <WorkStatus>
          <WorkName>CCH</WorkName>
     </WorkStatus>
     <WorkStatus>
          <WorkName>TMH</WorkName>
     </WorkStatus>
  </Identifier>
</Request>

下面是我的 JAXB 生成的请求类。同样每个xml元素对应的还有其他类:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "Operation",
    "Work",
    "Identifier"
})
@XmlRootElement(name = "Request", namespace = "http://www.sprts.com/clm/nso/mahsgd")
public class Request{

    @XmlElement(name = "Operation", required = true)
    protected Operation operation;
    @XmlElement(name = "Work", required = true)
    protected Work work;
    @XmlElement(name = "Identifier", required = true)
    protected Identifier identifier;

    \\ getters and setters
}

因此,使用 JAXB 我可以获得未编组的请求对象,该对象具有 xml 文件中的所有值。

现在我如何在不使用请求对象的 getter 的情况下以通用方式获取所有叶节点值(操作、名称、工作名称),然后我可以将其中的每一个放入某个集合中,比如说 List。我听说 DOM 被用来做类似的事情,但我需要使用 JAXB。

(不使用来自请求对象的getter,例如 String opertaion = request.getOperation();String name = request.getWork().getWorkModule().getName();)

--编辑--

谁能帮我找到一个最佳解决方案。如果问题陈述不清楚,请告诉我。

--编辑-- 在 Doughan & Alexandros 的帮助下,周围的一些人也能做到这一点。不确定解决方法(将 JAXB 对象转换为 DOM 对象到 InputSource)是否是最佳解决方案。下面是工作代码。

     JAXBContext jc = JAXBContext.newInstance(JAXBObject.class);

     // Create the Document
     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
     DocumentBuilder db = dbf.newDocumentBuilder();
     Document document = db.newDocument();

     // Marshal the Object to a Document
     Marshaller marshaller = jc.createMarshaller();
     marshaller.marshal(jaxbObject, document);

    XPathFactory xpf = XPathFactory.newInstance();
    XPath xp = xpf.newXPath();

    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    Source xmlSource = new DOMSource(document);
    Result outputTarget = new StreamResult(outputStream);
    TransformerFactory.newInstance().newTransformer().transform(xmlSource,outputTarget);
    InputStream is = new ByteArrayInputStream(outputStream.toByteArray());
    InputSource source = new InputSource(is);

    NodeList leafNodeObjects = (NodeList) xp.evaluate("//*[not(*)]", source, XPathConstants.NODESET);

    for(int x=0; x<leafNodeObjects.getLength(); x++) {
                    System.out.print("nodeElement = ");
                    System.out.print(leafNodeObjects.item(x).getNodeName());
                    System.out.print(" and node value = ");
                    System.out.println(leafNodeObjects.item(x).getTextContent());
                    inputDtos.add(new InputDto(leafNodeObjects.item(x).getNodeName(),
                            leafNodeObjects.item(x).getTextContent()));
   }

【问题讨论】:

  • 您手头有很棒的对象,其中每个属性/方法都有一定的意义。你想要实现什么,你想要一种通用的方式并考虑用 DOM 之类的东西来替换你的类型化数据结构?然而,显而易见但不好的答案是“使用反射”。但我敦促您解释您想要实现的目标,以便我们提出更好的解决方案。
  • 使用来自请求对象的简单 getter,我可以创建一个工作正常的列表。我在 xml 中有大约 150 个字段,因此手动映射每个 xml 元素仍然可以,但我希望这个数字将来会增长。在这种情况下,我必须再次验证从 xml 中添加或删除的字段,并且必须完成相应的映射才能放入我想要避免的集合中。

标签: java xml-parsing jaxb unmarshalling xmlnode


【解决方案1】:

来自您的赏金评论:

我想创建一个 NodeObject 列表,其中 NodeObject 有 nodeElement 和 nodeValue 属性。例如。如果我有一个像 Anil 然后我将为这个元素创建一个 NodeObject 使用 nodeElement = name 和 nodeValue = property。

您可以使用以下 XPath 从任何 XML 文档中获取叶节点(请参阅:How to select all leaf nodes using XPath expression?):

//*[not(*)]

这里使用javax.xml.xpath API 进行操作:

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

public class Demo {

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

        InputSource xml = new InputSource("input.xml");
        NodeList leafNodeObjects = (NodeList) xp.evaluate("//*[not(*)]", xml, XPathConstants.NODESET);

        for(int x=0; x<leafNodeObjects.getLength(); x++) {
            System.out.print("nodeElement = ");
            System.out.print(leafNodeObjects.item(x).getNodeName());
            System.out.print(" and node value = ");
            System.out.println(leafNodeObjects.item(x).getTextContent());
        }
    }

}

以下是运行此演示代码的输出:

nodeElement = Operation and node value = manual
nodeElement = Name and node value = AXN
nodeElement = WorkName and node value = CCH
nodeElement = WorkName and node value = TMH

【讨论】:

  • 您的代码可以正常获取叶节点值,但我无权访问 xml 文件。我得到了一个未编组的 pojo 对象来使用。有什么办法可以在 pojo 上使用 DOM 而不是 xml 文件?
  • @Suvasis - 您可以尝试在 JAXBSource 的实例上发出 XPath,或者您可以将 JAXB 对象编组到 DOM 并在其上发出 XPath。
  • @Doughan - 我正在寻找如何将 JAXB 对象转换为 InputSource 以便我可以通过评估方法应用 XPATH 以按照您上面所做的方式获取叶节点。您能否找到任何链接或提示以供参考。
  • @Doughan - 或者有没有办法将 DOM 对象转换为 InputSource?
  • 感谢 Doughan 和 Alexandros
【解决方案2】:

我的问题是:您是否能够更改 XSD 以满足您的需要,或者 XSD 是否由其他人控制并且您必须按原样使用它?

这很重要,因为 JAXB 的工作方式。基本上,JAXB 将 XSD 转换为 Java 类。它也可以做相反的事情(将 Java 类转换为 XSD)。关系在这里详细描述:http://docs.oracle.com/cd/E21764_01/web.1111/e13758/data_types.htm

在你的情况下,我假设有人编写了一个 XSD,你用它来生成 Java 类,但是这些类有很多:“Something getSomething1(), Something getSomething2(), ...Something getSomethingN() 方法当您希望使用 List getListOfSomethings() 方法时。

有两种方法可以解决这个问题:

(1) 更改 XSD 以使“某物”成为复杂类型的一部分,该类型是一个序列(或任何会导致 JAXB 为列表生成 getter 的东西,根据我的原始答案)。

这并不总是可能的。如果 XSD 由某个外部实体控制,上面写着“这就是我的数据的样子,你必须接受它,否则你的应用程序将无法读取我的数据”,那么你就不能这样做。举一个具体的例子,假设您的应用程序想要从美国国会图书馆读取 EAD 数据。它的 XSD 在这里:http://www.loc.gov/ead/eadschema.html。这个 XSD 就是这样。你不能改变它。如果您更改它,您的应用程序将使用您对不同数据的定义。您必须考虑下面的方法 (2),因为您无法控制 XSD。

(2) 不要使用 JAXB。而是使用允许您查询元素的 XML API。通过这种方式,您可以使用(例如)一个 XPath 查询(参见 http://docs.oracle.com/javase/tutorial/jaxp/xslt/xpath.html)收集所有“某些东西”。

您可以创建一个加载 XML 并具有 List getSomethings() 方法的包装类。这将遵循以下思路:

public class RequestWrapper {
    Document doc;
    public RequestWrapper(String xmlUri) {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        this.doc = builder.parse(xmlUri);
    }

    public List<Something> getSomethings() {
        XPathFactory xPathfactory = XPathFactory.newInstance();
        XPath xpath = xPathfactory.newXPath();
        XPathExpression expr = xpath.compile(<DEFINE A SUITABLE EXPRESSION>);
        NodeList nl = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);

        List<Something> somethings = new LinkedList<Something>();
        // loop over the nodelist creating instances of Something
        return somethings;
    }
}

这是一个很好的使用 XPath 和 Stax 的教程,它可能会派上用场:http://www.vogella.com/articles/JavaXML/article.html

(3) 如果您愿意放弃标准 Java API,您可以考虑使用一个库,可以让您更好地控制与 Java 的绑定,例如 Castor:http://castor.codehaus.org/xml-framework.html

最终,您的问题是数据以不方便的方式呈现,在这种情况下您必须执行 (2) 或 (3),或者您定义了不方便的 XSD,在这种情况下您必须执行 (1) .

【讨论】:

  • 我怀疑您是否可以使用 JAXB 做您想做的事情,因为 JAXB 使用与特定类定义的一对一关系工作。如果您真的必须使用 JAXB,您不妨编写一个包装类,该类接受 JAXB 对象,然后通过使用 for 循环并手动调用 JAXB 对象上的原始 getter 来收集所有叶元素。这样,您将编写一次意大利面条代码,然后在其他任何地方使用此包装类...
  • 感谢 Alexandros 提供的解决方案。不幸的是,我无法控制 XSD 并且客户端不想使用 DOM,因为我们必须在 XPATH 中硬编码所有必需的 xml 标签,这对于拥有 200 多个标签或可以进一步增长的大型 xml 来说很难维护未来。我会尝试你在第三个选项中提到的脚轮,看看它是如何工作的。
  • @Suvasis - 您可以编写一个通用 XPath 来从任何 XML 文档中获取叶节点:stackoverflow.com/a/20888804/383861
【解决方案3】:

您是在定义 XML 的结构,还是有人为您提供了您必须使用的固定 XSD?

如果您实际上是在定义请求 XML 的结构,则可以使用 @XmlElement 和 @XmlElementWrapper 等注释让 JAXB 处理集合。更多信息请参见:http://blog.bdoughan.com/2010/09/jaxb-collection-properties.html

【讨论】:

  • 是的。我有 XSD,但如何解决我的问题。如何使用 XSD 读取具有值的 xml 元素?我不想在 xml 转换的 pojo 上使用 getter 来获取这些值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-25
  • 1970-01-01
  • 1970-01-01
  • 2012-12-07
  • 2012-12-25
  • 1970-01-01
相关资源
最近更新 更多