Initial code commit before migration to RaptorJS 3

This commit is contained in:
Patrick Steele-Idem 2014-01-07 16:59:30 -07:00
commit ff475c6e62
75 changed files with 9913 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
/work
/build
/.idea/
/npm-debug.log
/node_modules
/*.sublime-workspace
*.orig
.DS_Store
coverage

40
.jshintrc Normal file
View File

@ -0,0 +1,40 @@
{
"predef": [
],
"globals": {
"define": true,
"require": true
},
"node" : true,
"es5" : false,
"browser" : true,
"boss" : false,
"curly": false,
"debug": false,
"devel": false,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": true,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": true,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": false,
"white": false,
"eqeqeq": false,
"latedef": true,
"unused": "vars",
/* Relaxing options: */
"eqnull": true
}

1
README.md Normal file
View File

@ -0,0 +1 @@
README.md

11
lib/async-optimizer.json Normal file
View File

@ -0,0 +1,11 @@
{
"dependencies": [
{
"package": "raptor/render-context/async"
},
{
"package": "raptor/promises"
},
"templating_async.js"
]
}

View File

@ -0,0 +1,187 @@
/*
* 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.
*/
/**
* Utility class to support sub-attributes in an XML attribute. Each sub-attribute must
* be separated by a semicolon. Within each sub-attribute, the name/value pair must
* be split using an equal sign. However, the name for the first sub-attribute
* is optional and a default name can be provided when reading the sub-attributes.
*
* <p>
* Sub-attribute format:
* (<attr-value>)?(<attr-name>=<attr-value>;)*(<attr-name>=<attr-value>)
*
*
*
*/
define.Class(
'raptor/templating/compiler/AttributeSplitter',
['raptor'],
function(raptor, require) {
"use strict";
var strings = require('raptor/strings'),
Expression = require('raptor/templating/compiler/Expression'),
TypeConverter = require('raptor/templating/compiler/TypeConverter'),
regExp = /"(?:[^"]|\\")*"|'(?:[^']|\\')*'|==|===|[;=]/g;
/**
*
*/
var AttributeSplitter = function() {
};
/**
* Parses the provided string to find the sub-attributes that it contains.
* The parsed output can be either returned as an array or a map. By default,
* the parsed output is returned as a map where each property corresponds
* to a sub-attribute. However, if the order of the sub-attributes is important
* then the "ordered" option can be set to "true" and
* an array will instead be returned where each element in the array is an object
* with a name and value property that corresponds to the matching sub-attribute.
*
* <p>
* Supported options:
* <ul>
* <li>ordered (boolean, defaults to "false") - If true then an array is returned (see above). Otherwise, an object is returned.
* </ul>
*
* @memberOf raptor/templating/compiler$AttributeSplitter
* @param attr {String} The attribute to split
* @param types {Object} Type definitions for the possible sub-attributes.
* @param options
* @returns
*/
AttributeSplitter.parse = function(attr, types, options) {
if (!options) {
options = {};
}
var partStart = 0,
ordered = options.ordered === true,
defaultName = options.defaultName,
removeDashes = options.removeDashes === true,
matches,
equalIndex = -1,
result = ordered ? [] : {},
handleError = function(message) {
if (options.errorHandler) {
options.errorHandler(message);
return;
}
else {
throw raptor.createError(new Error(message));
}
},
finishPart = function(endIndex) {
if (partStart === endIndex) {
//The part is an empty string... ignore it
return;
}
var name,
value;
if (equalIndex != -1) {
name = strings.trim(attr.substring(partStart, equalIndex));
value = attr.substring(equalIndex+1, endIndex);
}
else {
if (defaultName) {
name = defaultName;
value = attr.substring(partStart, endIndex);
if (!strings.trim(value).length) {
return; //ignore empty parts
}
}
else {
name = attr.substring(partStart, endIndex);
}
}
if (name) {
name = strings.trim(name);
}
if (!strings.trim(name).length && !strings.trim(value).length) {
equalIndex = -1;
return; //ignore empty parts
}
if (types) {
var type = types[name] || types['*'];
if (type) {
if (value != null) {
value = TypeConverter.convert(value, type.type, type.allowExpressions !== false);
}
if (type.name) {
name = type.name;
}
}
else {
handleError('Invalid sub-attribute name of "' + name + '"');
}
}
if (name && removeDashes) {
name = name.replace(/-([a-z])/g, function(match, lower) {
return lower.toUpperCase();
});
}
if (ordered) {
result.push({name: name, value: value});
}
else {
result[name] = value;
}
equalIndex = -1; //Reset the equal index
};
/*
* Keep searching the string for the relevant tokens.
*
* NOTE: The regular expression will also return matches for JavaScript strings,
* but they are just ignored. This ensures that semicolons inside strings
* are not treated as
*/
while((matches = regExp.exec(attr))) {
//console.error(matches[0]);
if (matches[0] == ';') {
finishPart(matches.index);
partStart = matches.index+1;
equalIndex = -1;
}
else if (matches[0] == '=') {
if (equalIndex == -1) {
equalIndex = matches.index;
}
}
}
finishPart(attr.length);
//console.error("AttributeSplitter - result: ", result);
return result;
};
return AttributeSplitter;
});

406
lib/compiler/ElementNode.js Normal file
View File

@ -0,0 +1,406 @@
/*
* 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/ElementNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var forEachEntry = raptor.forEachEntry,
forEach = raptor.forEach,
objects = require('raptor/objects'),
escapeXmlAttr = require('raptor/xml/utils').escapeXmlAttr,
XML_URI = 'http://www.w3.org/XML/1998/namespace',
XML_URI_ALT = 'http://www.w3.org/XML/1998/namespace',
ExpressionParser = require('raptor/templating/compiler/ExpressionParser');
var ElementNode = function(localName, uri, prefix) {
ElementNode.superclass.constructor.call(this, 'element');
if (!this._elementNode) {
this._elementNode = true;
this.dynamicAttributesExpression = null;
this.attributesByNS = {};
this.prefix = prefix;
this.localName = this.tagName = localName;
this.uri = uri;
this.allowSelfClosing = false;
this.startTagOnly = false;
}
};
ElementNode.prototype = {
getQName: function() {
return this.localName ? (this.prefix ? this.prefix + ":" : "") + this.localName : null;
},
/**
*
* @param startTagOnly
*/
setStartTagOnly: function(startTagOnly) {
this.startTagOnly = true;
},
/**
*
* @param allowSelfClosing
*/
setAllowSelfClosing: function(allowSelfClosing) {
this.allowSelfClosing = allowSelfClosing;
},
/**
*
* @returns {Boolean}
*/
isElementNode: function() {
return true;
},
/**
*
* @returns {Boolean}
*/
isTextNode: function() {
return false;
},
getAllAttributes: function() {
var allAttrs = [];
forEachEntry(this.attributesByNS, function(uri, attrs) {
forEachEntry(attrs, function(name, attr) {
allAttrs.push(attr);
});
}, this);
return allAttrs;
},
forEachAttributeAnyNS: function(callback, thisObj) {
forEachEntry(this.attributesByNS, function(uri, attrs) {
forEachEntry(attrs, function(name, attr) {
callback.call(thisObj, attr);
});
});
},
forEachAttributeNS: function(uri, callback, thisObj) {
var attrs = this.attributesByNS[uri || ''];
if (attrs) {
forEachEntry(attrs, function(name, attr) {
callback.call(thisObj, attr);
});
}
},
/**
*
* @returns {Array}
*/
getAttributes: function() {
var attributes = [];
forEachEntry(this.attributes, function(name, attr) {
attributes.push(attr);
}, this);
return attributes;
},
/**
*
* @param name
* @returns
*/
getAttribute: function(name) {
return this.getAttributeNS(null, name);
},
/**
*
* @param uri
* @param localName
* @returns
*/
getAttributeNS: function(uri, localName) {
var attrNS = this.attributesByNS[uri || ''];
var attr = attrNS ? attrNS[localName] : undefined;
return attr ? attr.value : undefined;
},
/**
*
* @param localName
* @param value
*/
setAttribute: function(localName, value, escapeXml) {
this.setAttributeNS(null, localName, value, null, escapeXml);
},
/**
*
* @param uri
* @param localName
* @param value
* @param prefix
*/
setAttributeNS: function(uri, localName, value, prefix, escapeXml) {
var attrNS = this.attributesByNS[uri || ''] || (this.attributesByNS[uri || ''] = {});
attrNS[localName] = {
localName: localName,
value: value,
prefix: prefix,
uri: uri,
escapeXml: escapeXml,
qName: prefix ? (prefix + ":" + localName) : localName,
name: uri ? (uri + ":" + localName) : localName,
toString: function() {
return this.name;
}
};
},
/**
*
* @param name
*/
setEmptyAttribute: function(name) {
this.setAttribute(name, null);
},
/**
*
* @param localName
*/
removeAttribute: function(localName) {
this.removeAttributeNS(null, localName);
},
/**
*
* @param uri
* @param localName
*/
removeAttributeNS: function(uri, localName) {
var attrNS = this.attributesByNS[uri || ''] || (this.attributesByNS[uri || ''] = {});
if (attrNS) {
delete attrNS[localName];
if (objects.isEmpty(attrNS)) {
delete this.attributesByNS[uri || ''];
}
}
},
removeAttributesNS: function(uri) {
delete this.attributesByNS[uri || ''];
},
/**
*
* @returns {Boolean}
*/
isPreserveWhitespace: function() {
var preserveSpace = ElementNode.superclass.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 !objects.isEmpty(this.attributesByNS);
},
hasAttributes: function() {
return this.hasAttributesNS('');
},
hasAttributesNS: function(uri) {
return this.attributesByNS[uri || ''] !== undefined;
},
hasAttribute: function(localName) {
return this.hasAttributeNS('', localName);
},
hasAttributeNS: function(uri, localName) {
var attrsNS = this.attributesByNS[uri || ''];
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;
},
/**
*
* @param template
*/
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();
}
template.text("<" + name);
this.forEachAttributeAnyNS(function(attr) {
var prefix = attr.prefix;
if (!prefix && attr.uri) {
prefix = this.resolveNamespacePrefix(attr.uri);
}
if (prefix) {
name = prefix + (attr.localName ? (":" + attr.localName) : "");
}
else {
name = attr.localName;
}
if (attr.value === null || attr.value === undefined) {
template.text(' ' + name);
}
else if (template.isExpression(attr.value)) {
template.attr(name, attr.value, attr.escapeXml !== false);
}
else {
var attrParts = [],
hasExpression = false,
invalidAttr = false;
ExpressionParser.parse(
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);
}
},
this);
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 + '="');
forEach(attrParts, 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 raptor.createError(new Error("Illegal state"));
}
});
template.text('"');
}
}
}
}, this);
if (this.dynamicAttributesExpression) {
template.attrs(this.dynamicAttributesExpression);
}
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 (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) + ">";
}
};
return ElementNode;
});

View File

@ -0,0 +1,20 @@
/*
* 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.Enum(
'raptor/templating/compiler/EscapeXmlContext',
['Element',
'Attribute']);

View File

@ -0,0 +1,69 @@
/*
* 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/Expression',
['raptor'],
function(raptor, require) {
"use strict";
var operatorsRegExp = /"(?:[^"]|\\")*"|'(?:[^']|\\')*'|\s+(?:and|or|lt|gt|eq|ne|lt|gt|ge|le)\s+/g,
strings = require('raptor/strings'),
replacements = {
"and": " && ",
"or": " || ",
"eq": " === ",
"ne": " !== ",
"lt": " < ",
"gt": " > ",
"ge": " >= ",
"le": " <= "
},
handleBinaryOperators = function(str) {
return str.replace(operatorsRegExp, function(match) {
return replacements[strings.trim(match)] || match;
});
};
var Expression = function(expression, replaceSpecialOperators) {
if (expression == null) {
throw raptor.createError(new Error("expression argument is required"));
}
if (replaceSpecialOperators !== false && typeof expression === 'string') {
expression = handleBinaryOperators(expression);
}
this.expression = expression;
};
Expression.prototype = {
/**
*
* @returns
*/
getExpression: function() {
return this.expression;
},
/**
*/
toString: function() {
return this.expression.toString();
}
};
return Expression;
});

View File

