跳转到主要内容

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

所有提交的信息确保安全。

  • 关闭 [x]

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

所有提交的信息确保安全。

  • 关闭 [x]

使用 XML: 处理指令和参数

添加多个样式表支持

Author photo
Benoît Marchal 是住在比利时那慕尔的顾问和作家。 他是 XML by Example Applied XML Solutions XML and the Enterprise 的作者。同时是 Gamelan 的专栏作家。 www.marchal.com 上有其最新项目的详细信息。

简介: 这个月,我们不辞辛劳的专栏作家将多个样式表的支持添加到 XM 内容管理项目中。 在这样做时,他涉及到了 TrAX URIResolver 并编写伪属性的解析器。如往常一样,可在 developerWorks 开放源码专区获得完整的源代码。

在“使用 XML”专栏文章中,Beno�t Marchal 每个月都报告其关于一个或多个开放源码 XML 开发项目的进展。 您可以随着他的进展,遵循他的设计决策和编码选择,也可以提出一些建议以及在您自己的项目中重用该开放源代码。

发布日期: 2001 年 9 月 01 日
级别: 初级
访问情况 : 1730 次浏览
评论: 


继续有关 XM 的工作。这个月,我已经添加了多个样式表支持,因此可以解决读者发来的最常见建议。 我还添加了一个将一些参数传递到样式表的选项,它使 XM 基本的发布功能更完美。 在开始继续开发更高级的功能期间,我已经包括了一个目录阅读器。(您可以下载有关这篇文章以及以前专栏文章的所有相关代码; 请参阅副栏 获取代码。)

多个样式表

XM 的头两个版本沿用“用一种模式来套用所有”策略,最初看上去似乎是一个好主意,但经过证实它是无效的。 更明确地讲,到现在为止,XM 只识别一种样式表 rules.xsl 。正如本系列的第一篇文章中说明的那样, 我原先认为我本可以使用不同的 XSLT 模板来选择样式的变更:

<xsl:template match="db:article">
   <!-- rules for an article here -->
</xsl:template>
<xsl:template match="xm:Directory">
   <!-- rules for a directory here -->
</xsl:template>

然而,我自己在 ananas.org(我完全用 XM 维护的网站)上的经历说明了我最初的计划不能很好地工作。 我还收到一些读者的建议,建议我解决那些他们察觉是缺陷的问题。最后, 当我开始着手内容生成(稍后将在本文中介绍)时,更觉得有必要添加多个样式表的支持了。

获取代码

跟往常一样, 可以从 CVS 资源库(请参阅 参考资料)下载该专栏文章的代码。可以从同一个地方下载 ZIP 文件。 这个月,下载文件包括样本 .xml 和 .xsl 文件。

处理指令

您可能记起,易用性是我优先使用 XM 的原因之一, 明确地讲,我不希望使用配置文件或“构建脚本”来选择哪个样式表适用于哪个地方(有关这一需求的完整讨论, 请阅读 使用 XML:将 XSLT 用于内容管理)。

读者建议用巧妙的命名约定来选择样式表,但对于解决方案的最好提议来自一位同事, 他提醒我使用 xml-stylesheet 处理指令。

如果您不熟悉 xml-stylesheet ,则可以看于 1999 年 7 月发表的小的 W3C 建议书,其中介绍了它,xml-stylesheet 是通过 Internet Explorer 5.0 而得到普及。处理指令将样式表(XSL 或 CSS)与 XML 文档相关联。 例如:

<?xml version="1.0"?>
        
        
          
          <?xml-stylesheet href="classic.xsl" type="text/xml"?>
<?xml-stylesheet href="funky.xsl" type="text/xml" alternate="yes"?>
        
        
<article>
<articleinfo>
 <title>ananas.org</title>
<!-- rest of the document goes here -->
      
      

一般而言,处理指令对应用程序特定的数据进行编码。您已经熟悉了处理指令,因为大多数 XML 文档都是以 XML 声明开始, 该声明本身就是特殊的处理指令。一条处理指令包含一个目标(在上面示例中, xml-stylesheet ),后跟数据。 用 <??> 定界符将处理指令包起来。目标确定应用程序, 而对应用程序不能识别的目标,其会忽略这些处理指令。

数据格式完全是自由的。XML 不指定将什么东西放进去(当然,除了 XML 声明)。 事实上,由于历史原因,处理指令可以包含 PostScript 图像或脚本等……但决不包含标记。

