Java开发网 Java开发网
注册 | 登录 | 帮助 | 搜索 | 排行榜 | 发帖统计  

您没有登录

» Java开发网 » 技术文章库  

按打印兼容模式打印这个话题 打印话题    把这个话题寄给朋友 寄给朋友    该主题的所有更新都将Email到你的邮箱 订阅主题
flat modethreaded modego to previous topicgo to next topicgo to back
作者 (转帖)使用 XML:完成 XI 实现 XMLReader 接口
killerNT



发贴: 0
积分: 0
于 2002-12-05 17:43 user profilesend a private message to usersearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
使用 XML:完成 XI 实现 XMLReader 接口

Benoit Marchal(bmarchal@pineapplesoft.com)
顾问,Pineapplesoft
2002 年 7 月

专栏作家 Benoit Marchal 继续描述 XI,它是一个将旧文本转换成 XML 的开放源码项目。为了提高效率,XI 现在实现了 SAX XMLReader 接口,这证明了该接口使 XI 链接到 XSLT 处理器变得容易。代码样本演示了这些技术,还可以获得完整的源代码。专栏每个月都报告作者旨在帮助志同道合的 XML 开发人员(尤其是那些使用 Java 技术的开发人员)的开放源码项目。
在上两篇专栏文章中,我一直在讨论 XI(XML Import 的缩写),它是一个将旧文件转换成 XML 的项目(请参阅参考资料)。XI 的动机来自将地址簿发布为 XML 站点的一部分的需求。因为地址簿是用电子邮件客户机的专用格式维护的,所以我需要将文本转换成 XML 的工具。

我利用这一机会尝试了构建到 JDK 1.4 中的新的正则表达式库。正则表达式倾向于一个灵活的转换解决方案:我可以描述如何将旧文档解析成一组正则表达式,而不是对转换例程硬编码。我将对地址簿使用一组规则,但我可以为其它日历或化学分析数据、Web 服务器日志或其它格式编写不同的规则。XI 是一种更常规的工具,您或我都可以在许多项目中使用和重用它。

现在在 XML 中
在上一篇专栏文章“Wrestling with Java NIO”(请参阅参考资料)中,我花了相当长的时间来研究正则表达式库。其结果是我的一些假设完全没有切中要害,但我仍设法使用正则表达式将地址簿解析成元素。

因为我的目标是给出一个常规解决方案,所以我创建了一个小型数据结构来保存这组规则。它使 XML 标记名与正则表达式在本质上相关联。虽然我不得不受限于固定的数据结构以便进行测试,但我组织了代码,这样,从一个文件填充数据结构将是一件简单的事情,这是我目前已经实现的一个特性。

本专栏文章主要是关于清除代码并确保它产生一个有效的 XML 文档。我还设法将现有的算法封装成 XML 解析器。正如您将要看到的,XML 解析器接口证明了它使处理 XSLT 处理器很容易。

编写 XML 文档的最佳方法
完成 XI 的最容易的解决方案就是重访代码,并修改各种打印(print)语句来写 XML 标记。确实,解析文档并将 XML 元素与节点相关联的逻辑早就出现了。例如,当逻辑与正则表达式匹配时,算法打印与之相关联的元素,如:

System.out.print(ruleset.getMatchAtLight Bulb.getQualifiedName());


修改这条语句以产生正确的 XML 并不难:

System.out.print("<"+ruleset.getMatchAtLight Bulb.getQualifiedName()+">");


当然,上面的语句只打印开始标记,所以我需要更多的打印语句用于结束标记和内容,但那做起来并不难。

如果我只对编写 XML 文档感兴趣,那么它或许是我愿意做的,因为它是最不费力的解决方案。要特别注意避免使用尖括号、& 和其它保留字符,但这些都是很琐碎的事情。我还可能要将 XML 文档保存在文件中,而不是打印到控制台 — 但再说一次,这是琐碎的事情。

然而,我并不愿意将 XML 文档写入文件。正如您可以回想起前几篇专栏文章,我不打算直接使用 XI 的输出。经验显示人们常常需要重新组织旧文档。例如,在地址簿中,我必需将 alias 和 note 行组合在一起。我可以将逻辑添加到 XI 中来处理这种情况和其它相似的情况,但我发现将导入过程分成两个步骤很有利:

语法转换
数据结构重组
语法转换获取文本信息并用最简单的 XML 结构封装它。一般情况下,产生的 XML 文档十分接近于原始文件。在大多数情况下,它与用 XML 标记替换定界符一样简单。这是 XI 所做的事情。

第二步是使用转换将这个原始 XML 文档转换成目标词汇表。我发现 XSLT 尤其适合于该目的,因为它是一种功能强大的转换语言。并且因为 XSLT 是一个标准,所以不会缺乏支持工具,如编辑器。

