mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
Checking in progress
This commit is contained in:
parent
704d6f9976
commit
6f01b98a5d
104
compiler/lib/Imports.js
Normal file
104
compiler/lib/Imports.js
Normal 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;
|
||||
@ -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))) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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>"],
|
||||
|
||||
@ -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
1
test-compiler.sh
Executable file
@ -0,0 +1 @@
|
||||
node_modules/.bin/mocha --ui bdd --reporter spec ./test/compiler-tests.js
|
||||
1
test-taglib-lookup.sh
Executable file
1
test-taglib-lookup.sh
Executable file
@ -0,0 +1 @@
|
||||
node_modules/.bin/mocha --ui bdd --reporter spec ./test/taglib-lookup-tests.js
|
||||
@ -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---------');
|
||||
|
||||
@ -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'));
|
||||
|
||||
8
test/test-project/custom-tag.rhtml.actual.js
Normal file
8
test/test-project/custom-tag.rhtml.actual.js
Normal 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>');
|
||||
};
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user