第16章 添加XML节点

16.1 设计需求

给定如下XML文档:

<?xml version="1.0" encoding="utf-8"?>
<table border="1" id="books">
<tr bgcolor="green">
<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. 设计nodeJS后台服务,该服务可在后台实现"添加图书"业务。
  2. 基于Ajax技术及三层架构实现"添加图书"业务。

16.2 设计分析

16.2.1 功能分析

测试用户期望功能如下:

  1. 测试用户可查看测试XML文档原始数据。
  2. 测试用户可添加图书信息。
  3. 测试用户可将图书信息恢复到初始状态。

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

功能用例建模

图16.1 功能用例建模

16.2.2 核心业务数据流分析

功能模块中的核心业务是:输入新图书信息并保存。

核心业务流程描述如下:

  1. 用户新的图书信息并形成标记数据文本。
  2. 文件添加数据模块在XML源文件对应文本中添加图书标记数据文本并形成新的标记数据文本。
  3. 将新的数据文本改写XML源文件。
  4. 查看改写后的XML源文件格式化数据。

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

核心业务数据流建模

图16.2 核心业务数据流建模

16.2.3 文件包及文件设计分析

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

序号 包设计 文件设计
1 设计表示层包(包名:UI),抽象表示层文件容器。 (1) 设计界面主页文件main.js。
(2) 主页引入jquery框架(jquery.js),辅助表示层处理。
2 设计业务逻辑层包(包名:server),抽象业务层文件容器。 (1) 设计通用业务逻辑服务文件server.js。
(2) 设计当前模块专用业务逻辑服务文件server_16.js。
(3) 引入xmldom模块(xmldom.js,外部安装获得)。
(4) 引入xpath模块(xpath.js,外部安装获得)。
3 设计数据层包(包名:data),抽象数据层文件容器。 (1) 设计XML数据存储文件books.xml。
(2) 设计XML数据备份文件books_bak.xml

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

文件包设计及文件关系

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

16.2.4 XML文档创新改进

为了减轻前端设计压力,可对现有XML文档进行创新设计,考虑到添加数据必须要有数据输入接口,而输入接口的数目与XML文档中数据条目数量有关,具体到以上文档,实际输入接口数目与<td>标记数量应该一致。

基于以上分析,可事先在以上给定XML文档中增加一行(即增加一个特定<tr>标记),该特定<tr>标记可事先嵌入HTML代码,目的是让应用程序读取这个特定<tr>即可得到输入界面,这样可避免前端设计人员再去设计数据输入界面。

需增加的特定<tr>标记设计如下:

<tr id="add_update_XML">
<td>
<input type="text"/>
</td>
<td>
<input type="text"/>
</td>
<td>
<input type="text"/>
</td>
</tr>

具体创新改进后的XML文档如下:

<?xml version="1.0" encoding="utf-8"?>
<table border="1" id="books">
<tr bgcolor="green">
<th id="ISBN">书号</th>
<th id="title">书名</th>
<th id="price">价格</th>
</tr>
<tr id="add_update_XML">
<td>
<input type="text"/>
</td>
<td>
<input type="text"/>
</td>
<td>
<input type="text"/>
</td>
</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>

应用程序读取id="add_update_XML"的 <tr>即可得到用户输入界面。

16.2.5 标记数据添加思路

对于轻量级XML文档来说,可以将整个文档读取为字符串,由于文档最后必定是结束标记</table>;因此,只要将该字符串替换成新数据对应的XML格式串,即可实现节点添加。

假定整个XML文档对应的字符串为XMLtext,新增加的数据对应的字符串为newDatatext,则调用以下替换代码即可实现添加新节点。

XMLtext.replace("</table>",newDatatext)

16.3 文件架构

文件架构如图16.4所示。

文件架构

图16.4 文件架构

文件功能说明见表16.4所述。

序号 文件(夹)名 作用
1 books.xml 用于测试的XML基础数据
2 Server.js NodeJS后台服务主程序
3 Server_16.js 服务当前应用的后台核心代码
4 jquery-1.11.1.js 加载JQuery函数库
5 main.html 用户界面主页
6 Data 存放XML数据文件的文件夹
7 server 存放业务逻辑层文件的文件夹
8 UI 存放表示层文件的文件夹
9 xmldom 安装xmldom模块产生的文件夹
10 xpath 安装xpath模块产生的文件夹
11 books_bak.xml books.xml的备份数据

16.4 代码实现

16.4.1 主页实现

主页main.html实现代码如下:

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <h2>标记数据添加测试</h2>
    <hr />
    <input type="button" value="添加图书" onclick="start_add_TagData()" />
    <input type="button" value="查看全部图书" onclick="view_All()" />
    <input type="button" value="恢复数据为初始状态" onclick="restore_TagData()" />
    <hr />
    <div id="outer_div">
    </div>
    <hr />
    <input id="post_but" type="button" value="提交新增图书" disabled="disabled" onclick="post_TagData()" />
</body>
</html>

<script type="text/javascript" src="jquery-1.11.1.js"></script>

