infusion
Version:
Infusion is an application framework for developing flexible stuff with JavaScript
469 lines (422 loc) • 15.3 kB
JavaScript
/*
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
*/
;
// 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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\"/g, """);
};
// 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;
};