第12章 基于表格内嵌排序的XML数据分页浏览

12.1 设计需求

给定如下XML文档:

<?xml version="1.0" encoding="utf-8"?> <table border="1" id="books"> <tr style="background-color:greenyellow"> <th id="ISBN">书号</th> <th id="title">书名</th> <th id="price">价格</th> </tr> <tr> <td>5744885</td> <td>Java programming</td> <td>80</td> </tr> <tr> <td>2323885</td> <td>C# programming</td> <td>87</td> </tr> <tr> <td>8888</td> <td>Python programming</td> <td>68</td> </tr> <tr> <td>8767885</td> <td>NodeJS</td> <td>90</td> </tr> <tr> <td>r64547565647689</td> <td>C++ programming</td> <td>68</td> </tr> <tr> <td>45634748758478</td> <td>MongoDB</td> <td>90</td> </tr> <tr> <td>675665647689</td> <td>operation system</td> <td>68</td> </tr> <tr> <td>2342353758478</td> <td>XML技术</td> <td>90</td> </tr> </table>

现提出如下设计需求:

  1. 设计合适的用户接口,可自定义浏览页面大小(每页可展示图书条目的数量)。
  2. 基于选定的页面大小,用户可选择性地浏览"最前页"、"最后页"、"前一页"、"后一页"。
  3. 基于三层架构读取XML文档。
  4. 单页中可按"价格"进行"降序"或"升序"排序浏览。
  5. 降序"或"升序"交互操作在表格内部进行。

12.2 设计分析

12.2.1 功能分析

测试用户期望功能如下:

  1. 定义页面大小(单页浏览条目数)。
  2. 浏览最前一页。
  3. 浏览最后一页。
  4. 浏览当前页前一页。
  5. 浏览当前页后一页。
  6. 测试用户可在当前页选择降序或升序浏览。

用户测试功能用例建模如图12.1所示。

功能用例建模
图12.1 功能用例建模

12.2.2 核心业务数据流分析

XML文档分页浏览的核心业务描述如下:

  1. 页面加载时,读取XML文档并XML文本形式返回到前端;
  2. XML文本转化为XML文档对象并保存为全局变量。
  3. 用户选择浏览页面大小(单页信息条目数)。
  4. XML文档对象、页面大小、页面总数、当前页面号、数据输出容器div作为参数,经分页处理程序处理后生成可视分页结果。
  5. 用户在当前页面中选择降序或升序,浏览当前页排序数据。

核心业务数据流建模如图12.2所示。

核心业务数据流
图12.2 核心业务数据流

12.2.3 文件包及文件设计分析

基于三层架构视角的文件包及文件设计如表12.1所述。

序号 包设计 文件设计
1 设计表示层包(包名:UI),抽象表示层文件容器。
  1. 设计界面主页文件main.js。
  2. 设计自定义文件XML_page.js(主页引入,用于分页浏览)。
  3. 设计自定义文件XSLT.js(用于绑定XML对象与XSL对象)
  4. 主页引入jquery框架(jquery.js),辅助表示层处理。
  5. 设计D.xslt文件,辅助格式化XML降序。
  6. 设计A.xslt文件,辅助格式化XML升序。
2 设计业务逻辑层包(包名:server),抽象业务层文件容器。
  1. 设计通用业务逻辑服务文件server.js。
  2. 设计当前模块专用业务逻辑服务文件server_12.js.
3 设计数据层包(包名:data),抽象数据层文件容器。
  1. 设计XML数据存储文件books.xml。

文件包设计及文件关系如图12.3所示。

文件包设计及文件关系
图12.3 文件包设计及文件关系

12.3 文件架构

文件架构如图12.4所示。

文件架构
图12.4 文件架构

文件(夹)功能说明见表12.3所述。

序号 文件(夹)名 作用
1 books.xml 存放在后台用于测试的XML基础数据文件
2 main.html 实现人机交互的用户界面主页
3 Server.js NodeJS后台通用逻辑服务程序,动态引入Server_12.js模块。
4 XML_page.js 包含XML文档数据分页函数。
5 jquery.js JQuery函数库,辅助表示层数据可视化。
6 D.xslt、A.xslt 包含"降序"及"升序"逻辑的XSLT文件
7 UI 存放表示层文件的文件夹
8 Server 存放业务逻辑层文件的文件夹
9 Data 存放XML数据文件的文件夹
10 Server_12.js 当前模块后台服务专用逻辑

12.4 代码实现

12.4.1 主页实现代码

对应文件:main.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
    <style>
        .order_span{
            cursor:pointer;
            font-size:8pt;
            font-weight:400;
            padding:3px;
        }
    </style>