简而言之,我不一定要求 XI 将 XML 文档写在文件中;我宁愿使它最优化,以便与 XSLT 处理器交互。JDK 1.4 携带有 Apache Xalan 的一个版本,它接受来自文件(流)、SAX 事件和 DOM 树的输入。在这三种接口中,我个人最喜欢 SAX。

SAX 很有吸引力,因为它易于编程,并且在处理 XML 文档时有一个相当有效的接口。与文件比较,它将编写的内容保存到临时文件;与 DOM 比较,它需要更少的内存。

对 SAX 接口进行编程
在本文的其余部分,我假设您熟悉 SAX 编程。如果不熟悉,您可能要转到同在 developerWorks 上的“SAX,the Power API”(请参阅参考资料)。

SAX 中两个最重要的接口是 XMLReader 和 ContentHandler。XMLReader 描述如何初始化和启动 XML 解析器,而 ContentHandler 列出当它解析 XML 文档时 XMLReader 发出的事件。

当读取 XML 文档时,您或许已经使用了这两个接口。然而,即使您熟悉它们,但该应用程序要求您从略微不同的角度查看 SAX。在这种情况下,我编写我自己的解析器,而不是成为 SAX 的用户。严格来说,XI 不是 XML 解析器;它不读取 XML 文档。然而,它对提供文本文档的 XML 视图,因此它可以符合 XMLReader 接口。

XI 的 SAX 实现在类 XIReader 中。该类太大,以致于无法在这里完整地复制它。在继续之前,我鼓励您从 developerWorks 的“开放源码”一节(请参阅参考资料)获得一份拷贝。

XIReader 处理两个问题:实现 SAX 接口和实际文本解析以及 XML 文档生成。清单 1 说明了该接口的实现。

清单 1:XIReader 的 SAX 实现

public class XIReader

implements XMLReader, Locator

{

protected ContentHandler contentHandler = null;

public ContentHandler getContentHandler()

{

return contentHandler;

}

public void setContentHandler(ContentHandler value)

throws NullPointerException

{

if(value == null)

throw new NullPointerException("ContentHandler");

else
contentHandler = value;

}

// ...

}


为了支持 XMLReader,XIReader 提供了下列方法来注册和访问各种 SAX 处理程序:ContentHandler、ErrorHandler、DTDHandler 和 EntityResolver。

严格来说,DTDHandler 和 EntityResolver 没有用:旧文本没有 DTD,所以 XIReader 决不会发出与 DTD 相关的事件。

同样,也没有必要使用 EntityResolver;如果您回想起来,解析器不应该将它用于顶层文档实体。该接口只对外部实体(如 DTD)有用!因此,对旧文本文档没有用。尽管如此,SAX 还是授权方法来设置和获取这两个处理程序,并且 XIReader 强迫这样做。

XIReader 还实现对 SAX 功能和特性的有限支持。功能和特性控制解析的各个方面;它们由如 http://xml.org/sax/features/namespaces 之类的 URL 标识。请注意,URL 仅担当标识符,所以不要设法打开它们。(请不要访问网站 — 没有可访问的网站。)

规范声明 XMLReader 必须支持将 http://xml.org/sax/features/namespaces 功能设置成 true(支持 false 是可选的),将 http://xml.org/sax/features/namespace-prefixes 设置成 false(true 是可选的)。

第一个功能控制解析器是否对 XML 名称空间(true)解码。XIReader 始终使用名称空间。第二个功能控制是否在属性(true)列表中报告名称空间声明。XIReader 支持两个值。

正如您可看到的,XIReader 提供最小程度的一致性。尽管如此,我还是发现必须支持将 http://xml.org/sax/features/namespace-prefixes 设置成 true(正如规范所需要的,不仅是 false),因为 Apache Xalan 需要将该特性设置成 true 来正确地处理名称空间。

规范定义了其它功能及其 URL,但不要求解析器支持它们。由于这些功能中的大多数都处理有效性和 XML 模式,所以我选择省略它们。

我还定义了一个新特性 http://ananas.org/xi/features/rulesets 来为解析器提供其规则文件。该特性接受指向规则文件的 InputSource 值。

ContentHandler 和解析
在上一篇专栏文章“Wrestling with Java NIO”(请参阅参考资料)中讨论的代码中,大量处理发生在名为 read() 的方法中。我将它重命名为 match() 以提高可读性,并且修改它使它在对输入文档解码时调用 ContentHandler。清单 2 说明了这一操作。如果将该代码与“Wrestling with Java NIO”中的代码相比,您将发现它们的结构十分相似。唯一的重要差别是 print() 语句已经被各种对 ContentHandler 的调用所替换。

清单 2:match() 和 ContentHandler

