UNPKG

atpl

Version:

A complete and fast template engine fully compatible with twig and similar to jinja with zero dependencies.

231 lines (230 loc) 12 kB
///<reference path='../imports.d.ts'/> "use strict"; var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; var TokenParserContext_1 = require('./TokenParserContext'); var RuntimeContext_1 = require('../runtime/RuntimeContext'); var TokenReader_1 = require('../lexer/TokenReader'); var ExpressionParser_1 = require('./ExpressionParser'); var SandboxPolicy_1 = require('../SandboxPolicy'); var TemplateTokenizer_1 = require('../lexer/TemplateTokenizer'); var ParserNode = require('./ParserNode'); var FlowException = (function (_super) { __extends(FlowException, _super); function FlowException(blockType, templateParser, tokenParserContext, templateTokenReader, expressionTokenReader) { _super.call(this); this.blockType = blockType; this.templateParser = templateParser; this.tokenParserContext = tokenParserContext; this.templateTokenReader = templateTokenReader; this.expressionTokenReader = expressionTokenReader; } return FlowException; }(Error)); exports.FlowException = FlowException; function debug(data) { //console.log(data); } var TemplateParser = (function () { function TemplateParser(templateProvider, languageContext) { this.templateProvider = templateProvider; this.languageContext = languageContext; this.registry = {}; this.registryString = {}; this.sandboxPolicy = new SandboxPolicy_1.SandboxPolicy(); } TemplateParser.prototype.getCache = function () { return this.languageContext.templateConfig.getCache(); }; TemplateParser.prototype.compileAndRenderStringToString = function (content, scope, tokenParserContextCommon) { if (scope === undefined) scope = {}; var runtimeContext = new RuntimeContext_1.RuntimeContext(this, scope, this.languageContext); this.compileAndRenderString(content, runtimeContext, tokenParserContextCommon); return runtimeContext.output; }; TemplateParser.prototype.compileAndRenderToString = function (path, scope, tokenParserContextCommon) { if (scope === undefined) scope = {}; var runtimeContext = new RuntimeContext_1.RuntimeContext(this, scope, this.languageContext); this.compileAndRender(path, runtimeContext, tokenParserContextCommon); return runtimeContext.output; }; TemplateParser.prototype.compileAndRenderString = function (content, runtimeContext, tokenParserContextCommon) { var template = new (this.compileString(content, runtimeContext, tokenParserContextCommon).class)(); template.render(runtimeContext); return template; }; TemplateParser.prototype.compileAndRender = function (path, runtimeContext, tokenParserContextCommon) { var template = new (this.compile(path, runtimeContext, tokenParserContextCommon).class)(); template.render(runtimeContext); return template; }; TemplateParser.prototype.getEvalCodeString = function (content, path, tokenParserContextCommon) { this.path = path; var templateTokenizer = new TemplateTokenizer_1.TemplateTokenizer(content); var tokenParserContext = new TokenParserContext_1.TokenParserContext(tokenParserContextCommon || new TokenParserContext_1.TokenParserContextCommon(), this.sandboxPolicy); try { var tokenReader = new TokenReader_1.TokenReader(templateTokenizer); tokenParserContext.setBlock('__main', this.parseTemplateSync(tokenParserContext, tokenReader)); } catch (e) { if (e instanceof FlowException) { //console.log(e); throw (new Error("Unexpected tag '" + e.blockType + "' on template root")); } throw (e); } var output = ''; output += 'CurrentTemplate = function() { this.name = ' + JSON.stringify(path) + '; };\n'; output += 'CurrentTemplate.prototype.render = function(runtimeContext) { runtimeContext.setTemplate(this); this.__main(runtimeContext); };\n'; tokenParserContext.iterateBlocks(function (blockNode, blockName) { output += 'CurrentTemplate.prototype.' + blockName + ' = function(runtimeContext) {\n'; { var avoidOutput = ((blockName == "__main") && (tokenParserContext.afterMainNodes.length > 0)); output += 'var that = this;\n'; output += 'runtimeContext.setCurrentBlock(that, ' + JSON.stringify(blockName) + ', function() {'; { output += blockNode.generateCode({ doWrite: !avoidOutput }) + "\n"; } if (avoidOutput) { tokenParserContext.iterateAfterMainNodes(function (blockNode2) { output += blockNode2.generateCode({ doWrite: false }) + "\n"; }); } output += '});'; } output += '};\n'; }); output += 'CurrentTemplate.prototype.macros = {};\n'; output += 'CurrentTemplate.prototype.macros.$runtimeContext = runtimeContext;\n'; tokenParserContext.iterateMacros(function (macroNode, macroName) { output += 'CurrentTemplate.prototype.macros.' + macroName + ' = function() {\n'; //output += 'console.log(this);\n'; output += 'var runtimeContext = this.$runtimeContext || this;\n'; //output += 'console.log("<<<<<<<<<<<<<<<<<<<<<<");console.log(this);\n'; output += macroNode.generateCode({ doWrite: true }); output += '};\n'; }); debug(output); return { output: output, tokenParserContext: tokenParserContext }; }; TemplateParser.prototype.getEvalCode = function (path, tokenParserContextCommon) { if (!this.getCache()) delete this.registry[path]; if (this.registry[path] !== undefined) { return this.registry[path]; } //console.log("TemplateParser.prototype.compile: " + path); var content = this.templateProvider.getSync(path, this.getCache()); return this.getEvalCodeString(content, path, tokenParserContextCommon); }; TemplateParser.prototype.compileString = function (content, runtimeContext, tokenParserContextCommon) { runtimeContext.sandboxPolicy = this.sandboxPolicy; if (!this.getCache()) delete this.registryString[content]; if (this.registryString[content] === undefined) { var info = this.getEvalCodeString(content, 'inline', tokenParserContextCommon); var output = info.output; var tokenParserContext = info.tokenParserContext; var CurrentTemplate = undefined; //console.log(runtimeContext); try { eval(output); } catch (e) { console.log('----------------------------'); console.log('Exception in eval: ' + output); console.log('----------------------------'); throw (e); } //console.log(output); //console.log(content); this.registryString[content] = { output: output, class: CurrentTemplate }; } return this.registryString[content]; }; TemplateParser.prototype.compile = function (path, runtimeContext, tokenParserContextCommon) { runtimeContext.sandboxPolicy = this.sandboxPolicy; if (!this.getCache()) delete this.registry[path]; if (this.registry[path] === undefined) { var info = this.getEvalCode(path, tokenParserContextCommon); var output = info.output; var tokenParserContext = info.tokenParserContext; var CurrentTemplate = undefined; try { eval(output); } catch (e) { console.log('----------------------------'); console.log('Exception in eval: ' + output); console.log('----------------------------'); throw (e); } //console.log(output); this.registry[path] = { output: output, class: CurrentTemplate }; } return this.registry[path]; }; TemplateParser.prototype.parseTemplateSyncOne = function (tokenParserContext, tokenReader) { if (!tokenReader.hasMore()) return null; var item = tokenReader.peek(); debug('parseTemplateSync: ' + item.type); switch (item.type) { case 'text': item = tokenReader.read(); return new ParserNode.ParserNodeOutputText(String(item.value)); case 'trimSpacesAfter': case 'trimSpacesBefore': item = tokenReader.read(); return new ParserNode.ParserNodeRaw('runtimeContext.trimSpaces();'); case 'expression': item = tokenReader.read(); // Propagate the "not done". return new ParserNode.ParserNodeRaw(this.parseTemplateExpressionSync(tokenParserContext, tokenReader, new TokenReader_1.TokenReader(item.value)).generateCode({ doWrite: true })); case 'block': item = tokenReader.read(); // Propagate the "not done". return this.parseTemplateBlockSync(tokenParserContext, tokenReader, new TokenReader_1.TokenReader(item.value)); } throw new Error("Invalid item.type == '" + item.type + "'"); }; TemplateParser.prototype.parseTemplateSync = function (tokenParserContext, tokenReader) { var nodes = new ParserNode.ParserNodeContainer([]); while (tokenReader.hasMore()) { nodes.add(this.parseTemplateSyncOne(tokenParserContext, tokenReader)); } return nodes; }; TemplateParser.prototype.parseTemplateExpressionSync = function (tokenParserContext, templateTokenReader, expressionTokenReader) { return new ParserNode.ParserNodeStatementExpression(new ParserNode.ParserNodeWriteExpression((new ExpressionParser_1.ExpressionParser(expressionTokenReader, tokenParserContext)).parseExpression())); }; TemplateParser.prototype.parseTemplateBlockSync = function (tokenParserContext, templateTokenReader, expressionTokenReader) { var that = this; var blockTypeToken = expressionTokenReader.read(); var blockType = blockTypeToken.value; if (blockTypeToken.type != 'id') { throw new Error("Block expected a type as first token but found : " + JSON.stringify(blockTypeToken)); } debug('BLOCK: ' + blockType); var blockHandler = this.languageContext.tags[blockType]; if (blockHandler !== undefined) { //console.log("blockHandler: " + blockType + " | " + tokenParserContext.common.sandbox); if (tokenParserContext.common.sandbox) { if (this.sandboxPolicy.allowedTags.indexOf(blockType) == -1) { throw new Error("Sandbox policy disallows block '" + blockType + "'"); } } return blockHandler(blockType, this, tokenParserContext, templateTokenReader, expressionTokenReader); } //throw new Error("Invalid block type '" + blockTypeToken.value + "' just can handle " + JSON.stringify(Object.keys(this.languageContext.tags))); throw new Error("Invalid block type '" + blockTypeToken.value + "'"); }; return TemplateParser; }()); exports.TemplateParser = TemplateParser;