</head>
<body>
    <h3>XML数据浏览测试</h3>
    <hr />
    【<span style="background-color:black;color:white">页面大小(每页显示记录</span>
    <select id="p_size" disabled="disabled" onchange='pagesize_change(xmldom, "p_size", "p_count","p_current", "result" )'>
        <option>3</option>
        <option>6</option>
        <option>9</option>
    </select><span style="background-color:black;color:white">条);</span>
    共<input readonly="readonly" id="p_count" type="text" style="width:60px" />页;
    当前是第<input readonly="readonly" id="p_current" type="text" value="1" style="width:60px"/>页】
    <hr />
    <input class="op_but" disabled="disabled" type="button" value="最前页" onclick='first_page(xmldom, "p_size", "p_count","p_current", "result" )' />
    <input class="op_but" disabled="disabled" type="button" value="上一页" onclick='prior_page(xmldom, "p_size", "p_count","p_current", "result" )' />
    <input class="op_but" disabled="disabled" type="button" value="下一页" onclick='next_page(xmldom, "p_size", "p_count","p_current", "result" )' />
    <input class="op_but" disabled="disabled" type="button" value="最后页" onclick='last_page(xmldom, "p_size", "p_count","p_current", "result" )' />
    <hr />
    <div id="result">
    </div>
</body>
</html>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="XML_page.js"></script>
<script type="text/javascript" src="XSLT.js"></script>
<script type="text/javascript" >
//初始化,按默认参数显示图书信息
var xmldom;
$(document).ready(function () {
    var xhr = new XMLHttpRequest();
    xhr.open("post", "http://localhost:1017/viewAllbooks_12", true);
    xhr.onreadystatechange = function () {
        if (xhr.status == 200 && xhr.readyState == 4) {
            xmldom = $.parseXML(xhr.responseText);
            var book_count = xmldom.getElementsByTagName("tr").length - 1;
            var page_size = parseInt($("#p_size option:selected").text());
            var page_count = Math.ceil(book_count / page_size);
            $("#p_count").val(page_count);
            $("#p_size").attr("disabled", false);
            $(".op_but").attr("disabled", false);
            display_cur_page(xmldom, "p_size", "p_count", "p_current", "result");
        }
    }
    xhr.send();
});

//价格降序
function dd_view() {
    $.get("D.xslt", function (xsldom) {
        var cur_XMLtext = $("#result").html().replace("<tbody>", "").replace("</tbody>", "");
        var cur_xmldom = $.parseXML(cur_XMLtext);
        displayXML(cur_xmldom, xsldom, "result");
    }, "xml");
}

//价格降序
function aa_view() {
    $.get("A.xslt", function (xsldom) {
        var cur_XMLtext = $("#result").html().replace("<tbody>", "").replace("</tbody>", "");
        var cur_xmldom = $.parseXML(cur_XMLtext);
        displayXML(cur_xmldom, xsldom, "result");
    }, "xml");
}
</script>

12.4.2 自定义JS文件实现代码

1、自定义分页逻辑

对应文件:XML_page.js

//xmldom:XML文档对象
// page_size_id:调整页面大小的下拉列表
//page_count_id:显示页面总数
//page_current_id:当前正在浏览的页面号
// result_div_id:输出结果的div
//显示当前页数据
function display_cur_page(xmldom, page_size_id, page_count_id, page_current_id, result_div_id) {
    var page_size = parseInt($("#" + page_size_id).val());
    var cur_page = parseInt($("#" + page_current_id).val());
    var begin_pos = (cur_page - 1) * page_size + 1;
    var end_pos = cur_page * page_size;
    var max_page = parseInt($("#" + page_count_id).val());
    var trs = xmldom.getElementsByTagName("tr");
    if (cur_page == max_page) { end_pos = trs.length - 1; }
    var table_str = "<table border='1'>";
    table_str += trs[0].outerHTML;
    for (var k = begin_pos; k <= end_pos; k++) {
        table_str += trs[k].outerHTML;
    }
    table_str += "</table>";
    $("#" + result_div_id).html(table_str);
}

//页面大小变化时
function pagesize_change(xmldom, page_size_id, page_count_id, page_current_id, result_div_id) {
    var book_count = xmldom.getElementsByTagName("tr").length - 1;
    var page_size = parseInt($("#" + page_size_id + " option:selected").text());
    var page_count = Math.ceil(book_count / page_size);
    $("#" + page_count_id).val(page_count);
    $("#" + page_current_id).val("1");
    display_cur_page(xmldom, page_size_id, page_count_id, page_current_id, result_div_id);
}

