Initial commit for marko v3 with htmljs-parser

Work-in-progress. Lots of failing tests.
This commit is contained in:
Patrick Steele-Idem 2015-11-23 12:17:46 -07:00
parent 032a5da4fa
commit 069b3e5ba9
776 changed files with 5095 additions and 8664 deletions

View File

@ -1,16 +1,6 @@
{
"predef": [
],
"globals": {
"define": true,
"require": true
},
"node" : true,
"es5" : false,
"browser" : true,
"esnext": true,
"boss" : false,
"curly": false,
"debug": false,
@ -35,6 +25,5 @@
"latedef": "func",
"unused": "vars",
"strict": false,
"eqnull": true
}

0
bin/markoc Executable file → Normal file
View File

175
compiler/Builder.js Normal file
View File

@ -0,0 +1,175 @@
'use strict';
var isArray = Array.isArray;
var ok = require('assert').ok;
var Program = require('./ast/Program');
var TemplateRoot = require('./ast/TemplateRoot');
var FunctionDeclaration = require('./ast/FunctionDeclaration');
var FunctionCall = require('./ast/FunctionCall');
var Literal = require('./ast/Literal');
var Identifier = require('./ast/Identifier');
var If = require('./ast/If');
var ElseIf = require('./ast/ElseIf');
var Else = require('./ast/Else');
var Assignment = require('./ast/Assignment');
var BinaryExpression = require('./ast/BinaryExpression');
var Vars = require('./ast/Vars');
var Return = require('./ast/Return');
var HtmlElement = require('./ast/HtmlElement');
var HtmlAttribute = require('./ast/HtmlAttribute');
var HtmlAttributeCollection = require('./ast/HtmlAttributeCollection');
var HtmlOutput = require('./ast/HtmlOutput');
var TextOutput = require('./ast/TextOutput');
var ForEach = require('./ast/ForEach');
var Node = require('./ast/Node');
var Slot = require('./ast/Slot');
var HtmlComment = require('./ast/HtmlComment');
class Builder {
program(body) {
return new Program({body});
}
templateRoot(body) {
return new TemplateRoot({body});
}
functionDeclaration(name, params, body) {
return new FunctionDeclaration({name, params, body});
}
functionCall(callee, args) {
if (args) {
if (!isArray(args)) {
args = [args];
}
} else {
args = [];
}
return new FunctionCall({callee, args});
}
literal(value) {
return new Literal({value});
}
identifier(name) {
return new Identifier({name});
}
ifStatement(test, body, elseStatement) {
return new If({test, body, else: elseStatement});
}
elseIfStatement(test, body, elseStatement) {
return new ElseIf({
if: new If({test, body, else: elseStatement})
});
}
elseStatement(body) {
return new Else({body});
}
assignment(left, right) {
return new Assignment({left, right});
}
strictEquality(left, right) {
var operator = '===';
return new BinaryExpression({left, right, operator});
}
vars(declarations, kind) {
return new Vars({declarations, kind});
}
returnStatement(argument) {
return new Return({argument});
}
htmlElement(tagName, attributes, body, argument) {
if (typeof tagName === 'object') {
let elInfo = tagName;
tagName = elInfo.tagName;
attributes = elInfo.attributes;
body = elInfo.body;
argument = elInfo.argument;
}
if (attributes) {
if (!(attributes instanceof HtmlAttributeCollection)) {
let attrCollection = new HtmlAttributeCollection();
if (isArray(attributes)) {
// Convert the attribute list into HtmlAttributeCollection...
for (let i=0; i<attributes.length; i++) {
let attr = attributes[i];
ok(attr, 'Invalid attribute at index ' + i);
if (attr instanceof HtmlAttribute) {
attrCollection.addAttribute(attr);
} else {
attr = new HtmlAttribute(attr);
attrCollection.addAttribute(attr);
}
}
} else {
for (let attrName in attributes) {
if (attributes.hasOwnProperty(attrName)) {
let attrValue = attributes[attrName];
let attr;
if (typeof attrValue === 'object' && !(attrValue instanceof Node)) {
var attrDef = attrValue;
attrDef.name = attrName;
attr = new HtmlAttribute(attrDef);
} else {
attr = new HtmlAttribute({
name: attrName,
value: attrValue
});
}
attrCollection.addAttribute(attr);
}
}
}
attributes = attrCollection;
}
}
return new HtmlElement({tagName, attributes, body, argument});
}
htmlOutput(argument) {
return new HtmlOutput({argument});
}
textOutput(argument, escape) {
return new TextOutput({argument, escape});
}
htmlComment(comment) {
return new HtmlComment({comment});
}
forEach(varName, target, body) {
if (typeof varName === 'object') {
var options = varName;
return new ForEach(options);
} else {
return new ForEach({varName, target, body});
}
}
slot() {
return new Slot();
}
}
module.exports = Builder;

448
compiler/CodeGenerator.js Normal file
View File

@ -0,0 +1,448 @@
'use strict';
var isArray = Array.isArray;
var Node = require('./ast/Node');
var Literal = require('./ast/Literal');
var Identifier = require('./ast/Identifier');
var ok = require('assert').ok;
var Container = require('./ast/Container');
class Slot {
constructor(generator) {
this._content = null;
this._start = generator._code.length;
this._currentIndent = generator._currentIndent;
this._inFunction = generator.inFunction;
this._statement = false;
}
setContent(content, options) {
this._content = content;
if (options && options.statement) {
this._statement = true;
}
}
generateCode(generator) {
if (!this._content) {
return;
}
generator._currentIndent = this._currentIndent;
generator.inFunction = this._inFunction;
var capture = generator._beginCaptureCode();
generator.generateCode(this._content);
var slotCode = capture.end();
if (!slotCode) {
return;
}
if (this._statement) {
slotCode += '\n' + this._currentIndent;
}
var oldCode = generator._code;
var beforeCode = oldCode.substring(0, this._start);
var afterCode = oldCode.substring(this._start);
generator._code = beforeCode + slotCode + afterCode;
}
}
class Generator {
constructor(options) {
this.root = null;
this._indentStr = options.indent != null ? options.indent : ' ';
this._indentSize = this._indentStr.length;
this._code = '';
this._currentIndent = '';
this.inFunction = false;
this._bufferedWrites = null;
this.builder = options.builder;
this.walker = options.walker;
this.outputType = options.output || 'html';
this._generatorCodeFuncName = 'generate' +
this.outputType.charAt(0).toUpperCase() +
this.outputType.substring(1) +
'Code';
this._slots = [];
}
createSlot() {
var slot = new Slot(this);
this._slots.push(slot);
return slot;
}
addStaticVar(name, value) {
ok(this.root, 'Invalid state');
var templateRoot = this.root;
if (templateRoot.type !== 'TemplateRoot') {
throw new Error('Root node is not of type "TemplateRoot". Actual ' + JSON.stringify(templateRoot));
}
templateRoot.addStaticVar(name, value);
}
generateCode(node, options) {
var isRoot = this.root == null;
if (isRoot) {
this.root = node;
}
if (options) {
if (options.onError) {
this._onError = options.onError;
}
}
let generator = this;
ok(node, '"node" is required');
if (typeof node === 'string') {
this.write(node);
return;
} else if (isArray(node) || (node instanceof Container)) {
node.forEach(this.generateCode, this);
return;
}
var generateCodeFunc = node.generateCode;
if (!generateCodeFunc) {
generateCodeFunc = node[this._generatorCodeFuncName];
if (!generateCodeFunc) {
throw new Error('Missing generator method for node of type "' +
node.type +
'". Node: ' + node);
}
}
var oldCurrentNode = this._currentNode;
this._currentNode = node;
var resultNode = generateCodeFunc.call(node, this);
if (resultNode) {
// The generateCode function can optionally return either of the following:
// - An AST node
// - An array/cointainer of AST nodes
this.generateCode(resultNode);
}
this._currentNode = oldCurrentNode;
if (isRoot) {
while(this._slots.length) {
let slots = this._slots;
this._slots = [];
for (let i=slots.length-1; i>=0; i--) {
let slot = slots[i];
slot.generateCode(generator);
}
}
}
return this._code;
}
generateBlock(body) {
if (!body) {
this.write('{}');
return;
}
if (typeof body === 'function') {
body = body();
}
if (!isArray(body) && !(body instanceof Container)) {
throw new Error('Invalid body');
}
if (body.length === 0) {
this.write('{}');
return;
}
this.write('{\n')
.incIndent();
var oldCodeLength = this._code.length;
this.generateStatements(body);
if (this._bufferedWrites) {
if (this._code.length !== oldCodeLength) {
this._code += '\n';
}
this.flushBufferedWrites();
}
this.decIndent()
.writeLineIndent()
.write('}');
}
generateStatements(nodes) {
ok(nodes, '"nodes" expected');
var firstStatement = true;
nodes.forEach((node) => {
if (node instanceof Node) {
node.statement = true;
}
var startCodeLen = this._code.length;
var currentIndent = this._currentIndent;
if (!firstStatement) {
this._write('\n');
}
if (!this._code.endsWith(currentIndent)) {
this.writeLineIndent();
}
var startPos = this._code.length;
this.generateCode(node);
if (this._code.length === startPos) {
this._code = this._code.slice(0, startCodeLen);
return;
}
if (!this._code.endsWith('\n')) {
this._code += ';\n';
}
firstStatement = false;
});
}
_beginCaptureCode() {
var oldCode = this._code;
this._code = '';
return {
generator: this,
end() {
var newCode = this.generator._code;
this.generator._code = oldCode;
return newCode;
}
};
}
addWriteLiteral(value) {
let lastWrite = this._bufferedWrites ?
this._bufferedWrites[this._bufferedWrites.length-1] :
null;
if (lastWrite instanceof Literal) {
lastWrite.value += value;
return;
}
var output = new Literal({value});
if (!this._bufferedWrites) {
this._bufferedWrites = [output];
} else {
this._bufferedWrites.push(output);
}
}
addWrite(output) {
if (output instanceof Literal) {
let lastWrite = this._bufferedWrites ?
this._bufferedWrites[this._bufferedWrites.length-1] :
null;
if (lastWrite instanceof Literal) {
lastWrite.value += output.value;
return;
}
}
if (!this._bufferedWrites) {
this._bufferedWrites = [output];
} else {
this._bufferedWrites.push(output);
}
}
flushBufferedWrites(addSeparator) {
var bufferedWrites = this._bufferedWrites;
if (!bufferedWrites) {
return;
}
this._bufferedWrites = null;
if (!addSeparator && !this._code.endsWith(this._currentIndent)) {
this.writeLineIndent();
}
var len = bufferedWrites.length;
for (var i=0; i<len; i++) {
var write = bufferedWrites[i];
if (i === 0) {
this._write('out.w(');
} else {
this._write(' +\n');
this.writeLineIndent();
this._write(this._indentStr);
}
this.generateCode(write);
}
this._write(');\n');
if (addSeparator) {
this._write('\n' + this._currentIndent);
}
}
write(code) {
if (this._bufferedWrites) {
this.flushBufferedWrites(true /* add separator */);
}
this._code += code;
return this;
}
_write(code) {
this._code += code;
return this;
}
incIndent(code) {
this._currentIndent += this._indentStr;
return this;
}
decIndent(code) {
this._currentIndent = this._currentIndent.substring(
0,
this._currentIndent.length - this._indentSize);
return this;
}
writeLineIndent() {
this._code += this._currentIndent;
return this;
}
writeIndent() {
this._code += this._indentStr;
return this;
}
isLiteralNode(node) {
return node instanceof Literal;
}
isIdentifierNode(node) {
return node instanceof Identifier;
}
writeLiteral(value) {
if (value === null) {
this.write('null');
} else if (value === undefined) {
this.write('undefined');
} else if (typeof value === 'string') {
this.write(JSON.stringify(value));
} else if (value === true) {
this.write('true');
} else if (value === false) {
this.write('false');
} else if (isArray(value)) {
this.write('[\n');
this.incIndent();
for (let i=0; i<value.length; i++) {
let v = value[i];
if (v instanceof Node) {
this.generateCode(v);
} else {
this.writeLiteral(v);
}
if (i < value.length - 1) {
this.write(',\n');
} else {
this.write('\n');
}
}
this.decIndent();
this.writeLineIndent();
this.write(']\n');
} else if (typeof value === 'number') {
this.write(value.toString());
} else if (typeof value === 'object') {
this.write('{\n');
this.incIndent();
let keys = Object.keys(value);
for (let i=0; i<keys.length; i++) {
let k = keys[i];
let v = value[k];
this.write(JSON.stringify(k) + ': ');
if (v instanceof Node) {
this.generateCode(v);
} else {
this.writeLiteral(v);
}
if (i < value.length - 1) {
this.write(',\n');
} else {
this.write('\n');
}
}
this.decIndent();
this.writeLineIndent();
this.write('}\n');
}
}
isPreserveWhitespaceEnabled() {
return false;
}
addError(message) {
var node = this._currentNode;
if (this._onError) {
this._onError({
node: node,
message: message
});
} else {
throw new Error(message);
}
}
}
module.exports = Generator;

View File

@ -1,43 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
function CommentNode(comment) {
CommentNode.$super.call(this, 'comment');
this.comment = comment;
}
CommentNode.prototype = {
doGenerateCode: function (template) {
if (!this.comment) {
return;
}
template.text('<!--' + (this.comment || '') + '-->');
},
setComment: function (comment) {
this.comment = comment;
},
isTextNode: function () {
return false;
},
isElementNode: function () {
return false;
},
toString: function () {
return '<!--' + this.comment + '-->';
}
};
require('raptor-util').inherit(CommentNode, require('./Node'));
module.exports = CommentNode;

124
compiler/Compiler.js Normal file
View File

@ -0,0 +1,124 @@
'use strict';
var ok = require('assert').ok;
var HtmlJsParser = require('./HtmlJsParser');
var CodeGenerator = require('./CodeGenerator');
var Builder = require('./Builder');
var path = require('path');
var taglibLookup = require('./taglib-lookup');
var createError = require('raptor-util/createError');
class Compiler {
constructor(options) {
options = options || {};
this.builder = options.builder || new Builder();
this.parser = options.parser;
if (!this.parser) {
this.parser = new HtmlJsParser({
compiler: this
});
}
this.codeGenerator = options.codeGenerator || new CodeGenerator({
builder: this.builder
});
this._reset();
}
_reset() {
this.path = null;
this.dirname = null;
this.taglibLookup = null;
}
transformNode(node) {
try {
this.taglibLookup.forEachNodeTransformer(node, function (transformer) {
if (!node.isTransformerApplied(transformer)) {
//Check to make sure a transformer of a certain type is only applied once to a node
node.setTransformerApplied(transformer);
//Mark the node as have been transformed by the current transformer
this._transformerApplied = true;
//Set the flag to indicate that a node was transformed
// node.compiler = this;
var transformerFunc = transformer.getFunc();
transformerFunc.call(transformer, node, this); //Have the transformer process the node (NOTE: Just because a node is being processed by the transformer doesn't mean that it has to modify the parse tree)
}
}, this);
} catch (e) {
throw createError(new Error('Unable to compile template at path "' + this.path + '". Error: ' + e.message), e);
}
}
transformTree(rootNode) {
var self = this;
function transformTreeHelper(node) {
self.transformNode(node);
/*
* Now process the child nodes by looping over the child nodes
* and transforming the subtree recursively
*
* NOTE: The length of the childNodes array might change as the tree is being performed.
* The checks to prevent transformers from being applied multiple times makes
* sure that this is not a problem.
*/
node.forEachChild(function (childNode) {
if (childNode.isDetached()) {
return; //The child node might have been removed from the tree
}
transformTreeHelper(childNode);
});
}
/*
* The tree is continuously transformed until we go through an entire pass where
* there were no new nodes that needed to be transformed. This loop makes sure that
* nodes added by transformers are also transformed.
*/
do {
this._transformerApplied = false;
//Reset the flag to indicate that no transforms were yet applied to any of the nodes for this pass
transformTreeHelper(rootNode); //Run the transforms on the tree
} while (this._transformerApplied);
return rootNode;
}
compile(src, templatePath) {
ok(src);
ok(templatePath);
this._reset();
this.path = templatePath;
this.dirname = path.dirname(templatePath);
this.taglibLookup = taglibLookup.buildLookup(this.dirname);
var ast = this.parser.parse(src, this.path);
// console.log('ROOT', JSON.stringify(ast, null, 2));
var transformedAST = this.transformTree(ast);
// console.log('transformedAST', JSON.stringify(ast, null, 2));
var self = this;
var compiledSrc = this.codeGenerator.generateCode(transformedAST, {
onError: function(eventArgs) {
var node = eventArgs.node;
var message = eventArgs.message;
self.addError(node, message);
}
});
return compiledSrc;
}
addError(node, message) {
throw new Error('addError() not fully implemented. Error: ' + message); // TODO
}
}
module.exports = Compiler;

View File

@ -1,350 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var createError = require('raptor-util').createError;
var escapeXmlAttr = require('raptor-util/escapeXml').attr;
var XML_URI = 'http://www.w3.org/XML/1998/namespace';
var XML_URI_ALT = 'http://www.w3.org/XML/1998/namespace';
var forEachEntry = require('raptor-util').forEachEntry;
function isEmpty(o) {
if (!o) {
return true;
}
for (var k in o) {
if (o.hasOwnProperty(k)) {
return false;
}
}
return true;
}
function ElementNode(localName, namespace, prefix) {
ElementNode.$super.call(this, 'element');
if (!this._elementNode) {
this._elementNode = true;
this.dynamicAttributesExpressionArray = null;
this.attributesByNS = {};
this.prefix = prefix;
this.localName = localName;
this.namespace = namespace;
this.allowSelfClosing = false;
this.startTagOnly = false;
}
}
ElementNode.prototype = {
addDynamicAttributes: function(expression) {
if (!this.dynamicAttributesExpressionArray) {
this.dynamicAttributesExpressionArray = [];
}
this.dynamicAttributesExpressionArray.push(expression);
},
getQName: function () {
return this.localName ? (this.prefix ? this.prefix + ':' : '') + this.localName : null;
},
setStartTagOnly: function (startTagOnly) {
this.startTagOnly = true;
},
setAllowSelfClosing: function (allowSelfClosing) {
this.allowSelfClosing = allowSelfClosing;
},
isElementNode: function () {
return true;
},
isTextNode: function () {
return false;
},
getAllAttributes: function () {
var allAttrs = [];
forEachEntry(this.attributesByNS, function (namespace, attrs) {
forEachEntry(attrs, function (name, attr) {
allAttrs.push(attr);
});
}, this);
return allAttrs;
},
forEachAttributeAnyNS: function (callback, thisObj) {
var attributes = [];
forEachEntry(this.attributesByNS, function (namespace, attrs) {
forEachEntry(attrs, function (name, attr) {
attributes.push(attr);
});
});
attributes.forEach(callback, thisObj);
},
forEachAttributeNS: function (namespace, callback, thisObj) {
namespace = this.resolveNamespace(namespace);
var attrs = this.attributesByNS[namespace];
if (attrs) {
forEachEntry(attrs, function (name, attr) {
callback.call(thisObj, attr);
});
}
},
getAttributes: function() {
return this.getAttributesNS('');
},
getAttributesNS: function (namespace) {
var attributes = [];
if (this.attributesByNS[namespace]) {
forEachEntry(this.attributesByNS[namespace], function (name, attr) {
attributes.push(attr);
});
}
return attributes;
},
getAttribute: function (name) {
return this.getAttributeNS(null, name);
},
getAttributeNS: function (namespace, localName) {
namespace = this.resolveNamespace(namespace);
var attrNS = this.attributesByNS[namespace];
var attr = attrNS ? attrNS[localName] : undefined;
return attr ? attr.value : undefined;
},
setAttribute: function (localName, value, escapeXml) {
this.setAttributeNS(null, localName, value, null, escapeXml);
},
setAttributeNS: function (namespace, localName, value, prefix, escapeXml) {
namespace = this.resolveNamespace(namespace);
var attrNS = this.attributesByNS[namespace] || (this.attributesByNS[namespace] = {});
attrNS[localName] = {
localName: localName,
value: value,
prefix: prefix,
namespace: namespace,
escapeXml: escapeXml,
qName: prefix ? prefix + ':' + localName : localName,
name: namespace ? namespace + ':' + localName : localName,
toString: function () {
return this.name;
}
};
},
setEmptyAttribute: function (name) {
this.setAttribute(name, null);
},
removeAttribute: function (localName) {
this.removeAttributeNS(null, localName);
},
removeAttributeNS: function (namespace, localName) {
namespace = this.resolveNamespace(namespace);
var attrNS = this.attributesByNS[namespace] || (this.attributesByNS[namespace] = {});
if (attrNS) {
delete attrNS[localName];
if (isEmpty(attrNS)) {
delete this.attributesByNS[namespace];
}
}
},
removeAttributesNS: function (namespace) {
namespace = this.resolveNamespace(namespace);
delete this.attributesByNS[namespace];
},
isPreserveWhitespace: function () {
var preserveSpace = ElementNode.$super.prototype.isPreserveWhitespace.call(this);
if (preserveSpace === true) {
return true;
}
var preserveAttr = this.getAttributeNS(XML_URI, 'space') || this.getAttributeNS(XML_URI_ALT, 'space') || this.getAttribute('xml:space') === 'preserve';
if (preserveAttr === 'preserve') {
return true;
}
return preserveSpace;
},
hasAttributesAnyNS: function () {
return !isEmpty(this.attributesByNS);
},
hasAttributes: function () {
return this.hasAttributesNS('');
},
hasAttributesNS: function (namespace) {
namespace = this.resolveNamespace(namespace);
return this.attributesByNS[namespace] !== undefined;
},
hasAttribute: function (localName) {
return this.hasAttributeNS('', localName);
},
hasAttributeNS: function (namespace, localName) {
namespace = this.resolveNamespace(namespace);
var attrsNS = this.attributesByNS[namespace];
return attrsNS ? attrsNS.hasOwnProperty(localName) : false;
},
removePreserveSpaceAttr: function () {
this.removeAttributeNS(XML_URI, 'space');
this.removeAttributeNS(XML_URI_ALT, 'space');
this.removeAttribute('space');
},
setStripExpression: function (stripExpression) {
this.stripExpression = stripExpression;
},
doGenerateCode: function (template) {
this.generateBeforeCode(template);
this.generateCodeForChildren(template);
this.generateAfterCode(template);
},
generateBeforeCode: function (template) {
var preserveWhitespace = this.preserveWhitespace = this.isPreserveWhitespace();
var name = this.prefix ? this.prefix + ':' + this.localName : this.localName;
if (preserveWhitespace) {
this.removePreserveSpaceAttr();
}
var _this = this;
if (template.isExpression(name)) {
template.text('<');
template.write(name);
} else {
template.text('<' + name);
}
this.forEachAttributeAnyNS(function (attr) {
var prefix = attr.prefix;
if (!prefix && attr.namespace) {
prefix = this.resolveNamespacePrefix(attr.namespace);
}
if (prefix) {
name = prefix + (attr.localName ? ':' + attr.localName : '');
} else {
name = attr.localName;
}
if (attr.value === null || attr.value === undefined) {
template.text(' ' + name);
} else if (attr.value === '') {
// Treat empty attributes as boolean attributes
template.text(' ' + name);
} else if (template.isExpression(attr.value)) {
template.attr(name, attr.value, attr.escapeXml !== false);
} else {
var attrParts = [];
var hasExpression = false;
var invalidAttr = false;
template.parseExpression(attr.value, {
text: function (text, escapeXml) {
attrParts.push({
text: text,
escapeXml: escapeXml !== false
});
},
expression: function (expression, escapeXml) {
hasExpression = true;
attrParts.push({
expression: expression,
escapeXml: escapeXml !== false
});
},
error: function (message) {
invalidAttr = true;
_this.addError('Invalid expression found in attribute "' + name + '". ' + message);
}
});
if (invalidAttr) {
template.text(name + '="' + escapeXmlAttr(attr.value) + '"');
} else {
if (hasExpression && attrParts.length === 1) {
template.attr(name, attrParts[0].expression, attrParts[0].escapeXml !== false);
} else {
template.text(' ' + name + '="');
attrParts.forEach(function (part) {
if (part.text) {
template.text(part.escapeXml !== false ? escapeXmlAttr(part.text) : part.text);
} else if (part.expression) {
template.write(part.expression, { escapeXmlAttr: part.escapeXml !== false });
} else {
throw createError(new Error('Illegal state'));
}
});
template.text('"');
}
}
}
}, this);
if (this.dynamicAttributesExpressionArray && this.dynamicAttributesExpressionArray.length) {
this.dynamicAttributesExpressionArray.forEach(function(expression) {
template.attrs(expression);
});
}
if (this.hasChildren()) {
template.text('>');
} else {
if (this.startTagOnly) {
template.text('>');
} else if (this.allowSelfClosing) {
template.text('/>');
}
}
},
generateAfterCode: function (template) {
var name = this.prefix ? this.prefix + ':' + this.localName : this.localName;
if (template.isExpression(name)) {
template.text('</');
template.write(name);
template.text('>');
} else {
if (this.hasChildren()) {
template.text('</' + name + '>');
} else {
if (!this.startTagOnly && !this.allowSelfClosing) {
template.text('></' + name + '>');
}
}
}
},
toString: function () {
return '<' + (this.prefix ? this.prefix + ':' + this.localName : this.localName) + '>';
}
};
require('raptor-util').inherit(ElementNode, require('./Node'));
Object.defineProperty(ElementNode.prototype, 'tagName', {
get: function() {
return this._localName;
},
set: function(value) {
this._localName = value;
}
});
Object.defineProperty(ElementNode.prototype, 'localName', {
get: function() {
return this._localName;
},
set: function(value) {
this._localName = value;
}
});
Object.defineProperty(ElementNode.prototype, 'qName', {
get: function() {
if (this.prefix) {
return this.prefix + ':' + this.localName;
} else {
return this.localName;
}
}
});
module.exports = ElementNode;

