XQuery/生成骨架型別轉換模組
對於包含多個標籤的文件型別,例如 TEI 或 DocBook,手動編寫此轉換程式碼既繁瑣又容易出錯。我們可以使用 XQuery 為 XQuery 模組生成基本文字。
本文使用與 轉換習語 中相同的示例。
從包含此文件中標籤的簡單列表開始,我們可以生成一個模組,該模組對包含這些標籤的文件執行身份轉換。
import module namespace gen = "http://www.cems.uwe.ac.uk/xmlwiki/gen" at "gen.xqm";
let $tags := ("websites","sites","site","uri","name","description")
let $config :=
<config>
<modulename>coupland</modulename>
<namespace>http://www.cems.uwe.ac.uk/xmlwiki/coupland</namespace>
</config>
return
gen:create-module($tags, $config)
以下是透過新增行建立的 XML 輸出 和文字 XQuery 檔案
declare option exist:serialize "method=text media-type=text/text";
到指令碼中。
如果我們將此指令碼另存為 coupid.xqm,我們可以使用它來生成轉換後的文件
import module namespace coupland = "http://www.cems.uwe.ac.uk/xmlwiki/coupland" at "coupid.xqm";
let $doc := doc("/db/Wiki/eXist/transformation/Coupland1.xml")/*
return
coupland:convert($doc)
我們還可以檢查身份轉換是否保留了文件的完整結構
import module namespace coupland = "http://www.cems.uwe.ac.uk/xmlwiki/coupland" at "coupid.xqm";
let $doc := doc("/db/Wiki/eXist/transformation/Coupland1.xml")/*
return
<compare>{deep-equal($doc,coupland:convert($doc))}</compare>
生成的模組如下所示
module namespace coupland = "http://www.cems.uwe.ac.uk/xmlwiki/coupland";
(: conversion module generated from a set of tags
:)
declare function coupland:convert($nodes as node()*) as item()* {
for $node in $nodes
return
typeswitch ($node)
case element(websites) return coupland:websites($node)
case element(sites) return coupland:sites($node)
case element(site) return coupland:site($node)
case element(uri) return coupland:uri($node)
case element(name) return coupland:name($node)
case element(description) return coupland:description($node)
default return
coupland:convert-default($node)
};
declare function coupland:convert-default($node as node()) as item()* {
$node
};
declare function coupland:websites($node as element(websites)) as item()* {
element websites{
$node/@*,
coupland:convert($node/node())
}
};
declare function coupland:sites($node as element(sites)) as item()* {
element sites{
$node/@*,
coupland:convert($node/node())
}
};
declare function coupland:site($node as element(site)) as item()* {
element site{
$node/@*,
coupland:convert($node/node())
}
};
declare function coupland:uri($node as element(uri)) as item()* {
element uri{
$node/@*,
coupland:convert($node/node())
}
};
declare function coupland:name($node as element(name)) as item()* {
element name{
$node/@*,
coupland:convert($node/node())
}
};
declare function coupland:description($node as element(description)) as item()* {
element description{
$node/@*,
coupland:convert($node/node())
}
};
函式 convert($nodes) 包含型別轉換語句,用於將節點分派到某個標籤函式。每個標籤函式建立一個具有該名稱的元素,複製屬性,然後遞迴呼叫 convert 函式並傳遞子節點。函式 convert-default 中定義的預設操作僅複製節點。
此函式生成執行身份轉換的 XQuery 模組的程式碼。
有兩個引數
- tags - 標籤序列
- config - 包含模組名稱、模組字首和模組名稱空間定義的 XML 節點。
declare variable $gen:cr := " ";
declare function gen:create-module($tags as xs:string*, $config as element(config) ) as element(module) {
let $modulename := $config/modulename/text()
let $prefix := $config/prefix/text()
let $pre:= concat($modulename,":",$prefix)
let $namespace := ($config/namespace,"http://mysite/module")[1]/text()
return
<module>
module namespace {$modulename} = "{$namespace}";
(: conversion module generated from a set of tags
:)
<function>
declare function {$pre}convert($nodes as node()*) as item()* {{ {$gen:cr}
for $node in $nodes
return
typeswitch ($node)
{for $tag in $tags
return
<s>case element({$tag}) return {$pre}{replace($tag,":","-")}($node)
</s>
}
default return
{$pre}convert-default($node)
}};
</function>
<function>
declare function {$pre}convert-default($node as node()) as item()* {{ {$gen:cr}
$node
}};
</function>
{for $tag in $tags
return
<function>
declare function {$pre}{replace($tag,":","-")}($node as element({$tag})) as item()* {{ {$gen:cr}
element {$tag} {{
$node/@*,
{$pre}convert($node/node())
}}{$gen:cr}
}};
</function>
}
</module>
};
文件或語料庫中的所有標籤都需要由身份轉換處理,因此最好從文件或語料庫本身生成標籤列表。以下函式按字母順序返回標籤序列。
declare function gen:tags($docs as node()*) as xs:string * {
for $tag in distinct-values ($docs//*/name(.))
order by $tag
return $tag
};
我們可以修改呼叫指令碼
let $doc := doc("/db/Wiki/eXist/transformation/Coupland1.xml")
let $tags := gen:tags($doc)
let $config :=
<config>
<modulename>coupland</modulename>
<namespace>http://www.cems.uwe.ac.uk/xmlwiki/coupland</namespace>
</config>
return
gen:create-module($tags, $config)
模組生成函式為每個標籤生成一個固定的程式碼模式。我們可以允許使用者使用回撥函式來生成程式碼模式,作為修改生成器程式碼本身的替代方法,從而自定義此模式。
修改後的函式程式碼具有以下修改
函式簽名;
declare function gen:create-module($tags as xs:string*, $callback as function, $config as element(config) ) as element(module) {
生成每個標籤函式
<function>
declare function {$pre}{replace($tag,":","-")}($node as element({$tag})) as item()* {{ {$gen:cr}
{util:call($callback,$tag,$pre)}{$gen:cr}
}};
</function>
要生成一個基本的轉換為 HTML 的轉換,其中 HTML 元素被複制,而非 HTML 元素被轉換為具有附加類屬性的 div 元素,我們定義函式來建立程式碼體、建立函式引用並呼叫 convert 函式
import module namespace gen = "http://www.cems.uwe.ac.uk/xmlwiki/gen" at "gen.xqm";
declare namespace fx = "http://www.cems.uwe.ac.uk/xmlwiki/fx";
declare variable $fx:html-tags :=
("p","a","em","q");
declare function fx:tag-code ($tag as xs:string, $pre as xs:string) {
if ($tag = $x:html-tags)
then
<code>
element {$tag} {{
$node/@*,
{$pre}convert($node/node())
}}
</code>
else
<code>
element div {{
attribute class {{"{$tag}" }},
$node/(@* except class),
{$pre}convert($node/node())
}}
</code>
};
declare option exist:serialize "method=text media-type=text/text";
let $doc := doc("/db/Wiki/eXist/transformation/Coupland1.xml")
let $tags := gen:tags($doc)
let $callback := util:function(QName("http://www.cems.uwe.ac.uk/xmlwiki/x","fx:tag-code"),2)
let $config :=
<config>
<modulename>coupland</modulename>
<namespace>http://www.cems.uwe.ac.uk/xmlwiki/coupland</namespace>
</config>
return
gen:create-module($tags, $callback, $config)
可能需要的生成器的另一個自定義是向所有簽名和呼叫新增額外的 $options 引數。這提供了一種機制,可以將配置引數傳遞到函數週圍以控制轉換。
當轉換很複雜時,需要重構、上下文相關的轉換和重新排序,不清楚 XQuery 型別轉換方法是否優於 XSLT 等效方法。為了比較,以下是等效的 XSLT。
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/websites">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Web Sites by Coupland</title>
<link rel="stylesheet" href="../../css/blueprint/screen.css" type="text/css" media="screen, projection"/>
<link rel="stylesheet" href="../../css/blueprint/print.css" type="text/css" media="print"/>
<!--[if IE ]><link rel="stylesheet" href="../../css/blueprint/ie.css" type="text/css" media="screen, projection" /><![endif]-->
<link rel="stylesheet" href="screen.css" type="text/css" media="screen"/>
</head>
<body>
<div class="container">
<h1>Design web sites by Ken Coupland</h1>
<xsl:apply-templates select="category">
<xsl:sort select="name"/>
</xsl:apply-templates>
</div>
</body>
</html>
</xsl:template>
<xsl:template match="websites/category">
<div>
<div class="span-10">
<h3>
<xsl:value-of select="name"/>
</h3>
<h4>
<xsl:value-of select="subtitle"/>
</h4>
<xsl:copy-of select="description/node()"/>
</div>
<div class="span-14 last">
<xsl:apply-templates select="../sites/site">
<xsl:sort select="(sortkey,name)[1]" order="ascending"/>
</xsl:apply-templates>
</div>
<hr />
</div>
</xsl:template>
<xsl:template match="site/category">
</xsl:template>
<xsl:template match="site">
<h3>
<xsl:value-of select="name"/>
</h3>
<span><a href="{uri}">Link</a></span>
<div class="site">
<xsl:apply-templates select="* except (uri,name,sortkey)"/>
</div>
</xsl:template>
<xsl:template match="description">
<p>
<xsl:copy-of select="node()"/>
</p>
</xsl:template>
<xsl:template match="image">
<img src="{uri}"/>
</xsl:template>
</xsl:stylesheet>
和 XQuery 用於在伺服器端應用此操作
declare option exist:serialize "method=xhtml media-type=text/html";
let $doc := doc("/db/Wiki/eXist/transformation/Coupland1.xml")
let $ss := doc("/db/Wiki/eXist/transformation/tohtml.xsl")
return
transform:transform($doc, $ss,())