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 | function 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 |
|
---|
30 | function notSupportedCookie() {
|
---|
31 | document.cookie = 'supportsXSLT=false; expires=0; path=/';
|
---|
32 | }
|
---|
33 |
|
---|
34 | function 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 |
|
---|
46 | function 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
|
---|
62 | function 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 |
|
---|
82 | function 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 |
|
---|
94 | var 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("\"'''\"", "\"'&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 |
|
---|
206 | function 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+
|
---|
236 | 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))},
|
---|
237 | 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];
|
---|
238 | f.length=f.a.length};o()}})();
|
---|