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,())