Click here to Skip to main content
15,867,488 members
Articles / Web Development

jQuery XML Parser and Search

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
29 Aug 2016CPOL6 min read 29.3K   440   9   3
jQuery XML Parser and Search

Introduction

The procedure described here will enable you to create a simple jQuery/XML based parser and search mechanism. This procedure will retrieve the XML through an AJAX request and then parse the data within jQuery to prepare it for the search mechanism. This solution will return results based on case insensitive full or partial keyword matches. The returned result set from the keyword search will be formatted as a direct hyperlink(s) to the corresponding sites. The methods of the jQuery search are very similar to a project by Mike Endale with the additions of a DOM parser, RegExp, and result set grouping.

Background

A client required a simple search tool to locate internal websites based on keyword searches. The keyword searches must be case insensitive and allow for partial match results to be returned. Due to the architecture of the client’s content management system, (SharePoint) only client-side scripts can be executed. Another obstacle for his solution is that the source data will come from multiple sources. The data is stored within multiple Excel spread sheets, CSV files and an MS Access database. This presented the need to develop an Access solution with a series of queries and a macro to function as a pseudo ETL that will merge, scrub and finally format the data as XML output. For the purpose of this solution we will detail the design of the JavaScript XML parser and not the design of the pseudo Access ETL macro tool.

Using the code

The solution approach will utilize a simple JavaScript/XML based search to send the data results to a HTML/JavaScript frontend. The frontend will reference the scripts: jQuery, XML and CSS files. The XML format will be utilized because of its readability and the fact that it’s one of the industry standard formats for data interchange. The XML data will be parsed through client-side jQuery using AJAX and presented through Internet Explorer 11.

The solution will use RegExp object to handle the keyword match, validation, and special character handling. The RegExp object string will be checked for dangerous syntax thereby improving the stability and overall usability of the solution.

We will use a JavaScript grouping functionality to return matching results as a collapsed record set by default. The collapsed record set line item will be the URL link to the associated Project Workspace website. Under the expanded group record set results will reside associated child records when expanded by On Click event.

Information Architecture

The parser function takes a complex hierarchical XML tree with nodes and attributes, and turns it into equivalent JavaScript objects and properties. The client-side JavaScript/XML based search goes through the following steps:

  1. The pseudo ETL tool prepares the data into an XML file (this is not covered in this project)
  2. The XML file is loaded to a specified location (this is not covered in this project)
  3. Upon a click event the JavaScript parser will load the XML data using AJAX method
  4. Check for the presence of keyword(s) for the search
    • If no keyword(s) are present throw error message "Please enter a search keyword"
  5. If a node contains a string for the URL attribute then those nodes are brought into an array.
  6. RegExp object keyword match with special character handling by replacement
  7. RegExp object keyword match transformed to case insensitive
  8. Loop the array to match up based on the validated RegExp object
    • If no result(s) are throw error message "No results were found!"
  9. Build result set with zebra striped even and odd rows for the top level group
  10. Build group matching PPID rows with associated Work Order(s) as sub groups
  11. Populate the results then pass them to the final set to be presented
  12. Show the result set with the titles of the columns and all groupings
    • Groupings are collapsed by default equivalent

User Interface

The user interface will be a simple HTML/JavaScript client-side based search to return keyword matched results as collapsed grouped record set by default. The collapsed record set in line item(s) will be the direct URL link to the associated project website. Under the expanded group record set results will reside associated child records when expanded by On Click event.

Inline Page References

First thing we will need to do is reference our scripts: jQuery, XML and CSS files.

< 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>

 

You’ll notice we’ve added the xmlData attribute to the search.js reference. This is the best way to pass the XML file location from the HTML file. This helps a great deal if you have multiple xml files you want to use as data source.

XML Data Sources

The XML data source can be structured in any way or can be any size; but it is recommended to keep source XML files under 1 MB to maintain an appropriate parser response time. Here is an example of the XML source used for this project:

< ?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>

Error Handling

For the purpose of this project we have utilized error handling in two critical areas. If no keyword(s) are present then the error message "Please enter a search keyword" will be presented. If no result(s) are generated then throw the error message "No results were found!" will be presented.

//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>';

Using the jQuery AJAX Request

We will call the XML through an Asynchronous JavaScript function through the predefined jQuery library that was enabled on page level above. AJAX stands for "Asynchronous JavaScript and XML", and was coined by Jesse James Garrett, founder of Adaptive Path. AJAX relies on XMLHttpRequest, CSS, DOM, and other technologies. The main characteristic of AJAX is its "asynchronous" nature, which means it can send and receive data from the server without having to refresh the page. With asynchronous mode, the client and the server will work independently and also communicate independently, allowing the user to continue interaction with the web page, independent of what is happening on the server.

function searchThis() {
  $.ajax({
    type: "GET",
    url: XMLSource,
    dataType: "xml",
    success: function (xml) {
      loadPublication(xml)
    }
  });
}

Using the DOM Parsing and RegExp

Since jQuery itself is not capable of parsing XML strings; we will leverage the browsers DOM parsing method which most browsers support in one form or another. This method is supported by Firefox, Chrome, Safari and the latest Internet Explorer browsers that have a DOMParser object. Older Internet Explorer browsers (like IE 8) use their proprietary ActiveX object. A cross-browser solution can be created that checks for the absence of the DOMParser, but that is out of scope for this project but may be added at a later date for additional cross-browser support.

Knowing the RegExp (Regular Expression) capabilities and syntax of JavaScript I wanted to have a simplified function within RegExp in my source to deal with any special characters. We also wanted the Regex to be case insensitive through defining the RegExp to ignore case for a more user friendly keyword searches.

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++;

Grouping the Returned Results

The user interface will be a simple HTML/JavaScript client-side based search to return keyword matched results as collapsed grouped record set by default. The collapsed record set in line item(s) will be the direct URL link to the associated Project website. Under the expanded group record set results will reside associated child records when expanded by On Click event.

//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 Code

For the purpose of this project here is the example of the full source code.

//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;
	}
}

History

ActiveX parsing may be added at a later date for additional cross-browser (IE 8) support.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
Pragmatic solutions focused professional with a proven record of accomplishments within service-oriented enterprise architectures.

Comments and Discussions

 
Questionhi i cannot config the html file as it doesnt load the search input Pin
Member 1025811110-Aug-17 18:05
Member 1025811110-Aug-17 18:05 
AnswerRe: hi i cannot config the html file as it doesnt load the search input Pin
bmiller36722-May-18 9:30
professionalbmiller36722-May-18 9:30 
QuestionJSON Pin
bmiller3677-Apr-16 13:49
professionalbmiller3677-Apr-16 13:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.