引言
XML Catalog 实现了根据 XSD 实时校验 XML 文件的功能。用户不用编写程序,通过少量的配置就可以在编辑 XML 文件的时候得到及时的反馈(需要在 XML 编辑器进行文件的编写),实现了实时的校验。
然而在实际应用中,由于环境的不同以及 XML 文件本身的不同,手动方式的配置并不能满足需求。比如,作者在实践中发现,在开发环境里手动配置的 XML Catalog 是不能保存在运行环境的。而且,许多实际的 XML 的编写方式并没有采用标准的格式,这也给 XML Catalog 的使用带来了很多的不便。
本文针对以上问题,通过例子来说明如何通过扩展 XML Catalog 来实现基于 xsd 对 XML 文件的自动化实时校验。
XML Catalog 介绍
XML Catalog 是基于 OASIS XML Catalog specification 标准的实现,它提出了一些关于 XML 文件如何引用外部资源的控制。Eclipse 的 WTP 提供了 XML Catalog 的功能,实现利用 schema 对 xml 文件的实时校验功能。XML Catalog 是由来自一个或者多个 catalog 条目文件的条目组成的 xml 文件,其保存了要校验的 xml 文件以及该文件对应的 xsd 文件的映射,在运行时可以自动将它们关联起来,从而实现对 xml 文件的校验。
下面通过一个例子来说明 XML catalog 的相关概念。
XML Catalog 相关概念介绍
<?xml version="1.0"?> <!DOCTYPE catalog PUBLIC "-//OASIS/DTD Entity Resolution XML Catalog V1.0//EN" "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd"> 1 <catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog"> 2 <group prefer="public" xml:base="file:///usr/share/xml/" > 3 <public publicId="-//OASIS//DTD DocBook XML V4.5//EN" 4 uri="docbook45/docbookx.dtd"/> <system systemId="http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" 5 uri="docbook45/docbookx.dtd"/> <system systemId="docbook4.5.dtd" 6 uri="docbook45/docbookx.dtd"/> </group> </catalog>
- DOCTYPE 表示这个文件是 OASIS XML catalog 文件。如果没有 Internet 连接,那么,整个 DOCTYPE 的声明需要删除或者注释掉。因为 catalog 处理器会尝试从网络去下载 catalog.dtd 文件,显然在没有网络的环境下,处理器因为找不到文件会报错。
- catalog 元素包含了 catalog 的内容和 catalog 的命名空间标识。
- group 元素是一个包装元素,可以设置在这个组里面包含的所有 catalog 条目的属性。属性 prefer="public" 指出 catalog resolver(解析器) 在使用 SYSTEM 标识符之前优先使用 PUBLIC 标识符。属性 xml:base 表明,所有 URI 都是相对这个路径。本例中,uri="docbook45/docbookx.dtd"/ 是个相对路径,绝对路径应该是 file:///usr/share/xml/ docbook45/docbookx.dtd "。
- public 元素将 publicId 映射到 uri 的路径。publicId 是资源的一个唯一标识,通常是该文件的命名空间 .
- system 元素将 systemId 映射到 uri 的路径。systemId 与 publicId 一样,也是资源的唯一标识,通常是资源文件在文件系统的全路径。
XML Catalog 原理
XML Catalog 提供了一种重新定位资源的机制,可以将 xml 引用的 artifacts,包括 URI 地址以及 namespace 名重新定位到另一个地址。通常这种机制被用来将远程的引用资源重定位到本地或者 web。XML catalog 就是一个描述外部实体引用和本地缓存的相同实体的映射的文件。
在实际的开发生产中,xml 文件经常会引用外部的文件,这些文件通常通过 URI 表示,其中以 URL 应用最广。但是如果是绝对的 URL, 那么只有当你的网络能够访问它时才能起作用,如果网络出现问题,那么将不能访问。当是相对 URL 时,例如"../../xml/dtd/docbookx.xml",只有当你的文件系统和定义者一致的时候才能起作用。
一种解决办法就是通过实体解析器(Entity Resolver)或者是 URI 解析器 (URI Resolver ),解析器可以通过检查资源的 URI 来定位资源。 用户通过配置 xml catalog, 手动的指定 xml 文件引用的 xsd 文件的本地地址,URI 解析器通过 xml catalog 里面的映射,找到对应的 xsd, 最后 xml catalog 处理器通过解析器找到的 xsd 对 xml 进行校验。
通俗点说,XML catalog 通过命名空间将 xml 文件及其对应的 xsd 文件联系起来,并通过解析器定位 xsd 文件的位置,最后通过处理器进行校验。
与 javax.xml.validation 通过 xsd 对 xml 进行校验的方法不同,xml catalog 可以通过 Namespace 来校验所有引用这个 xsd 的 xml 文件,从而达到批量校验的效果。例如,a.xml,b.xml,c.xml 都是由 d.xsd 校验,那么只要将 d.xsd 的命名空间配置好,通过该命名空间就可以校验以上三个文件了。
手动方式配置 XML Catalog
手动配置 XML Catalog 比较简单也比较容易理解,可以在 Eclipse 的开发环境中直接进行。选择 File -> New -> Other -> Examples -> Editing and Validating XML files,便可以进行配置了。如下图所示:
图 1. 打开 XML Catalog 的配置界面
从图 1 中可以看到有两种 xml catalog entity , 用户定义的实体及插件的实体。用手动方式生成的配置属于用户配置的实体。
图 2. 新建一个 XML Catalog Entry
新建一个 XML Catalog Entry 中的 Location 指明了 xsd 文件的位置,在这里我们选择来源于工作空间。
图 3. 从工作空间选择 student.xsd 文件
选择 OK 以后,XML Catalog Entry 就创建完成了。XML Catalog 会自动填写其他两个属性,如下图所示。
图 4. 完整的 XML Catalog Entry
如图所示,Location 标签指定了 xsd 的位置,在这里是工作空间的相对路径;Key Type 标签表明了在这里我们使用的是命名空间的名字作为 xml 和 xsd 的关联;Key 标签的值是 xml 命名空间的值。
图 5. 完整的 XML Catalog Entry
从图 5 中可以看出,新建的 XML Catalog Entry 已经在 User Specified Entries 目录下。图 1 到 4 显示了 XML Catalog 的手动配置步骤。下面,我们在 XML 编辑器里打开一个 xml 文件,看一下 XML Catalog 的功能。
图 6. 正确的 XML 文件
首先,用 XML 编辑器打开 sample.xml 文件。
图 7. XML Catalog 的实时校验结果
将 name 为 BB 的学生的 age 标签去调,从图中可以看到在编辑器的右边有一个红色的点,同时在 student 元素下面有个红色的波浪线,提示 student 元素有错误。这是,将鼠标移到 student 元素上,可以看到具体的提示信息,student 元素不完整,缺少 age 元素。
可以看出,应用 XML Catalog 对 xml 文件进行实时校验,配置步骤比较简单,只需提供相应的 xsd 文件及位置,就能将他们关联起来,实现自动的校验。
扩展 XML Catalog Contributions
在实践中发现,如果通过上一节介绍的手动方式在 Eclipse 开发环境配置 xml catalog,那么这些配置并不会在运行环境生效。然而通常情况下,我们需要的是运行环境的配置。比如我们要开发一些小工具,可以让用户编辑 xml 文件,同时我们内置了一些 xsd 文件来校验用户编辑的 xml 文件是不是正确。因为不能让用户在使用产品的时候再重新配置(因为对用户来说,他们并不需要知道这些 xsd 的存在,还有就是,不能增加用户的负担)。所以,我们需要在开发环境配置好 xsd 的信息,并让这些配置在运行时生效。通过调研,我们发现,如果要在运行环境中直接使用开发环境的配置,那么需要扩展 xml catalog Contributions 这个扩展点。
下面我们就一步步的演示如何在 Eclipse 插件里扩展 XML Catalog Contributions 扩展点。
1.打开 plugin.xml 文件的 Extensions 标签,点击 Add 按钮,添加扩展点。
图 8. 添加扩展点
2.在 Extension Point filter 文本框里输入扩展点的名字,org.eclipse.wst.xml.core.catalogContributions,并选中该扩展点,点击 OK 按钮。
图 9. 选中扩展点
图 10 展示了成功添加扩展点后的 Extension 页面。
图 10. 成功添加扩展点
3.新建一个 catalogContribution
图 11. 新建 catalogContribution
4.新建一个 public
图 12. 新建 public
5.填写 public 的属性信息。public 有两个属性,publicId 和 uri。
publicId 就是命名空间(namespace),uri 就是 xsd 文件的物理位置,可以通过 Browse 按钮来选择 xsd 文件。
图 13. 填写 public 详细信息
通过以上 5 步,就完成了对 xml catalog Contributions 的扩展。图 14 就是完成以上配置以后的 plug.xml 文件内容。从这个文件我们可以清楚地看清楚扩展的相应配置。
图 14. plugin.xml 文件
从图 15 可以看出,与手工方式的配置不同,在运行时的环境里,User Specified Entries 里面并没有内容。
图 15. 运行环境的 XML Catalog 配置
将 sample.xml 改错,可以看见 XML Catalog 的提示信息。
图 16. sampe.xml 文件
通过以上的步骤,我们演示了,如何扩展 XML Catalog。在下一个部分,我们将会演示如何扩展 URI Resolver 实现特殊 XML 文件的校验。
扩展 URI Resolver
XML Catalog 虽然提供了比较强大的功能,但是由于实际生产环境的复杂性,一些 xml 文件并不能由其进行校验。比如,有些 xml 文件,由于书写不规范,并没有命名空间,还有些 xml 文件,由于应用环境的原因,其使用了 xsi:schemaLocation 元素,将引用的 xsd 文件直接定位到了虚拟的路径,导致 xml catalog 的定位功能失效 . 对于第一种情况,由于 XML Catalog 的设计理念就是通过命名空间进行 xml 和 xsd 的关联,所以,不支持此种情况。对于第二中情况,我们可以通过扩展 XML Catalog 来实现。
在扩展 URI Resolver 之前,我们对 XML Catalog 可以处理的 xml 和 xsd 的类型进行了分类,以便大家能够清楚地知道 XML Catalog 可以解决哪些问题。
xsd \ xml | 有命名空间 | 无命名空间 |
---|---|---|
有命名空间,无 xsi:schemaLocation | 可以 | 不可以 |
有命名空间,有 xsi:schemaLocation,且其值是 xsd 实际存在的位置 | 可以 | 不可以 |
有命名空间,有 xsi:schemaLocation,且其值不是 xsd 实际存在的位置 | 可以 | 不可以 |
无命名空间 | 不可以 | 不可以 |
URI Resolver 简介
URIResolver 负责资源的定位,XML Catalog 的处理器根据 URIResolver 的资源位置找到相应的 xsd,然后进行校验。
特殊的 XML 文件
如图 18 所示,sample.xml 文件里中有一个 xsi:shemaLocation 属性,它将 xsd 的位置定位到 http://www.sample.com/sample/schemas/student.xsd。正常情况下,XML Catalog 会到该位置去找 xsd, 然而,在本文中这是一个无效的地址,所以 XML Catalog 会因为找不到 xsd 而不起作用。
图 18. 带有 schemaLocation 标签的 XML 文件
扩展 org.eclipse.wst.common.uriresolver.resolverExtensions
resolverExtensions 扩展点可以让用户注册自己的 URI Resolver,从而达到扩展默认 Resolver 的功能的作用。与默认的 Resolver 一样,用户扩展的 Resolver 也可以被编辑器,校验器和向导调用。
图 17. 添加扩展点 resolverExtensions
如下图所示,该扩展点有三个属性,class(类名), stage(阶段)和 priority(优先级)。Class 指定了实现 org.eclipse.wst.common.uriresolver.internal.provisional.URIResolver 接口的类;stage 指定了在哪个阶段运行 resolver(解析器),有三个值,分别是 prenormalization, postnormalization 和 physical,默认是 physical。Prenormalization 表示解析器在输入参数的格式统一之前运行,postnormalization 表示解析器在输入参数的格式统一之前运行,physical 表示在所有的 pre 和 postnormalization 解析器之后运行。Priority 指定了在处在相同 stage 的解析器的执行优先级。优先级分为 high(高级),medium(中级)和 low(低级)三种,默认为中级。
图 18. 填写扩展点的属性
下图为增加了 resolverExtensions 扩展点的 plugin.xml 文件的内容。
图 19. plugin.xml 文件内容
实现 URIResolverExtension
1.首先,在插件中新建一个类,实现 URIResolverExtension 接口。URIResolverExtension 接口提供了 resolve 方法,用来重新定位 xsd 资源,也就是找到有效的 xsd。该方法的参数有四个,第一个类型为 IFile,是在工作空间 (workspace) 中的文件,第二个参数是 String baseloaction,它是该文件在文件系统的绝对路径,第三个参数是 publicId,它是该文件的命名空间,最后一个参数是 String systemid,它是文件的实际路径,对于 xml 文件来说,它的 systeid 为空。但是因为我们在第 2 小节扩展了 XML Catalog, 所以,对于在 XML Catalog 文件里面配置好的 publicId,它的 systemId 就是该文件里面 Uri 的值,也就是 xsd 文件的实际路径。该函数的返回值就是 xml 文件引用的 xsd 文件的位置。
清单 1. 实现 URIResolverExtension 接口
public class MyURIResolverExtension implements URIResolverExtension { public MyURIResolverExtension() { } @Override public String resolve(IFile file, String baseLocation, String publicId, String systemId) { return null; } }
清单 2. 添加 catalog 到 catalog manager
ICatalog catalog = XMLCorePlugin.getDefault().getDefaultXMLCatalog(); if (catalog == null) { return null; }
清单 3. 重新定位资源
if (myResolved == null) { if (publicId != null) { if ((systemId != null && systemId.endsWith("student.xsd"))) //$NON-NLS-1$ { try { int index = systemId.lastIndexOf("/"); if (index > -1) systemId = systemId.substring(index); myResolved = catalog.resolvePublic(publicId, systemId); } catch (MalformedURLException me) { myResolved = null; } catch (IOException ie) { myResolved = null; } } } }
由清单 3 可以看出,我们通过 catalog manager 的 resolvePublic 方法,通过 publicId 找到 Catalog 里与 publicId 对应 uri 的值,这个值就是 xsd 文件的路径。
小结
本文首先介绍了 XML Catalog 相关概念以及基本原理,使读者对其有了初步的认识。接着通过一个简单的例子介绍了手动方式配置 XML Catalog 的步骤,使读者有了进一步的了解。最后,通过扩展 XML Catalog 和 URI Resolver 两个扩展点来实现比较高级的功能,使读者对该技术有了深入的了解。XML Catalog 是比较通用的解决 XML 实时校验问题的方法,尤其与 XML Catalog 和 URI Resolver 两个扩展点的结合在解决一些特殊的 XML 文件的实时校验问题时,往往能取得事半功倍的效果。