Index: main/trunk/greenstone3/web/client-side-xslt.js
===================================================================
--- main/trunk/greenstone3/web/client-side-xslt.js	(revision 30479)
+++ main/trunk/greenstone3/web/client-side-xslt.js	(revision 30480)
@@ -7,48 +7,90 @@
 
 @author		Steven McTainsh
-@date		14/02/2011
+@author		David Bainbridge
+@date		2011-2016
 
 */
 
-/* These URLs and file paths are fetched dynamically */
-//var gsweb = ""; // The file path to the Greenstone 3 web directory
-//var gsurl = ""; // The root URL for this Greenstone 3 installation
-
-/* Misc. switches and paths */
-var keyUrl = ''; // Used across methods to build up query string for text retrieval (client-side transformed version)
-var on = true; // Set to false to disable operation
-var debug = true;
-
-var index = 0; // Used for query array (to keep track of number of elements)
-var deferredEls = new Array(); // Elements to defer text retrieval for until later
-var queryArr = new Array(); // Text to query for (the text for the corresponding deferred element)
+
+function isSupported()
+{
+    // The whole script currently assumes SaxonCE is used to provide XSLT
+    // which means it is always supported => return true
+    //
+    // Could consider testing for native XSLT (using simple
+    // hardwired XML + XSL Tranform?) and using that
+    // if successful (presumably faster)
+    //
+    // Or even try to transform the given transform natively,
+    // and store 'isNativelySupported' as true/false in cookie
+    // accordingly
+
+    return true; 
+}
+
+function notSupportedCookie() {
+	document.cookie = 'supportsXSLT=false; expires=0; path=/';
+}
 
 function notSupported() {
-    // Set not supported cookie here
-    document.cookie = 'supportsXSLT=false; expires=0; path=/';
-    // Fall back to server version
-    var location = window.location.search.substring(1);
-    if(location == '') {
-	// Start with a question mark
-	location = window.location + "?o=server";
-    } else {
-	// Start with an ampersand
-	location = window.location + "&o=server";
-    }		
-    window.location = location;
-}
-
-/*
-$(document).ready(function() {
-    console.log("Client-side XSLT: Document is ready");
+    notSupportedCookie();
+
+    // knock out 'client-' part from URL
+    var location_href = window.location.href;
+
+    var new_location_href = location_href.replace(/^(.*)\/(client-)?([^\?\#]+)(.*)$/,"$1/$3$4");
+    console.log("Client-side XSLT not supported.  Redirecting to: " + new_location_href);
     
-    if (isSupported()) {		
-	transform(false);
-    }
-    else {
-	notSupported();
-    }
-});
-*/
+    window.location = new_location_href;
+}
+
+function getUrlParameterHashmap() {
+    var sPageURL = decodeURIComponent(window.location.search.substring(1));
+    var sURLVariables = sPageURL.split('&');
+
+    var paramHashmap = {};
+    
+    for (var i = 0; i < sURLVariables.length; i++) {
+        var sParameterName = sURLVariables[i].split('=');
+	paramHashmap[sParameterName[0]] = sParameterName[1];
+    }
+
+    return paramHashmap;
+}
+
+// From:
+//   http://stackoverflow.com/questions/298750/how-do-i-select-text-nodes-with-jquery
+function getTextNodesIn(node, includeWhitespaceNodes) {
+    var textNodes = [], nonWhitespaceMatcher = /\S/;
+
+    function getTextNodes(node) {
+        if (node.nodeType == 3) {
+            if (includeWhitespaceNodes || nonWhitespaceMatcher.test(node.nodeValue)) {
+                textNodes.push(node);
+            }
+        }
+	else {
+            for (var i = 0, len = node.childNodes.length; i < len; ++i) {
+                getTextNodes(node.childNodes[i]);
+            }
+        }
+    }
+
+    getTextNodes(node);
+    return textNodes;
+}
+
+function applyDisableEscapingToTextNodes(elem)
+{
+    var textNodes = getTextNodesIn(elem,false); // ignore whitespace
+
+    for (var i=textNodes.length-1; i>=0; i--) {
+	var text_node = textNodes[i];
+	var text = text_node.nodeValue;
+	var html = $.parseHTML(text);
+	$(text_node).replaceWith(html);	
+    }
+}
+
 
 
@@ -56,4 +98,6 @@
 
     try {
+	var paramHashmap = getUrlParameterHashmap();
+	
 	var rooturl = window.location.pathname;
 	var queryStr = window.location.search.substring(1);
@@ -63,25 +107,23 @@
 	}
 	queryStr += "o=clientside";