public void match(Ruleset ruleset,String st,boolean firstMatch)

throws SAXException

{

attributes.clear();

int i = 0;

while(i < ruleset.getMatchCount())

{

if(ruleset.getMatchAtLight Bulb.matches(st))

{

Match match = ruleset.getMatchAtLight Bulb;

if(firstMatch && contentHandler != null)

contentHandler.startElement(match.getNamespaceURI(),

match.getLocalName(),

match.getQualifiedName(),

attributes);

for(int j = 1;j <= match.getGroupCount();j++)

{

QName qname = match.getGroupNameAt(j);

Ruleset nextRuleset = (Ruleset)rulesetsMap.get(qname);

if(nextRuleset != null)

match(nextRuleset,match.getGroupValueAt(j),true);

else
{

Group group = match.getGroupNameAt(j);

if(contentHandler != null)

{

contentHandler.startElement(group.getNamespaceURI(),

group.getLocalName(),

group.getQualifiedName(),

attributes);

String value = match.getGroupValueAt(j);

int begin = 0,

end = 0;

while(begin < value.length())

{

if(value.length() - begin < chars.length)

end = value.length();

else
end = begin + chars.length;

value.getChars(begin,end,chars,0);

contentHandler.characters(chars,0,end - begin);

begin = end;

}

contentHandler.endElement(group.getNamespaceURI(),

group.getLocalName(),

group.getQualifiedName());

}

}

}

String rest = match.rest();

if(rest != null)

match(ruleset,rest,false);

if(firstMatch && contentHandler != null)

contentHandler.endElement(match.getNamespaceURI(),

match.getLocalName(),

match.getQualifiedName());

break;

}

else
i++;

}

if(i < ruleset.getMatchCount()

&& ruleset.getError() != null

&& errorHandler != null)

errorHandler.error(new SAXParseException(ruleset.getError(),

this));

}


XM 是在第一篇使用 XML 专栏文章中介绍的发布项目,如果您记得它,那么您会熟悉发出 ContentHandler 的事件。XM 这样做来修正悬着的超链接。XIReader 构建在同一逻辑上,但它更雄心勃勃。它发出足够多的事件来描述一个完整的文档,而不是为链接发出一个事件。

我承认我最初很想编写完整的 XMLReader 实现。但是,正如本专栏文章所显示的那样,它简直是太容易了……,它恰恰证明了一点,SAX 确实做到了如它的名字所指出的,是用于 XML 的简单 API。

ContentHandler 的使用尤其简单。请考虑一下,您通常有打印开始和结束标记以及内容的方法。这些方法会处理字符转义、缩排和其它与语法相关的问题。ContentHandler 基本上为您定义了这些方法。使用 startElement() 方法打印开始标记,使用 endElement() 方法打印结束标记,使用 characters() 方法打印内容。

读取规则文件
建立了 XMLReader 之后,我想使 XI 能够读取规则文件。我已经不只有可以处理地址簿的 XI 应用程序,所以想打破硬编码的正则表达式的束缚。

我主要保存了在前二篇专栏文章中引入的词汇表。规则文件类似于清单 3。根元素是 rules;它包含一个或多个 ruleset 元素。

每个 ruleset 都包含表示正则表达式的 match 列表。error 元素详细描述当 XI 不能与任何正则表达式匹配时要做些什么。最后,group 元素表示正则表达式中的组。与每个元素相连的是元素名,它是 XI 使用的名称。

清单 3:rules.xml

<?xml version="1.0"?>

<xi:rules version="1.0"

xmlns:xi="http://ananas.org/2002/xi/rules"

defaultPrefix="an"

targetNamespace="http://ananas.org/2002/sample">

<xi:ruleset name="address-book">

<xi:match name="alias"

pattern="^alias (.*)Sad.*)$">

<xi:group name="id"/>

<xi:group name="email"/>

</xi:match>

<xi:match name="note"

pattern="^note .*Sad.*)$">

<xi:group name="fields"/>

</xi:match>

<xi:error message="unknown line type"/>

</xi:ruleset>

<xi:ruleset name="fields">

<xi:match name="field"

pattern="[\s]*<([^<]*)>">

<xi:group name="field"/>

</xi:match>

</xi:ruleset>

</xi:rules>


我在原始词汇表和清单 3 中的词汇表之间做了一个更改:现在文档支持一个应用于整个规则文件的全局名称空间。我最初的想法是让用户在规则文件中指定多个名称空间,但它使 XIReader 变得不必要的复杂。

随着我深入研究问题,我认识到全局名称空间满足了所有需求中的 99%。但是,万一您真的需要多个名称空间,那该怎么办呢?您仍可以使用它,因为无论如何文档在 XSLT 中总是后处理的。将新的名称空间添加到样式表中是一件简单的事情。

