XRX/XSLTForms 和 eXist
您希望将标准的 XForms 应用程序移植到 XSLTForms,并能够从 eXist 的 XQueries 动态生成 XForms。
XSLTForms 是一款非常轻量级的 XForms 处理器,可以配置为在 Web 服务器或 Web 客户端上运行。XSLTForms 将 XForms 应用程序中的每个 XForms 元素转换为包含一些 JavaScript 的 HTML。如果您使用客户端进行转换,则必须注意客户端浏览器中的一些限制。
在客户端或服务器端执行转换的设计决策可能因情况而异。在服务器端转换表单有时可以避免将完整的 XSLT 转换传输到客户端,并且它也避免了浏览器依赖的差异。此过程也可能更自然地适用于使用 XProc 等工具在服务器端进行管道操作。
另一方面,一旦第一个表单加载完毕,XSLT 转换就可以从本地浏览器缓存中读取。这使您只需传输实际的 XForms 应用程序规范,而无需传输更大的 JavaScript 和 HTML 代码。因此,此选项可以最大程度地减少网络流量,并最大程度地减少显示更改的延迟。
XSLTForms 应用程序是一个基于 XForms 输入文件进行 XSL 转换的库。输出是将在许多浏览器(Firefox、Internet Explorer 6、7 和 8、Apple Safari、Google Chrome 和 Opera)上运行的 HTML 和 JavaScript 的组合。
请注意,对于内联网上的许多内部表单,可以要求所有用户使用支持客户端插件的 Web 浏览器。这意味着您的 XForms 加载时间非常快,并且您的测试过程很简单。对于公共表单,您通常无法指定用户必须使用什么浏览器,因此表单开发人员需要在许多不同版本的许多不同浏览器上进行测试。这会极大地减慢表单开发过程,并可能导致测试和开发成本增加。
eXist 2.0 和 2.1 默认安装了服务器端 XForms 处理器 betterFORM。为了允许 XSLTForms 处理 XForms,您应该执行以下操作之一
要在特定上下文中使用 XLSTForms,但在其他上下文中允许使用 betterFORM,请在您的 XQuery 中设置以下虚拟属性
let $attribute := request:set-attribute('betterform.filter.ignoreResponseBody', 'true')
在生成表单的 XQuery 中。
为了完全禁用 betterFORM,请更改
<property name="filter.ignoreResponseBody" value="false"
为 "true" 在 $EXIST_HOME/webapp/WEB-INF/betterform-config.xml 中
或者在 $EXIST_HOME/webapp/WEB-INF/betterform-config.xml 和/或 $EXIST_HOME/webapp/WEB-INF/web.xml 中注释掉 XFormsFilter 的条目并重新启动。
支持来自许多供应商的多个版本的浏览器的挑战之一是,它们渲染 CSS 的方式可能大不相同。由于 XForms 经常利用高级 CSS 结构来进行表单布局,因此这对 XForms 应用程序开发人员来说可能是一个很大的问题。IE 6 尤其令人头疼,因为它不支持完整的 CSS 盒模型。对于不使用 HTML 表格布局来渲染表单的表单来说尤其如此。对于单个浏览器的简单样式表,通常会变得非常复杂,需要很多天的测试。例如,inline-block 的使用在许多不同的浏览器上都有很多变化。
针对浏览器不兼容性的最佳防御措施是使您的表单尽可能小巧简单。使用许多不同的类属性,并且除非绝对必要,否则不要使用重复的结构。
Mozilla Firefox 浏览器中目前存在一个 命名空间错误,它阻止使用非空命名空间的任何实例加载。例如,以下 XML 文件无法使用 src 属性加载到实例中。
<ex:root xmlns:ex="http://www.example.com">
<ex:string>This is a test string.</ex:string>
</ex:root>
解决方法是在 HTML 根元素中添加一个虚拟命名空间前缀引用,例如
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:ex="http://www.example.com"
ex:dummy="dummy" >
<head>
ex:dummy="dummy" 将解决问题,并且实例将正确加载到表单中。此修补程序可能不适用于其他版本的 Firefox。请注意,实例可以内联加载以绕过此错误。另外请注意,在实例中使用默认命名空间也不起作用。
这被称为 Firefox 命名空间轴问题。在 Internet Explorer、Safari、Opera 或 Chrome 中,这不是问题。Firefox 是唯一不支持命名空间轴函数的浏览器。XML 支持似乎在 Mozilla 中的优先级很低。我们鼓励任何认为 Firefox 应该支持 XML 命名空间的人在 Mozilla 网站上投票支持此错误修复。
查看 Mozilla 错误 ID 94270。C. M. Sperberg-McQueen 的评论 非常有见地。
Uche Ogbuji 的文章讨论了 Firefox 3.0 为 XML 处理提供的最新功能。
要将您的标准 XForms 转换为与 XSLTForms 一起使用,您只需要在 XForms 文件的第一行添加以下语句。
<?xml-stylesheet type="text/xsl" href="/exist/rest/db/xforms/xsltforms/xsltforms.xsl"?>
您在 eXist 安装中使用的实际 URL 可能因版本而异。对于 XSLT 样式表来说,本文档篇幅较长(目前约 114KB,4500 行),但比许多运行超过 50000 行代码的 JavaScript 库要小得多。XSLT 进程还会加载一个 363KB 的 JavaScript 文件。关键在于,一旦它为第一个表单加载完毕,就可以存储在 Web 缓存中。将来,此文件的压缩版本可能会提供更快的加载时间。此过程始终比本机 XForms 处理器(如 Firefox XForms 扩展)慢,但好处是,一旦 XForms 样式表在一个浏览器上测试完毕,它就可以在多个浏览器上运行。
默认情况下,eXist 将始终尝试在遇到指示其应执行转换的 XML 处理指令时执行服务器端 XSLT 处理。
如果您的 XForms 是静态的,这意味着它们不会根据情况上下文发生任何变化,您可以将表单存储在 Web 文件系统上的任何位置。它们不需要存储在 eXist 中。
如果将 XForms 存储在 eXist 中,则必须注意两点。
默认情况下,eXist 会处理所有 XML XSL 处理指令,并在它们到达浏览器之前在服务器上运行它们。这不是 XSLTForms 最初设计的工作方式。它还需要客户端和服务器之间更多的带宽。
要禁用服务器端 XSLT 处理,您需要将以下指令添加到生成 XForms 的 XQuery 中
declare option exist:serialize "method=xhtml media-type=text/xml"; declare option exist:serialize "indent=no"; declare option exist:serialize "process-xsl-pi=no";
第一行设置 XHTML 序列化类型,并设置标准 XML MIME 类型。
注意:许多浏览器(如 IE)不识别正确的 XForms MIME 类型 application/xhtml+xml。为了解决此问题,您可以使用不正确的 MIME 类型 text/xml。有一些方法可以通过更改 Windows 注册表来修复 IE 以识别正确的 MIME 类型,但这些更改对于普通用户来说很难进行。有关更多详细信息,请参见 这里
第二行是当前版本的 XSLTForms 所必需的。最后一行告诉服务器端不要处理转换。
这三个选项可以合并成一个语句
declare option exist:serialize "method=xhtml media-type=text/xml indent=no process-xsl-pi=no";
请注意,在 eXist 上下文中,process-xsl-pi=no 意味着“不要在发送到浏览器之前在服务器上处理它”。
生成动态表单的一种方法是使用以下样式
let $form :=
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xf="http://www.w3.org/2002/xforms">
<head>
<title>XForms Template</title>
<xf:model>
<xf:instance xmlns="" id="save-data">
<data/>
</xf:instance>
</xf:model>
</head>
<body>
<h1>XForms Template</h1>
<p>This XForms application has been dynamically generated from an XQuery</p>
</body>
</html>
然后,您可以在表单末尾返回以下内容
let $xslt-pi := processing-instruction xml-stylesheet {'type="text/xsl" href="/exist/rest/db/xforms/xsltforms/xsltforms.xsl"'} return ($xslt-pi,$form)
为了进行调试,您还可以使用
let $xslt-pi := processing-instruction xml-stylesheet {'type="text/xsl" href="/exist/rest/db/xforms/xsltforms/xsltforms.xsl"'} let $debug := processing-instruction xsltforms-options {'debug="yes"'} return ($xslt-pi, $debug, $form)
这里,href 指向您已安装 XSLTForms 库的位置。
eXist 提供了一种使用 transform 模块进行服务器端 XSLT 转换的方法。以下是如何使用此 transform 函数来执行表单的服务器端转换的示例。
xquery version "1.0";
declare option exist:serialize "method=xhtml media-type=text/html indent=no";
let $form :=
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xf="http://www.w3.org/2002/xforms">
<head>
<title>XForms Template</title>
<xf:model>
<xf:instance xmlns="" id="save-data">
<data>
<name>John Smith</name>
</data>
</xf:instance>
</xf:model>
</head>
<body>
<h1>XForms Test Program</h1>
<xf:input ref="name">
<xf:label>Name: </xf:label>
</xf:input>
</body>
</html>
let $transform := '/exist/rest/db/xforms/xsltforms/xsltforms.xsl'
let $params :=
<parameters>
<param name="omit-xml-declaration" value="yes"/>
<param name="indent" value="no"/>
<param name="media-type" value="text/html"/>
<param name="method" value="xhtml"/>
<param name="baseuri" value="/exist/rest/db/xforms/xsltforms/"/>
</parameters>
let $serialization-options := 'method=xml media-type=text/html omit-xml-declaration=yes indent=no'
return
transform:transform($form, $transform, $params, $serialization-options)
也可以使用 eXist 的 URL 重写功能执行服务器端转换。
来自 /eXist/webapp/xforms/controller.xql 的示例
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<view>
<forward servlet="XSLTServlet">
(: Apply xsltforms.xsl stylesheet :)
<set-attribute name="xslt.stylesheet" value="xsltforms/xsltforms.xsl"/>
<set-attribute name="xslt.output.omit-xml-declaration" value="yes"/>
<set-attribute name="xslt.output.indent" value="no"/>
<set-attribute name="xslt.output.media-type" value="text/html"/>
<set-attribute name="xslt.output.method" value="xhtml"/>
<set-attribute name="xslt.baseuri" value="xsltforms/"/>
</forward>
</view>
<cache-control cache="yes"/>
</dispatch>
请参见 eXist 版本控制库中的示例
由于 XSLTForms 在 Web 客户端上使用 XSLT,因此它只知道如何解析 XML 文件。要使 CSS 文件正常工作,您必须使每个 CSS 文件都成为格式良好的 XML 文档。这可以通过简单地将 CSS 文件用根数据元素(例如)包装起来来轻松完成
<css>
/* empty rule to recover from parse errors */
empty {}
body {font-family: Arial, Helvetica, sans-serif;}
label {font-weight: bold;}
</css>
如果您希望 CSS 既是有效的 CSS,又是格式良好的 XML,您可以使用特殊的序言 <css><![CDATA[/**/ 和尾注 /*]]>*/</css>,如以下两个示例所示。从 XML 解析器的角度解释
<!--/*--><css><![CDATA[/**/
h1 {
body {font-family: Arial, Helvetica, sans-serif;}
label {font-weight: bold;}
}
/*]]>*/<!--/*--></css><!--*/-->
同一个文件从 CSS 解析器的角度解释
<!--/*--><css><![CDATA[/**/
h1 {
body {font-family: Arial, Helvetica, sans-serif;}
label {font-weight: bold;}
}
/*]]>*/<!--/*--></css><!--*/-->
然后,您只需使用 link 元素将 CSS 文件导入到 XForms 文件中即可
<link type="text/css" rel="stylesheet" href="style-with-root-wrapper.css"/>
XQuery 也可用于从服务器自动生成 CSS 文件。在服务器端,可以包装 CSS 文件,但对于非 XSLTForms 客户端,可以删除根元素。
如果 CSS 中存在不全局应用于所有其他表单的小更改,您可以将它们直接插入表单的 <style> 标记中。请注意,如果您的 XForms 是从 XQuery 动态生成的,则必须将样式用 CDATA 包装器括起来,或者使用双花括号 {{ 和 }} 来转义 XQuery 处理。
<style type="text/css">
<![CDATA[
@namespace xf url("http://www.w3.org/2002/xforms");
.block-form xf|label {
width: 15ex;
}
.PersonGivenName .xforms-value {width:20ex}
.PersonSurName .xforms-value {width:25ex}
]]>
</style>
请注意,在 Firefox 插件中,用于更改值的 CSS 类为 .xf-value。在 XSLTForms 中,该类为 .xforms-value。
XSLTForms 还允许您在 XForms 文件中添加以下 XML 处理指令标志。
<?css-conversion no?> <?xsltforms-options debug="no"?>
如果添加了这些行,则不需要将 CSS 文件转换为 XML 文件。您不能在 CSS 中使用 @namespace xf 功能,并且您可能需要向标记添加 class 属性,但一些用户更喜欢这种方法。
应使用以下语法来设置 XForms 元素的样式
@namespace xf url("http://www.w3.org/2002/xforms"); xf|label {font-weight: bold;}
请注意,此格式已在运行于 Windows、Mac 和 Linux 上的 IE 8、Firefox、Opera 和 Safari 中的 XSLTForms 上进行了测试。
块表单是指对每个控件使用块布局的表单,因此每个控件都显示在不同的行上。块表单具有一致的布局,并为提示文本、必填字段标签和帮助图标留有空间。仅当存在屏幕空间限制时,才应使用具有多行字段的内联表单。
为了正确呈现块表单,建议您将所有块表单包装在具有“block-form”类的标准 div 标记中
<div class="block-form">
...put the controls here...
</div>
这使您可以使用 CSS 文件,其中仅当块显示项目是块表单 div 元素的后代时,才会触发这些项目。
以下是一些块表单的示例 CSS 规则。
/* formatting rule for all XForms controls elements within a block form. Each input control is on a separate line */
.block-form xf|input,
.block-form xf|select,
.block-form xf|select1,
.block-form xf|textarea {
display: block;
margin: 1ex;
}
/* formatting rule for all labels in a block form */
.block-form xf|label {
display: inline-block;
width: 15ex; /* fix the width of all the labels in the block form */
float: left;
text-align: right;
margin-right: 1ex; /* margin to the right of the label */
}
其中最危险的部分是 inline-block 规则。这对于允许您修复标签的宽度是必需的。
如果您希望显示选择列表中的所有项目,则可以添加属性
<xf:select1 appearance="full">
为了将项目值保持在一起,您必须将以下内容添加到 CSS 文件中
xf|select1 xf|item {
margin-left: 21ex;
}
当前版本的 XSLTForms 不支持当使用 replace="all" 属性时使用正确 URL 重写的 xf:submission 元素。此结构在许多搜索表单中使用,当表单用于收集一组参数(例如,用于限制搜索的关键字和日期范围)时。这些搜索参数通常存储在模型中的单个实例中,并用于构造指向 RESTful 搜索服务的 URL。
以下是在模型中的实例中存储搜索参数的示例
<xf:instance xmlns="" id="search-params">
<data>
<url>http://www.example.com/search.xq?q=</url>
<q></q>
</data>
</xf:instance>
但是,XSLTForms 支持 xf:load 函数,当您将 xf:load 与 xf:resource 子元素一起使用时,可以实现相同的结果。
例如,在触发器操作中,您可以使用以下内容
<xf:trigger>
<xf:label>Search</xf:label>
<xf:action ev:event="DOMActivate">
<xf:load show="replace">
<xf:resource value="concat( 'search.xq?q=', instance('search-params')/q )"/>
</xf:load>
</xf:action>
</xf:trigger>
XSLTForms 可以更改实例数据中命名空间前缀的管理方式。如果您加载具有默认命名空间的实例,则所有提交的数据都将包含命名空间前缀。
如果您的输入为
<task xmlns="http://www.example.com/task">
<id/>
<task-name/>
<task-description/>
</task>
则保存的输出将变为
<task:task xmlns="http://www.example.com/task">
<task:id/>
<task:task-name/>
<task:task-description/>
</task:task>
这可以通过 submission 元素的 includenamespaceprefixes 属性进行控制。
使用 XSLTForms,您只能对实例中的每个元素有一个绑定。因此,例如,您不能将数据类型放在单独的绑定中作为必填绑定。它会生成错误,但不会指示哪个元素被绑定了两次。
无法将焦点设置到新插入的内容上。
参见重复测试 1
早于修订版 537(2012 年 4 月)的 XSLTForms 版本不支持 XForms 控件 upload。
对于 537 及更高版本,XForms 的“上传”控件受支持,并且行为基本与 XForms 1.1 规范中描述的一致。
对于早于 537 的版本,存在一个变通方法。
如果提交方法为“xml-urlencoded-post”,XSLTForms 将动态构建一个 ID 为“xsltforms_form”的表单。
如果此表单仍然存在,XSLTForms 将用提交的实例内容的序列化替换表单第一个子节点的值。
示例
模型
<xf:model>
<xf:instance>
<data>
<comment>Upload one or two files</comment>
<data>
</xf:instance>
<xf:submission id="sub" method="xml-urlencoded-post" replace="all" action="load.xql">
<xf:message level="modeless" ev:event="xforms-submit-error"> Submit error. </xf:message>
</xf:submission>
</xf:model>
表单
<form name="upload" id="xsltforms_form" action="load.xql" enctype="multipart/form-data" method="post">
<input type="hidden" name="postdata"/>
<input type="file" name="file1" style="width: 360px" />
<input type="file" name="file2" style="width: 360px" />
</form>
提交
<xf:submit submission="sub">
<xf:label class="label">Send</xf:label>
</xf:submit>
XQuery load.xql
...
let $collection := "/db/my/docs"
let $f1 := request:get-uploaded-file-name("file1")
let $f2 := request:get-uploaded-file-name("file2")
let $data := request:get-parameter("postdata",())
let $o1 := if ($f1 ne "") then xdb:store($collection, xdb:encode-uri($f1), request:get-uploaded-file-data("file1")) else ()
let $o2 := if ($f2 ne "") then xdb:store($collection, xdb:encode-uri($f2), request:get-uploaded-file-data("file2")) else ()
...
这是一种技巧,可以在 XSLTForms 应用程序中上传文件。文件内容不会加载到实例中。
- XSLTForms 主页
- SourceForge 上的 XSLTForms
- XSLTForms 测试结果
- XSLT 转换,转换本身大约有 3000 行
以上大部分材料是在 XSLTForms 作者 Alain Couthures 的帮助下生成的。