-	    
-	console.log("*** rooturl = " + rooturl);
-	console.log("*** queryStr = " + queryStr);
+
+	paramHashmap['o']="clientside";
+	
+	//console.log("*** rooturl = " + rooturl);
+	//console.log("*** queryStr = " + queryStr);
 
 	var skindoc = "";
 	var xmldoc = "";	
 	
-	$.get(rooturl + "?" + queryStr, function(data) {
-
-	    //dataStr = dataStr.replace(/gs3:id/g, "gs3id");
-		
-	    //data = parseFromString(dataStr, "text/xml");
-		
+	$.get(rooturl, paramHashmap, function(data) {
+
 	    var toplevel_children = $(data).children().eq(0).children();
 	    var skindoc = toplevel_children[0];
 	    var xmldoc = toplevel_children[1];
-	    
-		
-	    var site_name = $('xsltparams>param[name=site_name]', xmldoc).text();
+	    	
 	    var library_name = $('xsltparams>param[name=library_name]', xmldoc).text();
 	    var interface_name = $('xsltparams>param[name=interface_name]', xmldoc).text();
+	    var site_name = $('xsltparams>param[name=site_name]', xmldoc).text();
+	    var use_client_side_xslt = $('xsltparams>param[name=use_client_side_xslt]', xmldoc).text();
 	    
 	    //// Convert temporarily to text here		
@@ -89,248 +131,92 @@
 	    xmldoc = convertToString(xmldoc);
 		
-	    //// This could just be done on the server (later)
-	    ////data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" /*+ "<?xml-stylesheet type=\"text/xsl\" href=\"" + skinurl + "\"?>\r\n"*/ + data;
-	    //// replace all!
-	    //// Be careful with regex syntax and the use of special characters!
-	    //skindoc = skindoc.replace(/util\:exists\(\$meta, ''\)/g, "$meta!=''"); // For now - use regex instead
-	    //skindoc = skindoc.replace(/%3A/g, ":"); // undo colon escaping
-
-	    //skindoc = skindoc.replace(/util\:.+?\(\.*?\)/g, "true");
-
 	    skindoc = skindoc.replace(/<xslt:stylesheet\s+/,"<xslt:stylesheet xmlns:ixsl=\"http://saxonica.com/ns/interactiveXSLT\" xmlns:js=\"http://saxonica.com/ns/globalJS\" ");
-	    //extension-element-prefixes="ixsl" exclude-result-prefixes="xs xd d js"
 	    skindoc = skindoc.replace(/extension-element-prefixes="(.*?)"/,"extension-element-prefixes=\"$1 ixsl\"");
-	    //skindoc = skindoc.replace(/exclude-result-prefixes="(.*?)"/,"exclude-result-prefixes=\"$1 js\"");
-	    
 	    
 	    skindoc = skindoc.replace(/util\:exists\(\$meta, ''\)/g, "$meta!=''"); // For now - use regex instead
-	    skindoc = skindoc.replace(/util:replace\((.*?)\)/g, "replace($1)"); // exists in XSLT 2.0
-	    skindoc = skindoc.replace(/util:storeString\((.+?),(.+)?\)/g, "$1=$2");
-	    skindoc = skindoc.replace(/util:getString\((.+?)\)/g, "$1");
-	    skindoc = skindoc.replace(/util:getDetailFromDate\((.+?),.+?,.+?\)/g, "$1");
-
-	    //skindoc = skindoc.replace(/util:getNumberedItem\(([^,]+),([^,]+)\)$/,"js:getNumberedItem($1,$2)"); // never tested
-	    
-	    //skindoc = skindoc.replace(/\{\{/,"\\{");
-	    //skindoc = skindoc.replace(/\}\}/,"\\}");
-	    //skindoc = skindoc.replace(/"js:[^"]+"/g, "js:console.log('foo')");
-	    
+	    skindoc = skindoc.replace(/util:replace\((.*?)\)/g, "replace($1)"); // 'replace()' exists in XSLT 2.0
+	    skindoc = skindoc.replace(/util:storeString\(\s*'(.+?)'\s*,\s*'(.*?)'\s*\)/g, "js:storeString(string($1),string($2))");
+	    skindoc = skindoc.replace(/util:getString\('(.+?)'\)/g, "js:getString(string($1))");
+	    skindoc = skindoc.replace(/util:escapeNewLinesAndQuotes\(([^)]+)\)/g, "js:escapeNewLinesAndQuotes(string($1))");
+	    //attr_val = attr_val.replaceAll("util:escapeNewLinesAndQuotes\\(\\s*(.+?)\\s*\\)","$1");
+	    
+	    skindoc = skindoc.replace(/util:getDetailFromDate\((.+?),.+?,.+?\)/g, "'getDetailFromDate $1'"); // ****
+
+	    skindoc = skindoc.replace(/util:oidIsMatchOrParent\(([^,]+),([^)]+)\)/g,"js:oidIsMatchOrParent(string($1),string($2))");
+	    skindoc = skindoc.replace(/util:hashToDepthClass\(([^)]+)\)/g,"js:hashToDepthClass(string($1))");
+	    skindoc = skindoc.replace(/util:hashToSectionId\(([^)]+)\)/g,"js:hashToSectionId(string($1))");
+
+	    skindoc = skindoc.replace(/java:.*?getNumberedItem\(([^,]+),([^)]+)\)/g,"js:getNumberedItem(string($1),string($2))"); 
+
 	    // Convert to XML
 	    xmldoc = parseFromString(xmldoc, "text/xml");			
 	    skindoc = parseFromString(skindoc, "text/xml");