View File

@ -1,19 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module.exports = {
'ELEMENT': 'ELEMENT',
'ATTRIBUTE': 'ATTRIBUTE'
};

View File

@ -1,51 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var createError = require('raptor-util').createError;
var operatorsRegExp = /"(?:[^"]|\\")*"|'(?:[^']|\\')*'|\s+(?:and|or|lt|gt|eq|ne|lt|gt|ge|le)\s+/g;
var replacements = {
'and': ' && ',
'or': ' || ',
'eq': ' === ',
'ne': ' !== ',
'lt': ' < ',
'gt': ' > ',
'ge': ' >= ',
'le': ' <= '
};
function handleBinaryOperators(str) {
return str.replace(operatorsRegExp, function (match) {
return replacements[match.trim()] || match;
});
}
function Expression(expression, replaceSpecialOperators) {
if (expression == null) {
throw createError(new Error('expression argument is required'));
}
if (replaceSpecialOperators !== false && typeof expression === 'string') {
expression = handleBinaryOperators(expression);
}
this.expression = expression;
}
Expression.prototype = {
getExpression: function () {
return this.expression;
},
toString: function () {
return this.expression.toString();
}
};
module.exports = Expression;

75
compiler/HtmlJsParser.js Normal file
View File

@ -0,0 +1,75 @@
'use strict';
var htmljs = require('htmljs-parser');
var Parser = require('./Parser');
class HtmlJsParser extends Parser {
constructor(options) {
super(options);
}
doParse(src) {
var self = this;
var parser = htmljs.createParser({
ontext(event) {
self.handleCharacters(event.text);
},
oncontentplaceholder(event) {
// placeholder within content
self.handleBodyTextPlaceholder(event.expression, event.escape);
},
onnestedcontentplaceholder(event) {
// placeholder within string that is within content placeholder
},
onattributeplaceholder(event) {
// placeholder within attribute
},
oncdata(event) {
self.handleCharacters(event.text);
},
onopentag(event) {
var tagName = event.tagName;
var argument = event.argument;
var attributes = event.attributes;
self.handleStartElement({tagName, argument, attributes});
},
onclosetag(event) {
var tagName = event.tagName;
self.handleEndElement(tagName);
},
ondtd(event) {
// DTD (e.g. <DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0//EN">)
self.handleText(event.dtd);
},
ondeclaration(event) {
// Declaration (e.g. <?xml version="1.0" encoding="UTF-8" ?>)
self.handleText(event.declaration);
},
oncomment(event) {
// Text within XML comment
self.handleComment(event.text);
},
onerror(event) {
// Error
}
});
parser.parse(src);
}
getPos() {
return this.createPos(0); // TODO return proper position
}
}
module.exports = HtmlJsParser;

View File

@ -1,575 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var createError = require('raptor-util').createError;
var forEachEntry = require('raptor-util').forEachEntry;
var extend = require('raptor-util').extend;
var isArray = Array.isArray;
var EscapeXmlContext = require('./EscapeXmlContext');
function Node(nodeType) {
if (!this.nodeType) {
this._isRoot = false;
this.preserveWhitespace = null;
this.wordWrapEnabled = null;
this.escapeXmlBodyText = null;
this.escapeXmlContext = null;
this.nodeType = nodeType;
this.parentNode = null;
this.previousSibling = null;
this.nextSibling = null;
this.firstChild = null;
this.lastChild = null;
this.namespaceMappings = {};
this.prefixMappings = {};
this.transformersApplied = {};
this.properties = {};
this.beforeCode = [];
this.afterCode = [];
this.data = {}; // Property for associating arbitrary data with a node
this.flags = null;
}
}
Node.isNode = function(node) {
return node.__NODE === true;
};
Node.prototype = {
/**
* Marks this node with an arbitrary flag
* @param {String} flag The flag name
*/
setFlag: function(flag) {
if (!this.flags) {
this.flags = {};
}
this.flags[flag] = true;
},
/**
* Checks if this node has been marked with an arbitrary flag
* @param {String} flag The flag name
* @return {Boolean} True, if marked. False, otherwise.
*/
hasFlag: function(flag) {
return this.flags ? this.flags.hasOwnProperty(flag) : false;
},
/**
* Marks this node as being the root node of the tmeplate
* @param {Boolean} isRoot
*/
setRoot: function (isRoot) {
this._isRoot = isRoot;
},
/**
* Gets the position information associated with this node.
*
* The returned position object has the following properties:
* - filePath
* - line;
* - column;
*/
getPosition: function () {
var pos = this.pos || this.getProperty('pos') || {
toString: function () {
return '(unknown position)';
}
};
return pos;
},
/**
* Stores position information with this node.
*/
setPosition: function (pos) {
this.pos = pos;
},
/**
* Associates a compile-time error with this node that will be reported by the comiler.
*/
addError: function (error) {
var compiler = this.compiler;
var curNode = this;
while (curNode != null && !compiler) {
compiler = curNode.compiler;
if (compiler) {
break;
}
curNode = curNode.parentNode;
}
if (!compiler) {
throw createError(new Error('Template compiler not set for node ' + this));
}
var pos = this.getPosition();
compiler.addError(error + ' (' + this.toString() + ')', pos);
},
resolveNamespace: function(namespace) {
return namespace || '';
},
setProperty: function (name, value) {
this.properties[name] = value;
},
setProperties: function (props) {
if (!props) {
return;
}
extend(this.properties, props);
},
getProperties: function () {
return this.properties;
},
hasProperty: function (name) {
return this.properties.hasOwnProperty(name);
},
forEachProperty: function (callback, thisObj) {
forEachEntry(this.properties, callback, this);
},
getProperty: function (name) {
return this.properties[name];
},
removeProperty: function (name) {
delete this.properties[name];
},
/**
* Loops over the child nodes of this node
* @param {Function} callback A callback function
* @param {Boolean} thisObj The "this" for the callback function
*/
forEachChild: function (callback, thisObj) {
if (!this.firstChild) {
return;
}
var children = [];
var curChild = this.firstChild;
while (curChild) {
children.push(curChild);
curChild = curChild.nextSibling;
}
for (var i = 0, len = children.length; i < len; i++) {
curChild = children[i];
if (curChild.parentNode === this) {
//Make sure the node is still a child of this node
if (false === callback.call(thisObj, curChild)) {
return;
}
}
}
},
getExpression: function (template, childrenOnly, escapeXml, asFunction) {
if (!template) {
throw createError(new Error('template argument is required'));
}
var _this = this;
var methodCall;
if (escapeXml !== false) {
methodCall = 'out.captureString(';
} else {
methodCall = '__helpers.c(out, ';
}
return template.makeExpression({
toString: function () {
return template.captureCode(function () {
if (asFunction) {
template.code('function() {\n').code(template.indentStr(2) + 'return ' + methodCall + 'function() {\n').indent(3, function () {
if (childrenOnly === true) {
_this.generateCodeForChildren(template);
} else {
_this.generateCode(template);
}
}).code(template.indentStr(2) + '});\n').code(template.indentStr() + '}');
} else {
template.code(methodCall + 'function() {\n').indent(function () {
if (childrenOnly === true) {
_this.generateCodeForChildren(template);
} else {
_this.generateCode(template);
}
}).code(template.indentStr() + '})');
}
});
}
});
},
getBodyContentExpression: function (template, escapeXml) {
return this.getExpression(template, true, escapeXml, false);
},
getBodyContentFunctionExpression: function (template, escapeXml) {
return this.getExpression(template, true, escapeXml, true);
},
isTransformerApplied: function (transformer) {
return this.transformersApplied[transformer.id] === true;
},
setTransformerApplied: function (transformer) {
this.transformersApplied[transformer.id] = true;
},
hasChildren: function () {
return this.firstChild != null;
},
appendChild: function (childNode) {
if (childNode.parentNode) {
childNode.parentNode.removeChild(childNode);
}
if (!this.firstChild) {
this.firstChild = this.lastChild = childNode;
childNode.nextSibling = null;
childNode.previousSibling = null;
} else {
this.lastChild.nextSibling = childNode;
childNode.previousSibling = this.lastChild;
this.lastChild = childNode;
}
childNode.parentNode = this;
},
appendChildren: function (childNodes) {
if (!childNodes) {
return;
}
childNodes.forEach(function (childNode) {
this.appendChild(childNode);
}, this);
},
isRoot: function () {
return this._isRoot === true;
},
detach: function () {
if (this.parentNode) {
this.parentNode.removeChild(this);
}
},
removeChild: function (childNode) {
if (childNode.parentNode !== this) {
//Check if the child node is a child of the parent
return null;
}
var previousSibling = childNode.previousSibling;
var nextSibling = childNode.nextSibling;
if (this.firstChild === childNode && this.lastChild === childNode) {
//The child node is the one and only child node being removed
this.firstChild = this.lastChild = null;
} else if (this.firstChild === childNode) {
//The child node being removed is the first child and there is another child after it
this.firstChild = this.firstChild.nextSibling;
//Make the next child the first child
this.firstChild.previousSibling = null;
} else if (this.lastChild === childNode) {
//The child node being removed is the last child and there is another child before it
this.lastChild = this.lastChild.previousSibling;
//Make the previous child the last child
this.lastChild.nextSibling = null;
} else {
previousSibling.nextSibling = nextSibling;
nextSibling.previousSibling = previousSibling;
}
//Make sure the removed node is completely detached
childNode.parentNode = null;
childNode.previousSibling = null;
childNode.nextSibling = null;
return childNode;
},
removeChildren: function () {
while (this.firstChild) {
this.removeChild(this.firstChild);
}
},
replaceChild: function (newChild, replacedChild) {
if (newChild === replacedChild) {
return false;
}
if (!replacedChild) {
return false;
}
if (replacedChild.parentNode !== this) {
return false; //The parent does not have the replacedChild as a child... nothing to do
}
if (this.firstChild === replacedChild && this.lastChild === replacedChild) {
this.firstChild = newChild;
this.lastChild = newChild;
newChild.previousSibling = null;
newChild.nextSibling = null;
} else if (this.firstChild === replacedChild) {
newChild.nextSibling = replacedChild.nextSibling;
replacedChild.nextSibling.previousSibling = newChild;
this.firstChild = newChild;
} else if (this.lastChild === replacedChild) {
newChild.previousSibling = replacedChild.previousSibling;
newChild.nextSibling = null;
replacedChild.previousSibling.nextSibling = newChild;
this.lastChild = newChild;
} else {
replacedChild.nextSibling.previousSibling = newChild;
replacedChild.previousSibling.nextSibling = newChild;
newChild.nextSibling = replacedChild.nextSibling;
newChild.previousSibling = replacedChild.previousSibling;
}
newChild.parentNode = this;
replacedChild.parentNode = null;
replacedChild.previousSibling = null;
replacedChild.nextSibling = null;
return true;
},
insertAfter: function (node, referenceNode) {
if (!node) {
return false;
}
if (referenceNode && referenceNode.parentNode !== this) {
return false;
}
if (isArray(node)) {
node.forEach(function (node) {
this.insertAfter(node, referenceNode);
referenceNode = node;
}, this);
return true;
}
if (node === referenceNode) {
return false;
}
if (referenceNode === this.lastChild) {
this.appendChild(node);
return true;
}
if (node.parentNode) {
node.parentNode.removeChild(node);
}
if (!referenceNode || referenceNode === this.lastChild) {
this.appendChild(node);
return true;
} else {
referenceNode.nextSibling.previousSibling = node;
node.nextSibling = referenceNode.nextSibling;
node.previousSibling = referenceNode;
referenceNode.nextSibling = node;
}
node.parentNode = this;
return true;
},
insertBefore: function (node, referenceNode) {
if (!node) {
return false;
}
if (referenceNode && referenceNode.parentNode !== this) {
return false;
}
if (isArray(node)) {
var nodes = node;
var i;
for (i = nodes.length - 1; i >= 0; i--) {
this.insertBefore(nodes[i], referenceNode);
referenceNode = nodes[i];
}
return true;
}
if (node === referenceNode) {
return false;
}
if (node.parentNode) {
node.parentNode.removeChild(node);
}
if (!referenceNode) {
this.appendChild(node);
} else if (this.firstChild === referenceNode) {
this.firstChild = node;
this.firstChild.nextSibling = referenceNode;
this.firstChild.previousSibling = null;
referenceNode.previousSibling = this.firstChild;
node.parentNode = this;
} else {
this.insertAfter(node, referenceNode.previousSibling);
}
return true;
},
__NODE: true,
isTextNode: function () {
return false;
},
isElementNode: function () {
return false;
},
setStripExpression: function (stripExpression) {
this.stripExpression = stripExpression;
},
addBeforeCode: function (code) {
this.beforeCode.push(code);
},
addAfterCode: function (code) {
this.afterCode.push(code);
},
generateCode: function (template) {
this.compiler = template.compiler;
if (this.beforeCode.length) {
this.beforeCode.forEach(function (code) {
template.indent().code(code).code('\n');
});
}
var preserveWhitespace = this.isPreserveWhitespace();
if (preserveWhitespace == null) {
preserveWhitespace = template.options.preserveWhitespace;
if (preserveWhitespace === true || preserveWhitespace && preserveWhitespace['*']) {
this.setPreserveWhitespace(true);
} else {
this.setPreserveWhitespace(false);
}
}
var wordWrapEnabled = this.isWordWrapEnabled();
if (wordWrapEnabled == null) {
wordWrapEnabled = template.options.wordWrapEnabled;
if (wordWrapEnabled !== false) {
this.setWordWrapEnabled(true);
}
}
if (this.isEscapeXmlBodyText() == null) {
this.setEscapeXmlBodyText(true);
}
try {
if (!this.stripExpression || this.stripExpression.toString() === 'false') {
this.doGenerateCode(template);
} else if (this.stripExpression.toString() === 'true') {
this.generateCodeForChildren(template);
} else {
//There is a strip expression
if (!this.generateBeforeCode || !this.generateAfterCode) {
this.addError('The c-strip directive is not supported for node ' + this);
this.generateCodeForChildren(template);
return;
}
var nextStripVarId = template.data.nextStripVarId || (template.data.nextStripVarId = 0);
template.data.nextStripVarId++;
var varName = '__strip' + nextStripVarId++;
template.statement('var ' + varName + ' = !(' + this.stripExpression + ');');
template.statement('if (' + varName + ') {').indent(function () {
this.generateBeforeCode(template);
}, this).line('}');
this.generateCodeForChildren(template);
template.statement('if (' + varName + ') {').indent(function () {
this.generateAfterCode(template);
}, this).line('}');
}
} catch (e) {
throw createError(new Error('Unable to generate code for node ' + this + ' at position [' + this.getPosition() + ']. Exception: ' + e), e);
}
if (this.afterCode.length) {
this.afterCode.forEach(function (code) {
template.indent().code(code).code('\n');
});
}
},
isPreserveWhitespace: function () {
return this.preserveWhitespace;
},
setPreserveWhitespace: function (preserve) {
this.preserveWhitespace = preserve;
},
isWordWrapEnabled: function () {
return this.wordWrapEnabled;
},
setWordWrapEnabled: function (enabled) {
this.wordWrapEnabled = enabled;
},
doGenerateCode: function (template) {
this.generateCodeForChildren(template);
},
generateCodeForChildren: function (template, indent) {
if (!template) {
throw createError(new Error('The "template" argument is required'));
}
if (indent === true) {
template.incIndent();
}
this.forEachChild(function (childNode) {
if (childNode.isPreserveWhitespace() == null) {
childNode.setPreserveWhitespace(this.isPreserveWhitespace() === true);
}
if (childNode.isWordWrapEnabled() == null) {
childNode.setWordWrapEnabled(this.isWordWrapEnabled() === true);
}
if (childNode.isEscapeXmlBodyText() == null) {
childNode.setEscapeXmlBodyText(this.isEscapeXmlBodyText() !== false);
}
if (childNode.getEscapeXmlContext() == null) {
childNode.setEscapeXmlContext(this.getEscapeXmlContext() || require('./EscapeXmlContext').ELEMENT);
}
childNode.generateCode(template);
}, this);
if (indent === true) {
template.decIndent();
}
},
addNamespaceMappings: function (namespaceMappings) {
if (!namespaceMappings) {
return;
}
var existingNamespaceMappings = this.namespaceMappings;
var prefixMappings = this.prefixMappings;
forEachEntry(namespaceMappings, function (prefix, namespace) {
existingNamespaceMappings[prefix] = namespace;
prefixMappings[namespace] = prefix;
});
},
hasNamespacePrefix: function (namespace) {
return this.prefixMappings.hasOwnProperty(namespace);
},
resolveNamespacePrefix: function (namespace) {
var prefix = this.prefixMappings[namespace];
return !prefix && this.parentNode ? this.parentNode.resolveNamespacePrefix() : prefix;
},
forEachNamespace: function (callback, thisObj) {
forEachEntry(this.namespaceMappings, callback, thisObj);
},
getNodeClass: function () {
return this.nodeClass || this.constructor;
},
setNodeClass: function (nodeClass) {
this.nodeClass = nodeClass;
},
prettyPrintTree: function () {
var out = [];
function printNode(node, indent) {
out.push(indent + node.toString() + '\n');
node.forEachChild(function (child) {
printNode(child, indent + ' ');
});
}
printNode(this, '');
return out.join('');
},
setEscapeXmlBodyText: function (escapeXmlBodyText) {
this.escapeXmlBodyText = escapeXmlBodyText;
},
isEscapeXmlBodyText: function () {
return this.escapeXmlBodyText;
},
setEscapeXmlContext: function (escapeXmlContext) {
if (typeof escapeXmlContext === 'string') {
escapeXmlContext = EscapeXmlContext[escapeXmlContext.toUpperCase()];
}
this.escapeXmlContext = escapeXmlContext;
},
getEscapeXmlContext: function () {
return this.escapeXmlContext;
}
};
module.exports = Node;

View File

