All files doc.js

70.92% Statements 178/251
78.57% Branches 132/168
81.82% Functions 18/22
70.37% Lines 152/216

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372                              1x 219x 219x 3x 3x   219x     219x     1x 845x 845x 113x 112x 360x   844x     1x 232x 232x 312x       1x 212x 140x 140x 30x                 1x 3633x                 1x 1524x 1522x 1503x 1480x 1475x 1472x                           1x 1x     1x 11x 11x     1x 2x 2x     1x 16x 16x 16x 16x 16x     1x 232x 232x 77x 77x 77x 77x 77x 56x   77x 77x     78x     155x 13x 13x 26x   13x   142x 142x 2x     140x 140x 294x 217x   77x 77x     140x     232x     1x 947x 947x     1x 88x 88x                 1x 1x 1x 1x 1x 1x 1x 3x 1x 3x 1x               1x 216x 216x     1x 192x 192x 192x 25x 25x 25x   73x 25x 6x   167x 167x 167x 125x 116x   2x     1x 8506x 8506x 8506x 8506x 8506x 3614x 3459x     155x     4892x 882x 882x 882x   4010x 882x 882x 882x 882x 2870x 882x 2887x 1032x 1032x 156x 156x 398x 398x   156x   876x       3128x 398x 398x 398x 624x     2730x 2730x 4492x 2730x 53x     8506x     1x                                                                                                                                                                                         1x                  
import AST from './ast.js';
import Parsers from './parser.js';
import Symbols from './symbols.js';
import Utils from './utils.js';
import Version from './version.js';
import katex from '../lib/katex/katex-modified.min.js';
 
/**
   @class
   @classdesc A class representing a Guppy document.  To access this
   class, use `Guppy.Doc`.  To get the document for a particular guppy
   instance, say called `"guppy1"`, do `Guppy("guppy1").doc()`.
   @param {string} [doc=<m><e></e></m>] - An XML string representing the document
   @constructor
 */
var Doc = function(doc, type){
    type = type || "xml";
    if(type == "xml") this.set_content(doc || "<m><e></e></m>");
    else Iif(type == "latex") this.import_latex(doc);
    else Eif(type == "text") this.import_text(doc);
    else if(type == "ast") this.import_ast(doc);
    Iif(this.root().hasAttribute("v") && this.root().getAttribute("v") != Version.DOC_VERSION)
    throw Version.DOC_ERROR;
    else
    this.root().setAttribute("v",Version.DOC_VERSION);
}
 
Doc.prototype.is_small = function(nn){
    var n = nn.parentNode;
    while(n != null && n.nodeName != 'm'){
        if(n.getAttribute("small") == "yes") return true;
        n = n.parentNode
        while(n != null && n.nodeName != 'c') n = n.parentNode;
    }
    return false;
}
 
Doc.prototype.ensure_text_nodes = function(){
    var l = this.base.getElementsByTagName("e");
    for(var i = 0; i < l.length; i++){
        if(!(l[i].firstChild)) l[i].appendChild(this.base.createTextNode(""));
    }
}
 
Doc.prototype.is_blank = function(){
    if(this.base.getElementsByTagName("f").length > 0) return false;
    var l = this.base.getElementsByTagName("e");
    if(l.length == 1 && (!(l[0].firstChild) || l[0].firstChild.textContent == "")) return true;
    return false;
}
 
 
/**
    Get the document as a DOM object
    @memberof Doc
    @returns {Element}
*/
Doc.prototype.root = function(){
    return this.base.documentElement;
}
 
/**
    Get the content of the document as a string
    @memberof Doc
    @param {string} t - The rendering method to use ("latex", "text", "ast" (for syntax tree), or "xml" (for internal XML representation))
    @returns {string}
*/
Doc.prototype.get_content = function(t,r){
    if(t == "xml") return (new XMLSerializer()).serializeToString(this.base);
    else if(t == "ast") return JSON.stringify(this.syntax_tree());
    else if(t == "text") return AST.to_text(this.syntax_tree());
    else if(t == "function") return AST.to_function(this.syntax_tree(), r);
    else if(t == "eqns") return JSON.stringify(AST.to_eqlist(this.syntax_tree()));
    else return this.manual_render(t,this.root(),r);
}
 
