mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
620 lines
21 KiB
JavaScript
620 lines
21 KiB
JavaScript
/*
|
|
* 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.
|
|
*/
|
|
|
|
define.Class(
|
|
'raptor/templating/compiler/TemplateBuilder',
|
|
['raptor'],
|
|
function(raptor, require) {
|
|
"use strict";
|
|
|
|
var INDENT = " ";
|
|
|
|
var stringify = require('raptor/json/stringify'),
|
|
strings = require('raptor/strings'),
|
|
Expression = require('raptor/templating/compiler/Expression'),
|
|
forEach = raptor.forEach;
|
|
|
|
|
|
var CodeWriter = function(indent) {
|
|
this._indent = indent != null ? indent : INDENT + INDENT;
|
|
this._code = strings.createStringBuilder();
|
|
this.firstStatement = true;
|
|
this._bufferedText = null;
|
|
this._bufferedContextMethodCalls = null;
|
|
};
|
|
|
|
CodeWriter.prototype = {
|
|
|
|
write: function(expression) {
|
|
this.contextMethodCall("w", expression);
|
|
},
|
|
|
|
text: function(text) {
|
|
if (this._bufferedText === null) {
|
|
this._bufferedText = text;
|
|
}
|
|
else {
|
|
this._bufferedText += text;
|
|
}
|
|
},
|
|
|
|
contextMethodCall: function(methodName, args) {
|
|
|
|
this.flushText();
|
|
|
|
if (!this._bufferedContextMethodCalls) {
|
|
this._bufferedContextMethodCalls = [];
|
|
}
|
|
|
|
|
|
args = require('raptor/arrays').arrayFromArguments(arguments, 1);
|
|
|
|
this._bufferedContextMethodCalls.push([methodName, args]);
|
|
},
|
|
|
|
code: function(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,
|
|
thisObj,
|
|
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.decIndent(delta);
|
|
}
|
|
else if (typeof arguments[0] === 'string') {
|
|
this.code(this._indent + arguments[0]);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
flush: function() {
|
|
this.flushText();
|
|
this.flushMethodCalls();
|
|
},
|
|
|
|
flushText: function() {
|
|
var curText = this._bufferedText;
|
|
if (curText) {
|
|
this._bufferedText = null;
|
|
this.write(stringify(curText, {useSingleQuote: true}));
|
|
}
|
|
},
|
|
|
|
flushMethodCalls: function() {
|
|
var _bufferedContextMethodCalls = this._bufferedContextMethodCalls;
|
|
if (_bufferedContextMethodCalls) {
|
|
if (!this.firstStatement) {
|
|
this._code.append("\n");
|
|
}
|
|
|
|
this.firstStatement = false;
|
|
|
|
this._bufferedContextMethodCalls = null;
|
|
forEach(_bufferedContextMethodCalls, function(curWrite, i) {
|
|
var methodName = curWrite[0],
|
|
args = curWrite[1];
|
|
|
|
if (i === 0)
|
|
{
|
|
this._code.append(this.indentStr() + 'context.' + methodName + "(");
|
|
}
|
|
else {
|
|
this.incIndent();
|
|
this._code.append(this.indentStr() + '.' + methodName + "(");
|
|
}
|
|
|
|
args.forEach(function(arg, i) {
|
|
if (i !== 0) {
|
|
this._code.append(", ");
|
|
}
|
|
|
|
if (typeof arg === 'string') {
|
|
this._code.append(arg);
|
|
}
|
|
else if (typeof arg === 'boolean') {
|
|
this._code.append(arg ? 'true' : 'false');
|
|
}
|
|
else if (typeof arg === 'function') {
|
|
arg();
|
|
}
|
|
else if (arg instanceof Expression) {
|
|
this._code.append(arg.toString());
|
|
}
|
|
else if (arg) {
|
|
this._code.append(arg.toString());
|
|
}
|
|
else {
|
|
throw raptor.createError(new Error('Illegal arg for method call "' +methodName + '": ' + arg.toString() + " (" + i +")"));
|
|
}
|
|
}, this);
|
|
|
|
|
|
|
|
if (i < _bufferedContextMethodCalls.length -1) {
|
|
this._code.append(")\n");
|
|
}
|
|
else {
|
|
this._code.append(");\n");
|
|
}
|
|
if (i !== 0) {
|
|
this.decIndent();
|
|
}
|
|
}, this);
|
|
}
|
|
},
|
|
|
|
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();
|
|
}
|
|
};
|
|
|
|
|
|
var TemplateBuilder = function(compiler, resource, rootNode) {
|
|
this.resource = resource;
|
|
this.rootNode = rootNode;
|
|
this.compiler = compiler;
|
|
|
|
if (this.rootNode) {
|
|
this.rootNode.compiler = compiler;
|
|
|
|
this.rootNode.forEachNamespace(function(prefix, uri) {
|
|
|
|
if (!this.compiler.taglibs.isTaglib(uri)) {
|
|
require('raptor/templating/compiler').findAndLoadTaglib(uri);
|
|
|
|
if (!this.compiler.taglibs.isTaglib(uri)) {
|
|
this.rootNode.addError('Taglib with URI "' + uri + '" (prefix: "' + prefix + '") not found.');
|
|
}
|
|
}
|
|
}, this);
|
|
}
|
|
|
|
if (typeof resource === 'string') {
|
|
this.resource = null;
|
|
this.filePath = this.path = resource;
|
|
}
|
|
else if (require('raptor/resources').isResource(resource)) {
|
|
this.filePath = resource.getURL();
|
|
this.path = resource.getPath();
|
|
}
|
|
|
|
this.options = compiler.options;
|
|
this.templateName = null;
|
|
this.attributes = {};
|
|
|
|
this.writer = new CodeWriter();
|
|
|
|
this.staticVars = [];
|
|
this.staticVarsLookup = {};
|
|
this.helperFunctionsAdded = {};
|
|
|
|
this.vars = [];
|
|
this.varsLookup = {};
|
|
|
|
this.getStaticHelperFunction("empty", "e");
|
|
this.getStaticHelperFunction("notEmpty", "ne");
|
|
|
|
|
|
|
|
};
|
|
|
|
TemplateBuilder.prototype = {
|
|
|
|
isTaglib: function(uri) {
|
|
return this.rootNode.hasNamespacePrefix(uri);
|
|
},
|
|
|
|
getTemplateName: function() {
|
|
var options = this.options || {};
|
|
return options.templateName || this.templateName || options.defaultTemplateName;
|
|
},
|
|
|
|
_getHelperFunction: function(varName, propName, isStatic) {
|
|
var key = propName + ":" + (isStatic ? "static" : "context");
|
|
var added = this.helperFunctionsAdded[key];
|
|
if (added) {
|
|
return added;
|
|
}
|
|
else {
|
|
if (isStatic) {
|
|
this.addStaticVar(varName, "helpers." + propName);
|
|
}
|
|
else {
|
|
this.addVar(varName, "contextHelpers." + propName);
|
|
}
|
|
|
|
this.helperFunctionsAdded[key] = varName;
|
|
return varName;
|
|
}
|
|
},
|
|
|
|
getContextHelperFunction: function(varName, propName) {
|
|
return this._getHelperFunction(varName, propName, false);
|
|
},
|
|
|
|
captureCode: function(func, thisObj) {
|
|
var oldWriter = this.writer;
|
|
var newWriter = new CodeWriter(oldWriter.indentStr());
|
|
|
|
try
|
|
{
|
|
this.writer = newWriter;
|
|
func.call(thisObj);
|
|
return newWriter.getOutput();
|
|
}
|
|
finally {
|
|
this.writer = oldWriter;
|
|
}
|
|
},
|
|
|
|
getStaticHelperFunction: function(varName, propName) {
|
|
return this._getHelperFunction(varName, propName, true);
|
|
},
|
|
|
|
hasStaticVar: function(name) {
|
|
return this.staticVarsLookup[name] === true;
|
|
},
|
|
|
|
addStaticVar: function(name, expression) {
|
|
if (!this.staticVarsLookup[name]) {
|
|
this.staticVarsLookup[name] = true;
|
|
this.staticVars.push({name: name, expression: expression});
|
|
}
|
|
},
|
|
|
|
hasVar: function(name) {
|
|
return this.vars[name] === true;
|
|
},
|
|
|
|
addVar: function(name, expression) {
|
|
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 = [];
|
|
forEach(vars, 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()) {
|
|
if (escapeXml === false) {
|
|
this.contextMethodCall("a", stringify(name), valueExpression, false);
|
|
}
|
|
else {
|
|
this.contextMethodCall("a", stringify(name), valueExpression);
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
attrs: function(attrsExpression) {
|
|
if (!this.hasErrors()) {
|
|
this.contextMethodCall("a", attrsExpression);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
include: function(templateName, dataExpression) {
|
|
if (!this.hasErrors()) {
|
|
this.contextMethodCall("i", templateName, dataExpression);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
contextMethodCall: function(methodName, args) {
|
|
if (!this.hasErrors()) {
|
|
this.writer.contextMethodCall.apply(this.writer, arguments);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
addClassNameVar: function(className) {
|
|
var classVarName = className.replace(/[^a-zA-Z0-9]+/g, '_');
|
|
if (!this.hasStaticVar(classVarName)) {
|
|
this.addStaticVar(classVarName, JSON.stringify(className));
|
|
}
|
|
return classVarName;
|
|
},
|
|
|
|
addHelperFunction: function(className, functionName, bindToContext, targetVarName) {
|
|
var classVarName = this.addClassNameVar(className);
|
|
|
|
|
|
if (!targetVarName) {
|
|
targetVarName = functionName;
|
|
}
|
|
|
|
if (bindToContext === true) {
|
|
if (this.hasVar(targetVarName)) {
|
|
return;
|
|
}
|
|
|
|
this.addVar(targetVarName, "context.f(" + classVarName + "," + JSON.stringify(functionName) + ")");
|
|
}
|
|
else {
|
|
if (this.hasStaticVar(targetVarName)) {
|
|
return;
|
|
}
|
|
|
|
this.addStaticVar(targetVarName, this.getStaticHelperFunction("getHelper", "h") + "(" + classVarName + "," + JSON.stringify(functionName) + ")");
|
|
}
|
|
},
|
|
|
|
write: function(expression, options) {
|
|
if (!this.hasErrors()) {
|
|
|
|
if (options) {
|
|
if (options.escapeXml) {
|
|
expression = this.getStaticHelperFunction("escapeXml", "x") + "(" + expression + ")";
|
|
}
|
|
if (options.escapeXmlAttr) {
|
|
expression = this.getStaticHelperFunction("escapeXmlAttr", "xa") + "(" + 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;
|
|
},
|
|
|
|
getFilePath: function() {
|
|
return this.filePath;
|
|
},
|
|
|
|
getOutput: function() {
|
|
if (this.hasErrors()) {
|
|
return '';
|
|
}
|
|
|
|
var out = strings.createStringBuilder();
|
|
|
|
var templateName = this.getTemplateName();
|
|
if (!templateName) {
|
|
|
|
this.addError('Template name not defined in template at path "' + this.getFilePath() + '"');
|
|
}
|
|
|
|
var params = this.params;
|
|
if (params) {
|
|
params = ["context"].concat(params);
|
|
}
|
|
else {
|
|
params = ["context"];
|
|
}
|
|
|
|
out.append('$rset("rhtml", ');
|
|
out.append(stringify(templateName));
|
|
out.append(', ');
|
|
out.append('function(helpers, templateInfo) {\n');
|
|
//Write out the static variables
|
|
|
|
this.writer.flush();
|
|
|
|
this._writeVars(this.staticVars, out, INDENT);
|
|
out.append('\n' + INDENT + 'return function(data, context) {\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());
|
|
|
|
out.append(INDENT + '}\n});');
|
|
return out.toString();
|
|
},
|
|
|
|
setTemplateName: function(templateName) {
|
|
this.templateName = templateName;
|
|
},
|
|
|
|
makeExpression: function(expression) {
|
|
if (expression instanceof Expression) {
|
|
return expression;
|
|
}
|
|
else {
|
|
return new Expression(expression);
|
|
}
|
|
},
|
|
|
|
isExpression: function(expression) {
|
|
return expression instanceof Expression;
|
|
},
|
|
|
|
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(uri, localName) {
|
|
return this.compiler.getNodeClass(uri, localName);
|
|
},
|
|
|
|
transformTree: function(node) {
|
|
this.compiler.transformTree(node, this);
|
|
},
|
|
|
|
INDENT: INDENT
|
|
|
|
};
|
|
|
|
return TemplateBuilder;
|
|
}); |