@ -1,234 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var TextNode = require('./TextNode');
var ElementNode = require('./ElementNode');
var CommentNode = require('./CommentNode');
var charProps = require('char-props');
var path = require('path');
var ieConditionalCommentRegExp = /^\[if [^]*?<!\[endif\]$/;
// IE conditional comment format: <!--[if expression]> HTML <![endif]-->;
function isIEConditionalComment(comment) {
return ieConditionalCommentRegExp.test(comment);
}
function getRelativePath(absolutePath) {
if (typeof window === 'undefined') {
absolutePath = path.resolve(process.cwd(), absolutePath);
return path.relative(process.cwd(), absolutePath);
} else {
return absolutePath;
}
}
function Pos(filePath, line, column) {
this.filePath = getRelativePath(filePath);
this.line = line;
this.column = column;
}
Pos.prototype = {
toString: function() {
return this.filePath + ":" + this.line + ":" + this.column;
}
};
function ParseTreeBuilder(taglibs) {
this.taglibs = taglibs;
this.rootNode = null;
this.prevTextNode = null;
this.parentNode = null;
this.src = null;
this.filePath = null;
this.charProps = null;
this.nsStack = [];
this.compilerOptions = undefined;
}
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;
}
}
};
ParseTreeBuilder.prototype = {
createPos: function(line, column) {
if (arguments.length === 1) {
var index = arguments[0];
if (!this.charProps) {
this.charProps = charProps(this.src);
}
line = this.charProps.lineAt(index)+1;
column = this.charProps.columnAt(index);
}
return new Pos(this.filePath, line, column);
},
parse: function(src, filePath) {
this.src = src;
this.filePath = filePath;
this.doParse(src, filePath);
var rootNode = this.rootNode;
// Cleanup
this.src = null;
this.filePath = null;
this.charProps = null;
this.rootNode = null;
this.prevTextNode = null;
this.parentNode = null;
this.nsStack = [];
// Put the compiler options into the rootNode so that
// TemplateCompiler has access to these
rootNode.compilerOptions = this.compilerOptions;
return rootNode;
},
handleCharacters: function(t) {
if (!this.parentNode) {
return; //Some bad XML parsers allow text after the ending element...
}
if (this.prevTextNode) {
this.prevTextNode.text += t;
} else {
this.prevTextNode = new TextNode(t);
this.prevTextNode.pos = this.getPos();
this.parentNode.appendChild(this.prevTextNode);
}
},
handleStartElement: function(el, attributes) {
var self = this;
if (el.localName === 'compiler-options') {
attributes.forEach(function (attr) {
var attrLocalName = attr.localName;
var attrPrefix = attr.prefix;
var handler;
if (attrPrefix || ((handler = COMPILER_ATTRIBUTE_HANDLERS[attrLocalName]) === undefined)) {
var attrName = attrPrefix;
attrName = (attrName) ? attrName + ':' + attrLocalName : attrLocalName;
throw new Error('Invalid Marko compiler option: ' + attrName + ', Allowed: ' + Object.keys(COMPILER_ATTRIBUTE_HANDLERS));
}
handler(attr, self.compilerOptions || (self.compilerOptions = {}));
}, this);
return;
}
this.prevTextNode = null;
var namespaceMappings = this.nsStack.length ? Object.create(this.nsStack[this.nsStack.length-1]) : {};
this.nsStack.push(namespaceMappings);
attributes.forEach(function (attr) {
if (attr.prefix === 'xmlns') {
var nsPrefix = attr.localName;
var targetNS = attr.value;
namespaceMappings[nsPrefix] = targetNS;
}
}, this);
function getNS(node) {
if (node.namespace) {
return node.namespace;
} else if (node.prefix) {
if (node.prefix === 'xml') {
return 'http://www.w3.org/XML/1998/namespace';
}
return namespaceMappings[node.prefix] || node.prefix;
}
else {
return '';
}
}
var elNS = getNS(el);
var elementNode = new ElementNode(
el.localName,
elNS,
el.prefix);
elementNode.pos = this.getPos();
if (this.parentNode) {
this.parentNode.appendChild(elementNode);
} else {
elementNode.setRoot(true);
if (!elNS && el.localName === 'template') {
elementNode.localName = 'c-template';
}
this.rootNode = elementNode;
}
attributes.forEach(function (attr) {
var attrNS = getNS(attr);
var attrLocalName = attr.localName;
var attrPrefix = attr.prefix;
elementNode.setAttributeNS(attrNS, attrLocalName, attr.value, attrPrefix);
}, this);
this.parentNode = elementNode;
},
handleEndElement: function(elementName) {
if (elementName === 'compiler-options') {
return;
}
this.prevTextNode = null;
this.parentNode = this.parentNode.parentNode;
this.nsStack.pop();
},
handleComment: function(comment) {
var compilerOptions = this.compilerOptions;
var preserveComment = (compilerOptions && compilerOptions.preserveComments === true) ||
isIEConditionalComment(comment);
if (preserveComment) {
var commentNode = new CommentNode(comment);
this.parentNode.appendChild(commentNode);
}
},
getRootNode: function () {
return this.rootNode;
}
};
module.exports = ParseTreeBuilder;

View File

@ -1,125 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var htmlparser = require("htmlparser2");
var forEachEntry = require('raptor-util').forEachEntry;
var parserOptions = {
recognizeSelfClosing: true,
recognizeCDATA: true,
lowerCaseTags: false,
lowerCaseAttributeNames: false,
xmlMode: false
};
function splitName(name) {
var parts = name.split(':');
if (parts.length === 1) {
return {
localName: name
};
}
else if (parts.length === 2) {
return {
prefix: parts[0],
localName: parts[1]
};
}
}
var entities = {
quot: '"',
lt: '<',
gt: '>',
amp: '&'
};
function decodeEntities(data) {
// match numeric, hexadecimal & named entities
return data.replace(/&(#\d+|#x[0-9a-fA-F]+|[a-zA-Z0-9]+);/g, function(match, entityName) {
return entities[entityName] || '${entity:' + entityName + '}';
});
}
function ParseTreeBuilderHtml(taglibs) {
ParseTreeBuilderHtml.$super.apply(this, arguments);
this.parser = null;
}
ParseTreeBuilderHtml.prototype = {
getPos: function() {
return this.parser ? this.createPos(this.parser.startIndex) : null;
},
doParse: function (src, filePath) {
var _this = this;
// Create a pseudo root node
this.handleStartElement(splitName('c-template'), []);
var parser = this.parser = new htmlparser.Parser({
onopentag: function(name, attribs){
var el = splitName(name);
var attributes = [];
forEachEntry(attribs, function(name, value) {
var attr = splitName(name);
attr.value = decodeEntities(value);
attributes.push(attr);
});
if (name.toLowerCase() === 'script') {
attributes.push({
localName: 'c-escape-xml',
value: 'false'
});
}
_this.handleStartElement(el, attributes);
},
onprocessinginstruction: function(name, data) {
_this.handleCharacters('${startTag:' + data + '}');
// _this.handleCharacters(data);
// _this.handleCharacters('${entity:gt}');
},
// oncdatastart: function() {
// console.log('oncdatastart: ', arguments);
// },
// oncdataend: function() {
// console.log('oncommentend: ', arguments);
// },
ontext: function(text){
_this.handleCharacters(decodeEntities(text));
},
onclosetag: function(name){
_this.handleEndElement(name);
},
oncomment: function(comment) {
_this.handleComment(comment);
}
}, parserOptions);
parser.write(src);
parser.end();
// End the pseudo root node:
_this.handleEndElement();
}
};
require('raptor-util').inherit(ParseTreeBuilderHtml, require('./ParseTreeBuilder'));
module.exports = ParseTreeBuilderHtml;

View File

@ -1,107 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var sax = require("sax");
var extend = require('raptor-util/extend');
function ParseTreeBuilderXml(taglibs) {
ParseTreeBuilderXml.$super.apply(this, arguments);
this.parser = null;
this.filePath = null;
}
ParseTreeBuilderXml.prototype = {
getPos: function() {
var parser = this.parser;
var filePath = this.filePath;
var line = parser.line + 1;
return {
line: line,
column: parser.column,
filePath: filePath,
toString: function() {
return this.filePath + ":" + this.line + ":" + this.column;
}
};
},
doParse: function (src, filePath) {
this.filePath = filePath;
var parser = this.parser = sax.parser(true /*strict*/, {
trim: false,
normalize: false,
lowercasetags: false,
xmlns: true
});
var _this = this;
extend(parser, {
onerror: function(e) {
throw e;
},
ontext: function(text) {
text = text.replace(/\r\n|\r/g, "\n");
_this.handleCharacters(text);
},
oncdata: function(text) {
text = text.replace(/\r\n|\r/g, "\n");
_this.handleCharacters(text);
},
onopentag: function (node) {
var el = {
namespace: node.uri,
prefix: node.prefix,
localName: node.local
};
var attributes = Object.keys(node.attributes).map(function(attrName) {
var attr = node.attributes[attrName];
return {
namespace: attr.uri,
localName: attr.local,
prefix: attr.prefix,
value: attr.value
};
});
_this.handleStartElement(el, attributes);
},
onclosetag: function (name) {
_this.handleEndElement(name);
},
oncomment: function (comment) {
}
});
parser.write(src).close();
}
};
require('raptor-util').inherit(ParseTreeBuilderXml, require('./ParseTreeBuilder'));
module.exports = ParseTreeBuilderXml;

220
compiler/Parser.js Normal file
View File

@ -0,0 +1,220 @@
'use strict';
var charProps = require('char-props');
var path = require('path');
var ok = require('assert').ok;
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 [^]*?<!\[endif\]$/;
function isIEConditionalComment(comment) {
return ieConditionalCommentRegExp.test(comment);
}
function parseExpression(expression) {
// TODO Build an AST from the String expression
return expression;
}
function getRelativePath(absolutePath) {
if (typeof window === 'undefined') {
absolutePath = path.resolve(process.cwd(), absolutePath);
return path.relative(process.cwd(), absolutePath);
} else {
return absolutePath;
}
}
class Pos {
constructor(path, line, column) {
this.path = getRelativePath(path);
this.line = line;
this.column = column;
}
toString() {
return this.path + ":" + this.line + ":" + this.column;
}
}
class Parser {
constructor(options) {
ok(options, '"options" is required');
ok(options.compiler, '"options.compiler" is required');
ok(options.compiler.builder, '"options.compiler.builder" is required');
this.compiler = options.compiler;
this.builder = this.compiler.builder;
this.taglibLookup = this.compiler.taglibLookup;
this._reset();
}
_reset() {
// Cleanup
this.src = null;
this.path = null;
this.charProps = null;
this.prevTextNode = null;
this.compilerOptions = {};
this.stack = [];
}
parse(src, path) {
ok(path, '"path" is required');
var rootNode = this.builder.templateRoot();
this.stack.push({
node: rootNode
});
this.src = src;
this.path = path;
this.doParse(src, path);
this._reset();
return rootNode;
}
handleCharacters(text) {
if (this.prevTextNode && this.prevTextNode.isLiteral()) {
this.prevTextNode.appendText(text);
} else {
this.prevTextNode = this.builder.textOutput(this.builder.literal(text));
this.prevTextNode.pos = this.getPos();
this.parentNode.appendChild(this.prevTextNode);
}
}
handleStartElement(el) {
var builder = this.builder;
var tagName = el.tagName;
var attributes = el.attributes;
var argument = el.argument; // e.g. For <for(color in colors)>, 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 compiler = this.compiler;
var tagDef = compiler.taglibLookup.getTag(tagName);
if (tagDef) {
var nodeFactoryFunc = tagDef.getNodeFactory();
if (nodeFactoryFunc) {
node = nodeFactoryFunc(elDef, compiler);
}
}
if (!node) {
node = builder.htmlElement(elDef);
}
node.pos = this.getPos();
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) {
var compilerOptions = this.compilerOptions;
var preserveComment = (compilerOptions && compilerOptions.preserveComments === true) ||
isIEConditionalComment(comment);
if (preserveComment) {
var commentNode = this.builder.htmlComment(comment);
this.parentNode.appendChild(commentNode);
}
}
handleBodyTextPlaceholder(expression, escape) {
var textOutput = this.builder.textOutput(expression, escape);
this.parentNode.appendChild(textOutput);
}
createPos(line, column) {
if (arguments.length === 1) {
var index = arguments[0];
if (!this.charProps) {
this.charProps = charProps(this.src);
}
line = this.charProps.lineAt(index)+1;
column = this.charProps.columnAt(index);
}
return new Pos(this.path, line, column);
}
get parentNode() {
var last = this.stack[this.stack.length-1];
return last.node;
}
}
module.exports = Parser;

View File

@ -1,593 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var createError = require('raptor-util').createError;
var nodePath = require('path');
var stringify = require('raptor-json/stringify');
var StringBuilder = require('raptor-strings/StringBuilder');
var Expression = require('./Expression');
var arrayFromArguments = require('raptor-util').arrayFromArguments;
var INDENT = ' ';
function writeArg(writer, arg) {
if (typeof arg === 'string') {
writer._code.append(arg);
} else if (typeof arg === 'boolean') {
writer._code.append(arg ? 'true' : 'false');
} else if (typeof arg === 'function') {
arg();
} else if (arg instanceof Expression) {
writer._code.append( arg.toString() );
} else if (arg) {
writer._code.append(arg.toString());
} else {
throw createError(new Error('Illegal arg: ' + arg.toString()));
}
}
function writeArgs(writer, args) {
for (var i=0, len=args.length; i<len; i++) {
var arg = args[i];
if (i !== 0) {
writer._code.append(', ');
}
writeArg(writer, arg);
}
}
function safeVarName(varName) {
return varName.replace(/[^A-Za-z0-9_]/g, '_').replace(/^[0-9]+/, function(match) {
var str = '';
for (var i=0; i<match.length; i++) {
str += '_';
}
return str;
});
}
/**
* This class is used internally to manage how code and static text is added
* to the compiled text. It has logic to group up contiguous blocks of static
* text so that the static text is written out as a single string. It will
* also change writes.
*
* For example:
* out.w('foo')
* .w('bar')
*
* Instead of:
* out.w('foo');
* out.w('bar');
*
*/
function CodeWriter(concatWrites, indent) {
this._indent = indent != null ? indent : INDENT + INDENT;
this._code = new StringBuilder();
this.firstStatement = true;
this._bufferedText = null;
this._bufferedWrites = null;
this.concatWrites = concatWrites;
}
CodeWriter.prototype = {
write: function (expression) {
this.flushText();
if (!this._bufferedWrites) {
this._bufferedWrites = [];
}
this._bufferedWrites.push(expression);
},
text: function (text) {
if (this._bufferedText === null) {
this._bufferedText = text;
} else {
this._bufferedText += text;
}
},
functionCall: function (varName, args) {
this.flush();
this._code.append(this._indent + varName + '(');
writeArgs(this, args);
this._code.append(');\n');
},
code: function (code) {
if (typeof code === 'function') {
code = code();
}
this.flush();
this._code.append(code);
},
statement: function (code) {
this.flush();
this.code((this.firstStatement ? '' : '\n') + this._indent + code + '\n');
this.firstStatement = false;
},
line: function (code) {
this.code(this._indent + code + '\n');
},
indentStr: function (delta) {
if (arguments.length === 0) {
return this._indent;
} else {
var indent = this._indent;
for (var i = 0; i < delta; i++) {
indent += INDENT;
}
return indent;
}
},
indent: function () {
if (arguments.length === 0) {
this.code(this._indent);
} else if (arguments.length === 1 && typeof arguments[0] === 'number') {
this.code(this.indentStr(arguments[0]));
} else if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
var func;
var thisObj;
var delta;
if (typeof arguments[0] === 'function') {
delta = 1;
func = arguments[0];
thisObj = arguments[1];
} else {
delta = arguments[0];
func = arguments[1];
thisObj = arguments[2];
}
this.incIndent(delta);
func.call(thisObj, this);
this.decIndent(delta);
} else if (typeof arguments[0] === 'string') {
this.code(this._indent + arguments[0]);
}
return this;
},
flush: function () {
this.flushText();
this.flushWrites();
},
flushText: function () {
var curText = this._bufferedText;
if (curText) {
this._bufferedText = null;
this.write(stringify(curText, { useSingleQuote: true }));
}
},
flushWrites: function () {
var _this = this;
var code = this._code;
var _bufferedWrites = this._bufferedWrites;
function concat() {
code.append(_this.indentStr() + 'out.w(');
_bufferedWrites.forEach(function (expression, i) {
if (i !== 0) {
_this.incIndent();
code.append(' +\n' + this.indentStr());
}
writeArg(_this, expression);
if (i !== 0) {
_this.decIndent();
}
}, _this);
code.append(');\n');
}
function chain() {
_bufferedWrites.forEach(function (arg, i) {
if (i === 0) {
this._code.append(this.indentStr() + 'out.w(');
} else {
this.incIndent();
this._code.append(this.indentStr() + '.w(');
}
writeArg(this, arg);
if (i < _bufferedWrites.length - 1) {
this._code.append(')\n');
} else {
this._code.append(');\n');
}
if (i !== 0) {
this.decIndent();
}
}, _this);
}
if (_bufferedWrites) {
if (!this.firstStatement) {
this._code.append('\n');
}
this.firstStatement = false;
this._bufferedWrites = null;
if (this.concatWrites) {
concat();
} else {
chain();
}
}
},
incIndent: function (delta) {
if (arguments.length === 0) {
delta = 1;
}
this.flush();
this._indent = this.indentStr(delta);
this.firstStatement = true;
},
decIndent: function (delta) {
if (arguments.length === 0) {
delta = 1;
}
this.flush();
this._indent = this._indent.substring(INDENT.length * delta);
this.firstStatement = false;
},
getOutput: function () {
this.flush();
return this._code.toString();
}
};
/**
* This class provides the interface that compile-time transformers
* and compile-time tags can use to add JavaScript code to the final
* template.
*
* This class ensures that proper indentation is maintained so that
* compiled templates are readable.
*/
function TemplateBuilder(compiler, path, rootNode) {
this.rootNode = rootNode; // This is the root node for the AST. It should be a TemplateNode
this.compiler = compiler; // A reference to the compiler
this.path = path; // The file system path of the template being compiled
this.dirname = nodePath.dirname(path); // The file system directory of the template being compiled
this.options = compiler.options || {}; // Compiler options
this.data = this.attributes /* deprecated */ = {};
this.concatWrites = this.options.concatWrites !== false;
this.writer = new CodeWriter(this.concatWrites);
this.staticVars = [];
this.staticVarsLookup = {};
this.helperFunctionsAdded = {};
this.vars = [];
this.varsLookup = {};
this.staticCode = [];
this.getStaticHelperFunction('str', 's');
this.getStaticHelperFunction('empty', 'e');
this.getStaticHelperFunction('notEmpty', 'ne');
}
TemplateBuilder.prototype = {
captureCode: function (func, thisObj) {
var oldWriter = this.writer;
var newWriter = new CodeWriter(this.concatWrites, oldWriter.indentStr());
try {
this.writer = newWriter;
var value = func.call(thisObj);
return value == null ? newWriter.getOutput() : value;
} finally {
this.writer = oldWriter;
}
},
getStaticHelperFunction: function (varName, propName) {
var added = this.helperFunctionsAdded[propName];
if (added) {
return added;
} else {
this.addStaticVar(varName, '__helpers.' + propName);
this.helperFunctionsAdded[propName] = varName;
return varName;
}
},
addStaticCode: function(codeOrFunc) {
this.staticCode.push(codeOrFunc);
},
_getStaticCode: function() {
var staticCodeList = this.staticCode;
if (!staticCodeList.length) {
return;
}
var codeWriter = new CodeWriter(this.concatWrites, INDENT);
codeWriter.code('\n');
for (var i=0, len=staticCodeList.length; i<len; i++) {
var code = staticCodeList[i];
if (typeof code === 'function') {
var result = code(codeWriter);
if (result != null) {
codeWriter.code(result.toString());
}
} else {
codeWriter.code(code.toString());
}
}
return codeWriter.getOutput();
},
hasStaticVar: function (name) {
return this.staticVarsLookup[name] === true;
},
addStaticVar: function (name, expression) {
name = safeVarName(name);
if (!this.staticVarsLookup.hasOwnProperty(name)) {
this.staticVarsLookup[name] = true;
this.staticVars.push({
name: name,
expression: expression
});
}
return name;
},
hasVar: function (name) {
return this.vars[name] === true;
},
addVar: function (name, expression) {
name = safeVarName(name);
this.vars[name] = true;
this.vars.push({
name: name,
expression: expression
});
},
_writeVars: function (vars, out, indent) {
if (!vars.length) {
return;
}
out.append(indent + 'var ');
var declarations = [];
vars.forEach(function (v, i) {
declarations.push((i !== 0 ? indent + ' ' : '') + v.name + ' = ' + v.expression + (i === vars.length - 1 ? ';\n' : ',\n'));
});
out.append(declarations.join(''));
},
text: function (text) {
if (!this.hasErrors()) {
this.writer.text(text);
}
return this;
},
attr: function (name, valueExpression, escapeXml) {
if (!this.hasErrors()) {
var expression;
if (escapeXml === false) {
expression = this.getStaticHelperFunction('attr', 'a') + '(' + stringify(name) + ', ' + valueExpression + ', false)';
} else {
expression = this.getStaticHelperFunction('attr', 'a') + '(' + stringify(name) + ', ' + valueExpression + ')';
}
this.write(expression);
}
return this;
},
attrs: function (attrsExpression) {
if (!this.hasErrors()) {
var expression = this.getStaticHelperFunction('attrs', 'as') + '(' + attrsExpression + ')';
this.write(expression);
}
return this;
},
include: function (templatePath, dataExpression) {
if (!this.hasErrors()) {
if (typeof templatePath === 'string') {
var templateVar;
if (!this.hasExpression(templatePath)) {
// Resolve the static string to a full path only once
templateVar = this.addStaticVar(templatePath, '__helpers.l(require.resolve(' + this.compiler.convertType(templatePath, 'string', true) + '))');
this.statement(this.makeExpression(templateVar + '.render(' + dataExpression + ', out);'));
return;
}
}
this.contextHelperMethodCall(
'i',
typeof templatePath === 'string' ?
this.compiler.convertType(templatePath, 'string', true) :
templatePath,
dataExpression);
}
return this;
},
load: function (templatePath) {
if (!this.hasErrors()) {
this.contextHelperMethodCall('l', new Expression('require.resolve(' + templatePath + ')'));
}
return this;
},
functionCall: function(varName, args) {
if (!this.hasErrors()) {
args = arrayFromArguments(arguments, 1);
this.writer.functionCall(varName, args);
}
return this;
},
contextHelperMethodCall: function (methodName, args) {
if (!this.hasErrors()) {
args = arrayFromArguments(arguments, 1);
args.unshift('out');
this.writer.functionCall('__helpers.' + methodName, args);
}
return this;
},
getEscapeXmlFunction: function() {
return this.getStaticHelperFunction('escapeXml', 'x');
},
getEscapeXmlAttrFunction: function() {
return this.getStaticHelperFunction('escapeXmlAttr', 'xa');
},
write: function (expression, options) {
if (!this.hasErrors()) {
if (options) {
if (options.escapeXml) {
expression = this.getEscapeXmlFunction() + '(' + expression + ')';
} else if (options.escapeXmlAttr) {
expression = this.getEscapeXmlAttrFunction() + '(' + expression + ')';
}
}
this.writer.write(expression);
}
return this;
},
incIndent: function () {
if (!this.hasErrors()) {
this.writer.incIndent.apply(this.writer, arguments);
}
return this;
},
decIndent: function () {
if (!this.hasErrors()) {
this.writer.decIndent.apply(this.writer, arguments);
}
return this;
},
code: function (code) {
if (!this.hasErrors()) {
this.writer.code(code);
}
return this;
},
statement: function (code) {
if (!this.hasErrors()) {
this.writer.statement(code);
}
return this;
},
line: function (code) {
if (!this.hasErrors()) {
this.writer.line(code);
}
return this;
},
indentStr: function (delta) {
return this.writer.indentStr(delta);
},
indent: function () {
if (!this.hasErrors()) {
this.writer.indent.apply(this.writer, arguments);
}
return this;
},
getPath: function () {
return this.path;
},
getOutput: function () {
if (this.hasErrors()) {
return '';
}
var out = new StringBuilder();
var params = this.params;
if (params) {
params = ['out'].concat(params);
} else {
params = ['out'];
}
// Don't use "use strict" in compiled templates since it
// could break backwards compatibility.
// out.append('"use strict";\n');
out.append('function create(__helpers) {\n');
//Write out the static variables
this.writer.flush();
this._writeVars(this.staticVars, out, INDENT);
var staticCode = this._getStaticCode();
if (staticCode) {
out.append(staticCode);
}
out.append('\n' + INDENT + 'return function render(data, out) {\n');
//Write out the render variables
if (this.vars && this.vars.length) {
this._writeVars(this.vars, out, INDENT + INDENT);
out.append('\n');
}
out.append(this.writer.getOutput());
// We generate code that assign a partially Template instance to module.exports
// and then we fully initialize the Template instance. This was done to avoid
// problems with circular dependencies.
out.append(INDENT + '};\n}\n(module.exports = require("marko").c(__filename)).c(create);');
return out.toString();
},
makeExpression: function (expression, replaceSpecialOperators) {
return this.compiler.makeExpression(expression, replaceSpecialOperators);
},
hasExpression: function (str) {
return this.compiler.hasExpression(str);
},
isExpression: function (expression) {
return this.compiler.isExpression(expression);
},
parseExpression: function(str, listeners, options) {
return this.compiler.parseExpression(str, listeners, options);
},
parseAttribute: function(attr, types, options) {
return this.compiler.parseAttribute(attr, types, options);
},
getAttribute: function (name) {
return this.attributes[name];
},
setAttribute: function (name, value) {
this.attributes[name] = value;
return value;
},
hasErrors: function () {
return this.compiler.hasErrors();
},
addError: function (message, pos) {
this.compiler.addError(message, pos);
},
getErrors: function () {
return this.compiler.getErrors();
},
getNodeClass: function (namespace, localName) {
return this.compiler.getNodeClass(namespace, localName);
},
transformTree: function (node) {
this.compiler.transformTree(node, this);
},
getRequirePath: function(targetModuleFile) {
return this.compiler.getRequirePath(targetModuleFile);
},
INDENT: INDENT
};
module.exports = TemplateBuilder;

