1 | /*
|
---|
2 |
|
---|
3 | Greenstone 3 'Client-side transformer'
|
---|
4 | Performs client-side transformations of XSLT, using HTML5 local storage (simulated if the browser doesn't support it).
|
---|
5 |
|
---|
6 | Currently 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 |
|
---|
15 | function 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 |
|
---|
31 | function notSupportedCookie() {
|
---|
32 | document.cookie = 'supportsXSLT=false; expires=0; path=/';
|
---|
33 | }
|
---|
34 |
|
---|
35 | function 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 |
|
---|
47 | function 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
|
---|
63 | function 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 |
|
---|
83 | function 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 |
|
---|
97 | var 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 |
|
---|
212 | function 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 |
|
---|
226 | function 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+
|
---|
253 | c++);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))},
|
---|
254 | getItem: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];
|
---|
255 | f.length=f.a.length};o()}})();
|
---|