象声明一样, xml-stylesheet 有特殊地位,因为它是由 W3C 定义的。 它必须出现在文档的开始(即,在第一个元素之前),并且包含几个所谓的 伪属性。这个数据之所以称为伪属性, 是因为其语法与 XML 属性相似。

最重要的伪属性是 href ,它包含指向样式表的 URI。其它有用的伪属性有 typealternatetype 是样式表的 MIME 类型, 它用来区别 CSS 和 XSL。如果有多个 xml-stylesheet 指令, 则 alternate 表明哪一个替代主样式表。处理器应该用备用样式表列表提示用户。 然而,因为 XM 以批处理方式工作,所以它使用不同的策略,完全忽略备用样式表。

虽然 xml-stylesheet 是一种 W3C 标准,但 TrAX 处理器忽略它,除非另行告知。 应用程序必须明确地调用 getAssociatedStylesheet() 来检索处理指令,如下:

Source document = new StreamSource(file),
       stylesheet = factory.getAssociatedStylesheet(document,null,null,null);
if(null != stylesheet)
   transformer.transform(document,new StreamResult(System.out));
else
   throw new XMException("Cannot find the style sheet");

然而, getAssociatedStylesheet() 带来 XM 必须避免的两个问题。首先, getAssociatedStylesheet() 使高速缓存常用样式表变得困难。其次,它假设样式表存储在与文档相同的目录中。 我喜欢将样式表存储在不同的目录中,因为我发现,如果样式表都被分组在一个目录中,可以易于维护和共享样式表。

还传递样式表参数

选择样式表仅仅完成了解决方案的一半。 通常,我希望做一些小小的变动,而不必编写新的样式表。对于这种情况,我喜爱的解决方案是使用参数, 如清单 1 所示:


清单 1:样本参数
<xsl:stylesheet ...>
<xsl:param name="sponsor" select="'none'"/>
<xsl:template match="articleinfo">
   
        
        
          
          <xsl:if test="$sponsor='dw'">
        
        
      <center>
         <a href="http://www.ibm.com/developerWorks">
            <img align="middle" width="136" height="24" border="0"
                alt="developerWorks" src="!images/buttons/dw.gif"/>
         </a>
      </center>
   </xsl:if>
   <xsl:apply-templates/>
</xsl:template>
      
      

还有,如何传递这些参数?W3C 没有提议一种机制,所以定义新的处理指令似乎也就不足为奇了。XM 可以识别 xm-xsl-paramxml-stylesheetxm-xsl-param 的语法与其它处理指令相似,并且使用两个伪属性 namevalue

<?xml version="1.0"?>
        
        
          
          <?xm-xsl-param name="sponsor" value="dw"?>
        
        
<article>
   <articleinfo>
      <title>XM</title>
      
      

显而易见,TrAX 不支持 xm-xsl-param ,但因为我已经确定 XM 需要替换 getAssociatedStylesheet() ,所以解析 xm-xsl-param 的工作不是很多。

但这不是意味着要对文档解析两次吗?一次用于处理指令,另一次是使用 XSLT 处理器。实际上, 解析两次不会引起更多麻烦,因为处理指令必须出现在文档开始,所以 XM 只是重新解析文档的一小部分。

ProcessingInstructionHandler 和 PseudoAttributeTokenizer

ProcessingInstructionHandler 是一种 SAX ContentHandler ,它抽取 xml-stylesheetxm-xsl-param

处理程序截取 4 个事件。 setDocumentLocator()startDocument() 用于初始化。 大多数工作都发生在 processingInstruction() 中。至于 startElement() , 它用来停止解析,因为它标记这个开始的结束。要停止解析, startElement() 抛出一个异常。 这种作法近乎黑客所使用的手段,这是有争议的;异常一般用于报告错误,然而 startElement() 中没有错误,但 SAX 没有提供更“光明正大”的解决方案来停止解析。

虽然伪属性的语法与 XML 属性相类似,但 SAX 解析器不对它们进行译码。XM 使用它自己的解析器 PseudoAttributeTokenizer 来对伪属性进行译码。

PseudoAttributeTokenizer 每次扫描缓冲区一个字符,查找伪属性。 它使用一种典型的算法,这种算法可以在每本编译器书籍中找到。 如果您不熟悉方面的内容,那么我推荐您阅读以 Pascal 闻名的 Niklaus Wirth 的 Compiler Construction(请参阅 参考资料)。

