服务器 频道

Oracle开发人员的 JDOM 和 XML 分析

  【IT168 服务器学院】JDOM使XML在Java中的操作比以往任何时候都更加容易。

  碰巧,你在过去可能已经用过众多Java库中的某一个以操作XML数据结构。那么JDOM(Java文档对象模型,Java Document Object Model)的用途是什么?开发者又为什么需要它呢?

  JDOM是用于Java优化的XML数据操作的源代码开放的Java库。尽管它与万维网协会(W3C,World Wide Web Consortium)的DOM类似,但它是一种可供选择的文档对象模型,它不是在DOM基础上构建的,也不以DOM为模型。它们的主要区别是:DOM是与语言无关的,最初是用于HTML页的JavaScript操作,而JDOM专用于Java,因此它利用Java的特性,其中包括方法过载(method overloading)、集合(collection)、映像(reflection)和人们熟悉的编程风格等。对Java编程人员来说,JDOM显得更自然、更"适用"。就像Java优化的RMI(remote method invocation,远程方法调用)库要比与语言无关的CORBA(Common Object Request Broker Architecture,通用对象申请中介体系结构)更自然一样。

  你可以根据源代码开放的Apache类型(商业友好)的许可,在jdom.org找到JDOM。它是合作设计开发的,并拥有超过3000个订阅者的邮件列表。此库已经被Sun公司的Java社区进程(JCP,Java Community Process)接受作为Java规范申请(JSR-102),并很快成为正式Java规范。

  本系列文章将对JDOM进行技术介绍。本文提供一些关于重要类的信息。下一篇文章将介绍如何在自己的Java程序中应用JDOM。

  JDOM软件包结构

  JDOM库包括六个软件包。第一个是org.jdom包,它包括表示XML文档以及其组件的类,这些组件有:Attribute、CDATA、Comment、DocType、Document、Element、EntityRef、Namespace、Processing Instruction和Text等。如果你对XML很熟悉,通过类的名字就可以知道它的用途了。

  下一个是org.jdom.input包,它包括用于构建XML文档的类。其中最主要和最重要的类是SAXBuilder。SAXBuilder通过监听输入的XML简单API(SAX,Simple API for XML)事件建立相应的文档。如果需要由一个文件或其他流(stream)建立文档,那就可以采用SAXBuilder。它采用SAX分析器读取流,并根据SAX分析器的"回调"建立文档。这一设计的一个好处就是SAX分析器越快,SAXBuilder也会越快。另一个主要输入类是DOMBuilder。DOMBuilder由DOM树构建。如果预先已经有了一个DOM树,并希望用JDOM版本的树来代替,那么采用DOMBuilder是非常方便的。

  对于这些可能的生成器来说,不存在什么限制。例如,由于Xerces有Xerces本地接口(XNI,Xerces Native Interface),可用于在比SAX较低的层次上进行操作,所以编写一个XNIBuilder以支持某些分析器知识,而不采用SAX,就可能很有意义。一个构成JDOM项目的很流行的生成器是ResultSetBuilder。它利用JDBC结果集合,生成一个XML文档,用以表示SQL结果,根据是哪种元素和哪种属性,它有多种不同的编排。

  org.jdom.output包中包括输出XML文档的一些类。其中最重要的类是XMLOutputter。它将文档转换为一个字节流,用于输出到文件、流和接口程序(sockets)中。XMLOutputter具有很多支持原始输出(raw output)、完美输出(pretty output)、压缩输出或其他输出的特殊配置选项。它是一个相当复杂的类。这可能是为什么DOM level2中仍然没有这种能力的原因。

  其他输出器(outoutter)包括SAXOutputter,它可以产生基于文档内容的SAX事件。尽管它看起来有些神秘,但事实证明这个类在XSLT变换中极其有用,这是因为相对于将文档数据传递到一个引擎的字节来说,SAX事件是一种更为有效的方法。还有一个DOMOutputter输出器,它用于生成表示文档的DOM树。一个有趣的输出器是JTreeOutputter,它只有几十行代码,它可以建立表示文档的JTree。将它与ResultSetBuilder联合起来后,仅用几行代码就可以通过SQL查询得到结果的树视图。

  注意,与在DOM中不同,文档并不与它们的生成器捆绑在一起。这就形成了一个出色的模型,在这种模型中,你可以用一些类保持数据,另一些类构造数据,其他一些类来定制数据。可以随意进行混合和匹配。

  org.jdom.transform和org.jdom.xpath包中还有支持内置的XSLT转换和XPath查找的类。

  最后,org.jdom.adapters包中还包括在DOM交互中对库提供帮助的类。该库的使用者永远不需要调用这一软件包中的类。这些类是随时可用的,因为每个DOM实现都为特定的引导任务生成不同的方法名称,所以适配器类将标准调用转换成分析器专用的调用。Java API for XML Processing(JAXP)为解决此问题提供了另一种方法,事实上降低了对这些类的需要,但是这些类仍然保留了下来,这是因为由于许可证的原因,并不是所有的分析器都支持JAXP,也不是任何地方都安装有JAXP。

 

  生成文档

  文档由org.jdom.Documentclass表示,你可以从头建立一个文档:

  // This builds: <root/>

  Document doc = new Document(new Element("root"));

  也可以由文件、流、系统ID或URL建立一个文档:

  // This builds a document of whatever''s in the given resource

  SAXBuilder builder = new SAXBuilder();

  Document doc = builder.build(url);

  将几个调用结合在一起,可以很容易地在JDOM中建立一个简单的文档:

  // This builds: <root>This is the root</root>

  Document doc = new Document();

  Element e = new Element("root");

  e.setText("This is the root");

  doc.addContent(e);

  如果你是一位非常有才能的用户,你可能更愿意使用"方法链",在这种方式中,多种方法是依次调用的。这种方式之所以有效是因为集合方法返回它们所作用的对象,下面是一个例子:

  Document doc = new Document(

  new Element("root").setText("This is the root"));

  为了进行对比,下面给出如何用JAXP/DOM创建相同的文档:

  // JAXP/DOM

  DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

  DocumentBuilder builder = factory.newDocumentBuilder();

  Document doc = builder.newDocument();

  Element root = doc.createElement("root");

  Text text = doc.createText("This is the root");

  root.appendChild(text);

  doc.appendChild(root);

  用SAXBuilder生成文档

  如前所示,SAXBuilder提供了一种由任意面向字节的数据源来创建文档的简单机制。缺省的无参数SAXBuilder()构造器在后台利用JAXP来选择一个SAX分析器。如果你希望改变分析器,则可以设置javax.xml.parsers.SAXParserFactory系统属性,以指向你的分析器提供的SAXParser Factory实现。对于Oracle9i版本2 XML分析器,应当这样做:

  java -Djavax.xml.parsers.SAXParserFactory=

  oracle.xml.jaxp.JXSAXParserFactory YourApp

  对于Xerces分析器,应当这样做:

  java -Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp

  .SAXParserFactoryImpl YourApp

  如果没有安装JAXP,SAXBuilder缺省地指向Apache Xerces。一旦你创建了SAXBuilder实例,你就可以在生成器中设置几个属性,其中包括:

  setValidation(boolean validate)

  这一方法告诉分析器在生成过程中是否根据Document Type Definition(DTD,文档类型定义)进行验证。缺省设置为"否"(off)。所用的DTD是文档中DocType引用的一个。根据任何其他DTD进行验证是不可能的,因为还没有分析器支持这一功能。

  setIgnoringElementContentWhitespace(boolean ignoring)

  这一方法告诉分析器是否忽略元素内容中所谓的"可忽略空格(whitespace)"。按照XML1.0规范,元素内容中的空格必须由分析器保留,但当根据DTD进行验证时,分析器可能知道文档的特定部分不会支持空格,所以这一区域的任何空格都是"可忽略的"。其缺省设置为"否"(off)。如果你不希望使一个文档"往返旅行",将输入内容以原样输出的话,那么将这一开关打开通常会使性能略微有些提高。注意,这一标志只有在验证已完成时才有效。而进行验证会导致性能下降,所以这一技巧仅在已应用验证时才有用。

  setFeature(String name, String value)

  这一方法设置基础SAX分析器的一个特性。这是一个原始的"传递(pass-through)"调用,所以在应用这一方法时应非常小心,因为对特性的错误设置(如弄错名称空间)可能会中断JDOM行为。而且,依靠任何特定分析器特性都会限制可移植性。这一调用对于启用模式验证最为有用。

  setProperty(String name, Object value)

  这一方法设置基础SAX分析器的一个属性。它也是一个原始的"传递"调用,它具有同样的风险,而且对于有才能的用户同样有用,特别是对于模式验证。下面的代码结合这些方法,利用JAXP选择的分析器读取本地文件,验证功能有效,可忽略的空格都被忽略。

  SAXBuilder builder = new SAXBuilder();

  builder.setValidation(true);

  builder.setIgnoringElementContentWhitespace(true);

  Document doc = builder.build(new File("/tmp/foo.xml"));

 

  用XMLOutputter输出

  文档可以以多种不同的格式输出,但最常见的输出格式还是字节流。在JDOM中,XMLOutputter类提供这一能力。它的缺省无参数生成器试图忠实地输出一个与内存中贮存的完全一样的文档。下面的代码向一个文件生成一个文档的原始表示。

  // Raw output

  XMLOutputter outp = new XMLOutputter();

  outp.output(doc, fileStream);

  如果你不关心空格,那么你可以对文本块进行整理,以节省一点带宽:

  // Compressed output

  outp.setTextTrim(true);

  outp.output(doc, socketStream);

  如果希望文件打印得很漂亮,使其展示给人们看,则可以增加一些缩进空格,并增加一些新行:

  outp.setTextTrim(true);

  outp.setIndent(" ");

  outp.setNewlines(true);

  outp.output(doc, System.out);

  对于已有格式化空格的文档,要打印得很漂亮,就一定要进行整理。否则,就会在已经格式的文档上添加新的格式,使打印的文档显得很难看。

  定位元素树

  JDOM使得元素树的定位非常容易。为了取得根元素,可调用:

  Element root = doc.getRootElement();

  要得到它所有子元素的列表:

  List allChildren = root.getChildren();

  仅想得到具有给定名称的元素:

  List namedChildren = root.getChildren("name");

  仅想得到具有给定名称的第一个元素:

  Element child = root.getChild("name");

  getChildren()调用返回的列表是java.util.List,它是所有Java编程人员都知道的列表(List)接口的一个实现。这一列表令人感兴趣的地方在于它是活的。该列表的任何变化都会在支持的文档中立即反应出来。

  // Remove the fourth child

  allChildren.remove(3);

  // Remove children named "jack"

  allChildren.removeAll(root.getChildren("jack"));

  // Add a new child, at the tail or at the head

  allChildren.add(new Element("jane"));

  allChildren.add(0, new Element("jill"));

  利用该List隐喻(List metaphor)可以不必增加过多的方法而进行许多元素操作。但是为了方便起见,在结尾增加元素或者删去给定名字的元素之类的常用任务都有涉及到元素(Element)自身的方法,而不需要首先得到该List:

  root.removeChildren("jill");

  root.addContent(new Element("jenny"));

  采用JDOM的一个好处就是它可以很容易地在一个文档内或在文档之间移动元素。两种情况的代码相同,如下所示:

  Element movable = new Element("movable");

  parent1.addContent(movable);  // place

  parent1.removeContent(movable); // remove

  parent2.addContent(movable);  // add

  采用DOM时,移动元素就没有这么容易,因为在DOM中,元素是与它的生成工具紧紧联系在一起的。所以在文档之间移动DOM元素时,必须将其"导入"。

  而采用JDOM时,你需要记住的唯一一件事件就是在将一个元素增加到其他位置之前,需要先先将它删除,这样你就不会在树中形成循环。detach()方法可以用一行代码完成分离/增加:

  parent3.addContent(movable.detach());

  如果在将一个元素增加到另一个父元素之前忘了将它分离,则该库会产生一个异常(带有一个真正准确而有用的错误信息)。该库还会检查元素的名字和内容,以确保它们不包括空格之类的不恰当字符。它还会验证其他一些规则,如只有一个根元素、名称空间的声明是一致的、在注释和CDATA部分没有禁止使用的字符串等等。这一特性能够尽可能早地在该进程中进行"格式正确性"(well-formedness)错误检查。

  处理元素的属性

  元素属性的格式如下所示:

  <table width="100%" border="0"> ... </table>

  利用对元素的引用,可以要求元素的任意给定属性值:

  String val = table.getAttributeValue("width");

  也可以将属性看作一个对象,用于进行一些特殊的操作,如类型变换等:

  Attribute border = table.getAttribute("border");

  int size = border.getIntValue();

  要设置或改变一个属性,可以采用setAttribute():

  table.setAttribute("vspace", "0");

  要删除一个属性,可以采用removeAttribute():

  table.removeAttribute("vspace");

  处理元素文本内容

  一个文本内容类似于:

  <description>

  A cool demo

  </description>

  在JDOM中,这一文本可以通过调用直接获得:

  String desc = description.getText();

  要记住,因为XML 1.0规范要求保留空格,所以它会返回"\n A cool demo\n"。当然,有经验的编程人员常常不希望得到格式化的空格。有一个很方便的方法可以检索文本而同时忽略其中的空格:

  String betterDesc = description.getTextTrim();

  如果你真地希望去掉空格,那么有一个getTextNormalize()方法可以将内部空白成为一个标准空格。这一方法对于类似于下面这样的文本是非常方便的:

  <description>

  Sometimes you have text content with formatting

  space within the string.

  </description>

  要改变文本内容,可以应用setText()方法:

  description.setText("A new description");

  这一文本内的任何特殊字符都可以正确地被解释为一个字符,并根据需要在输出时删除,使以保持文本的语法正确。比如说你进行了这样一个调用:

  element.setText("<xml/> content");

  在内部存储中,仍然将这些字符串看作字符。不会对其内容进行隐式分析。在输出中,你将看到:

  <xml/> content<elt>

  这一操作保留了前面setText()调用的语义。如果你希望在一个元素中保留XML内容,则必须增加相应的JDOM子元素对象。

  处理CDATA节也可能在JDOM内进行。一个CDATA节指出不应被分析的文本块。它实质上是一个"语法糖块(syntactic sugar)",它允许很容易地包含HTML或XML内容,而不需要很多<和>换码字符。要建立一个CDATA节,只需要在CDATA对象内封装字符串即可:

  element.addContent(new CDATA("<xml/> content"));

  JDOM最了不起的地方是getText()调用返回字符串时,不会麻烦调用程序去判断它是否由CDATA节表示。

  处理混合内容

  一些元素包括有很多元素,如空格、注释、文本、子元素,以及其他元素:

  <table>

  <!-- Some comment -->

  Some text

  <tr>Some child element</tr>

  </table>

  如果一个元素中同时包含文本和子元素,就说它包含有"混合内容" 。处理混合内容可能是非常困难的,但JDOM使它变得非常容易。标准使用情况--返回文本内容和定位子元素--都非常简单:

  String text = table.getTextTrim(); // "Some text"

  Element tr = table.getChild("tr"); // A straight reference

  对于需要注释、空格块、处理指令和实体引用这样一些更复杂的应用来说,原始混合内容可以作为一个List(列表)来提供:

  List mixedCo = table.getContent();

  Iterator itr = mixedCo.iterator();

  while (itr.hasNext()) {

  Object o = i.next();

  if (o instanceof Comment) {

  ...

  }

  // Types include Comment, Element, CDATA, DocType,

  // ProcessingInstruction, EntityRef, and Text

  }

  就像子元素列表一样,对原始内容列表的改变会影响到支持的文档:

  // Remove the Comment. It''s "1" because "0" is a whitespace block.

  mixedCo.remove(1);

  如果你的观察力很强,则你会注意到这里有一个Text类。JDOM在内部采用Text类存储串内容,从而允许串具有"父辈(parentage)"关系,并更容易支持XPath访问。作为一个编程人员,在检索或设置文本时,不需要担心这种类,而只需要在访问原始内容列表时关心它就行了。.

  对于DocType、处理指令和EntityRef类的详细内容,请参见jdom.org上的API文档。

0
相关文章