跳轉到內容

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 框架輕鬆完成。

華夏公益教科書