要简化该代码, getc() 方法返回缓冲区中的下一个字符,而 putc() 替换缓冲区中 getc() 下一次调用的字符。

PseudoAttributeTokenizer 的公用接口由三个方法组成: hasMoreTokens() 测试缓冲区中是否还有伪属性, nextName() 返回下一个名称, nextValue() 返回下一个值。

让我们研究一下 nextName() 。它通过调用 eatSpaces() 来除去前导空格。接下来, 只要它发现有数字或字母,就一直循环下去,并将字符累积在变量( token )中。因为名称只包含数字和字母, 所以任何其它字符都可以表示这个循环的结束。 nextName() 特别关注读入缓冲区的、返回的最后一个字符, 其中,它将用于 nextValue()


清单 2:nextName() 示例
public String nextName()
   throws SAXParseException
{
   token.setLength(0);
   int c = eatSpaces();
   for(;;)
      if(c == -1)
         throw new SAXParseException(UNEXPECTED_EOS,locator);
      // strictly speaking a name cannot start with a digit...
      else if(!Character.isLetterOrDigit((char)c) && c != '-')
      {
         putc();   // put it back for the next call
         return token.length() == 0 ? null : token.toString();
      }
      else
      {
         token.append((char)c);
         c = getc();
      }
}

nextValue()nextName() 相似,但它先识别等号字符(由 nextName() 将它留在缓冲区中)和引号字符。 nextValue() 还译码预先定义的实体( <> 以及类似的)。

有了 tokenizer,就很容易译码处理指令。以下代码摘自 ProcessingInstructionHandler ,它用来识别 xml-stylesheetxm-xsl-param 的代码与这类似:


清单 3:ProcessingInstructionHandler 摘录
if(target.equals("xml-stylesheet"))
{
   String href = null,
          type = null;
   boolean alternate = false;
   PseudoAttributeTokenizer tokenizer =
      new PseudoAttributeTokenizer(data,locator);
   while(tokenizer.hasMoreTokens())
   {
      String name = tokenizer.nextName(),
             value = tokenizer.nextValue();
      if(name.equals("href"))
         href = value;
      else if(name.equals("alternate"))
         alternate = value.equals("yes");
      else if(name.equals("type"))
         type = value.trim();
      // ignore the media attribute...
   }
   if(type != null && href != null && !alternate &&
      (type.equals("text/xsl") || type.equals("text/xml") ||
       type.equals("application/xml+xslt")))
   {
      this.href = href;
      params.clear();
      readParams = true;
   }
   else
      readParams = false;
}

请记住,XM 会忽略备用样式表。W3C 建议书考虑到 HTTP,所以提供了优先于备用样式表的缺省样式表。XM 使用与这相同的规则, 应用它自己的缺省样式表,而不考虑备用样式表。

TemplatesManager

由于 XM 可使用多个样式表, 所以对高速缓存的逻辑进行了改进。这是由 TemplatesManager 来负责。当 StylingMover 请求 Templates 对象时,从高速缓存(如果该对象在其中)检索该对象。如果该对象不在其中, 则 TemplatesManager 装入样式表并将其放入高速缓存。本质上, TemplatesManager 是包含 java.util.Map 的封装器,并包含用于返回 Transformer 对象的附加方法。

正如前面所解释的那样,XM 不会将文档和样式表混在一起。它使用两个目录:文档目录和规则目录。TrAX 提供 URIResolver 接口以控制 XSLT 处理器如何装入文件。XSLT 处理器的 URIResolver 与 SAX 解析器的 EntityResolver 相似; 当该处理器装入已导入的样式表(通过 xsl:importxsl:include 元素)或文档(通过 document() 函数)时, 该处理器调用它的 resolve() 方法。

TemplatesManager 使用内部类 ReferenceResolver ,该类从规则目录装入样式表:


清单 4:ReferenceResolver 示例
protected class ReferenceResolver
   implements URIResolver
{
   protected File rulesDir;
   public ReferenceResolver(File rulesDir)
   {
      this.rulesDir = rulesDir;
   }
   public Source resolve(String href,String base)
   {
      if(href.endsWith(".xsl"))
      {
         File file = new File(rulesDir,href);
         if(file.exists())
            return new StreamSource(file);
      }
      return null;
   }
}

StylingMover

当然,我已经把 StylingMover 改写成新类。它现在用 ProcessingInstructionHandler 处理程序来解析文档。 它使用处理结果来选择样式表并指定参数,如 清单 5 所示。 特别要注意 try/catch 语句;因为 startElement() 使用特殊异常来停止解析,所以代码必须识别那不是一个错误。


