跳转到内容

XQuery/转换习语

来自 Wikibooks,开放书籍,开放世界

使用基本 typeswitch 语句的文档转换将相同的转换应用于元素,而不管它在文档中的位置如何。转换还保留文档顺序,因为它按文档顺序处理元素。与 XSLT 相比,XQuery 缺少一些机制,如模式、优先级和编号。本文探讨了这些限制中的一些。

该示例使用自定义 XML 架构来标记书籍“搜索:图形网络指南”的内容,作者 Ken Coupland,网站汇编。此文档使用特定于站点的架构进行格式化。该文档包含标记有类别的站点元素,以及提供类别注释的类别元素。为了比较,此数据集用于一个学生案例研究,该案例研究使用 XSLT 进行转换。

身份转换

[编辑 | 编辑源代码]
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()) 
     }
};

定制身份转换

[编辑 | 编辑源代码]

模块代码只是一个基本骨架,我们会对其进行编辑以定制转换。在这个示例中,我们将文档转换为 HTML。这将需要编辑许多元素转换器。


默认操作

[编辑 | 编辑源代码]

更改 convert-default 函数以提供不同的默认操作。例如

  declare function coupland:convert-default ($node)  {
   if ($node instance of element())
   then coupland:convert($node/node())
   else $node
};

将包含节点的内容,但删除标签及其属性。

更改元素名称

[编辑 | 编辑源代码]

站点描述将被呈现为 divs

  declare function coupland:description($node as element(description)) as item()* {
  element div{
     $node/@*,
     coupland:convert($node/node()) 
     }
};

忽略元素

[编辑 | 编辑源代码]

'class' 元素不需要

declare function coupland:class($node as element(class)) as item()* {
  ()
};

定义转换

[编辑 | 编辑源代码]

图像元素应使用 uri 作为源转换为 html img 元素

declare function coupland:image($node as element(image)) as item()* {
  element div {
    element img {
     attribute src { $node}
    }
  }
};

转换取决于上下文

[编辑 | 编辑源代码]

默认情况下,文档中任何位置具有相同名称的所有元素都以相同的方式转换。这通常不是必需的

declare function coupland:name($node as element(name)) as item()* {
  if ($node/parent::node() instance of element(site))
  then 
    element h3{
     $node/@*,
     coupland:convert($node/node()) 
     }
  else 
    element h1{
     $node/@*,
     coupland:convert($node/node()) 
     }
};

重新排序元素

[编辑 | 编辑源代码]

每个站点都将按名称、uri 然后其余子元素的顺序呈现

declare function coupland:site($node as element(site)) as item()* {
  element div{
     element div { 
        coupland:convert($node/name),
        coupland:convert($node/uri)
       } ,
     coupland:convert($node/(node() except (uri,name)))
     }
};

编号类别

[编辑 | 编辑源代码]

xsl:number 指令提供了一种生成分层节号的机制。此指令非常强大。在特定情况下,我们可以使用函数生成数字。

例如,要对类别进行编号,我们可以使用此函数为一系列兄弟节点中的节点创建编号。请注意,该编号基于原始文档中的节点顺序,而不是转换后的文档(与 xsl:number 相同)。

declare function coupland:number($node) as xs:string {
     concat(count($node/preceding-sibling::node()[name(.) = name($node)]) + 1,". ")
};