@ -0,0 +1,550 @@
/*
* 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/ExpressionParser',
['raptor'],
function(raptor, require) {
"use strict";
var Expression = require('raptor/templating/compiler/Expression'),
strings = require('raptor/strings'),
stringify = require('raptor/json/stringify'),
regexp = require('raptor/regexp'),
endingTokens = {
// "{": "}",
"${": "}",
"$!{": "}",
"{%": "%}",
"{?": "}",
"$": null,
"$!": null
},
createStartRegExpStr = function(starts) {
var parts = [];
raptor.forEach(starts, function(start) {
parts.push(regexp.escape("\\\\" + start));
parts.push(regexp.escape("\\" + start));
parts.push(regexp.escape(start));
});
return parts.join("|");
},
startRegExpStr = createStartRegExpStr(["{%", "${", "$!{", "$!", "$", "{?"]),
createStartRegExp = function() {
return new RegExp(startRegExpStr, "g");
},
getLine = function(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
};
},
errorContext = function(str, pos, length) {
var line = getLine(str, pos);
pos = line.pos;
str = line.str;
var start = pos - length,
end = pos + length,
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;
},
getConditionalExpression = function(expression) {
var tokensRegExp = /"(?:[^"]|\\")*"|'(?:[^']|\\')*'|\\\\;|\\;|[\{\};]/g,
matches,
depth = 0;
var parts = [],
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));
}
var getExpression = function(part) {
var expressionParts = [];
ExpressionParser.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>]}');
}
},
processNestedStrings = function(expression, foundStrings) {
var hasExpression,
parts,
handleText = function(text) {
parts.push(foundString.quote + text + foundString.quote);
},
handleExpression = function(expression) {
hasExpression = true;
parts.push(expression);
};
for (var i=foundStrings.length-1, foundString; i>=0; i--) {
foundString = foundStrings[i];
if (!foundString.value) {
continue;
}
hasExpression = false;
parts = [];
ExpressionParser.parse(foundString.value, {
text: handleText,
expression: handleExpression
});
if (hasExpression) {
expression = expression.substring(0, foundString.start) + "(" + parts.join('+') + ")" + expression.substring(foundString.end);
}
}
return expression;
};
/**
*
*/
var ExpressionParserHelper = define.Class(function() {
return {
init: function(callback, callbackThisObj) {
this.callback = callback;
this.callbackThisObj = callbackThisObj;
this.prevText = null;
this.prevEscapeXml = null;
},
_invokeCallback: function(name, value, escapeXml) {
if (!this.callback[name]) {
throw raptor.createError(new Error(name + " not allowed: " + value));
}
this.callback[name].call(this.callbackThisObj, value, escapeXml);
},
_endText: function() {
if (this.prevText !== null) {
this._invokeCallback("text", this.prevText, this.prevEscapeXml);
this.prevText = null;
this.prevEscapeXml = null;
}
},
/**
*
* @param newText
* @returns
*/
addXmlText: function(xmlText) {
this.addText(xmlText, false);
},
/**
*
* @param newText
* @returns
*/
addText: function(text, escapeXml) {
if (this.prevText !== null && this.prevEscapeXml === escapeXml) {
this.prevText += text;
}
else {
this._endText();
this.prevText = text;
this.prevEscapeXml = escapeXml;
}
},
addXmlExpression: function(expression, escapeXml) {
this.addExpression(expression, false);
},
/**
*
* @param expression
* @returns
*/
addExpression: function(expression, escapeXml) {
this._endText();
if (!(expression instanceof Expression)) {
expression = new Expression(expression);
}
this._invokeCallback("expression", expression, escapeXml !== false);
},
/**
*
* @param scriptlet
* @returns
*/
addScriptlet: function(scriptlet) {
this._endText();
this._invokeCallback("scriptlet", scriptlet);
}
};
});
var ExpressionParser = function() {
};
/**
* @memberOf raptor/templating/compiler$ExpressionParser
*
* @param str
* @param callback
* @param thisObj
*/
ExpressionParser.parse = function(str, callback, thisObj, options) {
if (!options) {
options = {};
}
var textStart = 0, //The index of the start of the next text block
textEnd, //The index of the current text block
startMatches, //The matches found when searching for the possible start tokens
endMatches, //The matches found when searching through special expression tokens
expressionStart, //The index of the start of the current expression
expression, //The current expression string
isScriptlet, //If true, then the expression is a scriptlet,
isConditional, //If true, then the expression is a conditional (i.e. {?<expression>;<true-template>[;<false-template>]}
startToken, //The start token for the current expression
custom = options.custom || {},
handleError = function(message) {
if (callback.error) {
callback.error.call(thisObj, message);
return;
}
else {
throw raptor.createError(new Error(message));
}
};
var startRegExp = createStartRegExp();
var helper = new ExpressionParserHelper(callback, thisObj);
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 raptor.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) { //Check if the start token has an end token... not all start tokens do. For example: $myVar
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.addXmlExpression(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; //Start searching from where the expression begins
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(":"),
customType;
if (firstColon != -1) {
customType = expression.substring(0, firstColon);
handler = custom[customType] || ExpressionParser.custom[customType];
if (handler) {
handler.call(ExpressionParser, 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.addXmlExpression(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();
};
ExpressionParser.custom = {
"xml": function(expression, helper) {
helper.addXmlExpression(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");
}
};
return ExpressionParser;
});

738
lib/compiler/Node.js Normal file
View File

@ -0,0 +1,738 @@
/*
* 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/Node',
['raptor'],
function(raptor, require) {
"use strict";
var forEachEntry = raptor.forEachEntry,
forEach = raptor.forEach,
isArray = Array.isArray,
isEmpty = require('raptor/objects').isEmpty;
var Node = function(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 = {};
}
};
Node.prototype = {
setRoot: function(isRoot) {
this._isRoot = isRoot;
},
getPosition: function() {
var pos = this.pos || this.getProperty("pos") || {
toString: function() {
return "(unknown position)";
}
};
return pos;
},
setPosition: function(pos) {
this.pos = pos;
},
addError: function(error) {
var compiler = this.compiler,
curNode = this;
while (curNode != null && !compiler) {
compiler = curNode.compiler;
if (compiler) {
break;
}
curNode = curNode.parentNode;
}
if (!compiler) {
throw raptor.createError(new Error("Template compiler not set for node " + this));
}
var pos = this.getPosition();
compiler.addError(error + " (" + this.toString() + ")", pos);
},
setProperty: function(name, value) {
this.setPropertyNS(null, name, value);
},
setPropertyNS: function(uri, name, value) {
if (!uri) {
uri = "";
}
var namespacedProps = this.properties[uri];
if (!namespacedProps) {
namespacedProps = this.properties[uri] = {};
}
namespacedProps[name] = value;
},
setProperties: function(props) {
this.setPropertiesNS(null, props);
},
setPropertiesNS: function(uri, props) {
if (!uri) {
uri = "";
}
forEachEntry(props, function(name, value) {
this.setPropertyNS(uri, name, value);
}, this);
},
getPropertyNamespaces: function() {
return Object.keys(this.properties);
},
getProperties: function(uri) {
return this.getPropertiesNS(null);
},
hasProperty: function(name) {
return this.hasPropertyNS('', name);
},
hasPropertyNS: function(uri, name) {
if (!uri) {
uri = "";
}
var namespaceProps = this.properties[uri];
return namespaceProps.hasOwnProperty(name);
},
getPropertiesByNS: function() {
return this.properties;
},
getPropertiesNS: function(uri) {
if (!uri) {
uri = "";
}
return this.properties[uri];
},
forEachProperty: function(callback, thisObj) {
forEachEntry(this.properties, function(uri, properties) {
forEachEntry(properties, function(name, value) {
callback.call(thisObj, uri, name, value);
}, this);
}, this);
},
getProperty: function(name) {
return this.getPropertyNS(null, name);
},
getPropertyNS: function(uri, name) {
if (!uri) {
uri = "";
}
var namespaceProps = this.properties[uri];
return namespaceProps ? namespaceProps[name] : undefined;
},
removeProperty: function(name) {
this.removePropertyNS("", name);
},
removePropertyNS: function(uri, name) {
if (!uri) {
uri = "";
}
var namespaceProps = this.properties[uri];
if (namespaceProps) {
delete namespaceProps[name];
}
if (isEmpty(namespaceProps)) {
delete this.properties[uri];
}
},
removePropertiesNS: function(uri) {
delete this.properties[uri];
},
forEachPropertyNS: function(uri, callback, thisObj) {
if (uri == null) {
uri = '';
}
var props = this.properties[uri];
if (props) {
forEachEntry(props, function(name, value) {
callback.call(thisObj, name, value);
}, this);
}
},
forEachChild: function(callback, thisObj) {
if (!this.firstChild) {
return;
}
/*
* Convert the child linked list to an array so that
* if the callback code manipulates the child nodes
* looping won't break
*/
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 raptor.createError(new Error("template argument is required"));
}
var _this = this;
return template.makeExpression({
toString: function() {
return template.captureCode(function() {
if (asFunction) {
template.code("function() {\n")
.code(template.indentStr(2) + "return context." + (escapeXml !== false ? "captureString" : "c") + "(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("context." + (escapeXml !== false ? "captureString" : "c") + "(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;
}
raptor.forEach(childNodes, 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,
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;
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)) {
raptor.forEach(node, 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,
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;
},
isTextNode: function() {
return false;
},
isElementNode: function() {
return false;
},
setStripExpression: function(stripExpression) {
this.stripExpression = stripExpression;
},
generateCode: function(template) {
this.compiler = template.compiler;
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.getAttribute("nextStripVarId");
if (nextStripVarId == null) {
nextStripVarId = template.setAttribute("nextStripVarId", 0);
}
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 raptor.createError(new Error("Unable to generate code for node " + this + " at position [" + this.getPosition() + "]. Exception: " + e), e);
}
},
/**
*
* @returns {Boolean}
*/
isPreserveWhitespace: function() {
return this.preserveWhitespace;
},
/**
*
* @param preserve
*/
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 raptor.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('raptor/templating/compiler/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, uri) {
existingNamespaceMappings[prefix] = uri;
prefixMappings[uri] = prefix;
});
},
hasNamespacePrefix: function(uri) {
return this.prefixMappings.hasOwnProperty(uri);
},
resolveNamespacePrefix: function(uri) {
var prefix = this.prefixMappings[uri];
return (!prefix && this.parentNode) ?
this.parentNode.resolveNamespacePrefix() :
prefix;
},
forEachNamespace: function(callback, thisObj) {
forEachEntry(this.namespaceMappings, callback, thisObj);
},
getNodeClass: function() {
return this.nodeClass || this.getClass();
},
setNodeClass: function(nodeClass) {
this.nodeClass = nodeClass;
},
prettyPrintTree: function() {
var out = [];
var printNode = function(node, indent) {
out.push(indent + node.toString() + '\n');
node.forEachChild(function(child) {
printNode(child, indent + " ");
}, this);
};
printNode(this, "");
return out.join('');
},
setEscapeXmlBodyText: function(escapeXmlBodyText) {
this.escapeXmlBodyText = escapeXmlBodyText;
},
isEscapeXmlBodyText: function() {
return this.escapeXmlBodyText;
},
setEscapeXmlContext: function(escapeXmlContext) {
this.escapeXmlContext = escapeXmlContext;
},
getEscapeXmlContext: function() {
return this.escapeXmlContext;
}
};
return Node;
});

View File

@ -0,0 +1,165 @@
/*
* 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/ParseTreeBuilder',
['raptor'],
function(raptor, require, exports, module) {
"use strict";
var logger = module.logger(),
sax = require('raptor/xml/sax'),
forEach = raptor.forEach,
TextNode = require('raptor/templating/compiler/TextNode'),
ElementNode = require('raptor/templating/compiler/ElementNode');
var ParseTreeBuilder = function() {
};
ParseTreeBuilder.parse = function(src, filePath, taglibs) {
var builder = new ParseTreeBuilder();
return builder.parse(src, filePath, taglibs);
};
ParseTreeBuilder.prototype = {
/**
* @param src {String} The XML source code to parse
* @param src {String} The file path (for debugging and error reporting purposes)
* @param taglibs {raptor/templating/compiler/TaglibCollection} The taglib collection. Required for resolving taglib URIs when short names are used.
*/
parse: function(src, filePath, taglibs) {
var logger = logger,
parentNode = null,
rootNode = null,
prevTextNode = null,
imports,
parser = sax.createParser({
trim: false,
normalize: false,
dom: src.documentElement != null
}),
characters = function(t, isCDATA) {
if (!parentNode) {
return; //Some bad XML parsers allow text after the ending element...
}
if (prevTextNode) {
prevTextNode.text += t;
}
else {
prevTextNode = new TextNode(t);
prevTextNode.pos = parser.getPos();
parentNode.appendChild(prevTextNode);
}
};
parser.on({
error: function(e) {
throw raptor.createError(e);
},
characters: function(t) {
characters(t, false);
},
cdata: function(t) {
characters(t, true);
},
startElement: function(el) {
prevTextNode = null;
var importsAttr,
importedAttr,
importedTag;
var elementNode = new ElementNode(
el.getLocalName(),
taglibs.resolveURI(el.getNamespaceURI()),
el.getPrefix());
elementNode.addNamespaceMappings(el.getNamespaceMappings());
elementNode.pos = parser.getPos();
forEach(el.getAttributes(), function(attr) {
if (attr.getLocalName() === 'imports' && !attr.getNamespaceURI()) {
importsAttr = attr.getValue();
}
}, this);
if (parentNode) {
parentNode.appendChild(elementNode);
}
else {
rootNode = elementNode;
if (importsAttr) {
imports = taglibs.getImports(importsAttr);
}
}
forEach(el.getAttributes(), function(attr) {
var attrURI = taglibs.resolveURI(attr.getNamespaceURI()),
attrLocalName = attr.getLocalName(),
attrPrefix = attr.getPrefix();
if (!attrURI && imports && (importedAttr = imports.getImportedAttribute(attrLocalName))) {
attrURI = importedAttr.uri;
attrLocalName = importedAttr.name;
attrPrefix = importedAttr.prefix;
}
elementNode.setAttributeNS(
attrURI,
attrLocalName,
attr.getValue(),
attrPrefix);
}, this);
if (!elementNode.uri && imports && (importedTag = imports.getImportedTag(elementNode.localName))) {
elementNode.uri = importedTag.uri;
elementNode.localName = importedTag.name;
elementNode.prefix = importedTag.prefix;
}
parentNode = elementNode;
},
endElement: function () {
prevTextNode = null;
parentNode = parentNode.parentNode;
}
}, this);
parser.parse(src, filePath);
rootNode.setRoot(true);
return rootNode;
},
getRootNode: function() {
return this.rootNode;
}
};
return ParseTreeBuilder;
});

389
lib/compiler/Taglib.js Normal file
View File

@ -0,0 +1,389 @@
/*
* 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/Taglib',
['raptor'],
function(raptor, require) {
"use strict";
var Taglib = function() {
this.uri = null;
this.shortName = null;
this.aliases = [];
this.tags = {};
this.textTransformers = [];
this.attributeMap = {};
this.functions = [];
this.helperObject = null;
this.patternAttributes = [];
};
Taglib.prototype = {
addAttribute: function(attribute) {
if (attribute.uri) {
throw raptor.createError(new Error('"uri" is not allowed for taglib attributes'));
}
if (attribute.name) {
this.attributeMap[attribute.name] = attribute;
}
else {
this.patternAttributes.push(attribute);
}
},
getAttribute: function(name) {
var attribute = this.attributeMap[name];
if (!attribute) {
for (var i=0, len=this.patternAttributes.length; i<len; i++) {
var patternAttribute = this.patternAttributes[i];
if (patternAttribute.pattern.test(name)) {
attribute = patternAttribute;
}
}
}
return attribute;
},
addTag: function(tag) {
var key = (tag.uri || '') + ":" + tag.name;
tag._taglib = this;
this.tags[key] = tag;
},
addTextTransformer: function(transformer) {
this.textTransformers.push(transformer);
},
forEachTag: function(callback, thisObj) {
raptor.forEachEntry(this.tags, function(key, tag) {
callback.call(thisObj, tag);
}, this);
},
addFunction: function(func) {
this.functions.push(func);
},
setHelperObject: function(helperObject) {
this.helperObject = helperObject;
},
getHelperObject: function() {
return this.helperObject;
},
addAlias: function(alias) {
this.aliases.push(alias);
}
};
Taglib.Tag = define.Class(function() {
var Tag = function() {
// this.handlerClass = null;
// this.nodeClass = null;
// this.renderer = null;
// this.template = null;
this.attributeMap = {};
this.transformers = {};
this.nestedVariables = {};
this.importedVariables = {};
this.nestedTags = {};
this.staticProperties = {};
this.patternAttributes = [];
this._taglib = null;
};
Tag.prototype = {
inheritFrom: function(superTag) {
var subTag = this;
/*
* Have the sub tag inherit any properties from the super tag that are not in the sub tag
*/
raptor.forEachEntry(superTag, function(k, v) {
if (subTag[k] === undefined) {
subTag[k] = v;
}
});
function inheritProps(sub, sup) {
raptor.forEachEntry(sup, function(k, v) {
if (!sub[k]) {
sub[k] = v;
}
});
}
['attributeMap',
'transformers',
'nestedVariables',
'importedVariables',
'nestedTags',
'staticProperties'].forEach(function(propName) {
inheritProps(subTag[propName], superTag[propName]);
});
subTag.patternAttributes = superTag.patternAttributes.concat(subTag.patternAttributes);
},
addNestedTag: function(nestedTag) {
var uri = nestedTag.uri || '';
this.nestedTags[uri + ":" + nestedTag.name] = nestedTag;
},
forEachNestedTag: function(callback, thisObj) {
raptor.forEachEntry(this.nestedTags, function(key, nestedTag) {
callback.call(thisObj, nestedTag);
});
},
forEachVariable: function(callback, thisObj) {
raptor.forEachEntry(this.nestedVariables, function(key, variable) {
callback.call(thisObj, variable);
});
},
forEachImportedVariable: function(callback, thisObj) {
raptor.forEachEntry(this.importedVariables, function(key, importedVariable) {
callback.call(thisObj, importedVariable);
});
},
forEachTransformer: function(callback, thisObj) {
raptor.forEachEntry(this.transformers, function(key, transformer) {
callback.call(thisObj, transformer);
});
},
forEachStaticProperty: function(callback, thisObj) {
raptor.forEachEntry(this.staticProperties, function(key, staticProp) {
callback.call(thisObj, staticProp);
});
},
hasTransformers: function() {
for (var k in this.transformers) {
return true;
}
return false;
},
addAttribute: function(attr) {
if (attr.pattern) {
this.patternAttributes.push(attr);
}
else {
var uri = attr.uri || '';
this.attributeMap[uri + ':' + attr.name] = attr;
}
},
getAttribute: function(uri, localName) {
if (uri == null) {
uri = '';
}
var attr = this.attributeMap[uri + ':' + localName] || this.attributeMap[uri + ':*'] || this.attributeMap['*:' + localName] || this.attributeMap['*:*'];
if (!attr && this.patternAttributes.length) {
for (var i=0, len=this.patternAttributes.length; i<len; i++) {
var patternAttribute = this.patternAttributes[i];
if (patternAttribute.uri !== uri) {
continue;
}
if (patternAttribute.pattern.test(localName)) {
attr = patternAttribute;
}
}
}
return attr;
},
getTaglibUri: function() {
if (!this._taglib) {
throw raptor.createError(new Error('Taglib not set for tag. (uri=' + this.uri + ', name=' + this.name + ')'));
}
return this._taglib.uri;
},
toString: function() {
return "[Tag: <" + this.uri + ":" + this.name + ">]";
},
forEachAttribute: function(callback, thisObj) {
raptor.forEachEntry(this.attributeMap, function(attrName, attr) {
callback.call(thisObj, attr);
});
},
addNestedVariable: function(nestedVariable) {
var key = nestedVariable.nameFromAttribute ? 'attr:' + nestedVariable.nameFromAttribute : nestedVariable.name;
this.nestedVariables[key] = nestedVariable;
},
addImportedVariable: function(importedVariable) {
var key = importedVariable.targetProperty;
this.importedVariables[key] = importedVariable;
},
addTransformer: function(transformer) {
var key = transformer.className;
this.transformers[key] = transformer;
},
addStaticProperty: function(prop) {
var key = prop.name;
this.staticProperties[key] = prop;
}
};
return Tag;
});
Taglib.Attribute = define.Class(function() {
var Attribute = function() {
this.name = null;
this.uri = null;
this.type = null;
this.required = false;
this.type = "string";
this.allowExpressions = true;
};
Attribute.prototype = {
};
return Attribute;
});
Taglib.Property = define.Class(function() {
var Property = function() {
this.name = null;
this.type = "string";
this.value = undefined;
};
Property.prototype = {
};
return Property;
});
Taglib.NestedVariable = define.Class(function() {
var NestedVariable = function() {
this.name = null;
};
NestedVariable.prototype = {
};
return NestedVariable;
});
Taglib.ImportedVariable = define.Class(function() {
var ImportedVariable = function() {
this.targetProperty = null;
this.expression = null;
};
ImportedVariable.prototype = {
};
return ImportedVariable;
});
Taglib.Transformer = define.Class(function() {
var uniqueId = 0;
var Transformer = function() {
this.id = uniqueId++;
this.tag = null;
this.className = null;
this.after = null;
this.before = null;
this.instance = null;
this.properties = {};
};
Transformer.prototype = {
getInstance: function() {
if (!this.className) {
throw raptor.createError(new Error("Transformer class not defined for tag transformer (tag=" + this.tag + ")"));
}
if (!this.instance) {
var Clazz = require(this.className);
if (Clazz.process) {
return Clazz;
}
this.instance = new Clazz();
this.instance.id = this.id;
}
return this.instance;
},
toString: function() {
return '[Taglib.Transformer: ' + this.className + ']';
}
};
return Transformer;
});
Taglib.Function = define.Class(function() {
var Function = function() {
this.name = null;
this.functionClass = null;
this.bindToContext = false;
};
Function.prototype = {
};
return Function;
});
Taglib.HelperObject = define.Class(function() {
var HelperObject = function() {
this.className = null;
this.moduleName = null;
};
HelperObject.prototype = {
};
return HelperObject;
});
return Taglib;
});

View File

@ -0,0 +1,548 @@
/*
* 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.
*/
/**
* Merges a set of taglibs for ea
*/
define.Class(
'raptor/templating/compiler/TaglibCollection',
['raptor'],
function(raptor, require, exports, module) {
"use strict";
var forEach = raptor.forEach,
strings = require('raptor/strings'),
ElementNode = require('raptor/templating/compiler/ElementNode'),
TextNode = require('raptor/templating/compiler/TextNode'),
/*
* Probably one of the more amazing regular expressions you will ever see...
*
* Valid imports:
* x, y, z from http://raptorjs.org/templates/core
* x, y, z from core
* x, y, z from core as my-core
* * from core as c
* core
* core as my-core
*/
importRegExp = /^(?:(\*|(?:(?:@?[A-Za-z0-9_\-]+\s*,\s*)*@?[A-Za-z0-9_\-]+))\s+from\s+)?([^ ]*)(?:\s+as\s+([A-Za-z0-9_\-]+))?$/,
getImported = function(lookup, localName, imports) {
if (lookup[localName] != null) {
return lookup[localName];
}
var prefixEnd = localName.indexOf('-'),
prefix,
uri,
name;
if (prefixEnd != -1) {
prefix = localName.substring(0, prefixEnd);
name = localName.substring(prefixEnd+1);
uri = imports._prefixes[prefix];
if (uri) {
return {
uri: uri,
name: name,
prefix: prefix
};
}
}
return null;
};
var Imports = function(taglibs, importsStr) {
this._tagImports = {};
this._attrImports = {};
this._prefixes = {};
var parts = strings.trim(importsStr).split(/\s*;\s*/);
forEach(parts, function(part) {
if (!part) { //Skip empty strings
return;
}
var match = part.match(importRegExp),
imports,
importsLookup = {},
from,
as;
if (!match) {
throw raptor.createError(new Error('Invalid import: "' + part + '"'));
}
else {
imports = match[1];
from = taglibs.resolveURI(match[2]);
as = match[3];
if (!as) {
as = taglibs.resolvePrefix(from) || taglibs.resolveShortName(from); //Use either the prefix (preferred) or the short name if provided
if (!as) {
throw raptor.createError(new Error('Unable to handle imports from "' + from + '". The taglib does not have a prefix or short name defined.'));
}
}
}
this._prefixes[as] = from;
if (imports) {
forEach(imports.split(/\s*,\s*/), function(importedTagName) {
importsLookup[importedTagName] = true;
});
}
taglibs.forEachTag(from, function(tag, taglib) {
if (tag.uri === from && (importsLookup['*'] || importsLookup[tag.name])) {
/*
* Import tags with a URI that matches the taglib URI
*/
this._tagImports[tag.name] = { uri: from, name: tag.name, prefix: as };
}
/*
* Allow imports for attributes that can be assigned to tags with a different URI
* e.g. <div c-if="someCondition"></div> --> <div c:if="someCondition"></div>
*/
tag.forEachAttribute(function(attr) {
if (tag.uri !== from && (importsLookup['*'] || importsLookup["@" + attr.name])) {
this._attrImports[attr.name] = { uri: from, name: attr.name, prefix: as };
}
}, this);
}, this);
}, this);
};
Imports.prototype = {
getImportedTag: function(localName) {
return getImported(this._tagImports, localName, this);
},
getImportedAttribute: function(localName) {
return getImported(this._attrImports, localName, this);
}
};
var TaglibCollection = function() {
this.tagTransformersLookup = {}; //Tag transformers lookup
this.tags = {}; //Tag definitions lookup
this.textTransformers = {};
this.taglibsByURI = {}; //Lookup to track the URIs of taglibs that have been added to this collection
this.uriToShortNameMapping = {};
this.aliases = {};
this.uriToPrefixMapping = {};
this.functionsLookup = {};
this.attributeLookup = {};
this.nestedTags = {};
};
TaglibCollection.prototype = {
getAttribute: function(tagUri, tagName, attrUri, attrName) {
var tags = this.tags;
tagUri = this.resolveURI(tagUri);
var attributeLookup = this.attributeLookup;
if (attrUri == null) {
attrUri = '';
}
var _findAttrForTag = function(tagLookupKey) {
var attr = attributeLookup[tagLookupKey + ":" + attrUri + ":" + attrName] ||
attributeLookup[tagLookupKey + ":" + attrUri + ":*"] ||
attributeLookup[tagLookupKey + ":*:" + attrName] ||
attributeLookup[tagLookupKey + ":*:*"];
if (!attr) {
var tag = tags[tagLookupKey];
if (tag) {
attr = tag.getAttribute(attrUri, attrName);
}
}
return attr;
};
var attr = _findAttrForTag(tagUri + ":" + tagName) ||
_findAttrForTag(tagUri + ":*") ||
_findAttrForTag("*:*");
if (attr && attr.uri && attr.uri !== '*') {
//The attribute is being imported
var taglib = this.taglibsByURI[attr.uri];
if (!taglib) {
throw raptor.createError(new Error('Taglib with URI "' + attr.uri + '" not found for imported attribute with name "' + attrName + '"'));
}
attr = taglib.getAttribute(attrName);
if (!attr) {
throw raptor.createError(new Error('Attribute "' + attrName + '" imported from taglib with URI "' + attr.uri + '" not found in taglib.'));
}
}
if (!attr) {
var attrShortUri = this.resolveShortName(attrUri);
if (attrShortUri !== attrUri) {
return this.getAttribute(tagUri, tagName, attrShortUri, attrName);
}
}
return attr;
},
forEachTag: function(uri, callback, thisObj) {
uri = this.resolveURI(uri);
var taglib = this.taglibsByURI[uri];
if (!taglib) {
return;
}
raptor.forEachEntry(taglib.tags, function(key, tag) {
callback.call(thisObj, tag, taglib);
});
},
/**
* Checks if the provided URI is the URI of a taglib
*
* @param uri {String} The URI to check
* @returns {Boolean} Returns true if the URI is that of a taglib. False, otherwise.
*/
isTaglib: function(uri) {
return this.taglibsByURI[this.resolveURI(uri)] != null;
},
/**
* Adds a new taglib to the collection
*
* @param taglib {raptor/templating/compiler/Taglib} The taglib to add
*/
add: function(taglib) {
var targetTaglib = this.taglibsByURI[taglib.uri] || taglib;
this.taglibsByURI[taglib.uri] = targetTaglib; //Mark the taglib as added
if (taglib.shortName && taglib.shortName != taglib.uri) {
/*
* If the taglib has a short name then record that mapping so that we
* can map the short name to the full URI
*/
this.taglibsByURI[taglib.shortName] = taglib; //Mark the short name as being a taglib
if (taglib.shortName) {
targetTaglib.shortName = taglib.shortName;
this.addAlias(taglib.uri, taglib.shortName);
this.uriToShortNameMapping[taglib.uri] = taglib.shortName; //Add the reverse-mapping
}
if (taglib.prefix) {
targetTaglib.prefix = taglib.prefix;
this.uriToPrefixMapping[taglib.uri] = taglib.prefix;
}
}
taglib.aliases.forEach(function(alias) {
this.addAlias(taglib.uri, alias);
}, this);
/*
* Index all of the tags in the taglib by registering them
* based on the tag URI and the tag name
*/
taglib.forEachTag(function(tag, i) {
var uri = tag.uri == null ? (tag.uri = taglib.uri) : tag.uri, //If not specified, the tag URI should be the same as the taglib URI
name = tag.name,
key = uri + ":" + name; //The taglib will be registered using the combination of URI and tag name
this.tags[key] = tag; //Register the tag using the combination of URI and tag name so that it can easily be looked up
if (tag.hasTransformers()) { //Check if the tag has any transformers that should be applied
var tagTransformersForTags = this.tagTransformersLookup[key] || (this.tagTransformersLookup[key] = []); //A reference to the array of the tag transformers with the same key
//Now add all of the transformers for the node (there will typically only be one...)
tag.forEachTransformer(function(transformer) {
if (!transformer) {
throw raptor.createError(new Error("Transformer is null"));
}
tagTransformersForTags.push(transformer);
}, this);
}
tag.forEachNestedTag(function(nestedTag) {
this.nestedTags[tag.uri + ":" + tag.name + ":" + nestedTag.uri + ":" + nestedTag.name] = nestedTag;
}, this);
tag.forEachAttribute(function(attr) {
this.attributeLookup[tag.uri + ":" + tag.name + ":" + (attr.uri || '') + ":" + attr.name] = attr;
}, this);
}, this);
/*
* Now register all of the text transformers that are part of the provided taglibs
*/
forEach(taglib.textTransformers, function(textTransformer) {
this.textTransformers[textTransformer.className] = textTransformer;
}, this);
forEach(taglib.functions, function(func) {
if (!func.name) {
throw raptor.createError(new Error("Function name not set."));
}
this.functionsLookup[taglib.uri + ":" + func.name] = func;
if (targetTaglib.shortName) {
this.functionsLookup[taglib.shortName + ":" + func.name] = func;
}
}, this);
},
addAlias: function(uri, alias) {
this.aliases[alias] = uri;
},
/**
* Invokes a callback for eaching matching transformer that is found for the current node.
* If the provided node is an element node then the match is based on the node's
* URI and the local name. If the provided node is a text node then all
* text transformers will match.
*
* @param node {raptor/templating/compiler$Node} The node to match transformers to
* @param callback {Function} The callback function to invoke for each matching transformer
* @param thisObj {Object} The "this" object to use when invoking the callback function
*/
forEachNodeTransformer: function(node, callback, thisObj) {
/*
* Based on the type of node we have to choose how to transform it
*/
if (node instanceof ElementNode) {
this.forEachTagTransformer(node.uri, node.localName, callback, thisObj);
}
else if (node instanceof TextNode) {
this.forEachTextTransformer(callback, thisObj);
}
},
/**
* Resolves a taglib short name to a taglib URI.
*
* <p>
* If the provided short name is not a known short name then it is just returned.
*
* @param shortName {String} The taglib short name to resolve
* @returns {String} The resolved URI or the input string if it is not a known short name
*/
resolveURI: function(inputURI) {
if (!inputURI) {
return inputURI;
}
var uri;
while(true) {
uri = this.aliases[inputURI] || inputURI;
if (uri === inputURI) {
return uri;
}
inputURI = uri;
}
},
/**
* Resolves a taglib URI to a taglib short name.
*
* <p>
* If the provided URI is not a known short name then it is just returned.
*
* @param uri {String} The taglib uri to resolve to a short name
* @returns {String} The resolved short name or undefined if the taglib does not have a short name
*/
resolveShortName: function(uri) {
if (!uri) {
return uri;
}
uri = this.resolveURI(uri);
return this.uriToShortNameMapping[uri]; //Otherwise lookup the short name for the long URI
},
resolvePrefix: function(uri) {
if (!uri) {
return uri;
}
uri = this.resolveURI(uri); //Resolve the short name to a long URI
return this.uriToPrefixMapping[uri]; //See if there is a mapping from the long URI to a prefix
},
/**
* Invokes a provided callback for each tag transformer that
* matches the provided URI and tag name.
*
* @param uri {String} The tag URI or an empty string if the tag is not namespaced
* @param tagName {String} The local name of the tag (e.g. "div")
* @param callback {Function} The callback function to invoke
* @param thisObj {Object} The "this" object to use when invoking the callback function
*/
forEachTagTransformer: function(uri, tagName, callback, thisObj) {
/*
* 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.
*/
if (uri == null) {
uri = '';
}
var matchingTransformersByName = {};
var matchingTransformers = [];
var handled = {};
var before = {};
/*
* Handle all of the transformers in the tag transformers entry
*/
var _addTransformers = function(transformers) {
raptor.forEach(transformers, function(transformer) {
if (!transformer) {
throw raptor.createError(new Error("Invalid transformer"));
}
if (!matchingTransformersByName[transformer.className]) {
matchingTransformersByName[transformer.className] = transformer;
matchingTransformers.push(transformer);
if (transformer.before) {
(before[transformer.before] || (before[transformer.before] = [])).push(transformer);
}
}
});
};
/*
* Handle all of the transformers for all possible matching transformers.
*
* Start with the least specific and end with the most specific.
*/
_addTransformers(this.tagTransformersLookup["*:*"]); //Wildcard for both URI and tag name (i.e. transformers that apply to every element)
_addTransformers(this.tagTransformersLookup[uri + ":*"]); //Wildcard for tag name but matching URI (i.e. transformers that apply to every element with a URI, regadless of tag name)
_addTransformers(this.tagTransformersLookup[uri + ":" + tagName]); //All transformers that match the URI and tag name exactly
var _handleTransformer = function(transformer) {
if (!handled[transformer.className]) {
handled[transformer.className] = true;
if (transformer.after) { //Check if this transformer is required to run
if (!matchingTransformersByName[transformer.after]) {
throw raptor.createError(new Error('After transformers not found for "' + transformer.after + '"'));
}
_handleTransformer(matchingTransformersByName[transformer.after]); //Handle any transformers that this transformer is supposed to run after
}
raptor.forEach(before[transformer.className], _handleTransformer); //Handle any transformers that are configured to run before this transformer
callback.call(thisObj, transformer);
}
};
matchingTransformers.forEach(function(transformer) {
_handleTransformer(transformer);
}, this);
},
/**
* Invokes a provided callback for each registered text transformer.
*
* @param callback {Function} The callback function to invoke
* @param thisObj {Object} The "this" object to use when invoking the callback function
*/
forEachTextTransformer: function(callback, thisObj) {
raptor.forEachEntry(this.textTransformers, function(className, textTransformer) {
var keepGoing = callback.call(thisObj, textTransformer);
if (keepGoing === false) {
return false;
}
return true;
});
},
/**
* Returns the definition of a tag that was loaded from the taglib with the specified
* URI and with the matching
* @param uri
* @param localName
* @returns
*/
getTag: function(uri, localName) {
uri = this.resolveURI(uri);
var tag = this.tags[uri + ":" + localName];
if (!tag) {
tag = this.tags[uri + ":*"]; //See if there was a wildcard tag definition in the taglib
}
return tag;
},
getNestedTag: function(parentTagUri, parentTagName, nestedTagUri, nestedTagName) {
parentTagUri = parentTagUri || '';
nestedTagUri = nestedTagUri || '';
return this.nestedTags[parentTagUri + ":" + parentTagName + ":" + nestedTagUri + ":" + nestedTagName];
},
getFunction: function(uri, functionName) {
return this.functionsLookup[uri + ":" + functionName];
},
getHelperObject: function(uri) {
uri = this.resolveURI(uri);
var taglib = this.taglibsByURI[uri];
if (!taglib) {
throw new Error("Invalid taglib URI: " + uri);
}
return taglib.getHelperObject();
},
getImports: function(importsStr) {
return new Imports(this, importsStr);
}
};
return TaglibCollection;
});