-	    
+
+	    console.log("Applying client-side XSLT");	    
 	    var output = '';
-		/*
-	    var params = {  'library_name': library_name, 'interface_name': interface_name };
-	    var proc = Saxon.run( {
- 		stylesheet:   skindoc,
- 		source:       xmldoc,
-		parameters: params
-	    });
-*/
-		
-      /*
-		var xml = Saxon.requestXML("http://localhost:8383/greenstone3/mozarts-library/collection/digital-music-stand/page/about?o=xml");
-		var xsl = Saxon.requestXML("http://localhost:8383/greenstone3/mozarts-library/collection/digital-music-stand/page/about?o=skinandlibdocfinal");
-*/
-	    var proc = Saxon.newXSLT20Processor(skindoc);
-		
+
+	    //var proc = Saxon.newXSLT20Processor(skindoc);
+	    var proc = Saxon.newXSLT20Processor();
+	    
 	    proc.setParameter(null, 'library_name', library_name);
 	    proc.setParameter(null, 'interface_name', interface_name);
-	    proc.setParameter(null, 'site_name', site_name);		
-
+	    proc.setParameter(null, 'site_name', site_name);
+	    proc.setParameter(null, 'use_client_side_xslt', use_client_side_xslt);		
+
+	    // Consider making above XSLT20Porcessor constructor take no  arguments,
+	    // and specify transform through importStylesheet(), this combination
+	    // of code is more consistent with other XSLT systems
+	    //
+	    proc.importStylesheet(skindoc);
+	    
 	    result = proc.transformToDocument(xmldoc);
+	    //result = proc.transformToFragment(xmldoc);
+
+	    //proc.updateHTMLDocument(xmldoc);
+	    //return;
+
+	    var excerptid = paramHashmap['excerptid'];
+	    if (excerptid) {
+		result = result.getElementById(excerptid);
+	    }
+	    
+	    applyDisableEscapingToTextNodes(result);
 	    xmlSer = new XMLSerializer();
 	    output = xmlSer.serializeToString(result);
