跳转到内容

XQuery/表格上的查询

来自维基教科书,开放的书籍,开放的世界

我们经常以表格结构提供数据,并且需要提取相对于表格内位置的数据。例如,我们可能需要填充表格中某个单元格的数据,该单元格包含该列中其他值的总和。

我们将构建一个 XQuery 函数库,该库使用 XPath 表达式从表格中获取值,假设您位于表格中的某个位置。

示例输入

[编辑 | 编辑源代码]

以下是一个示例表格,其中每个单元格都包含表格数据元素中的行号和列号。

let $table :=
<table>
    <tr>
        <td>r1.c1</td><td>r1.c2</td><td>r1.c3</td><td>r1.c4</td>
    </tr>
    <tr>
        <td>r2.c1</td><td>r2.c2</td><td>r2.c3</td><td>r2.c4</td>
    </tr>
    <tr>
        <td>r3.c1</td><td>r3.c2</td><td>r3.c3</td><td>r3.c4</td>
    </tr>
    <tr>
        <td>r4.c1</td><td>r4.c2</td><td>r4.c3</td><td>r4.c4</td>
    </tr>
</table>

示例函数

[编辑 | 编辑源代码]

以下列出了三个提取单元格、行和列的函数

declare function local:cell($table as node(), $row-num as xs:integer, $col-num as xs:integer) {
$table/tr[$row-num]/td[$col-num]
};

此函数接受输入表格,并使用 XPath 表达式中的谓词删除除单个行和单个列之外的所有内容。因此,要获取第二行和第三列,执行的表达式为:$table/tr[2]/td[3]

(: returns all the cells of the current row :)
declare function local:current-row($current-td as node()) {
  $current-td/..
};

此函数仅获取包含当前单元格的 <tr> 元素。因此,如果我们运行

  $row := local:current-row( local:cell($table, 2, 3) )

我们将得到

<tr>
   <td>r2.c1</td>
   <td>r2.c2</td>
   <td>r2.c3</td>
   <td>r2.c4</td>
</tr>

我们的最后一个实用函数将查找表格中某列的所有单元格。以下是如何执行此操作的代码

(: returns all the cells of the current column :)
declare function local:current-col($current-td as node()) as node()* {
  (: figure out what column we are on by counting prior cells :)
  let $col-num := count($current-td/preceding-sibling::td) + 1
  return
  <col-cells>
    {$current-td/../../tr/td[$col-num]}
  </col-cells>
};

此函数稍微复杂一些。我们需要首先确定我们在表格中的哪一列。为此,我们将使用 preceding-sibling XPath 轴表达式来计算表格中先前单元格的数量。然后,我们将加 1,以便如果之前没有列,我们将位于第一列。如果我们不确定某些单元格是否使用其他元素名称(例如 <th> 用于表格标题),我们也可以使用 preceding-sibling::*。确定了所在列后,我们只需返回表格(通过添加 ../.. 获取),然后获取所有行,仅获取当前列 /tr/td[$col-num]

示例驱动程序查询

[编辑 | 编辑源代码]
(: put table here :)
let $cell-r2-c3 := local:cell($table, 2, 3)

return
<results>
   <current-cell>{$cell-r2-c3}</current-cell>
   <current-row>{local:current-row($cell-r2-c3)}</current-row>
   <current-column>{local:current-col($cell-r2-c3)}</current-column>
</results>

返回

<results>
   <current-cell>
      <td>r2.c3</td>
   </current-cell>
   <current-row>
      <tr>
         <td>r2.c1</td>
         <td>r2.c2</td>
         <td>r2.c3</td>
         <td>r2.c4</td>
      </tr>
   </current-row>
   <current-column>
      <col-cells>
         <td>r1.c3</td>
         <td>r2.c3</td>
         <td>r3.c3</td>
         <td>r4.c3</td>
      </col-cells>
   </current-column>
</results>

向表格添加计算

[编辑 | 编辑源代码]

现在我们有了获取单元格的行列的策略,让我们向表格中添加两种计算类型。我们将修改表格,使其仅包含数字或表达式 {rowsum} 或 {colsum}。表格将如下所示

带有数据和操作的表格