View File

@ -0,0 +1,581 @@
/*
* 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/TaglibXmlLoader",
['raptor'],
function(raptor, require, exports, module) {
"use strict";
var objectMapper = require('raptor/xml/sax/object-mapper'),
logger = module.logger(),
regexp = require('raptor/regexp'),
Taglib = require('raptor/templating/compiler/Taglib'),
Tag = Taglib.Tag,
Attribute = Taglib.Attribute,
Property = Taglib.Property,
NestedVariable = Taglib.NestedVariable,
ImportedVariable = Taglib.ImportedVariable,
Transformer = Taglib.Transformer,
Function = Taglib.Function,
HelperObject = Taglib.HelperObject,
STRING = "string",
BOOLEAN = "boolean",
OBJECT = "object";
var TaglibXmlLoader = function(src, resource) {
this.src = src;
this.filePath = typeof resource === 'string' ? resource : resource.getURL();
this.resource = resource;
};
TaglibXmlLoader.load = function(src, resource) {
var loader = new TaglibXmlLoader(src, resource);
return loader.load();
};
TaglibXmlLoader.prototype = {
load: function() {
var src = this.src,
filePath = this.filePath,
taglibResource = this.resource,
tagsById = {},
handleTagExtends = function(subTag) {
var extendsId = subTag['extends'];
if (!extendsId) {
return;
}
delete subTag['extends'];
var superTag = tagsById[extendsId];
if (!superTag) {
throw raptor.createError(new Error('Parent tag with ID "' + extendsId + '" not found in taglib at path "' + filePath + '"'));
}
if (superTag['extends']) {
handleTagExtends(superTag);
}
subTag.inheritFrom(superTag);
};
var taglib;
var attributeHandler = {
_type: OBJECT,
_begin: function() {
return new Attribute();
},
_end: function(attr, parent) {
if (attr.uri == null) {
attr.uri = '';
}
parent.addAttribute(attr);
},
"name": {
_type: STRING
},
"pattern": {
_type: STRING,
_set: function(parent, name, value) {
var patternRegExp = regexp.simple(value);
parent.pattern = patternRegExp;
}
},
"target-property": {
_type: STRING,
_targetProp: "targetProperty"
},
"uri": {
_type: STRING
},
"deprecated": {
_type: STRING
},
"required": {
_type: BOOLEAN
},
"type": {
_type: STRING
},
"allow-expressions": {
_type: BOOLEAN,
_targetProp: "allowExpressions"
},
"preserve-name": {
_type: BOOLEAN,
_targetProp: "preserveName"
},
"description": {
_type: STRING
}
};
var importVariableHandler = {
_type: OBJECT,
_begin: function() {
return new ImportedVariable();
},
_end: function(importedVariable, tag) {
if (importedVariable.name) {
if (!importedVariable.targetProperty) {
importedVariable.targetProperty = importedVariable.name;
}
importedVariable.expression = importedVariable.name;
delete importedVariable.name;
}
if (!importedVariable.targetProperty) {
throw raptor.createError(new Error('The "target-property" attribute is required for an imported variable'));
}
if (!importedVariable.expression) {
throw raptor.createError(new Error('The "expression" attribute is required for an imported variable'));
}
tag.addImportedVariable(importedVariable);
},
"name": { // Short-hand for target-property="<name>" and expression="<name>"
_type: STRING
},
"target-property": {
_type: STRING,
_targetProp: "targetProperty"
},
"expression": {
_type: STRING
}
};
var variableHandler = {
_type: OBJECT,
_begin: function() {
return new NestedVariable();
},
_end: function(nestedVariable, tag) {
if (!nestedVariable.name && !nestedVariable.nameFromAttribute) {
throw raptor.createError(new Error('The "name" or "name-from-attribute" attribute is required for a nested variable'));
}
tag.addNestedVariable(nestedVariable);
},
"name": {
_type: STRING,
_targetProp: "name"
},
"name-from-attribute": {
_type: STRING,
_targetProp: "nameFromAttribute"
},
"name-from-attr": {
_type: STRING,
_targetProp: "nameFromAttribute"
}
};
var handlers = {
"raptor-taglib": {
_type: OBJECT,
_begin: function() {
var newTaglib = new Taglib();
if (!taglib) {
taglib = newTaglib;
}
return newTaglib;
},
"attribute": attributeHandler,
"tlib-version": {
_type: STRING,
_targetProp: "version"
},
"short-name": {
_type: STRING,
_targetProp: "shortName"
},
"uri": {
_type: STRING,
_set: function(taglib, name, value, context) {
if (!taglib.uri) {
taglib.uri = value;
}
else {
taglib.addAlias(value);
}
}
},
"prefix": {
_type: STRING
},
"tag": {
_type: OBJECT,
_begin: function() {
return new Tag();
},
_end: function(tag) {
if (tag.uri === undefined) {
tag.uri = taglib.uri;
}
taglib.addTag(tag);
if (tag.id) {
tagsById[tag.id] = tag;
}
},
"name": {
_type: STRING,
_targetProp: "name"
},
"uri": {
_type: STRING,
_set: function(tag, name, value, context) {
tag.uri = value || '';
}
},
"id": {
_type: STRING
},
"preserveSpace": {
_type: BOOLEAN,
_targetProp: "preserveWhitespace"
},
"preserve-space": {
_type: BOOLEAN,
_targetProp: "preserveWhitespace"
},
"preserve-whitespace": {
_type: BOOLEAN,
_targetProp: "preserveWhitespace"
},
"preserveWhitespace": {
_type: BOOLEAN,
_targetProp: "preserveWhitespace"
},
"extends": {
_type: STRING,
_targetProp: "extends"
},
"handler-class": {
_type: STRING,
_targetProp: "handlerClass"
},
"renderer": {
_type: STRING,
_targetProp: "handlerClass"
},
"template": {
_type: STRING,
_targetProp: "template"
},
"node-class": {
_type: STRING,
_targetProp: "nodeClass"
},
"dynamic-attributes": {
_type: BOOLEAN,
_targetProp: "dynamicAttributes"
},
"dynamic-attributes-remove-dashes": {
_type: BOOLEAN,
_targetProp: "dynamicAttributesRemoveDashes"
},
"<attribute>": attributeHandler,
"<static-property>": {
_type: OBJECT,
_begin: function() {
return new Property();
},
_end: function(prop, tag) {
if (!prop.name) {
throw raptor.createError(new Error('The "name" property is required for a <static-property>'));
}
if (!prop.hasOwnProperty('value')) {
throw raptor.createError(new Error('The "value" property is required for a <static-property>'));
}
tag.addStaticProperty(prop);
},
"name": {
_type: STRING
},
"type": {
_type: STRING
},
"value": {
_type: STRING
}
},
"<nested-tag>": {
_type: OBJECT,
_begin: function() {
return new Tag();
},
_end: function(nestedTag, tag) {
if (nestedTag.uri === null || nestedTag.uri === undefined) {
nestedTag.uri = taglib.uri;
}
nestedTag.targetProperty = nestedTag.targetProperty || nestedTag.name;
if (!nestedTag.name) {
throw raptor.createError(new Error('The "name" property is required for a <nested-tag>'));
}
tag.addNestedTag(nestedTag);
},
"name": {
_type: STRING
},
"type": {
_type: STRING
},
"target-property": {
_type: STRING,
_targetProp: "targetProperty"
}
},
"nested-variable": variableHandler, // Issue #28: "nested-variable" deprecated. "variable" should be used instead.
"variable": variableHandler,
"imported-variable": importVariableHandler, // Issue #28: "imported-variable" deprecated. "import-variable" should be used instead.
"import-variable": importVariableHandler,
"transformer-class": {
_type: STRING,
_set: function(tag, name, value) {
var transformer = new Transformer();
transformer.className = value;
tag.addTransformer(transformer);
}
},
"transformer": {
_type: OBJECT,
_begin: function() {
return new Transformer();
},
_end: function(transformer, tag) {
tag.addTransformer(transformer);
},
"class-name": {
_type: STRING,
_targetProp: "className"
},
"after": {
_type: STRING,
_targetProp: "after"
},
"before": {
_type: STRING,
_targetProp: "before"
},
"<properties>": {
_type: OBJECT,
_begin: function(parent) {
return (parent.properties = {});
},
"<*>": {
_type: STRING
}
}
}
},
//end "tag"
"text-transformer": {
_type: OBJECT,
_begin: function() {
return new Transformer();
},
_end: function(textTransformer) {
taglib.addTextTransformer(textTransformer);
},
"class-name": {
_type: STRING,
_targetProp: "className"
}
},
"import-taglib": {
_type: OBJECT,
_begin: function() {
return {};
},
_end: function(importedTaglib) {
var path = importedTaglib.path,
importedXmlSource,
importedTaglibResource;
if (path.startsWith('/')) {
importedTaglibResource = require('raptor/resources').findResource(path);
}
else {
importedTaglibResource = taglibResource.resolve(path);
}
if (!importedTaglibResource.exists()) {
throw raptor.createError(new Error('Imported taglib with path "' + path + '" not found in taglib at path "' + filePath + '"'));
}
require('raptor/templating/compiler').recordLoadedTaglib(importedTaglibResource);
importedXmlSource = importedTaglibResource.readAsString();
objectMapper.read(
importedXmlSource,
importedTaglibResource.getURL(),
handlers);
},
"path": {
_type: STRING
}
},
"function": {
_type: OBJECT,
_begin: function() {
return new Function();
},
_end: function(func) {
taglib.addFunction(func);
},
"name": {
_type: STRING
},
"class": {
_type: STRING,
_targetProp: "functionClass"
},
"bind-to-context": {
_type: BOOLEAN,
_targetProp: "bindToContext"
}
},
"helper-object": {
_type: OBJECT,
_begin: function() {
return new HelperObject();
},
_end: function(helperObject) {
taglib.setHelperObject(helperObject);
},
"module-name": {
_type: STRING,
_targetProp: "moduleName"
},
"class-name": {
_type: STRING,
_targetProp: "className"
}
}
}
};
objectMapper.read(
src,
filePath,
handlers);
taglib.forEachTag(function(tag) {
handleTagExtends(tag);
});
return taglib;
}
};
return TaglibXmlLoader;
});

View File

@ -0,0 +1,620 @@
/*
* 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;
});

View File

@ -0,0 +1,299 @@
/*
* 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/TemplateCompiler',
['raptor'],
function(raptor, require, exports, module) {
"use strict";
var TemplateBuilder = require('raptor/templating/compiler/TemplateBuilder'),
ParseTreeBuilder = require('raptor/templating/compiler/ParseTreeBuilder'),
Expression = require('raptor/templating/compiler/Expression'),
minifier = require.exists("raptor/js-minifier") ? require("raptor/js-minifier") : null,
TypeConverter = require('raptor/templating/compiler/TypeConverter'),
logger = module.logger();
/**
* @param taglibs {raptor/templating/compiler/TaglibCollection} The collection of taglibs that are available to the compiler
* @param options {object} The options for the compiler.
*/
var TemplateCompiler = function(taglibs, options) {
this.taglibs = taglibs;
this.options = options || {};
this.workDir = this.options.workDir || require('raptor/templating/compiler').workDir || require('raptor/temp').workDir;
this.errors = [];
};
TemplateCompiler.prototype = {
/**
* This method processes every node in the tree using a pre-order traversal.
* That is, the parent node is transformed before its child nodes are
* transformed.
*
* <p>
* NOTE:
* This method is repeatedly called until there are no more nodes in the tree
* that need to be transformed. This is because transformers might add
* new nodes to the tree in a position that has already been passed and
* we want to make sure that all new nodes added to the tree are transformed
* as necessary.
*
* @param node {raptor/templating/compiler/Node} The root node to transform
* @param templateBuilder {raptor/templating/compiler/TemplateBuilder} The template builder object that is used to control how the compiled code is generated
*/
transformTree: function(rootNode, templateBuilder) {
if (!templateBuilder) {
throw raptor.createError(new Error("The templateBuilder argument is required"));
}
var transformTreeHelper = function(node) {
try
{
this.taglibs.forEachNodeTransformer( //Handle all of the transformers that are appropriate for this node
node, //The node being transformed
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;
transformer.getInstance().process(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)
}
},
this);
}
catch(e) {
throw raptor.createError(new Error('Unable to compile template at path "' + templateBuilder.filePath + ". 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.call(this, childNode);
}, this);
};
/*
* 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.call(this, rootNode); //Run the transforms on the tree
}
while (this._transformerApplied);
},
/**
* Compiles the XML source code for a template and returns the resulting compiled JavaScript code.
*
* <p>
* When the returned code is evaluated by a JavaScript engine it will register the function
* to render the template. The function is registered with the name found as the "name" attribute
* of the root &ltc:template> element unless a template name is passed in as a compiler option.
*
*
* @param xmlSrc {String} The XML source code for the template
* @param filePath {String} The path to the input template for debugging/error reporting only
* @returns {String} The JavaScript code for the compiled template
*/
compile: function(xmlSrc, resource, callback, thisObj) {
var _this = this,
rootNode,
templateBuilder,
handleErrors = function() {
var message = "Errors in template:\n",
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();
throw error;
};
var filePath;
if (require('raptor/resources').isResource(resource)) {
filePath = resource.getURL();
}
else if (typeof resource === 'string'){
filePath = resource;
}
try
{
/*
* First build the parse tree for the tempate
*/
rootNode = ParseTreeBuilder.parse(xmlSrc, filePath, this.taglibs); //Build a parse tree from the input XML
templateBuilder = new TemplateBuilder(this, resource, rootNode); //The templateBuilder object is need to manage the compiled JavaScript output
this.transformTree(rootNode, templateBuilder);
}
catch(e) {
throw raptor.createError(new Error('An error occurred while trying to compile template at path "' + filePath + '". Exception: ' + e), e);
}
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) {
throw raptor.createError(new Error('An error occurred while trying to compile template at path "' + filePath + '". Exception: ' + e), e);
}
if (this.hasErrors()) {
handleErrors();
}
var output = templateBuilder.getOutput(); //Get the compiled output from the template builder
//console.error('COMPILED TEMPLATE (' + filePath + ')\n', '\n' + output, '\n------------------');
if (minifier && this.options.minify === true) {
output = minifier.minify(output);
}
if (callback) {
callback.call(thisObj, {
source: output,
templateName: templateBuilder.getTemplateName()
});
}
var options = this.options;
if (options && options.nameCallback) {
options.nameCallback(templateBuilder.getTemplateName());
}
return output;
},
/**
*
* @param xmlSrc {String} The XML source code for the template
* @param filePath {String} The path to the input template for debugging/error reporting only
* @return {void}
*/
compileAndLoad: function(xmlSrc, resource) {
var compiledSrc = this.compile(xmlSrc, resource, function(result) { //Get the compiled output for the template
require('raptor/templating').unload(result.templateName); //Unload any existing template with the same name
});
try
{
//console.log('compileAndLoad: ' + (resource ? resource.getURL() : '(no resource') + ': ' + compiledSrc);
this._eval(compiledSrc, resource); //Evaluate the compiled code and register the template
}
catch(e) {
var filePath;
if (typeof resource === 'string') {
filePath = resource;
}
else if (require('raptor/resources').isResource(resource)) {
filePath = resource.getURL();
}
logger.error("Unable to load compiled template: " + compiledSrc, e);
throw raptor.createError(new Error('Unable to load template at path "' + filePath + '". Exception: ' + e.message), e);
}
},
_eval: function(compiledSrc, resource) {
eval(compiledSrc);
},
/**
* Returns true if the provided object is an Expression object, false otherwise
* @param expression {Object} The object to test
* @returns {Boolean} True if the provided object is an Expression object, false otherwise
*/
isExpression: function(expression) {
return expression instanceof Expression;
},
/**
*
* @param uri
* @param localName
* @returns {TagHandlerNode}
*/
createTagHandlerNode: function(uri, localName) {
var TagHandlerNode = require("raptor/templating/taglibs/core/TagHandlerNode");
var tag = this.taglibs.getTag(uri, localName);
var tagHandlerNode = new TagHandlerNode(tag);
return tagHandlerNode;
},
/**
*
* @param value
* @param type
* @param allowExpressions
* @returns
*/
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;
},
getNodeClass: function(uri, localName) {
var tag = this.taglibs.getTag(uri, localName);
if (tag && tag.nodeClass) {
return require(tag.nodeClass);
}
throw raptor.createError(new Error('Node class not found for uri "' + uri + '" and localName "' + localName + '"'));
},
createTag: function() {
var Taglib = require('raptor/templating/compiler/Taglib');
return new Taglib.Tag();
}
};
return TemplateCompiler;
});

