第17章 更新XML格式数据

17.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 id="add_update_XML"> <td> <input type="text"/> </td> <td> <input type="text"/> </td> <td> <input type="text"/> </td> </tr> <!-- 更多数据行... --> </table>

现提出如下设计需求:

  1. 设计nodeJS后台服务,该服务可在后台实现"更新图书信息"业务。
  2. 后台需基于DOM模型实现XML节点更新。
  3. 基于Ajax技术及三层架构实现"更新图书"业务。

17.2 设计分析

17.2.1 功能分析

测试用户期望功能如下:

  1. 测试用户可查看测试XML文档全部原始数据。
  2. 测试用户可模糊查询图书信息。
  3. 测试用户可更新图书信息。
功能用例建模
图17.1 功能用例建模

17.2.2 核心业务数据流分析

功能模块中的核心业务是:模糊查询图书并定位一条图书记录,更新图书信息并保存。

核心业务流程描述如下:

  1. 模糊查询图书信息并向用户输出表格化格式数据。
  2. 用户选定要更新的某行图书信息。
  3. 用户编辑选定的图书信息。
  4. 用户提交新数据及更新前书号到后台。
  5. 在内存根据更新前书号定位原图书位置并将新数据覆盖旧数据。
  6. 保存新数据覆盖XML源文件。
核心业务数据流
图17.2 核心业务数据流

17.2.3 文件包及文件设计分析

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

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

17.2.4 XML文档创新改进

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

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

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

<tr id="add_update_XML"> <td> <input class="edit_data" style="color:blue" type="text"/> </td> <td> <input class="edit_data" style="color:blue" type="text"/> </td> <td> <input class="edit_data" style="color:blue" type="text"/> </td> <td> <input type="button" value="提交新数据" onclick="post_new_XMLdata()"/> <input type="button" value="取消" onclick="$('tr').hide()"/> </td> </tr>

另外,为了新旧数据对比,可在这个<tr>标记之前再增加一个tr标记,用于临时存放待更新的旧数据。

17.3 文件架构

文件架构
图17.4 文件架构

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

序号 文件(夹)名 作用
1 books.xml 用于测试的XML基础数据
2 Server.js NodeJS后台服务主程序
3 Server_17.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 用于恢复的XML基础数据
表17.2 文件及文件夹功能说明

17.4 代码实现

17.4.1 主页实现

<!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="text" id="title_input" /> <input type="button" value="模糊查询图书" onclick="select_TagData()" /> <hr style="margin-top:0;margin-bottom:0" /> <input type="button" value="恢复数据为初始状态" onclick="restore_TagData()" /> <hr style="margin-top:0;margin-bottom:0" /> <div id="outer_div"> </div> </body> </html> <script type="text/javascript" src="jquery-1.11.1.js"></script> <script type="text/javascript"> //基于XPath的标记数据模糊查询 function select_TagData() { var book_name = $("#title_input").val(); var xhttp = new XMLHttpRequest(); xhttp.open("post", "http://localhost:1017/mhFind_17?param=" + encodeURIComponent(book_name), true); xhttp.onreadystatechange = function () { if (xhttp.status == 200 && xhttp.readyState == 4) { $("#outer_div").html("<table border='1'>" + xhttp.responseText + "</table>"); $("#old_data").hide(); $("#add_update_XML").hide(); var trs = $("tr"); for (var k = 3; k < trs.length; k++) { trs[k].innerHTML = trs[k].innerHTML + '<td><input type="button" value="更新" onclick="init_update(this)" /></td>'; } } } xhttp.send(); } //初始化更新 function init_update(cur_obj) { var update_inputs = $("#add_update_XML").find("input"); var cur_tr_obj = $(cur_obj).parent().parent(); var cur_tds = cur_tr_obj.find("td"); for (var k = 0; k < cur_tds.length - 1; k++) { document.getElementById("old_data").innerHTML += cur_tds[k].outerHTML; update_inputs[k].value = cur_tds[k].innerHTML; } document.getElementById("old_data").innerHTML += "原有图书信息"; $("tr").hide(); $("#table_head").show(); $("#old_data").show(); $("#add_update_XML").show(); } //提交新数据 function post_new_XMLdata() { if (edited()) { var update_inputs = $("#add_update_XML").find("input.edit_data"); var count = update_inputs.length; var new_dataTr = "\n<tr>\n"; for (var k = 0; k < count; k++) { new_dataTr += "<td>" + $(update_inputs[k]).val() + "</td>\n"; } new_dataTr += "</tr>"; //提交新数据到后台 var old_isbn = $("#old_data").find("td")[0].innerHTML; alert(old_isbn); var xhttp = new XMLHttpRequest(); xhttp.open("post", "http://localhost:1017/postUpdate_17?param1=" + encodeURIComponent(old_isbn) + "¶m2=" + encodeURIComponent(new_dataTr), true); xhttp.onreadystatechange = function () { if (xhttp.status == 200 && xhttp.readyState == 4) { alert(xhttp.responseText); } } xhttp.send(); } else { alert("数据未做任何更新,无需提交到后台!"); } } //判断数据是否已编辑 function edited() { var update_inputs = $("#add_update_XML").find("input.edit_data"); var old_td = $("#old_data").find("td"); var re_value = false; for (var k = 0; k < update_inputs.length; k++) { if ($(update_inputs[k]).val() != old_td[k].innerHTML) { re_value = true; } } return re_value; } //恢复数据 function restore_TagData() { if (confirm("确定要将数据恢复到最初状态?")) { var xhttp = new XMLHttpRequest(); xhttp.open("post", "http://localhost:1017/restoreData_17", true); xhttp.onreadystatechange = function () { if (xhttp.status == 200 && xhttp.readyState == 4) { alert(xhttp.responseText); } } xhttp.send(); } } </script>

