引言
XML Catalog 实现了根据 XSD 实时校验 XML 文件的功能。用户不用编写程序,通过少量的配置就可以在编辑 XML 文件的时候得到及时的反馈(需要在XML 编辑器进行文件的编写),实现了实时的校验。
然而在实际应用中,由于环境的不同以及 XML 文件本身的不同,手动方式的配置并不能满足需求。比如,作者在实践中发现,在开发环境里手动配置的 XML Catalog 是不能保存在运行环境的。而且,许多实际的 XML 的编写方式并没有采用标准的格式,这也给XML Catalog 的使用带来了很多的不便。
本文针对以上问题,通过例子来说明如何通过扩展 XML Catalog 来实现基于 xsd 对 XML 文件的自动化实时校验。
XML Catalog 介绍
XML Catalog 是基于 OASISXML Catalog specification 标准的实现,它提出了一些关于 XML 文件如何引用外部资源的控制。Eclipse 的 WTP 提供了 XML Catalog 的功能,实现利用schema 对 xml 文件的实时校验功能。XMLCatalog 是由来自一个或者多个 catalog 条目文件的条目组成的 xml 文件,其保存了要校验的 xml 文件以及该文件对应的 xsd 文件的映射,在运行时可以自动将它们关联起来,从而实现对 xml 文件的校验。
下面通过一个例子来说明 XML catalog 的相关概念。
XML Catalog 相关概念介绍
<!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> |
1. DOCTYPE表示这个文件是 OASIS XML catalog 文件。如果没有 Internet 连接,那么,整个DOCTYPE 的声明需要删除或者注释掉。因为 catalog 处理器会尝试从网络去下载catalog.dtd 文件,显然在没有网络的环境下,处理器因为找不到文件会报错。
2. catalog元素包含了 catalog 的内容和catalog 的命名空间标识。
3. group 元素是一个包装元素,可以设置在这个组里面包含的所有 catalog 条目的属性。属性prefer="public" 指出 catalog resolver(解析器) 在使用 SYSTEM 标识符之前优先使用PUBLIC 标识符。属性 xml:base 表明,所有 URI 都是相对这个路径。本例中,uri="docbook45/docbookx.dtd"/ 是个相对路径,绝对路径应该是 file:///usr/share/xml/ docbook45/docbookx.dtd "。
4. public元素将 publicId 映射到 uri 的路径。publicId 是资源的一个唯一标识,通常是该文件的命名空间 .
5. 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 ValidatingXML files,便可以进行配置了。如下图所示:
从图 1 中可以看到有两种 xmlcatalog entity , 用户定义的实体及插件的实体。用手动方式生成的配置属于用户配置的实体。
新建一个 XML Catalog Entry 中的 Location 指明了 xsd 文件的位置,在这里我们选择来源于工作空间。
图 _3. 从工作空间选择 student.xsd 文件
图 _4. 完整的 XML Catalog Entry
图 _5. 完整的 XML Catalog Entry
从图 5 中可以看出,新建的 XML Catalog Entry 已经在 User Specified Entries 目录下
。图 1 到 4 显示了 XML Catalog 的手动配置步骤。下面,我们在 XML 编辑器里打开
一个 xml 文件,看一下 XML Catalog 的功能。
图 _6. 正确的 XML 文件
首先,用 XML 编辑器打开 sample.xml 文件。
将 name 为 BB 的学生的 age 标签去调,从图中可以看到在编辑器的右边有一个红色的点,同时在 student 元素下面有个红色的波浪线,提示 student 元素有错误。这是,将鼠标移到 student 元素上,可以看到具体的提示信息,student 元素不完整,缺少 age 元素。
可以看出,应用 XML Catalog 对 xml 文件进行实时校验,配置步骤比较简单,只需提供相应的 xsd 文件及位置,就能将他们关联起来,实现自动的校验。
下面我们就一步步的演示如何在 Eclipse 插件里扩展 XML Catalog Contributions 扩展点。
1.打开 plugin.xml 文件的 Extensions 标签,点击 Add 按钮,添加扩展点。
图 _8. 添加扩展点
2.在 Extension Pointfilter 文本框里输入扩展点的名字,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 里面并没有内容。
将 sample.xml 改错,可以看见 XML Catalog 的提示信息。
扩展 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 可以解决哪些问题。
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 文件
resolverExtensions 扩展点可以让用户注册自己的 URI Resolver,从而达到扩展默认 Resolver 的功能的作用。与默认的 Resolver 一样,用户扩展的 Resolver 也可以被编辑器,校验器和向导调用。
图 17. 添加扩展点 resolverExtensions
图 _18. 填写扩展点的属性
图 _19. plugin.xml 文件内容
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; }
} |
ICatalog catalog = XMLCorePlugin.getDefault().getDefaultXMLCatalog(); if (catalog == null) { return null; } |
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 文件的路径。