-
-	    var doc = document.open();
-	    doc.write(output);
-	    doc.close();
-
-	    document.cookie = 'supportsXSLT=true; expires=0; path=/';
-
-			
-	    
-	}, 'xml');
-    }
-    catch (e) {
-	if(trial) {
-	    notSupportedCookie();
-	}
-	else {
-	    notSupported();
-	}
-    }
-}
-
-      
-
-
-function transform(trial) {
-
-    //alert("transform(): trial = " + trial);
-    
-    try {
-	var rooturl = window.location.pathname;
-	var queryStr = window.location.search.substring(1);
-	queryStr = queryStr.replace(/o=.*?(&|$)/g,"");
-	if (queryStr != '') {
-	    queryStr += "&";
-	}
-	queryStr += "o=clientside";
-	    
-	console.log("*** rooturl = " + rooturl);
-	console.log("*** queryStr = " + queryStr);
-
-	var skindoc = "";
-	var xmldoc = "";	
-	
-	    $.get(rooturl + "?" + queryStr, function(dataStr) {
- 		//dataStr = dataStr.trim();
-		dataStr = dataStr.replace(/gs3:id/g, "gs3id");
-		//dataStr = dataStr.replace(/&/g,"&amp;");
-		//dataStr = dataStr.replace(/&amp;quot;/g,"&quot;");
-		//dataStr = dataStr.replace(/&amp;lt;/g,"&lt;");
-		//dataStr = dataStr.replace(/&amp;gt;/g,"&gt;");
-		//dataStr = dataStr.replace(/&amp;amp;/g,"&amp;");
-		
-		data = parseFromString(dataStr, "text/xml");
-		
-		var toplevel_children = $(data).children().eq(0).children();
-		var skindoc = toplevel_children[0];
-		var xmldoc = toplevel_children[1];
-
-		
-	        //gsurl = $($(xmldoc).find("metadata[name=siteURL]")[0]).text();
-	        //gsurl = $(xmldoc).find("pageRequest").attr("baseURL");
-	        var site_name = $('xsltparams>param[name=site_name]', xmldoc).text();
-	        var library_name = $('xsltparams>param[name=library_name]', xmldoc).text();
-	        var interface_name = $('xsltparams>param[name=interface_name]', xmldoc).text();
-
- 	        //gsurl = $(xmldoc).find("pageRequest").attr("baseURL") + site_name;
-		//gsweb = new RegExp($($(xmldoc).find("param[name=filepath]")[0]).text().replace(/\\/g, "\\\\"), "g");
-			    
-		//// Convert temporarily to text here		
-		skindoc = convertToString(skindoc);
-		xmldoc = convertToString(xmldoc);
-		
-		//// This could just be done on the server (later)
-		////data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" /*+ "<?xml-stylesheet type=\"text/xsl\" href=\"" + skinurl + "\"?>\r\n"*/ + data;
-		//// replace all!
-		//// Be careful with regex syntax and the use of special characters!
-		//skindoc = skindoc.replace(/util\:exists\(\$meta, ''\)/g, "$meta!=''"); // For now - use regex instead
-		//skindoc = skindoc.replace(/%3A/g, ":"); // undo colon escaping
-
-		//skindoc = skindoc.replace(/util\:.+?\(\.*?\)/g, "true");
-
-		skindoc = skindoc.replace(/util\:exists\(\$meta, ''\)/g, "$meta!=''"); // For now - use regex instead
-		skindoc = skindoc.replace(/util:storeString\((.+?),(.+)?\)/g, "$1=$2");
-		skindoc = skindoc.replace(/util:getString\((.+?)\)/g, "$1");
-		skindoc = skindoc.replace(/util:getDetailFromDate\((.+?),.+?,.+?\)/g, "$1");
-		
-		
-		// Convert to XML
-		xmldoc = parseFromString(xmldoc, "text/xml");			
-		skindoc = parseFromString(skindoc, "text/xml");
-			
-		var output = '';
-
-		// And post-process...			
-		if(window.ActiveXObject) {
-		    // IE
-		    var procFactory = new ActiveXObject("MSXML2.XSLTemplate");
-		    procFactory.stylesheet = skindoc;
-		    var proc = procFactory.createProcessor();
-		    proc.input = xmldoc;
-		    
-		    proc.addParameter('library_name', library_name);
-		    proc.addParameter('interface_name', interface_name);
-		    
-		    proc.transform();
-		    output = proc.output;
-		}
-		else {			
-		    // Firefox, Chrome ...
-		    xsltProc = new XSLTProcessor();
-		    // The leading slash is oh-so important here
-		    xsltProc.setParameter(null, 'library_name', library_name);
-		    xsltProc.setParameter(null, 'interface_name', interface_name);
-		    try {
-			xsltProc.importStylesheet(skindoc);
-		    }
-		    catch(e) {
-			alert("Malformed importStyle sheet:" + e.message + "\n======\nSee web browser console for more details");
-			// Look for util: of java:
-			if (debug) {
-			    console.error("Stylesheet:\n" + skindoc.documentElement.outerHTML);
-			}
-		    }
-		    
-		    //
-		    try {
-			result = xsltProc.transformToDocument(xmldoc);
-			xmlSer = new XMLSerializer();
-			output = xmlSer.serializeToString(result);
-		    }
-		    catch(e) {
-			alert("XSL Transform failed:" + e.message + "\n======\nSee web browser console for more details");
-			console.error("doc:\n" + convertToString(xmldoc) + "\n========\nStylesheet:\n" + convertToString(skindoc));
-		    }
-		}
-			
+	    
+	    if (excerptid) {
+		var callback = paramHashmap['callback'];
+		parent[callback](output);		
+	    }
+	    else {
 		var doc = document.open();
 		doc.write(output);
 		doc.close();
+
+		// ****
+		document.cookie = 'supportsXSLT=true; expires=0; path=/';
+	    }
 			
-		document.cookie = 'supportsXSLT=true; expires=0; path=/';
-		
-	    }, 'text');
-	}
+	    
+	}, 'xml');
+
+    }
     catch (e) {
-	if(trial) {
-	    notSupportedCookie();
-	}
-	else {
-	    notSupported();
-	}
-    }
-}
-
-function notSupportedCookie() {
-	document.cookie = 'supportsXSLT=false; expires=0; path=/';
-}
-
-
-// Method equivalent to PHP's in_array method
-function contains(array, value) {
-
-	for(var val in array)
-		if(array[val] == value)
-			return true;
-	
-	return false;
-}
-
-// Method equivalent to PHP's own
-function print_r(arr) {
-	
-	var result = "";
-	
-	for(var a in arr) {
-		result += a + " => " + arr[a] + "\r\n";
-	}
-	
-	return result;
-}
+	alert("Error occured:" + e.message + "\n======\nSee web browser console for more details");
+	notSupported();
+	
+    }
+}
+
 
 function convertToString(content) {
-	try {
-		// If this fails, it's another indication that the browser doesn't have the support we need
-		if(typeof XMLSerializer != 'undefined') {
-			return (new XMLSerializer()).serializeToString(content);
-		} else {
-			return content.xml;
-		}		
-	}
+    try {
+	// If this fails, it's another indication that the browser doesn't have the support we need
+	if(typeof XMLSerializer != 'undefined') {
+	    return (new XMLSerializer()).serializeToString(content);
+	} else {
+	    return content.xml;
+	}		
+    }
     catch (e) {
 	notSupported();
@@ -361,29 +247,4 @@
 }
 
-function isSupported() {
-
-    return true;
-    // change test below to simple XML + XSL Transform
-    
-    /*
-	// Are cookies enabled?
-	if(navigator.cookieEnabled && typeof navigator.cookieEnabled != 'undefined') {	
-		// Is there a cookie?
-		if(document.cookie.indexOf('supportsXSLT=') > -1) {
-			// Cookie exists - don't try the transformation, as the server will
-			// read the cookie and determine which version to serve up.
-			// If it happens to be client-side, allow transformation to proceed
-			return (document.cookie.indexOf('supportsXSLT=true') > -1);
-		} else {
-			// Cookie doesn't exist - test!
-			transform(true);
-			applyText(true);
-			return (document.cookie.indexOf('supportsXSLT=true') > -1);
-		}
-	} else {
-		return false;
-	}
-*/
-}
 
 /* Simulating web storage for browsers that don't support it */
