Java开发网 |
注册 |
登录 |
帮助 |
搜索 |
排行榜 |
发帖统计
|
您没有登录 |
» Java开发网 » 技术文章库
打印话题 寄给朋友 订阅主题 |
作者 | (转帖)使用 XML:完成 XI 实现 XMLReader 接口 |
killerNT
发贴: 0 积分: 0 |
于 2002-12-05 17:43
使用 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.getMatchAt.getQualifiedName()); 修改这条语句以产生正确的 XML 并不难: System.out.print("<"+ruleset.getMatchAt.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.getMatchAt.matches(st)) { Match match = ruleset.getMatchAt; 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 (.*).*)$"> <xi:group name="id"/> <xi:group name="email"/> </xi:match> <xi:match name="note" pattern="^note .*.*)$"> <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")); } |
话题树型展开 |
人气 | 标题 | 作者 | 字数 | 发贴时间 |
7712 | (转帖)使用 XML:完成 XI 实现 XMLReader 接口 | killerNT | 14031 | 2002-12-05 17:43 |
已读帖子 新的帖子 被删除的帖子 |
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 |