/**
    Evaluate the document using user-supplied functions to interpret symbols
    @memberof Doc
    @param {Object} evaluators - A dictionary where each key is a node
    type in the AST ("var", "val", "sin", "cos", etc.) and the
    corresponding value is a function that takes a list of argument
    (the results of evaluating that AST node's arguments) as well as,
    optionally, a second argument for the parent AST node to the one
    currently being evaluated.
    @returns {Object}
*/
Doc.prototype.evaluate = function(evaluators){
    return AST.eval(this.syntax_tree(), evaluators);
}
 
Doc.prototype.import_text = function(text, syms, s2n){
    var ast = Parsers.TextParser.tokenise_and_parse(text);
    this.import_ast(ast, syms, s2n);
}
 
Doc.prototype.import_latex = function(text, syms, s2n){
    var ast = Parsers.LaTeXParser.tokenise_and_parse(text);
    this.import_ast(ast, syms, s2n);
}
 
Doc.prototype.import_ast = function(ast, syms, s2n){
    syms = syms || Symbols.symbols;
    s2n = s2n || Symbols.symbol_to_node;
    var doc = AST.to_xml(ast, syms, s2n);
    this.base = doc;
    this.ensure_text_nodes();
}
 
Doc.prototype.syntax_tree = function(n){
    n = n || this.root()
    if(n.nodeName == "f"){
        var ans = {"args":[], "kwargs":{}};
        ans['value'] = n.getAttribute("type");
        ans['type'] = "function";
        Iif(n.hasAttribute("ast_value")) ans['value'] = n.getAttribute("ast_value");
        if(n.hasAttribute("ast_type")) ans['type'] = n.getAttribute("ast_type");
        else if(Utils.is_char(n)) ans['type'] = "name";
 
        var iterator = this.xpath_list("./*[name()='c' or name()='l']", n)
        for(var nn = iterator.iterateNext(); nn != null; nn = iterator.iterateNext()){
            //if(nn.hasAttribute("name")) ans.kwargs[nn.getAttribute("name")] = this.syntax_tree(nn)
            //else ans.args.push(this.syntax_tree(nn))
            ans.args.push(this.syntax_tree(nn))
        }
    }
    else if(n.nodeName == "l"){
        ans = [];
        for(nn = n.firstChild; nn != null; nn = nn.nextSibling){
        ans.push(this.syntax_tree(nn));
        }
        ans = ["list",ans];
    }
    else Eif(n.nodeName == "c" || n.nodeName == "m"){
        if(n.hasAttribute("mode") && n.getAttribute("mode") == "text"){
            ans = n.firstChild.firstChild.textContent;
        }
        else{
            var tokens = []
            for(nn = n.firstChild; nn != null; nn = nn.nextSibling){
                if(nn.nodeName == "e"){
                    tokens = tokens.concat(Parsers.EParser.tokenise(nn.firstChild.textContent));
                }
                else Eif(nn.nodeName == "f"){
                    tokens.push(this.syntax_tree(nn));
                }
            }
            ans = Parsers.EParser.parse(tokens);
        }
    }
    return ans;
}
 
