使用Java自定义对象实现将XML解析为DOM树

5

我希望在Java中将XML文档解析为DOM树,使得树中的某些对象(例如org.w3c.dom.Nodeorg.w3c.dom.Element的实例)可以向下转型为我创建的类的实例,同时最小化我需要重新实现的与XML相关的代码量。举一个(非常简单的)例子,如果我有一个如下的XML元素:

<Vector size="5">
  1.0 -1.0 3.0 -2.73e2
</Vector>

我希望自定义解析器,以便实例化以下内容:
public class Vector extends /* some parser class */ {
  private double[] elements;

  /* constructors; etc.*/

  public double dotProduct(Vector v) {
    /* implementation */
  }
}

我希望能够将由解析器创建的Vector实例传递给例如javax.xml.xpath对象的方法,并使其正确工作。最快的方法是什么?这是否可能仅使用Java SE,还是需要第三方库(例如Xerces)?


你能控制XML的外观吗? - Daniel Kaplan
是的,我完全掌控XML及其模式。 - TechnocratiK
3个回答

1
我不确定您的要求是什么,但假设您控制XML的外观,我将使用XStream。它将允许您完全跳过所有DOM操作。
现在从他们的2分钟教程中看来,它似乎不适合这种用例,但实际上它确实适用。您首先创建Java类,确保它们生成所需的XML外观,然后将其用于将已存在的XML作为XStream对象读回程序中。这是一个非常愉快的库可供使用。

我需要立即在DOM中反映出对我的对象(即它们的成员)所做的更改。看起来XStream从DOM实例化了一组单独的对象。 - TechnocratiK
你为什么需要 DOM 呢? - Daniel Kaplan
主要使用XPath来导航XML层次结构。 - TechnocratiK
但是您可以将整个文件读入对象树中,并遍历对象中的数据,避免使用XPath。 如果您喜欢使用XPath,则XStream不适合您。 但是,如果您想将XML提取为对象,则XStream是最好的工具。 我不知道有任何东西只完成一半。 - Daniel Kaplan

0

注意:我是EclipseLink JAXB (MOXy)的负责人,也是JAXB (JSR-222)专家组的成员。

JAXB中的Binder机制可能是您正在寻找的。它不允许将DOM节点强制转换为域对象,但它确实维护域对象与其对应的DOM节点之间的链接。

注意:在使用MOXy作为JAXB提供程序时,以下代码运行良好,但在我运行的JDK版本中包含的JAXB实现中抛出异常。

JAVA模型

我将使用以下领域模型作为示例。

客户

import java.util.*;
import javax.xml.bind.annotation.*;

@XmlRootElement
public class Customer {

    private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();

    @XmlElementWrapper
    @XmlElement(name="phoneNumber")
    public List<PhoneNumber> getPhoneNumbers() {
        return phoneNumbers;
    }

}

电话号码

import javax.xml.bind.annotation.*;

public class PhoneNumber {

    private String type;
    private String number;

    @XmlAttribute
    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    @XmlValue
    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

}

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

XML(input.xml)

<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <phoneNumbers>
        <phoneNumber type="work">555-1111</phoneNumber>
        <phoneNumber type="home">555-2222</phoneNumber>
    </phoneNumbers>
</customer>

演示代码

在下面的演示代码中,我将执行以下操作:

  1. 使用XPath查找子元素,然后使用Binder查找相应的域对象。
  2. 更新域对象并使用Binder将更改应用于DOM。
  3. 更新DOM并使用Binder将更改应用于域对象。

演示

import javax.xml.bind.Binder;
import javax.xml.bind.JAXBContext;
import javax.xml.parsers.*;
import javax.xml.xpath.*;

import org.w3c.dom.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document document = db.parse("src/forum16599580/input.xml");

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

        JAXBContext jc = JAXBContext.newInstance(Customer.class);
        Binder<Node> binder = jc.createBinder();
        binder.unmarshal(document);

        // Use Node to Get Object
        Node phoneNumberElement = (Node) xpath.evaluate("/customer/phoneNumbers/phoneNumber[2]", document, XPathConstants.NODE);
        PhoneNumber phoneNumber = (PhoneNumber) binder.getJAXBNode(phoneNumberElement);

        // Modify Object to Update DOM
        phoneNumber.setNumber("555-2OBJ");
        binder.updateXML(phoneNumber);
        System.out.println(xpath.evaluate("/customer/phoneNumbers/phoneNumber[2]", document, XPathConstants.STRING));

        // Modify DOM to Update Object
        phoneNumberElement.setTextContent("555-2DOM");
        binder.updateJAXB(phoneNumberElement);
        System.out.println(phoneNumber.getNumber());
    }

}

输出

555-2OBJ
555-2DOM

0

在过去的10年中,我经历了完全相同的问题,为化学、图形、数学等构建XML DOM。我的解决方案是使用可以进行子类化的DOM(我使用xom.nu,但也有其他选择)。W3C DOM不允许子类化(如果我没记错的话),因此您必须构建一个委托模型。(我很多年前尝试过这个方法,但软件工具和库使所有这些变得更加容易(例如IDE将生成委托方法)。

如果您正在做大量工作,特别是如果您正在创建大量自定义方法,则建议您自己编写系统。努力将花费在您的方法(点积)上,而不是XML。

例如,在这里是我的三维点类

public class CMLPoint3 extends AbstractPoint3 

(它扩展了基类CMLElement,该基类又扩展了nu.xom.Element)

元素的创建是一个工厂。这是我的SVGDOM的一部分:

public static SVGElement readAndCreateSVG(Element element) {
    SVGElement newElement = null;
    String tag = element.getLocalName();
    if (tag == null || tag.equals(S_EMPTY)) {
        throw new RuntimeException("no tag");
    } else if (tag.equals(SVGCircle.TAG)) {
        newElement = new SVGCircle();
    } else if (tag.equals(SVGClipPath.TAG)) {
        newElement = new SVGClipPath();
    } else if (tag.equals(SVGDefs.TAG)) {
        newElement = new SVGDefs();
    } else if (tag.equals(SVGDesc.TAG)) {
        newElement = new SVGDesc();
    } else if (tag.equals(SVGEllipse.TAG)) {
        newElement = new SVGEllipse();
    } else if (tag.equals(SVGG.TAG)) {

...
    } else {
            newElement = new SVGG();
            newElement.setClassName(tag);
            System.err.println("unsupported svg element: "+tag);
        }
        if (newElement != null) {
            newElement.copyAttributesFrom(element);
            createSubclassedChildren(element, newElement);
        }
        return newElement;

你可以看到用于复制和递归的工具。

你需要考虑的问题有:

  • 这与XSD有多密切的关联
  • 我是否使用XSD数据类型
  • 我是否在输入时进行验证
  • 我是否将DOM作为主要数据结构(是的)
  • 事物会有多频繁地变化。

顺便说一下,我已经经历了6个版本的迭代,并正在考虑另一个版本(使用Scala作为主要引擎)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接