XML Catalog 是基于 OASIS XML Catalog specification 标准的实现,它提出了一些关于 XML 文件如何引用外部资源的控制。Eclipse 的 WTP 提供了 XML Catalog 的功能,实现利用 schema 对 xml 文件的实时校验功能。XML Catalog 是由来自一个或者多个 catalog 条目文件的条目组成的 xml 文件,其保存了要校验的 xml 文件以及该文件对应的 xsd 文件的映射,在运行时可以自动将它们关联起来,从而实现对 xml 文件的校验。
下面通过一个例子来说明 XML catalog 的相关概念。
12 3
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 比较简单也比较容易理解,可以在 Eclipse 的开发环境中直接进行。选择 File -> New -> Other -> Examples -> Editing and Validating XML files,便可以进行配置了。如下图所示:
从图 1 中可以看到有两种 xml catalog entity , 用户定义的实体及插件的实体。用手动方式生成的配置属于用户配置的实体。
新建一个 XML Catalog Entry 中的 Location 指明了 xsd 文件的位置,在这里我们选择来源于工作空间。
选择 OK 以后,XML Catalog Entry 就创建完成了。XML Catalog 会自动填写其他两个属性,如下图所示。
如图所示,Location 标签指定了 xsd 的位置,在这里是工作空间的相对路径;Key Type 标签表明了在这里我们使用的是命名空间的名字作为 xml 和 xsd 的关联;Key 标签的值是 xml 命名空间的值。
从图 5 中可以看出,新建的 XML Catalog Entry 已经在 User Specified Entries 目录下。图 1 到 4 显示了 XML Catalog 的手动配置步骤。下面,我们在 XML 编辑器里打开一个 xml 文件,看一下 XML Catalog 的功能。
首先,用 XML 编辑器打开 sample.xml 文件。
将 name 为 BB 的学生的 age 标签去调,从图中可以看到在编辑器的右边有一个红色的点,同时在 student 元素下面有个红色的波浪线,提示 student 元素有错误。这是,将鼠标移到 student 元素上,可以看到具体的提示信息,student 元素不完整,缺少 age 元素。
可以看出,应用 XML Catalog 对 xml 文件进行实时校验,配置步骤比较简单,只需提供相应的 xsd 文件及位置,就能将他们关联起来,实现自动的校验。
在实践中发现,如果通过上一节介绍的手动方式在 Eclipse 开发环境配置 xml catalog,那么这些配置并不会在运行环境生效。然而通常情况下,我们需要的是运行环境的配置。比如我们要开发一些小工具,可以让用户编辑 xml 文件,同时我们内置了一些 xsd 文件来校验用户编辑的 xml 文件是不是正确。因为不能让用户在使用产品的时候再重新配置(因为对用户来说,他们并不需要知道这些 xsd 的存在,还有就是,不能增加用户的负担)。所以,我们需要在开发环境配置好 xsd 的信息,并让这些配置在运行时生效。通过调研,我们发现,如果要在运行环境中直接使用开发环境的配置,那么需要扩展 xml catalog Contributions 这个扩展点。
下面我们就一步步的演示如何在 Eclipse 插件里扩展 XML Catalog Contributions 扩展点。
1.打开 plugin.xml 文件的 Extensions 标签,点击 Add 按钮,添加扩展点。
2.在 Extension Point filter 文本框里输入扩展点的名字,org.eclipse.wst.xml.core.catalogContributions,并选中该扩展点,点击 OK 按钮。
图 10 展示了成功添加扩展点后的 Extension 页面。
3.新建一个 catalogContribution
4.新建一个 public
5.填写 public 的属性信息。public 有两个属性,publicId 和 uri。
publicId 就是命名空间(namespace),uri 就是 xsd 文件的物理位置,可以通过 Browse 按钮来选择 xsd 文件。
通过以上 5 步,就完成了对 xml catalog Contributions 的扩展。图 14 就是完成以上配置以后的 plug.xml 文件内容。从这个文件我们可以清楚地看清楚扩展的相应配置。
从图 15 可以看出,与手工方式的配置不同,在运行时的环境里,User Specified Entries 里面并没有内容。
将 sample.xml 改错,可以看见 XML Catalog 的提示信息。
通过以上的步骤,我们演示了,如何扩展 XML Catalog。在下一个部分,我们将会演示如何扩展 URI Resolver 实现特殊 XML 文件的校验。
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 实际存在的位置 | 可以 | 不可以 |
无命名空间 | 不可以 | 不可以 |
URIResolver 负责资源的定位,XML Catalog 的处理器根据 URIResolver 的资源位置找到相应的 xsd,然后进行校验。
如图 18 所示,sample.xml 文件里中有一个 xsi:shemaLocation 属性,它将 xsd 的位置定位到 http://www.sample.com/sample/schemas/student.xsd。正常情况下,XML Catalog 会到该位置去找 xsd, 然而,在本文中这是一个无效的地址,所以 XML Catalog 会因为找不到 xsd 而不起作用。
resolverExtensions 扩展点可以让用户注册自己的 URI Resolver,从而达到扩展默认 Resolver 的功能的作用。与默认的 Resolver 一样,用户扩展的 Resolver 也可以被编辑器,校验器和向导调用。
如下图所示,该扩展点有三个属性,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(低级)三种,默认为中级。
下图为增加了 resolverExtensions 扩展点的 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 文件的位置。
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 文件的路径。