跳轉到內容

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

華夏公益教科書