jQuery XML 解析器和搜索





5.00/5 (5投票s)
jQuery XML 解析器和搜索
引言
此处描述的过程将使您能够创建一个简单的基于 jQuery/XML 的解析器和搜索机制。此过程将通过 AJAX 请求检索 XML,然后在 jQuery 中解析数据,为搜索机制做好准备。此解决方案将根据不区分大小写的完整或部分关键字匹配返回结果。关键字搜索返回的结果集将格式化为指向相应站点的直接超链接。jQuery 搜索的方法与 Mike Endale 的一个项目非常相似,增加了 DOM 解析器、RegExp 和结果集分组。
背景
客户需要一个简单的搜索工具,根据关键字搜索来查找内部网站。关键字搜索必须不区分大小写,并允许返回部分匹配的结果。由于客户内容管理系统(SharePoint)的架构,只能执行客户端脚本。他解决方案的另一个障碍是源数据将来自多个来源。数据存储在多个 Excel 电子表格、CSV 文件和一个 MS Access 数据库中。这需要开发一个 Access 解决方案,其中包含一系列查询和一个宏,作为伪 ETL,合并、清理并最终将数据格式化为 XML 输出。为了本解决方案的目的,我们将详细介绍 JavaScript XML 解析器的设计,而不是伪 Access ETL 宏工具的设计。
使用代码
解决方案方法将利用简单的基于 JavaScript/XML 的搜索,将数据结果发送到 HTML/JavaScript 前端。前端将引用脚本:jQuery、XML 和 CSS 文件。XML 格式将被使用,因为它具有可读性,并且是数据交换的行业标准格式之一。XML 数据将通过客户端 jQuery 使用 AJAX 进行解析,并通过 Internet Explorer 11 进行呈现。
该解决方案将使用 RegExp 对象来处理关键字匹配、验证和特殊字符处理。将检查 RegExp 对象字符串中的危险语法,从而提高解决方案的稳定性和整体可用性。
我们将使用 JavaScript 分组功能,默认将匹配结果作为折叠的记录集返回。折叠的记录集条目将是链接到相关项目工作区网站的 URL。展开的分组记录集结果下将包含相关的子记录,当通过单击事件展开时。
信息架构
解析器函数接收复杂的层次化 XML 树(包含节点和属性),并将其转换为等效的 JavaScript 对象和属性。基于客户端 JavaScript/XML 的搜索遵循以下步骤:
- 伪 ETL 工具将数据准备成 XML 文件(此项目不涵盖此内容)
- XML 文件被加载到指定位置(此项目不涵盖此内容)
- 在点击事件发生时,JavaScript 解析器将使用 AJAX 方法加载 XML 数据
- 检查是否存在用于搜索的关键字
- 如果没有关键字,则抛出错误消息“请输入搜索关键字”
- 如果一个节点包含 URL 属性的字符串,则将这些节点放入一个数组中。
- RegExp 对象关键字匹配,通过替换处理特殊字符
- RegExp 对象关键字匹配转换为不区分大小写
- 循环数组,根据验证过的 RegExp 对象进行匹配
- 如果没有结果,则抛出错误消息“未找到结果!”
- 构建结果集,顶层分组具有斑马纹的奇偶行
- 构建匹配 PPID 的分组行,以及相关的工单作为子分组
- 填充结果,然后将它们传递到最终集合以供呈现
- 显示结果集,包括列标题和所有分组
- 分组默认折叠
用户界面
用户界面将是一个简单的 HTML/JavaScript 客户端搜索,默认以折叠的分组记录集形式返回关键字匹配的结果。折叠的记录集条目将是直接链接到相关项目网站的 URL。展开的分组记录集结果下将包含相关的子记录,当通过点击事件展开时。
内联页面引用
首先,我们需要引用我们的脚本:jQuery、XML 和 CSS 文件。
< link rel="stylesheet" href="path/default.css" /> < script type="text/javascript" src="path/jquery-1.4.2.min.js"></script> < script id="data" type="text/javascript" src="path/search.js" xmlData="data.xml"></script> < input id="term" type="text"/> < input name="Search" id="searchButton" type="button" value="Search"/> < div id="result">< /div>
您会注意到我们在 search.js 引用中添加了 xmlData
属性。这是将 XML 文件位置从 HTML 文件传递过来的最佳方式。如果您有多个 XML 文件需要用作数据源,这将非常有帮助。
XML 数据源
XML 数据源可以以任何方式构建,也可以是任何大小;但建议将源 XML 文件保持在 1 MB 以下,以维持适当的解析器响应时间。以下是本项目使用的 XML 源示例:
< ?xml version="1.0" encoding="UTF-8"?> < dataroot generated="2015-11-20T10:30" xmlns:od="urn:schemas-microsoft-com:officedata"> < etl> < PPID Lead="Slow,Roy" Description="NORTH OF FAIR" PID="P002"> < WO Description="SHELTON - BANK (SAFETY)" PM="Slow,Roy" Status="CLOSED" WID="305577" WOXREF="SHEL" Program="REINFORCEMENT"> < /WO> < Archive>Archived</Archive> < record search="P002NORTH OF FAIRSHELSHELTON 305577SHELTON - BANK (SAFETY)Slow,Roy"/> < url address="P002"/> < /PPID> < /etl> < /dataroot>
错误处理
为了本项目,我们在两个关键区域使用了错误处理。如果没有关键字,将显示错误消息“请输入搜索关键字”。如果没有生成任何结果,则抛出错误消息“未找到结果!”。
//Check if a keyword exists if (keyword == '') { errMsg += 'Please enter a search keyword'; } else { searchThis(); }
if (i == 0) { pub += '< div class="error">'; pub += 'No results were found!'; pub += '< /div>';
使用 jQuery AJAX 请求
我们将通过预定义的 jQuery 库(已在上层页面中启用)异步 JavaScript 函数来调用 XML。AJAX 是“Asynchronous JavaScript and XML”的缩写,由 Adaptive Path 创始人 Jesse James Garrett 创造。AJAX 依赖于 XMLHttpRequest、CSS、DOM 和其他技术。AJAX 的主要特点是其“异步”性质,这意味着它可以发送和接收数据而无需刷新页面。在异步模式下,客户端和服务器将独立工作并独立通信,允许用户继续与网页交互,而独立于服务器上发生的事情。
function searchThis() { $.ajax({ type: "GET", url: XMLSource, dataType: "xml", success: function (xml) { loadPublication(xml) } }); }
使用 DOM 解析和 RegExp
由于 jQuery 本身无法解析 XML 字符串;我们将利用浏览器支持的 DOM 解析方法。此方法得到 Firefox、Chrome、Safari 和最新的 Internet Explorer 浏览器的支持,它们都有 DOMParser 对象。旧的 Internet Explorer 浏览器(如 IE 8)使用其专有的 ActiveX 对象。可以创建一个跨浏览器解决方案,检查 DOMParser 的缺失,但这超出了本项目的范围,但可能会在以后添加以提供额外的跨浏览器支持。
了解 JavaScript 的 RegExp(正则表达式)功能和语法,我希望在我的源代码中有一个简化的 RegExp 函数来处理任何特殊字符。我们还希望 Regex 不区分大小写,通过定义忽略大小写的 RegExp 来实现更友好的关键字搜索。
function loadPublication(xmlData) { i = 0; var row; var searchExp = ""; var ppid = "P"; $(xmlData).find('PPID').each(function () { // Check if a site URL attr exists if($(this).find('url').attr('address').length) { //Set the search string Variables var record = $(this).find('record').attr('search'); var archive = $(this).find('Archive'); //Escape characters in keyword expression and use global match RegExp.escape = function(keyword) { return keyword.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); }; //Keyword expression will be case agnostic var exp = new RegExp(keyword, "i"); //Use formated keyword as the default search searchExp = record.match(exp); //If the search expression is not null if (searchExp != null) { //Start building the result if ((i % 2) == 0) { row = 'even'; } else { row = 'odd'; } if($(this).attr('PID') != ppid) { ppid = $(this).attr('PID'); i++;
分组返回结果
用户界面将是一个简单的 HTML/JavaScript 客户端搜索,默认以折叠的分组记录集形式返回关键字匹配的结果。折叠的记录集条目将是直接链接到相关项目网站的 URL。展开的分组记录集结果下将包含相关的子记录,当通过点击事件展开时。
//Grouping of the results function expgroupby(e) { docElts=document.all; numElts=docElts.length; images = e.getElementsByTagName("IMG"); img=images[0]; srcPath=img.src; index=srcPath.lastIndexOf("/"); imgName=srcPath.slice(index+1); var b="auto"; if(imgName=="plus.gif"){ b=""; img.src="/_layouts/images/minus.gif" }else{ b="none"; img.src="/_layouts/images/plus.gif" } oldName=img.name; img.name=img.alt; spanNode=img; while(spanNode!=null){ spanNode=spanNode.parentNode; if(spanNode!=null&&spanNode.id!=null&&spanNode.id=="wrapper")break } while(spanNode.nextSibling!=null&&spanNode.nextSibling.id!="wrapper"){ spanNode=spanNode.nextSibling; spanNode.style.display=b; } }
完整源代码
为了本项目,这里是完整源代码的示例。
//Full source $(document).ready(function () { //Global Variables var XMLSource = $('#data').attr('xmlData'); var keyword = ''; var pub = ''; var i = 0; $("#searchButton").click(function () { keyword = $("input#term").val(); //Reset any message var errMsg = ''; pub = ''; //Check if a keyword exists if (keyword == '') { errMsg += 'Please enter a search keyword'; } else { searchThis(); } if (errMsg != '') { pub += '< div class="error">'; pub += errMsg; pub += '< /div>'; } //Show error $('#result').html(pub); }); //Use enter key to trigger the search query $("input#term").keypress(function (e) { var key = e.which; if (key == 13){ $("#searchButton").click(); return false; } }); function searchThis() { $.ajax({ type: "GET", url: XMLSource, dataType: "xml", success: function (xml) { loadPublication(xml) } }); } function loadPublication(xmlData) { i = 0; var row; var searchExp = ""; var ppid = "P"; $(xmlData).find('PPID').each(function () { // Check if a site URL attr exists if($(this).find('url').attr('address').length) { var record = $(this).find('record').attr('search'); var archive = $(this).find('Archive'); //Escape characters in keyword expression and use global match RegExp.escape = function(keyword) { return keyword.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); }; //Keyword expression will be case agnostic var exp = new RegExp(keyword, "i"); //Use formated keyword as the default search searchExp = record.match(exp); //If the search expression is not null if (searchExp != null) { //Start building the result if ((i % 2) == 0) { row = 'even'; } else { row = 'odd'; } if($(this).attr('PID') != ppid) { ppid = $(this).attr('PID'); i++; //Grouped header row with URL links from the xml attr pub += '< tr id="wrapper" class="row ' + row + '">' + '< td colspan="8">' + '< a onclick="javascript:expgroupby(this);return false;" href="javascript:">' + '< img name="collapse" alt="expand" src="/_layouts/images/plus.gif" border="0" />< /a>' + '< a href="http://project.com/sites/tp/Projects/' + $(this).find('url').attr('address') + '">' + ' ' + $(this).attr('PID')+ ' - ' + $(this).attr('Description') + ' - ' + $(this).attr('Lead') + '< /a>< /td>' + '</tr>'; } //Bottom grouped expand detail fields pub += '<tr id="item" style="display: none;">' + '< td valign="top" class="col2">' + $(this).find('WO').attr('WID') + '< /td>' + '< td valign="top" class="col3">' + $(this).find('WO').attr('Description') + '< /td>' + '< td valign="top" class="col4">' + $(this).find('WO').attr('PM') + '< /td>' + '< td valign="top" class="col5">' + $(this).find('WO').attr('Status') + '< /td>' + '< td valign="top" class="col6">' + $(this).find('WO').attr('WOXREF') + '< /td>' + '< td valign="top" class="col7">' + $(this).find('WO').attr('Program') + '< /td>' + '< td valign="top" class="col8">' + $(this).find('Archive').text() + '< /td>' + '< /tr>'; } } }); if (i == 0) { pub += '< div class="error">'; pub += 'No results were found!'; pub += '< /div>'; //Populate the result $('#result').html(pub); } else { //Pass the result set showResult(pub); } } function showResult(resultSet) { //Show the result with the titles of the column fields pub = '< div class="message">There are ' + i + ' results!< /div>'; pub += '< table id="grid" border="0">'; pub += '< thead>< tr>< td>< th class="mess">PPID - Project Description - Lead PM< /th>< /td>< /tr>'; pub += '< tr>< th class="col2">WO Number< /th>'; pub += '< th class="col3">WO Description< /th>'; pub += '< th class="col4">Project Manager< /th>'; pub += '< th class="col5">Status< /th>'; pub += '< th class="col6">XRef< /th>'; pub += '< th class="col7">Program< /th>'; pub += '< th class="col8">Archive Status< /th>'; pub += '< /tr>< /thead>'; pub += '< tbody>'; pub += resultSet; pub += '< /tbody>'; pub += '< /table>'; //Populate the results $('#result').html(pub) $('#grid').tablesorter(); } }); //Grouping of the results function expgroupby(e) { docElts=document.all; numElts=docElts.length; images = e.getElementsByTagName("IMG"); img=images[0]; srcPath=img.src; index=srcPath.lastIndexOf("/"); imgName=srcPath.slice(index+1); var b="auto"; if(imgName=="plus.gif"){ b=""; img.src="/_layouts/images/minus.gif" }else{ b="none"; img.src="/_layouts/images/plus.gif" } oldName=img.name; img.name=img.alt; spanNode=img; while(spanNode!=null){ spanNode=spanNode.parentNode; if(spanNode!=null&&spanNode.id!=null&&spanNode.id=="wrapper")break } while(spanNode.nextSibling!=null&&spanNode.nextSibling.id!="wrapper"){ spanNode=spanNode.nextSibling; spanNode.style.display=b; } }
历史
以后可能会添加 ActiveX 解析以提供额外的跨浏览器(IE 8)支持。