Added support for parsing a template to get the "raw" AST (no resolving custom tags, custom attributes, placeholders, etc.)

This commit is contained in:
Patrick Steele-Idem 2016-03-04 10:45:38 -07:00
parent 579ef6310f
commit bd51b39ce0
3 changed files with 102 additions and 34 deletions

View File

@ -2,6 +2,10 @@
var htmljs = require('htmljs-parser'); var htmljs = require('htmljs-parser');
class HtmlJsParser { class HtmlJsParser {
constructor(options) {
this.ignorePlaceholders = options && options.ignorePlaceholders === true;
}
parse(src, handlers) { parse(src, handlers) {
var listeners = { var listeners = {
onText(event) { onText(event) {
@ -54,12 +58,11 @@ class HtmlJsParser {
// Document type: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd // Document type: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
// NOTE: The value will be all of the text between "<!" and ">"" // NOTE: The value will be all of the text between "<!" and ">""
handlers.handleCharacters('<!' + event.value + '>'); handlers.handleDocumentType(event.value);
}, },
onDeclaration(event) { onDeclaration(event) {
// Declaration (e.g. <?xml version="1.0" encoding="UTF-8" ?>) handlers.handleDeclaration(event.value);
handlers.handleCharacters('<?' + event.value + '?>');
}, },
onComment(event) { onComment(event) {
@ -78,6 +81,7 @@ class HtmlJsParser {
}; };
var parser = this.parser = htmljs.createParser(listeners, { var parser = this.parser = htmljs.createParser(listeners, {
ignorePlaceholders: this.ignorePlaceholders,
isOpenTagOnly: function(tagName) { isOpenTagOnly: function(tagName) {
return handlers.isOpenTagOnly(tagName); return handlers.isOpenTagOnly(tagName);
} }

View File

@ -69,7 +69,7 @@ function mergeShorthandClassNames(el, shorthandClassNames, context) {
} }
class Parser { class Parser {
constructor(parserImpl) { constructor(parserImpl, options) {
ok(parserImpl, '"parserImpl" is required'); ok(parserImpl, '"parserImpl" is required');
this.parserImpl = parserImpl; this.parserImpl = parserImpl;
@ -77,6 +77,8 @@ class Parser {
this.prevTextNode = null; this.prevTextNode = null;
this.stack = null; this.stack = null;
this.raw = options && options.raw === true;
// The context gets provided when parse is called // The context gets provided when parse is called
// but we store it as part of the object so that the handler // but we store it as part of the object so that the handler
// methods have access // methods have access
@ -133,27 +135,31 @@ class Parser {
argument = argument.value; argument = argument.value;
} }
if (tagNameExpression) { var raw = this.raw;
tagName = builder.parseExpression(tagNameExpression);
} else if (tagName === 'marko-compiler-options') {
attributes.forEach(function (attr) {
let attrName = attr.name;
let handler = COMPILER_ATTRIBUTE_HANDLERS[attrName];
if (!handler) { if (!raw) {
context.addError({ if (tagNameExpression) {
code: 'ERR_INVALID_COMPILER_OPTION', tagName = builder.parseExpression(tagNameExpression);
message: 'Invalid Marko compiler option of "' + attrName + '". Allowed: ' + Object.keys(COMPILER_ATTRIBUTE_HANDLERS).join(', '), } else if (tagName === 'marko-compiler-options') {
pos: el.pos, attributes.forEach(function (attr) {
node: el let attrName = attr.name;
}); let handler = COMPILER_ATTRIBUTE_HANDLERS[attrName];
return;
}
handler(attr, context); if (!handler) {
}); context.addError({
code: 'ERR_INVALID_COMPILER_OPTION',
message: 'Invalid Marko compiler option of "' + attrName + '". Allowed: ' + Object.keys(COMPILER_ATTRIBUTE_HANDLERS).join(', '),
pos: el.pos,
node: el
});
return;
}
return; handler(attr, context);
});
return;
}
} }
this.prevTextNode = null; this.prevTextNode = null;
@ -183,11 +189,14 @@ class Parser {
} }
if (valid) { if (valid) {
attrValue = replacePlaceholderEscapeFuncs(parsedExpression, context); if (raw) {
attrValue = parsedExpression;
} else {
attrValue = replacePlaceholderEscapeFuncs(parsedExpression, context);
}
} else { } else {
attrValue = null; attrValue = null;
} }
} }
var attrDef = { var attrDef = {
@ -205,7 +214,14 @@ class Parser {
}) })
}; };
var node = this.context.createNodeForEl(elDef); var node;
if (raw) {
node = builder.htmlElement(elDef);
node.pos = elDef.pos;
} else {
node = this.context.createNodeForEl(elDef);
}
if (attributeParseErrors.length) { if (attributeParseErrors.length) {
@ -214,15 +230,31 @@ class Parser {
}); });
} }
if (el.shorthandClassNames) {
mergeShorthandClassNames(node, el.shorthandClassNames, context);
}
if (el.shorthandId) { // TODO Retain the shorthand class names and IDs in raw mode
if (node.hasAttribute('id')) { if (raw) {
context.addError(node, 'A shorthand ID cannot be used in conjunction with the "id" attribute'); if (el.shorthandId) {
} else { let parsed = builder.parseExpression(el.shorthandId.value);
node.setAttributeValue('id', builder.parseExpression(el.shorthandId.value)); node.rawShorthandId = parsed.value;
}
if (el.shorthandClassNames) {
node.rawShorthandClassNames = el.shorthandClassNames.map((className) => {
let parsed = builder.parseExpression(className.value);
return parsed.value;
});
}
} else {
if (el.shorthandClassNames) {
mergeShorthandClassNames(node, el.shorthandClassNames, context);
}
if (el.shorthandId) {
if (node.hasAttribute('id')) {
context.addError(node, 'A shorthand ID cannot be used in conjunction with the "id" attribute');
} else {
node.setAttributeValue('id', builder.parseExpression(el.shorthandId.value));
}
} }
} }
@ -252,12 +284,30 @@ class Parser {
var preserveComment = this.context.isPreserveComments() || var preserveComment = this.context.isPreserveComments() ||
isIEConditionalComment(comment); isIEConditionalComment(comment);
if (preserveComment) { if (this.raw || preserveComment) {
var commentNode = builder.htmlComment(builder.literal(comment)); var commentNode = builder.htmlComment(builder.literal(comment));
this.parentNode.appendChild(commentNode); this.parentNode.appendChild(commentNode);
} }
} }
handleDeclaration(value) {
this.prevTextNode = null;
var builder = this.context.builder;
var declarationNode = builder.declaration(builder.literal(value));
this.parentNode.appendChild(declarationNode);
}
handleDocumentType(value) {
this.prevTextNode = null;
var builder = this.context.builder;
var docTypeNode = builder.documentType(builder.literal(value));
this.parentNode.appendChild(docTypeNode);
}
handleBodyTextPlaceholder(expression, escape) { handleBodyTextPlaceholder(expression, escape) {
this.prevTextNode = null; this.prevTextNode = null;
var builder = this.context.builder; var builder = this.context.builder;

View File

@ -6,8 +6,16 @@ var Parser = require('./Parser');
var HtmlJsParser = require('./HtmlJsParser'); var HtmlJsParser = require('./HtmlJsParser');
var Builder = require('./Builder'); var Builder = require('./Builder');
var extend = require('raptor-util/extend'); var extend = require('raptor-util/extend');
var CompileContext = require('./CompileContext');
var NODE_ENV = process.env.NODE_ENV; var NODE_ENV = process.env.NODE_ENV;
var defaultParser = new Parser(new HtmlJsParser()); var defaultParser = new Parser(new HtmlJsParser());
var rawParser = new Parser(
new HtmlJsParser({
ignorePlaceholders: true
}),
{
raw: true
});
var defaultOptions = { var defaultOptions = {
/** /**
@ -133,9 +141,15 @@ function clearCaches() {
exports.taglibLoader.clearCache(); exports.taglibLoader.clearCache();
} }
function parseRaw(templateSrc, filename) {
var context = new CompileContext(templateSrc, filename, Builder.DEFAULT_BUILDER);
return rawParser.parse(templateSrc, context);
}
exports.createBuilder = createBuilder; exports.createBuilder = createBuilder;
exports.compileFile = compileFile; exports.compileFile = compileFile;
exports.compile = compile; exports.compile = compile;
exports.parseRaw = parseRaw;
exports.defaultOptions = defaultOptions; exports.defaultOptions = defaultOptions;
exports.checkUpToDate = checkUpToDate; exports.checkUpToDate = checkUpToDate;
exports.getLastModified = getLastModified; exports.getLastModified = getLastModified;