并在转换类别名称时调用此函数

 element h2{
     $node/@*,
     coupland:number($node/parent::node()), 
     coupland:convert($node/node())

参数化

[编辑 | 编辑源代码]

转换可以清楚地应用于不同的文档,但通常相同的转换要在不同的上下文中使用。XSLT 提供了对所有模板全局的参数和变量。

在 XQuery 中,我们可以在模块中声明全局变量,也可以在函数周围传递一个或多个参数(模块生成 在这里很有用)。

....

生成索引

[编辑 | 编辑源代码]

XSLT 使用模式机制来允许以多种方式处理相同的模板。一个常见的用例是,相同的转换必须生成索引和内容。

有几种方法可以实现。我们可以通过在调用中传递额外的模式参数来模仿 XSLT 方法,并在每个函数中选择要应用的转换。或者,我们将模式追加到函数名称。使用上下文(全局或传递)更难,因为需要更新模式。

最简单的方法是使用两个 typeswitch 转换,并在更高层级组合结果。这清楚地分离了两种转换模式。模块生成 技术在这里很有用。

复杂转换

[编辑 | 编辑源代码]

整个 HTML 文档可以在根元素的转换器中构建。该页面使用蓝图样式表。每个站点的类别都会被渲染,包括分类到该类别的站点。

declare function coupland:websites($node as element(websites)) as item()* {
(: the root element so convert to html :)
  <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">
       {
        for $category in $node/category
        order by $category/class
        return
          <div>
            <div class="span-10">
              {coupland:convert($category)}
            </div>
            <div class="span-14 last">
              {for $site in $node/sites/site[category=$category/class]
               order by ($site/sortkey,$site/name)[1]
               return
                 coupland:convert($site)
              }
            </div>
            <hr />
          </div>
        }
        </div>  
      </body>
   </html>
};

完成的转换

[编辑 | 编辑源代码]

完整的 XQuery 模块现在看起来像这样

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 node()?) as item()* {
  for $node in $nodes
  return 
     typeswitch ($node)
       case element(category) return coupland:category($node)
           case element(class) return coupland:class($node)
           case element(description) return coupland:description($node)
           case element(em) return coupland:em($node)
           case element(hub) return coupland:hub($node)
           case element(image) return coupland:image($node)
           case element(name) return coupland:name($node)
           case element(p) return coupland:p($node)
           case element(q) return coupland:q($node)
           case element(site) return coupland:site($node)
           case element(sites) return coupland:sites($node)
           case element(sortkey) return coupland:sortkey($node)
           case element(subtitle) return coupland:subtitle($node)
           case element(uri) return coupland:uri($node)
           case element(websites) return coupland:websites($node)
           
       default return 
         coupland:convert-default($node)
  };

declare function coupland:convert-default($node as node() as node()?) as item()* {
  $node
  };

declare function coupland:category($node as element(category) as node()?) as item()* {
  if ($node/parent::node() instance of element(site))
  then ()
  else 
    element div{
     $node/@*,
     coupland:convert($node/node()) 
    }
};
   
declare function coupland:class($node as element(class) as node()?) as item()* {
  ()
};
   
declare function coupland:description($node as element(description) as node()?) as item()* {
  element div{
     $node/@*,
     coupland:convert($node/node()) 
     }
};
   
declare function coupland:em($node as element(em) as node()?) as item()* {
  element em{
     $node/@*,
     coupland:convert($node/node()) 
     }
};
   
declare function coupland:hub($node as element(hub) as node()?) as item()* {
  element hub{
     $node/@*,
     coupland:convert($node/node()) 
     }
};
   
declare function coupland:image($node as element(image) as node()?) as item()* {
  element div {
    element img {
     attribute src { $node}
    }
  }
};
   
declare function coupland:name($node as element(name) as node()?) as item()* {
  if ($node/parent::node() instance of element(site))
  then 
    element span {
     attribute style {"font-size: 16pt"},
     $node/@*,
     coupland:convert($node/node())
     }
  else 
    element h1{
     $node/@*,
     coupland:number($node/parent::node()), 
     coupland:convert($node/node()) 
     }
};
   
declare function coupland:p($node as element(p) as node()?) as item()* {
  element p{
     $node/@*,
     coupland:convert($node/node()) 
     }
};
   
declare function coupland:q($node as element(q) as node()?) as item()* {
  element q{
     $node/@*,
     coupland:convert($node/node()) 
     }
};
   
declare function coupland:site($node as element(site) as node()?) as item()* {
  element div{
     element div { 
        coupland:convert($node/name),
        coupland:convert($node/uri)
       } ,
     coupland:convert($node/(node() except (uri,name)))
     }
};
   
declare function coupland:sites($node as element(sites) as node()?) as item()* {
    for $site in $node/site
    order by $node/sortkey
    return 
       coupland:convert($node/site) 
};
   
declare function coupland:sortkey($node as element(sortkey) as node()?) as item()* {
  ()
};
   
declare function coupland:subtitle($node as element(subtitle) as node()?) as item()* {
  element div{
     $node/@*,
     coupland:convert($node/node()) 
     }
};
   
declare function coupland:uri($node as element(uri) as node()?) as item()* {
  <span>
    {element a{
     attribute href {$node },
     "Link"
     }
    }
  </span>
};
   
declare function coupland:websites($node as element(websites) as node()?) as item()* {
(: the rot element so convert to html :)
  <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">
       {
        for $category in $node/category
        order by $category/class
        return
          <div>
            <div class="span-10">
              {coupland:convert($category)}
            </div>
            <div class="span-14 last">
              {for $site in $node/sites/site[category=$category/class]
               order by ($site/sortkey,$site/name)[1]
               return
                 coupland:convert($site)
              }
            </div>
            <hr />
          </div>
        }
        </div>  
      </body>
   </html>
};
华夏公益教科书