Dynamic attribute support improved and other improvements

This commit is contained in:
Patrick Steele-Idem 2014-02-05 12:15:50 -07:00
parent f599e0e7e0
commit f8578b94db
48 changed files with 340 additions and 438 deletions

View File

@ -23,7 +23,7 @@ var XML_URI_ALT = 'http://www.w3.org/XML/1998/namespace';
var ExpressionParser = require('./ExpressionParser');
var forEachEntry = require('raptor-util').forEachEntry;
function ElementNode(localName, uri, prefix) {
function ElementNode(localName, namespace, prefix) {
ElementNode.$super.call(this, 'element');
if (!this._elementNode) {
this._elementNode = true;
@ -31,7 +31,7 @@ function ElementNode(localName, uri, prefix) {
this.attributesByNS = {};
this.prefix = prefix;
this.localName = this.tagName = localName;
this.uri = uri;
this.namespace = namespace;
this.allowSelfClosing = false;
this.startTagOnly = false;
}
@ -54,7 +54,7 @@ ElementNode.prototype = {
},
getAllAttributes: function () {
var allAttrs = [];
forEachEntry(this.attributesByNS, function (uri, attrs) {
forEachEntry(this.attributesByNS, function (namespace, attrs) {
forEachEntry(attrs, function (name, attr) {
allAttrs.push(attr);
});
@ -63,7 +63,7 @@ ElementNode.prototype = {
},
forEachAttributeAnyNS: function (callback, thisObj) {
var attributes = [];
forEachEntry(this.attributesByNS, function (uri, attrs) {
forEachEntry(this.attributesByNS, function (namespace, attrs) {
forEachEntry(attrs, function (name, attr) {
attributes.push(attr);
});
@ -71,8 +71,8 @@ ElementNode.prototype = {
attributes.forEach(callback, thisObj);
},
forEachAttributeNS: function (uri, callback, thisObj) {
var attrs = this.attributesByNS[uri || ''];
forEachAttributeNS: function (namespace, callback, thisObj) {
var attrs = this.attributesByNS[namespace || ''];
if (attrs) {
forEachEntry(attrs, function (name, attr) {
callback.call(thisObj, attr);
@ -89,24 +89,24 @@ ElementNode.prototype = {
getAttribute: function (name) {
return this.getAttributeNS(null, name);
},
getAttributeNS: function (uri, localName) {
var attrNS = this.attributesByNS[uri || ''];
getAttributeNS: function (namespace, localName) {
var attrNS = this.attributesByNS[namespace || ''];
var attr = attrNS ? attrNS[localName] : undefined;
return attr ? attr.value : undefined;
},
setAttribute: function (localName, value, escapeXml) {
this.setAttributeNS(null, localName, value, null, escapeXml);
},
setAttributeNS: function (uri, localName, value, prefix, escapeXml) {
var attrNS = this.attributesByNS[uri || ''] || (this.attributesByNS[uri || ''] = {});
setAttributeNS: function (namespace, localName, value, prefix, escapeXml) {
var attrNS = this.attributesByNS[namespace || ''] || (this.attributesByNS[namespace || ''] = {});
attrNS[localName] = {
localName: localName,
value: value,
prefix: prefix,
uri: uri,
namespace: namespace,
escapeXml: escapeXml,
qName: prefix ? prefix + ':' + localName : localName,
name: uri ? uri + ':' + localName : localName,
name: namespace ? namespace + ':' + localName : localName,
toString: function () {
return this.name;
}
@ -118,17 +118,17 @@ ElementNode.prototype = {
removeAttribute: function (localName) {
this.removeAttributeNS(null, localName);
},
removeAttributeNS: function (uri, localName) {
var attrNS = this.attributesByNS[uri || ''] || (this.attributesByNS[uri || ''] = {});
removeAttributeNS: function (namespace, localName) {
var attrNS = this.attributesByNS[namespace || ''] || (this.attributesByNS[namespace || ''] = {});
if (attrNS) {
delete attrNS[localName];
if (objects.isEmpty(attrNS)) {
delete this.attributesByNS[uri || ''];
delete this.attributesByNS[namespace || ''];
}
}
},
removeAttributesNS: function (uri) {
delete this.attributesByNS[uri || ''];
removeAttributesNS: function (namespace) {
delete this.attributesByNS[namespace || ''];
},
isPreserveWhitespace: function () {
var preserveSpace = ElementNode.$super.prototype.isPreserveWhitespace.call(this);
@ -147,14 +147,14 @@ ElementNode.prototype = {
hasAttributes: function () {
return this.hasAttributesNS('');
},
hasAttributesNS: function (uri) {
return this.attributesByNS[uri || ''] !== undefined;
hasAttributesNS: function (namespace) {
return this.attributesByNS[namespace || ''] !== undefined;
},
hasAttribute: function (localName) {
return this.hasAttributeNS('', localName);
},
hasAttributeNS: function (uri, localName) {
var attrsNS = this.attributesByNS[uri || ''];
hasAttributeNS: function (namespace, localName) {
var attrsNS = this.attributesByNS[namespace || ''];
return attrsNS ? attrsNS.hasOwnProperty(localName) : false;
},
removePreserveSpaceAttr: function () {
@ -179,8 +179,8 @@ ElementNode.prototype = {
template.text('<' + name);
this.forEachAttributeAnyNS(function (attr) {
var prefix = attr.prefix;
if (!prefix && attr.uri) {
prefix = this.resolveNamespacePrefix(attr.uri);
if (!prefix && attr.namespace) {
prefix = this.resolveNamespacePrefix(attr.namespace);
}
if (prefix) {
name = prefix + (attr.localName ? ':' + attr.localName : '');

View File

@ -1,104 +0,0 @@
/*
* 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
*/
var importRegExp = /^(?:(\*|(?:(?:@?[A-Za-z0-9_\-]+\s*,\s*)*@?[A-Za-z0-9_\-]+))\s+from\s+)?([^ ]*)(?:\s+as\s+([A-Za-z0-9_\-]+))?$/;
function getImported(lookup, localName, imports) {
if (lookup[localName] != null) {
return lookup[localName];
}
var prefixEnd = localName.indexOf('-');
var prefix;
var namespace;
var name;
if (prefixEnd != -1) {
prefix = localName.substring(0, prefixEnd);
name = localName.substring(prefixEnd + 1);
namespace = imports._prefixes[prefix];
if (namespace) {
return {
namespace: namespace,
name: name,
prefix: prefix
};
}
}
return null;
}
function Imports(taglibs, importsStr) {
this._tagImports = {};
this._attrImports = {};
this._prefixes = {};
var parts = importsStr.trim().split(/\s*;\s*/);
parts.forEach(function (part) {
if (!part) {
//Skip empty strings
return;
}
var match = part.match(importRegExp), imports, importsLookup = {}, from, as;
if (!match) {
throw new Error('Invalid import: "' + part + '"');
} else {
imports = match[1];
from = match[2];
as = match[3];
if (!as) {
as = from;
}
}
this._prefixes[as] = from;
if (imports) {
imports.split(/\s*,\s*/).forEach(function (importedTagName) {
importsLookup[importedTagName] = true;
});
}
taglibs.forEachTag(from, function (tag, taglib) {
if (tag.namespace === from && (importsLookup['*'] || importsLookup[tag.name])) {
/*
* Import tags with a URI that matches the taglib URI
*/
this._tagImports[tag.name] = {
namespace: 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.namespace !== from && (importsLookup['*'] || importsLookup['@' + attr.name])) {
this._attrImports[attr.name] = {
namespace: 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);
}
};
module.exports = Imports;

View File

@ -71,92 +71,92 @@ Node.prototype = {
setProperty: function (name, value) {
this.setPropertyNS(null, name, value);
},
setPropertyNS: function (uri, name, value) {
if (!uri) {
uri = '';
setPropertyNS: function (namespace, name, value) {
if (!namespace) {
namespace = '';
}
var namespacedProps = this.properties[uri];
var namespacedProps = this.properties[namespace];
if (!namespacedProps) {
namespacedProps = this.properties[uri] = {};
namespacedProps = this.properties[namespace] = {};
}
namespacedProps[name] = value;
},
setProperties: function (props) {
this.setPropertiesNS(null, props);
},
setPropertiesNS: function (uri, props) {
if (!uri) {
uri = '';
setPropertiesNS: function (namespace, props) {
if (!namespace) {
namespace = '';
}
forEachEntry(props, function (name, value) {
this.setPropertyNS(uri, name, value);
this.setPropertyNS(namespace, name, value);
}, this);
},
getPropertyNamespaces: function () {
return Object.keys(this.properties);
},
getProperties: function (uri) {
getProperties: function (namespace) {
return this.getPropertiesNS(null);
},
hasProperty: function (name) {
return this.hasPropertyNS('', name);
},
hasPropertyNS: function (uri, name) {
if (!uri) {
uri = '';
hasPropertyNS: function (namespace, name) {
if (!namespace) {
namespace = '';
}
var namespaceProps = this.properties[uri];
var namespaceProps = this.properties[namespace];
return namespaceProps.hasOwnProperty(name);
},
getPropertiesByNS: function () {
return this.properties;
},
getPropertiesNS: function (uri) {
if (!uri) {
uri = '';
getPropertiesNS: function (namespace) {
if (!namespace) {
namespace = '';
}
return this.properties[uri];
return this.properties[namespace];
},
forEachProperty: function (callback, thisObj) {
forEachEntry(this.properties, function (uri, properties) {
forEachEntry(this.properties, function (namespace, properties) {
forEachEntry(properties, function (name, value) {
callback.call(thisObj, uri, name, value);
callback.call(thisObj, namespace, name, value);
}, this);
}, this);
},
getProperty: function (name) {
return this.getPropertyNS(null, name);
},
getPropertyNS: function (uri, name) {
if (!uri) {
uri = '';
getPropertyNS: function (namespace, name) {
if (!namespace) {
namespace = '';
}
var namespaceProps = this.properties[uri];
var namespaceProps = this.properties[namespace];
return namespaceProps ? namespaceProps[name] : undefined;
},
removeProperty: function (name) {
this.removePropertyNS('', name);
},
removePropertyNS: function (uri, name) {
if (!uri) {
uri = '';
removePropertyNS: function (namespace, name) {
if (!namespace) {
namespace = '';
}
var namespaceProps = this.properties[uri];
var namespaceProps = this.properties[namespace];
if (namespaceProps) {
delete namespaceProps[name];
}
if (isEmpty(namespaceProps)) {
delete this.properties[uri];
delete this.properties[namespace];
}
},
removePropertiesNS: function (uri) {
delete this.properties[uri];
removePropertiesNS: function (namespace) {
delete this.properties[namespace];
},
forEachPropertyNS: function (uri, callback, thisObj) {
if (uri == null) {
uri = '';
forEachPropertyNS: function (namespace, callback, thisObj) {
if (namespace == null) {
namespace = '';
}
var props = this.properties[uri];
var props = this.properties[namespace];
if (props) {
forEachEntry(props, function (name, value) {
callback.call(thisObj, name, value);
@ -506,16 +506,16 @@ Node.prototype = {
}
var existingNamespaceMappings = this.namespaceMappings;
var prefixMappings = this.prefixMappings;
forEachEntry(namespaceMappings, function (prefix, uri) {
existingNamespaceMappings[prefix] = uri;
prefixMappings[uri] = prefix;
forEachEntry(namespaceMappings, function (prefix, namespace) {
existingNamespaceMappings[prefix] = namespace;
prefixMappings[namespace] = prefix;
});
},
hasNamespacePrefix: function (uri) {
return this.prefixMappings.hasOwnProperty(uri);
hasNamespacePrefix: function (namespace) {
return this.prefixMappings.hasOwnProperty(namespace);
},
resolveNamespacePrefix: function (uri) {
var prefix = this.prefixMappings[uri];
resolveNamespacePrefix: function (namespace) {
var prefix = this.prefixMappings[namespace];
return !prefix && this.parentNode ? this.parentNode.resolveNamespacePrefix() : prefix;
},
forEachNamespace: function (callback, thisObj) {

View File

@ -18,7 +18,6 @@ var createError = require('raptor-util').createError;
var sax = require('raptor-xml/sax');
var TextNode = require('./TextNode');
var ElementNode = require('./ElementNode');
var Imports = require('./Imports');
function ParseTreeBuilder() {
}
@ -32,7 +31,6 @@ ParseTreeBuilder.prototype = {
var parentNode = null;
var rootNode = null;
var prevTextNode = null;
var imports;
var parser = sax.createParser({
trim: false,
normalize: false,
@ -63,29 +61,18 @@ ParseTreeBuilder.prototype = {
},
startElement: function (el) {
prevTextNode = null;
var importsAttr;
var importedAttr;
var importedTag;
var elementNode = new ElementNode(el.getLocalName(), el.getNamespaceURI(), el.getPrefix());
elementNode.addNamespaceMappings(el.getNamespaceMappings());
elementNode.pos = parser.getPos();
el.getAttributes().forEach(function (attr) {
if (attr.getLocalName() === 'imports' && !attr.getNamespaceURI()) {
importsAttr = attr.getValue();
}
}, this);
if (parentNode) {
parentNode.appendChild(elementNode);
} else {
rootNode = elementNode;
if (importsAttr) {
imports = new Imports(taglibs, importsAttr);
}
if (el.getLocalName() === 'template' && !el.getNamespaceURI()) {
rootNode.uri = 'core';
rootNode.namespace = 'core';
}
}
@ -93,18 +80,9 @@ ParseTreeBuilder.prototype = {
var attrURI = attr.getNamespaceURI();
var attrLocalName = attr.getLocalName();
var 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 () {

View File

@ -1,5 +1,6 @@
var ok = require('assert').ok;
var createError = require('raptor-util').createError;
var forEachEntry = require('raptor-util').forEachEntry;
function TaglibLookup() {
this.namespaces = {};
@ -175,6 +176,16 @@ TaglibLookup.prototype = {
return attr;
},
forEachTag: function (namespace, callback, thisObj) {
var taglibId = this.resolveNamespace(namespace);
var taglib = this.taglibsById[taglibId];
if (!taglib) {
return;
}
forEachEntry(taglib.tags, function (key, tag) {
callback.call(thisObj, tag, taglib);
});
},
forEachNodeTransformer: function (node, callback, thisObj) {
/*
* Based on the type of node we have to choose how to transform it

View File

@ -176,7 +176,25 @@ TaglibXmlLoader.prototype = {
_type: STRING,
_targetProp: 'version'
},
'alias': {
'uri': {
_type: STRING,
_set: function (taglib, name, value, context) {
taglib.addNamespace(value);
}
},
'namespace': {
_type: STRING,
_set: function (taglib, name, value, context) {
taglib.addNamespace(value);
}
},
'short-name': {
_type: STRING,
_set: function (taglib, name, value, context) {
taglib.addNamespace(value);
}
},
'prefix': {
_type: STRING,
_set: function (taglib, name, value, context) {
taglib.addNamespace(value);
@ -243,11 +261,22 @@ TaglibXmlLoader.prototype = {
},
'node-class': {
_type: STRING,
_targetProp: 'nodeClass'
_set: function (tag, name, path) {
tag.nodeClass = resolvePath(path);
}
},
'dynamic-attributes': {
_type: BOOLEAN,
_targetProp: 'dynamicAttributes'
_set: function(tag, name, isDynamicAttributes) {
if (isDynamicAttributes === true || isDynamicAttributes === 'true') {
var attr = new Attribute();
attr.name = '*';
attr.dynamicAttribute = true;
attr.type = 'string';
tag.addAttribute(attr);
}
}
},
'dynamic-attributes-remove-dashes': {
_type: BOOLEAN,

View File

@ -430,8 +430,8 @@ TemplateBuilder.prototype = {
getErrors: function () {
return this.compiler.getErrors();
},
getNodeClass: function (uri, localName) {
return this.compiler.getNodeClass(uri, localName);
getNodeClass: function (namespace, localName) {
return this.compiler.getNodeClass(namespace, localName);
},
transformTree: function (node) {
this.compiler.transformTree(node, this);

View File

@ -45,6 +45,7 @@ TemplateCompiler.prototype = {
function transformTreeHelper(node) {
try {
_this.taglibs.forEachNodeTransformer(node, function (transformer) {
if (!node.isTransformerApplied(transformer)) {
//Check to make sure a transformer of a certain type is only applied once to a node
node.setTransformerApplied(transformer);

View File

@ -67,6 +67,9 @@ function buildAttribute(attr, attrProps, path) {
invokeHandlers(attrProps, {
type: function(value) {
attr.type = value;
},
targetProperty: function(value) {
attr.targetProperty = value;
}
}, path);
@ -108,12 +111,27 @@ function buildTag(tagObject, path, taglib, dirname) {
var attr = new Taglib.Attribute(namespace, localName);
if (typeof attrProps === 'string') {
attr.type = attrProps;
if (attrProps == null) {
attrProps = {
type: 'string'
};
}
else {
buildAttribute(attr, attrProps, attrName + '@' + path);
else if (typeof attrProps === 'string') {
attrProps = {
type: attrProps
};
}
buildAttribute(attr, attrProps, attrName + '@' + path);
if (localName === '*') {
attr.dynamicAttribute = true;
if (attr.targetProperty === undefined) {
attr.targetProperty = '*';
}
else if (!attr.targetProperty) {
attr.targetProperty = null;
}
}
tag.addAttribute(attr);

View File

@ -82,7 +82,7 @@ exports.update = function(runtime, Context) {
return this;
},
attrs: attrs,
t: function (handler, props, body, dynamicAttributes, namespacedProps) {
t: function (handler, props, body, namespacedProps) {
if (!props) {
props = {};
}
@ -90,9 +90,6 @@ exports.update = function(runtime, Context) {
if (body) {
props.invokeBody = body;
}
if (dynamicAttributes) {
props.dynamicAttributes = dynamicAttributes;
}
if (namespacedProps) {
extend(props, namespacedProps);
}

View File

@ -14,8 +14,8 @@ module.exports = {
}
var argProps = [];
var propsToRemove = [];
node.forEachProperty(function (uri, name, value) {
if (uri === '' && name.startsWith('arg-')) {
node.forEachProperty(function (namespace, name, value) {
if (namespace === '' && name.startsWith('arg-')) {
var argName = name.substring('arg-'.length);
argProps.push(JSON.stringify(argName) + ': ' + value);
propsToRemove.push(name);

View File

@ -3,9 +3,8 @@
<tlib-version>1.0</tlib-version>
<uri>http://raptorjs.org/templates/async</uri>
<short-name>async</short-name>
<prefix>async</prefix>
<namespace>http://raptorjs.org/templates/async</namespace>
<namespace>async</namespace>
<tag>

View File

@ -3,9 +3,8 @@
<tlib-version>1.0</tlib-version>
<uri>http://raptorjs.org/templates/caching</uri>
<short-name>caching</short-name>
<prefix>caching</prefix>
<namespace>http://raptorjs.org/templates/caching</namespace>
<namespace>caching</namespace>
<tag>

View File

@ -49,22 +49,22 @@ CoreTagTransformer.prototype = {
this.findNestedAttrs(node, compiler, template);
var inputAttr;
var forEachNode;
var uri;
var namespace;
var tag;
var nestedTag;
var coreNS = compiler.taglibs.resolveNamespace('core');
function forEachProp(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') {
if (attr.namespace === 'http://www.w3.org/2000/xmlns/' || attr.namespace === 'http://www.w3.org/XML/1998/namespace' || attr.prefix == 'xmlns') {
return; //Skip xmlns attributes
}
var prefix = attr.prefix;
var attrUri = attr.uri;
var attrUri = attr.namespace;
var resolvedAttrNamespace = attrUri ? compiler.taglibs.resolveNamespace(attrUri) : null;
attrUri = attr.prefix && resolvedAttrNamespace === tag.taglib.id ? null : attr.uri;
attrUri = attr.prefix && resolvedAttrNamespace === tag.taglib.id ? null : attr.namespace;
var attrDef = compiler.taglibs.getAttribute(uri, node.localName, attrUri, attr.localName);
var attrDef = compiler.taglibs.getAttribute(namespace, node.localName, attrUri, attr.localName);
var type = attrDef ? attrDef.type || 'string' : 'string';
var taglibIdForTag = compiler.taglibs.resolveNamespaceForTag(tag);
@ -77,8 +77,9 @@ CoreTagTransformer.prototype = {
prefix = '';
}
var isAttrForTaglib = compiler.taglibs.isTaglib(attrUri);
if (!attrDef && (isAttrForTaglib || !tag.dynamicAttributes)) {
if (!attrDef) {
// var isAttrForTaglib = compiler.taglibs.isTaglib(attrUri);
//Tag doesn't allow dynamic attributes
node.addError('The tag "' + tag.name + '" in taglib "' + taglibIdForTag + '" does not support attribute "' + attr + '"');
return;
@ -98,7 +99,9 @@ CoreTagTransformer.prototype = {
}
var propName;
if (attrDef) {
if (attrDef.dynamicAttribute) {
propName = attr.localName;
} else {
if (attrDef.targetProperty) {
propName = attrDef.targetProperty;
} else if (attrDef.preserveName) {
@ -106,8 +109,6 @@ CoreTagTransformer.prototype = {
} else {
propName = removeDashes(attr.localName);
}
} else {
propName = attr.localName;
}
callback.call(thisObj, attrUri, propName, value, prefix, attrDef);
});
@ -117,14 +118,14 @@ CoreTagTransformer.prototype = {
callback.call(thisObj, '', staticProp.name, value, '', staticProp);
});
}
uri = node.uri;
if (!uri && node.isRoot() && node.localName === 'template') {
uri = 'core';
namespace = node.namespace;
if (!namespace && node.isRoot() && node.localName === 'template') {
namespace = 'core';
}
if (node.parentNode) {
var parentUri = node.parentNode.uri;
var parentUri = node.parentNode.namespace;
var parentName = node.parentNode.localName;
nestedTag = compiler.taglibs.getNestedTag(parentUri, parentName, uri, node.localName);
nestedTag = compiler.taglibs.getNestedTag(parentUri, parentName, namespace, node.localName);
if (nestedTag) {
node.setWordWrapEnabled(false);
node.parentNode.setProperty(nestedTag.targetProperty, node.getBodyContentExpression(template));
@ -132,7 +133,7 @@ CoreTagTransformer.prototype = {
return;
}
}
tag = node.tag || compiler.taglibs.getTag(uri, node.localName);
tag = node.tag || compiler.taglibs.getTag(namespace, node.localName);
var coreAttrHandlers = {
'space': function(attr) {
@ -265,7 +266,7 @@ CoreTagTransformer.prototype = {
};
node.forEachAttributeAnyNS(function(attr) {
var attrNS = attr.uri;
var attrNS = attr.namespace;
if (!attrNS) {
return;
}
@ -273,7 +274,7 @@ CoreTagTransformer.prototype = {
attrNS = compiler.taglibs.resolveNamespace(attrNS);
if (attrNS === coreNS) {
node.removeAttributeNS(attr.uri, attr.localName);
node.removeAttributeNS(attr.namespace, attr.localName);
var handler = coreAttrHandlers[attr.localName];
if (!handler) {
node.addError('Unsupported attribute: ' + attr.qName);
@ -301,15 +302,15 @@ CoreTagTransformer.prototype = {
// 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) {
forEachProp(function (namespace, name, value, prefix, attrDef) {
if (attrDef.dynamicAttribute && attrDef.targetProperty) {
if (attrDef.removeDashes === true) {
name = removeDashes(name);
}
node.addDynamicAttribute(prefix ? prefix + ':' + name : name, value);
node.setDynamicAttributesProperty(attrDef.targetProperty);
} else {
node.setPropertyNS(namespace, name, value);
}
});
} else if (tag.nodeClass) {
@ -317,19 +318,19 @@ CoreTagTransformer.prototype = {
extend(node, NodeCompilerClass.prototype);
NodeCompilerClass.call(node);
node.setNodeClass(NodeCompilerClass);
forEachProp(function (uri, name, value) {
node.setPropertyNS(uri, name, value);
forEachProp(function (namespace, name, value) {
node.setPropertyNS(namespace, name, value);
});
}
} else if (uri && compiler.isTaglib(uri)) {
node.addError('Tag ' + node.toString() + ' is not allowed for taglib "' + uri + '"');
} else if (namespace && compiler.isTaglib(namespace)) {
node.addError('Tag ' + node.toString() + ' is not allowed for taglib "' + namespace + '"');
}
},
findNestedAttrs: function (node, compiler, template) {
var coreNS = compiler.taglibs.resolveNamespace('core');
node.forEachChild(function (child) {
if (compiler.taglibs.resolveNamespace(child.uri) === coreNS && child.localName === 'attr') {
if (compiler.taglibs.resolveNamespace(child.namespace) === coreNS && child.localName === 'attr') {
this.handleAttr(child, compiler, template);
}
}, this);
@ -343,7 +344,7 @@ CoreTagTransformer.prototype = {
var hasValue = node.hasAttribute('value');
var attrName = node.getAttribute('name');
var attrValue = node.getAttribute('value');
var attrUri = node.getAttribute('uri') || '';
var attrUri = node.getAttribute('namespace') || '';
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 + '"' : ''));
@ -351,7 +352,7 @@ CoreTagTransformer.prototype = {
}
node.removeAttribute('name');
node.removeAttribute('value');
node.removeAttribute('uri');
node.removeAttribute('namespace');
node.removeAttribute('prefix');
if (node.hasAttributesAnyNS()) {
var invalidAttrs = node.getAllAttributes().map(function (attr) {

View File

@ -15,6 +15,9 @@
*/
'use strict';
var stringify = require('raptor-json/stringify');
var nodePath = require('path');
var fs = require('fs');
var extend = require('raptor-util').extend;
function IncludeNode(props) {
IncludeNode.$super.call(this);
@ -60,12 +63,12 @@ IncludeNode.prototype = {
} 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');
resourcePath = nodePath.resolve(template.dirname, resourcePath);
if (!fs.existsSync(resourcePath)) {
this.addError('Resource not found: ' + resourcePath);
return;
}
template.write(stringify(resource.readAsString()));
template.write(stringify(fs.readFileSync(resourcePath, {encoding: 'utf8'})));
}
} else {
this.addError('"template" or "resource" attribute is required');

View File

@ -33,19 +33,24 @@ function addHandlerVar(template, renderer) {
function getPropsStr(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);
}
template.indent(function () {
forEachEntry(props, function (name, value) {
if (value instanceof Expression) {
var expressionStr;
template.indent(function () {
expressionStr = value.expression.toString();
});
propsArray.push(template.indentStr() + stringify(name) + ': ' + expressionStr);
} else if (typeof value === 'string' || typeof value === 'object') {
propsArray.push(template.indentStr() + stringify(name) + ': ' + stringify(value));
} else {
propsArray.push(template.indentStr() + stringify(name) + ': ' + value);
}
});
});
if (propsArray.length) {
return '{\n' + propsArray.join(',\n') + '\n' + template.indentStr() + '}';
} else {
@ -76,6 +81,9 @@ TagHandlerNode.prototype = {
}
this.dynamicAttributes[name] = value;
},
setDynamicAttributesProperty: function(name) {
this.dynamicAttributesProperty = name;
},
addPreInvokeCode: function (code) {
this.preInvokeCode.push(code);
},
@ -135,12 +143,21 @@ TagHandlerNode.prototype = {
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 {
if (_this.dynamicAttributes) {
template.indent(function() {
_this.getProperties()[_this.dynamicAttributesProperty] = template.makeExpression(getPropsStr(_this.dynamicAttributes, template));
});
}
template.code(getPropsStr(_this.getProperties(), template));
}
if (_this.hasChildren()) {
@ -151,13 +168,6 @@ TagHandlerNode.prototype = {
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');
@ -166,11 +176,11 @@ TagHandlerNode.prototype = {
if (hasNamespacedProps) {
template.code(',\n').line('{').indent(function () {
var first = true;
forEachEntry(namespacedProps, function (uri, props) {
forEachEntry(namespacedProps, function (namespace, props) {
if (!first) {
template.code(',\n');
}
template.code(template.indentStr() + '"' + uri + '": ' + getPropsStr(props, template));
template.code(template.indentStr() + '"' + namespace + '": ' + getPropsStr(props, template));
first = false;
});
}).indent().code('}');

View File

@ -34,38 +34,7 @@ TemplateNode.prototype = {
} else {
params = null;
}
this.forEachProperty(function (uri, name, value) {
if (!uri) {
uri = this.uri;
}
if (name === 'functions' || name === 'importFunctions' || name === 'importHelperFunctions') {
value.split(/\s*[,;]\s*/g).forEach(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);
this.generateCodeForChildren(template);
}
};

View File

@ -36,7 +36,7 @@ WithNode.prototype = {
}
});
var varDefs = [];
raptor.forEach(withVars, function (withVar, i) {
withVars.forEach(function (withVar, i) {
if (!varNameRegExp.test(withVar.name)) {
this.addError('Invalid variable name of "' + withVar.name + '" in "' + vars + '"');
}

View File

@ -3,9 +3,9 @@
<tlib-version>1.0</tlib-version>
<alias>http://raptorjs.org/templates/core</alias>
<alias>core</alias>
<alias>c</alias>
<namespace>http://raptorjs.org/templates/core</namespace>
<namespace>core</namespace>
<namespace>c</namespace>
<tag id="template">

View File

@ -24,7 +24,7 @@ HtmlTagTransformer.prototype = {
var preserveWhitespace = options.preserveWhitespace || {};
var allowSelfClosing = options.allowSelfClosing || {};
var startTagOnly = options.startTagOnly || {};
var lookupKey = node.uri ? node.uri + ':' + node.localName : node.localName;
var lookupKey = node.namespace ? node.namespace + ':' + node.localName : node.localName;
if (node.isPreserveWhitespace() == null) {
if (preserveWhitespace[lookupKey] === true) {
node.setPreserveWhitespace(true);

View File

@ -3,8 +3,8 @@
<tlib-version>1.0</tlib-version>
<alias>http://raptorjs.org/templates/html</alias>
<alias>html</alias>
<namespace>http://raptorjs.org/templates/html</namespace>
<namespace>html</namespace>
<tag>

View File

@ -1,7 +1,7 @@
<raptor-taglib>
<tlib-version>1.0</tlib-version>
<short-name>layout</short-name>
<uri>http://raptorjs.org/templates/layout</uri>
<namespace>layout</namespace>
<namespace>http://raptorjs.org/templates/layout</namespace>
<tag name="use" renderer="raptor/templating/taglibs/layout/UseTag" dynamic-attributes="true" dynamic-attributes-remove-dashes="true">
<attribute name="template"/>

View File

@ -5,7 +5,7 @@ module.exports = {
node.setProperty('templatePath', templatePath);
function convertDependencyTags(parent) {
parent.forEachChild(function (child) {
if (child.isElementNode() && !child.uri) {
if (child.isElementNode() && !child.namespace) {
// Convert unnamespaced element nodes to "DependencyTag" nodes
child.tag = compiler.taglibs.getTag('optimizer', 'dependency');
if (child.localName !== 'dependency') {
@ -29,7 +29,7 @@ module.exports = {
});
}
node.forEachChild(function (child) {
if (!child.uri && (child.tagName === 'dependencies' || child.tagName === 'includes')) {
if (!child.namespace && (child.tagName === 'dependencies' || child.tagName === 'includes')) {
child.tag = compiler.taglibs.getTag('optimizer', 'dependencies');
convertDependencyTags(child);
}

View File

@ -2,8 +2,8 @@
<tlib-version>1.0</tlib-version>
<short-name>optimizer</short-name>
<uri>http://raptorjs.org/templates/optimizer</uri>
<namespace>optimizer</namespace>
<namespace>http://raptorjs.org/templates/optimizer</namespace>
<tag>
<name>page</name>

View File

@ -156,7 +156,7 @@ WidgetsTagTransformer.prototype = {
node.setAttribute('id', template.makeExpression('widget.elId(' + JSON.stringify(widgetElIdAttr) + ')'));
}
}
if (node.localName === 'widget' && node.uri === widgetsNS) {
if (node.localName === 'widget' && node.namespace === widgetsNS) {
if (node.getAttribute('id') != null) {
node.setProperty('scope', template.makeExpression('widget'));
}

View File

@ -57,13 +57,13 @@ describe('raptor-templates/compiler' , function() {
testCompiler('test-project/custom-tag.rhtml');
});
it('should compile a template with <c:invoke>', function() {
testCompiler('test-project/tabs.rhtml');
});
// it.only('should compile a template with <c:invoke>', function() {
// testCompiler('test-project/tabs.rhtml');
// });
it.only('should compile a template with <c:include>', function() {
testCompiler('test-project/test-templates/include.rhtml');
});
// it('should compile a template with <c:include>', function() {
// testCompiler('test-project/test-templates/include.rhtml');
// });
});

View File

@ -59,15 +59,15 @@ function testRender(path, data, done, options) {
describe('raptor-templates' , function() {
beforeEach(function(done) {
for (var k in require.cache) {
if (require.cache.hasOwnProperty(k)) {
delete require.cache[k];
}
}
// for (var k in require.cache) {
// if (require.cache.hasOwnProperty(k)) {
// delete require.cache[k];
// }
// }
require('raptor-logging').configureLoggers({
'raptor-templates': 'INFO'
});
// require('raptor-logging').configureLoggers({
// 'raptor-templates': 'INFO'
// });
done();
});
@ -206,7 +206,7 @@ describe('raptor-templates' , function() {
});
it("should allow for an element to be replaced with the result of an expression", function(done) {
testRender("test-project/test-templates/replace.rhtml", {message: "Hello World!"});
testRender("test-project/test-templates/replace.rhtml", {message: "Hello World!"}, done);
});
it("should allow for includes", function(done) {
@ -221,27 +221,6 @@ describe('raptor-templates' , function() {
testRender("test-project/test-templates/require.rhtml", {}, done);
});
it("should allow for context helper functions", function(done) {
var context = require('raptor/templating').createContext();
context.getAttributes().loggedInUser = {
firstName: "John",
lastName: "Doe"
};
testRender("test-project/test-templates/context-helper-functions-shortname.rhtml", {}, context, done);
testRender("test-project/test-templates/context-helper-functions-uri.rhtml", {}, context, done);
});
it("should allow for template imports", function(done) {
testRender("test-project/test-templates/imports1.rhtml", {showConditionalTab: false}, done);
});
it("should allow for template simple imports", function(done) {
testRender("test-project/test-templates/imports2.rhtml", {showConditionalTab: false}, done);
});
// it("should handle errors correctly", function(done) {
@ -308,9 +287,13 @@ describe('raptor-templates' , function() {
testRender("test-project/test-templates/conditional-attributes.rhtml", {}, done);
});
it("should allow for dynamic attributes", function(done) {
it("should allow for dynamic attributes to be passed to tag renderer using a custom property name", function(done) {
testRender("test-project/test-templates/dynamic-attributes.rhtml", {}, done);
});
it("should allow for dynamic attributes to be passed to tag renderer", function(done) {
testRender("test-project/test-templates/dynamic-attributes2.rhtml", {}, done);
});
// it("should allow for nodes to be converted to expressions", function(done) {
// var ElementNode = require('raptor/templating/compiler/ElementNode');

View File

@ -125,7 +125,7 @@ describe('raptor-templates/compiler/taglibs' , function() {
transformers.push(transformer);
});
expect(transformers.length).to.equal(1);
expect(transformers.length).to.equal(2);
});
it('should lookup tag transformers correctly for namespaced tag with transformer', function() {
@ -139,7 +139,7 @@ describe('raptor-templates/compiler/taglibs' , function() {
transformers.push(transformer);
});
expect(transformers.length).to.equal(1);
expect(transformers.length).to.equal(2);
lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project/transformers'));
@ -148,18 +148,36 @@ describe('raptor-templates/compiler/taglibs' , function() {
transformers.push(transformer);
});
expect(transformers.length).to.equal(2);
expect(transformers[0].name).to.equal('foo');
expect(transformers[1].name).to.equal('CoreTagTransformer');
expect(transformers.length).to.equal(3);
expect(transformers[0].path.indexOf('HtmlTagTransformer')).to.not.equal(-1);
expect(transformers[1].name).to.equal('foo');
expect(transformers[2].name).to.equal('CoreTagTransformer');
transformers = [];
lookup.forEachTagTransformer('transform', 'bar', function(transformer) {
transformers.push(transformer);
});
expect(transformers.length).to.equal(2);
expect(transformers[0].name).to.equal('CoreTagTransformer');
expect(transformers[1].name).to.equal('bar');
expect(transformers.length).to.equal(3);
expect(transformers[0].path.indexOf('HtmlTagTransformer')).to.not.equal(-1);
expect(transformers[1].name).to.equal('CoreTagTransformer');
expect(transformers[2].name).to.equal('bar');
});
it('should lookup tag transformers core tag with custom node', function() {
var transformers = [];
var taglibLookup = require('../compiler/lib/taglib-lookup');
var lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project/nested'));
lookup.forEachTagTransformer('c', 'else', function(transformer) {
transformers.push(transformer);
});
expect(transformers.length).to.equal(3);
// expect(transformers[0].name).to.equal('HTagTransformer');
expect(transformers[1].name).to.equal('CoreTagTransformer');
expect(transformers[2].name).to.equal('ElseTagTransformer');
});
});

View File

@ -0,0 +1,15 @@
exports.render = function(input, context) {
context.write("test: " + input.test + "|");
var dynamicAttributes = input.dynamicAttributes;
if (dynamicAttributes) {
var keys = Object.keys(dynamicAttributes).sort();
var entries = keys.map(function(key) {
return key+"="+dynamicAttributes[key];
});
context.write("dynamic attributes: [" + entries.join(", ") + "]");
}
else {
context.write("dynamic attributes: []");
}
};

View File

@ -0,0 +1,15 @@
exports.render = function(input, context) {
context.write("test: " + input.test + "|");
var dynamicAttributes = input['*'];
if (dynamicAttributes) {
var keys = Object.keys(dynamicAttributes).sort();
var entries = keys.map(function(key) {
return key+"="+dynamicAttributes[key];
});
context.write("dynamic attributes: [" + entries.join(", ") + "]");
}
else {
context.write("dynamic attributes: []");
}
};

View File

@ -22,6 +22,23 @@
"attributes": {
"title": "string"
}
},
"dynamic-attributes": {
"renderer": "./dynamic-attributes-tag.js",
"attributes": {
"test": "string",
"*": {
"type": "string",
"target-property": "dynamicAttributes"
}
}
},
"dynamic-attributes2": {
"renderer": "./dynamic-attributes-tag2.js",
"attributes": {
"test": "string",
"*": "string"
}
}
}
}

View File

@ -1 +1 @@
TBD
&lt;hello>

View File

@ -1 +1 @@
TBD
<div></div><div class="some-class"></div><div></div>

View File

@ -1,13 +0,0 @@
<c:template
xmlns:c="core"
xmlns:test="test"
test:functions="user,isLoggedIn">
<c:if test="isLoggedIn()">
Hello ${user().firstName} ${user().lastName}!
</c:if>
<c:if test="!isLoggedIn()">
You are not logged in.
</c:if>
</c:template>

View File

@ -1,13 +0,0 @@
<c:template
xmlns:c="core"
xmlns:test="http://raptorjs.org/templates/test"
test:functions="user,isLoggedIn">
<c:if test="isLoggedIn()">
Hello ${user().firstName} ${user().lastName}!
</c:if>
<c:if test="!isLoggedIn()">
You are not logged in.
</c:if>
</c:template>

View File

@ -1 +1 @@
TBD
test: Hello|dynamic attributes: [class=my-class, id=myId]

View File

@ -0,0 +1,6 @@
<template
xmlns:c="http://raptorjs.org/templates/core"
xmlns:test="http://raptorjs.org/templates/test">
<test:dynamic-attributes2 test="World" class="my-class" id="myId"/>
</template>

View File

@ -0,0 +1 @@
test: World|dynamic attributes: [class=my-class, id=myId]

View File

@ -1 +1 @@
TBD
A , B , C , <div>C</div>

View File

@ -1,21 +0,0 @@
<template
params="showConditionalTab"
imports="
@if from http://raptorjs.org/templates/core;
* from test;">
<tabs>
<tab title="Tab 1">
Tab 1 content
</tab>
<tab title="Tab 2">
Tab 2 content
</tab>
<tab title="Tab 3" if="showConditionalTab">
Tab 3 content
</tab>
</tabs>
</template>

View File

@ -1,19 +0,0 @@
<template
params="showConditionalTab"
imports="core;test">
<test-tabs>
<test-tab title="Tab 1">
Tab 1 content
</test-tab>
<test-tab title="Tab 2">
Tab 2 content
</test-tab>
<test-tab title="Tab 3" c-if="showConditionalTab">
Tab 3 content
</test-tab>
</test-tabs>
</template>

View File

@ -3,6 +3,6 @@
params="">
BEGIN
<c:include resource="/test-templates/include-resource-target.txt" static="true"/>
<c:include resource="include-resource-target.txt" static="true"/>
END
</c:template>

View File

@ -0,0 +1 @@
BEGIN Hello World! END

View File

@ -1 +1 @@
TBD
<html><head><title>Optimizer: Server Includes</title></head><body>Hello World! <script>$(function() { alert('test'); })</script></body></html>

View File

@ -1 +1 @@
TBD
<span title="Popover Title" data-content="Popover Content">Link Text</span><span title="Popover Title" data-content="Popover Content">Link Text</span>

View File

@ -1 +1 @@
TBD
<div class="over-50"></div><div></div><div class="over-50"></div><div class="#"></div><span class="under;-50\"></span><input type="checked" checked>Hello John! Over 50

View File

@ -1 +1 @@
TBD
Hello JOHN! You have 10 new messages.

View File

@ -0,0 +1 @@
1 7 11<div>1) hello</div><div>2) hello</div><div>3) hello</div>