17.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("./17/server/server_17.js")(request, response, req_head, Q_obj, fs_obj); //%%%%%%%%%%%%%------------end your code }); server.listen(1017); console.log("Server is running at port 1017...");

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

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

function server_17(request, response, req_head, Q_obj, fs_obj) { //%%%%%%%%%%%%%-----------begin your code if (req_head == "/mhFind_17") { //模糊查询 var b_title = Q_obj["param"]; var b_xpath = "/table/tr[position()<4] | /table/tr[contains(td[2],'" + b_title +"')]"; var select = require("./xpath");//获取查询函数 var dom = require("./xmldom").DOMParser;//获取DOMParser类 fs_obj.readFile("./17/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); if (nodes.length == 2) { response.end("未找到相关数据!"); } else { response.end(nodes.toString()); } } }); return; } //以下更新XML文档 if (req_head == "/postUpdate_17") { var old_isbn = Q_obj["param1"]; var new_data = Q_obj["param2"]; fs_obj.readFile("./17/data/books.xml", "utf-8", function (err, data) { if (err) { response.end(err); } else { var dom = require("./xmldom").DOMParser; var XMLdom = new dom().parseFromString(data); var tr_doms = XMLdom.getElementsByTagName("tr");//找到所有tr var rec_order=-1; for (var k = 3; k < tr_doms.length; k++) { var temp_isbn = tr_doms[k].getElementsByTagName("td")[0].textContent; if (temp_isbn == old_isbn) { rec_order = k; break; } } if (rec_order > 2) { tr_doms[rec_order] = new dom().parseFromString(new_data);//tr对象替换 var new_XMLtext = '<?xml version="1.0" encoding="utf-8"?>\n'; new_XMLtext += '<table border="1" id="books">\n'; new_XMLtext += tr_doms.toString() + "\n</table>"; fs_obj.writeFile("./17/data/books.xml", new_XMLtext ,"utf-8", function (err) { if (err) { response.end(err); } else { response.end("成功更新数据"); } }); } else { response.end("当前数据可能已被其他用户修改,更新失败!"); } } }); return; } //恢复数据 if (req_head == "/restoreData_17") { fs_obj.readFile("./17/data/books_bak.xml", "utf-8", function (err, data) { if (err) { response.end(err.toString()); } else { fs_obj.writeFile("./17/data/books.xml", data, function (err) { if (err) { response.end(err.toString()); } else { response.end("成功恢复数据"); } }); } }); return; } //%%%%%%%%%%%%%------------end your code } module.exports=server_17

17.4.4 实现效果

数据更新前模糊查询如图17.5、17.6所示。

数据更新前实现效果
图17.5 数据更新前实现效果

数据更新进行中如图17.6所示。

数据更新进行中实现效果
图17.6 数据更新进行中实现效果

17.5 问题思考

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

问题思考:

  1. 改进XML文档设计,在原有XML中添加特定<tr>标记(包含合理数量的输入接口定义)。
  2. 读取特定<tr>标记并在网页中可视化。
  3. 在可视化后的用户接口中更新选定数据。
  4. 提取输入新数据后的<tr>标记对应的HTML代码(假定为newDatatext)。
  5. 将newData_text及搜索关键字提交到后台。
  6. 后台程序将源XML文档转化成文档对象。
  7. 从文档对象中找出所有<tr>标记并生行对象集合。
  8. 后端根据前端传递过来的搜索关键字定位到要修改的行对象。
  9. 将newData_text转化为对象并替换已定位成功需修改的行对象。
  10. 将新的行对象转化为字符串并写入源文件。

17.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> <!-- 更多数据行... --> </table>

现提出如下设计需求:

  1. 设计nodeJS后台服务,该服务可在后台实现"更新图书"业务。
  2. 基于Ajax技术及三层架构实现"更新图书"业务。
  3. 后台基于DOM实现更新XML节点。
  4. 仿真案例功能实现基于以上设计需求业务。