UNPKG

toloframework

Version:

Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.

209 lines (200 loc) 7.77 kB
var Tree = require("./htmltree"); var Fatal = require("./fatal"); // Void elements do not have any children. var VOID_ELEMENTS = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"]; var rxEntity = /^&[a-zA-Z]+;/; var rxStartTag = /^<([a-zA-Z_$][a-zA-Z0-9$_:\.-]*)/; var rxAttrib = /^[ \t\n\r]*([a-zA-Z_$][a-zA-Z0-9$_:-]*)([ \t\n\r]*=[ \t\n\r]*)?/; var rxEndTag = /^[ \t\n\r]*>/; var rxAutoCloseTag = /^[ \t\n\r]*\/>/; var rxCloseTag = /^<\/([a-zA-Z_$][a-zA-Z0-9$_:-]*)>/; var rxComment = /^<\!--(.+)-->/; var rxDocType = /^<\!([a-zA-Z_$][a-zA-Z0-9$_:-]*)/; var rxStartProcessing = /^<\?([a-zA-Z_$][a-zA-Z0-9$_:-]*)/; var rxEndProcessing = /^\?>/; function parse(content) { var cursor = 0, index = 0, node, root = {children: []}, stack = [root]; function append(node) { try { if (node.type == Tree.TEXT && root.children.length > 0 && root.children[root.children.length - 1].type == Tree.TEXT) { root.children[root.children - 1].text += node.text; } else { root.children.push(node); } } catch( ex ) { console.info("[tlk-htmlparser] node=...", node); throw( ex ); } } function flushText() { var text = content.substr(cursor, index - cursor); if (text.length > 0) { append({type: Tree.TEXT, text: text}); } cursor = index; } /** * @param {array...} rules Each rule is an array with two elements: * a regular expression and a function to call if that regexp * matches. */ function match() { var i, rule, rx, m, lastIndex = index; for (i = 0 ; i < arguments.length ; i++) { rule = arguments[i]; rx = rule[0]; m = rx.exec(content.substr(index)); if (m) { if (rule[1](m)) { return true; } index = lastIndex; } } return false; } function parseAttribs(node) { while (match([rxAttrib, function (m) { var name = m[1]; var value = null; index += m[0].length; if (m[2]) { // There is a value between single or double quotes. var quote = content.charAt(index), c; if (quote == '"' || quote == "'") { value = ""; index++; while (index < content.length) { c = content.charAt(index); if (c == quote) { index++; break; } if (c == '\\') { index++; c = content.charAt(index); if (index >= content.length) break; } value += c; index++; } } } node.attribs[name] = value; return true; }])); } try { while (index < content.length) { if (!match( [rxStartTag, function (m) { flushText(); node = {type: Tree.TAG, name: m[1], attribs: {}, children: [], pos: index}; index += m[0].length; parseAttribs(node); return match( [rxAutoCloseTag, function (m) { node.autoclose = true; root.children.push(node); index += m[0].length; cursor = index; return true; }], [rxEndTag, function (m) { if (VOID_ELEMENTS.indexOf(node.name.toLowerCase()) > -1) { // Void elements have no children and do not need any closing syntax. node.void = true; root.children.push(node); index += m[0].length; cursor = index; return true; } else { root.children.push(node); stack.push(node); root = node; index += m[0].length; cursor = index; return true; } }] ); }], [rxCloseTag, function (m) { if (stack.length == 1) { throw {msg: "Unexpected closing tag " + m[0] + "!", pos: index}; } if (root.name != m[1]) { throw {msg: "Invalid closing tag " + m[0] + ", expected </" + root.name + ">!", pos: index}; } flushText(); stack.pop(); root = stack[stack.length - 1]; index += m[0].length; cursor = index; return true; }], [rxEntity, function (m) { // This is an HTML entity. flushText(); append({type: Tree.ENTITY, text: m[0], pos: index}); cursor = index = index + m[0].length; return true; }], [rxComment, function (m) { flushText(); append({type: Tree.COMMENT, text: m[1], pos: index}); index += m[0].length; cursor = index; return true; }], [rxDocType, function (m) { flushText(); node = {type: Tree.DOCTYPE, name: m[1], attribs: {}, pos: index}; index += m[0].length; parseAttribs(node); return match([rxEndTag, function (m) { append(node); index += m[0].length; cursor = index; return true; }]); }], [rxStartProcessing, function (m) { flushText(); node = {type: Tree.PROCESSING, name: m[1], attribs: {}, pos: index}; index += m[0].length; parseAttribs(node); return match([rxEndProcessing, function (m) { append(node); index += m[0].length; cursor = index; return true; }]); }] )) { index++; } } flushText(); } catch (ex) { if (typeof ex.pos !== 'undefined') { Fatal.fire(ex.msg + "\n\n" + Fatal.extractCodeAtPos(content, ex.pos)); } else { Fatal.bubble(ex); } } return stack[0]; } exports.parse = parse;