View File

@ -1,295 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var createError = require('raptor-util').createError;
var TemplateBuilder = require('./TemplateBuilder');
var parser = require('./parser');
var Expression = require('./Expression');
var TypeConverter = require('./TypeConverter');
var taglibs = require('./taglibs');
var nodePath = require('path');
var ok = require('assert').ok;
var attributeParser = require('./attribute-parser');
var expressionParser = require('./expression-parser');
var inherit = require('raptor-util/inherit');
var extend = require('raptor-util/extend');
var _Node = require('./Node');
var ElementNode = require('./ElementNode');
var TextNode = require('./TextNode');
var TagHandlerNode = require('../taglibs/core/TagHandlerNode');
var deresolve = require('./util/deresolve');
var upToDate = require('./up-to-date');
function TemplateCompiler(path, options) {
this.dirname = nodePath.dirname(path);
this.path = path;
this.taglibs = taglibs.buildLookup(this.dirname);
this.options = options || {};
this.errors = [];
}
TemplateCompiler.prototype = {
isTaglib: function(ns) {
return this.taglibs.isTaglib(ns);
},
transformTree: function (rootNode, templateBuilder) {
if (!templateBuilder) {
throw createError(new Error('The templateBuilder argument is required'));
}
var _this = this;
function transformTreeHelper(node) {
try {
_this.taglibs.forEachNodeTransformer(node, function (transformer) {
if (!node.isTransformerApplied(transformer)) {
//Check to make sure a transformer of a certain type is only applied once to a node
node.setTransformerApplied(transformer);
//Mark the node as have been transformed by the current transformer
_this._transformerApplied = true;
//Set the flag to indicate that a node was transformed
node.compiler = _this;
var transformerFunc = transformer.getFunc();
transformerFunc.call(transformer, node, _this, templateBuilder); //Have the transformer process the node (NOTE: Just because a node is being processed by the transformer doesn't mean that it has to modify the parse tree)
}
});
} catch (e) {
throw createError(new Error('Unable to compile template at path "' + _this.path + '". Error: ' + e.message), e);
}
/*
* Now process the child nodes by looping over the child nodes
* and transforming the subtree recursively
*
* NOTE: The length of the childNodes array might change as the tree is being performed.
* The checks to prevent transformers from being applied multiple times makes
* sure that this is not a problem.
*/
node.forEachChild(function (childNode) {
if (!childNode.parentNode) {
return; //The child node might have been removed from the tree
}
transformTreeHelper(childNode);
});
}
/*
* The tree is continuously transformed until we go through an entire pass where
* there were no new nodes that needed to be transformed. This loop makes sure that
* nodes added by transformers are also transformed.
*/
do {
this._transformerApplied = false;
//Reset the flag to indicate that no transforms were yet applied to any of the nodes for this pass
transformTreeHelper(rootNode); //Run the transforms on the tree
} while (this._transformerApplied);
},
compile: function (src, callback, thisObj) {
var _this = this;
var filePath = this.path;
var rootNode;
var templateBuilder;
var err;
function returnError(err) {
if (callback) {
return callback.call(thisObj, err);
} else {
throw err;
}
}
try {
/*
* First build the parse tree for the tempate
*/
rootNode = parser.parse(src, filePath, this.taglibs);
if (rootNode.compilerOptions) {
// compiler options were set in the template so use those here
this.options = extend(extend({}, this.options), rootNode.compilerOptions);
}
//Build a parse tree from the input XML
templateBuilder = new TemplateBuilder(this, filePath, rootNode);
//The templateBuilder object is need to manage the compiled JavaScript output
this.transformTree(rootNode, templateBuilder);
} catch (e) {
err = createError(new Error('An error occurred while trying to compile template at path "' + filePath + '". Exception: ' + (e.stack || e)), e);
return returnError(err);
}
try {
/*
* The tree has been transformed and we can now generate
*/
rootNode.generateCode(templateBuilder); //Generate the code and have all output be managed by the TemplateBuilder
} catch (e) {
err = createError(new Error('An error occurred while trying to compile template at path "' + filePath + '". Exception: ' + e), e);
return returnError(err);
}
if (this.hasErrors()) {
var message = 'An error occurred while trying to compile template at path "' + filePath + '". Error(s) in template:\n';
var errors = _this.getErrors();
for (var i = 0, len = errors.length; i < len; i++) {
message += (i + 1) + ') ' + (errors[i].pos ? '[' + errors[i].pos + '] ' : '') + errors[i].message + '\n';
}
var error = new Error(message);
error.errors = _this.getErrors();
return returnError(error);
} else {
var output = templateBuilder.getOutput();
if (callback) {
callback.call(thisObj, null, output);
}
return output;
}
},
isExpression: function (expression) {
return expression instanceof Expression;
},
hasExpression: function(str) {
return expressionParser.hasExpression(str);
},
makeExpression: function (expression, replaceSpecialOperators) {
if (this.isExpression(expression)) {
return expression;
} else {
return new Expression(expression, replaceSpecialOperators);
}
},
parseExpression: function(str, listeners, options) {
return expressionParser.parse(str, listeners, options);
},
parseAttribute: function(attr, types, options) {
return attributeParser.parse(attr, types, options);
},
createTagHandlerNode: function (tagName) {
var tag = this.taglibs.getTag(tagName);
var tagHandlerNode = this.createNode(TagHandlerNode, tag);
tagHandlerNode.localName = tagName;
return tagHandlerNode;
},
convertType: function (value, type, allowExpressions) {
return TypeConverter.convert(value, type, allowExpressions);
},
addError: function (message, pos) {
this.errors.push({
message: message,
pos: pos
});
},
hasErrors: function () {
return this.errors.length !== 0;
},
getErrors: function () {
return this.errors;
},
/**
* Returns the constructor for an AST node based
* on the tag name.
*/
getNodeClass: function (tagName) {
ok(arguments.length === 1, 'Invalid args');
var tag = this.taglibs.getTag(tagName);
if (tag && tag.nodeClass) {
var nodeClass = require(tag.nodeClass);
nodeClass.prototype.constructor = nodeClass;
return nodeClass;
} else {
return ElementNode;
}
throw createError(new Error('Node class not found for tag "' + tagName + '"'));
},
/**
* There are three types of nodes that can be added to an AST: Node, ElementNode and TextNode
* Nodes that produce an HTML tag should extend ElementNode.
* Nodes that produce text should extend TextNode
* For everything else, a node should inherit from the base Node class
*/
inheritNode: function(Ctor) {
if (!Ctor.prototype.__NODE) {
var nodeType = Ctor.nodeType || 'node';
nodeType = nodeType.toLowerCase();
if (nodeType === 'element') {
inherit(Ctor, ElementNode);
} else if (nodeType === 'node') {
inherit(Ctor, _Node);
} else {
throw new Error('Invalid node type: ' + nodeType);
}
}
},
/**
* Create a new AST node that can be added to the AST tree
*
* The first argument can either be a tag name or a construtor
* function.
*/
createNode: function(Ctor, arg) {
if (typeof Ctor === 'string') {
var tagName = Ctor;
Ctor = this.getNodeClass(tagName);
if (Ctor === ElementNode) {
return new ElementNode(
tagName,
'',
'');
}
}
ok(Ctor != null, 'Ctor is required');
ok(typeof Ctor === 'function', 'Ctor should be a function');
this.inheritNode(Ctor);
return new Ctor(arg);
},
/**
* Helper method to create a new Text node that can be added to the AST.
* The Text node will generate code that renders static HTML
*/
createTextNode: function(text, escapeXml) {
return new TextNode(text, escapeXml);
},
/**
* Returns the max last modified date of a template and all of its taglibs
*/
getLastModified: function() {
return upToDate.getLastModified(this.path, this.taglibs);
},
checkUpToDate: function(targetFile) {
if (this.options.checkUpToDate === false) {
return false;
}
return upToDate.checkUpToDate(targetFile, this.path, this.taglibs);
},
getRequirePath: function(targetModuleFile) {
return deresolve(targetModuleFile, this.dirname);
}
};
module.exports = TemplateCompiler;

View File