自动生成内容

到目前为止, 有关 XM 的工作已经涉及了基本发布特性。虽然它们很重要,但我相信 XM 的真正价值体现在从一开始我就萦绕在脑际的自动内容生成。 简而言之,这个想法是让 XM 为您生成 XML 文档。

例如,许多网站都包含下载区。如果经常更改文件列表, 则要维护一个带总是最新列表的 XML 文档是困难的。最好使用一种软件来自动生成列表。 该文档可能类似于清单 6。同样可以从 SQL 数据库、邮箱或者甚至远程网站生成文档!


清单 6:由 XM 读取的目录
<?xml version="1.0" encoding="UTF-8"?>
<xm:Directory xmlns:xm="http://www.ananas.org/2001/XM/Walk/Directory">
    <xm:File isDirectory="false" isFile="true" isHidden="false" canRead="true" 
             isMarked="false"  lastModified="2001-07-07T18:21:10" canWrite="true"
             length="749">NotImplementedException.java</xm:File>
    <xm:File isDirectory="false" isFile="true" isHidden="false" canRead="true" 
             isMarked="false" lastModified="2001-07-20T11:49:42" canWrite="true"
             length="6229">ContentHandlerExtractor.java</xm:File>
    <xm:File isDirectory="false" isFile="true" isHidden="false" canRead="true" 
             isMarked="false" lastModified="2001-09-05T07:10:10" canWrite="true"
             length="2351">JAXPHelper.java</xm:File>
</xm:Directory>

上个月的专栏文章中介绍了 Mover ,其用于简化添加自动内容生成的过程。 这个月,我已经在代码中预先包含了目录生成,并打算下个月再讲述它。同时, 如果您对此感兴趣,可回顾一下 DirectoryReaderWalkHandlerWalkMover


轮到您了

目前,我正在用 XM 维护两个网站:ananas.org 和一个内部网。从使用 XM 而得到的实际经验对于确定如何更改软件十分有用。 欢迎您的加入,希望您下载 XM 副本,尝试它,来构建您自己的网站。 请在 ananas-discussion 邮件列表报告您的发现(请参阅 参考资料)。

我已经将 ananas.org 网站的代码(.xml 文档和 .xsl 样式表)添加到 CVS 资源库中, 您可以从那出发来设计您自己的网站。

如果安装了早期版本的 XM,则需要更新软件以利用这个月的改进:将 rules.xsl 文件重命名为 default.xsl ,并将它移到 rules 目录。 这与用于选择样式表的新标准匹配。


参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.

  • 参与本文的 论坛

  • 可以从 ananas.org下载该项目的代码。在那里有到 developerWorks 上的 CVS 资源库以及 ananas-discussion 邮件列表的链接。 我希望您加入列表,并就该项目,提出您的想法。

  • 如果想要 ZIP 文件,也可以获得它。

  • XM 将 XalanXerces-J分别用作 XSLT 处理器和 XML 解析器。最初,IBM(和Lotus)开发了这两个工具,后来将代码赠予 Apache Foundation。

  • XML Extender for DB2与 DirectoryReader 类似,但它用于 DB2 数据库。它允许您将数据库作为 XML 文档访问……可以用 XSLT 来转换 XML 文档。

  • Niklaus Wirth 的 Compiler Construction (ISBN 0-2014-0353-6)是对解析的最好介绍之一。共 180 页,可以很快读完。

  • 在 developerWorks XML 专区中查找更多的 XML 参考资料。

关于作者

Author photo

Benoît Marchal 是住在比利时那慕尔的顾问和作家。 他是 XML by Example Applied XML Solutions XML and the Enterprise 的作者。同时是 Gamelan 的专栏作家。 www.marchal.com 上有其最新项目的详细信息。

关于报告滥用的帮助

报告滥用

谢谢! 此内容已经标识给管理员注意。


关于报告滥用的帮助

报告滥用

报告滥用提交失败。 请稍后重试。


developerWorks:登录


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

 


当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

请选择您的昵称:

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

(长度在 3 至 31 个字符之间)


单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

 


为本文评分

评论

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=21390
ArticleTitle=使用 XML: 处理指令和参数
publish-date=09012001
author1-email=bmarchal@pineapplesoft.com
author1-email-cc=

标签

Help
使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

使用 滑动条 调节标签的数量。

热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。