source: main/trunk/greenstone3/web/client-side-xslt.js@ 36943

Last change on this file since 36943 was 35896, checked in by cstephen, 2 years ago

Upgrade web root jQuery and fix clientside XSLT processing.

  • Property svn:executable set to *
File size: 9.8 KB
Line 
1/*
2
3Greenstone 3 'Client-side transformer'
4Performs client-side transformations of XSLT, using HTML5 local storage (simulated if the browser doesn't support it).
5
6Currently only supports Firefox 3 and greater, since it's the only browser (at the time of writing) that can do this properly.
7
8@author Steven McTainsh
9@author David Bainbridge
10@date 2011-2016
11
12*/
13
14function isSupported()
15{
16 // The whole script currently assumes SaxonCE is used to provide XSLT
17 // which means it is always supported => return true
18 //
19 // Could consider testing for native XSLT (using simple
20 // hardwired XML + XSL Tranform?) and using that
21 // if successful (presumably faster)
22 //
23 // Or even try to transform the given transform natively,
24 // and store 'isNativelySupported' as true/false in cookie
25 // accordingly
26
27 return true;
28}
29
30function notSupportedCookie() {
31 document.cookie = 'supportsXSLT=false; expires=0; path=/';
32}
33
34function notSupported() {
35 notSupportedCookie();
36
37 // Remove the client-side XSLT output parameter
38 var location_href = window.location.href.replace("o=xsltclient", "");
39
40 // location_href = location_href.replace(/^(.*)\/(client-)?([^\?\#]+)(.*)$/,"$1/$3$4");
41 console.log("Client-side XSLT not supported. Redirecting to: " + location_href);
42
43 window.location.href = location_href;
44}
45
46function getUrlParameterHashmap() {
47 var sPageURL = decodeURIComponent(window.location.search.substring(1));
48 var sURLVariables = sPageURL.split('&');
49
50 var paramHashmap = {};
51
52 for (var i = 0; i < sURLVariables.length; i++) {
53 var sParameterName = sURLVariables[i].split('=');
54 paramHashmap[sParameterName[0]] = sParameterName[1];
55 }
56
57 return paramHashmap;
58}
59
60// From:
61// http://stackoverflow.com/questions/298750/how-do-i-select-text-nodes-with-jquery
62function getTextNodesIn(node, includeWhitespaceNodes) {
63 var textNodes = [], nonWhitespaceMatcher = /\S/;
64
65 function getTextNodes(node) {
66 if (node.nodeType == 3) {
67 if (includeWhitespaceNodes || nonWhitespaceMatcher.test(node.nodeValue)) {
68 textNodes.push(node);
69 }
70 }
71 else {
72 for (var i = 0, len = node.childNodes.length; i < len; ++i) {
73 getTextNodes(node.childNodes[i]);
74 }
75 }
76 }
77
78 getTextNodes(node);
79 return textNodes;
80}
81
82function applyDisableEscapingToTextNodes(elem)
83{
84 var textNodes = getTextNodesIn(elem,false); // ignore whitespace
85
86 for (var i=textNodes.length-1; i>=0; i--) {
87 var text_node = textNodes[i];
88 var text = text_node.nodeValue;
89 var html = $.parseHTML(text);
90 $(text_node).replaceWith(html);
91 }
92}
93
94var onSaxonLoad = function() {
95 try {
96 var paramHashmap = getUrlParameterHashmap();
97 var rooturl = window.location.pathname;
98 var queryStr = window.location.search.substring(1);
99
100 queryStr = queryStr.replace(/o=.*?(&|$)/g,"");
101 if (queryStr != '') {
102 queryStr += "&";
103 }
104
105 queryStr += "o=clientside";
106 paramHashmap['o']="clientside";
107
108 //console.log("*** rooturl = " + rooturl);
109 //console.log("*** queryStr = " + queryStr);
110
111 $.get(rooturl, paramHashmap, function(data) {
112 var toplevel_children = $(data).children().eq(0).children();
113 var skindoc = toplevel_children[0];
114 var xmldoc = toplevel_children[1];
115
116 var library_name = $('xsltparams>param[name=library_name]', xmldoc).text();
117 var interface_name = $('xsltparams>param[name=interface_name]', xmldoc).text();
118 var site_name = $('xsltparams>param[name=site_name]', xmldoc).text();
119 var use_client_side_xslt = $('xsltparams>param[name=use_client_side_xslt]', xmldoc).text();
120
121 // Convert temporarily to text so we can easily replace invalid values
122 var xmlSerializer = new XMLSerializer();
123 skindoc = xmlSerializer.serializeToString(skindoc);
124 xmldoc = xmlSerializer.serializeToString(xmldoc);
125
126 skindoc = skindoc.replace(/<xslt:stylesheet\s+/,"<xslt:stylesheet xmlns:ixsl=\"http://saxonica.com/ns/interactiveXSLT\" xmlns:js=\"http://saxonica.com/ns/globalJS\" ");
127 skindoc = skindoc.replace(/extension-element-prefixes="(.*?)"/,"extension-element-prefixes=\"$1 ixsl\"");
128
129 skindoc = skindoc.replace(/util\:exists\(\$meta, ''\)/g, "$meta!=''"); // For now - use regex instead
130 skindoc = skindoc.replace(/util:replace\((.*?)\)/g, "replace($1)"); // 'replace()' exists in XSLT 2.0
131 skindoc = skindoc.replace(/util:storeString\(\s*'(.+?)'\s*,\s*'(.*?)'\s*\)/g, "js:storeString(string($1),string($2))");
132 skindoc = skindoc.replace(/util:getString\('(.+?)'\)/g, "js:getString(string($1))");
133 skindoc = skindoc.replace(/util:escapeNewLinesAndQuotes\(([^)]+)\)/g, "js:escapeNewLinesAndQuotes(string($1))");
134 skindoc = skindoc.replace(/util:escapeNewLinesQuotesAngleBracketsForJSString\(([^)]+)\)/g, "js:escapeNewLinesQuotesAngleBracketsForJSString(string($1))");
135
136 skindoc = skindoc.replace(/util:getDetailFromDate\((.+?),.+?,.+?\)/g, "'getDetailFromDate $1'"); // ****
137
138 skindoc = skindoc.replace(/util:oidIsMatchOrParent\(([^,]+),([^)]+)\)/g,"js:oidIsMatchOrParent(string($1),string($2))");
139 skindoc = skindoc.replace(/util:hashToDepthClass\(([^)]+)\)/g,"js:hashToDepthClass(string($1))");
140 skindoc = skindoc.replace(/util:hashToSectionId\(([^)]+)\)/g,"js:hashToSectionId(string($1))");
141
142 skindoc = skindoc.replace(/java:.*?getNumberedItem\(([^,]+),([^)]+)\)/g, "js:getNumberedItem(string($1),string($2))");
143
144 // The XMLSerializer converts some escaping, which we undo here
145 // so that Saxon-CE can successfully parse the document.
146 skindoc = skindoc.replace("\"'''\"", "\"'&amp;apos;'\"");
147
148 // Convert back to XML
149 xmldoc = parseFromString(xmldoc, "text/xml");
150 skindoc = parseFromString(skindoc, "text/xml");
151
152 var proc = Saxon.newXSLT20Processor();
153 proc.setParameter(null, 'library_name', library_name);
154 proc.setParameter(null, 'interface_name', interface_name);
155 proc.setParameter(null, 'site_name', site_name);
156 proc.setParameter(null, 'use_client_side_xslt', use_client_side_xslt);
157
158 console.log("Applying client-side XSLT");
159 proc.importStylesheet(skindoc);
160 var result = proc.transformToDocument(xmldoc);
161
162 var excerptid = paramHashmap['excerptid'];
163 if (excerptid) {
164 result = result.getElementById(excerptid);
165 }
166
167 applyDisableEscapingToTextNodes(result);
168 var DOMString = xmlSerializer.serializeToString(result);
169
170 if (excerptid) {
171 var callback = paramHashmap['callback'];
172 parent[callback](DOMString);
173 }
174 else
175 {
176 /**
177 * NOTE (Carl - cstephen):
178 * This is not an ideal solution. document.open() is a slow option
179 * and is limited in certain browsers, wherein scripts are prevented
180 * from running under certain networking conditions.
181 *
182 * Ideally we'd directly replace the document with the constructed DOM.
183 * However, despite the replacement being successful, it wasn't being
184 * parsed successfully at the time of writing.
185 *
186 * If you'd like to attempt this again, the following is a good starting point:
187 * var processor = Saxon.newXSLT20Processor();
188 * var documentFragment = processor.transformToFragment(xmldoc);
189 * document.replaceChild(documentFragment, document.documentElement);
190 */
191
192 var newDocument = document.open("text/html");
193 newDocument.write(DOMString);
194 newDocument.close();
195
196 document.cookie = 'supportsXSLT=true; expires=0; path=/';
197 }
198 }, 'xml');
199 }
200 catch (e) {
201 alert("Error occured:" + e.message + "\n======\nSee web browser console for more details");
202 notSupported();
203 }
204}
205
206function parseFromString(content, contentType) {
207 try {
208 var retobj;
209
210 if (typeof window.DOMParser != 'undefined') {
211 // Firefox, Chrome
212 retobj = (new DOMParser()).parseFromString(content, contentType);
213 }
214 else {
215 // IE
216 var retobj = new ActiveXObject("Microsoft.XMLDOM");
217 retobj.async = "false";
218 retobj.loadXML(content);
219 }
220
221 return retobj;
222 }
223 catch(e) {
224 var obj = new ActiveXObject('MSXML.DomDocument');
225 obj.async = false;
226 obj.loadXML(content);
227
228 return obj;
229 }
230}
231
232
233/* Simulating web storage for browsers that don't support it */
234/* Credit: http://www.thewojogroup.com/2010/01/simulating-html5-local-storage/ */
235(function(){var k=this;if(!k.localStorage&&navigator.cookieEnabled){var x="storageData_",y="++",z="::",l=function(a,c,b){var e=new Date;e.setTime(e.getTime()+b);b="; expires="+e.toGMTString();document.cookie=a+"="+c+b+"; path=/"},h=function(a){a=a+"=";for(var c=document.cookie.split(";"),b=0,e=c.length;b<e;b++){for(var d=c[b];d.charAt(0)==" ";)d=d.substring(1,d.length);if(d.indexOf(a)===0)return d.substring(a.length,d.length)}return null},m=function(a){l(a,"",-1)},i=function(){for(var a="",c=0;h(y+c)!==null;)a+=h(y+
236c++);return a==""?[]:a.split(y)},n=function(a){for(var c=Math.ceil(a.length/4E3),b=0;b<c||h(y+b)!==null;){b<c?l(y+b,a.substr(b*4E3,(b+1)*4E3>a.length?a.length-b*4E3:4E3),2592E3):m(y+b);b++}},f=k.localStorage={length:0,setItem:function(a,c){var b=i(),e=0,d=b.length,g=false;for(e=0;e<d;e++){var j=b[e].split(z);if(j[0]==a){j[1]=c;g=true;b[e]=j.join(z);e=d}}if(!g){b.push(a+z+c.replace(/::/g,": :").replace(/\+\+/g,"+ +"));this.a.push(a);this.length++}n(b.join(y))},
237getItem:function(a){var c=i(),b=0,e=c.length;for(b=0;b<e;b++){var d=c[b].split(z);if(d[0]==a&&d[1])return d[1]}return null},removeItem:function(a){var c=i(),b=0,e=c.length,d=false,g=[];for(b=0;b<e;b++)if(c[b].split(z)[0]!=a)g.push(c[b]);else d=true;if(d){n(g.join(y));o()}},clear:function(){for(var a=0;h(y+a)!==null;)m(y+a++);this.a=[];this.length=0},key:function(a){return a<this.length?this.a[a]:null},a:[]},o=function(){f.a=i();for(var a=0;f.a[a];)f.a[a]=f.a[a++].split(z)[0];
238f.length=f.a.length};o()}})();
Note: See TracBrowser for help on using the repository browser.