'use strict'; var ok = require('assert').ok; var path = require('path'); var Node = require('./ast/Node'); var COMPILER_ATTRIBUTE_HANDLERS = { whitespace: function(attr, compilerOptions) { if (attr.value === 'preserve') { compilerOptions.preserveWhitespace = true; } }, comments: function(attr, compilerOptions) { if (attr.value === 'preserve') { compilerOptions.preserveComments = true; } } }; var ieConditionalCommentRegExp = /^\[if [^]*?, args will be "color in colors" if (tagName === 'compiler-options') { var compilerOptions = this.compilerOptions; attributes.forEach(function (attr) { let attrName = attr.name; let attrValue = attr.value; let handler = COMPILER_ATTRIBUTE_HANDLERS[attrValue]; if (!handler) { throw new Error('Invalid Marko compiler option: ' + attrName + ', Allowed: ' + Object.keys(COMPILER_ATTRIBUTE_HANDLERS)); } handler(attr, compilerOptions); }); return; } this.prevTextNode = null; var node; var elDef = { tagName: tagName, argument: argument, attributes: attributes.map((attr) => { var isLiteral = false; if (attr.hasOwnProperty('literalValue')) { isLiteral = true; } var attrDef = { name: attr.name, value: isLiteral ? builder.literal(attr.literalValue) : parseExpression(attr.expression) }; if (attr.argument) { attrDef.argument = attr.argument; } return attrDef; }) }; var elNode = builder.htmlElement(elDef); var taglibLookup = context.taglibLookup; var tagDef = taglibLookup.getTag(tagName); if (tagDef) { var nodeFactoryFunc = tagDef.getNodeFactory(); if (nodeFactoryFunc) { node = nodeFactoryFunc(elNode, context); if (!(node instanceof Node)) { throw new Error('Invalid node returned from node factory for tag "' + tagName + '".'); } } } if (!node) { node = elNode; } node.pos = el.pos; var foundAttrs = {}; // Validate the attributes attributes.forEach((attr) => { let attrName = attr.name; let attrDef = taglibLookup.getAttribute(tagName, attrName); if (!attrDef) { if (tagDef) { // var isAttrForTaglib = compiler.taglibs.isTaglib(attrUri); //Tag doesn't allow dynamic attributes context.addError({ node: node, message: 'The tag "' + tagName + '" in taglib "' + getTaglibPath(tagDef.taglibId) + '" does not support attribute "' + attrName + '"' }); } return; } attr.def = attrDef; foundAttrs[attrName] = true; }); if (tagDef) { // Add default values for any attributes. If an attribute has a declared // default value and the attribute was not found on the element // then add the attribute with the specified default value tagDef.forEachAttribute(function (attrDef) { var attrName = attrDef.name; if (attrDef.hasOwnProperty('defaultValue') && !foundAttrs.hasOwnProperty(attrName)) { attributes.push({ name: attrName, value: builder.literal(attrDef.defaultValue) }); } else if (attrDef.required === true) { // TODO Only throw an error if there is no data argument provided (just HTML attributes) if (!foundAttrs.hasOwnProperty(attrName)) { context.addError({ node: node, message: 'The "' + attrName + '" attribute is required for tag "' + tagName + '" in taglib "' + getTaglibPath(tagDef.taglibId) + '".' }); } } }); } this.parentNode.appendChild(node); this.stack.push({ node: node, tag: null }); } handleEndElement(elementName) { if (elementName === 'compiler-options') { return; } this.prevTextNode = null; this.stack.pop(); } handleComment(comment) { this.prevTextNode = null; var builder = this.context.builder; var compilerOptions = this.compilerOptions; var preserveComment = (compilerOptions && compilerOptions.preserveComments === true) || isIEConditionalComment(comment); if (preserveComment) { var commentNode = builder.htmlComment(comment); this.parentNode.appendChild(commentNode); } } handleBodyTextPlaceholder(expression, escape) { this.prevTextNode = null; var builder = this.context.builder; var text = builder.text(expression, escape); this.parentNode.appendChild(text); } get parentNode() { var last = this.stack[this.stack.length-1]; return last.node; } } module.exports = Parser;