//下一页
function next_page(xmldom, page_size_id, page_count_id, page_current_id, result_div_id) {
    var page_count = parseInt($("#" + page_count_id).val());
    var cu_page = parseInt($("#" + page_current_id).val());
    cu_page++;
    if (cu_page > page_count) { alert("已到最后一页"); return; }
    else {
        $("#" + page_current_id).val(cu_page);
        display_cur_page(xmldom, page_size_id, page_count_id, page_current_id, result_div_id);
    }
}

//上一页
function prior_page(xmldom, page_size_id, page_count_id, page_current_id, result_div_id) {
    var cu_page = parseInt($("#" + page_current_id).val());
    cu_page--;
    if (cu_page == 0) { alert("已到最前一页"); return; }
    else {
        $("#" + page_current_id).val(cu_page);
        display_cur_page(xmldom, page_size_id, page_count_id, page_current_id, result_div_id);
    }
}

//最前一页
function first_page(xmldom, page_size_id, page_count_id, page_current_id, result_div_id) {
    $("#" + page_current_id).val("1");
    display_cur_page(xmldom, page_size_id, page_count_id, page_current_id, result_div_id);
}

//最后一页
function last_page(xmldom, page_size_id, page_count_id, page_current_id, result_div_id) {
    var page_count = parseInt($("#" + page_count_id).val());
    $("#" + page_current_id).val(page_count);
    display_cur_page(xmldom, page_size_id, page_count_id, page_current_id, result_div_id);
}

2、自定义XML文档与XSL文档绑定逻辑

对应文件:XSLT.js

//以下是xsl格式化XML并输出结果到div
function displayXML(xml, xsl, div_id) {
    // code for IE
    if (window.ActiveXObject) {
        ex = xml.transformNode(xsl);
        document.getElementById(div_id).innerHTML = "";
        document.getElementById(div_id).innerHTML = ex;
    }
    // code for Chrome, Firefox, Opera, etc.
    else if (document.implementation && document.implementation.createDocument) {
        xsltProcessor = new XSLTProcessor();
        xsltProcessor.importStylesheet(xsl);
        resultDocument = xsltProcessor.transformToFragment(xml, document);
        document.getElementById(div_id).innerHTML = "";
        document.getElementById(div_id).appendChild(resultDocument);
    }
}

12.4.3 后台服务实现代码

1、主服务实现

对应文件:server.js

// JavaScript source code
//------------请将这个文件保存为Unicode格式,否则麻烦大大的--------------
const http_obj = require("http");//用于产生服务对象
const fs_obj = require("fs");//读写后台文件
const url_tran_obj = require("url");//解释URL

const server = http_obj.createServer(function (request, response) {
    //请求路径处理
    var req_str = decodeURI(request.url);
    var req_head;
    var POS = req_str.indexOf("?");
    if (POS == -1) { req_head = req_str; }
    else { req_head = req_str.substring(0, POS); }
    
    //设置查询对象
    var url_obj = url_tran_obj.parse(req_str, true);
    var Q_obj = url_obj.query;
    
    //响应头设置
    response.setHeader("Content-Type", "text/plain;charset=utf-8");
    response.setHeader("Access-Control-Allow-Origin", "*");//实现跨域访问
    response.writeHead(200);
    
    //%%%%%%%%%%%%%-----------begin your code
    require("./12/server/server_12.js")(request, response, req_head, Q_obj, fs_obj);
    //%%%%%%%%%%%%%------------end your code
});

server.listen(1007);
console.log("Server is running at port 1007...");

2、子服务实现

文件server_12.js实现代码:

function server_12(request, response, req_head, Q_obj, fs_obj) {
    if (req_head == "/viewAllbooks_12") {
        //代码1
        fs_obj.readFile("./12/data/books.xml", "utf-8", function (err, data) {
            if (err) { response.end(err); }
            else { response.end(data); }
        });
    }
}

module.exports = server_12

12.4.4 XSL实现代码

1、降序XSL代码

对应文件:D.xslt

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
    <xsl:template match="/">
        <table border="1" id="books">
            <tr style="background-color:greenyellow">
                <th id="ISBN">书号</th>
                <th id="title">书名</th>
                <th id="price">
                    价格
                    <span class="order_span" onclick="dd_view()">降序</span>
                    <span class="order_span" onclick="aa_view()">升序</span>
                </th>
            </tr>
            <xsl:for-each select="table/tr[position()>1]">
                <xsl:sort select="td[3]" order="descending" data-type="number"/>
                <tr>
                    <td>
                        <xsl:value-of select="td[1]"/>
                    </td>
                    <td>
                        <xsl:value-of select="td[2]"/>
                    </td>
                    <td>
                        <xsl:value-of select="td[3]"/>
                    </td>
                </tr>
            </xsl:for-each>
        </table>
    </xsl:template>