View File

@ -0,0 +1,38 @@
var nodeRequire = require;
define.extend('raptor/templating/compiler/TemplateCompiler', function(require, target) {
"use strict";
var vm = require('vm'),
raptor = require('raptor');
return {
_eval: function(compiledSrc, resource) {
var filePath;
if (resource) {
if (typeof resource === 'string') {
filePath = resource;
}
else if (require('raptor/resources').isResource(resource)) {
filePath = resource.isFileResource() ? resource.getFilePath() : resource.getURL();
}
}
try
{
if (filePath && require('raptor/files').exists(filePath)) { //This is a hack, but line numbers for SyntaxErrors are getting lost if we don't use require
delete nodeRequire.cache[filePath];
nodeRequire(filePath);
}
else {
vm.runInThisContext(compiledSrc, filePath || null);
}
}
catch(e) {
throw raptor.createError(new Error('Unable to load compile templated at path "' + filePath + '". Exception: ' + e), e);
}
}
};
});

View File

@ -0,0 +1,19 @@
define.extend('raptor/templating/compiler/TemplateCompiler', function(require, target) {
"use strict";
return {
_eval: function(compiledSrc, resource) {
var filePath;
if (resource) {
if (typeof resource === 'string') {
filePath = resource;
}
else if (require('raptor/resources').isResource(resource)) {
filePath = resource.getURL();
}
}
__rhinoHelpers.runtime.evaluateString(compiledSrc, filePath || null);
}
};
});

View File

@ -0,0 +1,127 @@
define.extend('raptor/templating/compiler/TemplateCompiler', ['raptor'], function(raptor, require, target) {
"use strict";
var File = require('raptor/files/File'),
resources = require('raptor/resources'),
optionsKey = function(options) {
var key = JSON.stringify(options);
var crypto = require('crypto');
var shasum = crypto.createHash('sha1');
shasum.update(key);
var checksum = shasum.digest('hex');
if (checksum.length > 5) {
checksum = checksum.substring(0, 5);
}
return checksum;
},
logger = require('raptor/logging').logger('raptor/templating/compiler/TemplateCompiler_server');
return {
_getOptionsKey: function() {
if (!this._optionsKey) {
this._optionsKey = optionsKey(this.options);
}
return this._optionsKey;
},
_getWorkFile: function(resource) {
if (!this.workDir || !resource || !resource.isFileResource()) {
return null;
}
//var optionsKey = this._getOptionsKey();
var path = typeof resource === 'string' ? resource : resource.getPath();
var workFile = new File(this.workDir, path + '.js');
return workFile;
},
/**
* [_compileResource description]
* @param {[type]} resource [description]
* @return {[type]} [description]
* @private
*/
_compileResource: function(path) {
var resource = resources.findResource(path);
if (!resource.exists()) {
throw raptor.createError(new Error('Unable to compile template with resource path "' + path + '". Resource not found'));
}
var compiledSource,
outputPath,
workFile;
if (this.workDir) {
workFile = this._getWorkFile(resource);
}
if (!workFile || !workFile.exists() || resource.lastModified() > workFile.lastModified()) {
// The work file does not exist or it is out-of-date so we need to read the resource and compile
var xmlSource = resource.readAsString();
compiledSource = this.compile(xmlSource, resource);
if (workFile) { // If there is a work file then write out the compiled source so that we don't have to recompile again until the input resource is modified
workFile.writeAsString(compiledSource);
}
}
else {
// The work file exists and it is up-to-date so we can use that to return the compiled source
compiledSource = workFile.readAsString();
}
if (workFile) {
outputPath = workFile.getAbsolutePath();
}
else {
outputPath = resource.getURL() + ".js";
}
return {
templateResource: resource,
compiledSource: compiledSource,
outputPath: outputPath,
outputFile: workFile
};
},
/**
*
* @param path
* @returns
*/
compileResource: function(path) {
var compiledInfo = this._compileResource(path);
return compiledInfo.compiledSource;
},
/**
*
* @param path
* @returns
*/
compileAndLoadResource: function(path) {
var compiledInfo = this._compileResource(path);
this._eval(compiledInfo.compiledSource, compiledInfo.outputPath);
var resource = compiledInfo.templateResource;
if (require('raptor/templating/compiler').isWatchingEnabled()) {
resource.watch(
function() {
console.log('Template modified at path "' + resource.getURL() + '". Reloading template...');
try
{
this.compileAndLoadResource(resource);
}
catch(e) {
logger.warn('Unable to re-compile modified template at path "' + resource.getURL() + '". Exception: ' + e, e);
}
},
this);
}
}
};
});

215
lib/compiler/TextNode.js Normal file
View File

@ -0,0 +1,215 @@
/*
* 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/TextNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var escapeXml = require('raptor/xml/utils').escapeXml,
attrReplace = /[&<>\"\']/g,
replacements = {
'<': "&lt;",
'>': "&gt;",
'&': "&amp;",
'"': "&quot;",
"'": "&apos;"
},
escapeXmlAttr = function(str) {
return str.replace(attrReplace, function(match) {
return replacements[match];
});
},
EscapeXmlContext = require('raptor/templating/compiler/EscapeXmlContext');
var TextNode = function(text, escapeXml) {
TextNode.superclass.constructor.call(this, 'text');
if (text != null && typeof text !== 'string') {
throw raptor.createError('Invalid text: ' + text);
}
this.text = text;
this.escapeXml = escapeXml !== false;
};
TextNode.prototype = {
normalizeText: function() {
var normalizedText = this.getEscapedText(),
curChild = this.nextSibling,
nodeToRemove;
while(curChild && curChild.isTextNode()) {
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) {
//console.error(JSON.stringify(initialSpaceMatches[0]));
var indentMatches = /\n[^\n]*$/.exec(initialSpaceMatches[0]);
if (indentMatches) {
//console.error(JSON.stringify(indentMatches[0]));
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;
}
if (!this.previousSibling) {
//First child
text = text.replace(/^\n\s*/g, "");
}
if (!this.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,
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 + "]";
}
};
return TextNode;
});

View File

@ -0,0 +1,107 @@
/*
* 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/TypeConverter',
['raptor'],
function(raptor, require) {
"use strict";
var ExpressionParser = require('raptor/templating/compiler/ExpressionParser'),
stringify = require('raptor/json/stringify'),
Expression = require('raptor/templating/compiler/Expression');
var TypeConverter = function() {
};
TypeConverter.convert = function(value, targetType, allowExpressions) {
var hasExpression = false,
expressionParts = [];
if (value == null) {
return value;
}
if (targetType === 'custom' || targetType === 'identifier') {
return value;
}
if (targetType === 'expression') {
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) {
return new Expression(expressionParts.join("+"));
}
value = processedText;
}
if (targetType === 'string') {
return allowExpressions ? new Expression(value ? stringify(value) : "null") : value;
}
else if (targetType === 'boolean') {
if (allowExpressions) {
return new Expression(value);
}
else {
value = value.toLowerCase();
value = value === 'true' || value === 'yes'; //convert it to a boolean
return 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 {
throw raptor.createError(new Error("Unsupported attribute type: " + targetType));
}
};
return TypeConverter;
});

222
lib/compiler/compiler.js Normal file
View File

@ -0,0 +1,222 @@
/*
* 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(
"raptor/templating/compiler",
function(require, exports, module) {
"use strict";
var TaglibCollection = require('raptor/templating/compiler/TaglibCollection'),
taglibs = new TaglibCollection(),
extend = require('raptor').extend,
ExpressionParser = require('raptor/templating/compiler/ExpressionParser'),
defaultOptions = {
minify: false,
preserveWhitespace: {
'pre': true,
'textarea': true
},
allowSelfClosing: { //Conditionally enable self-closing tags for specific elements. Self-closing tag: <div /> Not self-closing tag: <div></div>
//'pre': true
},
startTagOnly: {
'img': true,
'br': true,
'input': true,
'meta': true,
'link': true,
'hr': true
}
};
return {
/**
* Creates a new object that can be used to compile templates with the
* provided options.
*
* <p>
* Allowed options:
* <ul>
* <li>
* <b>preserveWhitespace</b> (object|boolean): An object that defines which elements should
* have their whitespace preserved. While most whitespace gets normalized
* in HTML documents, some HTML elements make use of their whitespace (e.g. PRE and TEXTAREA tags).
* If this option is set to "true" then all whitespace is preserved.
*
* <p>
* Default value:
<pre>
{
'pre': true,
'textarea': true
}
</pre>
* </li>
* <li>
* <b>allowSelfClosing</b> (object): An object that defines which elements are allowed
* to be self-closing. By default, all elements are allowed to be self-closing.
* Some browsers do not handle certain HTML elements that are self-closing
* and require a separate ending tag.
*
* <p>
* Default value:
<pre>
allowSelfClosing: {
'script': false,
'div': false
}
</pre>
* </li>
* <li>
* <b>startTagOnly</b> (object): An object that defines which elements should only be
* written out with the opening tag and not the closing tags. For HTML5
* output that is not well-formed XML it is acceptable to write
* certain elements with the opening tag only.
*
* <p>
* Default value:
<pre>
startTagOnly: {
'img': true,
'br': true
}
</pre>
* </li>
* </ul>
*
* @param options Compiler options (see above)
* @returns {raptor/templating/compiler$TemplateCompiler} The newly created compiler
*/
createCompiler: function(options) {
if (this.discoverTaglibs) { //Only discover taglibs if that method is implemented
this.discoverTaglibs(); //The discoverTaglibs method is implemented on the server so execute it now
}
var TemplateCompiler = require("raptor/templating/compiler/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), //Create a clone of the default options that can be extended
options);
}
else {
options = defaultOptions; //Otherwise, no options were provided so use the default options
}
return new TemplateCompiler(taglibs, options);
},
/**
* Compiles an XML template by creating a new compiler using the provided options and
* then passing along the XML source code for the template to be compiled.
*
* For a list of options see {@link raptor/templating/compiler/createCompiler}
*
* @param xmlSource {String} The XML source code for the template to compile
* @param path {String} The path to the template (for debugging/error reporting purposes only)
* @returns {String} The JavaScript code for the compiled template.
*/
compile: function(xmlSource, path, options) {
return this.createCompiler(options).compile(xmlSource, path);
},
/**
*
* @param xmlSource {String} The XML source code for the template to compile
* @param path {String} The path to the template (for debugging/error reporting purposes only)
* @returns {void}
*/
compileAndLoad: function(xmlSource, path, options) {
this.createCompiler(options).compileAndLoad(xmlSource, path);
},
/**
*
* @param taglibXml
* @param path
* @returns
*/
loadTaglibXml: function(taglibXml, path) {
var TaglibXmlLoader = require("raptor/templating/compiler/TaglibXmlLoader");
var taglib = TaglibXmlLoader.load(taglibXml, path);
this.addTaglib(taglib);
return taglib;
},
/**
* Adds a {@link raptor/templating/compiler/Taglib} instance to the internal {@link raptor/templating/compiler/TaglibCollection} so
* that the taglib is available to all compilers.
*
* @param taglib {raptor/templating/compiler/Taglib} The taglib to add
* @returns {void}
*/
addTaglib: function(taglib) {
taglibs.add(taglib);
},
addTaglibAlias: function(uri, alias) {
taglibs.addAlias(uri, alias);
},
clearTaglibs: function() {
this.taglibs = taglibs = new TaglibCollection();
},
hasTaglib: function(uri) {
return taglibs.isTaglib(uri);
},
/**
*
* Registers a custom expression handler with the given name and handler function.
*
* <p>
* Custom expression handlers are functions that can be used to control the compiled output
* of certain expressions. Custom expression handlers are of the following form:
* ${<handler-name>:<custom-expression>}
*
* <p>
* When a custom expression handler is used in a template then the provided handler
* function will be invoked with two arguments:
* <ul>
* <li><b>customExpression</b> (String) The custom expression provided in the template the (the part after the colon)</li>
* <li><b>helper</b> (ExpressionParserHelper) The helper that can be used to control the compiled output</li>
* </ul>
*
* @param name
* @param func
* @returns
*/
registerCustomExpressionHandler: function(name, func) {
ExpressionParser.custom[name] = func;
},
recordLoadedTaglib: function(taglibResource) {
// No-op by default
},
defaultOptions: defaultOptions,
taglibs: taglibs
};
});

View File

@ -0,0 +1,52 @@
/*
* 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.
*/
/**
* @extension Server
*/
define.extend(
"raptor/templating/compiler",
function(require, compiler) {
"use strict";
var raptor = require('raptor'),
discoveryComplete = false;
return {
/**
*
* @returns
*/
discoverTaglibs: function() {
if (discoveryComplete) {
return;
}
discoveryComplete = true;
var taglibPaths = $rget("rtld");
raptor.forEach(taglibPaths, function(path) {
var resource = require('raptor/resources').findResource(path);
if (resource && resource.exists()) {
this.loadTaglibXml(resource.readAsString(), resource.getPath());
}
}, this);
}
};
});

View File

@ -0,0 +1,65 @@
/*
* 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.
*/
/**
* @extension Rhino
*/
define.extend(
"raptor/templating/compiler",
function(require, compiler) {
"use strict";
var java = require("raptor/java");
var convertJavaOptions = function(javaOptions) {
var options = {};
options.templateName = javaOptions.templateName;
return options;
};
return {
/**
*
* @param src
* @param path
* @param javaOptions
* @returns
*/
rhinoCompile: function(src, path, javaOptions) {
return this.compile(src, path, convertJavaOptions(javaOptions));
},
/**
*
* @param path
* @param javaOptions
* @returns
*/
rhinoCompileResource: function(path, javaOptions) {
return this.compileResource(path, convertJavaOptions(javaOptions));
},
/**
*
* @param path
* @param javaOptions
* @returns
*/
rhinoCompileAndLoadResource: function(path, javaOptions) {
return this.compileAndLoadResource(path, convertJavaOptions(javaOptions));
}
};
});

View File

@ -0,0 +1,302 @@
/*
* 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.
*/
/**
* @extension Server
*/
define.extend(
"raptor/templating/compiler",
function(require, compiler) {
"use strict";
var logger = require('raptor/logging').logger("raptor/templating/compiler"),
raptor = require('raptor'),
resources = require('raptor/resources'),
packaging = require('raptor/packaging'),
discoveryComplete = false,
searchPathListenerHandler = null,
watchingEnabled = false,
loadedTaglibPaths = {},
taglibsLastModified = null;
Object.defineProperty(compiler, "workDir", {
get: function() {
return this._workDir;
},
set: function(val) {
this.setWorkDir(val);
}
});
return {
enableWatching: function() {
watchingEnabled = true;
},
disableWatching: function() {
watchingEnabled = false;
},
isWatchingEnabled: function() {
return watchingEnabled;
},
setWatchingEnabled: function(enabled) {
watchingEnabled = enabled;
},
/**
*
* @param path
* @returns
*/
compileAndLoadResource: function(path, options) {
this.createCompiler(options).compileAndLoadResource(path);
},
/**
*
* @param path
* @returns
*/
compileResource: function(path, options) {
return this.createCompiler(options).compileResource(path);
},
loadModuleTaglibs: function(moduleName) {
var manifest = require('raptor/packaging').getModuleManifest(moduleName);
if (manifest) {
this.loadPackageTaglibs(manifest);
}
},
loadPackageTaglibs: function(manifest) {
var taglibs = manifest.getRaptorProp('taglibs');
if (taglibs) {
raptor.forEach(taglibs, function(rtldPath) {
var key = manifest.getURL() + ':' + rtldPath;
if (!loadedTaglibPaths[key]) {
loadedTaglibPaths[key] = true;
var rtldResource = manifest.resolveResource(rtldPath);
if (!rtldResource || !rtldResource.exists()) {
throw raptor.createError(new Error('Raptor TLD "' + rtldPath + '" not found for manifest "' + manifest.getURL() + '"'));
}
this.loadTaglib(rtldResource);
}
}, this);
}
},
findAndLoadTaglib: function(uri) {
var pathBuilders = [
function(uri) {
var path = uri;
if (!path.endsWith('.rtld')) {
path += '.rtld';
}
if (!path.startsWith('/')) {
path = '/' + path;
}
return path;
},
function(uri) {
var lastSlash = uri.lastIndexOf('/');
var shortName = lastSlash === -1 ? uri : uri.substring(lastSlash+1);
path = uri + '/' + shortName;
if (!path.endsWith('.rtld')) {
path += '.rtld';
}
if (!path.startsWith('/')) {
path = '/' + path;
}
return path;
}
];
for (var i=0, len=pathBuilders.length; i<len; i++) {
var pathBuilder = pathBuilders[i];
var path = pathBuilder(uri);
var taglibResource = require('raptor/resources').findResource(path);
if (taglibResource && taglibResource.exists()) {
var taglib = require('raptor/templating/compiler').loadTaglib(taglibResource);
this.addTaglibAlias(taglib.uri, uri);
return;
}
}
// Last resort: see if the URI is associated with a module that registers
// the taglibs...
require('raptor/templating/compiler').loadModuleTaglibs(uri);
},
/**
*
* @returns
*/
discoverTaglibs: function(force) {
if (discoveryComplete && force !== true) {
return;
}
discoveryComplete = true;
this.clearTaglibs();
loadedTaglibPaths = {};
packaging.forEachTopLevelPackageManifest(this.loadPackageTaglibs, this);
resources.forEach("/rtlds", function(rtldsResource) {
if (rtldsResource.isDirectory()) {
rtldsResource.forEachChild(function(rtldResource) {
if (rtldResource.isFile() && rtldResource.getName().endsWith(".rtld")) {
this.loadTaglib(rtldResource);
}
}, this);
}
}, this);
if (!searchPathListenerHandler) {
searchPathListenerHandler = require('raptor/resources').getSearchPath().subscribe("modified", function() {
discoveryComplete = false;
this.discoverTaglibs(); //If the search path is modified then rediscover the taglibs
}, this);
}
},
loadTaglib: function(taglibResource) {
this.recordLoadedTaglib(taglibResource);
var xml = taglibResource.readAsString();
return this.loadTaglibXml(xml, taglibResource);
},
recordLoadedTaglib: function(taglibResource) {
var workDir = this.getWorkDir();
if (workDir) {
var taglibsLastModified = this._readTaglibsLastModified(workDir);
taglibsLastModified.lastModified = Math.max(taglibResource.lastModified(), taglibsLastModified.lastModified || 0);
taglibsLastModified.urls[taglibResource.getURL()] = true;
var newStr = taglibsLastModified.lastModified + '\n' + Object.keys(taglibsLastModified.urls).join('\n');
if (newStr != taglibsLastModified.written) {
taglibsLastModified.written = newStr;
this._taglibsWorkFile.writeAsString(newStr);
}
}
},
_readTaglibsLastModified: function(workDir) {
var taglibsWorkFile = this._taglibsWorkFile;
if (taglibsLastModified == null) {
taglibsLastModified = {
lastModified: null,
urls: {},
written: null
};
if (taglibsWorkFile.exists()) {
try {
taglibsLastModified.str = taglibsWorkFile.readAsString();
var lastModifiedEnd = taglibsLastModified.str.indexOf('\n');
taglibsLastModified.lastModified = parseInt(taglibsLastModified.str.substring(0, lastModifiedEnd), 10);
taglibsLastModified.str.substring(lastModifiedEnd+1).split('\n').forEach(function(url) {
taglibsLastModified.urls[url] = true;
});
}
catch(e) {
logger.warn('Unable to read "' + taglibsWorkFile.getAbsolutePath() + '". Exception: ' + e, e);
}
}
}
return taglibsLastModified;
},
_validateWorkDir: function(workDir) {
if (!workDir) {
return;
}
logger.debug('Validating work directory at path "' + workDir + '"...');
var taglibsLastModified = this._readTaglibsLastModified(workDir);
function isUpToDate() {
var lastModified = taglibsLastModified.lastModified;
if (lastModified == null) {
return true;
}
var files = require('raptor/files');
var urls = Object.keys(taglibsLastModified.urls);
for (var i=0, len=urls.length; i<len; i++) {
var url = urls[i];
if (url.startsWith('file://')) { // Only supporting file URLs for now...
var taglibFile = files.fromFileUrl(url);
if (!taglibFile.exists() || taglibFile.lastModified() > lastModified) {
return false;
}
}
}
return true;
}
if (!isUpToDate()) {
console.log('One ore more taglibs modified. Removing compiled templates work directory at path "' + workDir.getAbsolutePath() + '"...');
workDir.remove();
}
},
setWorkDir: function(workDir) {
if (workDir) {
logger.debug('Setting work directory to "' + workDir + '"...');
var File = require('raptor/files/File');
if (typeof workDir === 'string') {
workDir = new File(workDir);
}
this._workDir = workDir;
this._taglibsWorkFile = new File(this._workDir, 'taglibs.txt');
this._validateWorkDir(workDir);
}
else {
this._workDir = null;
this._taglibsWorkFile = null;
}
},
getWorkDir: function() {
return this._workDir;
}
};
});

