65.9K
CodeProject 正在变化。 阅读更多。
Home

jQuery XML 解析器和搜索

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2016年4月5日

CPOL

6分钟阅读

viewsIcon

32102

downloadIcon

478

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 的搜索遵循以下步骤:

  1. 伪 ETL 工具将数据准备成 XML 文件(此项目不涵盖此内容)
  2. XML 文件被加载到指定位置(此项目不涵盖此内容)
  3. 在点击事件发生时,JavaScript 解析器将使用 AJAX 方法加载 XML 数据
  4. 检查是否存在用于搜索的关键字
    • 如果没有关键字,则抛出错误消息“请输入搜索关键字”
  5. 如果一个节点包含 URL 属性的字符串,则将这些节点放入一个数组中。
  6. RegExp 对象关键字匹配,通过替换处理特殊字符
  7. RegExp 对象关键字匹配转换为不区分大小写
  8. 循环数组,根据验证过的 RegExp 对象进行匹配
    • 如果没有结果,则抛出错误消息“未找到结果!”
  9. 构建结果集,顶层分组具有斑马纹的奇偶行
  10. 构建匹配 PPID 的分组行,以及相关的工单作为子分组
  11. 填充结果,然后将它们传递到最终集合以供呈现
  12. 显示结果集,包括列标题和所有分组
    • 分组默认折叠

用户界面

用户界面将是一个简单的 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)支持。

© . All rights reserved.