[编辑 | 编辑源代码]
<table>
    <tr>
        <td>1.1</td><td>1.2</td><td>1.3</td><td>1.4</td><td>rowsum</td>
    </tr>
    <tr>
        <td>2.1</td><td>2.2</td><td>2.3</td><td>2.4</td><td>rowsum</td>
    </tr>
    <tr>
        <td>3.1</td><td>3.2</td><td>3.3</td><td>3.4</td><td>rowsum</td>
    </tr>
    <tr>
        <td>4.1</td><td>4.2</td><td>4.3</td><td>4.4</td><td>rowsum</td>
    </tr>
    <tr>
        <td>colsum</td><td>colsum</td><td>colsum</td><td>colsum</td><td>colsum</td>
    </tr>
</table>

现在,我们需要一个函数来将每个计算替换为值。我们还将用对可转换为数字的值进行求和的函数替换每个行和列的函数。

完整源代码

[编辑 | 编辑源代码]
xquery version "1.0";

(: returns the cell of a table at the specified row and column number :)
declare function local:cell($table as node(), $row-num as xs:integer, $col-num as xs:integer) {
$table/tr[$row-num]/td[$col-num]
};


(: returns the sum of all items in the current row that are castable to a decimal:)
declare function local:sum-current-row($current-td as node()) as xs:decimal {
  sum(
    for $td in $current-td/..//td
    return
      if ($td castable as xs:double)
        then xs:double($td/text())
        else ()
    )
};

(: returns the sum of all items in the current column that are castable to a decimal :)
declare function local:sum-current-col($current-td as node()) as xs:decimal {
  (: figure out what column we are on by counting prior cells :)
  let $col-num := count($current-td/preceding-sibling::td) + 1
  return
  sum(
    for $td in $current-td/../../tr/td[$col-num]
        return
            if ($td castable as xs:double)
              then xs:decimal($td)
              else ()
   )
};

declare function local:transform-table($table as node()) as node() {
<table>
   {for $row in $table/tr
    return
       <tr>
          {for $td in $row/td
          return
             if ($td castable as xs:decimal)
                then $td
                else
                   <td>
                     {if ($td = 'rowsum')
                     then local:sum-current-row($td)
                     else
                        if ($td = 'colsum')
                            then local:sum-current-col($td)
                            else 'unknown-function'
                            }
                   </td>
          }
       </tr>
     }
</table>
};

let $title := 'table queries'

let $table :=

<table>
    <tr>
        <td>1.1</td><td>1.2</td><td>1.3</td><td>1.4</td><td>rowsum</td>
    </tr>
    <tr>
        <td>2.1</td><td>2.2</td><td>2.3</td><td>2.4</td><td>rowsum</td>
    </tr>
    <tr>
        <td>3.1</td><td>3.2</td><td>3.3</td><td>3.4</td><td>rowsum</td>
    </tr>
    <tr>
        <td>4.1</td><td>4.2</td><td>4.3</td><td>4.4</td><td>rowsum</td>
    </tr>
    <tr>
        <td>colsum</td><td>colsum</td><td>colsum</td><td>colsum</td><td>colsum</td>
    </tr>
</table>



(: get the nth row and nth column :)
let $cell-r2-c3 := local:cell($table, 2, 3)

return
<html>
  <body>
    {local:transform-table($table)}
  </body>
</html>

带有行和列总计的表格

[编辑 | 编辑源代码]

返回以下结果(需要屏幕截图)

1.1 1.2 1.3 1.4 5
2.1 2.2 2.3 2.4 9
3.1 3.2 3.3 3.4 13
4.1 4.2 4.3 4.4 17
10.4 10.8 11.2 11.6 0

请注意,rowsum 的最终 colsum,计算结果为零。这是因为在执行 colsum 之前,尚未计算行特定子总计的总计,也尚未将它们放置在原始表格中。为了解决这个问题,我们可以执行以下一项或多项操作

  • 使用更新(XQuery 更新)在每次操作后更新表格
  • 一个新的 tablesum 函数
  • 将函数泛化为更像电子表格。

为此,需要在计算总计时使用总计更新表格。但是,除非我们使用依赖图来了解计算应发生的顺序,否则总计的顺序可能不正确。这可以通过使用 XForms 框架轻松完成。

华夏公益教科书