用 HC 来补救
编写本专栏文章的乐趣之一是当继续该专栏时我可以重用项目。在这个案例中,我使用几个月前介绍的 HC,处理程序编译器(Handler Compiler)来简化解析规则文件。

如果您没有阅读过相应的专栏文章,那么 HC 是获取 XPaths 作注解的 Java 类并将它转换成 SAX ContentHandler 的预编译器。该类中的每个方法都与一个或多个 XPath 匹配。实际上,它省去编写许多冗长乏味的状态管理代码。

清单 4 是规则文件的处理程序。可以在 Javadoc 注释中看到那些 XPath。该处理程序为规则词汇表中的每个元素定义一个方法。当它遍历规则文件时,它用正则表达式填充数据结构。

清单 4:RulesHandler.java

package org.ananas.xi;

import java.util.*;

import org.xml.sax.*;

/**

* @xmlns xi http://ananas.org/2002/xi/rules

*/

public class RulesHandler

implements org.ananas.hc.HCHandler

{

private String namespaceURI = null;

private String prefix = null;

private List rulesets = null;

private Ruleset getLastRuleset()

{

return (Ruleset)rulesets.get(rulesets.size() - 1);

}

/**

* @xpath xi:rules

*/

public void init(Attributes attributes)

{

rulesets = new ArrayList();

namespaceURI = attributes.getValue("targetNamespace");

prefix = attributes.getValue("defaultPrefix");

if(namespaceURI != null)

{

namespaceURI = namespaceURI.trim();

if(namespaceURI.equals(""))

namespaceURI = null;

}

if(prefix != null)

{

prefix = prefix.trim();

if(prefix.equals(""))

prefix = null;

}

}

/**

* @xpath xi:rules/xi:ruleset

*/

public void doRuleset(Attributes attributes)

throws SAXException

{

String name = attributes.getValue("name");

if(name != null)

rulesets.add(new Ruleset(namespaceURI,

name,

prefix));

else
throw new SAXException("name attribute required for xi:ruleset");

}

/**

* @xpath xi:rules/xi:ruleset/xi:match

*/

public void doMatch(Attributes attributes)

throws SAXException

{

String name = attributes.getValue("name"),

pattern = attributes.getValue("pattern");

if(name != null && pattern != null)

{

Ruleset ruleset = getLastRuleset();

ruleset.addMatch(new Match(namespaceURI,

name,

prefix,

pattern));

}

else
throw new SAXException("name and pattern attributes" +

"required for xi:match");

}

/**

* @xpath xi:rules/xi:ruleset/xi:error

*/

public void doError(Attributes attributes)

throws SAXException

{

String message = attributes.getValue("message");

if(message != null)

{

Ruleset ruleset = getLastRuleset();

if(ruleset.getError() == null)

ruleset.setError(message);

else
throw new SAXException("no more than one error per xi:ruleset");

}

else
throw new SAXException("message attribute required for xi:error");

}

/**

* @xpath xi:rules/xi:ruleset/xi:match/xi:group

*/

public void doGroup(Attributes attributes)

throws SAXException

{

String name = attributes.getValue("name");

if(name != null)

{

Ruleset ruleset = getLastRuleset();

Match match = ruleset.getLastMatch();

match.addGroup(new Group(namespaceURI,

name,

prefix));

}

else
throw new SAXException("name attribute required for xi:group");

}

public Ruleset[] getRulesets()

{

Ruleset[] array = new Ruleset[rulesets.size()];

return (Ruleset[])rulesets.toArray(array);

}

public String getNamespaceURI()

{

return namespaceURI;

}

public String getPrefix()

{

return prefix;

}

}


直到下一次
处理 XI 的工作接近完成了。现在,您有了一个运行的处理器,如清单 5 说明的那样,使它与 XSLT 处理器交互是一件简单的事情。在下一篇专栏文章中,我将围绕现有核心介绍简单的用户界面,以使 XI 更有用。

清单 5:样本 Main 方法

public static void main(String[] params)

throws TransformerException, TransformerConfigurationException,

SAXException, IOException

{

InputSource inputSource = new InputSource(new FileInputStream(params[0]));

inputSource.setSystemId(params[0]);

XMLReader xmlReader =

XMLReaderFactory.createXMLReader("org.ananas.xi.XIReader");

xmlReader.setProperty(XIReader.RULESETS_URI,new InputSource("rules.xml"));

TransformerFactory factory = TransformerFactory.newInstance();

Transformer transformer = factory.newTransformer();

transformer.transform(new SAXSource(xmlReader,inputSource),new

StreamResult("result.xml"));

}




flat modethreaded modego to previous topicgo to next topicgo to back
  已读帖子
  新的帖子
  被删除的帖子
Jump to the top of page

   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923