View File

@ -0,0 +1,70 @@
{
"dependencies": [
{
"package": "raptor/resources"
},
{
"package": "raptor/regexp"
},
{
"package": "raptor/strings"
},
{
"package": "raptor/objects"
},
{
"package": "raptor/listeners"
},
{
"package": "raptor/json/stringify"
},
{
"package": "raptor/xml/utils"
},
{
"package": "raptor/xml/sax/object-mapper"
},
{
"package": "raptor/temp"
},
"Expression.js",
"ExpressionParser.js",
"TypeConverter.js",
"AttributeSplitter.js",
"TemplateBuilder.js",
"TemplateCompiler.js",
"Taglib.js",
"TaglibCollection.js",
"compiler.js",
"Node.js",
"ElementNode.js",
"TextNode.js",
"ParseTreeBuilder.js",
"TaglibXmlLoader.js",
"EscapeXmlContext.js",
{
"path": "compiler_server.js",
"if-extension": "server"
},
{
"path": "TemplateCompiler_server.js",
"if-extension": "server"
},
{
"path": "compiler_rhino.js",
"if-extension": "rhino"
},
{
"path": "TemplateCompiler_rhino.js",
"if-extension": "rhino"
},
{
"path": "TemplateCompiler_node.js",
"if-extension": "node"
},
{
"path": "compiler_browser.js",
"if-extension": "browser"
}
]
}

350
lib/index.js Normal file
View File

@ -0,0 +1,350 @@
/*
* 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.
*/
/**
* This module provides the runtime for rendering compiled templates.
*
*
* <p>The code for the Raptor Templates compiler is kept separately
* in the {@link raptor/templating/compiler} module.
*/
define('raptor/templating', ['raptor'], function(raptor, require, exports, module) {
"use strict";
var getRegisteredTemplate = function(name) {
return $rget('rhtml', name);
},
loadedTemplates = {},
isArray = Array.isArray,
createError = raptor.createError,
StringBuilder = require('raptor/strings/StringBuilder'),
escapeXml = require('raptor/xml/utils').escapeXml,
escapeXmlAttr = require('raptor/xml/utils').escapeXmlAttr,
renderContext = require('raptor/render-context'),
Context = renderContext.Context,
_getFunction = Context.classFunc,
templating,
/**
* Helper function to return the singleton instance of a tag handler
*
* @param name {String} The class name of the tag handler
* @returns {Object} The tag handler singleton instance.
*/
_getHandler = function(name) {
var Handler = require(name), //Load the handler class
instance;
if (Handler.process || Handler.render) {
instance = Handler;
}
else if (!(instance = Handler.instance)) { //See if an instance has already been created
instance = Handler.instance = new Handler(); //If not, create and store a new instance
}
return instance; //Return the handler instance
},
/**
* Helper function to check if an object is "empty". Different types of objects are handled differently:
* 1) null/undefined: Null and undefined objects are considered empty
* 2) String: The string is trimmed (starting and trailing whitespace is removed) and if the resulting string is an empty string then it is considered empty
* 3) Array: If the length of the array is 0 then the array is considred empty
*
*/
notEmpty = function(o) {
if (Array.isArray(o) === true) {
return o.length !== 0;
}
return o;
},
helpers = {
/**
* Helper function to return a static helper function
*
* @function
* @param uri
* @param name
* @returns {Function} The corresponding helper function. An exception is thrown if the helper function is not found
*/
h: _getFunction,
t: _getHandler,
/**
* forEach helper function
*
* @param array {Array} The array to iterate over
* @param callback {Function} The callback function to invoke for each iteration
* @returns {void}
*/
fv: function(array, callback) {
if (!array) {
return;
}
if (!array.forEach) {
array = [array];
}
var i=0,
len=array.length, //Cache the array size
loopStatus = { //The "loop status" object is provided as the second argument to the callback function used for each iteration
/**
* Returns the length of the array that is being iterated over
* @returns {int} The length of the array
*/
getLength: function() {
return len;
},
/**
* Returns true if the current iteration is the last iteration
* @returns {Boolean} True if the current iteration is the last iteration. False, otherwse.
*/
isLast: function() {
return i === len-1;
},
isFirst: function() {
return i === 0;
},
getIndex: function() {
return i;
}
};
for (; i<len; i++) { //Loop over the elements in the array
var o = array[i];
callback(o || '', loopStatus);
}
},
f: raptor.forEach,
fl: function(array, func) {
if (array != null) {
if (!isArray(array)) {
array = [array];
}
func(array, 0, array.length);
}
},
fp: function(o, func) {
if (!o) {
return;
}
for (var k in o)
{
if (o.hasOwnProperty(k))
{
func(k, o[k]);
}
}
},
e: function(o) {
return !notEmpty(o);
},
ne: notEmpty,
/**
* escapeXml helper function
*
* @param str
* @returns
*/
x: escapeXml,
xa: escapeXmlAttr,
nx: function(str) {
return {
toString: function() {
return str;
}
};
}
};
templating = {
/**
* Returns a function that can be used to render the template with the specified name.
*
* The template function should always be invoked with two arguments:
* <ol>
* <li><b>data</b> {Object}: The template data object</li>
* <li><b>context</b> {@link raptor/templating/Context}: The template context object</li>
* </ul>
*
* @param {String} templateName The name of the template
* @return {Function} The function that can be used to render the specified template.
*/
templateFunc: function(templateName) {
/*
* We first need to find the template rendering function. It's possible
* that the factory function for the template rendering function has been
* registered but that the template rendering function has not already
* been created.
*
* The template rendering functions are lazily initialized.
*/
var templateFunc = loadedTemplates[templateName]; //Look for the template function in the loaded templates lookup
if (!templateFunc) { //See if the template has already been loaded
/*
* If we didn't find the template function in the loaded template lookup
* then it means that the template has not been fully loaded and initialized.
* Therefore, check if the template has been registerd with the name provided
*/
templateFunc = getRegisteredTemplate(templateName);
if (!templateFunc && this.findTemplate) {
this.findTemplate(templateName);
templateFunc = getRegisteredTemplate(templateName);
}
if (templateFunc) { //Check the registered templates lookup to see if a factory function has been register
/*
* We found that template has been registered so we need to fully initialize it.
* To create the template rendering function we must invoke the template factory
* function which expects a reference to the static helpers object.
*
* NOTE: The factory function allows static private variables to be created once
* and are then made available to the rendering function as part of the
* closure for the rendering function
*/
var templateInfo = this.getTemplateInfo(templateName);
templateFunc = templateFunc(helpers, templateInfo); //Invoke the factory function to get back the rendering function
}
if (!templateFunc) {
throw createError(new Error('Template not found with name "' + templateName + '"'));
}
loadedTemplates[templateName] = templateFunc; //Store the template rendering function in the lookup
}
return templateFunc;
},
getTemplateInfo: function(templateName) {
return {
name: templateName
};
},
/**
* Renders a template to the provided context.
*
* <p>
* The template specified by the templateName parameter must already have been loaded. The data object
* is passed to the compiled rendering function of the template. All output is written to the provided
* context using the "writer" associated with the context.
*
* @param templateName The name of the template to render. The template must have been previously rendered
* @param data The data object to pass to the template rendering function
* @param context The context to use for all rendered output (required)
*/
render: function(templateName, data, context) {
if (!context) {
throw createError(new Error("Context is required"));
}
var templateFunc = this.templateFunc(templateName);
try
{
templateFunc(data || {}, context); //Invoke the template rendering function with the required arguments
}
catch(e) {
throw createError(new Error('Unable to render template with name "' + templateName + '". Exception: ' + e), e);
}
},
/**
* Renders a template and captures the output as a String
*
* @param templateName {String}The name of the template to render. NOTE: The template must have already been loaded.
* @param data {Object} The data object to provide to the template rendering function
* @param context {raptor/templating/Context} The context object to use (optional). If a context is provided then the writer will be
* temporarily swapped with a StringBuilder to capture the output of rendering. If a context
* is not provided then one will be created using the "createContext" method.
* @returns {String} The string output of the template
*/
renderToString: function(templateName, data, context) {
var sb = new StringBuilder(); //Create a StringBuilder object to serve as a buffer for the output
if (context === undefined) {
/*
* If a context object is not provided then we need to create a new context object and use the StringBuilder as the writer
*/
this.render(templateName, data, new Context(sb));
}
else {
var _this = this;
/*
* If a context is provided then we need to temporarily swap out the writer for the StringBuilder
*/
context.swapWriter(sb, function() {
_this.render(templateName, data, context);
}); //Swap in the writer, render the template and then restore the original writer
}
return sb.toString(); //Return the final string associated with the StringBuilder
},
/**
* Unloads a template so that it can be reloaded.
*
* @param templateName
*/
unload: function(templateName) {
delete loadedTemplates[templateName];
$rset('rhtml', templateName, undefined);
},
/**
* Helper function to return a helper function
*
* @function
* @param uri
* @param name
* @returns {Function} The corresponding helper function. An exception is thrown if the helper function is not found
*/
getFunction: _getFunction,
/**
* Creates a new context object that can be used as the context for
* template rendering.
*
* @param writer {Object} An object that supports a "write" and a "toString" method.
* @returns {templating$Context} The newly created context object
*/
createContext: renderContext.createContext,
getHandler: _getHandler,
/**
* Helper functions (with short names for minification reasons)
*/
helpers: helpers
};
return templating;
});

74
lib/index_async.js Normal file
View File

@ -0,0 +1,74 @@
/*
* 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.
*/
/**
*
* @extension Async
*
*/
define.extend('raptor/templating', function(require) {
"use strict";
var promises = require('raptor/promises');
return {
renderToStringAsync: function(templateName, data, context) {
if (!context) {
context = this.createContext();
}
var promise = this.renderAsync(templateName, data, context);
var deferred = promises.defer();
promise
.then(function() {
deferred.resolve(context.getOutput());
})
.fail(function(e) {
deferred.reject(e);
});
return deferred.promise;
},
renderAsync: function(templateName, data, context) {
if (!context) {
throw new Error("context is required");
}
var deferred = promises.defer();
var attributes = context.attributes;
var asyncAttributes = attributes.async || (attributes.async = {});
asyncAttributes.remaining = 0;
asyncAttributes.deferred = deferred;
try {
this.render(templateName, data, context);
}
catch(e) {
deferred.reject(e);
}
asyncAttributes.firstPassComplete = true;
if (asyncAttributes.remaining === 0) {
deferred.resolve(context);
}
return deferred.promise;
}
};
});

66
lib/index_rhino.js Normal file
View File

@ -0,0 +1,66 @@
/*
* 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.
*/
/**
*
* @extension Rhino
*
*/
define.extend('raptor/templating', function(require) {
"use strict";
var raptor = require('raptor');
var WrappedWriter = function(javaWriter) {
this.javaWriter = javaWriter;
this.write = function(o) {
if (o != null) {
this.javaWriter.write(o.toString());
}
};
};
return {
/**
* Provides a Rhino-compatible render function that bridges the gap between
* the Java world and the JavaScript world.
*
* @param templateName {java.lang.String} The name of template to render
* @param data {String|java.lang.Object} The data object to pass to the template rendering function
* @param javaWriter {java.io.Writer} The Java Writer object to use for output
* @returns {void}
*/
rhinoRender: function(templateName, data, context) {
if (data && typeof data === 'string') {
try
{
data = eval("(" + data + ")"); //Convert the JSON string to a native JavaScript object
}
catch(e) {
throw raptor.createError('Invalid JSON data passed to "' + templateName + '". Exception: ' + e, e);
}
}
this.render('' + templateName, data, context);
},
rhinoCreateContext: function(javaWriter) {
var context = this.createContext(new WrappedWriter(javaWriter)); //Wrap the Java writer with a JavaScript object
return context;
}
};
});

134
lib/index_server.js Normal file
View File

@ -0,0 +1,134 @@
/*
* 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.
*/
/**
*
* @extension Server
*
*/
define.extend('raptor/templating', function(require) {
"use strict";
var strings = require('raptor/strings'),
resources = require('raptor/resources'),
files = require('raptor/files'),
File = require('raptor/files/File'),
templateInfoByName = {},
logger = require('raptor/logging').logger('raptor/templating_server');
return {
/**
* This method gets invoked if a template with a particular name has not been loaded/registered
* and will then try to find it and load it.
*
* This method will attempt to convert the template name to various
* resource paths to try and find the template on disk. It assumes
* that templates are named with a particular convention. In addition to
* trying the template as a file path, the following
* conventions are supported:
*
* <ol>
* <li>my/template --> /my/template.rhtml
* <li>my/template --> /my/template/template.rhtml
* </ol>
*
* @param name {String} The template name
* @returns {void}
*/
findTemplate: function(name) {
var path = name,
resource,
templatePath,
templateFile = name instanceof File ? name : new File(name);
if (templateFile.exists() && templateFile.isFile()) {
resource = resources.createFileResource(templateFile.getAbsolutePath());
name = templateFile.getAbsolutePath();
}
else if (name instanceof File) {
return;
}
else {
templatePath = path;
if (!strings.startsWith(templatePath, '/')) {
templatePath = '/' + templatePath;
}
if (!strings.endsWith(templatePath, '.rhtml')) {
templatePath += '.rhtml';
}
resource = resources.findResource(templatePath);
}
if (!resource || !resource.exists()) {
templatePath = path;
if (!strings.startsWith(templatePath, '/')) {
templatePath = '/' + templatePath;
}
var lastSlash = templatePath.lastIndexOf('/');
if (lastSlash != -1) {
resource = resources.findResource(templatePath + templatePath.substring(lastSlash) + ".rhtml");
}
}
if (resource && resource.exists()) {
templateInfoByName[name] = {
resource: resource,
filePath: resource.isFileResource() ? resource.getFilePath() : null,
name: name
};
require('raptor/templating/compiler').compileAndLoadResource(resource, {templateName: name});
}
},
unloadFile: function(path) {
for (var curName in templateInfoByName) {
if (templateInfoByName.hasOwnProperty(curName)) {
var templateInfo = templateInfoByName[curName];
if (templateInfo.filePath === path) {
this.unload(curName);
}
}
}
},
getTemplateInfo: function(name) {
return templateInfoByName[name] || {templateName: name};
},
renderToFile: function(templateName, data, outputFile, context) {
if (typeof outputFile === 'string') {
outputFile = new File(outputFile);
}
return this.renderToStringAsync(templateName, data, context)
.then(
function(html) {
outputFile.writeAsString(html);
},
function(e) {
logger.error(e);
});
}
};
});

14
lib/optimizer.json Normal file
View File

@ -0,0 +1,14 @@
{
"dependencies": [
{
"package": "raptor/render-context"
},
{
"package": "raptor/strings"
},
{
"package": "raptor/xml/utils"
},
"templating.js"
]
}

View File

@ -0,0 +1,68 @@
define(
'raptor/templating/taglibs/async/AsyncFragmentTag',
function(require, exports, module) {
"use strict";
var logger = require('raptor-logging').logger(module);
var promises = require('raptor/promises');
return {
render: function(input, context) {
var dataProvider = input.dataProvider,
arg = input.arg || {};
arg.context = context;
context.beginAsyncFragment(function(asyncContext, asyncFragment) {
function onError(e) {
asyncFragment.end(e);
}
function renderBody(data) {
try {
if (input.invokeBody) {
input.invokeBody(asyncContext, data);
}
asyncFragment.end();
}
catch(e) {
onError(e);
}
}
try {
var promise;
if (typeof dataProvider === 'function') {
// The data provider is a function we can call to
// get the data or a promise
promise = dataProvider(arg);
}
else {
promise = promises.isPromise(dataProvider) ?
dataProvider : // Data provider is already a promise
context.requestData(dataProvider, arg); // Otherwise request the data using a data provider
// associated with the context
}
if (promise.resolvedValue) { // Legacy "resolvedValue" support
renderBody(promise.resolvedValue);
}
else {
promises.immediateThen(promise,
function(data) {
renderBody(data);
},
onError)
.fail(onError);
}
}
catch(e) {
onError(e);
}
}, input.timeout);
}
};
});

View File

@ -0,0 +1,60 @@
define(
'raptor/templating/taglibs/async/AsyncFragmentTagTransformer',
['raptor'],
function(raptor, require) {
"use strict";
var varNameRegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;
return {
process: function(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 = [];
node.forEachProperty(function(uri, name, value) {
if (uri === '' && name.startsWith('arg-')) {
var argName = name.substring('arg-'.length);
argProps.push(JSON.stringify(argName) + ": " + value);
propsToRemove.push(name);
}
});
propsToRemove.forEach(function(propName) {
node.removeProperty(propName);
});
var argString;
if (argProps.length) {
argString = "{" + argProps.join(", ") + "}";
}
var arg = node.getProperty('arg');
if (arg) {
argString = 'require("raptor").extend(' + arg + ', ' + argString +')';
}
if (argString) {
node.setProperty("arg", template.makeExpression(argString));
}
}
};
});

View File

@ -0,0 +1,32 @@
<raptor-taglib>
<tlib-version>1.0</tlib-version>
<uri>http://raptorjs.org/templates/async</uri>
<short-name>async</short-name>
<prefix>async</prefix>
<tag>
<name>fragment</name>
<handler-class>raptor/templating/taglibs/async/AsyncFragmentTag</handler-class>
<attribute name="dependency" type="string" target-property="dataProvider" deprecated="Use 'data-provider' instead"/>
<attribute name="data-provider" type="string" />
<attribute name="arg" type="expression" preserve-name="true"/>
<attribute pattern="arg-*" type="string" preserve-name="true"/>
<attribute name="var" type="identifier"/>
<attribute name="timeout" type="integer" />
<variable name="context"/>
<variable name-from-attribute="var OR dependency OR data-provider|keep"/>
<transformer>
<class-name>raptor/templating/taglibs/async/AsyncFragmentTagTransformer</class-name>
<after>raptor/templating/taglibs/core/CoreTagTransformer</after>
</transformer>
</tag>
</raptor-taglib>

View File

