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

问题描述:

我想将XML文档解析为Java中的DOM树,使树中的某些对象(例如org.w3c.dom.Nodeorg.w3c.dom.Element的实例)可以向下转换为类我已经创建了,同时尽量减少了需要(重新)实现的与XML相关的代码量。作为(非常简单的)例如,如果我有一个像XML元素:使用Java中的自定义对象实现将XML解析到DOM树中

<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)是必要的?

+0

您是否掌握了XML的外观? –

+0

是的,我完全控制了XML及其模式。 – TechnocratiK

我不确定你的要求是什么,但假设你在控制XML的外观,我会用的是XStream。它将允许你完全跳过所有的DOM操作。

现在从their 2 minute tutorial,它可能看起来不像它是为这个用例而构建的,但它实际上是。首先创建您的java类,确保它们以您希望的方式生成XML,然后使用它将您已经存在的XML作为XStream对象读回到程序中。这是一个非常愉快的图书馆使用。

+0

我需要对我的对象(即其成员)所做的更改立即反映在DOM中。看来XStream从DOM实例化了一组单独的对象。 – TechnocratiK

+0

为什么你需要一个DOM? –

+0

主要使用XPath来导航XML层次结构。 – TechnocratiK

备注:我是EclipseLink JAXB (MOXy)的领导和JAXB (JSR-222)专家组的成员。

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

注:使用莫西作为JAXB提供商时下面的代码都成功了,但使用包含在JDK版本我正好运行JAXB的IMPL时抛出异常。

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

要指定莫西为您的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(输入。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 

我经历过的正是这种在过去的10年里,建设XML DOM的用于化学,图形,数学等。我自己的解决方案是使用一个DOM,其中的元素可以被分类(我使用xom.nu,但也有其他的)。 w3c dom不允许子类化(IIRC),所以你将不得不建立一个委托模型。 (我在很多年以前就尝试过并拒绝它,但是软件工具和库使这一切变得更加容易(例如,IDE将生成委托方法)

如果您正在做很多事情,尤其是如果您要创建很多定制的方法,那么我会推荐滚动你自己的系统。这一努力将在你的方法(dotProduct),而不是XML。

这里,例如,is my class for a 3D point

public class CMLPoint3 extends AbstractPoint3 

(延伸基地class 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(我这样做)
  • 事情变化的频率如何。

FWIW我已经通过了6个版本的修改,并且正在考虑另外一个版本(使用Scala作为主引擎)。