</xsl:stylesheet>

2、升序XSL代码

对应文件:A.xslt

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
    <xsl:template match="/">
        <table border="1" id="books">
            <tr style="background-color:greenyellow">
                <th id="ISBN">书号</th>
                <th id="title">书名</th>
                <th id="price">
                    价格
                    <span class="order_span" onclick="dd_view()">降序</span>
                    <span class="order_span" onclick="aa_view()">升序</span>
                </th>
            </tr>
            <xsl:for-each select="table/tr[position()>1]">
                <xsl:sort select="td[3]" order="ascending" data-type="number"/>
                <tr>
                    <td>
                        <xsl:value-of select="td[1]"/>
                    </td>
                    <td>
                        <xsl:value-of select="td[2]"/>
                    </td>
                    <td>
                        <xsl:value-of select="td[3]"/>
                    </td>
                </tr>
            </xsl:for-each>
        </table>
    </xsl:template>
</xsl:stylesheet>

12.4.5 XML文件改进实现代码

对应文件:books.xml

<?xml version="1.0" encoding="utf-8"?>
<table border="1" id="books">
    <tr style="background-color:greenyellow">
        <th id="ISBN">书号</th>
        <th id="title">书名</th>
        <th id="price">
            价格
            <span class="order_span" onclick="dd_view()">降序</span>
            <span class="order_span" onclick="aa_view()">升序</span>
        </th>
    </tr>
    <tr>
        <td>5744885</td>
        <td>Java programming</td>
        <td>80</td>
    </tr>
    <tr>
        <td>2323885</td>
        <td>C# programming</td>
        <td>87</td>
    </tr>
    <tr>
        <td>8888</td>
        <td>Python programming</td>
        <td>68</td>
    </tr>
    <tr>
        <td>8767885</td>
        <td>NodeJS</td>
        <td>90</td>
    </tr>
    <tr>
        <td>r64547565647689</td>
        <td>C++ programming</td>
        <td>68</td>
    </tr>
    <tr>
        <td>45634748758478</td>
        <td>MongoDB</td>
        <td>90</td>
    </tr>
    <tr>
        <td>675665647689</td>
        <td>operation system</td>
        <td>68</td>
    </tr>
    <tr>
        <td>2342353758478</td>
        <td>XML技术</td>
        <td>90</td>
    </tr>
</table>

12.4.5 实现效果

降序实现效果如图12.5所示。

序升实现效果如图12.6所示。

降序实现效果
图12.5 降序实现效果
升序实现效果
图12.6 升序实现效果

12.5 问题思考

问题:通过以上实例演示,在基于三层架构分页浏览XML数据时,动态调用XSLT模板格式化XML数据实现表格内嵌式排序的技术要点是什么?

思考(知识归纳):

  1. 首先,应根据设计需求在后台设计合适的XSLT文件。
  2. 在XSLT文件存放在UI文件包中,便于AJAX get()函数读取。
  3. 根据需要AJAX get()函数动态读取不同功能XSLT文件并生成XSL对象。
  4. 将当前页XML数据转化为对象xmldom。
  5. 对象xmldom与对象xsldom绑定并格式化输出。

12.6 仿真实训

给定如下XML文档:

<?xml version="1.0" encoding="utf-8"?> <table border="1" id="books"> <tr style="background-color:greenyellow"> <th id="s_id">学号</th> <th id="name">姓名</th> <th id="age">年龄</th> </tr> <tr> <td>5744885</td> <td>Max</td> <td>18</td> </tr> <tr> <td>2323885</td> <td>Jack</td> <td>17</td> </tr> <tr> <td>8888</td> <td>Roseg</td> <td>18</td> </tr> <tr> <td>8767885</td> <td>Json</td> <td>19</td> </tr> <tr> <td>64547565647689</td> <td>张三</td> <td>16</td> </tr> <tr> <td>45634748758478</td> <td>李四</td> <td>19</td> </tr> <tr> <td>675665647689</td> <td>王五</td> <td>18</td> </tr> <tr> <td>2342353758478</td> <td>刘六</td> <td>20</td> </tr> </table>

现提出如下设计需求:

  1. 设计合适的用户接口,可自定义浏览页面大小(每页可展示图书条目的数量)。
  2. 基于选定的页面大小,用户可选择性地浏览"最前页"、"最后页"、"前一页"、"后一页"。
  3. 基于三层架构读取XML文档。
  4. 单页中按"价格"进行"降序"或"升序"排序浏览。
  5. "降序"或"升序"交互操作在表格内部进行。

仿照本章案例完成相关设计并实现相关功能。