@ -0,0 +1,8 @@
{
"dependencies": [
{
"package": "raptor/render-context/async"
},
"AsyncFragmentTag.js"
]
}

View File

@ -0,0 +1,37 @@
define(
'raptor/templating/taglibs/caching/CachedFragmentTag',
function(require) {
"use strict";
return {
render: function(input, context) {
var attributes = context.attributes,
cacheProvider = attributes.cacheProvider,
cache,
cachedHtml,
cacheKey = input.cacheKey;
if (!cacheKey) {
throw require('raptor').createError(new Error('cache-key is required for <caching:cached-fragment>'));
}
if (!cacheProvider) {
cacheProvider = require('raptor/caching').getDefaultProvider();
}
cache = cacheProvider.getCache(input.cacheName);
cachedHtml = cache.get(cacheKey, function() {
return context.captureString(function() {
if (input.invokeBody) {
input.invokeBody();
}
});
});
context.write(cachedHtml);
}
};
});

View File

@ -0,0 +1,20 @@
<raptor-taglib>
<tlib-version>1.0</tlib-version>
<uri>http://raptorjs.org/templates/caching</uri>
<short-name>caching</short-name>
<prefix>caching</prefix>
<tag>
<name>cached-fragment</name>
<handler-class>raptor/templating/taglibs/caching/CachedFragmentTag</handler-class>
<attribute name="cache-key"/>
<attribute name="cache-name"/>
</tag>
</raptor-taglib>

View File

@ -0,0 +1,8 @@
{
"dependencies": [
{
"package": "raptor/caching"
},
"CachedFragmentTag.js"
]
}

View File

@ -0,0 +1,61 @@
/*
* 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/taglibs/core/AssignNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var varNameRegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;
var AssignNode = function(props) {
AssignNode.superclass.constructor.call(this);
if (props) {
this.setProperties(props);
}
};
AssignNode.prototype = {
doGenerateCode: function(template) {
var varName = this.getProperty("var"),
value = this.getProperty("value");
if (!varName) {
this.addError('"var" attribute is required');
}
else if (!varNameRegExp.test(varName)) {
this.addError('Invalid variable name of "' + varName + '"');
varName = null;
}
if (!value) {
this.addError('"value" attribute is required');
}
if (varName) {
template.statement(varName + "=" + value + ";");
}
}
};
return AssignNode;
});

View File

@ -0,0 +1,89 @@
/*
* 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/taglibs/core/ChooseNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var strings = require('raptor/strings'),
WhenNode = require('raptor/templating/taglibs/core/WhenNode'),
OtherwiseNode = require('raptor/templating/taglibs/core/OtherwiseNode');
var ChooseNode = function(props) {
ChooseNode.superclass.constructor.call(this);
};
ChooseNode.prototype = {
doGenerateCode: function(template) {
var otherwiseNode = null,
foundWhenNode = false;
var allowedNodes = [];
this.forEachChild(function(child) {
if (child.isTextNode()) {
if (!strings.isEmpty(child.getText())) {
this.addError('Static text "' + strings.trim(child.getText()) + '" is not allowed in ' + this.toString() + " tag.");
}
}
else if (child.getNodeClass() === WhenNode){
if (otherwiseNode) {
this.addError(otherwiseNode + ' tag must be last child of tag ' + this + '.');
return;
}
if (!foundWhenNode) {
foundWhenNode = true;
child.firstWhen = true;
}
allowedNodes.push(child);
}
else if (child.getNodeClass() === OtherwiseNode) {
if (otherwiseNode) {
this.addError('More than one ' + otherwiseNode + ' tag is not allowed as child of tag ' + this + '.');
return;
}
otherwiseNode = child;
allowedNodes.push(otherwiseNode);
}
else {
this.addError(child + ' tag is not allowed as child of tag ' + this + '.');
child.generateCode(template); //Generate the code for the children so that we can still show errors to the user for nested nodes
}
}, this);
allowedNodes.forEach(function(child, i) {
child.hasElse = i < allowedNodes.length - 1;
child.generateCode(template);
});
if (!foundWhenNode) {
this.addError('' + otherwiseNode + ' tag is required to have at least one sibling <c:when> tag.');
}
}
};
return ChooseNode;
});

View File

@ -0,0 +1,451 @@
/*
* 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.
*/
function removeDashes(str) {
return str.replace(/-([a-z])/g, function(match, lower) {
return lower.toUpperCase();
});
}
define.Class(
"raptor/templating/taglibs/core/CoreTagTransformer",
['raptor'],
function(raptor, require) {
"use strict";
var extend = raptor.extend,
coreNS = "http://raptorjs.org/templates/core",
WriteNode = require('raptor/templating/taglibs/core/WriteNode'),
ForNode = require("raptor/templating/taglibs/core/ForNode"),
IfNode = require("raptor/templating/taglibs/core/IfNode"),
ElseIfNode = require("raptor/templating/taglibs/core/ElseIfNode"),
ElseNode = require("raptor/templating/taglibs/core/ElseNode"),
WithNode = require("raptor/templating/taglibs/core/WithNode"),
WhenNode = require("raptor/templating/taglibs/core/WhenNode"),
OtherwiseNode = require("raptor/templating/taglibs/core/OtherwiseNode"),
TagHandlerNode = require("raptor/templating/taglibs/core/TagHandlerNode"),
IncludeNode = require('raptor/templating/taglibs/core/IncludeNode'),
Expression = require('raptor/templating/compiler/Expression'),
AttributeSplitter = require('raptor/templating/compiler/AttributeSplitter'),
TypeConverter = require('raptor/templating/compiler/TypeConverter'),
getPropValue = function(value, type, allowExpressions) {
return TypeConverter.convert(value, type, allowExpressions);
};
return {
process: function(node, compiler, template) {
//Find and handle nested <c:attrs> elements
this.findNestedAttrs(node, compiler, template);
var forEachAttr,
ifAttr,
elseIfAttr,
attrsAttr,
whenAttr,
withAttr,
escapeXmlAttr,
parseBodyTextAttr,
stripAttr,
contentAttr,
replaceAttr,
inputAttr,
forEachNode,
uri,
tag,
nestedTag,
forEachProp = function(callback, thisObj) {
node.forEachAttributeAnyNS(function(attr) {
if (attr.uri=== 'http://www.w3.org/2000/xmlns/' || attr.uri === 'http://www.w3.org/XML/1998/namespace' || attr.prefix == 'xmlns') {
return; //Skip xmlns attributes
}
var prefix = attr.prefix;
var attrUri = compiler.taglibs.resolveURI(attr.uri);
attrUri = attr.prefix && (attrUri != tag.getTaglibUri()) ? attr.uri : null;
var attrDef = compiler.taglibs.getAttribute(uri, node.localName, attrUri, attr.localName);
var type = attrDef ? (attrDef.type || 'string') : 'string',
value;
if (attrUri === tag.getTaglibUri()) {
prefix = '';
}
var isTaglibUri = template.isTaglib(attrUri);
if (!attrDef && (isTaglibUri || !tag.dynamicAttributes)) {
//Tag doesn't allow dynamic attributes
node.addError('The tag "' + tag.name + '" in taglib "' + tag.getTaglibUri() + '" does not support attribute "' + attr + '"');
return;
}
if (attr.value instanceof Expression) {
value = attr.value;
}
else {
try
{
value = getPropValue(attr.value, type, attrDef ? attrDef.allowExpressions !== false : true);
}
catch(e) {
node.addError('Invalid attribute value of "' + attr.value + '" for attribute "' + attr.name + '": ' + e.message);
value = attr.value;
}
}
var propName;
if (attrDef) {
if (attrDef.targetProperty) {
propName = attrDef.targetProperty;
}
else if (attrDef.preserveName) {
propName = attr.localName;
}
else {
propName = removeDashes(attr.localName);
}
}
else {
propName = attr.localName;
}
callback.call(thisObj, attrUri, propName, value, prefix, attrDef);
}, this);
tag.forEachStaticProperty(function(staticProp) {
var value = getPropValue(staticProp.value, staticProp.type, false);
callback.call(thisObj, '', staticProp.name, value, '', staticProp);
});
};
uri = node.uri;
if (!uri && node.isRoot() && node.localName === 'template') {
uri = coreNS;
}
if (node.parentNode) {
var parentUri = node.parentNode.uri;
var parentName = node.parentNode.localName;
nestedTag = compiler.taglibs.getNestedTag(parentUri, parentName, uri, node.localName);
if (nestedTag) {
node.setWordWrapEnabled(false);
node.parentNode.setProperty(nestedTag.targetProperty, node.getBodyContentExpression(template));
node.detach();
return;
}
}
tag = node.tag || compiler.taglibs.getTag(uri, node.localName);
if (node.getAttributeNS(coreNS, "space") === "preserve" || node.getAttributeNS(coreNS, "whitespace") === "preserve") {
node.setPreserveWhitespace(true);
}
node.removeAttributeNS(coreNS, "space");
node.removeAttributeNS(coreNS, "whitespace");
if ((escapeXmlAttr = node.getAttributeNS(coreNS, "escape-xml")) != null) {
node.removeAttributeNS(coreNS, "escape-xml");
node.setEscapeXmlBodyText(escapeXmlAttr !== "false");
}
if ((parseBodyTextAttr = node.getAttributeNS(coreNS, "parse-body-text")) != null) {
node.removeAttributeNS(coreNS, "parse-body-text");
node.parseBodyText = parseBodyTextAttr !== "false";
}
if ((whenAttr = node.getAttributeNS(coreNS, "when")) != null) {
node.removeAttributeNS(coreNS, "when");
var whenNode = new WhenNode({test: new Expression(whenAttr), pos: node.getPosition()});
node.parentNode.replaceChild(whenNode, node);
whenNode.appendChild(node);
}
if (node.getAttributeNS(coreNS, "otherwise") != null) {
node.removeAttributeNS(coreNS, "otherwise");
var otherwiseNode = new OtherwiseNode({pos: node.getPosition()});
node.parentNode.replaceChild(otherwiseNode, node);
otherwiseNode.appendChild(node);
}
if ((attrsAttr = node.getAttributeNS(coreNS, "attrs")) != null) {
node.removeAttributeNS(coreNS, "attrs");
node.dynamicAttributesExpression = attrsAttr;
}
if (((forEachAttr = node.getAttributeNS(coreNS, "for")) || (forEachAttr = node.getAttributeNS(coreNS, "for-each"))) != null) {
node.removeAttributeNS(coreNS, "for");
node.removeAttributeNS(coreNS, "for-each");
var forEachProps = AttributeSplitter.parse(
forEachAttr,
{
each: {
type: "custom"
},
separator: {
type: "expression"
},
"status-var": {
type: "identifier"
},
"for-loop": {
type: "boolean",
allowExpressions: false
}
},
{
removeDashes: true,
defaultName: "each",
errorHandler: function(message) {
node.addError('Invalid c:for attribute of "' + forEachAttr + '". Error: ' + message);
}
});
forEachProps.pos = node.getPosition(); //Copy the position property
forEachNode = new ForNode(forEachProps);
//Surround the existing node with an "forEach" node by replacing the current
//node with the new "forEach" node and then adding the current node as a child
node.parentNode.replaceChild(forEachNode, node);
forEachNode.appendChild(node);
}
if ((ifAttr = node.getAttributeNS(coreNS, "if")) != null) {
node.removeAttributeNS(coreNS, "if");
var ifNode = new IfNode({
test: new Expression(ifAttr),
pos: node.getPosition()
});
//Surround the existing node with an "if" node by replacing the current
//node with the new "if" node and then adding the current node as a child
node.parentNode.replaceChild(ifNode, node);
ifNode.appendChild(node);
}
if ((elseIfAttr = node.getAttributeNS(coreNS, "else-if")) != null) {
node.removeAttributeNS(coreNS, "else-if");
var elseIfNode = new ElseIfNode({
test: new Expression(elseIfAttr),
pos: node.getPosition()
});
//Surround the existing node with an "if" node by replacing the current
//node with the new "if" node and then adding the current node as a child
node.parentNode.replaceChild(elseIfNode, node);
elseIfNode.appendChild(node);
}
if ((node.getAttributeNS(coreNS, "else")) != null) {
node.removeAttributeNS(coreNS, "else");
var elseNode = new ElseNode({
pos: node.getPosition()
});
//Surround the existing node with an "if" node by replacing the current
//node with the new "if" node and then adding the current node as a child
node.parentNode.replaceChild(elseNode, node);
elseNode.appendChild(node);
}
if ((withAttr = node.getAttributeNS(coreNS, "with")) != null) {
node.removeAttributeNS(coreNS, "with");
var withNode = new WithNode({
vars: withAttr,
pos: node.getPosition()
});
node.parentNode.replaceChild(withNode, node);
withNode.appendChild(node);
}
if ((contentAttr = (node.getAttributeNS(coreNS, "bodyContent") || node.getAttributeNS(coreNS, "content"))) != null) {
node.removeAttributeNS(coreNS, "bodyContent");
node.removeAttributeNS(coreNS, "content");
var newChild = new WriteNode({expression: contentAttr, pos: node.getPosition()});
node.removeChildren();
node.appendChild(newChild);
}
if (node.getAttributeNS(coreNS, "trim-body-indent") === 'true') {
node.removeAttributeNS(coreNS, "trim-body-indent");
node.trimBodyIndent = true;
}
if (node.getAttributeNS && (stripAttr = node.getAttributeNS(coreNS, "strip")) != null) {
node.removeAttributeNS(coreNS, "strip");
if (!node.setStripExpression) {
node.addError("The c:strip directive is not allowed for target node");
}
node.setStripExpression(stripAttr);
}
if (node.getAttributeNS && (replaceAttr = node.getAttributeNS(coreNS, "replace")) != null) {
node.removeAttributeNS(coreNS, "replace");
var replaceWriteNode = new WriteNode({expression: replaceAttr, pos: node.getPosition()});
//Replace the existing node with an node that only has children
node.parentNode.replaceChild(replaceWriteNode, node);
node = replaceWriteNode;
}
if (tag) {
if (tag.preserveWhitespace) {
node.setPreserveWhitespace(true);
}
if (tag.handlerClass || tag.template)
{
if (tag.handlerClass) {
//Instead of compiling as a static XML element, we'll
//make the node render as a tag handler node so that
//writes code that invokes the handler
TagHandlerNode.convertNode(
node,
tag);
if ((inputAttr = node.getAttributeNS(coreNS, "input")) != null) {
node.removeAttributeNS(coreNS, "input");
node.setInputExpression(template.makeExpression(inputAttr));
}
}
else {
// The tag is mapped to a template that will be used to perform
// the rendering so convert the node into a "IncludeNode" that can
// be used to include the output of rendering a template
IncludeNode.convertNode(
node,
tag.template);
}
forEachProp(function(uri, name, value, prefix, attrDef) {
if (attrDef) {
node.setPropertyNS(uri, name, value);
}
else {
if (tag.dynamicAttributesRemoveDashes === true) {
name = removeDashes(name);
}
node.addDynamicAttribute(prefix ? prefix + ':' + name : name, value);
}
});
}
else if (tag.nodeClass){
var NodeCompilerClass = require(tag.nodeClass);
extend(node, NodeCompilerClass.prototype);
NodeCompilerClass.call(node);
node.setNodeClass(NodeCompilerClass);
forEachProp(function(uri, name, value) {
node.setPropertyNS(uri, name, value);
});
}
}
else if (uri && template.isTaglib(uri)) {
node.addError('Tag ' + node.toString() + ' is not allowed for taglib "' + uri + '"');
}
},
findNestedAttrs: function(node, compiler, template) {
node.forEachChild(function(child) {
if (child.uri === coreNS && child.localName === 'attr') {
this.handleAttr(child, compiler, template);
}
}, this);
},
handleAttr: function(node, compiler, template) {
var parentNode = node.parentNode;
if (!parentNode.isElementNode()) {
node.addError(this.toString() + ' tag is not nested within an element tag.');
return;
}
var hasValue = node.hasAttribute("value");
var attrName = node.getAttribute("name");
var attrValue = node.getAttribute("value");
var attrUri = node.getAttribute("uri") || '';
var attrPrefix = node.getAttribute("prefix") || '';
if (parentNode.hasAttributeNS(attrUri, attrName)) {
node.addError(node.toString() + ' tag adds duplicate attribute with name "' + attrName + '"' + (attrUri ? ' and URI "' + attrUri + '"' : ''));
return;
}
node.removeAttribute("name");
node.removeAttribute("value");
node.removeAttribute("uri");
node.removeAttribute("prefix");
if (node.hasAttributesAnyNS()) {
//There shouldn't be any other attributes...
var invalidAttrs = node.getAllAttributes().map(function(attr) {
return attr.qName;
});
node.addError("Invalid attributes for tag " + node.toString() + ': ' + invalidAttrs.join(", "));
return;
}
//Cleanup whitespace between <c:attr> tags
if (node.previousSibling && node.previousSibling.isTextNode() && node.previousSibling.getText().trim() === '') {
node.previousSibling.detach();
}
if (node.nextSibling && node.nextSibling.isTextNode() && node.nextSibling.getText().trim() === '') {
node.nextSibling.detach();
}
if (node.nextSibling && node.nextSibling.isTextNode()) {
node.nextSibling.setText(node.nextSibling.getText().replace(/^\n\s*/, ""));
}
node.detach(); //Remove the node out of the tree
compiler.transformTree(node, template);
if (hasValue) {
parentNode.setAttributeNS(attrUri, attrName, attrValue, attrPrefix);
}
else {
node.setEscapeXmlContext(require('raptor/templating/compiler/EscapeXmlContext').Attribute); //Escape body text and expressions as attributes
parentNode.setAttributeNS(attrUri, attrName, node.getBodyContentExpression(template), attrPrefix, false /* Don't escape...pre-escaped*/);
}
}
};
});

View File

@ -0,0 +1,102 @@
/*
* 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/taglibs/core/CoreTextTransformer",
['raptor'],
function(raptor, require) {
"use strict";
var ExpressionParser = require('raptor/templating/compiler/ExpressionParser'),
TextNode = require('raptor/templating/compiler/TextNode'),
WriteNode = require('raptor/templating/taglibs/core/WriteNode'),
ScriptletNode = require('raptor/templating/taglibs/core/ScriptletNode');
return {
process: function(node, compiler) {
if (node.parentNode && node.parentNode.parseBodyText === false) {
return; //Don't try to parse expressions
}
var parts = [];
ExpressionParser.parse(
node.text,
{
text: function(text, escapeXml) {
parts.push({text: text, escapeXml: escapeXml});
},
expression: function(expression, escapeXml) {
parts.push({expression: expression, escapeXml: escapeXml});
},
scriptlet: function(scriptlet) {
parts.push({scriptlet: scriptlet});
},
error: function(message) {
node.addError(message);
}
},
this);
if (parts.length > 0) {
var startIndex = 0;
if (parts[0].text) {
node.setText(parts[0].text); //Update this text node to match first text part and we'll add the remaining
node.setEscapeXml(parts[0].escapeXml !== false);
startIndex = 1;
}
else {
node.text = ''; //The first part is an expression so we'll just zero out this text node
startIndex = 0;
}
var newNodes = [];
for (var i=startIndex, part, newNode; i<parts.length; i++) {
part = parts[i];
newNode = null;
if (part.hasOwnProperty('text')) {
newNode = new TextNode(part.text, part.escapeXml !== false);
newNode.setTransformerApplied(this); //We shouldn't reprocess the new text node
}
else if (part.hasOwnProperty('expression')) {
newNode = new WriteNode({expression: part.expression, escapeXml: part.escapeXml !== false});
}
else if (part.hasOwnProperty('scriptlet')) {
newNode = new ScriptletNode(part.scriptlet);
}
if (newNode) {
newNode.setPosition(node.getPosition());
newNodes.push(newNode);
}
}
if (newNodes.length) {
node.parentNode.insertAfter(newNodes, node);
}
}
}
};
});

View File

@ -0,0 +1,85 @@
/*
* 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/taglibs/core/DefNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var strings = require('raptor/strings'),
funcDefRegExp = /^([A-Za-z_][A-Za-z0-9_]*)\(((?:[A-Za-z_][A-Za-z0-9_]*,\s*)*[A-Za-z_][A-Za-z0-9_]*)?\)$/;
var DefNode = function(props) {
DefNode.superclass.constructor.call(this);
if (props) {
this.setProperties(props);
}
};
DefNode.prototype = {
doGenerateCode: function(template) {
var func = this.getProperty("function");
if (!func) {
this.addError('"function" attribute is required');
this.generateCodeForChildren(template);
return;
}
func = strings.trim(func);
var matches = funcDefRegExp.exec(func);
if (matches) {
var name = matches[1];
var params = matches[2] ? matches[2].split(/\s*,\s*/) : [];
var definedFunctions = template.getAttribute("core:definedFunctions");
if (!definedFunctions) {
definedFunctions = template.setAttribute("core:definedFunctions", {});
}
definedFunctions[name] = {
params: params,
bodyParam: this.getProperty("bodyParam")
};
}
else {
this.addError('Invalid function name of "' + func + '"');
this.generateCodeForChildren(template);
return;
}
template
.statement('function ' + func + ' {')
.indent(function() {
template
.line('return context.c(function() {')
.indent(function() {
this.generateCodeForChildren(template);
}, this)
.line('});');
}, this)
.line('}');
}
};
return DefNode;
});