@ -1,186 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var createError = require('raptor-util').createError;
var escapeXml = require('raptor-util/escapeXml');
var EscapeXmlContext = require('./EscapeXmlContext');
var attrReplace = /[&<>\"\']/g;
var replacements = {
'<': '&lt;',
'>': '&gt;',
'&': '&amp;',
'"': '&quot;',
'\'': '&apos;'
};
function escapeXmlAttr(str) {
return str.replace(attrReplace, function (match) {
return replacements[match];
});
}
function TextNode(text, escapeXml) {
TextNode.$super.call(this, 'text');
if (text != null && typeof text !== 'string') {
throw createError('Invalid text: ' + text);
}
if (text) {
text = text.replace(/\r\n/g, '\n');
}
this.text = text;
this.escapeXml = escapeXml !== false;
}
TextNode.prototype = {
normalizeText: function () {
var normalizedText = this.getEscapedText();
var curChild = this.nextSibling;
var nodeToRemove;
while (curChild && (curChild.isTextNode() || curChild.javaScriptOnly)) {
if (curChild.javaScriptOnly) {
curChild = curChild.nextSibling;
continue;
}
normalizedText += curChild.getEscapedText();
nodeToRemove = curChild;
curChild = curChild.nextSibling;
nodeToRemove.detach();
}
this.setText(normalizedText);
this.escapeXml = false; //Make sure the text is not re-escaped
},
getEscapedText: function () {
var text = this.getText();
var parentNode = this.parentNode;
var shouldEscapeXml = this.escapeXml !== false && parentNode && parentNode.isEscapeXmlBodyText() !== false;
if (shouldEscapeXml) {
if (this.getEscapeXmlContext() === EscapeXmlContext.ATTRIBUTE) {
return escapeXmlAttr(text);
} else {
return escapeXml(text);
}
} else {
return text;
}
},
trim: function () {
var text = this.getText();
if (!text) {
return;
}
var parentNode = this.parentNode;
if (parentNode && parentNode.trimBodyIndent) {
var initialSpaceMatches = /^\s+/.exec(text);
if (initialSpaceMatches) {
var indentMatches = /\n[^\n]*$/.exec(initialSpaceMatches[0]);
if (indentMatches) {
var indentRegExp = new RegExp(indentMatches[0].replace(/\n/g, '\\n'), 'g');
text = text.replace(indentRegExp, '\n');
}
text = text.replace(/^\s*/, '').replace(/\s*$/, '');
this.setText(text);
}
}
if (this.isPreserveWhitespace()) {
return;
}
var previousSibling = this.previousSibling;
while (previousSibling && previousSibling.javaScriptOnly) {
previousSibling = previousSibling.previousSibling;
}
var nextSibling = this.nextSibling;
while (nextSibling && nextSibling.javaScriptOnly) {
nextSibling = nextSibling.nextSibling;
}
if (!previousSibling) {
//First child
text = text.replace(/^\n\s*/g, '');
}
if (!nextSibling) {
//Last child
text = text.replace(/\n\s*$/g, '');
}
if (/^\n\s*$/.test(text)) {
//Whitespace between elements
text = '';
}
text = text.replace(/\s+/g, ' ');
if (this.isWordWrapEnabled() && text.length > 80) {
var start = 0;
var end;
while (start < text.length) {
end = Math.min(start + 80, text.length);
var lastSpace = text.substring(start, end).lastIndexOf(' ');
if (lastSpace != -1) {
lastSpace = lastSpace + start; //Adjust offset into original string
} else {
lastSpace = text.indexOf(' ', end); //No space before the 80 column mark... search for the first space after to break on
}
if (lastSpace != -1) {
text = text.substring(0, lastSpace) + '\n' + text.substring(lastSpace + 1);
start = lastSpace + 1;
} else {
break;
}
}
}
this.setText(text);
},
doGenerateCode: function (template) {
/*
* After all of the transformation of the tree we
* might have ended up with multiple text nodes
* as siblings. We want to normalize adjacent
* text nodes so that whitespace removal rules
* will be correct
*/
this.normalizeText();
this.trim();
var text = this.getText();
if (text) {
template.text(text);
}
},
getText: function () {
return this.text;
},
setText: function (text) {
this.text = text;
},
isTextNode: function () {
return true;
},
isElementNode: function () {
return false;
},
setEscapeXml: function (escapeXml) {
this.escapeXml = escapeXml;
},
isEscapeXml: function () {
return this.escapeXml;
},
toString: function () {
var text = this.text && this.text.length > 25 ? this.text.substring(0, 25) + '...' : this.text;
text = text.replace(/[\n]/g, '\\n');
return '[text: ' + text + ']';
}
};
require('raptor-util').inherit(TextNode, require('./Node'));
module.exports = TextNode;

View File

@ -1,96 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var createError = require('raptor-util').createError;
var expressionParser = require('./expression-parser');
var stringify = require('raptor-json/stringify');
var Expression = require('./Expression');
function TypeConverter() {
}
TypeConverter.convert = function (value, targetType, allowExpressions) {
var hasExpression = false;
var expressionParts = [];
if (value == null) {
return value;
}
if (targetType === 'custom' || targetType === 'identifier') {
return value;
}
if (targetType === 'expression' || targetType === 'object' || targetType === 'array') {
if (value === '') {
value = 'null';
}
return new Expression(value);
}
var processedText = '';
if (allowExpressions) {
expressionParser.parse(value, {
text: function (text) {
processedText += text;
expressionParts.push(stringify(text));
},
expression: function (expression) {
expressionParts.push(expression);
hasExpression = true;
}
});
if (hasExpression) {
value = new Expression(expressionParts.join('+'));
if (targetType === 'template') {
return new Expression('__helpers.l(' + value + ')');
} else {
return value;
}
}
value = processedText;
}
if (targetType === 'string') {
return allowExpressions ? new Expression(value != null ? stringify(value) : 'null') : value;
} else if (targetType === 'boolean') {
if (!allowExpressions) {
value = value.toLowerCase();
}
if (!allowExpressions || value === 'true' || value === 'yes' || value === '') {
//convert it to a boolean
return new Expression(true);
}
return new Expression(value);
} else if (targetType === 'float' || targetType === 'double' || targetType === 'number' || targetType === 'integer' || targetType === 'int') {
if (allowExpressions) {
return new Expression(value);
} else {
if (targetType === 'integer') {
value = parseInt(value, 10);
} else {
value = parseFloat(value);
}
return value;
}
} else if (targetType === 'path') {
return new Expression('require.resolve(' + JSON.stringify(value) + ')');
} else if (targetType === 'template') {
return new Expression('__helpers.l(require.resolve(' + JSON.stringify(value) + '))');
} else {
throw createError(new Error('Unsupported attribute type: ' + targetType));
}
};
module.exports = TypeConverter;

44
compiler/Walker.js Normal file
View File

@ -0,0 +1,44 @@
'use strict';
var isArray = Array.isArray;
var Container = require('./ast/Container');
class Walker {
constructor(options) {
this._visit = options.visit;
this.transform = options.transform !== false;
}
walk(node) {
if (node == null) {
return;
}
if (isArray(node)) {
let nodes = node;
let len = nodes.length;
for (var i=0; i<len; i++) {
this.walk(nodes[i]);
}
} else if (node instanceof Container) {
let container = node;
if (this.transform) {
container.safeForEach(this.walk, this);
} else {
container.forEach(this.walk, this);
}
} else {
this._visit(node);
if (node.walkChildren) {
node.walkChildren(this);
} else if (node.forEachChild) {
node.forEachChild(this.walk, this);
}
}
}
}
module.exports = Walker;

View File

@ -0,0 +1,109 @@
'use strict';
var ok = require('assert').ok;
var isArray = Array.isArray;
var Container = require('./Container');
class ArrayContainer extends Container {
constructor(node, array) {
super(node);
if (array) {
ok(isArray(array), 'Invalid array');
for (let i=0; i<array.length; i++) {
array[i].container = this;
}
}
this.array = array || [];
}
// forEach(callback, thisObj) {
// var array = this.array;
//
// for (var i=0; i<array.length; i++) {
// var item = array[i];
// if (item == null) {
// throw new Error('Invalid node in container at index ' + i + '. Array: ' + JSON.stringify(array, null, 2));
// }
// callback.call(thisObj, item, i);
// }
// }
forEach(callback, thisObj) {
var array = this.array;
for (var i=0; i<array.length; i++) {
var item = array[i];
if (item.container === this) {
callback.call(thisObj, item, i);
}
}
}
replaceChild(newChild, oldChild) {
ok(newChild, 'Invalid child');
var array = this.array;
var len = array.length;
for (var i=0; i<len; i++) {
var curChild = array[i];
if (curChild === oldChild) {
array[i] = newChild;
oldChild.container = null;
newChild.container = this;
return true;
}
}
return false;
}
removeChild(child) {
var childIndex = this.array.indexOf(child);
if (childIndex !== -1) {
this.array.splice(childIndex, 1);
}
child.container = null;
}
appendChild(newChild) {
ok(newChild, 'Invalid child');
this.array.push(newChild);
newChild.container = this;
}
forEachNextSibling(node, callback, thisObj) {
if (node.container !== this) {
throw new Error('Node does not belong to container: ' + node);
}
var array = this.array;
var found = false;
for (var i=0; i<array.length; i++) {
var curNode = array[i];
if (curNode.container !== this) {
continue;
}
if (found) {
if (curNode.container === this) {
var keepGoing = callback.call(thisObj, curNode) !== false;
if (!keepGoing) {
return;
}
}
} else if (curNode === node) {
found = true;
continue;
}
}
}
get length() {
return this.array.length;
}
get items() {
return this.array;
}
}
module.exports = ArrayContainer;

View File

@ -0,0 +1,22 @@
'use strict';
var Node = require('./Node');
class Assignment extends Node {
constructor(def) {
super('Assignment');
this.left = def.left;
this.right = def.right;
}
generateCode(generator) {
var left = this.left;
var right = this.right;
generator.generateCode(left);
generator.write(' = ');
generator.generateCode(right);
}
}
module.exports = Assignment;

View File

@ -0,0 +1,24 @@
'use strict';
var Node = require('./Node');
class BinaryExpression extends Node {
constructor(def) {
super('BinaryExpression');
this.left = def.left;
this.right = def.right;
this.operator = def.operator;
}
generateCode(generator) {
var left = this.left;
var right = this.right;
var operator = this.operator;
generator.generateCode(left);
generator.write(' ' + operator + ' ');
generator.generateCode(right);
}
}
module.exports = BinaryExpression;

13
compiler/ast/Container.js Normal file
View File

@ -0,0 +1,13 @@
'use strict';
class Container {
constructor(node) {
this.node = node;
}
toJSON() {
return this.items;
}
}
module.exports = Container;

25
compiler/ast/Else.js Normal file
View File

@ -0,0 +1,25 @@
'use strict';
var Node = require('./Node');
class Else extends Node {
constructor(def) {
super('Else');
this.body = this.makeContainer(def.body);
this.matched = false;
}
generateCode(generator) {
if (!this.matched) {
generator.addError('Unmatched else statement');
return;
}
var body = this.body;
generator.write('else ');
generator.generateBlock(body);
generator.write('\n');
}
}
module.exports = Else;

27
compiler/ast/ElseIf.js Normal file
View File

@ -0,0 +1,27 @@
'use strict';
var Node = require('./Node');
var ok = require('assert').ok;
class ElseIf extends Node {
constructor(def) {
super('ElseIf');
this.if = def.if;
this.matched = false;
}
generateCode(generator) {
if (!this.matched) {
generator.addError('Unmatched else statement');
return;
}
var ifStatement = this.if;
ok(ifStatement);
generator.write('else ');
generator.generateCode(this.if);
}
}
module.exports = ElseIf;

31
compiler/ast/ForEach.js Normal file
View File

@ -0,0 +1,31 @@
'use strict';
var ok = require('assert').ok;
var Node = require('./Node');
class ForEach extends Node {
constructor(def) {
super('ForEach');
this.varName = def.varName;
this.target = def.target;
this.body = this.makeContainer(def.body);
ok(this.varName, '"varName" is required');
ok(this.target, '"target" is required');
}
generateCode(generator) {
var varName = this.varName;
var target = this.target;
var builder = generator.builder;
generator.addStaticVar('forEach', '__helpers.f');
return builder.functionCall('forEach', [
target,
builder.functionDeclaration(null, [varName], this.body)
]);
}
}
module.exports = ForEach;

View File

@ -0,0 +1,42 @@
'use strict';
var Node = require('./Node');
class FunctionCall extends Node {
constructor(def) {
super('FunctionCall');
this.callee = def.callee;
this.args = def.args;
if (this.args && !Array.isArray(this.args)) {
throw new Error('Invalid args');
}
}
generateCode(generator) {
var callee = this.callee;
var args = this.args;
generator.generateCode(callee);
generator.write('(');
if (args && args.length) {
for (let i=0, argsLen = args.length; i<argsLen; i++) {
if (i !== 0) {
generator.write(', ');
}
let arg = args[i];
if (!arg) {
throw new Error('Arg ' + i + ' is not valid for function call: ' + JSON.stringify(this.toJSON()));
}
generator.generateCode(arg);
}
}
generator.write(')');
}
}
module.exports = FunctionCall;

View File

@ -0,0 +1,56 @@
'use strict';
var Node = require('./Node');
var ok = require('assert').ok;
class FunctionDeclaration extends Node {
constructor(def) {
super('FunctionDeclaration');
this.name = def.name;
this.params = def.params;
this.body = this.makeContainer(def.body);
}
generateCode(generator) {
var name = this.name;
var params = this.params;
var body = this.body;
var statement = this.statement;
if (name != null) {
ok(typeof name === 'string', 'Function name should be a string');
}
generator.write('function' + (name ? ' ' + name : '') + '(');
if (params && params.length) {
for (let i=0, paramsLen = params.length; i<paramsLen; i++) {
if (i !== 0) {
generator.write(', ');
}
var param = params[i];
if (typeof param === 'string') {
generator.write(param);
} else {
if (param.type !== 'Identifier') {
throw new Error('Illegal param: ' + param);
}
generator.generateCode(param);
}
}
}
generator.write(') ');
var oldInFunction = generator.inFunction;
generator.inFunction = true;
generator.generateBlock(body);
generator.inFunction = oldInFunction;
if (statement) {
generator.write('\n');
}
}
}
module.exports = FunctionDeclaration;

View File

@ -0,0 +1,33 @@
'use strict';
var Literal = require('./Literal');
var ok = require('assert').ok;
class HtmlAttribute {
constructor(def) {
ok(def, 'Invalid attribute definition');
this.name = def.name.toLowerCase();
this.value = def.value;
this.argument = def.argument;
}
isLiteralValue() {
return this.value instanceof Literal;
}
isLiteralString() {
return this.isLiteralValue() &&
typeof this.value.value === 'string';
}
isLiteralBoolean() {
return this.isLiteralValue() &&
typeof this.value.value === 'boolean';
}
}
HtmlAttribute.isHtmlAttribute = function(attr) {
return (attr instanceof HtmlAttribute);
};
module.exports = HtmlAttribute;

View File

@ -0,0 +1,105 @@
'use strict';
var ok = require('assert').ok;
var HtmlAttribute = require('./HtmlAttribute');
class HtmlAttributeCollection {
constructor(attributes) {
this.all = [];
this.lookup = {};
if (attributes) {
if (Array.isArray(attributes)) {
attributes.forEach((attr) => {
this.addAttribute(attr);
});
} else {
for (var attrName in attributes) {
if (attributes.hasOwnProperty(attrName)) {
var attr = attributes[attrName];
attr.name = attrName;
this.addAttribute(attr);
}
}
}
}
}
addAttribute(newAttr) {
if (arguments.length === 2) {
let name = arguments[0];
let expression = arguments[1];
newAttr = new HtmlAttribute(name, expression);
}
ok(HtmlAttribute.isHtmlAttribute(newAttr), 'Invalid attribute');
var name = newAttr.name;
if (this.lookup.hasOwnProperty(name)) {
for (var i=0; i<this.all.length; i++) {
var curAttr = this.all[i];
if (curAttr.name === name) {
this.all.splice(i, 1);
break;
}
}
}
this.lookup[name] = newAttr;
this.all.push(newAttr);
}
removeAttribute(name) {
ok(typeof name === 'string', 'Invalid attribute name');
name = name.toLowerCase();
if (!this.lookup.hasOwnProperty(name)) {
return false;
}
delete this.lookup[name];
for (var i=0; i<this.all.length; i++) {
var curAttr = this.all[i];
if (curAttr.name === name) {
this.all.splice(i, 1);
break;
}
}
return true;
}
hasAttribute(name) {
ok(typeof name === 'string', 'Invalid attribute name');
name = name.toLowerCase();
return this.lookup.hasOwnProperty(name);
}
hasAttributes() {
return this.all.length > 0;
}
getAttribute(name) {
return this.lookup[name];
}
getAttributes() {
return this.all;
}
toJSON() {
return this.all;
}
toString() {
return JSON.stringify(this.all);
}
}
module.exports = HtmlAttributeCollection;

View File

@ -0,0 +1,19 @@
'use strict';
var Node = require('./Node');
class HtmlComment extends Node {
constructor(def) {
super('HtmlComment');
this.comment = def.comment;
}
generateHtmlCode(generator) {
var comment = this.comment;
generator.addWrite('<--');
generator.addWrite(comment);
generator.addWrite('-->');
}
}
module.exports = HtmlComment;

116
compiler/ast/HtmlElement.js Normal file
View File

@ -0,0 +1,116 @@
'use strict';
var Node = require('./Node');
var escapeXmlAttr = require('raptor-util/escapeXml').attr;
var HtmlAttributeCollection = require('./HtmlAttributeCollection');
class HtmlElement extends Node {
constructor(def) {
super('HtmlElement');
this.tagName = def.tagName;
this._attributes = def.attributes;
if (!(this._attributes instanceof HtmlAttributeCollection)) {
this._attributes = new HtmlAttributeCollection(this._attributes);
}
this.body = this.makeContainer(def.body);
this.argument = def.argument;
this.allowSelfClosing = false;
this.startTagOnly = false;
}
generateHtmlCode(generator) {
var tagName = this.tagName;
var body = this.body;
var startTagOnly = this.startTagOnly;
var allowSelfClosing = this.allowSelfClosing;
var hasBody = body && body.length;
// Starting tag
generator.addWriteLiteral('<' + tagName);
var attributes = this._attributes && this._attributes.all;
if (attributes) {
for (let i=0; i<attributes.length; i++) {
let attr = attributes[i];
let attrName = attr.name;
let attrValue = attr.value;
if (attr.isLiteralValue()) {
var literalValue = attrValue.value;
if (typeof literalValue === 'boolean') {
if (literalValue === true) {
generator.addWriteLiteral(' ' + attrName);
}
} else if (literalValue != null) {
generator.addWriteLiteral(' ' + attrName + '="' + escapeXmlAttr(literalValue) + '"');
}
} else if (attrValue) {
generator.addWriteLiteral(' ' + attrName + '="');
generator.isInAttribute = true;
// TODO Deal with escaping dynamic HTML attribute expression
generator.addWrite(attrValue);
generator.isInAttribute = false;
generator.addWriteLiteral('"');
} else if (attr.argument) {
generator.addWriteLiteral(' ' + attrName + '(');
generator.addWriteLiteral(attr.argument);
generator.addWriteLiteral(')');
}
}
}
if (hasBody) {
generator.addWriteLiteral('>');
} else {
if (startTagOnly) {
generator.addWriteLiteral('>');
} else if (allowSelfClosing) {
generator.addWriteLiteral('/>');
}
}
// Body
if (hasBody) {
generator.generateStatements(body);
}
// Ending tag
if (tagName instanceof Node) {
generator.addWriteLiteral('</');
generator.addWrite(tagName);
generator.addWriteLiteral('>');
} else {
if (hasBody) {
generator.addWriteLiteral('</' + tagName + '>');
} else {
if (!startTagOnly && !allowSelfClosing) {
generator.addWriteLiteral('></' + tagName + '>');
}
}
}
}
removeAttribute(name) {
if (this._attributes) {
this._attributes.removeAttribute(name);
}
}
hasAttribute(name) {
return this._attributes != null && this._attributes.hasAttribute(name);
}
getAttributes() {
return this._attributes.all;
}
get attributes() {
return this._attributes.all;
}
}
module.exports = HtmlElement;

View File

@ -0,0 +1,22 @@
'use strict';
var Node = require('./Node');
class HtmlOutput extends Node {
constructor(def) {
super('HtmlOutput');
this.argument = def.argument;
}
isLiteral() {
return this.argument instanceof Node && this.argument.type === 'Literal';
}
generateHtmlCode(generator) {
let argument = this.argument;
generator.addWrite(argument);
}
}
module.exports = HtmlOutput;

View File

@ -0,0 +1,17 @@
'use strict';
var Node = require('./Node');
class Identifier extends Node {
constructor(def) {
super('Identifier');
this.name = def.name;
}
generateCode(generator) {
var name = this.name;
generator.write(name);
}
}
module.exports = Identifier;

81
compiler/ast/If.js Normal file
View File

@ -0,0 +1,81 @@
'use strict';
var Node = require('./Node');
function removeWhitespaceNodes(whitespaceNodes) {
for (var i=0; i<whitespaceNodes.length; i++) {
whitespaceNodes[i].detach();
}
}
class If extends Node {
constructor(def) {
super('If');
this.test = def.test;
this.body = this.makeContainer(def.body);
this.else = def.else;
}
generateCode(generator) {
if (this.else) {
this.else.matched = true;
} else {
// We want to match up any else/else if statements
// with this node so that we can generate the code
// correctly.
let previous = this;
let whitespaceNodes = [];
this.forEachNextSibling((curNode) => {
if (curNode.type === 'Else') {
curNode.detach();
if (whitespaceNodes.length) {
removeWhitespaceNodes(whitespaceNodes);
}
previous.else = curNode;
curNode.matched = true;
return false; // Stop searching
} else if (curNode.type === 'ElseIf') {
curNode.detach();
if (whitespaceNodes.length) {
removeWhitespaceNodes(whitespaceNodes);
}
previous.else = curNode;
previous = curNode;
curNode.matched = true;
return true; // Keep searching since they may be more ElseIf/Else nodes...
} else if (curNode.type === 'TextOutput') {
if (curNode.isWhitespace()) {
whitespaceNodes.push(curNode);
return true; // Just whitespace... keep searching
} else {
return false; // Stop searching
}
} else {
return false; // Stop searching
}
});
}
var test = this.test;
var body = this.body;
generator.write('if (');
generator.generateCode(test);
generator.write(') ');
generator.generateBlock(body);
if (this.else) {
generator.write(' ');
generator.generateCode(this.else);
} else {
generator.write('\n');
}
}
appendChild(newChild) {
this.body.appendChild(newChild);
}
}
module.exports = If;

17
compiler/ast/Literal.js Normal file
View File

@ -0,0 +1,17 @@
'use strict';
var Node = require('./Node');
class Literal extends Node {
constructor(def) {
super('Literal');
this.value = def.value;
}
generateCode(generator) {
var value = this.value;
generator.writeLiteral(value);
}
}
module.exports = Literal;

80
compiler/ast/Node.js Normal file
View File

@ -0,0 +1,80 @@
'use strict';
var Container = require('./Container');
var ArrayContainer = require('./ArrayContainer');
var ok = require('assert').ok;
var extend = require('raptor-util/extend');
class Node {
constructor(type) {
this.type = type;
this.statement = false;
this.container = null;
this.pos = null;
this.transformersApplied = {};
}
wrap(wrapperNode) {
ok(this.container, 'Node does not belong to a container: ' + this);
var replaced = this.container.replaceChild(wrapperNode, this);
ok(replaced, 'Invalid state. Child does not belong to the container');
wrapperNode.appendChild(this);
}
makeContainer(array) {
if (array instanceof Container) {
return array;
}
return new ArrayContainer(this, array);
}
appendChild(node) {
ok(this.body, 'Node does not support child nodes: ' + this);
this.body.appendChild(node);
}
forEachChild(callback, thisObj) {
if (this.body) {
this.body.forEach(callback, thisObj);
}
}
forEachNextSibling(callback, thisObj) {
var container = this.container;
if (container) {
container.forEachNextSibling(this, callback, thisObj);
}
}
isTransformerApplied(transformer) {
return this.transformersApplied[transformer.id] === true;
}
setTransformerApplied(transformer) {
this.transformersApplied[transformer.id] = true;
}
toString() {
return JSON.stringify(this, null, 2);
}
toJSON() {
let result = extend({}, this);
delete result.container;
delete result.statement;
delete result.pos;
delete result.transformersApplied;
return result;
}
detach() {
this.container.removeChild(this);
}
isDetached() {
return this.container == null;
}
}
module.exports = Node;

20
compiler/ast/Program.js Normal file
View File

@ -0,0 +1,20 @@
'use strict';
var Node = require('./Node');
class Program extends Node {
constructor(def) {
super('Program');
this.body = def.body;
}
generateCode(generator) {
var body = this.body;
generator.generateStatements(body);
if (generator._bufferedWrites) {
generator._write('\n');
generator.flushBufferedWrites();
}
}
}
module.exports = Program;

83
compiler/ast/Require.js Normal file
View File

@ -0,0 +1,83 @@
'use strict';
var ok = require('assert').ok;
var Node = require('./Node');
class Require extends Node {
constructor(def) {
/*
<var foo=require('foo')/>
<require module='./foo' var=foo/>
<static>
<var foo=require('foo')/>
<require module='./foo' var=foo/>
</static>
*/
throw new Error('TODO Determine require syntax');
super('Require');
this.module = def.module;
this.varName = def.var;
this.isStatic = def.static !== false;
ok(this.module, '"module" is required');
ok(this.varName, '"var" is required');
}
generateCode(generator) {
var modulePath = this.module;
var varName = this.varName;
var isStatic = this.isStatic;
var builder = generator.builder;
var requireFunctionCall = builder.functionCall('require', [modulePath]);
if (varName) {
if (isStatic) {
generator.addStaticVar(varName, requireFunctionCall);
} else {
return builder.vars([
name: varName,
])
return requireFunctionCall;
}
} else {
if (isStatic) {
generator.addStaticStatement(requireFunctionCall);
} else {
}
generator.addStaticStatement()
template.functionCall('require', module);
}
if (isStatic) {
}
generator.builder.functionCall('require')
var varName = this.varName;
var target = this.target;
var builder = generator.builder;
generator.addStaticVar('forEach', '__helpers.f');
return builder.functionCall('forEach', [
target,
builder.functionDeclaration(null, [varName], this.body)
]);
}
}
module.exports = Require;

26
compiler/ast/Return.js Normal file
View File

@ -0,0 +1,26 @@
'use strict';
var Node = require('./Node');
class Return extends Node {
constructor(def) {
super('Return');
this.argument = def.argument;
}
generateCode(generator) {
if (!generator.inFunction) {
throw new Error('"return" not allowed outside a function body');
}
var argument = this.argument;
generator.write('return ');
if (argument) {
generator.generateCode(argument);
}
}
}
module.exports = Return;

33
compiler/ast/Slot.js Normal file
View File

@ -0,0 +1,33 @@
'use strict';
var Node = require('./Node');
class Slot extends Node {
constructor(def) {
super('Slot');
this.generatorSlot = null;
}
generateCode(generator) {
// At the time the code for this node is to be generated we instead
// create a slot. A slot is just a marker in the output code stream
// that we can later inject code into. The injection happens after
// the entire tree has been walked.
this.generatorSlot = generator.createSlot();
}
setContent(content) {
this.generatorSlot.setContent(content, {
statement: this.statement
});
}
toJSON() {
return {
type: this.type
};
}
}
module.exports = Slot;

View File

@ -0,0 +1,69 @@
'use strict';
var Node = require('./Node');
function createVarsArray(vars) {
return Object.keys(vars).map(function(varName) {
var varInit = vars[varName];
return {
id: varName,
init: varInit
};
});
}
class TemplateRoot extends Node {
constructor(def) {
super('TemplateRoot');
this.body = this.makeContainer(def.body);
this.staticVars = {};
this.addStaticVar('str', '__helpers.s');
this.addStaticVar('empty', '__helpers.e');
this.addStaticVar('notEmpty', '__helpers.ne');
this.addStaticVar('escapeXml', '__helpers.x');
}
generateCode(generator) {
var body = this.body;
var staticVars = this.staticVars;
var builder = generator.builder;
var program = builder.program;
var functionDeclaration = builder.functionDeclaration;
var vars = builder.vars;
var returnStatement = builder.returnStatement;
var slot = builder.slot;
var staticsSlot = slot();
var outputNode = program([
functionDeclaration('create', ['__helpers'], [
staticsSlot,
returnStatement(
functionDeclaration('render', ['data', 'out'], body))
]),
'(module.exports = require("marko").c(__filename)).c(create)'
]);
generator.generateCode(outputNode);
staticsSlot.setContent([
vars(createVarsArray(staticVars))
]);
}
toJSON(prettyPrinter) {
return {
type: this.type,
body: this.body,
staticVars: this.staticVars
};
}
addStaticVar(name, init) {
this.staticVars[name] = init;
}
}
module.exports = TemplateRoot;

139
compiler/ast/TextOutput.js Normal file
View File

@ -0,0 +1,139 @@
'use strict';
var Node = require('./Node');
var Literal = require('./Literal');
var ok = require('assert').ok;
function trim(textOutputNode) {
var text = textOutputNode.argument.value;
var isFirst = textOutputNode.isFirst;
var isLast = textOutputNode.isLast;
if (isFirst) {
//First child
text = text.replace(/^\n\s*/g, '');
}
if (isLast) {
//Last child
text = text.replace(/\n\s*$/g, '');
}
if (/^\n\s*$/.test(text)) {
//Whitespace between elements
text = '';
}
text = text.replace(/\s+/g, ' ');
textOutputNode.argument.value = text;
}
class TextOutput extends Node {
constructor(def) {
super('TextOutput');
this.argument = def.argument;
this.escape = def.escape;
this.normalized = false;
this.isFirst = false;
this.isLast = false;
}
isLiteral() {
return this.argument instanceof Node && this.argument.type === 'Literal';
}
generateHtmlCode(generator) {
this.normalizeText();
var argument = this.argument;
if (argument instanceof Literal) {
if (!argument.value) {
return;
}
} else {
let builder = generator.builder;
// TODO Only escape if necessary
argument = builder.functionCall(
'escapeXml',
[argument]);
}
generator.addWrite(argument);
}
normalizeText(generator) {
if (this.normalized) {
return;
}
var container = this.container;
if (!container) {
return;
}
var isFirst = true;
var currentTextLiteral = null;
var literalTextNodes = [];
container.forEach((curChild) => {
if (curChild.noOutput) {
// Skip over AST nodes that produce no HTML output
return;
}
if (curChild.type === 'TextOutput') {
curChild.normalized = true;
}
if (curChild.type === 'TextOutput' && curChild.isLiteral()) {
if (currentTextLiteral) {
currentTextLiteral.argument.value += curChild.argument.value;
curChild.detach();
} else {
currentTextLiteral = curChild;
literalTextNodes.push(currentTextLiteral);
if (isFirst) {
currentTextLiteral.isFirst = true;
}
}
} else {
currentTextLiteral = null;
}
isFirst = false;
});
if (currentTextLiteral) {
// Last child text
currentTextLiteral.isLast = true;
}
literalTextNodes.forEach(trim);
}
isWhitespace() {
var argument = this.argument;
return (argument instanceof Literal) &&
(typeof argument.value === 'string') &&
(argument.value.trim() === '');
}
appendText(text) {
if (!this.isLiteral()) {
throw new Error('Text cannot be appended to a non-literal TextOutput node');
}
this.argument.value += text;
}
toJSON() {
return {
type: this.type,
argument: this.argument
};
}
}
module.exports = TextOutput;

60
compiler/ast/Vars.js Normal file
View File

@ -0,0 +1,60 @@
'use strict';
var Node = require('./Node');
class Vars extends Node {
constructor(def) {
super('Vars');
this.kind = def.kind || 'var';
this.declarations = def.declarations;
}
generateCode(generator) {
var declarations = this.declarations;
var kind = this.kind;
if (!declarations || !declarations.length) {
return;
}
for (let i=0; i<declarations.length; i++) {
var declaration = declarations[i];
if (i === 0) {
generator.write(kind + ' ');
} else {
generator.writeLineIndent();
generator.write(' ');
}
var varName = declaration.id || declaration.name;
if (typeof varName !== 'string') {
throw new Error('Invalid variable name: ' + varName);
}
// TODO Validate the variable name
generator.generateCode(varName);
var initValue;
if (declaration.hasOwnProperty('init')) {
initValue = declaration.init;
} else if (declaration.hasOwnProperty('value')) {
initValue = declaration.value;
}
if (initValue) {
generator.write(' = ');
generator.generateCode(initValue);
}
if (i < declarations.length - 1) {
generator.write(',\n');
} else {
generator.write(';\n');
}
}
}
}
module.exports = Vars;

View File

@ -1,7 +0,0 @@
{
"dependencies": [
"../taglibs/browser.json",
"marko-async/browser.json",
"marko-layout/browser.json"
]
}

View File

@ -1,474 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var createError = require('raptor-util').createError;
var Expression = require('./Expression');
var strings = require('raptor-strings');
var stringify = require('raptor-json/stringify');
var regexp = require('raptor-regexp');
var ok = require('assert').ok;
var endingTokens = {
'${': '}',
'$!{': '}',
'{%': '%}',
'{?': '}',
'$': null,
'$!': null
};
var parse;
function createStartRegExpStr(starts) {
var parts = [];
starts.forEach(function (start) {
parts.push(regexp.escape('\\\\' + start));
parts.push(regexp.escape('\\' + start));
parts.push(regexp.escape(start));
});
return parts.join('|');
}
var startRegExpStr = createStartRegExpStr([
'{%',
'${',
'$!{',
'$!',
'$',
'{?'
]);
function createStartRegExp() {
return new RegExp(startRegExpStr, 'g');
}
function getLine(str, pos) {
var lines = str.split('\n');
var index = 0;
var line;
while (index < lines.length) {
line = lines[index];
if (pos - line.length + 1 < 0) {
break;
} else {
pos -= line.length + 1;
}
index++;
}
return {
str: line,
pos: pos
};
}
function errorContext(str, pos, length) {
var line = getLine(str, pos);
pos = line.pos;
str = line.str;
var start = pos - length;
var end = pos + length;
var i;
if (start < 0) {
start = 0;
}
if (end > str.length) {
end = str.length;
}
var prefix = '...';
var suffix = '...';
var context = '\n' + prefix + str.substring(start, end) + suffix + '\n';
for (i = 0; i < prefix.length; i++) {
context += ' ';
}
for (i = start; i < end; i++) {
context += i === pos ? '^' : ' ';
}
for (i = 0; i < suffix.length; i++) {
context += ' ';
}
return context;
}
function getConditionalExpression(expression) {
var tokensRegExp = /"(?:[^"]|\\")*"|'(?:[^']|\\')*'|\\\\;|\\;|[\{\};]/g;
var matches;
var depth = 0;
var parts = [];
var partStart = 0;
while ((matches = tokensRegExp.exec(expression))) {
if (matches[0] === '{') {
depth++;
continue;
} else if (matches[0] === '}') {
if (depth !== 0) {
depth--;
continue;
}
} else if (matches[0] === '\\\\;') {
/*
* 1) Convert \\; --> \;
* 2) Start searching again after the single slash
*/
expression = expression.substring(0, matches.index) + '\\;' + expression.substring(tokensRegExp.lastIndex);
tokensRegExp.lastIndex = matches.index + 1;
continue;
} else if (matches[0] === '\\;') {
/*
* 1) Convert \; --> ;
* 2) Start searching again after the semicolon
*/
expression = expression.substring(0, matches.index) + ';' + expression.substring(tokensRegExp.lastIndex);
tokensRegExp.lastIndex = matches.index + 1;
continue;
} else if (matches[0] === ';') {
if (depth === 0) {
parts.push(expression.substring(partStart, matches.index));
partStart = tokensRegExp.lastIndex;
}
}
}
if (partStart < expression.length) {
parts.push(expression.substring(partStart));
}
function getExpression(part) {
var expressionParts = [];
parse(part, {
text: function (text) {
expressionParts.push(stringify(text));
},
expression: function (expression) {
expressionParts.push(expression);
}
});
return expressionParts.join('+');
}
if (parts.length === 1) {
return '(' + parts[0] + ' ? ' + 'null' + ' : \'\')';
} else if (parts.length === 2) {
return '(' + parts[0] + ' ? ' + getExpression(parts[1]) + ' : \'\')';
} else if (parts.length === 3) {
return'(' + parts[0] + ' ? ' + getExpression(parts[1]) + ' : ' + getExpression(parts[2]) + ')';
} else {
throw new Error('Invalid simple conditional of "' + expression + '". Simple conditionals should be in the form {?<expression>;<true-template>[;<false-template>]}');
}
}
function processNestedStrings(expression, foundStrings) {
var hasExpression;
var parts;
var foundString;
function handleText(text) {
parts.push(foundString.quote + text + foundString.quote);
}
function handleExpression(expression) {
hasExpression = true;
parts.push(expression);
}
for (var i = foundStrings.length - 1; i >= 0; i--) {
foundString = foundStrings[i];
if (!foundString.value) {
continue;
}
hasExpression = false;
parts = [];
parse(foundString.value, {
text: handleText,
expression: handleExpression
});
if (hasExpression) {
expression = expression.substring(0, foundString.start) + '(' + parts.join('+') + ')' + expression.substring(foundString.end);
}
}
return expression;
}
function ExpressionParserHelper(listeners) {
this.listeners = listeners;
this.prevText = null;
this.prevEscapeXml = null;
}
ExpressionParserHelper.prototype = {
_invokeCallback: function (name, value, escapeXml) {
if (!this.listeners[name]) {
throw createError(new Error(name + ' not allowed: ' + value));
}
this.listeners[name](value, escapeXml);
},
_endText: function () {
if (this.prevText !== null) {
this._invokeCallback('text', this.prevText, this.prevEscapeXml);
this.prevText = null;
this.prevEscapeXml = null;
}
},
addXmlText: function (xmlText) {
this.addText(xmlText, false);
},
addText: function (text, escapeXml) {
if (this.prevText !== null && this.prevEscapeXml === escapeXml) {
this.prevText += text;
} else {
this._endText();
this.prevText = text;
this.prevEscapeXml = escapeXml;
}
},
addUnescapedExpression: function (expression, escapeXml) {
this.addExpression(expression, false);
},
addExpression: function (expression, escapeXml) {
this._endText();
escapeXml = escapeXml !== false;
if (!(expression instanceof Expression)) {
if (!escapeXml) {
// The expression might be a ternary operator
// so we need to surround it with parentheses.
// Minification will remove unnecessary parentheses.
// We don't need to surround with parentheses if
// the expression will be escaped since the expression
// is an argument to a function call
expression = 'str(' + expression + ')';
}
expression = new Expression(expression);
}
this._invokeCallback('expression', expression, escapeXml !== false);
},
addScriptlet: function (scriptlet) {
this._endText();
this._invokeCallback('scriptlet', scriptlet);
}
};
/**
* @memberOf raptor/templating/compiler$ExpressionParser
*
* @param str
* @param callback
* @param thisObj
*/
parse = function (str, listeners, options) {
ok(str != null, '"str" is required');
if (!options) {
options = {};
}
var textStart = 0;
var textEnd;
var startMatches;
var endMatches;
var expressionStart;
var expression;
var isScriptlet;
var isConditional;
var startToken;
var custom = options.custom || {};
function handleError(message) {
if (listeners.error) {
listeners.error(message);
return;
} else {
throw createError(new Error(message));
}
}
var startRegExp = createStartRegExp();
var helper = new ExpressionParserHelper(listeners);
startRegExp.lastIndex = 0;
/*
* Look for any of the possible start tokens (including the escaped and double-escaped versions)
*/
outer:
while ((startMatches = startRegExp.exec(str))) {
if (strings.startsWith(startMatches[0], '\\\\')) {
// \\${
/*
* We found a double-escaped start token.
*
* We found a start token that is preceeded by an escaped backslash...
* The start token is a valid start token preceded by an escaped
* backslash. Add a single black slash and handle the expression
*/
textEnd = startMatches.index + 1;
//Include everything up to and include the first backslash as part of the text
startToken = startMatches[0].substring(2);
//Record the start token
expressionStart = startMatches.index + startMatches[0].length; //The expression starts after the start token
} else if (strings.startsWith(startMatches[0], '\\')) {
// \${
/*
* We found a start token that is escaped. We should
* add the unescaped start token to the text output.
*/
helper.addText(str.substring(textStart, startMatches.index));
//Add everything preceeding the start token
helper.addText(startMatches[0].substring(1));
//Add the start token excluding the initial escape character
textStart = startRegExp.lastIndex;
// The next text block we find will be after this match
continue;
} else if (endingTokens.hasOwnProperty(startMatches[0])) {
/*
* We found a valid start token
*/
startToken = startMatches[0];
//Record the start token
textEnd = startMatches.index; //The text ends where the start token begins
} else {
throw createError(new Error('Illegal state. Unexpected start token: ' + startMatches[0]));
}
expressionStart = startRegExp.lastIndex;
//Expression starts where the start token ended
if (textStart !== textEnd) {
//If there was any text between expressions then add it now
helper.addText(str.substring(textStart, textEnd));
}
var endToken = endingTokens[startToken];
//Look up the end token
if (!endToken) {
var variableRegExp = /^([_a-zA-Z]\w*(?:\.[_a-zA-Z]\w*)*)/g;
variableRegExp.lastIndex = 0;
var variableMatches = variableRegExp.exec(str.substring(expressionStart));
//Find the variable name that follows the starting "$" token
if (!variableMatches) {
//We did not find a valid variable name after the starting "$" token
//handleError('Invalid simple variable expression. Location: ' + errorContext(str, expressionStart, 10)); //TODO: Provide a more helpful error message
helper.addText(startMatches[0]);
startRegExp.lastIndex = textStart = expressionStart;
continue outer;
}
var varName = variableMatches[1];
if (startToken === '$!') {
helper.addUnescapedExpression(varName); //Add the variable as an expression
} else {
helper.addExpression(varName); //Add the variable as an expression
}
startRegExp.lastIndex = textStart = expressionStart = expressionStart + varName.length;
continue outer;
}
isScriptlet = startToken === '{%';
isConditional = startToken === '{?';
var endRegExp = /"((?:[^"]|\\")*)"|'((?:[^']|\\')*)'|\%\}|[\{\}]/g;
//Now we need to find the ending curly
endRegExp.lastIndex = expressionStart;
var depth = 0;
var foundStrings = [];
var handler;
while ((endMatches = endRegExp.exec(str))) {
if (endMatches[0] === '{') {
depth++;
continue;
} else if (endMatches[0] === '}') {
if (isScriptlet) {
continue;
}
if (depth !== 0) {
depth--;
continue;
}
} else if (endMatches[0] === '%}') {
if (!isScriptlet) {
handleError('Ending "' + endMatches[0] + '" token was found but matched with starting "' + startToken + '" token. Location: ' + errorContext(str, endMatches.index, 10));
}
} else {
if (endMatches[0].charAt(0) === '\'' || endMatches[0].charAt(0) === '"') {
foundStrings.push({
start: endMatches.index - expressionStart,
end: endMatches.index + endMatches[0].length - expressionStart,
value: endMatches[0].slice(1, -1),
json: endMatches[0],
quote: endMatches[0].charAt(0)
});
}
continue;
}
//console.log("EXPRESSION: " + str.substring(firstCurly+1, endMatches.index));
expression = str.substring(expressionStart, endMatches.index);
handler = null;
if (startToken === '${') {
var firstColon = expression.indexOf(':');
var customType;
if (firstColon != -1) {
customType = expression.substring(0, firstColon);
handler = custom[customType] || exports.custom[customType];
if (handler) {
handler.call(exports, expression.substring(firstColon + 1), helper);
}
}
}
if (!handler) {
if (isScriptlet) {
helper.addScriptlet(expression);
} else if (isConditional) {
helper.addExpression(getConditionalExpression(expression));
} else {
if (foundStrings.length > 0) {
expression = processNestedStrings(expression, foundStrings);
}
if (startToken === '$!{') {
helper.addUnescapedExpression(expression);
} else {
helper.addExpression(expression);
}
}
}
startRegExp.lastIndex = endRegExp.lastIndex;
//Start searching from where the end token ended
textStart = endRegExp.lastIndex;
//console.log('Found ending curly. Start index now: ' + searchStart);
continue outer;
}
handleError('Ending "' + endingTokens[startToken] + '" token not found for "' + startToken + '" token. Location: ' + errorContext(str, startMatches.index, 10) + '\n');
}
if (textStart !== str.length) {
helper.addText(str.substring(textStart, str.length));
}
helper._endText();
};
function hasExpression(str) {
var hasExpressionFlag = false;
parse(str, {
text: function (text) {
},
expression: function (expression) {
hasExpressionFlag = true;
}
});
return hasExpressionFlag;
}
exports.hasExpression = hasExpression;
exports.parse = parse;
exports.custom = {
'xml': function (expression, helper) {
helper.addUnescapedExpression(new Expression(expression));
},
'entity': function (expression, helper) {
helper.addXmlText('&' + expression + ';');
},
'startTag': function (expression, helper) {
helper.addXmlText('<' + expression + '>');
},
'endTag': function (expression, helper) {
helper.addXmlText('</' + expression + '>');
},
'newline': function (expression, helper) {
helper.addText('\n');
}
};

