UNPKG

infusion

Version:

Infusion is an application framework for developing flexible stuff with JavaScript

469 lines (422 loc) 15.3 kB
/* Copyright The Infusion copyright holders See the AUTHORS.md file at the top-level directory of this distribution and at https://github.com/fluid-project/infusion/raw/main/AUTHORS.md. Licensed under the Educational Community License (ECL), Version 2.0 or the New BSD license. You may not use this file except in compliance with one these Licenses. You may obtain a copy of the ECL 2.0 License and BSD License at https://github.com/fluid-project/infusion/raw/main/Infusion-LICENSE.txt */ "use strict"; // unsupported, non-API function fluid.parseTemplate = function (template, baseURL, scanStart, cutpoints_in, opts) { opts = opts || {}; if (!template) { fluid.fail("empty template supplied to fluid.parseTemplate"); } var t; var parser; var tagstack; var lumpindex = 0; var nestingdepth = 0; var justended = false; var defstart = -1; var defend = -1; var debugMode = false; var cutpoints = []; // list of selector, tree, id var simpleClassCutpoints = {}; var cutstatus = []; var XMLLump = function (lumpindex, nestingdepth) { return { //rsfID: "", //text: "", //downmap: {}, //attributemap: {}, //finallump: {}, nestingdepth: nestingdepth, lumpindex: lumpindex, parent: t }; }; function isSimpleClassCutpoint(tree) { return tree.length === 1 && tree[0].predList.length === 1 && tree[0].predList[0].clazz; } function init(baseURLin, debugModeIn, cutpointsIn) { t.rootlump = XMLLump(0, -1); // eslint-disable-line new-cap tagstack = [t.rootlump]; lumpindex = 0; nestingdepth = 0; justended = false; defstart = -1; defend = -1; baseURL = baseURLin; debugMode = debugModeIn; if (cutpointsIn) { for (var i = 0; i < cutpointsIn.length; ++i) { var tree = fluid.parseSelector(cutpointsIn[i].selector, fluid.simpleCSSMatcher); var clazz = isSimpleClassCutpoint(tree); if (clazz) { simpleClassCutpoints[clazz] = cutpointsIn[i].id; } else { cutstatus.push([]); cutpoints.push($.extend({}, cutpointsIn[i], {tree: tree})); } } } } function findTopContainer() { for (var i = tagstack.length - 1; i >= 0; --i) { var lump = tagstack[i]; if (lump.rsfID !== undefined) { return lump; } } return t.rootlump; } function newLump() { var togo = XMLLump(lumpindex, nestingdepth); // eslint-disable-line new-cap if (debugMode) { togo.line = parser.getLineNumber(); togo.column = parser.getColumnNumber(); } //togo.parent = t; t.lumps[lumpindex] = togo; ++lumpindex; return togo; } function addLump(mmap, ID, lump) { var list = mmap[ID]; if (!list) { list = []; mmap[ID] = list; } list[list.length] = lump; } function checkContribute(ID, lump) { if (ID.indexOf("scr=contribute-") !== -1) { var scr = ID.substring("scr=contribute-".length); addLump(t.collectmap, scr, lump); } } function debugLump(lump) { // TODO expand this to agree with the Firebug "self-selector" idiom return "<" + lump.tagname + ">"; } function hasCssClass(clazz, totest) { if (!totest) { return false; } // algorithm from jQuery return (" " + totest + " ").indexOf(" " + clazz + " ") !== -1; } function matchNode(term, headlump, headclazz) { if (term.predList) { for (var i = 0; i < term.predList.length; ++i) { var pred = term.predList[i]; if (pred.id && headlump.attributemap.id !== pred.id) {return false;} if (pred.clazz && !hasCssClass(pred.clazz, headclazz)) {return false;} if (pred.tag && headlump.tagname !== pred.tag) {return false;} } return true; } } function tagStartCut(headlump) { var togo; var headclazz = headlump.attributemap["class"]; var i; if (headclazz) { var split = headclazz.split(" "); for (i = 0; i < split.length; ++i) { var simpleCut = simpleClassCutpoints[split[i].trim()]; if (simpleCut) { return simpleCut; } } } for (i = 0; i < cutpoints.length; ++i) { var cut = cutpoints[i]; var cutstat = cutstatus[i]; var nextterm = cutstat.length; // the next term for this node if (nextterm < cut.tree.length) { var term = cut.tree[nextterm]; if (nextterm > 0) { if (cut.tree[nextterm - 1].child && cutstat[nextterm - 1] !== headlump.nestingdepth - 1) { continue; // it is a failure to match if not at correct nesting depth } } var isMatch = matchNode(term, headlump, headclazz); if (isMatch) { cutstat[cutstat.length] = headlump.nestingdepth; if (cutstat.length === cut.tree.length) { if (togo !== undefined) { fluid.fail("Cutpoint specification error - node " + debugLump(headlump) + " has already matched with rsf:id of " + togo); } if (cut.id === undefined || cut.id === null) { fluid.fail("Error in cutpoints list - entry at position " + i + " does not have an id set"); } togo = cut.id; } } } } return togo; } function tagEndCut() { if (cutpoints) { for (var i = 0; i < cutpoints.length; ++i) { var cutstat = cutstatus[i]; if (cutstat.length > 0 && fluid.peek(cutstat) === nestingdepth) { cutstat.length--; } } } } function processTagEnd() { tagEndCut(); var endlump = newLump(); --nestingdepth; endlump.text = "</" + parser.getName() + ">"; var oldtop = fluid.peek(tagstack); oldtop.close_tag = t.lumps[lumpindex - 1]; tagstack.length--; justended = true; } function processTagStart(isempty) { ++nestingdepth; if (justended) { justended = false; var backlump = newLump(); backlump.nestingdepth--; } if (t.firstdocumentindex === -1) { t.firstdocumentindex = lumpindex; } var headlump = newLump(); var stacktop = fluid.peek(tagstack); headlump.uplump = stacktop; var tagname = parser.getName(); headlump.tagname = tagname; // NB - attribute names and values are now NOT DECODED!! var attrs = headlump.attributemap = parser.m_attributes; var ID = attrs[fluid.ID_ATTRIBUTE]; if (ID === undefined) { ID = tagStartCut(headlump); } for (var attrname in attrs) { if (ID === undefined) { if (/href|src|codebase|action/.test(attrname)) { ID = "scr=rewrite-url"; } // port of TPI effect of IDRelationRewriter else if (ID === undefined && /for|headers/.test(attrname)) { ID = "scr=null"; } } } if (ID) { // TODO: ensure this logic is correct on RSF Server if (ID.charCodeAt(0) === 126) { // "~" ID = ID.substring(1); headlump.elide = true; } checkContribute(ID, headlump); headlump.rsfID = ID; var downreg = findTopContainer(); if (!downreg.downmap) { downreg.downmap = {}; } while (downreg) { // TODO: unusual fix for locating branches in parent contexts (applies to repetitive leaves) if (downreg.downmap) { addLump(downreg.downmap, ID, headlump); } downreg = downreg.uplump; } addLump(t.globalmap, ID, headlump); var colpos = ID.indexOf(":"); if (colpos !== -1) { var prefix = ID.substring(0, colpos); if (!stacktop.finallump) { stacktop.finallump = {}; } stacktop.finallump[prefix] = headlump; } } // TODO: accelerate this by grabbing original template text (requires parser // adjustment) as well as dealing with empty tags headlump.text = "<" + tagname + fluid.dumpAttributes(attrs) + (isempty && !ID ? "/>" : ">"); tagstack[tagstack.length] = headlump; if (isempty) { if (ID) { processTagEnd(); } else { --nestingdepth; tagstack.length--; } } } function processDefaultTag() { if (defstart !== -1) { if (t.firstdocumentindex === -1) { t.firstdocumentindex = lumpindex; } var text = parser.getContent().substr(defstart, defend - defstart); justended = false; var newlump = newLump(); newlump.text = text; defstart = -1; } } /** ACTUAL BODY of fluid.parseTemplate begins here **/ t = fluid.XMLViewTemplate(); init(baseURL, opts.debugMode, cutpoints_in); var idpos = template.indexOf(fluid.ID_ATTRIBUTE); if (scanStart) { var brackpos = template.indexOf(">", idpos); parser = fluid.XMLP(template.substring(brackpos + 1)); } else { parser = fluid.XMLP(template); } parseloop: // eslint-disable-line indent while (true) { var iEvent = parser.next(); switch (iEvent) { case fluid.XMLP._ELM_B: processDefaultTag(); //var text = parser.getContent().substr(parser.getContentBegin(), parser.getContentEnd() - parser.getContentBegin()); processTagStart(false, ""); break; case fluid.XMLP._ELM_E: processDefaultTag(); processTagEnd(); break; case fluid.XMLP._ELM_EMP: processDefaultTag(); //var text = parser.getContent().substr(parser.getContentBegin(), parser.getContentEnd() - parser.getContentBegin()); processTagStart(true, ""); break; case fluid.XMLP._PI: case fluid.XMLP._DTD: defstart = -1; continue; // not interested in reproducing these case fluid.XMLP._TEXT: case fluid.XMLP._ENTITY: case fluid.XMLP._CDATA: case fluid.XMLP._COMMENT: if (defstart === -1) { defstart = parser.m_cB; } defend = parser.m_cE; break; case fluid.XMLP._ERROR: fluid.setLogging(true); var message = "Error parsing template: " + parser.m_cAlt + " at line " + parser.getLineNumber(); fluid.log(message); fluid.log("Just read: " + parser.m_xml.substring(parser.m_iP - 30, parser.m_iP)); fluid.log("Still to read: " + parser.m_xml.substring(parser.m_iP, parser.m_iP + 30)); fluid.fail(message); break parseloop; case fluid.XMLP._NONE: break parseloop; } } processDefaultTag(); var excess = tagstack.length - 1; if (excess) { fluid.fail("Error parsing template - unclosed tag(s) of depth " + (excess) + ": " + fluid.transform(tagstack.splice(1, excess), function (lump) {return debugLump(lump);}).join(", ")); } return t; }; // unsupported, non-API function fluid.debugLump = function (lump) { var togo = lump.text; togo += " at "; togo += "lump line " + lump.line + " column " + lump.column + " index " + lump.lumpindex; togo += lump.parent.href === null ? "" : " in file " + lump.parent.href; return togo; }; // Public definitions begin here fluid.ID_ATTRIBUTE = "rsf:id"; // unsupported, non-API function fluid.getPrefix = function (id) { var colpos = id.indexOf(":"); return colpos === -1 ? id : id.substring(0, colpos); }; // unsupported, non-API function fluid.SplitID = function (id) { var that = {}; var colpos = id.indexOf(":"); if (colpos === -1) { that.prefix = id; } else { that.prefix = id.substring(0, colpos); that.suffix = id.substring(colpos + 1); } return that; }; // unsupported, non-API function fluid.XMLViewTemplate = function () { return { globalmap: {}, collectmap: {}, lumps: [], firstdocumentindex: -1 }; }; // TODO: find faster encoder fluid.XMLEncode = function (text) { return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;"); }; // unsupported, non-API function fluid.dumpAttributes = function (attrcopy) { var togo = ""; for (var attrname in attrcopy) { var attrvalue = attrcopy[attrname]; if (attrvalue !== null && attrvalue !== undefined) { togo += " " + attrname + "=\"" + attrvalue + "\""; } } return togo; }; // unsupported, non-API function fluid.aggregateMMap = function (target, source) { for (var key in source) { var targhas = target[key]; if (!targhas) { target[key] = []; } target[key] = target[key].concat(source[key]); } }; /* Returns a "template structure", with globalmap in the root, and a list * of entries {url, template, cutpoints} for each parsed template. */ fluid.parseTemplates = function (resourceSpec, templateList, opts) { var togo = []; opts = opts || {}; togo.globalmap = {}; for (var i = 0; i < templateList.length; ++i) { var resource = resourceSpec[templateList[i]]; var lastslash = resource.url.lastIndexOf("/"); var baseURL = lastslash === -1 ? "" : resource.url.substring(0, lastslash + 1); var template = fluid.parseTemplate(resource.resourceText, baseURL, opts.scanStart && i === 0, resource.cutpoints, opts); if (i === 0) { fluid.aggregateMMap(togo.globalmap, template.globalmap); } template.url = resource.url; template.baseURL = baseURL; template.resourceKey = resource.resourceKey; togo[i] = template; fluid.aggregateMMap(togo.globalmap, template.rootlump.downmap); } return togo; };