View File

@ -0,0 +1,52 @@
/*
* 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/taglibs/core/ElseIfNode',
'raptor/templating/compiler/ElementNode',
['raptor'],
function(raptor, require) {
"use strict";
var ElseIfNode = function(props) {
ElseIfNode.superclass.constructor.call(this, "http://raptorjs.org/templates/core", "else-if", "c");
if (props) {
this.setProperties(props);
}
};
ElseIfNode.prototype = {
doGenerateCode: function(template) {
var test = this.getProperty("test");
if (!test) {
this.addError('"test" attribute is required');
return;
}
template
.line('else if (' + test + ') {')
.indent(function() {
this.generateCodeForChildren(template);
}, this)
.line('}');
}
};
return ElseIfNode;
});

View File

@ -0,0 +1,52 @@
/*
* 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/taglibs/core/ElseNode',
'raptor/templating/compiler/ElementNode',
['raptor'],
function(raptor, require) {
"use strict";
var strings = require('raptor/strings');
var ElseNode = function(props) {
ElseNode.superclass.constructor.call(this, "else", "http://raptorjs.org/templates/core", "c");
if (props) {
this.setProperties(props);
}
};
ElseNode.prototype = {
doGenerateCode: function(template) {
if (this.valid == null) {
return; //Don't generate code for an invalid else
}
template
.line('else {')
.indent(function() {
this.generateCodeForChildren(template);
}, this)
.line('}');
}
};
return ElseNode;
});

View File

@ -0,0 +1,68 @@
/*
* 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/taglibs/core/ElseTagTransformer",
['raptor'],
function(raptor, require) {
"use strict";
var strings = require('raptor/strings');
return {
process: function(node, compiler) {
var curNode = node.previousSibling,
matchingNode,
IfNode = compiler.getNodeClass('http://raptorjs.org/templates/core', 'if'),
ElseIfNode = compiler.getNodeClass('http://raptorjs.org/templates/core', 'else-if'),
whitespaceNodes = [];
while (curNode) {
if (curNode.getNodeClass() === ElseIfNode || curNode.getNodeClass() === IfNode) {
matchingNode = curNode;
break;
}
else if (curNode.isTextNode()) {
if (!strings.isEmpty(curNode.getText())) {
node.addError('Static text "' + strings.trim(curNode.getText()) + '" is not allowed before ' + node.toString() + " tag.");
return;
}
else {
whitespaceNodes.push(curNode);
}
}
else {
node.addError(curNode + ' is not allowed before ' + node.toString() + " tag.");
return;
}
curNode = curNode.previousSibling;
}
if (!matchingNode) {
node.addError('<c:if> or <c:else-if> node not found immediately before ' + node.toString() + " tag.");
return;
}
whitespaceNodes.forEach(function(whitespaceNode) {
whitespaceNode.parentNode.removeChild(whitespaceNode);
});
matchingNode.hasElse = true;
node.valid = true;
}
};
});

171
lib/taglibs/core/ForNode.js Normal file
View File

@ -0,0 +1,171 @@
/*
* 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/taglibs/core/ForNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var forEachRegEx = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+in\s+(.+)$/,
forEachPropRegEx = /^\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*,\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)\s+in\s+(.+)$/,
stringify = require("raptor/json/stringify").stringify,
parseForEach = function(value) {
var match = value.match(forEachRegEx);
if (match) {
return {
"var": match[1],
"in": match[2]
};
}
else {
match = value.match(forEachPropRegEx);
if (!match) {
throw new Error('Invalid each attribute of "' + value + '"');
}
return {
"nameVar": match[1],
"valueVar": match[2],
"in": match[3]
};
}
};
var ForNode = function(props) {
ForNode.superclass.constructor.call(this);
if (props) {
this.setProperties(props);
}
};
ForNode.prototype = {
doGenerateCode: function(template) {
var each = this.getProperty("each"),
eachProp = false,
separator = this.getProperty("separator"),
statusVar = this.getProperty("statusVar") || this.getProperty("varStatus");
if (!each) {
this.addError('"each" attribute is required');
this.generateCodeForChildren(template);
return;
}
var parts;
try
{
parts = parseForEach(each);
}
catch(e) {
this.addError(e.message);
this.generateCodeForChildren(template);
return;
}
var items = template.makeExpression(parts["in"]),
varName = parts["var"],
nameVarName = parts.nameVar,
valueVarName = parts.valueVar;
if (nameVarName) {
if (separator) {
this.addError('Separator is not supported when looping over properties');
this.generateCodeForChildren(template);
return;
}
if (statusVar) {
this.addError('Loop status variable not supported when looping over properties');
this.generateCodeForChildren(template);
return;
}
}
if (separator && !statusVar) {
statusVar = "__loop";
}
var funcName;
var forEachParams;
if (statusVar) {
forEachParams = [varName, statusVar];
funcName = template.getStaticHelperFunction("forEachWithStatusVar", "fv");
template
.statement(funcName + '(' + items + ', function(' + forEachParams.join(",") + ') {')
.indent(function() {
this.generateCodeForChildren(template);
if (separator) {
template
.statement("if (!" + statusVar + ".isLast()) {")
.indent(function() {
template.write(template.isExpression(separator) ? separator.getExpression() : stringify(separator));
}, this)
.line('}');
}
}, this)
.line('});');
}
else {
if (this.getProperty('forLoop') === true) {
forEachParams = ["__array", "__index", "__length", varName];
template
.statement(template.getStaticHelperFunction("forLoop", "fl") + '(' + items + ', function(' + forEachParams.join(",") + ') {')
.indent(function() {
template
.statement('for (;__index<__length;__index++) {')
.indent(function() {
template.statement(varName + '=__array[__index];');
this.generateCodeForChildren(template);
}, this)
.line('}');
this.generateCodeForChildren(template);
}, this)
.line('});');
}
else {
forEachParams = nameVarName ? [nameVarName, valueVarName] : [varName];
funcName = nameVarName ? template.getStaticHelperFunction("forEachProp", "fp") :
template.getStaticHelperFunction("forEach", "f");
template
.statement(funcName + '(' + items + ', function(' + forEachParams.join(",") + ') {')
.indent(function() {
this.generateCodeForChildren(template);
}, this)
.line('});');
}
}
}
};
return ForNode;
});

View File

@ -0,0 +1,50 @@
/*
* 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/taglibs/core/IfNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var IfNode = function(props) {
IfNode.superclass.constructor.call(this);
if (props) {
this.setProperties(props);
}
};
IfNode.prototype = {
doGenerateCode: function(template) {
var test = this.getProperty("test");
if (!test) {
this.addError('"test" attribute is required');
}
template
.statement('if (' + test + ') {')
.indent(function() {
this.generateCodeForChildren(template);
}, this)
.line('}');
}
};
return IfNode;
});

View File

@ -0,0 +1,103 @@
/*
* 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/taglibs/core/IncludeNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var stringify = require('raptor/json/stringify'),
extend = raptor.extend;
var IncludeNode = function(props) {
IncludeNode.superclass.constructor.call(this);
if (props) {
this.setProperties(props);
}
};
IncludeNode.convertNode = function(node, template) {
extend(node, IncludeNode.prototype);
IncludeNode.call(node);
node.setProperty("template", stringify(template));
};
IncludeNode.prototype = {
doGenerateCode: function(template) {
var templateName = this.getProperty("template"),
templateData = this.getProperty("templateData") || this.getProperty("template-data"),
resourcePath,
_this = this;
if (templateName) {
this.removeProperty("template");
var dataExpression;
if (templateData) {
dataExpression = templateData;
}
else {
dataExpression = {
toString: function() {
var propParts = [];
_this.forEachPropertyNS('', function(name, value) {
name = name.replace(/-([a-z])/g, function(match, lower) {
return lower.toUpperCase();
});
propParts.push(stringify(name) + ": " + value);
}, _this);
if (_this.hasChildren()) {
propParts.push(stringify("invokeBody") + ": " + _this.getBodyContentFunctionExpression(template, false));
}
return "{" + propParts.join(", ") + "}";
}
};
}
template.include(templateName, dataExpression);
}
else if ((resourcePath = this.getAttribute("resource"))) {
var isStatic = this.getProperty("static") !== false;
if (isStatic) {
var resource = require('raptor/resources').findResource(resourcePath);
if (!resource.exists()) {
this.addError('"each" attribute is required');
return;
}
template.write(stringify(resource.readAsString()));
}
}
else {
this.addError('"template" or "resource" attribute is required');
}
}
};
return IncludeNode;
});

View File

@ -0,0 +1,133 @@
/*
* 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/taglibs/core/InvokeNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var forEach = raptor.forEach;
var InvokeNode = function(props) {
InvokeNode.superclass.constructor.call(this);
if (props) {
this.setProperties(props);
}
};
InvokeNode.prototype = {
doGenerateCode: function(template) {
var func = this.getProperty("function"),
funcDef,
bodyParam,
definedFunctions = template.getAttribute("core:definedFunctions");
if (!func) {
this.addError('"function" attribute is required');
return;
}
if (func.indexOf('(') === -1) {
funcDef = definedFunctions ? definedFunctions[func] : null;
// if (!funcDef) {
// this.addError('Function with name "' + func + '" not defined using <c:define>.');
// }
var argParts = [];
var validParamsLookup = {};
var params = [];
if (funcDef) {
params = funcDef.params || [];
bodyParam = funcDef.bodyParam;
/*
* Loop over the defined parameters to figure out the names of allowed parameters and add them to a lookup
*/
forEach(params, function(param) {
validParamsLookup[param] = true;
}, this);
}
var bodyArg = null;
if (this.hasChildren()) {
if (!funcDef || !funcDef.bodyParam) {
this.addError('Nested content provided when invoking macro "' + func + '" but defined macro does not support nested content.');
}
else {
bodyArg = this.getBodyContentExpression(template, false);
}
}
/*
* VALIDATION:
* Loop over all of the provided attributes and make sure they are allowed
*/
this.forEachPropertyNS('', function(name, value) {
if (name === 'function') {
return;
}
if (!validParamsLookup[name]) {
this.addError('Parameter with name "' + name + '" not supported for function with name "' + func + '". Allowed parameters: ' + params.join(", "));
}
}, this);
/*
* One more pass to build the argument list
*/
forEach(params, function(param) {
validParamsLookup[param] = true;
if (param === bodyParam) {
argParts.push(bodyArg ? bodyArg : "undefined");
}
else {
var arg = this.getAttribute(param);
if (arg == null) {
argParts.push("undefined");
}
else {
argParts.push(this.getProperty(param));
}
}
}, this);
template.write(func + "(" + argParts.join(",") + ")");
}
else {
var funcName = func.substring(0, func.indexOf('('));
funcDef = definedFunctions ? definedFunctions[funcName] : null;
if (funcDef) {
template.write(func);
}
else {
template.statement(func + ";\n");
}
}
}
};
return InvokeNode;
});

View File

@ -0,0 +1,51 @@
/*
* 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/taglibs/core/OtherwiseNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var strings = require('raptor/strings');
var OtherwiseNode = function(props) {
OtherwiseNode.superclass.constructor.call(this);
if (props) {
this.setProperties(props);
}
};
OtherwiseNode.prototype = {
doGenerateCode: function(template) {
template
.line('else {')
.indent(function() {
this.generateCodeForChildren(template);
}, this)
.line('}');
},
toString: function() {
return "<c:otherwise>";
}
};
return OtherwiseNode;
});

View File

@ -0,0 +1,43 @@
/*
* 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/taglibs/core/ScriptletNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var ScriptletNode = function(code) {
ScriptletNode.superclass.constructor.call(this, 'scriptlet');
this.code = code;
};
ScriptletNode.prototype = {
doGenerateCode: function(template) {
if (this.code) {
template.code(this.code);
}
},
toString: function() {
return '{%' + this.code + '%}';
}
};
return ScriptletNode;
});

View File

@ -0,0 +1,288 @@
/*
* 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/taglibs/core/TagHandlerNode",
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var extend = raptor.extend,
objects = require('raptor/objects'),
forEach = raptor.forEach,
forEachEntry = raptor.forEachEntry,
stringify = require('raptor/json/stringify'),
Expression = require('raptor/templating/compiler/Expression'),
addHandlerVar = function(template, handlerClass) {
var handlerVars = template._handlerVars || (template._handlerVars = {});
var handlerVar = handlerVars[handlerClass];
if (!handlerVar) {
handlerVars[handlerClass] = handlerVar = handlerClass.replace(/[.\-\/]/g, '_');
template.addStaticVar(handlerVar, template.getStaticHelperFunction("getTagHandler", "t") + "(" + stringify(handlerClass) + ")");
}
return handlerVar;
},
getPropsStr = function(props, template) {
var propsArray = [];
if (props) {
forEachEntry(props, function(name, value) {
if (value instanceof Expression) {
var expressionStr;
template.indent(function() {
expressionStr = value.expression.toString();
});
propsArray.push(template.indentStr(1) + stringify(name) + ": " + expressionStr);
}
else if (typeof value === 'string' || typeof value === 'object') {
propsArray.push(template.indentStr(1) + stringify(name) + ": " + stringify(value));
}
else {
propsArray.push(template.indentStr(1) + stringify(name) + ": " + value);
}
});
if (propsArray.length) {
return "{\n" + propsArray.join(',\n') + "\n" + template.indentStr() + "}";
}
else {
return "{}";
}
}
else {
return "{}";
}
};
var TagHandlerNode = function(tag) {
if (!this.nodeType) {
TagHandlerNode.superclass.constructor.call(this);
}
this.tag = tag;
this.dynamicAttributes = null;
this.preInvokeCode = [];
this.postInvokeCode = [];
this.inputExpression = null;
};
TagHandlerNode.convertNode = function(node, tag) {
extend(node, TagHandlerNode.prototype);
TagHandlerNode.call(node, tag);
};
TagHandlerNode.prototype = {
addDynamicAttribute: function(name, value) {
if (!this.dynamicAttributes) {
this.dynamicAttributes = {};
}
this.dynamicAttributes[name] = value;
},
addPreInvokeCode: function(code) {
this.preInvokeCode.push(code);
},
addPostInvokeCode: function(code) {
this.postInvokeCode.push(code);
},
setInputExpression: function(expression) {
this.inputExpression = expression;
},
doGenerateCode: function(template) {
/*
context.t(
handler,
props,
bodyFunc,
dynamicAttributes,
namespacedProps)
*/
///////////////////
var handlerVar = addHandlerVar(template, this.tag.handlerClass);
this.tag.forEachImportedVariable(function(importedVariable) {
this.setProperty(importedVariable.targetProperty, new Expression(importedVariable.expression));
}, this);
var namespacedProps = raptor.extend({}, this.getPropertiesByNS());
delete namespacedProps[''];
var hasNamespacedProps = !objects.isEmpty(namespacedProps);
///////////
var _this = this;
var variableNames = [];
_this.tag.forEachVariable(function(v) {
var varName;
if (v.nameFromAttribute) {
var possibleNameAttributes = v.nameFromAttribute.split(/\s+or\s+|\s*,\s*/i);
for (var i=0,len=possibleNameAttributes.length; i<len; i++) {
var attrName = possibleNameAttributes[i],
keep = false;
if (attrName.endsWith('|keep')) {
keep = true;
attrName = attrName.slice(0, 0-'|keep'.length);
possibleNameAttributes[i] = attrName;
}
varName = this.getAttribute(attrName);
if (varName) {
if (!keep) {
this.removeProperty(attrName);
}
break;
}
}
if (!varName) {
this.addError('Attribute ' + possibleNameAttributes.join(" or ") + ' is required');
varName = "_var"; // Let it continue with errors
}
}
else {
varName = v.name;
if (!varName) {
this.addError('Variable name is required');
varName = "_var"; // Let it continue with errors
}
}
variableNames.push(varName);
}, this);
if (_this.preInvokeCode.length) {
_this.preInvokeCode.forEach(function(code) {
template
.indent()
.code(code)
.code("\n");
});
}
template.contextMethodCall("t", function() {
template
.code("\n")
.indent(function() {
template
.line(handlerVar + ',')
.indent();
if (_this.inputExpression) {
template.code(_this.inputExpression);
}
else {
template.code(getPropsStr(_this.getProperties(), template));
}
if (_this.hasChildren()) {
var bodyParams = [];
forEach(variableNames, function(varName) {
bodyParams.push(varName);
});
template
.code(',\n')
.line("function(" + bodyParams.join(",") + ") {")
.indent(function() {
_this.generateCodeForChildren(template);
})
.indent()
.code('}');
}
else {
if (hasNamespacedProps || _this.dynamicAttributes) {
template
.code(",\n")
.indent().code("null");
}
}
if (_this.dynamicAttributes) {
template
.code(",\n")
.indent().code(getPropsStr(_this.dynamicAttributes, template));
}
else {
if (hasNamespacedProps) {
template
.code(",\n")
.indent().code("null");
}
}
if (hasNamespacedProps) {
template
.code(",\n")
.line("{")
.indent(function() {
var first = true;
forEachEntry(namespacedProps, function(uri, props) {
if (!first) {
template.code(',\n');
}
template.code(template.indentStr() + '"' + uri + '": ' + getPropsStr(props, template));
first = false;
});
})
.indent()
.code("}");
}
});
});
if (_this.postInvokeCode.length) {
_this.postInvokeCode.forEach(function(code) {
template
.indent()
.code(code)
.code("\n");
});
}
}
};
return TagHandlerNode;
});

View File

@ -0,0 +1,106 @@
/*
* 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/taglibs/core/TemplateNode",
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var forEach = raptor.forEach;
var TemplateNode = function(props) {
TemplateNode.superclass.constructor.call(this);
if (props) {
this.setProperties(props);
}
};
TemplateNode.prototype = {
doGenerateCode: function(template) {
var name = this.getProperty("name"),
params = this.getProperty("params");
if (params) {
params = params.split(/(?:\s*,\s*)|(?:\s+)/g);
forEach(params, function(param) {
param = param.trim();
if (param.length) {
template.addVar(param, "data." + param);
}
}, this);
}
else {
params = null;
}
this.forEachProperty(function(uri, name, value) {
if (!uri) {
uri = this.uri;
}
if (name === 'functions' || name === 'importFunctions' || name === 'importHelperFunctions') {
forEach(value.split(/\s*[,;]\s*/g), function(funcName) {
var func = template.compiler.taglibs.getFunction(uri, funcName);
if (!func) {
this.addError('Function with name "' + funcName + '" not found in taglib "' + uri + '"');
}
else {
template.addHelperFunction(func.functionClass, funcName, func.bindToContext === true);
}
}, this);
}
else if (name === 'importHelperObject') {
var varName = value;
if (!template.compiler.taglibs.isTaglib(uri)) {
this.addError('Helper object not found for taglib "' + uri + '". Taglib with URI "' + uri + '" not found.');
}
else {
var helperObject = template.compiler.taglibs.getHelperObject(uri);
if (!helperObject) {
this.addError('Helper object not found for taglib "' + uri + '"');
}
else {
if (helperObject.className) {
template.addVar(varName, "context.o(" + JSON.stringify(helperObject.className) + ")");
}
else if (helperObject.moduleName) {
template.addStaticVar(varName, "require(" + JSON.stringify(helperObject.moduleName) + ")");
}
}
}
}
}, this);
if (name) {
template.setTemplateName(name);
}
else if (!template.getTemplateName()) {
this.addError('The "name" attribute is required for the ' + this.toString() + ' tag or it must be passed in as a compiler option.');
}
this.generateCodeForChildren(template);
}
};
return TemplateNode;
});

View File

