1 /*
  2     Copyright 2008,
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software: you can redistribute it and/or modify
 13     it under the terms of the GNU Lesser General Public License as published by
 14     the Free Software Foundation, either version 3 of the License, or
 15     (at your option) any later version.
 16 
 17     JSXGraph is distributed in the hope that it will be useful,
 18     but WITHOUT ANY WARRANTY; without even the implied warranty of
 19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 20     GNU Lesser General Public License for more details.
 21 
 22     You should have received a copy of the GNU Lesser General Public License
 23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
 24 */
 25 
 26 /**
 27  * The FileReader object bundles the file input capabilities of JSXGraph.
 28  */
 29 JXG.FileReader = {
 30 
 31     /**
 32      * Opens a file using the given URL and passes the contents to {@link JXG.FileReader#parseString}
 33      * @param {String} url
 34      * @param {JXG.Board|function} board Either a board or in case <tt>format</tt> equals 'raw' this has to be a callback function.
 35      * @param {String} format The expected file format. Possible values are <dl>
 36      * <dt>raw</dt><dd>Raw text file. In this case <tt>board</tt> has to be a callback function.</dd>
 37      * <dt>geonext</dt><dd>Geonext File <a href="http://www.geonext.de">http://www.geonext.de</a></dd>
 38      * <dt>intergeo</dt><dd>Intergeo file format <a href="http://www.i2geo.net">http://www.i2geo.net</a></dd>
 39      * <dt>tracenpoche</dt><dd>Tracenpoche construction <a href="http://www.tracenpoche.net">http://www.tracenpoche.net</a></dd>
 40      * <dt>graph</dt><dd>Graph file</dd>
 41      * <dt>digraph</dt><dd>DiGraph file</dd>
 42      * <dt>geogebra</dt><dd>Geogebra File <a href="http://www.geogebra.org">http://www.geogebra.org</a></dd>
 43      * <dl><dt>cdy or cinderella</dt><dd>Cinderella (<a href="http://www.cinderella.de/">http://www.cinderella.de</a></dd>
 44      * </dl>
 45      * @param {Boolean} async Call ajax asynchonously.
 46      */
 47     parseFileContent: function (url, board, format, async) {
 48         var request = false;
 49         
 50         if(!JXG.exists(async)) {
 51             async = true;
 52         }
 53 
 54         //this.request = false;
 55 
 56         try {
 57             request = new XMLHttpRequest();
 58             if(format.toLowerCase()=='raw') {
 59                 request.overrideMimeType('text/plain; charset=iso-8859-1');
 60             } else {
 61                 request.overrideMimeType('text/xml; charset=iso-8859-1');
 62             }
 63         } catch (e) {
 64             try {
 65                 request = new ActiveXObject("Msxml2.XMLHTTP");
 66             } catch (e) {
 67                 try {
 68                     request = new ActiveXObject("Microsoft.XMLHTTP");
 69                 } catch (e) {
 70                     request = false;
 71                 }
 72             }
 73         }
 74 
 75         if (!request) {
 76             alert("AJAX not activated!");
 77             return;
 78         }
 79         
 80         request.open("GET", url, async);
 81 
 82         if(format.toLowerCase() === 'raw') {
 83             this.cbp = function() {
 84                 var req = request;
 85                 if (req.readyState == 4) {
 86                     board(req.responseText);
 87                 }
 88             };
 89         } else {
 90             this.cbp = function() {
 91                 var req = request;
 92                 if (req.readyState == 4) {
 93                     var text = '';
 94 
 95                     if (typeof req.responseStream!='undefined' &&
 96                         (req.responseText.slice(0,2) == "PK"                            // ZIP -> Geogebra
 97                             || JXG.Util.asciiCharCodeAt(req.responseText.slice(0,1),0)==31) // gzip -> Cinderella
 98                         ) {
 99                         text = JXG.Util.Base64.decode(BinFileReader(req)); // After this, text contains the base64 encoded, zip-compressed string
100                     } else {
101                         text = req.responseText;
102                     }
103                     this.parseString(text, board, format, false);
104                 }
105             };
106         }
107 
108         this.cb = JXG.bind(this.cbp, this);
109         request.onreadystatechange = this.cb;
110 
111         try {
112             request.send(null);
113         } catch (e) {
114             throw new Error("JSXGraph: A problem occurred while trying to read '" + url + "'.");
115         }
116     },
117 
118     /**
119      * Cleans out unneccessary whitespaces in a chunk of xml.
120      * @param {XMLElement} el
121      */
122     cleanWhitespace: function (el) {
123         var cur = el.firstChild;
124 
125         while (cur != null) {
126             if (cur.nodeType == 3 && !/\S/.test(cur.nodeValue)) {
127                 el.removeChild( cur );
128             } else if ( cur.nodeType == 1 ) {
129                 this.cleanWhitespace( cur );
130             }
131             cur = cur.nextSibling;
132         }
133     },
134 
135     /**
136      * Converts a given string into a XML tree.
137      * @param {String} str
138      * @returns {XMLElement} The xml tree represented by the root node.
139      */
140     stringToXMLTree: function (str) {
141         // The string "str" is converted into a XML tree.
142         if(typeof DOMParser === 'undefined') {
143             // IE workaround, since there is no DOMParser
144             DOMParser = function () {};
145             DOMParser.prototype.parseFromString = function (str, contentType) {
146                 if (typeof ActiveXObject !== 'undefined') {
147                     var d = new ActiveXObject('MSXML.DomDocument');
148                     d.loadXML(str);
149                     return d;
150                 }
151             };
152         }
153         var parser = new DOMParser(),
154             tree = parser.parseFromString(str, 'text/xml');
155 
156         this.cleanWhitespace(tree);
157         return tree;
158     },
159 
160     /**
161      * Parses a given string according to the file format given in format.
162      * @param {String} str Contents of the file.
163      * @param {JXG.Board} board The board the construction in the file should be loaded in.
164      * @param {String} format Possible values are <dl>
165      * <dt>raw</dt><dd>Raw text file. In this case <tt>board</tt> has to be a callback function.</dd>
166      * <dt>geonext</dt><dd>Geonext File <a href="http://www.geonext.de">http://www.geonext.de</a></dd>
167      * <dt>intergeo</dt><dd>Intergeo file format <a href="http://www.i2geo.net">http://www.i2geo.net</a></dd>
168      * <dt>tracenpoche</dt><dd>Tracenpoche construction <a href="http://www.tracenpoche.net">http://www.tracenpoche.net</a></dd>
169      * <dt>graph</dt><dd>Graph file</dd>
170      * <dt>digraph</dt><dd>DiGraph file</dd>
171      * <dt>geogebra</dt><dd>Geogebra File <a href="http://www.geogebra.org">http://www.geogebra.org</a></dd>
172      * <dl><dt>cdy or cinderella</dt><dd>Cinderella (<a href="http://www.cinderella.de/">http://www.cinderella.de</a></dd>
173      * </dl>
174      * @param {Boolean} isString Some file formats can be given as Base64 encoded strings or as plain xml, in both cases
175      * they are given as strings. This flag is used to distinguish those cases: <tt>true</tt> means, it is given as a string,
176      * no need to un-Base64 and unzip the file.
177      */
178     parseString: function (str, board, format, isString) {
179         var tree, graph, xml;
180 
181         format = format.toLowerCase();
182 
183         switch (format) {
184             case 'cdy':
185             case 'cinderella':
186                 // if isString is true, str is the base64 encoded zip file, otherwise it's just the zip file
187                 if(isString) {
188                     str = JXG.Util.Base64.decode(str);
189                 }
190 
191                 str = JXG.CinderellaReader.readCinderella(str, board);
192                 board.xmlString = str;
193 
194                 break;
195             case 'tracenpoche':
196                 board.xmlString = JXG.TracenpocheReader.readTracenpoche(str, board);
197 
198                 break;
199             case 'graph':
200                 str = JXG.GraphReader.readGraph(str, board, false);
201                 break;
202             case 'digraph':
203                 str = JXG.GraphReader.readGraph(str, updateboard, true);
204                 break;
205             case 'geonext':
206                 // str is a string containing the XML code of the construction
207                 str = JXG.GeonextReader.prepareString(str);
208                 xml = true;
209                 break;
210             case 'geogebra':
211                 isString = str.slice(0, 2) !== "PK";
212 
213                 // if isString is true, str is a base64 encoded string, otherwise it's the zipped file
214                 str = JXG.GeogebraReader.prepareString(str, isString);
215                 xml = true;
216                 break;
217             case 'intergeo':
218                 if(isString) {
219                     str = JXG.Util.Base64.decode(str);
220                 }
221 
222                 str = JXG.IntergeoReader.prepareString(str);
223                 xml = true;
224                 break;
225         }
226 
227         if (xml) {
228             board.xmlString = str;
229             tree = this.stringToXMLTree(str);
230             // Now, we can walk through the tree
231             this.readElements(tree, board, format);
232         }
233     },
234 
235     /**
236      * Reading the elements of a geonext or geogebra file
237      * @param {} tree expects the content of the parsed geonext file returned by function parseFromString
238      * @param {Object} board board object
239      */
240     readElements: function (tree, board, format) {
241         if (format.toLowerCase()=='geonext') {
242             board.suspendUpdate();
243             if(tree.getElementsByTagName('GEONEXT').length != 0) {
244                 JXG.GeonextReader.readGeonext(tree, board);
245             }
246             board.unsuspendUpdate();
247         }
248         else if(tree.getElementsByTagName('geogebra').length != 0) {
249             JXG.GeogebraReader.readGeogebra(tree, board);
250         }
251         else if(format.toLowerCase()=='intergeo') {
252             JXG.IntergeoReader.readIntergeo(tree, board);
253         }
254     }
255 };
256 
257 // The following code is vbscript. This is a workaround to enable binary data downloads via AJAX in
258 // Microsoft Internet Explorer.
259 
260 if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent && document && document.write)) {
261 document.write('<script type="text/vbscript">\n\
262 Function Base64Encode(inData)\n\
263   Const Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"\n\
264   Dim cOut, sOut, I\n\
265   For I = 1 To LenB(inData) Step 3\n\
266     Dim nGroup, pOut, sGroup\n\
267     nGroup = &H10000 * AscB(MidB(inData, I, 1)) + _\n\
268       &H100 * MyASC(MidB(inData, I + 1, 1)) + MyASC(MidB(inData, I + 2, 1))\n\
269     nGroup = Oct(nGroup)\n\
270     nGroup = String(8 - Len(nGroup), "0") & nGroup\n\
271     pOut = Mid(Base64, CLng("&o" & Mid(nGroup, 1, 2)) + 1, 1) + _\n\
272       Mid(Base64, CLng("&o" & Mid(nGroup, 3, 2)) + 1, 1) + _\n\
273       Mid(Base64, CLng("&o" & Mid(nGroup, 5, 2)) + 1, 1) + _\n\
274       Mid(Base64, CLng("&o" & Mid(nGroup, 7, 2)) + 1, 1)\n\
275     sOut = sOut + pOut\n\
276   Next\n\
277   Select Case LenB(inData) Mod 3\n\
278     Case 1: \'8 bit final\n\
279       sOut = Left(sOut, Len(sOut) - 2) + "=="\n\
280     Case 2: \'16 bit final\n\
281       sOut = Left(sOut, Len(sOut) - 1) + "="\n\
282   End Select\n\
283   Base64Encode = sOut\n\
284 End Function\n\
285 \n\
286 Function MyASC(OneChar)\n\
287   If OneChar = "" Then MyASC = 0 Else MyASC = AscB(OneChar)\n\
288 End Function\n\
289 \n\
290 Function BinFileReader(xhr)\n\
291     Dim byteString\n\
292     Dim b64String\n\
293     Dim i\n\
294     byteString = xhr.responseBody\n\
295     ReDim byteArray(LenB(byteString))\n\
296     For i = 1 To LenB(byteString)\n\
297         byteArray(i-1) = AscB(MidB(byteString, i, 1))\n\
298     Next\n\
299     b64String = Base64Encode(byteString)\n\
300     BinFileReader = b64String\n\
301 End Function\n\
302 </script>\n');
303 }
304