mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
Initial commit for marko v3 with htmljs-parser
Work-in-progress. Lots of failing tests.
This commit is contained in:
parent
032a5da4fa
commit
069b3e5ba9
13
.jshintrc
13
.jshintrc
@ -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
0
bin/markoc
Executable file → Normal file
175
compiler/Builder.js
Normal file
175
compiler/Builder.js
Normal 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
448
compiler/CodeGenerator.js
Normal 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;
|
||||
@ -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
124
compiler/Compiler.js
Normal 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;
|
||||
@ -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;
|
||||
@ -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'
|
||||
};
|
||||
@ -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
75
compiler/HtmlJsParser.js
Normal 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;
|
||||
575
compiler/Node.js
575
compiler/Node.js
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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
220
compiler/Parser.js
Normal 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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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 = {
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'&': '&',
|
||||
'"': '"',
|
||||
'\'': '''
|
||||
};
|
||||
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;
|
||||
@ -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
44
compiler/Walker.js
Normal 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;
|
||||
|
||||
109
compiler/ast/ArrayContainer.js
Normal file
109
compiler/ast/ArrayContainer.js
Normal 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;
|
||||
22
compiler/ast/Assignment.js
Normal file
22
compiler/ast/Assignment.js
Normal 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;
|
||||
24
compiler/ast/BinaryExpression.js
Normal file
24
compiler/ast/BinaryExpression.js
Normal 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
13
compiler/ast/Container.js
Normal 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
25
compiler/ast/Else.js
Normal 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
27
compiler/ast/ElseIf.js
Normal 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
31
compiler/ast/ForEach.js
Normal 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;
|
||||
42
compiler/ast/FunctionCall.js
Normal file
42
compiler/ast/FunctionCall.js
Normal 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;
|
||||
56
compiler/ast/FunctionDeclaration.js
Normal file
56
compiler/ast/FunctionDeclaration.js
Normal 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;
|
||||
33
compiler/ast/HtmlAttribute.js
Normal file
33
compiler/ast/HtmlAttribute.js
Normal 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;
|
||||
105
compiler/ast/HtmlAttributeCollection.js
Normal file
105
compiler/ast/HtmlAttributeCollection.js
Normal 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;
|
||||
19
compiler/ast/HtmlComment.js
Normal file
19
compiler/ast/HtmlComment.js
Normal 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
116
compiler/ast/HtmlElement.js
Normal 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;
|
||||
22
compiler/ast/HtmlOutput.js
Normal file
22
compiler/ast/HtmlOutput.js
Normal 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;
|
||||
17
compiler/ast/Identifier.js
Normal file
17
compiler/ast/Identifier.js
Normal 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
81
compiler/ast/If.js
Normal 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
17
compiler/ast/Literal.js
Normal 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
80
compiler/ast/Node.js
Normal 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
20
compiler/ast/Program.js
Normal 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
83
compiler/ast/Require.js
Normal 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
26
compiler/ast/Return.js
Normal 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
33
compiler/ast/Slot.js
Normal 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;
|
||||
69
compiler/ast/TemplateRoot.js
Normal file
69
compiler/ast/TemplateRoot.js
Normal 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
139
compiler/ast/TextOutput.js
Normal 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
60
compiler/ast/Vars.js
Normal 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;
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"dependencies": [
|
||||
"../taglibs/browser.json",
|
||||
"marko-async/browser.json",
|
||||
"marko-layout/browser.json"
|
||||
]
|
||||
}
|
||||
@ -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
115
compiler/index.js
Normal 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();
|
||||
};
|
||||
*/
|
||||
@ -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'));
|
||||
@ -1,6 +0,0 @@
|
||||
{
|
||||
"main": "./marko-compiler.js",
|
||||
"browser": {
|
||||
"./up-to-date.js": "./up-to-date-browser.js"
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
5
compiler/taglib-finder/package.json
Normal file
5
compiler/taglib-finder/package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"browser": {
|
||||
"./index.js": "./index-browser.js"
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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('../');
|
||||
@ -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;
|
||||
1
compiler/taglib-loader/Taglib/index.js
Normal file
1
compiler/taglib-loader/Taglib/index.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./Taglib');
|
||||
@ -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) {
|
||||
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
var loader = require('./loader');
|
||||
var Taglib = require('../Taglib');
|
||||
var Taglib = require('./Taglib');
|
||||
|
||||
var cache = {};
|
||||
|
||||
@ -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);
|
||||
@ -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
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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();
|
||||
};
|
||||
@ -1,5 +0,0 @@
|
||||
{
|
||||
"browser": {
|
||||
"./taglib-finder.js": "./taglib-finder-browser.js"
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
|
||||
};
|
||||
140
package.json
140
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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
180
taglibs/async/README.md
Normal 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>
|
||||
```
|
||||
16
taglibs/async/async-fragment-error-tag-transformer.js
Normal file
16
taglibs/async/async-fragment-error-tag-transformer.js
Normal 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));
|
||||
};
|
||||
11
taglibs/async/async-fragment-placeholder-tag-transformer.js
Normal file
11
taglibs/async/async-fragment-placeholder-tag-transformer.js
Normal 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));
|
||||
};
|
||||
50
taglibs/async/async-fragment-tag-transformer.js
Normal file
50
taglibs/async/async-fragment-tag-transformer.js
Normal 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));
|
||||
}
|
||||
};
|
||||
202
taglibs/async/async-fragment-tag.js
Normal file
202
taglibs/async/async-fragment-tag.js
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
11
taglibs/async/async-fragment-timeout-tag-transformer.js
Normal file
11
taglibs/async/async-fragment-timeout-tag-transformer.js
Normal 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));
|
||||
};
|
||||
72
taglibs/async/async-fragments-tag.js
Normal file
72
taglibs/async/async-fragments-tag.js
Normal 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;
|
||||
});
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": [
|
||||
"require: **/*.js"
|
||||
"require: *.js"
|
||||
]
|
||||
}
|
||||
1
taglibs/async/client-reorder-browser.js
Normal file
1
taglibs/async/client-reorder-browser.js
Normal file
@ -0,0 +1 @@
|
||||
exports.isSupported = false;
|
||||
35
taglibs/async/client-reorder-runtime.js
Normal file
35
taglibs/async/client-reorder-runtime.js
Normal 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);
|
||||
}
|
||||
1
taglibs/async/client-reorder-runtime.min.js
vendored
Normal file
1
taglibs/async/client-reorder-runtime.min.js
vendored
Normal 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])}};
|
||||
12
taglibs/async/client-reorder.js
Normal file
12
taglibs/async/client-reorder.js
Normal 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;
|
||||
};
|
||||
76
taglibs/async/marko-taglib.json
Normal file
76
taglibs/async/marko-taglib.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -1,16 +0,0 @@
|
||||
{
|
||||
"taglib-id": "marko-caching",
|
||||
"tags": {
|
||||
"cached-fragment": {
|
||||
"renderer": "./cached-fragment-tag",
|
||||
"attributes": {
|
||||
"cache-key": {
|
||||
"type": "string"
|
||||
},
|
||||
"cache-name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user