<script type="text/javascript">
    //基于XPath的标记数据添加
    function start_add_TagData() {
        var xhttp = new XMLHttpRequest();
        xhttp.open("post", "http://localhost:1017/startAdd_16", true);
        xhttp.onreadystatechange = function () {
            if (xhttp.status == 200 && xhttp.readyState == 4) {
                $("#outer_div").html("<table border='1'>" + xhttp.responseText + "</table>");
                $("#post_but").attr("disabled",false);
            }
        }
        xhttp.send();
    }

    //提交新增图书
    function post_TagData() {
        var inputs = $("#add_update_XML").find("input");
        var isEmpty = false;
        for (var k = 0; k < inputs.length; k++) {
            if ($(inputs[k]).val() == "") { isEmpty = true; }
        }
        if (isEmpty) { alert("各项信息不能为空!"); }
        else{
            var new_tr = "<tr>\n";
            for (var k = 0; k < inputs.length; k++) {
                new_tr += "<td>" + $(inputs[k]).val() + "</td>\n";
            }
            new_tr += "</tr>";

            //将新行提交到后台
            var xhttp = new XMLHttpRequest();
            xhttp.open("post", "http://localhost:1017/AddXMLDataRow_16?param="+encodeURIComponent(new_tr), true);
            xhttp.onreadystatechange = function () {
                if (xhttp.status == 200 && xhttp.readyState == 4) {
                    alert(xhttp.responseText);
                }
            }
            xhttp.send();
        }
    }

    //查看全部图书
    function view_All() {
        var xhttp = new XMLHttpRequest();
        xhttp.open("post", "http://localhost:1017/viewAll_16", true);
        xhttp.onreadystatechange = function () {
            if (xhttp.status == 200 && xhttp.readyState == 4) {
                $("#outer_div").html(xhttp.responseText);
                $("#add_update_XML").hide();
            }
        }
        xhttp.send();
    }

    //恢复数据
    function restore_TagData() {
        if (confirm("确定要将数据恢复到最初状态?")) {
            var xhttp = new XMLHttpRequest();
            xhttp.open("post", "http://localhost:1017/restoreData_16", true);
            xhttp.onreadystatechange = function () {
                if (xhttp.status == 200 && xhttp.readyState == 4) {
                    alert(xhttp.responseText);
                }
            }
            xhttp.send();
        }
    }
</script>

16.4.2 后台服务主程序实现

服务主程序server.js实现代码如下:

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("./16/server/server_16.js")(request, response, req_head, Q_obj, fs_obj);
    //%%%%%%%%%%%%%------------------end your code
});

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

16.4.3 后台服务子模块程序实现

子模块程序server_16.实现代码如下:

function server_16(request, response, req_head, Q_obj, fs_obj) {
    //%%%%%%%%%%%%%----------------begin your code
    if (req_head == "/startAdd_16") {
        //添加前准备
        var b_xpath = "/table/tr[position()<3]";
        var select = require("./xpath");//获取查询函数
        var dom = require("./xmldom").DOMParser;//获取DOMParser类
        fs_obj.readFile("./16/data/books.xml", "utf-8", function (err, data) {
            if (err) { response.end(err); }
            else {
                var xmldom = new dom().parseFromString(data);
                var nodes = select(xmldom, b_xpath);
                response.end(nodes.toString());
            }
        });
        return;
    }

    if (req_head == "/AddXMLDataRow_16") {
        //添加数据(尾部字符串替换)
        var b_new_tr = Q_obj["param"];
        fs_obj.readFile("./16/data/books.xml", "utf-8", function (err, data) {
            if (err) { response.end(err); }
            else {
                var new_xml = data.replace("</table>",b_new_tr+"\n</table>") ;
                fs_obj.writeFile("./16/data/books.xml", new_xml, function (err) {
                    if (err) { response.end(err); }
                    else { response.end("成功添加数据"); }
                });
            }
        });
        return;
    }

    if (req_head == "/viewAll_16") {
        //查看全部图书
        fs_obj.readFile("./16/data/books.xml", "utf-8", function (err, data) {
            if (err) { response.end(err); }
            else {
                response.end(data);
            }
        });
        return;
    }

    if (req_head == "/restoreData_16") {
        fs_obj.readFile("./16/data/books_bak.xml", "utf-8", function (err, data) {
            if (err) { response.end(err.toString()); }
            else {
                fs_obj.writeFile("./16/data/books.xml", data, function (err) {
                    if (err) { response.end(err.toString()); }
                    else { response.end("成功恢复数据"); }
                });
            }
        });
        return;
    }
    //%%%%%%%%%%%%%------------------end your code
}

module.exports=server_16

数据添加实现效果如图16.5所示。

数据添加实现效果

图16.5 数据添加实现效果

16.5 问题思考

问题提出:添加XML节点的主要技术要点是什么?

问题思考:

  1. 改进XML文档设计,在原有XML中添加特定<tr>标记(包含合理数量的输入接口定义)。
  2. 读取特定<tr>标记并在内页中可视化。
  3. 在可视化后的用户接口中输入新数据。
  4. 提取输入新数据后的<tr>标记对应的HTML代码(假定为newDatatext)。
  5. 将newData_text提交到后台。
  6. 后台程序将源XML文档读到字符串变量中(假定为XMLtext);然后执行替换操作:XMLtext.replace("</table>",newDatatext)
  7. 将执行替换操作后的XMLtext写入源XML文档。

16.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 id="add_update_XML">
<td>
<input type="text"/>
</td>
<td>
<input type="text"/>
</td>
<td>
<input type="text"/>
</td>
</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. 设计nodeJS后台服务,该服务可在后台实现"添加学生"业务。
  2. 基于Ajax技术及三层架构实现"添加图书"业务。
  3. 仿真案例功能实现基于以上设计需求业务。