115
compiler/index.js Normal file
View File

@ -0,0 +1,115 @@
'use strict';
var Builder = require('./Builder');
var CodeGenerator = require('./CodeGenerator');
var Compiler = require('./Compiler');
var Walker = require('./Walker');
var defaultOptions = {
/**
* Set of tag names that should automatically have whitespace preserved.
* Alternatively, if value is `true` then whitespace will be preserved
* for all tags.
*/
preserveWhitespace: {
'pre': true,
'textarea': true,
'script': true
},
/**
* If true, then the compiler will check the disk to see if a previously compiled
* template is the same age or newer than the source template. If so, the previously
* compiled template will be loaded. Otherwise, the template will be recompiled
* and saved to disk.
*
* If false, the template will always be recompiled. If `writeToDisk` is false
* then this option will be ignored.
*/
checkUpToDate: true,
/**
* If true (the default) then compiled templates will be written to disk. If false,
* compiled templates will not be written to disk (i.e., no `.marko.js` file will
* be generated)
*/
writeToDisk: true
};
var req = require;
function createBuilder(options) {
return new Builder(options);
}
function createWalker(options) {
return new Walker(options);
}
function generateCode(ast, options) {
var builder = options && options.builder;
if (!builder) {
builder = createBuilder(options);
}
var generatorOptions = {
builder
};
var generator = new CodeGenerator(generatorOptions);
return generator.generateCode(ast);
}
function compileFile(path, options, callback) {
var fs = req('fs');
if (typeof options === 'function') {
callback = options;
options = null;
}
var compiler = new Compiler(options);
if (callback) {
fs.readFile(path, {encoding: 'utf8'}, function(err, templateSrc) {
if (err) {
return callback(err);
}
try {
callback(null, compiler.compile(templateSrc, path));
} catch(e) {
callback(e);
}
});
} else {
let templateSrc = fs.readFileSync(path, {encoding: 'utf8'});
return compiler.compile(templateSrc, path);
}
}
function checkUpToDate(templateFile, templateJsFile) {
return false; // TODO Implement checkUpToDate
}
exports.createBuilder = createBuilder;
exports.generateCode = generateCode;
exports.compileFile = compileFile;
exports.defaultOptions = defaultOptions;
exports.checkUpToDate = checkUpToDate;
exports.createWalker = createWalker;
var taglibLookup = require('./taglib-lookup');
taglibLookup.registerTaglib(require.resolve('../taglibs/core/marko-taglib.json'));
/*
exports.Taglib = require('./Taglib');
exports.loader = require('./taglib-loader');
exports.lookup = require('./taglib-lookup');
exports.buildLookup = exports.lookup.buildLookup;
exports.registerTaglib = exports.lookup.registerTaglib;
exports.excludeDir = exports.lookup.excludeDir;
exports.clearCaches = function() {
exports.lookup.clearCaches();
require('./taglib-finder').clearCaches();
};
*/

View File

@ -1,152 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var extend = require('raptor-util/extend');
var req = require; // Fool code inspectors used by client-side bundles
var nodePath = require('path');
var defaultOptions = {
/**
* Set of tag names that should automatically have whitespace preserved.
* Alternatively, if value is `true` then whitespace will be preserved
* for all tags.
*/
preserveWhitespace: {
'pre': true,
'textarea': true,
'script': true
},
/**
* Set of tag names that should be allowed to be rendered as a self-closing
* XML tag. A self-closing tag will only be rendered if the tag has no nested
* content. HTML doesn't allow self-closing tags so you should likely
* never use this.
*/
allowSelfClosing: {},
/**
* Set of tag names that should be rendered with a start tag only.
*/
startTagOnly: {
'img': true,
'br': true,
'input': true,
'meta': true,
'link': true,
'hr': true
},
/**
* If true, then the compiler will check the disk to see if a previously compiled
* template is the same age or newer than the source template. If so, the previously
* compiled template will be loaded. Otherwise, the template will be recompiled
* and saved to disk.
*
* If false, the template will always be recompiled. If `writeToDisk` is false
* then this option will be ignored.
*/
checkUpToDate: true,
/**
* If true (the default) then compiled templates will be written to disk. If false,
* compiled templates will not be written to disk (i.e., no `.marko.js` file will
* be generated)
*/
writeToDisk: true
};
if (process.env.MARKO_CLEAN === '' || process.env.MARKO_CLEAN === 'true') {
defaultOptions.checkUpToDate = false;
}
extend(exports, {
createCompiler: function (path, options) {
var TemplateCompiler = require('./TemplateCompiler');
//Get a reference to the TemplateCompiler class
if (options) {
/*
* If options were provided then they should override the default options.
* NOTE: Only top-level properties are overridden
*/
options = extend(extend({}, defaultOptions), options);
} else {
options = defaultOptions; //Otherwise, no options were provided so use the default options
}
return new TemplateCompiler(path, options);
},
compile: function (src, path, options, callback) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}
return this.createCompiler(path, options).compile(src, callback);
},
compileFile: function(path, options, callback) {
var fs = req('fs');
if (typeof options === 'function') {
callback = options;
options = null;
}
var compiler = this.createCompiler(path, options);
fs.readFile(path, {encoding: 'utf8'}, function(err, src) {
if (err) {
return callback(err);
}
try {
callback(null, compiler.compile(src));
} catch(e) {
callback(e);
}
});
},
getLastModified: function(path, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
var compiler = this.createCompiler(path, options);
callback(null, compiler.getLastModified());
},
Node: require('./Node'),
ElementNode: require('./ElementNode'),
TextNode: require('./TextNode'),
expressionParser: require('./expression-parser'),
Expression: require('./Expression'),
TypeConverter: require('./TypeConverter'),
EscapeXmlContext: require('./EscapeXmlContext'),
defaultOptions: defaultOptions,
clearCaches: function() {
exports.taglibs.clearCaches();
}
});
exports.TemplateCompiler = require('./TemplateCompiler');
exports.taglibs = require('./taglibs');
exports.taglibs.excludeDir(nodePath.join(__dirname, '../'));
exports.taglibs.registerTaglib(require.resolve('../taglibs/core/marko-taglib.json'));
exports.taglibs.registerTaglib(require.resolve('../taglibs/html/marko-taglib.json'));
exports.taglibs.registerTaglib(require.resolve('../taglibs/caching/marko-taglib.json'));
exports.taglibs.registerTaglib(require.resolve('marko-layout/marko-taglib.json'));
exports.taglibs.registerTaglib(require.resolve('marko-async/marko-taglib.json'));

View File

@ -1,6 +0,0 @@
{
"main": "./marko-compiler.js",
"browser": {
"./up-to-date.js": "./up-to-date-browser.js"
}
}

View File

@ -13,10 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
require('raptor-polyfill/string/endsWith');
var taglibLoader = require('./taglib-loader');
var taglibLoader = require('../taglib-loader');
var trailingSlashRegExp = /[\\/]$/;
var excludedDirs = {};
@ -175,11 +172,12 @@ function excludeDir(dirname) {
excludedDirs[dirname] = true;
}
exports.find = find;
exports.excludeDir = excludeDir;
exports.clearCaches = function() {
function clearCaches() {
existsCache = {};
findCache = {};
taglibsForNodeModulesDirCache = {};
};
}
exports.find = find;
exports.excludeDir = excludeDir;
exports.clearCaches = clearCaches;

View File

@ -0,0 +1,5 @@
{
"browser": {
"./index.js": "./index-browser.js"
}
}

View File

