      1 /*
      2     Copyright 2008-2025
      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