Checking in progress

This commit is contained in:
Steele-Idem 2014-02-02 12:16:57 -07:00
parent 704d6f9976
commit 6f01b98a5d
14 changed files with 171 additions and 58 deletions

104
compiler/lib/Imports.js Normal file
View File

@ -0,0 +1,104 @@
/*
* 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

@ -18,6 +18,8 @@ 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() {
}
ParseTreeBuilder.parse = function (src, filePath, taglibs) {
@ -40,6 +42,7 @@ ParseTreeBuilder.prototype = {
if (!parentNode) {
return; //Some bad XML parsers allow text after the ending element...
}
if (prevTextNode) {
prevTextNode.text += t;
} else {
@ -63,24 +66,27 @@ ParseTreeBuilder.prototype = {
var importsAttr;
var importedAttr;
var importedTag;
var elementNode = new ElementNode(el.getLocalName(), taglibs.resolveURI(el.getNamespaceURI()), el.getPrefix());
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 = taglibs.getImports(importsAttr);
imports = new Imports(taglibs, importsAttr);
}
}
el.getAttributes().forEach(function (attr) {
var attrURI = taglibs.resolveURI(attr.getNamespaceURI());
var attrURI = attr.getNamespaceURI();
var attrLocalName = attr.getLocalName();
var attrPrefix = attr.getPrefix();
if (!attrURI && imports && (importedAttr = imports.getImportedAttribute(attrLocalName))) {

View File

@ -61,7 +61,7 @@ Taglib.prototype = {
ok(arguments.length === 1, 'Invalid args');
ok(tag.name, '"tag.name" is required');
var key = (tag.namespace || '') + ':' + tag.name;
var key = (tag.namespace == null ? this.id : tag.namespace) + ':' + tag.name;
this.tags[key] = tag;
},
addTextTransformer: function (transformer) {
@ -273,11 +273,11 @@ Taglib.Transformer = function () {
}
Transformer.prototype = {
getInstance: function () {
if (!this.filename) {
if (!this.path) {
throw createError(new Error('Transformer class not defined for tag transformer (tag=' + this.tag + ')'));
}
if (!this.instance) {
var Clazz = require(this.filename);
var Clazz = require(this.path);
if (Clazz.process) {
return Clazz;
}

View File

@ -20,6 +20,10 @@ TaglibLookup.prototype = {
return this.namespaces[namespace];
},
isTaglib: function(namespace) {
return this.namespaces[namespace] != null;
},
addTaglib: function (taglib) {
ok(taglib, '"taglib" is required');
ok(taglib.id, '"taglib.id" expected');

View File

@ -177,28 +177,10 @@ CodeWriter.prototype = {
return this._code.toString();
}
};
function TemplateBuilder(compiler, resource, rootNode) {
this.resource = resource;
function TemplateBuilder(compiler, path, rootNode) {
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-templates/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.path = this.path;
this.options = compiler.options;
this.templateName = null;
this.attributes = {};
@ -212,13 +194,6 @@ function TemplateBuilder(compiler, resource, rootNode) {
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];
@ -398,18 +373,12 @@ TemplateBuilder.prototype = {
getPath: function () {
return this.path;
},
getFilePath: function () {
return this.filePath;
},
getOutput: function () {
if (this.hasErrors()) {
return '';
}
var out = new StringBuilder();
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);

View File

@ -19,14 +19,22 @@ var TemplateBuilder = require('./TemplateBuilder');
var ParseTreeBuilder = require('./ParseTreeBuilder');
var Expression = require('./Expression');
var TypeConverter = require('./TypeConverter');
var taglibLookup = require('./taglib-lookup');
var nodePath = require('path');
function TemplateCompiler(taglibs, options) {
this.taglibs = taglibs;
function TemplateCompiler(path, options) {
this.dirname = nodePath.dirname(path);
this.path = path;
this.taglibs = taglibLookup.buildLookup(this.dirname);
this.options = options || {};
this.errors = [];
}
TemplateCompiler.prototype = {
isTaglib: function(ns) {
return this.taglibs.isTaglib(ns);
},
transformTree: function (rootNode, templateBuilder) {
if (!templateBuilder) {
throw createError(new Error('The templateBuilder argument is required'));
@ -76,8 +84,9 @@ TemplateCompiler.prototype = {
transformTreeHelper(rootNode); //Run the transforms on the tree
} while (this._transformerApplied);
},
compile: function (xmlSrc, filePath, callback, thisObj) {
compile: function (xmlSrc, callback, thisObj) {
var _this = this;
var filePath = this.path;
var rootNode;
var templateBuilder;
function handleErrors() {
@ -135,9 +144,9 @@ TemplateCompiler.prototype = {
isExpression: function (expression) {
return expression instanceof Expression;
},
createTagHandlerNode: function (uri, localName) {
createTagHandlerNode: function (ns, localName) {
var TagHandlerNode = require('../taglibs/core/TagHandlerNode');
var tag = this.taglibs.getTag(uri, localName);
var tag = this.taglibs.getTag(ns, localName);
var tagHandlerNode = new TagHandlerNode(tag);
return tagHandlerNode;
},
@ -156,12 +165,12 @@ TemplateCompiler.prototype = {
getErrors: function () {
return this.errors;
},
getNodeClass: function (uri, localName) {
var tag = this.taglibs.getTag(uri, localName);
getNodeClass: function (ns, localName) {
var tag = this.taglibs.getTag(ns, localName);
if (tag && tag.nodeClass) {
return require(tag.nodeClass);
}
throw createError(new Error('Node class not found for uri "' + uri + '" and localName "' + localName + '"'));
throw createError(new Error('Node class not found for namespace "' + ns + '" and localName "' + localName + '"'));
},
createTag: function () {
var Taglib = require('./Taglib');

View File

@ -34,7 +34,7 @@ var defaultOptions = {
};
extend(exports, {
createCompiler: function (options) {
createCompiler: function (path, options) {
var TemplateCompiler = require('./TemplateCompiler');
//Get a reference to the TemplateCompiler class
if (options) {
@ -47,12 +47,11 @@ extend(exports, {
options = defaultOptions; //Otherwise, no options were provided so use the default options
}
var taglibs = require('./taglibs').getTaglibCollection();
return new TemplateCompiler(taglibs, options);
return new TemplateCompiler(path, options);
},
compile: function (src, path, options) {
return this.createCompiler(options).compile(src, path);
return this.createCompiler(path, options).compile(src);
},
Node: require('./Node'),

View File

@ -13,7 +13,7 @@
"url": "https://github.com/raptorjs/raptor-templates.git"
},
"scripts": {
"test": "node_modules/.bin/mocha --ui bdd --reporter spec ./test"
"test": "node_modules/.bin/mocha --ui bdd --reporter spec ./test"
},
"author": "Patrick Steele-Idem <psteeleidem@ebay.com>",
"maintainers": ["Patrick Steele-Idem <psteeleidem@ebay.com>"],

View File

@ -128,6 +128,7 @@ CoreTagTransformer.prototype = {
}
}
tag = node.tag || compiler.taglibs.getTag(uri, node.localName);
console.log(uri + ':' + node.localName + ' --> ', tag);
if (node.getAttributeNS(coreNS, 'space') === 'preserve' || node.getAttributeNS(coreNS, 'whitespace') === 'preserve') {
node.setPreserveWhitespace(true);
}
@ -296,7 +297,7 @@ CoreTagTransformer.prototype = {
node.setPropertyNS(uri, name, value);
});
}
} else if (uri && template.isTaglib(uri)) {
} else if (uri && compiler.isTaglib(uri)) {
node.addError('Tag ' + node.toString() + ' is not allowed for taglib "' + uri + '"');
}
},

1
test-compiler.sh Executable file
View File

@ -0,0 +1 @@
node_modules/.bin/mocha --ui bdd --reporter spec ./test/compiler-tests.js

1
test-taglib-lookup.sh Executable file
View File

@ -0,0 +1 @@
node_modules/.bin/mocha --ui bdd --reporter spec ./test/taglib-lookup-tests.js

View File

@ -12,13 +12,14 @@ function testCompiler(path) {
var expectedPath = nodePath.join(__dirname, path + '.expected.js');
var actualPath = nodePath.join(__dirname, path + '.actual.js');
var compiler = require('../compiler').createCompiler();
var compiler = require('../compiler').createCompiler(inputPath);
var src = fs.readFileSync(inputPath, {encoding: 'utf8'});
var expected = fs.readFileSync(expectedPath, {encoding: 'utf8'});
var output = compiler.compile(src, path);
var output = compiler.compile(src);
fs.writeFileSync(actualPath, output, {encoding: 'utf8'});
var expected = fs.readFileSync(expectedPath, {encoding: 'utf8'});
if (output !== expected) {
throw new Error('Unexpected output for "' + inputPath + '":\nEXPECTED (' + expectedPath + '):\n---------\n' + expected +
'\n---------\nACTUAL (' + actualPath + '):\n---------\n' + output + '\n---------');

View File

@ -40,6 +40,16 @@ describe('raptor-templates/compiler/taglibs' , function() {
expect(ifTag.namespace).to.equal(null);
});
it('should lookup core template for top-level template', function() {
var taglibLookup = require('../compiler/lib/taglib-lookup');
var lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project'));
// console.log(Object.keys(lookup.tags));
var templateTag = lookup.getTag('c', 'template');
expect(templateTag != null).to.equal(true);
expect(templateTag.name).to.equal('template');
expect(templateTag.namespace).to.equal(null);
});
it('should lookup custom tag for top-level template', function() {
var taglibLookup = require('../compiler/lib/taglib-lookup');
var lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project'));

View File

@ -0,0 +1,8 @@
function create(helpers) {
var empty = helpers.e,
notEmpty = helpers.ne;
return function render(data, context) {
context.w('<test:hello name="world"></test:hello>');
};
}