@ -1,12 +1,12 @@
/*
* Copyright 2011 eBay Software Foundation
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -14,15 +14,18 @@
* limitations under the License.
*/
var makeClass = require('raptor-util').makeClass;
'use strict';
module.exports = makeClass({
$init: function(name) {
class Attribute {
constructor(name) {
this.name = name;
this.type = null;
this.required = false;
this.type = 'string';
this.allowExpressions = true;
this.setFlag = null;
this.pattern = null;
}
});
}
module.exports = Attribute;

View File

@ -1,12 +1,12 @@
/*
* Copyright 2011 eBay Software Foundation
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -14,11 +14,13 @@
* limitations under the License.
*/
var makeClass = require('raptor-util').makeClass;
'use strict';
module.exports = makeClass({
$init: function() {
class ImportedVariable {
constructor() {
this.targetProperty = null;
this.expression = null;
}
});
}
module.exports = ImportedVariable;

View File

@ -1,23 +1,24 @@
/*
* Copyright 2011 eBay Software Foundation
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var makeClass = require('raptor-util').makeClass;
module.exports = makeClass({
$init: function() {
class NestedVariable {
constructor() {
this.name = null;
}
});
}
module.exports = NestedVariable;

View File

@ -1,25 +1,26 @@
/*
* Copyright 2011 eBay Software Foundation
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var makeClass = require('raptor-util').makeClass;
module.exports = makeClass({
$init: function() {
class Property {
constructor() {
this.name = null;
this.type = 'string';
this.value = undefined;
}
});
}
module.exports = Property;

View File

@ -1,22 +1,23 @@
/*
* Copyright 2011 eBay Software Foundation
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var makeClass = require('raptor-util').makeClass;
var forEachEntry = require('raptor-util').forEachEntry;
'use strict';
var forEachEntry = require('raptor-util/forEachEntry');
var extend = require('raptor-util/extend');
var ok = require('assert').ok;
var HtmlElement = require('../../ast/HtmlElement');
function inheritProps(sub, sup) {
forEachEntry(sup, function (k, v) {
@ -26,11 +27,32 @@ function inheritProps(sub, sup) {
});
}
module.exports = makeClass({
$init: function(taglib) {
function createNodeFactory(codeGenerator, typeName) {
class CustomNode extends HtmlElement {
constructor(options) {
super(options);
this.type = typeName;
if (this.init) {
this.init(options);
}
}
}
extend(CustomNode.prototype, codeGenerator);
return function nodeFactory(el) {
return new CustomNode(el);
};
}
class Tag{
constructor(taglib) {
this.name = undefined;
this.taglibId = taglib ? taglib.id : null;
this.renderer = null;
this.nodeClass = null;
this.codeGeneratorModulePath = null;
this.nodeFactoryPath = null;
this.template = null;
this.attributes = {};
this.transformers = {};
@ -43,8 +65,10 @@ module.exports = makeClass({
this.isNestedTag = false;
this.parentTagName = null;
this.type = null; // Only applicable for nested tags
},
inheritFrom: function (superTag) {
this._nodeFactory = undefined;
}
inheritFrom(superTag) {
var subTag = this;
/*
* Have the sub tag inherit any properties from the super tag that are not in the sub tag
@ -64,15 +88,17 @@ module.exports = makeClass({
inheritProps(subTag[propName], superTag[propName]);
});
subTag.patternAttributes = superTag.patternAttributes.concat(subTag.patternAttributes);
},
forEachVariable: function (callback, thisObj) {
}
forEachVariable(callback, thisObj) {
if (!this.nestedVariables) {
return;
}
this.nestedVariables.vars.forEach(callback, thisObj);
},
forEachImportedVariable: function (callback, thisObj) {
}
forEachImportedVariable(callback, thisObj) {
if (!this.importedVariables) {
return;
}
@ -80,13 +106,14 @@ module.exports = makeClass({
forEachEntry(this.importedVariables, function (key, importedVariable) {
callback.call(thisObj, importedVariable);
});
},
forEachTransformer: function (callback, thisObj) {
}
forEachTransformer(callback, thisObj) {
forEachEntry(this.transformers, function (key, transformer) {
callback.call(thisObj, transformer);
});
},
hasTransformers: function () {
}
hasTransformers() {
/*jshint unused:false */
for (var k in this.transformers) {
if (this.transformers.hasOwnProperty(k)) {
@ -95,8 +122,8 @@ module.exports = makeClass({
}
return false;
},
addAttribute: function (attr) {
}
addAttribute(attr) {
if (attr.pattern) {
this.patternAttributes.push(attr);
} else {
@ -114,18 +141,18 @@ module.exports = makeClass({
this.attributes[attr.name] = attr;
}
},
toString: function () {
}
toString() {
return '[Tag: <' + this.name + '@' + this.taglibId + '>]';
},
forEachAttribute: function (callback, thisObj) {
}
forEachAttribute(callback, thisObj) {
for (var attrName in this.attributes) {
if (this.attributes.hasOwnProperty(attrName)) {
callback.call(thisObj, this.attributes[attrName]);
}
}
},
addNestedVariable: function (nestedVariable) {
}
addNestedVariable(nestedVariable) {
if (!this.nestedVariables) {
this.nestedVariables = {
__noMerge: true,
@ -134,30 +161,30 @@ module.exports = makeClass({
}
this.nestedVariables.vars.push(nestedVariable);
},
addImportedVariable: function (importedVariable) {
}
addImportedVariable(importedVariable) {
if (!this.importedVariables) {
this.importedVariables = {};
}
var key = importedVariable.targetProperty;
this.importedVariables[key] = importedVariable;
},
addTransformer: function (transformer) {
}
addTransformer(transformer) {
var key = transformer.path;
transformer.taglibId = this.taglibId;
this.transformers[key] = transformer;
},
setBodyFunction: function(name, params) {
}
setBodyFunction(name, params) {
this.bodyFunction = {
__noMerge: true,
name: name,
params: params
};
},
setBodyProperty: function(propertyName) {
}
setBodyProperty(propertyName) {
this.bodyProperty = propertyName;
},
addNestedTag: function(nestedTag) {
}
addNestedTag(nestedTag) {
ok(nestedTag.name, '"nestedTag.name" is required');
if (!this.nestedTags) {
@ -171,8 +198,8 @@ module.exports = makeClass({
}
this.nestedTags[nestedTag.name] = nestedTag;
},
forEachNestedTag: function (callback, thisObj) {
}
forEachNestedTag(callback, thisObj) {
if (!this.nestedTags) {
return;
}
@ -180,8 +207,30 @@ module.exports = makeClass({
forEachEntry(this.nestedTags, function (key, nestedTag) {
callback.call(thisObj, nestedTag);
});
},
hasNestedTags: function() {
}
hasNestedTags() {
return this.nestedTags != null;
}
});
getNodeFactory() {
var nodeFactory = this._nodeFactory;
if (nodeFactory !== undefined) {
return nodeFactory;
}
if (this.codeGeneratorModulePath) {
var loadedCodeGeneratorModule = require(this.codeGeneratorModulePath);
nodeFactory = createNodeFactory(loadedCodeGeneratorModule, this.codeGeneratorModulePath);
} else if (this.nodeFactoryPath) {
nodeFactory = require(this.nodeFactoryPath);
if (typeof nodeFactory !== 'function') {
throw new Error('Invalid node factory exported by module at path "' + this.nodeFactoryPath + '"');
}
} else {
return null;
}
return (this._nodeFactory = nodeFactory);
}
}
module.exports = Tag;

View File

@ -15,8 +15,10 @@
*/
'use strict';
var forEachEntry = require('raptor-util').forEachEntry;
var forEachEntry = require('raptor-util/forEachEntry');
var ok = require('assert').ok;
var extend = require('raptor-util/extend');
var taglibLoader;
function handleImport(taglib, importedTaglib) {
@ -41,30 +43,31 @@ function handleImport(taglib, importedTaglib) {
}
}
function Taglib(path) {
ok(path, '"path" expected');
this.path = this.id = path;
this.dirname = null;
this.tags = {};
this.textTransformers = [];
this.attributes = {};
this.patternAttributes = [];
this.inputFilesLookup = {};
this.imports = null;
this.importsLookup = null;
}
Taglib.prototype = {
addInputFile: function(path) {
class Taglib {
constructor(path) {
ok(path, '"path" expected');
this.path = this.id = path;
this.dirname = null;
this.tags = {};
this.textTransformers = [];
this.attributes = {};
this.patternAttributes = [];
this.inputFilesLookup = {};
this.imports = null;
this.importsLookup = null;
}
addInputFile(path) {
this.inputFilesLookup[path] = true;
},
}
getInputFiles: function() {
getInputFiles() {
return Object.keys(this.inputFilesLookup);
},
}
addAttribute: function (attribute) {
addAttribute (attribute) {
if (attribute.pattern) {
this.patternAttributes.push(attribute);
} else if (attribute.name) {
@ -72,8 +75,8 @@ Taglib.prototype = {
} else {
throw new Error('Invalid attribute: ' + require('util').inspect(attribute));
}
},
getAttribute: function (name) {
}
getAttribute (name) {
var attribute = this.attributes[name];
if (!attribute) {
for (var i = 0, len = this.patternAttributes.length; i < len; i++) {
@ -84,27 +87,40 @@ Taglib.prototype = {
}
}
return attribute;
},
addTag: function (tag) {
}
addTag (tag) {
ok(arguments.length === 1, 'Invalid args');
ok(tag.name, '"tag.name" is required');
if (!tag.name) {
throw new Error('"tag.name" is required: ' + JSON.stringify(tag));
}
this.tags[tag.name] = tag;
tag.taglibId = this.id || this.path;
},
addTextTransformer: function (transformer) {
}
addTextTransformer (transformer) {
this.textTransformers.push(transformer);
},
forEachTag: function (callback, thisObj) {
}
forEachTag (callback, thisObj) {
forEachEntry(this.tags, function (key, tag) {
callback.call(thisObj, tag);
}, this);
},
}
addImport: function(path) {
addImport(path) {
var importedTaglib = taglibLoader.load(path);
handleImport(this, importedTaglib);
}
};
toJSON() {
return {
path: this.path,
tags: this.tags,
textTransformers: this.textTransformers,
attributes: this.attributes,
patternAttributes: this.patternAttributes,
imports: this.imports
};
}
}
Taglib.Tag = require('./Tag');
Taglib.Attribute = require('./Attribute');
@ -115,4 +131,4 @@ Taglib.Transformer = require('./Transformer');
module.exports = Taglib;
taglibLoader = require('../taglib-loader');
taglibLoader = require('../');

View File

@ -1,25 +1,23 @@
/*
* Copyright 2011 eBay Software Foundation
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var makeClass = require('raptor-util').makeClass;
'use strict';
var nextTransformerId = 0;
module.exports = makeClass({
$init: function() {
class Transformer {
constructor() {
this.id = nextTransformerId++;
this.name = null;
this.tag = null;
@ -27,9 +25,9 @@ module.exports = makeClass({
this.priority = null;
this._func = null;
this.properties = {};
},
}
getFunc: function () {
getFunc() {
if (!this.path) {
throw new Error('Transformer path not defined for tag transformer (tag=' + this.tag + ')');
}
@ -51,8 +49,10 @@ module.exports = makeClass({
}
}
return this._func;
},
toString: function () {
}
toString() {
return '[Taglib.Transformer: ' + this.path + ']';
}
});
}
module.exports = Transformer;

View File

@ -0,0 +1 @@
module.exports = require('./Taglib');

View File

@ -1,12 +1,12 @@
/*
* Copyright 2011 eBay Software Foundation
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,7 +15,7 @@
*/
var ok = require('assert').ok;
var forEachEntry = require('raptor-util').forEachEntry;
var forEachEntry = require('raptor-util/forEachEntry');
var loader = require('./loader');
module.exports = function handleAttributes(value, parent, path) {

View File

@ -15,7 +15,7 @@
*/
var loader = require('./loader');
var Taglib = require('../Taglib');
var Taglib = require('./Taglib');
var cache = {};

View File

@ -1,12 +1,12 @@
/*
* Copyright 2011 eBay Software Foundation
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -17,7 +17,7 @@
var assert = require('assert');
var raptorRegexp = require('raptor-regexp');
var propertyHandlers = require('property-handlers');
var Taglib = require('../Taglib');
var Taglib = require('./Taglib');
function AttrHandlers(attr){
assert.ok(attr);

View File

@ -1,12 +1,12 @@
/*
* Copyright 2011 eBay Software Foundation
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,16 +16,16 @@
require('raptor-polyfill/string/startsWith');
var ok = require('assert').ok;
var Taglib = require('../Taglib');
var Taglib = require('./Taglib');
var propertyHandlers = require('property-handlers');
var isObjectEmpty = require('raptor-util/isObjectEmpty');
var nodePath = require('path');
var resolve = require('../../util/resolve'); // NOTE: different implementation for browser
var resolve = require('../util/resolve'); // NOTE: different implementation for browser
var ok = require('assert').ok;
var bodyFunctionRegExp = /^([A-Za-z_$][A-Za-z0-9_]*)(?:\(([^)]*)\))?$/;
var safeVarName = /^[A-Za-z_$][A-Za-z0-9_]*$/;
var handleAttributes = require('./handleAttributes');
var Taglib = require('../Taglib');
var Taglib = require('./Taglib');
var propertyHandlers = require('property-handlers');
var forEachEntry = require('raptor-util').forEachEntry;
var loader = require('./loader');
@ -161,6 +161,21 @@ TagHandlers.prototype = {
handleAttributes(value, tag, path);
},
/**
* A custom tag can be mapped to module that is is used
* to generate compile-time code for the custom tag. A
* node type is created based on the methods and methods
* exported by the code generator module.
*/
codeGenerator: function(value) {
var tag = this.tag;
var dirname = this.dirname;
var path = resolve(value, dirname);
tag.codeGeneratorModulePath = path;
this.taglib.addInputFile(path);
},
/**
* A custom tag can be mapped to a compile-time Node that gets
* added to the parsed Abstract Syntax Tree (AST). The Node can
@ -168,14 +183,15 @@ TagHandlers.prototype = {
* should be a path to a JS module that gets resolved using the
* equivalent of require.resolve(path)
*/
nodeClass: function(value) {
nodeFactory: function(value) {
var tag = this.tag;
var dirname = this.dirname;
var path = resolve(value, dirname);
tag.nodeClass = path;
tag.nodeFactoryPath = path;
this.taglib.addInputFile(path);
},
/**
* If the "preserve-whitespace" property is set to true then
* all whitespace nested below the custom tag in a template

View File

@ -14,14 +14,13 @@
* limitations under the License.
*/
require('raptor-polyfill/string/startsWith');
var ok = require('assert').ok;
var nodePath = require('path');
var handleAttributes = require('./handleAttributes');
var scanTagsDir = require('./scanTagsDir');
var resolve = require('../../util/resolve'); // NOTE: different implementation for browser
var resolve = require('../util/resolve'); // NOTE: different implementation for browser
var propertyHandlers = require('property-handlers');
var Taglib = require('../Taglib');
var Taglib = require('./Taglib');
var taglibReader = require('./taglib-reader');
var loader = require('./loader');
var tryRequire = require('try-require');
@ -66,7 +65,6 @@ function handleTag(taglibHandlers, tagName, path) {
path = '<' + tagName + '> tag in ' + taglib.path;
}
var tag = loader.tagLoader.loadTag(tagObject, path, taglib, tagDirname);
if (tag.name === undefined) {
tag.name = tagName;

View File

@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var ok = require('assert').ok;
var createError = require('raptor-util').createError;
var Taglib = require('./Taglib');
var Taglib = require('../taglib-loader/Taglib');
var extend = require('raptor-util/extend');
function transformerComparator(a, b) {
@ -81,19 +81,18 @@ function merge(target, source) {
* A taglib lookup merges in multiple taglibs so there is a single and fast lookup
* for custom tags and custom attributes.
*/
function TaglibLookup() {
this.merged = {};
this.taglibsById = {};
this._inputFiles = null;
}
class TaglibLookup {
constructor() {
this.merged = {};
this.taglibsById = {};
this._inputFiles = null;
}
TaglibLookup.prototype = {
hasTaglib: function(taglib) {
hasTaglib(taglib) {
return this.taglibsById.hasOwnProperty(taglib.id);
},
}
_mergeNestedTags: function(taglib) {
_mergeNestedTags(taglib) {
var Tag = Taglib.Tag;
// Loop over all of the nested tags and register a new custom tag
// with the fully qualified name
@ -119,9 +118,9 @@ TaglibLookup.prototype = {
handleNestedTag(nestedTag, tag.name);
});
});
},
}
addTaglib: function (taglib) {
addTaglib(taglib) {
ok(taglib, '"taglib" is required');
ok(taglib.id, '"taglib.id" expected');
@ -139,12 +138,12 @@ TaglibLookup.prototype = {
});
this._mergeNestedTags(taglib);
},
}
getTag: function (element) {
getTag(element) {
if (typeof element === 'string') {
element = {
localName: element
tagName: element
};
}
var tags = this.merged.tags;
@ -152,21 +151,21 @@ TaglibLookup.prototype = {
return;
}
var tagKey = element.namespace ? element.namespace + ':' + element.localName : element.localName;
return tags[tagKey];
},
var tagName = element.tagName;
return tags[tagName];
}
getAttribute: function (element, attr) {
getAttribute(element, attr) {
if (typeof element === 'string') {
element = {
localName: element
tagName: element
};
}
if (typeof attr === 'string') {
attr = {
localName: attr
name: attr
};
}
@ -175,18 +174,18 @@ TaglibLookup.prototype = {
return;
}
var tagKey = element.namespace ? element.namespace + ':' + element.localName : element.localName;
var attrKey = attr.namespace ? attr.namespace + ':' + attr.localName : attr.localName;
var tagName = element.tagName;
var attrName = attr.name;
function findAttributeForTag(tag, attributes, attrKey) {
function findAttributeForTag(tag, attributes, attrName) {
// try by exact match first
var attribute = attributes[attrKey];
if (attribute === undefined && attrKey !== '*') {
var attribute = attributes[attrName];
if (attribute === undefined && attrName !== '*') {
if (tag.patternAttributes) {
// try searching by pattern
for (var i = 0, len = tag.patternAttributes.length; i < len; i++) {
var patternAttribute = tag.patternAttributes[i];
if (patternAttribute.pattern.test(attrKey)) {
if (patternAttribute.pattern.test(attrName)) {
attribute = patternAttribute;
break;
}
@ -199,41 +198,42 @@ TaglibLookup.prototype = {
var globalAttributes = this.merged.attributes;
function tryAttribute(tagKey, attrKey) {
var tag = tags[tagKey];
function tryAttribute(tagName, attrName) {
var tag = tags[tagName];
if (!tag) {
return undefined;
}
return findAttributeForTag(tag, tag.attributes, attrKey) ||
findAttributeForTag(tag, globalAttributes, attrKey);
return findAttributeForTag(tag, tag.attributes, attrName) ||
findAttributeForTag(tag, globalAttributes, attrName);
}
var attrDef = tryAttribute(tagKey, attrKey) || // Look for an exact match at the tag level
tryAttribute('*', attrKey) || // If not there, see if there is a exact match on the attribute name for attributes that apply to all tags
tryAttribute(tagKey, '*'); // Otherwise, see if there is a splat attribute for the tag
var attrDef = tryAttribute(tagName, attrName) || // Look for an exact match at the tag level
tryAttribute('*', attrName) || // If not there, see if there is a exact match on the attribute name for attributes that apply to all tags
tryAttribute(tagName, '*'); // Otherwise, see if there is a splat attribute for the tag
return attrDef;
},
}
forEachNodeTransformer: function (node, callback, thisObj) {
forEachNodeTransformer(node, callback, thisObj) {
/*
* Based on the type of node we have to choose how to transform it
*/
if (node.isElementNode()) {
if (node.type === 'HtmlElement') {
this.forEachTagTransformer(node, callback, thisObj);
} else if (node.isTextNode()) {
} else if (node.type === 'TextOutput') {
this.forEachTextTransformer(callback, thisObj);
}
},
forEachTagTransformer: function (element, callback, thisObj) {
}
forEachTagTransformer(element, callback, thisObj) {
if (typeof element === 'string') {
element = {
localName: element
tagName: element
};
}
var tagKey = element.namespace ? element.namespace + ':' + element.localName : element.localName;
var tagName = element.tagName;
/*
* If the node is an element node then we need to find all matching
* transformers based on the URI and the local name of the element.
@ -243,7 +243,7 @@ TaglibLookup.prototype = {
function addTransformer(transformer) {
if (!transformer || !transformer.getFunc) {
throw createError(new Error('Invalid transformer'));
throw new Error('Invalid transformer');
}
transformers.push(transformer);
@ -256,8 +256,8 @@ TaglibLookup.prototype = {
*/
if (this.merged.tags) {
if (this.merged.tags[tagKey]) {
this.merged.tags[tagKey].forEachTransformer(addTransformer);
if (this.merged.tags[tagName]) {
this.merged.tags[tagName].forEachTransformer(addTransformer);
}
if (this.merged.tags['*']) {
@ -268,14 +268,16 @@ TaglibLookup.prototype = {
transformers.sort(transformerComparator);
transformers.forEach(callback, thisObj);
},
forEachTextTransformer: function (callback, thisObj) {
}
forEachTextTransformer(callback, thisObj) {
if (this.merged.textTransformers) {
this.merged.textTransformers.sort(transformerComparator);
this.merged.textTransformers.forEach(callback, thisObj);
}
},
getInputFiles: function() {
}
getInputFiles() {
if (!this._inputFiles) {
var inputFilesSet = {};
@ -296,10 +298,11 @@ TaglibLookup.prototype = {
}
return this._inputFiles;
},
}
toString: function() {
toString() {
return 'lookup: ' + this.getInputFiles().join(', ');
}
};
}
module.exports = TaglibLookup;

View File

@ -13,11 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var taglibLoader = require('./taglib-loader');
var taglibFinder = require('./taglib-finder');
var taglibLoader = require('../taglib-loader');
var taglibFinder = require('../taglib-finder');
var TaglibLookup = require('./TaglibLookup');
exports.registeredTaglibs = [];
@ -73,9 +72,11 @@ function registerTaglib(taglib) {
exports.registeredTaglibs.push(taglib);
}
function clearCaches() {
lookupCache = {};
}
exports.excludeDir = taglibFinder.excludeDir;
exports.registerTaglib = registerTaglib;
exports.buildLookup = buildLookup;
exports.clearCaches = function() {
lookupCache = {};
};
exports.clearCaches = clearCaches;

View File

@ -1,26 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
exports.Taglib = require('./Taglib');
exports.loader = require('./taglib-loader');
exports.lookup = require('./taglib-lookup');
exports.buildLookup = exports.lookup.buildLookup;
exports.registerTaglib = exports.lookup.registerTaglib;
exports.excludeDir = exports.lookup.excludeDir;
exports.clearCaches = function() {
exports.lookup.clearCaches();
require('./taglib-finder').clearCaches();
};

View File

@ -1,5 +0,0 @@
{
"browser": {
"./taglib-finder.js": "./taglib-finder-browser.js"
}
}

View File

@ -1,23 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
exports.getLastModified = function(sourceFile, taglibs) {
return -1;
};
exports.checkUpToDate = function(targetFile, sourceFile, taglibs) {
return false;
};

View File

@ -1,79 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var fs = require('fs'); // Fool the code inspectors for client-side bundlers
exports.getLastModified = function(sourceFile, taglibs) {
var statSource = fs.statSync(sourceFile);
var lastModifiedTime = statSource.mtime.getTime();
var taglibFiles = taglibs.getInputFiles();
var len = taglibFiles.length;
for (var i=0; i<len; i++) {
var taglibFileStat;
var taglibFile = taglibFiles[i];
try {
taglibFileStat = fs.statSync(taglibFile);
} catch(e) {
continue;
}
lastModifiedTime = Math.max(lastModifiedTime, taglibFileStat.mtime.getTime());
}
return lastModifiedTime;
};
exports.checkUpToDate = function(targetFile, sourceFile, taglibs) {
var statTarget;
try {
statTarget = fs.statSync(targetFile);
} catch(e) {
return false;
}
var statSource = fs.statSync(sourceFile);
if (statSource.mtime.getTime() > statTarget.mtime.getTime()) {
return false;
}
// Now check if any of the taglib files have been modified after the target file was generated
var taglibFiles = taglibs.getInputFiles();
var len = taglibFiles.length;
for (var i=0; i<len; i++) {
var taglibFileStat;
var taglibFile = taglibFiles[i];
try {
taglibFileStat = fs.statSync(taglibFile);
} catch(e) {
continue;
}
if (taglibFileStat.mtime.getTime() > statTarget.mtime.getTime()) {
return false;
}
}
return true;
};

View File

@ -1,74 +1,70 @@
{
"name": "marko",
"description": "Marko is an extensible, streaming, asynchronous, high performance, HTML-based templating language that can be used in Node.js or in the browser.",
"keywords": [
"templating",
"template",
"async",
"streaming"
],
"repository": {
"type": "git",
"url": "https://github.com/marko-js/marko.git"
},
"scripts": {
"init-tests": "./test/init-tests.sh",
"test": "npm run init-tests && node_modules/.bin/mocha --ui bdd --reporter spec ./test && node_modules/.bin/jshint compiler/ runtime/ taglibs/",
"test-fast": "npm run init-tests && node_modules/.bin/mocha --ui bdd --reporter spec ./test/render-test",
"test-async": "npm run init-tests && node_modules/.bin/mocha --ui bdd --reporter spec ./test/render-async-test",
"test-taglib-loader": "npm run init-tests && node_modules/.bin/mocha --ui bdd --reporter spec ./test/taglib-loader-test",
"jshint": "node_modules/.bin/jshint compiler/ runtime/ taglibs/"
},
"author": "Patrick Steele-Idem <pnidem@gmail.com>",
"maintainers": [
"Patrick Steele-Idem <pnidem@gmail.com>"
],
"dependencies": {
"app-module-path": "^1.0.0",
"async-writer": "^1.4.0",
"browser-refresh-client": "^1.0.0",
"char-props": "~0.1.5",
"events": "^1.0.2",
"htmlparser2": "^3.7.2",
"jsonminify": "^0.2.3",
"marko-async": "^2.0.0",
"marko-layout": "^2.0.0",
"minimatch": "^0.2.14",
"property-handlers": "^1.0.0",
"raptor-args": "^1.0.0",
"raptor-json": "^1.0.1",
"raptor-logging": "^1.0.1",
"raptor-modules": "^1.0.5",
"raptor-polyfill": "^1.0.0",
"raptor-promises": "^1.0.1",
"raptor-regexp": "^1.0.0",
"raptor-strings": "^1.0.0",
"raptor-util": "^1.0.0",
"resolve-from": "^1.0.0",
"sax": "^0.6.0",
"try-require": "^1.2.1"
},
"devDependencies": {
"bluebird": "^2.9.30",
"chai": "~1.8.1",
"jshint": "^2.9.1-rc1",
"mocha": "~1.15.1",
"through": "^2.3.4"
},
"license": "Apache-2.0",
"bin": {
"markoc": "bin/markoc"
},
"main": "runtime/marko-runtime.js",
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"browser": {
"./node-require.js": "./node-require-browser.js"
},
"homepage": "http://markojs.com/",
"version": "2.7.30",
"logo": {
"url": "https://raw.githubusercontent.com/marko-js/branding/master/marko-logo-small.png"
}
"name": "marko",
"description": "Marko is an extensible, streaming, asynchronous, high performance, HTML-based templating language that can be used in Node.js or in the browser.",
"keywords": [
"templating",
"template",
"async",
"streaming"
],
"repository": {
"type": "git",
"url": "https://github.com/marko-js/marko.git"
},
"scripts": {
"init-tests": "./test/init-tests.sh",
"test": "npm run init-tests && node_modules/.bin/mocha --ui bdd --reporter spec ./test && node_modules/.bin/jshint compiler/ runtime/ taglibs/",
"test-fast": "npm run init-tests && node_modules/.bin/mocha --ui bdd --reporter spec ./test/render-test",
"test-async": "npm run init-tests && node_modules/.bin/mocha --ui bdd --reporter spec ./test/render-async-test",
"test-taglib-loader": "npm run init-tests && node_modules/.bin/mocha --ui bdd --reporter spec ./test/taglib-loader-test",
"jshint": "node_modules/.bin/jshint compiler/ runtime/ taglibs/"
},
"author": "Patrick Steele-Idem <pnidem@gmail.com>",
"maintainers": [
"Patrick Steele-Idem <pnidem@gmail.com>"
],
"dependencies": {
"app-module-path": "^1.0.0",
"async-writer": "^1.4.0",
"browser-refresh-client": "^1.0.0",
"char-props": "~0.1.5",
"events": "^1.0.2",
"jsonminify": "^0.2.3",
"minimatch": "^0.2.14",
"property-handlers": "^1.0.0",
"raptor-args": "^1.0.0",
"raptor-json": "^1.0.1",
"raptor-logging": "^1.0.1",
"raptor-modules": "^1.0.5",
"raptor-polyfill": "^1.0.0",
"raptor-promises": "^1.0.1",
"raptor-regexp": "^1.0.0",
"raptor-strings": "^1.0.0",
"raptor-util": "^1.0.0",
"resolve-from": "^1.0.0",
"try-require": "^1.2.1"
},
"devDependencies": {
"bluebird": "^2.9.30",
"chai": "^3.3.0",
"jshint": "^2.5.0",
"mocha": "^2.3.3",
"through": "^2.3.4"
},
"license": "Apache-2.0",
"bin": {
"markoc": "bin/markoc"
},
"main": "runtime/marko-runtime.js",
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"browser": {
"./node-require.js": "./node-require-browser.js"
},
"homepage": "http://markojs.com/",
"version": "3.0.0-beta.1",
"logo": {
"url": "https://raw.githubusercontent.com/marko-js/branding/master/marko-logo-small.png"
}
}

View File

@ -49,15 +49,12 @@ function loadFile(templatePath) {
var targetDir = nodePath.dirname(templatePath);
var targetFile = templatePath + '.js';
var compiler = markoCompiler.createCompiler(templatePath);
var isUpToDate = compiler.checkUpToDate(targetFile);
var isUpToDate = markoCompiler.checkUpToDate(templatePath, targetFile);
if (isUpToDate) {
return require(targetFile);
}
var templateSrc = fs.readFileSync(templatePath, fsReadOptions);
var compiledSrc = compiler.compile(templateSrc);
var compiledSrc = markoCompiler.compileFile(templatePath);
// console.log('Compiled code for "' + templatePath + '":\n' + compiledSrc);
var filename = nodePath.basename(targetFile);

View File

@ -1,24 +1,32 @@
/*
* Copyright 2011 eBay Software Foundation
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
module.exports = function render(input, out) {
out.write('<!--');
if (input.renderBody) {
input.renderBody(out);
function AsyncFragmentErrorNode(props) {
AsyncFragmentErrorNode.$super.call(this, 'async-fragment-error');
if (props) {
this.setProperties(props);
}
}
AsyncFragmentErrorNode.nodeType = 'element';
AsyncFragmentErrorNode.prototype = {
doGenerateCode: function (template) {
throw new Error('Illegal State. This node should have been removed');
}
out.write('-->');
};
module.exports = AsyncFragmentErrorNode;

View File

@ -1,31 +1,32 @@
/*
* Copyright 2011 eBay Software Foundation
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
require('raptor-polyfill/string/endsWith');
var ParseTreeBuilderHtml = require('./ParseTreeBuilderHtml');
var ParseTreeBuilderXml = require('./ParseTreeBuilderXml');
function parse(src, filePath, taglibs) {
var ParseTreeBuilder = filePath.endsWith('.marko.xml') ?
ParseTreeBuilderXml :
ParseTreeBuilderHtml;
var parseTreeBuilder = new ParseTreeBuilder(taglibs);
return parseTreeBuilder.parse(src, filePath);
'use strict';
function AsyncFragmentPlaceholderNode(props) {
AsyncFragmentPlaceholderNode.$super.call(this, 'async-fragment-placeholder');
if (props) {
this.setProperties(props);
}
}
exports.parse = parse;
AsyncFragmentPlaceholderNode.nodeType = 'element';
AsyncFragmentPlaceholderNode.prototype = {
doGenerateCode: function (template) {
throw new Error('Illegal State. This node should have been removed');
}
};
module.exports = AsyncFragmentPlaceholderNode;

View File

@ -1,23 +1,32 @@
/*
* Copyright 2011 eBay Software Foundation
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var ExpressionParser = require('./ExpressionParser');
function registerCustomExpressionHandler(name, func) {
ExpressionParser.custom[name] = func;
'use strict';
function AsyncFragmentTimeoutNode(props) {
AsyncFragmentTimeoutNode.$super.call(this, 'async-fragment-timeout');
if (props) {
this.setProperties(props);
}
}
exports.registerCustomExpressionHandler = registerCustomExpressionHandler;
AsyncFragmentTimeoutNode.nodeType = 'element';
AsyncFragmentTimeoutNode.prototype = {
doGenerateCode: function (template) {
throw new Error('Illegal State. This node should have been removed');
}
};
module.exports = AsyncFragmentTimeoutNode;

180
taglibs/async/README.md Normal file
View File

@ -0,0 +1,180 @@
marko-async
=====================
The `marko-async` taglib provides support for the more efficient and simpler "Pull Model "approach to providing templates with view model data.
* __Push Model:__ Request all needed data upfront and wait for all of the data to be received before building the view model and then rendering the template.
* __Pull Model:__ Pass asynchronous data provider functions to template immediately start rendering the template. Let the template _pull_ the data needed during rendering.
The Pull Model approach to template rendering requires the use of a templating engine that supports asynchronous template rendering (e.g. [marko](https://github.com/marko-js/marko) and [dust](https://github.com/linkedin/dustjs)). This is because before rendering the template begins not all of data may have been fully retrieved. Parts of a template that depend on data that is not yet available are rendered asynchronously with the Pull Model approach.
# Push Model versus Pull Model
The problem with the traditional Push Model approach is that template rendering is delayed until _all_ data has been fully received. This reduces the time to first byte, and it also may result in the server sitting idle while waiting for data to be loaded from remote services. In addition, if certain data is no longer needed by a template then only the template needs to be modified and not the controller.
With the new Pull Model approach, template rendering begins immediately. In addition, fragments of the template that depend on data from data providers are rendered asynchronously and wait only on the associated data provider to complete. The template rendering will only be delayed for data that the template actually needs.
# Example
```javascript
var template = require('./template.marko');
module.exports = function(req, res) {
var userId = req.query.userId;
template.render({
userProfileDataProvider: function(callback) {
userProfileService.getUserProfile(userId, callback);
}
}, res);
}
```
```html
<async-fragment data-provider="data.userProfileDataProvider"
var="userProfile">
<ul>
<li>
First name: ${userProfile.firstName}
</li>
<li>
Last name: ${userProfile.lastName}
</li>
<li>
Email address: ${userProfile.email}
</li>
</ul>
</async-fragment>
```
# Out-of-order Flushing
The marko-async taglib also supports out-of-order flushing. Enabling out-of-order flushing requires two steps:
1. Add the `client-reorder` attribute to the `<async-fragment>` tag:<br>
```html
<async-fragment data-provider="data.userProfileDataProvider"
var="userProfile"
client-reorder="true">
<ul>
<li>
First name: ${userProfile.firstName}
</li>
<li>
Last name: ${userProfile.lastName}
</li>
<li>
Email address: ${userProfile.email}
</li>
</ul>
</async-fragment>
```
2. Add the `<async-fragments>` to the end of the page.
If the `client-reorder` is `true` then a placeholder element will be rendered to the output instead of the final HTML for the async fragment. The async fragment will be instead rendered at the end of the page and client-side JavaScript code will be used to move the async fragment into the proper place in the DOM. The `<async-fragments>` will be where the out-of-order fragments are rendered before they are moved into place. If there are any out-of-order fragments then inline JavaScript code will be injected into the page at this location to move the DOM nodes into the proper place in the DOM.
# Taglib API
## `<async-fragment>`
Supported Attributes:
* __`arg`__ (expression): The argument object to provide to the data provider function.
* __`arg-<arg_name>`__ (string): An argument to add to the `arg` object provided to the data provider function.
* __`client-reorder`__ (boolean): If `true`, then the async fragments will be flushed in the order they complete and JavaScript running on the client will be used to move the async fragments into the proper HTML order in the DOM. Defaults to `false`.
* __`data-provider`__ (expression, required): The source of data for the async fragment. Must be a reference to one of the following:
- `Function(callback)`
- `Function(args, callback)`
- `Promise`
- Data
* __`error-message`__ (string): Message to output if the fragment errors out. Specifying this will prevent the rendering from aborting.
* __`name`__ (string): Name to assign to this async fragment. Used for debugging purposes as well as by the `show-after` attribute (see below).
* __`placeholder`__ (string): Placeholder text to show while waiting for an out-of-order fragment to complete. Only applicable if `client-reorder` is set to `true`.
* __`show-after`__ (string): When `client-reorder` is set to `true` then displaying this fragment will be delayed until the referenced async fragment is shown.
* __`timeout`__ (integer): Override the default timeout of 10 seconds with this param. Units are in
milliseconds so `timeout="40000"` would give a 40 second timeout.
* __`timeout-message`__ (string): Message to output if the fragment times out. Specifying this
will prevent the rendering from aborting.
* __`var`__: Variable name to use when consuming the data provided by the data provider
## `<async-fragment-placeholder>`
This tag can be used to control what text is shown while an out-of-order async fragment is waiting to be loaded. Only applicable if `client-reorder` is set to `true`.
Example:
```html
<async-fragment data-provider="data.userDataProvider" var="user" client-reorder>
<async-fragment-placeholder>
Loading user data...
</async-fragment-placeholder>
<ul>
<li>First name: $user.firstName</li>
<li>Last name: $user.lastName</li>
</ul>
</async-fragment>
```
## `<async-fragment-error>`
This tag can be used to control what text is shown when an async fragment errors out.
Example:
```html
<async-fragment data-provider="data.userDataProvider" var="user">
<async-fragment-error>
An error occurred!
</async-fragment-error>
<ul>
<li>First name: $user.firstName</li>
<li>Last name: $user.lastName</li>
</ul>
</async-fragment>
```
## `<async-fragment-timeout>`
This tag can be used to control what text is shown when an async fragment times out.
Example:
```html
<async-fragment data-provider="data.userDataProvider" var="user">
<async-fragment-timeout>
A timeout occurred!
</async-fragment-timeout>
<ul>
<li>First name: $user.firstName</li>
<li>Last name: $user.lastName</li>
</ul>
</async-fragment>
```
## `<async-fragments>`
Container for all out-of-order async fragments. If any `<async-fragment>` have `client-reorder` set to true then this tag needs to be included in the page template (typically, right before the closing `</body>` tag).
Example:
```html
<!DOCTYPE html>
<html>
...
<body>
...
<async-fragment ... client-reorder/>
...
<async-fragments/>
</body>
</html>
```

View File

@ -0,0 +1,16 @@
'use strict';
module.exports = function transform(node, compiler, template) {
var asyncFragmentNode = node.parentNode;
if (!asyncFragmentNode) {
template.addError('<async-fragment-error> should be nested directly below an <async-fragment> tag.');
return;
}
// Remove the node from the tree
node.detach();
asyncFragmentNode.setProperty('errorMessage', node.getBodyContentExpression(template));
};

View File

@ -0,0 +1,11 @@
'use strict';
module.exports = function transform(node, compiler, template) {
var asyncFragmentNode = node.parentNode;
// Remove the node from the tree
node.detach();
asyncFragmentNode.setProperty('placeholder', node.getBodyContentExpression(template));
};

View File

@ -0,0 +1,50 @@
'use strict';
var varNameRegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;
module.exports = function transform(node, compiler, template) {
var varName = node.getAttribute('var') || node.getAttribute('data-provider') || node.getAttribute('dependency');
if (varName) {
if (!varNameRegExp.test(varName)) {
node.addError('Invalid variable name of "' + varName + '"');
return;
}
} else {
node.addError('Either "var" or "data-provider" is required');
return;
}
var argProps = [];
var propsToRemove = [];
var hasNameProp = false;
node.forEachProperty(function (name, value) {
if (name.startsWith('arg-')) {
var argName = name.substring('arg-'.length);
argProps.push(JSON.stringify(argName) + ': ' + value);
propsToRemove.push(name);
} else if (name === 'name') {
hasNameProp = true;
}
});
if (!hasNameProp) {
var name = node.getAttribute('data-provider');
node.setProperty('_name', name);
}
propsToRemove.forEach(function (propName) {
node.removeProperty(propName);
});
var argString;
if (argProps.length) {
argString = '{' + argProps.join(', ') + '}';
}
var arg = node.getProperty('arg');
if (arg) {
var extendFuncName = template.getStaticHelperFunction('extend', 'xt');
argString = extendFuncName + '(' + arg + ', ' + argString + ')';
}
if (argString) {
node.setProperty('arg', template.makeExpression(argString));
}
};

View File

@ -0,0 +1,202 @@
'use strict';
var logger = require('raptor-logging').logger(module);
var asyncWriter = require('async-writer');
var AsyncValue = require('raptor-async/AsyncValue');
var isClientReorderSupported = require('./client-reorder').isSupported;
function isPromise(o) {
return o && typeof o.then === 'function';
}
function promiseToCallback(promise, callback, thisObj) {
if (callback) {
var finalPromise = promise
.then(function(data) {
callback(null, data);
});
if (typeof promise.catch === 'function') {
finalPromise = finalPromise.catch(function(err) {
callback(err);
});
} else if (typeof promise.fail === 'function') {
finalPromise = finalPromise.fail(function(err) {
callback(err);
});
}
if (finalPromise.done) {
finalPromise.done();
}
}
return promise;
}
function requestData(provider, args, callback, thisObj) {
if (isPromise(provider)) {
// promises don't support a scope so we can ignore thisObj
promiseToCallback(provider, callback);
return;
}
if (typeof provider === 'function') {
var data = (provider.length === 1) ?
// one argument so only provide callback to function call
provider.call(thisObj, callback) :
// two arguments so provide args and callback to function call
provider.call(thisObj, args, callback);
if (data !== undefined) {
if (isPromise(data)) {
promiseToCallback(data, callback);
}
else {
callback(null, data);
}
}
} else {
// Assume the provider is a data object...
callback(null, provider);
}
}
module.exports = function render(input, out) {
var dataProvider = input.dataProvider;
var arg = input.arg || {};
arg.out = out;
var events = out.global.events;
var clientReorder = isClientReorderSupported && input.clientReorder === true;
var asyncOut;
var done = false;
var timeoutId = null;
var name = input.name || input._name;
var scope = input.scope || this;
function renderBody(err, data, timeoutMessage) {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
done = true;
var targetOut = asyncOut || out;
events.emit('asyncFragmentBeforeRender', {
clientReorder: clientReorder,
out: targetOut,
name: name
});
if (err) {
if (input.errorMessage) {
console.error('Async fragment (' + name + ') failed. Error:', (err.stack || err));
targetOut.write(input.errorMessage);
} else {
targetOut.error(err);
}
} else if (timeoutMessage) {
asyncOut.write(timeoutMessage);
} else {
if (input.renderBody) {
input.renderBody(targetOut, data);
}
}
if (!clientReorder) {
events.emit('asyncFragmentFinish', {
clientReorder: false,
out: targetOut
});
}
if (asyncOut) {
asyncOut.end();
// Only flush if we rendered asynchronously and we aren't using
// client-reordering
if (!clientReorder) {
out.flush();
}
}
}
var method = input.method;
if (method) {
dataProvider = dataProvider[method].bind(dataProvider);
}
requestData(dataProvider, arg, renderBody, scope);
if (!done) {
var timeout = input.timeout;
var timeoutMessage = input.timeoutMessage;
if (timeout == null) {
timeout = 10000;
} else if (timeout <= 0) {
timeout = null;
}
if (timeout != null) {
timeoutId = setTimeout(function() {
var message = 'Async fragment (' + name + ') timed out after ' + timeout + 'ms';
if (timeoutMessage) {
logger.error(message);
renderBody(null, null, timeoutMessage);
} else {
renderBody(new Error(message));
}
}, timeout);
}
if (clientReorder) {
var asyncFragmentContext = out.global.__asyncFragments || (asyncFragmentContext = out.global.__asyncFragments = {
fragments: [],
nextId: 0
});
var id = input.name || asyncFragmentContext.nextId++;
out.write('<span id="afph' + id + '">' + (input.placeholder || '') + '</span>');
var asyncValue = new AsyncValue();
// Write to an in-memory buffer
asyncOut = asyncWriter.create(null, {global: out.global});
asyncOut
.on('finish', function() {
asyncValue.resolve(asyncOut.getOutput());
})
.on('error', function(err) {
asyncValue.reject(err);
});
var fragmentInfo = {
id: id,
asyncValue: asyncValue,
out: asyncOut,
after: input.showAfter
};
if (asyncFragmentContext.fragments) {
asyncFragmentContext.fragments.push(fragmentInfo);
} else {
events.emit('asyncFragmentBegin', fragmentInfo);
}
} else {
out.flush(); // Flush everything up to this async fragment
asyncOut = out.beginAsync({
timeout: 0, // We will use our code for controlling timeout
name: name
});
}
}
};

View File

@ -0,0 +1,11 @@
'use strict';
module.exports = function transform(node, compiler, template) {
var asyncFragmentNode = node.parentNode;
// Remove the node from the tree
node.detach();
asyncFragmentNode.setProperty('timeoutMessage', node.getBodyContentExpression(template));
};

View File

@ -0,0 +1,72 @@
var clientReorder = require('./client-reorder');
module.exports = function(input, out) {
var global = out.global;
var events = global.events;
out.flush();
var asyncOut = out.beginAsync({ last: true, timeout: -1 });
out.onLast(function(next) {
var asyncFragmentsContext = global.__asyncFragments;
if (!asyncFragmentsContext || !asyncFragmentsContext.fragments.length) {
asyncOut.end();
next();
return;
}
var remaining = asyncFragmentsContext.fragments.length;
var done = false;
function handleAsyncFragment(af) {
af.asyncValue.done(function(err, html) {
if (done) {
return;
}
if (err) {
done = true;
return asyncOut.error(err);
}
if (!global._afRuntime) {
asyncOut.write(clientReorder.getCode());
global._afRuntime = true;
}
asyncOut.write('<div id="af' + af.id + '" style="display:none">' +
html +
'</div>' +
'<script type="text/javascript">$af(' + (typeof af.id === 'number' ? af.id : '"' + af.id + '"') + (af.after ? (',"' + af.after + '"') : '' ) + ')</script>');
af.out.writer = asyncOut.writer;
events.emit('asyncFragmentFinish', {
clientReorder: true,
out: af.out
});
out.flush();
if (--remaining === 0) {
done = true;
asyncOut.end();
next();
}
});
}
asyncFragmentsContext.fragments.forEach(handleAsyncFragment);
events.on('asyncFragmentBegin', function(af) {
remaining++;
handleAsyncFragment(af);
});
// Now that we have a listener attached, we want to receive any additional
// out-of-sync fragments via an event
delete asyncFragmentsContext.fragments;
});
};

View File

@ -1,5 +1,5 @@
{
"dependencies": [
"require: **/*.js"
"require: *.js"
]
}

View File

@ -0,0 +1 @@
exports.isSupported = false;

View File

@ -0,0 +1,35 @@
function $af(id, after, doc, sourceEl, targetEl, docFragment, childNodes, i, len, af) {
af = $af;
if (after && !af[after]) {
(af[(after = after + '$')] || (af[after] = [])).push(id);
} else {
doc = document;
sourceEl = doc.getElementById('af' + id);
targetEl = doc.getElementById('afph' + id);
docFragment = doc.createDocumentFragment();
childNodes = sourceEl.childNodes;
i = 0;
len=childNodes.length;
for (; i<len; i++) {
docFragment.appendChild(childNodes.item(0));
}
targetEl.parentNode.replaceChild(docFragment, targetEl);
af[id] = 1;
after = af[id + '$'];
if (after) {
i = 0;
len = after.length;
for (; i<len; i++) {
af(after[i]);
}
}
}
// sourceEl.parentNode.removeChild(sourceEl);
}

View File

@ -0,0 +1 @@
function $af(d,a,e,l,g,h,k,b,f,c){c=$af;if(a&&!c[a])(c[a+="$"]||(c[a]=[])).push(d);else{e=document;l=e.getElementById("af"+d);g=e.getElementById("afph"+d);h=e.createDocumentFragment();k=l.childNodes;b=0;for(f=k.length;b<f;b++)h.appendChild(k.item(0));g.parentNode.replaceChild(h,g);c[d]=1;if(a=c[d+"$"])for(b=0,f=a.length;b<f;b++)c(a[b])}};

View File

@ -0,0 +1,12 @@
var code;
var fs = require('fs');
exports.isSupported = true;
exports.getCode = function() {
if (!code) {
code = fs.readFileSync(require.resolve('./client-reorder-runtime.min.js'), 'utf8');
code = '<script type="text/javascript">' + code + '</script>';
}
return code;
};

View File

@ -0,0 +1,76 @@
{
"tags": {
"async-fragment": {
"renderer": "./async-fragment-tag",
"attributes": {
"data-provider": {
"type": "expression"
},
"arg": {
"type": "expression",
"preserve-name": true
},
"arg-*": {
"pattern": true,
"type": "string",
"preserve-name": true
},
"var": {
"type": "identifier"
},
"timeout": {
"type": "integer"
},
"method": {
"type": "string"
},
"timeout-message": {
"type": "string"
},
"error-message": {
"type": "string"
},
"name": {
"type": "string",
"description": "Name of async fragment (for debugging purposes only)"
},
"client-reorder": {
"type": "boolean",
"description": "Use JavaScript on client to move async fragment into the proper place."
},
"scope": {
"type": "expression"
},
"show-after": {
"type": "string"
},
"placeholder": {
"type": "string"
}
},
"vars": [
{
"name-from-attribute": "var"
}
],
"transformer": "./async-fragment-tag-transformer"
},
"async-fragments": {
"renderer": "./async-fragments-tag",
"attributes": {
}
},
"async-fragment-placeholder": {
"node-class": "./AsyncFragmentPlaceholderNode",
"transformer": "./async-fragment-placeholder-tag-transformer"
},
"async-fragment-timeout": {
"node-class": "./AsyncFragmentTimeoutNode",
"transformer": "./async-fragment-timeout-tag-transformer"
},
"async-fragment-error": {
"node-class": "./AsyncFragmentErrorNode",
"transformer": "./async-fragment-error-tag-transformer"
}
}
}

View File

@ -1,106 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var raptorCache;
var defaultCacheManager;
var req = require; // Fool the optimizer
var caches = {};
function createCache() {
var cache = {};
return {
get: function(cacheKey, options, callback) {
var value = cache[cacheKey];
if (value !== undefined) {
return callback(null, value);
}
var builder = options.builder;
builder(function(err, value) {
if (err) {
return callback(err);
}
if (value === undefined) {
value = null;
}
cache[cacheKey] = value;
callback(null, value);
});
}
};
}
var defaultCacheManager = {
getCache: function(cacheName) {
return caches[cacheName] || (caches[cacheName] = createCache());
}
};
module.exports = {
render: function (input, out) {
if (raptorCache === undefined) {
try {
raptorCache = req('raptor-cache');
defaultCacheManager = raptorCache.createCacheManager({
profiles: {
'*': {
'marko/cached-fragment': {
store: 'memory',
encoding: 'utf8'
}
}
}
});
}
catch(e) {}
}
var cacheKey = input.cacheKey;
if (!cacheKey) {
throw new Error('cache-key is required for <cached-fragment>');
}
var cacheManager = input.cacheManager || defaultCacheManager;
var cache = cacheManager.getCache(input.cacheName || 'marko/cached-fragment');
var asyncContext = out.beginAsync();
cache.get(cacheKey,
{
builder: function(callback) {
var result = out.captureString(function () {
if (input.renderBody) {
input.renderBody(out);
}
});
callback(null, result);
}
}, function(err, result) {
if (err) {
return asyncContext.error(err);
}
asyncContext.end(result);
});
}
};

View File

@ -1,16 +0,0 @@
{
"taglib-id": "marko-caching",
"tags": {
"cached-fragment": {
"renderer": "./cached-fragment-tag",
"attributes": {
"cache-key": {
"type": "string"
},
"cache-name": {
"type": "string"
}
}
}
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
// var varNameRegExp = /^[A-Za-z_][A-Za-z0-9_\.]*$/;
function AssignNode(props) {
AssignNode.$super.call(this);
if (props) {
this.setProperties(props);
}
}
AssignNode.prototype = {
doGenerateCode: function (template) {
var varName = this.getProperty('var');
var value = this.getProperty('value');
if (!varName) {
this.addError('"var" attribute is required');
}
if (!value) {
this.addError('"value" attribute is required');
}
if (varName) {
template.statement(varName + '=' + value + ';');
}
}
};
module.exports = AssignNode;

Some files were not shown because too many files have changed in this diff Show More