跳转到内容

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 := "&#13;";

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 参数。这提供了一种机制,可以将配置参数传递到函数周围以控制转换。

使用 XSLT 进行转换

[编辑 | 编辑源代码]

当转换很复杂时,需要重构、上下文相关的转换和重新排序,不清楚 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,())

通过 XSLT 转换为 HTML

华夏公益教科书