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 的幫助下生成的。