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');
class HtmlJsParser {
constructor(options) {
this.ignorePlaceholders = options && options.ignorePlaceholders === true;
}
parse(src, handlers) {
var listeners = {
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
// NOTE: The value will be all of the text between "<!" and ">""
handlers.handleCharacters('<!' + event.value + '>');
handlers.handleDocumentType(event.value);
},
onDeclaration(event) {
// Declaration (e.g. <?xml version="1.0" encoding="UTF-8" ?>)
handlers.handleCharacters('<?' + event.value + '?>');
handlers.handleDeclaration(event.value);
},
onComment(event) {
@ -78,6 +81,7 @@ class HtmlJsParser {
};
var parser = this.parser = htmljs.createParser(listeners, {
ignorePlaceholders: this.ignorePlaceholders,
isOpenTagOnly: function(tagName) {
return handlers.isOpenTagOnly(tagName);
}

View File

@ -69,7 +69,7 @@ function mergeShorthandClassNames(el, shorthandClassNames, context) {
}
class Parser {
constructor(parserImpl) {
constructor(parserImpl, options) {
ok(parserImpl, '"parserImpl" is required');
this.parserImpl = parserImpl;
@ -77,6 +77,8 @@ class Parser {
this.prevTextNode = null;
this.stack = null;
this.raw = options && options.raw === true;
// The context gets provided when parse is called
// but we store it as part of the object so that the handler
// methods have access
@ -133,27 +135,31 @@ class Parser {
argument = argument.value;
}
if (tagNameExpression) {
tagName = builder.parseExpression(tagNameExpression);
} else if (tagName === 'marko-compiler-options') {
attributes.forEach(function (attr) {
let attrName = attr.name;
let handler = COMPILER_ATTRIBUTE_HANDLERS[attrName];
var raw = this.raw;
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;
}
if (!raw) {
if (tagNameExpression) {
tagName = builder.parseExpression(tagNameExpression);
} else if (tagName === 'marko-compiler-options') {
attributes.forEach(function (attr) {
let attrName = attr.name;
let handler = COMPILER_ATTRIBUTE_HANDLERS[attrName];
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;
@ -183,11 +189,14 @@ class Parser {
}
if (valid) {
attrValue = replacePlaceholderEscapeFuncs(parsedExpression, context);
if (raw) {
attrValue = parsedExpression;
} else {
attrValue = replacePlaceholderEscapeFuncs(parsedExpression, context);
}
} else {
attrValue = null;
}
}
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) {
@ -214,15 +230,31 @@ class Parser {
});
}
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));
// TODO Retain the shorthand class names and IDs in raw mode
if (raw) {
if (el.shorthandId) {
let parsed = 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() ||
isIEConditionalComment(comment);
if (preserveComment) {
if (this.raw || preserveComment) {
var commentNode = builder.htmlComment(builder.literal(comment));
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) {
this.prevTextNode = null;
var builder = this.context.builder;

View File

@ -6,8 +6,16 @@ var Parser = require('./Parser');
var HtmlJsParser = require('./HtmlJsParser');
var Builder = require('./Builder');
var extend = require('raptor-util/extend');
var CompileContext = require('./CompileContext');
var NODE_ENV = process.env.NODE_ENV;
var defaultParser = new Parser(new HtmlJsParser());
var rawParser = new Parser(
new HtmlJsParser({
ignorePlaceholders: true
}),
{
raw: true
});
var defaultOptions = {
/**
@ -133,9 +141,15 @@ function clearCaches() {
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.compileFile = compileFile;
exports.compile = compile;
exports.parseRaw = parseRaw;
exports.defaultOptions = defaultOptions;
exports.checkUpToDate = checkUpToDate;
exports.getLastModified = getLastModified;