@ -0,0 +1,58 @@
/*
* 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/taglibs/core/VarNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var varNameRegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;
var VarNode = function(props) {
VarNode.superclass.constructor.call(this);
if (props) {
this.setProperties(props);
}
};
VarNode.prototype = {
doGenerateCode: function(template) {
var varName = this.getProperty("name"),
value = this.getProperty("value") || this.getProperty("string-value");
if (!varName) {
this.addError('"name" attribute is required');
}
else if (!varNameRegExp.test(varName)) {
this.addError('Invalid variable name of "' + varName + '"');
varName = null;
}
if (varName) {
template.statement('var ' + varName + (value ? "=" + value : "") + ";");
}
}
};
return VarNode;
});

View File

@ -0,0 +1,58 @@
/*
* 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/taglibs/core/WhenNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var WhenNode = function(props) {
WhenNode.superclass.constructor.call(this);
if (props) {
this.setProperties(props);
}
};
WhenNode.prototype = {
doGenerateCode: function(template) {
var test = this.getProperty("test");
if (!test) {
this.addError('"test" attribute is required for ' + this.toString() + " tag.");
}
var ifCode = 'if (' + test + ')';
if (!this.firstWhen) {
template.line('else ' + ifCode + ' {');
}
else {
template.statement(ifCode + ' {');
}
template
.indent(function() {
this.generateCodeForChildren(template);
}, this)
.line('}');
}
};
return WhenNode;
});

View File

@ -0,0 +1,80 @@
/*
* 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/taglibs/core/WithNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var AttributeSplitter = require('raptor/templating/compiler/AttributeSplitter'),
varNameRegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;
var WithNode = function(props) {
WithNode.superclass.constructor.call(this);
if (props) {
this.setProperties(props);
}
};
WithNode.prototype = {
doGenerateCode: function(template) {
var vars = this.getProperty("vars"),
_this = this;
if (!vars) {
this.addError('"vars" attribute is required');
}
var withVars = AttributeSplitter.parse(
vars,
{
"*": {
type: "expression"
}
},
{
ordered: true,
errorHandler: function(message) {
_this.addError('Invalid variable declarations of "' + vars + '". Error: ' + message);
}
});
var varDefs = [];
raptor.forEach(withVars, function(withVar, i) {
if (!varNameRegExp.test(withVar.name)) {
this.addError('Invalid variable name of "' + withVar.name + '" in "' + vars + '"');
}
varDefs.push((i > 0 ? template.indentStr(1) + " " : "") + withVar.name + (withVar.value ? ("=" + withVar.value) : "") + (i < withVars.length - 1 ? ",\n" : ";"));
}, this);
template
.statement('(function() {')
.indent(function() {
template.statement('var ' + varDefs.join(""));
this.generateCodeForChildren(template);
}, this)
.line('}());');
}
};
return WithNode;
});

View File

@ -0,0 +1,72 @@
/*
* 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/taglibs/core/WriteNode',
'raptor/templating/compiler/Node',
['raptor'],
function(raptor, require) {
"use strict";
var escapeXmlAttr = require('raptor/xml/utils').escapeXmlAttr,
EscapeXmlContext = require('raptor/templating/compiler/EscapeXmlContext');
var WriteNode = function(props) {
WriteNode.superclass.constructor.call(this, 'write');
if (props) {
this.setProperties(props);
}
};
WriteNode.prototype = {
doGenerateCode: function(template) {
var expression = this.getExpression(),
escapeXml;
if (this.hasProperty('escapeXml')) {
escapeXml = this.getProperty("escapeXml") !== false;
}
else {
escapeXml = this.getProperty("escape-xml") !== false;
}
if (escapeXml === true) {
if (this.getEscapeXmlContext() === EscapeXmlContext.Attribute) {
expression = template.getStaticHelperFunction("escapeXmlAttr", "xa") + "(" + expression + ")";
}
else {
expression = template.getStaticHelperFunction("escapeXml", "x") + "(" + expression + ")";
}
}
if (expression) {
template.write(expression);
}
},
getExpression: function() {
return this.getProperty("expression") || this.getProperty("value") || this.getAttribute("expression") || this.getAttribute("value");
},
toString: function() {
return '<c:write expression="' + this.getExpression() + '">';
}
};
return WriteNode;
});

View File

@ -0,0 +1,88 @@
{
"dependencies": [
{
"type": "js",
"path": "AssignNode.js"
},
{
"type": "js",
"path": "CoreTagTransformer.js"
},
{
"type": "js",
"path": "ElseTagTransformer.js"
},
{
"type": "js",
"path": "CoreTextTransformer.js"
},
{
"type": "js",
"path": "ForNode.js"
},
{
"type": "js",
"path": "IfNode.js"
},
{
"type": "js",
"path": "ElseIfNode.js"
},
{
"type": "js",
"path": "ElseNode.js"
},
{
"type": "js",
"path": "TagHandlerNode.js"
},
{
"type": "js",
"path": "TemplateNode.js"
},
{
"type": "js",
"path": "WriteNode.js"
},
{
"type": "js",
"path": "InvokeNode.js"
},
{
"type": "js",
"path": "ScriptletNode.js"
},
{
"type": "js",
"path": "ChooseNode.js"
},
{
"type": "js",
"path": "DefNode.js"
},
{
"type": "js",
"path": "IncludeNode.js"
},
{
"type": "js",
"path": "OtherwiseNode.js"
},
{
"type": "js",
"path": "VarNode.js"
},
{
"type": "js",
"path": "WhenNode.js"
},
{
"type": "js",
"path": "WithNode.js"
},
{
"type": "rtld",
"path": "core.rtld"
}
]
}

View File

@ -0,0 +1,13 @@
{
"type": "rtld",
"name": "raptor/templating/taglibs/core",
"version": "1.0",
"description": "RaptorJS Templating Module",
"homepage": "http://raptorjs.org",
"authors": [
{
"name": "Patrick Steele-Idem",
"email": "psteeleidem@ebay.com"
}
]
}

472
lib/taglibs/core/core.rtld Normal file
View File

@ -0,0 +1,472 @@
<raptor-taglib>
<tlib-version>1.0</tlib-version>
<uri>http://raptorjs.org/templates/core</uri>
<short-name>core</short-name>
<prefix>c</prefix>
<tag id="template">
<name>template</name>
<attribute>
<name>name</name>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>params</name>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>imports</name>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<uri>*</uri>
<name>functions</name>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<uri>*</uri>
<name>import-functions</name>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<uri>*</uri>
<name>import-helper-object</name>
<allow-expressions>false</allow-expressions>
</attribute>
<node-class>raptor/templating/taglibs/core/TemplateNode</node-class>
</tag>
<tag extends="template">
<uri></uri>
<name>template</name>
</tag>
<tag>
<name>*</name>
<uri>*</uri> <!-- Register attributes supported by all tags in all namespaces -->
<attribute uri="core">
<name>space</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute uri="core">
<name>whitespace</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute uri="core">
<name>for</name>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute uri="core">
<name>for-each</name>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute uri="core">
<name>if</name>
<type>expression</type>
</attribute>
<attribute uri="core">
<name>else</name>
<type>empty</type>
</attribute>
<attribute uri="core">
<name>else-if</name>
<type>expression</type>
</attribute>
<attribute uri="core">
<name>attrs</name>
<type>expression</type>
</attribute>
<attribute uri="core">
<name>when</name>
<type>expression</type>
</attribute>
<attribute uri="core">
<name>with</name>
<type>custom</type>
</attribute>
<attribute uri="core">
<name>otherwise</name>
<type>empty</type>
</attribute>
<attribute uri="core">
<name>parse-body-text</name>
<type>boolean</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute uri="core">
<name>trim-body-indent</name>
<type>boolean</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute uri="core">
<name>strip</name>
<type>boolean</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute uri="core">
<name>bodyContent</name>
<type>expression</type>
<deprecated>Use "content" attribute instead. This will be removed in the future.</deprecated>
</attribute>
<attribute uri="core">
<name>content</name>
<type>expression</type>
</attribute>
<attribute uri="core">
<name>replace</name>
<type>expression</type>
</attribute>
<attribute uri="core">
<name>input</name>
<type>expression</type>
</attribute>
<!-- Compiler that applies to all tags as well -->
<transformer>
<class-name>raptor/templating/taglibs/core/CoreTagTransformer</class-name>
</transformer>
</tag>
<tag>
<name>for</name>
<node-class>raptor/templating/taglibs/core/ForNode</node-class>
<attribute>
<name>each</name>
<required>false</required>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>separator</name>
<type>string</type>
</attribute>
<attribute>
<name>status-var</name>
<type>identifier</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>varStatus</name>
<type>identifier</type>
<allow-expressions>false</allow-expressions>
<deprecated>Use status-var instead. This will be removed in the future.</deprecated>
</attribute>
<attribute>
<name>for-loop</name>
<type>boolean</type>
<allow-expressions>false</allow-expressions>
</attribute>
</tag>
<tag>
<name>write</name>
<node-class>raptor/templating/taglibs/core/WriteNode</node-class>
<attribute>
<name>value</name>
<required>true</required>
<type>expression</type>
</attribute>
<attribute>
<name>escapeXml</name>
<type>boolean</type>
<allow-expressions>false</allow-expressions>
<deprecated>Use escape-xml instead. This will be removed in the future.</deprecated>
</attribute>
<attribute>
<name>escape-xml</name>
<type>boolean</type>
<allow-expressions>false</allow-expressions>
</attribute>
</tag>
<tag>
<name>if</name>
<node-class>raptor/templating/taglibs/core/IfNode</node-class>
<attribute>
<name>test</name>
<type>expression</type>
</attribute>
</tag>
<tag>
<name>else</name>
<node-class>raptor/templating/taglibs/core/ElseNode</node-class>
<transformer>
<class-name>raptor/templating/taglibs/core/ElseTagTransformer</class-name>
<after>raptor/templating/taglibs/core/CoreTagTransformer</after>
<properties>
<type>else</type>
</properties>
</transformer>
</tag>
<tag>
<name>else-if</name>
<attribute name="test" type="expression"/>
<node-class>raptor/templating/taglibs/core/ElseIfNode</node-class>
<transformer>
<class-name>raptor/templating/taglibs/core/ElseTagTransformer</class-name>
<after>raptor/templating/taglibs/core/CoreTagTransformer</after>
<properties>
<type>else-if</type>
</properties>
</transformer>
</tag>
<tag>
<name>invoke</name>
<node-class>raptor/templating/taglibs/core/InvokeNode</node-class>
<attribute>
<name>function</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
<required>true</required>
</attribute>
<attribute>
<name>*</name>
<uri></uri>
<type>string</type>
<allow-expressions>true</allow-expressions>
</attribute>
</tag>
<tag>
<name>choose</name>
<node-class>raptor/templating/taglibs/core/ChooseNode</node-class>
</tag>
<tag>
<name>when</name>
<node-class>raptor/templating/taglibs/core/WhenNode</node-class>
<attribute>
<name>test</name>
<type>expression</type>
</attribute>
</tag>
<tag>
<name>otherwise</name>
<node-class>raptor/templating/taglibs/core/OtherwiseNode</node-class>
</tag>
<tag>
<name>def</name>
<node-class>raptor/templating/taglibs/core/DefNode</node-class>
<attribute>
<name>function</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>body-param</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
</tag>
<tag>
<name>with</name>
<node-class>raptor/templating/taglibs/core/WithNode</node-class>
<attribute>
<name>vars</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
</tag>
<tag>
<name>include</name>
<node-class>raptor/templating/taglibs/core/IncludeNode</node-class>
<attribute>
<name>template</name>
<type>string</type>
</attribute>
<attribute>
<name>templateData</name>
<type>expression</type>
<deprecated>Use template-data instead. This will be removed in the future.</deprecated>
</attribute>
<attribute>
<name>template-data</name>
<type>expression</type>
</attribute>
<attribute>
<name>resource</name>
<type>string</type>
</attribute>
<attribute>
<name>static</name>
<type>boolean</type>
<allow-expressions>false</allow-expressions>
</attribute>
<dynamic-attributes>true</dynamic-attributes>
</tag>
<tag>
<name>attr</name>
<attribute>
<name>name</name>
<type>string</type>
</attribute>
<attribute>
<name>value</name>
<type>string</type>
</attribute>
<attribute>
<name>uri</name>
<type>string</type>
</attribute>
<attribute>
<name>prefix</name>
<type>string</type>
</attribute>
</tag>
<tag>
<name>var</name>
<node-class>raptor/templating/taglibs/core/VarNode</node-class>
<attribute>
<name>name</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>value</name>
<type>expression</type>
</attribute>
<attribute>
<name>string-value</name>
<type>string</type>
</attribute>
<attribute>
<name>boolean-value</name>
<type>boolean</type>
</attribute>
<attribute>
<name>number-value</name>
<type>number</type>
</attribute>
</tag>
<tag>
<name>assign</name>
<node-class>raptor/templating/taglibs/core/AssignNode</node-class>
<attribute>
<name>var</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>value</name>
<type>expression</type>
</attribute>
</tag>
<text-transformer>
<class-name>raptor/templating/taglibs/core/CoreTextTransformer</class-name>
</text-transformer>
</raptor-taglib>

View File

@ -0,0 +1,15 @@
define(
'raptor/templating/taglibs/html/CommentTag',
function(require, exports, module) {
"use strict";
return {
process: function(input, context) {
context.write('<!--');
if (input.invokeBody) {
input.invokeBody();
}
context.write('-->');
}
};
});

View File

@ -0,0 +1,60 @@
/*
* 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/taglibs/html/DocTypeNode',
'raptor/templating/compiler/ElementNode',
['raptor'],
function(raptor, require) {
"use strict";
var ExpressionParser = require('raptor/templating/compiler/ExpressionParser');
var DocTypeNode = function(props) {
DocTypeNode.superclass.constructor.call(this);
if (props) {
this.setProperties(props);
}
};
DocTypeNode.prototype = {
doGenerateCode: function(template) {
var doctype = this.getAttribute("value") || this.getProperty("value");
template.text("<!DOCTYPE ");
ExpressionParser.parse(
doctype,
{
text: function(text, escapeXml) {
template.text(text);
},
expression: function(expression) {
template.write(expression);
},
error: function(message) {
this.addError('Invalid doctype: "' + doctype + '". ' + message);
}
},
this);
template.text(">");
}
};
return DocTypeNode;
});

View File

@ -0,0 +1,68 @@
/*
* 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/taglibs/html/HtmlTagTransformer",
['raptor'],
function(raptor, require) {
"use strict";
var DocTypeNode = require('raptor/templating/taglibs/html/DocTypeNode');
return {
process: function(node, compiler) {
if (node.isElementNode()) {
var options = compiler.options || {};
var preserveWhitespace = options.preserveWhitespace || {};
var allowSelfClosing = options.allowSelfClosing || {};
var startTagOnly = options.startTagOnly || {};
var lookupKey = node.uri ? node.uri + ":" + node.localName : node.localName;
if (node.isPreserveWhitespace() == null) {
if (preserveWhitespace[lookupKey] === true) {
node.setPreserveWhitespace(true);
}
}
if (allowSelfClosing[lookupKey] === true) {
node.setAllowSelfClosing(true);
}
if (compiler.options.xhtml !== true && startTagOnly[lookupKey] === true) {
node.setStartTagOnly(true);
}
if (node.getQName() === 'html' && node.hasAttributeNS("http://raptorjs.org/templates/html", "doctype")) {
var doctype = node.getAttributeNS("http://raptorjs.org/templates/html", "doctype");
var docTypeNode = new DocTypeNode({
value: doctype,
pos: node.getPosition()
});
node.parentNode.insertBefore(docTypeNode, node);
node.removeAttributeNS("http://raptorjs.org/templates/html", "doctype");
}
}
}
};
});

View File

@ -0,0 +1,16 @@
{
"dependencies": [
{
"type": "js",
"path": "HtmlTagTransformer.js"
},
{
"type": "js",
"path": "DocTypeNode.js"
},
{
"type": "rtld",
"path": "html.rtld"
}
]
}

View File

@ -0,0 +1,13 @@
{
"type": "rtld",
"name": "raptor/templating/taglibs/html",
"version": "1.0",
"description": "RaptorJS Templating Module",
"homepage": "http://raptorjs.org",
"authors": [
{
"name": "Patrick Steele-Idem",
"email": "psteeleidem@ebay.com"
}
]
}

View File

@ -0,0 +1,54 @@
<raptor-taglib>
<tlib-version>1.0</tlib-version>
<short-name>html</short-name>
<uri>http://raptorjs.org/templates/html</uri>
<tag>
<name>pre</name>
<uri></uri> <!-- Register attributes supported by all tags in all namespaces -->
<!-- Compiler that applies to all tags as well -->
<transformer>
<class-name>raptor/templating/taglibs/html/HtmlTagTransformer</class-name>
</transformer>
</tag>
<tag>
<name>html</name>
<uri></uri> <!-- Register attributes supported by all tags in all namespaces -->
<attribute name="doctype" type="string"/>
<!-- Compiler that applies to all tags as well -->
<transformer>
<class-name>raptor/templating/taglibs/html/HtmlTagTransformer</class-name>
</transformer>
</tag>
<tag>
<name>doctype</name>
<attribute name="value" type="custom"/>
<node-class>raptor/templating/taglibs/html/DocTypeNode</node-class>
</tag>
<tag>
<name>*</name>
<uri>*</uri> <!-- Register attributes supported by all tags in all namespaces -->
<!-- Compiler that applies to all tags as well -->
<transformer>
<class-name>raptor/templating/taglibs/html/HtmlTagTransformer</class-name>
</transformer>
</tag>
<tag>
<name>comment</name>
<handler-class>raptor/templating/taglibs/html/CommentTag</handler-class>
</tag>
</raptor-taglib>

View File

@ -0,0 +1,25 @@
define(
'raptor/templating/taglibs/layout/PlaceholderTag',
function(require) {
return {
render: function(input, context) {
var content = input.content[input.name];
if (content) {
if (content.value) {
context.write(content.value);
}
else if (content.invokeBody) {
content.invokeBody();
}
}
else {
if (input.invokeBody) {
input.invokeBody();
}
}
}
};
});

View File

@ -0,0 +1,9 @@
define(
'raptor/templating/taglibs/layout/PutTag',
function(require) {
return {
render: function(input, context) {
input._layout.handlePutTag(input);
}
};
});

View File

@ -0,0 +1,31 @@
define(
'raptor/templating/taglibs/layout/UseTag',
function(require) {
var templating = require('raptor/templating');
var raptor = require('raptor');
return {
render: function(input, context) {
var content = {};
input.invokeBody({
handlePutTag: function(putTag) {
content[putTag.into] = putTag;
}
});
var viewModel = raptor.extend(
input.dynamicAttributes || {},
{
layoutContent: content
});
templating.render(
input.template,
viewModel,
context);
}
};
});

View File

@ -0,0 +1,22 @@
<raptor-taglib>
<tlib-version>1.0</tlib-version>
<short-name>layout</short-name>
<uri>http://raptorjs.org/templates/layout</uri>
<tag name="use" renderer="raptor/templating/taglibs/layout/UseTag" dynamic-attributes="true" dynamic-attributes-remove-dashes="true">
<attribute name="template"/>
<variable name="_layout"/>
</tag>
<tag name="put" renderer="raptor/templating/taglibs/layout/PutTag">
<attribute name="into"/>
<attribute name="value"/>
<import-variable name="_layout"/>
</tag>
<tag name="placeholder" renderer="raptor/templating/taglibs/layout/PlaceholderTag">
<attribute name="name"/>
<import-variable expression="data.layoutContent" target-property="content"/>
</tag>
</raptor-taglib>

View File

@ -0,0 +1,7 @@
{
"dependencies": [
"UseTag.js",
"PutTag.js",
"PlaceholderTag.js"
]
}

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "raptor-templates",
"version": "0.1.0-SNAPSHOT",
"description": "Raptor Templates",
"keywords": [
"templating",
"template",
"async",
"streaming"
],
"repository": {
"type": "git",
"url": "https://github.com/raptorjs/raptor-templates.git"
},
"scripts": {
"test": "node_modules/.bin/mocha --ui bdd --reporter spec ./test"
},
"author": "Patrick Steele-Idem <psteeleidem@ebay.com>",
"maintainers": ["Patrick Steele-Idem <psteeleidem@ebay.com>"],
"dependencies": {
"raptor-logging": "~0.1.0-SNAPSHOT"
},
"devDependencies": {
"mocha": "~1.15.1",
"chai": "~1.8.1"
},
"license": "Apache License v2.0",
"bin": {},
"main": "lib/index.js",
"publishConfig": {
"registry": "https://registry.npmjs.org/"
}
}

64
test/.jshintrc Normal file
View File

@ -0,0 +1,64 @@
{
"predef": [
"jasmine",
"spyOn",
"it",
"xit",
"console",
"describe",
"xdescribe",
"beforeEach",
"before",
"after",
"waits",
"waitsFor",
"runs",
"raptor",
"$rset",
"$radd",
"$rget",
"$renv",
"$rwidgets",
"$",
"dust",
"__rhinoHelpers",
"Packages",
"JavaAdapter",
"unescape"
],
"globals": {
"define": true,
"require": true
},
"node" : true,
"es5" : false,
"browser" : true,
"boss" : false,
"curly": false,
"debug": false,
"devel": false,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": true,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": true,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": false,
"white": false,
"eqeqeq": false,
"latedef": true,
"unused": "vars",
/* Relaxing options: */
"eqnull": true
}

View File

@ -0,0 +1,29 @@
'use strict';
var chai = require('chai');
chai.Assertion.includeStack = true;
require('chai').should();
var expect = require('chai').expect;
var path = require('path');
describe('raptor-templates' , function() {
beforeEach(function(done) {
for (var k in require.cache) {
if (require.cache.hasOwnProperty(k)) {
delete require.cache[k];
}
}
require('raptor-logging').configureLoggers({
'raptor-templates': 'INFO'
});
done();
});
it('should do something', function(done) {
done();
});
});