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

Last change on this file since 33098 was 30480, checked in by davidb, 8 years ago

Next iteration in the development of this file. There are still some DL pages that need some porting to work with the new system, but the majority of them now work, and it tends to be ones that make use of AJAX calls the need addressing.

  • Property svn:executable set to *
File size: 9.5 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
14
15function isSupported()
16{
17 // The whole script currently assumes SaxonCE is used to provide XSLT
18 // which means it is always supported => return true
19 //
20 // Could consider testing for native XSLT (using simple
21 // hardwired XML + XSL Tranform?) and using that
22 // if successful (presumably faster)
23 //
24 // Or even try to transform the given transform natively,
25 // and store 'isNativelySupported' as true/false in cookie
26 // accordingly
27
28 return true;
29}
30
31function notSupportedCookie() {
32 document.cookie = 'supportsXSLT=false; expires=0; path=/';
33}
34
35function notSupported() {
36 notSupportedCookie();
37
38 // knock out 'client-' part from URL
39 var location_href = window.location.href;
40
41 var new_location_href = location_href.replace(/^(.*)\/(client-)?([^\?\#]+)(.*)$/,"$1/$3$4");
42 console.log("Client-side XSLT not supported. Redirecting to: " + new_location_href);
43
44 window.location = new_location_href;
45}
46
47function getUrlParameterHashmap() {
48 var sPageURL = decodeURIComponent(window.location.search.substring(1));
49 var sURLVariables = sPageURL.split('&');
50
51 var paramHashmap = {};
52
53 for (var i = 0; i < sURLVariables.length; i++) {
54 var sParameterName = sURLVariables[i].split('=');
55 paramHashmap[sParameterName[0]] = sParameterName[1];
56 }
57
58 return paramHashmap;
59}
60
61// From:
62// http://stackoverflow.com/questions/298750/how-do-i-select-text-nodes-with-jquery
63function getTextNodesIn(node, includeWhitespaceNodes) {
64 var textNodes = [], nonWhitespaceMatcher = /\S/;
65
66 function getTextNodes(node) {
67 if (node.nodeType == 3) {
68 if (includeWhitespaceNodes || nonWhitespaceMatcher.test(node.nodeValue)) {
69 textNodes.push(node);
70 }
71 }
72 else {
73 for (var i = 0, len = node.childNodes.length; i < len; ++i) {
74 getTextNodes(node.childNodes[i]);
75 }
76 }
77 }
78
79 getTextNodes(node);
80 return textNodes;
81}
82
83function applyDisableEscapingToTextNodes(elem)
84{
85 var textNodes = getTextNodesIn(elem,false); // ignore whitespace
86
87 for (var i=textNodes.length-1; i>=0; i--) {
88 var text_node = textNodes[i];
89 var text = text_node.nodeValue;
90 var html = $.parseHTML(text);
91 $(text_node).replaceWith(html);
92 }
93}
94
95
96
97var onSaxonLoad = function() {
98
99 try {
100 var paramHashmap = getUrlParameterHashmap();
101
102 var rooturl = window.location.pathname;
103 var queryStr = window.location.search.substring(1);
104 queryStr = queryStr.replace(/o=.*?(&|$)/g,"");
105 if (queryStr != '') {
106 queryStr += "&";
107 }
108 queryStr += "o=clientside";
109
110 paramHashmap['o']="clientside";
111
112 //console.log("*** rooturl = " + rooturl);
113 //console.log("*** queryStr = " + queryStr);
114
115 var skindoc = "";
116 var xmldoc = "";
117
118 $.get(rooturl, paramHashmap, function(data) {
119
120 var toplevel_children = $(data).children().eq(0).children();
121 var skindoc = toplevel_children[0];
122 var xmldoc = toplevel_children[1];
123
124 var library_name = $('xsltparams>param[name=library_name]', xmldoc).text();
125 var interface_name = $('xsltparams>param[name=interface_name]', xmldoc).text();
126 var site_name = $('xsltparams>param[name=site_name]', xmldoc).text();
127 var use_client_side_xslt = $('xsltparams>param[name=use_client_side_xslt]', xmldoc).text();
128
129 //// Convert temporarily to text here
130 skindoc = convertToString(skindoc);
131 xmldoc = convertToString(xmldoc);
132
133 skindoc = skindoc.replace(/<xslt:stylesheet\s+/,"<xslt:stylesheet xmlns:ixsl=\"http://saxonica.com/ns/interactiveXSLT\" xmlns:js=\"http://saxonica.com/ns/globalJS\" ");
134 skindoc = skindoc.replace(/extension-element-prefixes="(.*?)"/,"extension-element-prefixes=\"$1 ixsl\"");
135
136 skindoc = skindoc.replace(/util\:exists\(\$meta, ''\)/g, "$meta!=''"); // For now - use regex instead
137 skindoc = skindoc.replace(/util:replace\((.*?)\)/g, "replace($1)"); // 'replace()' exists in XSLT 2.0
138 skindoc = skindoc.replace(/util:storeString\(\s*'(.+?)'\s*,\s*'(.*?)'\s*\)/g, "js:storeString(string($1),string($2))");
139 skindoc = skindoc.replace(/util:getString\('(.+?)'\)/g, "js:getString(string($1))");
140 skindoc = skindoc.replace(/util:escapeNewLinesAndQuotes\(([^)]+)\)/g, "js:escapeNewLinesAndQuotes(string($1))");
141 //attr_val = attr_val.replaceAll("util:escapeNewLinesAndQuotes\\(\\s*(.+?)\\s*\\)","$1");
142
143 skindoc = skindoc.replace(/util:getDetailFromDate\((.+?),.+?,.+?\)/g, "'getDetailFromDate $1'"); // ****
144
145 skindoc = skindoc.replace(/util:oidIsMatchOrParent\(([^,]+),([^)]+)\)/g,"js:oidIsMatchOrParent(string($1),string($2))");
146 skindoc = skindoc.replace(/util:hashToDepthClass\(([^)]+)\)/g,"js:hashToDepthClass(string($1))");
147 skindoc = skindoc.replace(/util:hashToSectionId\(([^)]+)\)/g,"js:hashToSectionId(string($1))");
148
149 skindoc = skindoc.replace(/java:.*?getNumberedItem\(([^,]+),([^)]+)\)/g,"js:getNumberedItem(string($1),string($2))");
150
151 // Convert to XML
152 xmldoc = parseFromString(xmldoc, "text/xml");
153 skindoc = parseFromString(skindoc, "text/xml");
154
155 console.log("Applying client-side XSLT");
156 var output = '';
157
158 //var proc = Saxon.newXSLT20Processor(skindoc);
159 var proc = Saxon.newXSLT20Processor();
160
161 proc.setParameter(null, 'library_name', library_name);
162 proc.setParameter(null, 'interface_name', interface_name);
163 proc.setParameter(null, 'site_name', site_name);
164 proc.setParameter(null, 'use_client_side_xslt', use_client_side_xslt);
165
166 // Consider making above XSLT20Porcessor constructor take no arguments,
167 // and specify transform through importStylesheet(), this combination
168 // of code is more consistent with other XSLT systems
169 //
170 proc.importStylesheet(skindoc);
171
172 result = proc.transformToDocument(xmldoc);
173 //result = proc.transformToFragment(xmldoc);
174
175 //proc.updateHTMLDocument(xmldoc);
176 //return;
177
178 var excerptid = paramHashmap['excerptid'];
179 if (excerptid) {
180 result = result.getElementById(excerptid);
181 }
182
183 applyDisableEscapingToTextNodes(result);
184 xmlSer = new XMLSerializer();
185 output = xmlSer.serializeToString(result);
186
187 if (excerptid) {
188 var callback = paramHashmap['callback'];
189 parent[callback](output);
190 }
191 else {
192 var doc = document.open();
193 doc.write(output);
194 doc.close();
195
196 // ****
197 document.cookie = 'supportsXSLT=true; expires=0; path=/';
198 }
199
200
201 }, 'xml');
202
203 }
204 catch (e) {
205 alert("Error occured:" + e.message + "\n======\nSee web browser console for more details");
206 notSupported();
207
208 }
209}
210
211
212function convertToString(content) {
213 try {
214 // If this fails, it's another indication that the browser doesn't have the support we need
215 if(typeof XMLSerializer != 'undefined') {
216 return (new XMLSerializer()).serializeToString(content);
217 } else {
218 return content.xml;
219 }
220 }
221 catch (e) {
222 notSupported();
223 }
224}
225
226function parseFromString(content, contentType) {
227 try {
228 var retobj;
229
230 if(typeof window.DOMParser != 'undefined') {
231 // Firefox, Chrome
232 retobj = (new DOMParser()).parseFromString(content, contentType);
233 } else {
234 // IE
235 var retobj = new ActiveXObject("Microsoft.XMLDOM");
236 retobj.async = "false";
237 retobj.loadXML(content);
238 }
239
240 return retobj;
241 }
242 catch(e) {
243 var obj = new ActiveXObject('MSXML.DomDocument');
244 obj.async = false; obj.loadXML(content);
245 return obj;
246 }
247}
248
249
250/* Simulating web storage for browsers that don't support it */
251/* Credit: http://www.thewojogroup.com/2010/01/simulating-html5-local-storage/ */
252(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+
253c++);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))},
254getItem: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];
255f.length=f.a.length};o()}})();
Note: See TracBrowser for help on using the repository browser.