Doc.prototype.xpath_node = function(xpath, node){
    node = node || this.root()
    return this.base.evaluate(xpath, node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}
 
Doc.prototype.xpath_list = function(xpath, node){
    node = node || this.root()
    return this.base.evaluate(xpath, node, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
}
 
/**
    Get the names of symbols used in this document
    @memberof Doc
    @param {string[]} [groups] - A list of groups you want strings for
    @returns {string[]}
*/
Doc.prototype.get_symbols = function(groups){
    var types = {};
    var ans = [];
    var groups_selector = "//f";
    Iif(groups) groups_selector += "[" + groups.map(function(){ return ""; }).join(" or ") + "]";
    var iterator = this.xpath_list(groups_selector)
    for(var nn = iterator.iterateNext(); nn != null; nn = iterator.iterateNext())
        types[nn.getAttribute("type")] = true;
    for(var t in types)
        ans.push(t);
    return ans;
}
 
/**
    Set the content of the document
    @memberof Doc
    @param {string} xml_data - An XML string representing the content of the document
*/
Doc.prototype.set_content = function(xml_data){
    this.base = (new window.DOMParser()).parseFromString(xml_data, "text/xml");
    this.ensure_text_nodes();
}
 
Doc.prototype.auto_bracket = function(n){
    var e0 = n.firstChild;
    var e1 = n.lastChild;
    if(n.childElementCount == 3 && e0.firstChild.textContent == "" && e1.firstChild.textContent == ""){ // single f child, all e children empty
        var f = e0.nextSibling;
    var cs = 0;
    var c = null;
    // Count immediate children of f that are c nodes in cs and store the last one in c
    for(var nn = f.firstChild; nn; nn = nn.nextSibling) if(nn.tagName == "c"){ c = nn; cs++; }
        if(cs == 1 && c.getAttribute("is_bracket") == "yes") return false; // if the f child is a bracket, don't bracket
        if(Utils.is_char(f) && e0.getAttribute("current") != "yes" && e0.getAttribute("temp") != "yes" && e1.getAttribute("current") != "yes" && e1.getAttribute("temp") != "yes") return false; // if the f child is a character and not current or temp cursor location, don't bracket
    }
    else Eif(n.childElementCount == 1){ // Single e child
        var s = e0.firstChild.textContent;
        if(s.length != 1 && Number(s)+"" != s) return true; // If content is neither a single character nor a number, bracket it
        if(e0.getAttribute("current") == "yes" || e0.getAttribute("temp") == "yes") return true; // If content has the cursor or temp cursor, bracket it
        return false;
    }
    return true;
}
 
Doc.prototype.manual_render = function(t,n,r){
    var ans = "";
    var nn = null;
    var i = null;
    var spacer = t == "latex" ? " " : "";
    if(n.nodeName == "e"){
        if(t == "latex" && r){
            ans = n.getAttribute("render");
        }
        else{
            ans = n.firstChild.textContent;
        }
    }
    else if(n.nodeName == "f"){
        var real_type = (t == "latex" && this.is_small(n)) ? "small_latex" : t;
        nn = this.xpath_node("./b[@p='"+real_type+"']", n) || this.xpath_node("./b[@p='"+t+"']", n);
        Eif(nn) ans = this.manual_render(t,nn,r);
    }
    else if(n.nodeName == "b"){
        var cs = []
        i = 1;
        var par = n.parentNode;
        for(nn = par.firstChild; nn != null; nn = nn.nextSibling)
            if(nn.nodeName == "c" || nn.nodeName == "l") cs[i++] = this.manual_render(t,nn,r);
        for(nn = n.firstChild; nn != null; nn = nn.nextSibling){
            if(nn.nodeType == 3) ans += nn.textContent + spacer;
            else Eif(nn.nodeType == 1){
                if(nn.hasAttribute("d")){
                    var dim = parseInt(nn.getAttribute("d"));
                    var joiner = function(d,l){
                        if(d > 1) for(var k = 0; k < l.length; k++) l[k] = joiner(d-1,l[k]);
                        return l.join(nn.getAttribute('sep'+(d-1)));
                    }
                ans += joiner(dim,cs[parseInt(nn.getAttribute("ref"))]) + spacer;
                }
                else ans += cs[parseInt(nn.getAttribute("ref"))] + spacer;
            }
        }
    }
    else if(n.nodeName == "l"){
        ans = [];
        i = 0;
        for(nn = n.firstChild; nn != null; nn = nn.nextSibling){
            ans[i++] = this.manual_render(t,nn,r);
        }
    }
    else Eif(n.nodeName == "c" || n.nodeName == "m"){
        for(nn = n.firstChild; nn != null; nn = nn.nextSibling)
            ans += this.manual_render(t,nn,r) + spacer;
        if(t == "latex" && n.getAttribute("bracket") == "yes" && this.auto_bracket(n)) {
            ans = "\\left("+ans+"\\right)";
        }
    }
    return ans;
}
 
Doc.render_all = function(t, delim, root_node){
    var l,i,n,d,s,ans = [];
    if(!t || t == "xml"){
        l = document.getElementsByTagName("script");
        for(i = 0; i < l.length; i++){
            if(l[i].getAttribute("type") == "text/guppy_xml"){
                n = l[i];
                d = new Doc(n.innerHTML);
                s = document.createElement("span");
        var len = ans.length;
        var new_id = "guppy-"+t+"-render-"+len;
        while(document.getElementById(new_id)) new_id = "guppy-xml-render-"+(++len);
                s.setAttribute("id",new_id);
                s.setAttribute("class","guppy-render");
                katex.render(d.get_content("latex"), s);
                n.parentNode.insertBefore(s, n);
                n.parentNode.removeChild(n);
                ans.push({"container":s, "doc":d})
            }
        }
    }
    else {
        var subs = function(node) {
            if(!node) return;
            var excludeElements = ['script', 'style', 'iframe', 'canvas', 'pre', 'code'];
            do {
                switch (node.nodeType) {
                case 1:
                    // Don't process KaTeX elements, Guppy instances, Javascript, or CSS
                    if (excludeElements.indexOf(node.tagName.toLowerCase()) > -1 || (" "+node.getAttribute("class")+" ").indexOf(" katex ") > -1 || (""+node.getAttribute("class")).indexOf("guppy") > -1) {
                        continue;
                    }
                    subs(node.firstChild);
                    break;
                case 3:
                    var text_node = node;
                    var offset = text_node.textContent.indexOf(delim);
                    while(offset > -1){
                        var next = text_node.textContent.substring(offset+delim.length).indexOf(delim);
                        if(next == -1) break;
                        var before = text_node.textContent.substring(0,offset);
                        var content = text_node.textContent.substring(offset+delim.length,offset+delim.length+next);
                        var after = text_node.textContent.substring(offset+delim.length+next+delim.length);
 
                        // Make the span to render the doc in
                        var s = document.createElement("span");
            var l = ans.length;
            var new_id = "guppy-"+t+"-render-"+l;
            while(document.getElementById(new_id)) new_id = "guppy-"+t+"-render-"+(++l);
                        s.setAttribute("id",new_id);
            s.setAttribute("class","guppy-render");
 
            try {
                            // Create the document
                            d = new Doc(content,t);
 
                            // Render the doc
                            katex.render(d.get_content("latex"), s);
            }
            catch (e) {
                s.innerHTML = "ERROR: "+e.message;
            }
                        var new_node = document.createTextNode(after)
                        text_node.parentNode.insertBefore(document.createTextNode(before), text_node);
                        text_node.parentNode.insertBefore(s, text_node);
                        text_node.parentNode.insertBefore(new_node, text_node);
                        text_node.parentNode.removeChild(text_node);
                        text_node = new_node;
            node = new_node;
                        ans.push({"id":new_id, "doc":d});
 
                        offset = text_node.textContent.indexOf(delim);
                    }
                    break;
        default:
                    break;
                }
            } while ((node = node.nextSibling));
 
        }
        delim = delim || "$$";
        subs(root_node || document.documentElement);
    }
    return ans;
}
 
/**
    Render a given document into a specified HTML element.
    @param {string} doc - A GuppyXML string to be rendered
    @param {string} target_id - The ID of the HTML element to render into
    @param {string} type - Optional type of the doc provided. Default is `xml`
    @memberof Doc
*/
Doc.render = function(doc, target_id, type){
    var d = new Doc(doc, type);
    var target = document.getElementById(target_id);
    katex.render(d.get_content("latex"), target);
    return {"container":target, "doc":d};
}
 
 
export default Doc;