1 /*
  2     Copyright 2008-2024
  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 dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 29     and <https://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 /*global JXG:true, define: true, ActiveXObject:true, jxgBinFileReader:true, DOMParser:true, XMLHttpRequest:true, document:true, navigator:true*/
 33 /*jslint nomen: true, plusplus: true*/
 34 
 35 import JXG from "../jxg.js";
 36 import Env from "../utils/env.js";
 37 import Type from "../utils/type.js";
 38 import Encoding from "../utils/encoding.js";
 39 import Base64 from "../utils/base64.js";
 40 
 41 /**
 42  * The FileReader object bundles the file input capabilities of JSXGraph.
 43  */
 44 JXG.FileReader = {
 45     /**
 46      *
 47      * @param {String} url
 48      * @param {JXG.Board} board
 49      * @param {String} format
 50      * @param {Boolean} async
 51      * @param {Function} callback
 52      *
 53      * @private
 54      */
 55     handleRemoteFile: function (url, board, format, async, encoding, callback) {
 56         var request = false;
 57 
 58         try {
 59             request = new XMLHttpRequest();
 60             if (format.toLowerCase() === "raw") {
 61                 request.overrideMimeType("text/plain; charset=" + encoding);
 62             } else {
 63                 request.overrideMimeType("text/xml; charset=" + encoding);
 64             }
 65         } catch (e) {
 66             try {
 67                 request = new ActiveXObject("Msxml2.XMLHTTP");
 68             } catch (ex) {
 69                 try {
 70                     request = new ActiveXObject("Microsoft.XMLHTTP");
 71                 } catch (exc) {
 72                     request = false;
 73                 }
 74             }
 75         }
 76         if (!request) {
 77             JXG.debug("AJAX not activated!");
 78             return;
 79         }
 80 
 81         request.open("GET", url, async);
 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                     text = "";
 93 
 94                 if (req.readyState === 4) {
 95                     // Hack for ancient IEs:
 96                     // We use the Visual Basic stuff from below.
 97                     if (
 98                         Type.exists(req.responseStream) &&
 99                         // PK: zip, geogebra
100                         // 31: gzip, cinderella
101                         (req.responseText.slice(0, 2) === "PK" ||
102                             Encoding.asciiCharCodeAt(req.responseText.slice(0, 1), 0) === 31)
103                     ) {
104                         // After this, text contains the binary? zip-compressed string
105                         text = Base64.decode(jxgBinFileReader(req));
106                     } else {
107                         // This is for all browsers except ancient IEs.
108                         text = req.responseText;
109                         // console.log(text);
110                     }
111                     this.parseString(text, board, format, callback);
112                 }
113             };
114         }
115 
116         this.cb = Type.bind(this.cbp, this);
117         // Old style
118         request.onreadystatechange = this.cb;
119 
120         try {
121             request.send(null);
122         } catch (ex2) {
123             throw new Error(
124                 "JSXGraph: A problem occurred while trying to read remote file '" + url + "'."
125             );
126         }
127     },
128 
129     /**
130      *
131      * @param {Blob} url The Blob or File from which to read
132      * @param {JXG.Board} board
133      * @param {String} format
134      * @param {Boolean} async
135      * @param {Function} callback
136      *
137      * @private
138      */
139     handleLocalFile: function (url, board, format, async, encoding, callback) {
140         if (!Type.exists(async)) {
141             async = true;
142         }
143 
144         if (format.toLowerCase() === "raw") {
145             this.cbp = function (e) {
146                 board(e.target.result);
147             };
148         } else {
149             this.cbp = function (e) {
150                 var text = e.target.result;
151                 //console.log(text);
152                 this.parseString(text, board, format, callback);
153             };
154         }
155 
156         this.cb = Type.bind(this.cbp, this);
157 
158         var reader = new FileReader();
159         reader.onload = this.cb;
160         if (format.toLowerCase() === "raw") {
161             reader.readAsText(url);
162         } else {
163             reader.readAsText(url, encoding);
164         }
165     },
166 
167     /**
168      * Opens a file using the given URL and passes the contents to {@link JXG.FileReader#parseString}
169      * @param {String} url
170      * @param {JXG.Board|function} board Either a board or in case <tt>format</tt> equals 'raw' this has to be a callback function.
171      * @param {String} format The expected file format. Possible values are <dl>
172      * <dt>raw</dt><dd>Raw text file. In this case <tt>board</tt> has to be a callback function.</dd>
173      * <dt>geonext</dt><dd>Geonext File <a href="https://www.geonext.de">https://www.geonext.de</a></dd>
174      * <dt>intergeo</dt><dd>Intergeo file format <a href="https://www.i2geo.net">https://www.i2geo.net</a></dd>
175      * <dt>tracenpoche</dt><dd>Tracenpoche construction <a href="https://tracenpoche.sesamath.net/">https://tracenpoche.sesamath.net/</a></dd>
176      * <dt>graph</dt><dd>Graph file</dd>
177      * <dt>digraph</dt><dd>DiGraph file</dd>
178      * <dt>geogebra</dt><dd>Geogebra File <a href="https://www.geogebra.org">https://www.geogebra.org</a></dd>
179      * <dl><dt>cdy or cinderella</dt><dd>Cinderella (<a href="https://www.cinderella.de/">https://www.cinderella.de</a></dd>
180      * </dl>
181      * @param {Boolean} async Call ajax asynchonously.
182      * @param {function} callback A function that is run when the board is ready.
183      */
184     parseFileContent: function (url, board, format, async, encoding, callback) {
185         if (Type.isString(url) || FileReader === undefined) {
186             this.handleRemoteFile(url, board, format, async, encoding, callback);
187         } else {
188             this.handleLocalFile(url, board, format, async, encoding, callback);
189         }
190     },
191 
192     /**
193      * Parses a given string according to the file format given in format.
194      * @param {String} str Contents of the file.
195      * @param {JXG.Board} board The board the construction in the file should be loaded in.
196      * @param {String} format Possible values are <dl>
197      * <dt>raw</dt><dd>Raw text file. In this case <tt>board</tt> has to be a callback function.</dd>
198      * <dt>geonext</dt><dd>Geonext File <a href="https://www.geonext.de">https://www.geonext.de</a></dd>
199      * <dt>intergeo</dt><dd>Intergeo file format <a href="https://www.i2geo.net">https://www.i2geo.net</a></dd>
200      * <dt>tracenpoche</dt><dd>Tracenpoche construction <a href="https://tracenpoche.sesamath.net/">https://tracenpoche.sesamath.net/</a></dd>
201      * <dt>graph</dt><dd>Graph file</dd>
202      * <dt>digraph</dt><dd>DiGraph file</dd>
203      * <dt>geogebra</dt><dd>Geogebra File <a href="https://www.geogebra.org">https://www.geogebra.org</a></dd>
204      * <dl><dt>cdy or cinderella</dt><dd>Cinderella (<a href="https://www.cinderella.de/">https://www.cinderella.de</a></dd>
205      * </dl>
206      * @param {function} callback
207      */
208     parseString: function (str, board, format, callback) {
209         var Reader, read;
210 
211         format = format.toLowerCase();
212         Reader = JXG.readers[format];
213 
214         if (Type.exists(Reader)) {
215             read = new Reader(board, str);
216             read.read();
217         } else if (format === "jessiecode") {
218         } else {
219             throw new Error("JSXGraph: There is no reader available for '" + format + "'.");
220         }
221 
222         if (Type.isFunction(callback)) {
223             callback(board);
224         }
225     }
226 };
227 
228 // The following code is vbscript. This is a workaround to enable binary data downloads via AJAX in
229 // Microsoft Internet Explorer.
230 
231 /*jslint evil:true, es5:true, white:true*/
232 /*jshint multistr:true*/
233 if (
234     !Env.isMetroApp() &&
235     Env.isBrowser &&
236     typeof navigator === "object" &&
237     /msie/i.test(navigator.userAgent) &&
238     !/opera/i.test(navigator.userAgent) &&
239     document &&
240     document.write
241 ) {
242     document.write(
243         '<script type="text/vbscript">\n\
244 Function Base64Encode(inData)\n\
245   Const Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"\n\
246   Dim cOut, sOut, I\n\
247   For I = 1 To LenB(inData) Step 3\n\
248     Dim nGroup, pOut, sGroup\n\
249     nGroup = &H10000 * AscB(MidB(inData, I, 1)) + _\n\
250       &H100 * MyASC(MidB(inData, I + 1, 1)) + MyASC(MidB(inData, I + 2, 1))\n\
251     nGroup = Oct(nGroup)\n\
252     nGroup = String(8 - Len(nGroup), "0") & nGroup\n\
253     pOut = Mid(Base64, CLng("&o" & Mid(nGroup, 1, 2)) + 1, 1) + _\n\
254       Mid(Base64, CLng("&o" & Mid(nGroup, 3, 2)) + 1, 1) + _\n\
255       Mid(Base64, CLng("&o" & Mid(nGroup, 5, 2)) + 1, 1) + _\n\
256       Mid(Base64, CLng("&o" & Mid(nGroup, 7, 2)) + 1, 1)\n\
257     sOut = sOut + pOut\n\
258   Next\n\
259   Select Case LenB(inData) Mod 3\n\
260     Case 1: \'8 bit final\n\
261       sOut = Left(sOut, Len(sOut) - 2) + "=="\n\
262     Case 2: \'16 bit final\n\
263       sOut = Left(sOut, Len(sOut) - 1) + "="\n\
264   End Select\n\
265   Base64Encode = sOut\n\
266 End Function\n\
267 \n\
268 Function MyASC(OneChar)\n\
269   If OneChar = "" Then MyASC = 0 Else MyASC = AscB(OneChar)\n\
270 End Function\n\
271 \n\
272 Function jxgBinFileReader(xhr)\n\
273     Dim byteString\n\
274     Dim b64String\n\
275     Dim i\n\
276     byteString = xhr.responseBody\n\
277     ReDim byteArray(LenB(byteString))\n\
278     For i = 1 To LenB(byteString)\n\
279         byteArray(i-1) = AscB(MidB(byteString, i, 1))\n\
280     Next\n\
281     b64String = Base64Encode(byteString)\n\
282     jxgBinFileReader = b64String\n\
283 End Function\n\
284 </script>\n'
285     );
286 }
287 
288 export default JXG.FileReader;
289