blob: d2b07ab7ff25ed4a9a11aea24ef3f6e91d2582bf [file] [log] [blame]
// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
class CGBlock {
int _blockType; // Code type of this block
int _indent; // Number of spaces to prefix for each statement
bool _inEach; // This block or any currently active blocks is a
// #each. If so then any element marked with a
// var attribute is repeated therefore the var
// is a List type instead of an Element type.
String _localName; // optional local name for #each or #with
List<CGStatement> _stmts;
int localIndex; // Local variable index (e.g., e0, e1, etc.)
// Block Types:
static const int CONSTRUCTOR = 0;
static const int EACH = 1;
static const int WITH = 2;
CGBlock([this._indent = 4,
this._blockType = CGBlock.CONSTRUCTOR,
this._inEach = false,
this._localName = null]) :
_stmts = new List<CGStatement>(), localIndex = 0 {
assert(_blockType >= CGBlock.CONSTRUCTOR && _blockType <= CGBlock.WITH);
}
bool get anyStatements => !_stmts.isEmpty;
bool get isConstructor => _blockType == CGBlock.CONSTRUCTOR;
bool get isEach => _blockType == CGBlock.EACH;
bool get isWith => _blockType == CGBlock.WITH;
bool get hasLocalName => _localName != null;
String get localName => _localName;
CGStatement push(var elem, var parentName, [bool exact = false]) {
var varName;
if (elem is TemplateElement && elem.hasVar) {
varName = elem.varName;
} else {
varName = localIndex++;
}
CGStatement stmt = new CGStatement(elem, _indent, parentName, varName,
exact, _inEach);
_stmts.add(stmt);
return stmt;
}
void pop() {
_stmts.removeLast();
}
void add(String value) {
if (_stmts.last != null) {
_stmts.last.add(value);
}
}
CGStatement get last => _stmts.length > 0 ? _stmts.last : null;
/**
* Returns mixed list of elements marked with the var attribute. If the
* element is inside of a #each the name exposed is:
*
* List varName;
*
* otherwise it's:
*
* var varName;
*
* TODO(terry): For scalars var varName should be Element tag type e.g.,
*
* DivElement varName;
*/
String get globalDeclarations {
StringBuffer buff = new StringBuffer();
for (final CGStatement stmt in _stmts) {
buff.write(stmt.globalDeclaration());
}
return buff.toString();
}
/**
* List of statement constructors for each var inside a #each.
*
* ${#each products}
* <div var=myVar>...</div>
* ${/each}
*
* returns:
*
* myVar = [];
*/
String get globalInitializers {
StringBuffer buff = new StringBuffer();
for (final CGStatement stmt in _stmts) {
buff.write(stmt.globalInitializers());
}
return buff.toString();
}
String get codeBody {
StringBuffer buff = new StringBuffer();
for (final CGStatement stmt in _stmts) {
buff.write(stmt.emitDartStatement());
}
return buff.toString();
}
}
class CGStatement {
bool _exact; // If True not HTML construct instead exact stmt
bool _repeating; // Stmt in a #each this block or nested block.
StringBuffer _buff;
var _elem;
int _indent;
var parentName;
String varName;
bool _globalVariable;
bool _closed;
CGStatement(this._elem, this._indent, this.parentName, var varNameOrIndex,
[this._exact = false, this._repeating = false]) :
_buff = new StringBuffer(), _closed = false {
if (varNameOrIndex is String) {
// We have the global variable name
varName = varNameOrIndex;
_globalVariable = true;
} else {
// local index generate local variable name.
varName = "e${varNameOrIndex}";
_globalVariable = false;
}
}
bool get hasGlobalVariable => _globalVariable;
String get variableName => varName;
String globalDeclaration() {
if (hasGlobalVariable) {
String spaces = Codegen.spaces(_indent);
return (_repeating) ?
" List ${varName}; // Repeated elements.\n" : " var ${varName};\n";
}
return "";
}
String globalInitializers() {
if (hasGlobalVariable && _repeating) {
return " ${varName} = [];\n";
}
return "";
}
void add(String value) {
_buff.write(value);
}
bool get isClosed => _closed;
void close() {
if (_elem is TemplateElement && _elem.scoped) {
add("</${_elem.tagName}>");
}
_closed = true;
}
String emitDartStatement() {
StringBuffer statement = new StringBuffer();
String spaces = Codegen.spaces(_indent);
if (_exact) {
statement.add("${spaces}${_buff.toString()};\n");
} else {
String localVar = "";
String tmpRepeat;
if (hasGlobalVariable) {
if (_repeating) {
tmpRepeat = "tmp_${varName}";
localVar = "var ";
}
} else {
localVar = "var ";
}
/* Emiting the following code fragment where varName is the attribute
value for var=
varName = new Element.html('HTML GOES HERE');
parent.elements.add(varName);
for repeating elements in a #each:
var tmp_nnn = new Element.html('HTML GOES HERE');
varName.add(tmp_nnn);
parent.elements.add(tmp_nnn);
for elements w/o var attribute set:
var eNNN = new Element.html('HTML GOES HERE');
parent.elements.add(eNNN);
*/
if (_elem is TemplateCall) {
// Call template NameEntry2
String cls = _elem.toCall;
String params = _elem.params;
statement.add("\n${spaces}// Call template ${cls}.\n");
statement.add(
"${spaces}${localVar}${varName} = new ${cls}${params};\n");
statement.add(
"${spaces}${parentName}.elements.add(${varName}.root);\n");
} else {
bool isTextNode = _elem is TemplateText;
String createType = isTextNode ? "Text" : "Element.html";
if (tmpRepeat == null) {
statement.add("${spaces}${localVar}${varName} = new ${createType}('");
} else {
statement.add(
"${spaces}${localVar}${tmpRepeat} = new ${createType}('");
}
statement.add(isTextNode ? _buff.toString().trim() : _buff.toString());
if (tmpRepeat == null) {
statement.add(
"');\n${spaces}${parentName}.elements.add(${varName});\n");
} else {
statement.add(
"');\n${spaces}${parentName}.elements.add(${tmpRepeat});\n");
statement.add("${spaces}${varName}.add(${tmpRepeat});\n");
}
}
}
return statement.toString();
}
}
class Codegen {
static const String SPACES = " ";
static String spaces(int numSpaces) {
return SPACES.substring(0, numSpaces);
}
// TODO(terry): Before generating Dart class need a validate phase that
// checks mangles all class names to be prefix with the
// template name to avoid any class name collisions. Also,
// investigate possible runtime check mode to insure that only
// valid CSS class names are used (in case someone uses strings
// and not the generated getters to the CSS class selector. This
// mode would be slower would require that every class name set
// (maybe jQuery too) is for a particular view (requires walking
// the HTML tree looking for a parent template prefix that
// matches the CSS prefix. (more thinking needed).
static String generate(List<Template> templates, String filename) {
List<String> fileParts = filename.split('.');
assert(fileParts.length == 2);
filename = fileParts[0];
StringBuffer buff = new StringBuffer();
int injectId = 0; // Inject function id
buff.write("// Generated Dart class from HTML template.\n");
buff.write("// DO NOT EDIT.\n\n");
String addStylesheetFuncName = "add_${filename}_templatesStyles";
for (final template in templates) {
// Emit the template class.
TemplateSignature sig = template.signature;
buff.write(_emitClass(sig.name, sig.params, template.content,
addStylesheetFuncName));
}
// TODO(terry): Stylesheet aggregator should not be global needs to be
// bound to this template file not global to the app.
// Emit the stylesheet aggregator.
buff.write("\n\n// Inject all templates stylesheet once into the head.\n");
buff.write("bool ${filename}_stylesheet_added = false;\n");
buff.write("void ${addStylesheetFuncName}() {\n");
buff.write(" if (!${filename}_stylesheet_added) {\n");
buff.write(" StringBuffer styles = new StringBuffer();\n\n");
buff.write(" // All templates stylesheet.\n");
for (final template in templates) {
TemplateSignature sig = template.signature;
buff.write(" styles.add(${sig.name}.stylesheet);\n");
}
buff.write("\n ${filename}_stylesheet_added = true;\n");
buff.write(" document.head.elements.add(new Element.html('<style>"
"\${styles.toString()}</style>'));\n");
buff.write(" }\n");
buff.write("}\n");
return buff.toString();
}
static String _emitCSSSelectors(css.Stylesheet stylesheet) {
if (stylesheet == null) {
return "";
}
List<String> classes = [];
for (final production in stylesheet.topLevels) {
if (production is css.IncludeDirective) {
for (final topLevel in production.styleSheet.topLevels) {
if (topLevel is css.RuleSet) {
classes = css.Generate.computeClassSelectors(topLevel, classes);
}
}
} else if (production is css.RuleSet) {
classes = css.Generate.computeClassSelectors(production, classes);
}
}
List<String> dartNames = [];
for (final String knownClass in classes) {
StringBuffer dartName = new StringBuffer();
List<String> splits = knownClass.split('-');
if (splits.length > 0) {
dartName.add(splits[0]);
for (int idx = 1; idx < splits.length; idx++) {
String part = splits[idx];
// Character between 'a'..'z' mapped to 'A'..'Z'
dartName.add("${part[0].toUpperCase()}${part.substring(1)}");
}
dartNames.add(dartName.toString());
}
}
StringBuffer buff = new StringBuffer();
if (classes.length > 0) {
assert(classes.length == dartNames.length);
buff.write("\n // CSS class selectors for this template.\n");
for (int i = 0; i < classes.length; i++) {
buff.write(
" static String get ${dartNames[i]} => \"${classes[i]}\";\n");
}
}
return buff.toString();
}
static String _emitClass(String className,
List<Map<Identifier, Identifier>> params,
TemplateContent content,
String addStylesheetFuncName) {
StringBuffer buff = new StringBuffer();
// Emit the template class.
buff.write("class ${className} {\n");
buff.write(" Map<String, Object> _scopes;\n");
buff.write(" Element _fragment;\n\n");
bool anyParams = false;
for (final param in params) {
buff.write(" ${param['type']} ${param['name']};\n");
anyParams = true;
}
if (anyParams) buff.write("\n");
ElemCG ecg = new ElemCG();
if (!ecg.pushBlock()) {
world.error("Error at ${content}");
}
var root = content.html.children.length > 0 ? content.html.children[0] :
content.html;
bool firstTime = true;
for (var child in root.children) {
if (child is TemplateText) {
if (!firstTime) {
ecg.closeStatement();
}
String textNodeValue = child.value.trim();
if (textNodeValue.length > 0) {
CGStatement stmt = ecg.pushStatement(child, "_fragment");
}
}
ecg.emitConstructHtml(child, "", "_fragment");
firstTime = false;
}
// Create all element names marked with var.
String decls = ecg.globalDeclarations;
if (decls.length > 0) {
buff.write("\n // Elements bound to a variable:\n");
buff.write("${decls}\n");
}
// Create the constructor.
buff.write(" ${className}(");
bool firstParam = true;
for (final param in params) {
if (!firstParam) {
buff.write(", ");
}
buff.write("this.${param['name']}");
firstParam = false;
}
buff.write(") : _scopes = new Map<String, Object>() {\n");
String initializers = ecg.globalInitializers;
if (initializers.length > 0) {
buff.write(" //Global initializers.\n");
buff.write("${initializers}\n");
}
buff.write(" // Insure stylesheet for template exist in the document.\n");
buff.write(" ${addStylesheetFuncName}();\n\n");
buff.write(" _fragment = new DocumentFragment();\n");
buff.write(ecg.codeBody); // HTML for constructor to build.
buff.write(" }\n\n"); // End constructor
buff.write(emitGetters(content.getters));
buff.write(" Element get root => _fragment;\n");
// Emit all CSS class selectors:
buff.write(_emitCSSSelectors(content.css));
// Emit the injection functions.
buff.write("\n // Injection functions:");
for (final expr in ecg.expressions) {
buff.write("${expr}");
}
buff.write("\n // Each functions:\n");
for (var eachFunc in ecg.eachs) {
buff.write("${eachFunc}\n");
}
buff.write("\n // With functions:\n");
for (var withFunc in ecg.withs) {
buff.write("${withFunc}\n");
}
buff.write("\n // CSS for this template.\n");
buff.write(" static const String stylesheet = ");
if (content.css != null) {
buff.write("\'\'\'\n ${content.css.toString()}\n");
buff.write(" \'\'\';\n\n");
// TODO(terry): Emit all known selectors for this template.
buff.write(" // Stylesheet class selectors:\n");
} else {
buff.write("\"\";\n");
}
buff.write(" String safeHTML(String html) {\n");
buff.write(" // TODO(terry): Escaping for XSS vulnerabilities TBD.\n");
buff.write(" return html;\n");
buff.write(" }\n");
buff.write("}\n"); // End class
return buff.toString();
}
// TODO(terry): Need to generate function to inject any TemplateExpressions
// to call SafeHTML wrapper.
static String emitGetters(List<TemplateGetter> getters) {
StringBuffer buff = new StringBuffer();
for (final TemplateGetter getter in getters) {
buff.write(' String ${getter.getterSignatureAsString()} {\n');
buff.write(' return \'\'\'');
var docFrag = getter.docFrag.children[0];
for (final child in docFrag.children) {
buff.write(child.toString().trim());
}
buff.write('\'\'\';\n');
buff.write(' }\n\n');
}
return buff.toString();
}
}
class ElemCG {
// List of identifiers and quoted strings (single and double quoted).
var identRe = new RegExp(
"\s*('\"\\'\\\"[^'\"\\'\\\"]+'\"\\'\\\"|[_A-Za-z][_A-Za-z0-9]*)");
List<CGBlock> _cgBlocks;
StringBuffer _globalDecls; // Global var declarations for all blocks.
StringBuffer _globalInits; // Global List var initializtion for all
// blocks in a #each.
String funcCall; // Func call after element creation?
List<String> expressions; // List of injection function declarations.
List<String> eachs; // List of each function declarations.
List<String> withs; // List of with function declarations.
ElemCG() :
expressions = [],
eachs = [],
withs = [],
_cgBlocks = [],
_globalDecls = new StringBuffer(),
_globalInits = new StringBuffer();
bool get isLastBlockConstructor {
CGBlock block = _cgBlocks.last;
return block.isConstructor;
}
List<String> activeBlocksLocalNames() {
List<String> result = [];
for (final CGBlock block in _cgBlocks) {
if (block.isEach || block.isWith) {
if (block.hasLocalName) {
result.add(block.localName);
}
}
}
return result;
}
/**
* Active block with this localName.
*/
bool matchBlocksLocalName(String name) {
for (final CGBlock block in _cgBlocks) {
if (block.isEach || block.isWith) {
if (block.hasLocalName && block.localName == name) {
return true;
}
}
}
return false;
}
/**
* Any active blocks?
*/
bool isNestedBlock() {
for (final CGBlock block in _cgBlocks) {
if (block.isEach || block.isWith) {
return true;
}
}
return false;
}
/**
* Any active blocks with localName?
*/
bool isNestedNamedBlock() {
for (final CGBlock block in _cgBlocks) {
if ((block.isEach || block.isWith) && block.hasLocalName) {
return true;
}
}
return false;
}
// Any current active #each blocks.
bool anyEachBlocks(int blockToCreateType) {
bool result = blockToCreateType == CGBlock.EACH;
for (final CGBlock block in _cgBlocks) {
if (block.isEach) {
result = result || true;
}
}
return result;
}
bool pushBlock([int indent = 4, int blockType = CGBlock.CONSTRUCTOR,
String itemName = null]) {
if (itemName != null && matchBlocksLocalName(itemName)) {
world.error("Active block already exist with local name: ${itemName}.");
return false;
} else if (itemName == null && this.isNestedBlock()) {
world.error('''
Nested #each or #with must have a localName;
\n #each list [localName]\n #with object [localName]''');
return false;
}
_cgBlocks.add(
new CGBlock(indent, blockType, anyEachBlocks(blockType), itemName));
return true;
}
void popBlock() {
_globalDecls.add(lastBlock.globalDeclarations);
_globalInits.add(lastBlock.globalInitializers);
_cgBlocks.removeLast();
}
CGStatement pushStatement(var elem, var parentName) {
return lastBlock.push(elem, parentName, false);
}
CGStatement pushExactStatement(var elem, var parentName) {
return lastBlock.push(elem, parentName, true);
}
bool get isClosedStatement {
return (lastBlock != null && lastBlock.last != null) ?
lastBlock.last.isClosed : false;
}
void closeStatement() {
if (lastBlock != null && lastBlock.last != null &&
!lastBlock.last.isClosed) {
lastBlock.last.close();
}
}
String get lastVariableName {
if (lastBlock != null && lastBlock.last != null) {
return lastBlock.last.variableName;
}
}
String get lastParentName {
if (lastBlock != null && lastBlock.last != null) {
return lastBlock.last.parentName;
}
}
CGBlock get lastBlock => _cgBlocks.length > 0 ? _cgBlocks.last : null;
void add(String str) {
_cgBlocks.last.add(str);
}
String get globalDeclarations {
assert(_cgBlocks.length == 1); // Only constructor body should be left.
_globalDecls.add(lastBlock.globalDeclarations);
return _globalDecls.toString();
}
String get globalInitializers {
assert(_cgBlocks.length == 1); // Only constructor body should be left.
_globalInits.add(lastBlock.globalInitializers);
return _globalInits.toString();
}
String get codeBody {
closeStatement();
return _cgBlocks.last.codeBody;
}
/* scopeName for expression
* parentVarOrIndex if # it's a local variable if string it's an exposed
* name (specified by the var attribute) for this element.
*
*/
emitElement(var elem,
[String scopeName = "",
var parentVarOrIdx = 0,
bool immediateNestedEach = false]) {
if (elem is TemplateElement) {
if (!elem.isFragment) {
add("<${elem.tagName}${elem.attributesToString()}>");
}
String prevParent = lastVariableName;
for (var childElem in elem.children) {
if (childElem is TemplateElement) {
closeStatement();
if (childElem.hasVar) {
emitConstructHtml(childElem, scopeName, prevParent,
childElem.varName);
} else {
emitConstructHtml(childElem, scopeName, prevParent);
}
closeStatement();
} else {
emitElement(childElem, scopeName, parentVarOrIdx);
}
}
// Close this tag.
closeStatement();
} else if (elem is TemplateText) {
String outputValue = elem.value.trim();
if (outputValue.length > 0) {
bool emitTextNode = false;
if (isClosedStatement) {
String prevParent = lastParentName;
CGStatement stmt = pushStatement(elem, prevParent);
emitTextNode = true;
}
// TODO(terry): Need to interpolate following:
// {sp} → space
// {nil} → empty string
// {\r} → carriage return
// {\n} → new line (line feed)
// {\t} → tab
// {lb} → left brace
// {rb} → right brace
add("${outputValue}"); // remove leading/trailing whitespace.
if (emitTextNode) {
closeStatement();
}
}
} else if (elem is TemplateExpression) {
emitExpressions(elem, scopeName);
} else if (elem is TemplateEachCommand) {
// Signal to caller new block coming in, returns "each_" prefix
emitEach(elem, "List", elem.listName.name, "parent", immediateNestedEach,
elem.hasLoopItem ? elem.loopItem.name : null);
} else if (elem is TemplateWithCommand) {
// Signal to caller new block coming in, returns "each_" prefix
emitWith(elem, "var", elem.objectName.name, "parent",
elem.hasBlockItem ? elem.blockItem.name : null);
} else if (elem is TemplateCall) {
emitCall(elem, parentVarOrIdx);
}
}
// TODO(terry): Hack prefixing all names with "${scopeName}." but don't touch
// quoted strings.
String _resolveNames(String expr, String prefixPart) {
StringBuffer newExpr = new StringBuffer();
Iterable<Match> matches = identRe.allMatches(expr);
int lastIdx = 0;
for (Match m in matches) {
if (m.start > lastIdx) {
newExpr.add(expr.substring(lastIdx, m.start));
}
bool identifier = true;
if (m.start > 0) {
int charCode = expr.codeUnitAt(m.start - 1);
// Starts with ' or " then it's not an identifier.
identifier = charCode != 34 /* " */ && charCode != 39 /* ' */;
}
String strMatch = expr.substring(m.start, m.end);
if (identifier) {
newExpr.add("${prefixPart}.${strMatch}");
} else {
// Quoted string don't touch.
newExpr.add("${strMatch}");
}
lastIdx = m.end;
}
if (expr.length > lastIdx) {
newExpr.add(expr.substring(lastIdx));
}
return newExpr.toString();
}
/**
* Construct the HTML each top-level node get's it's own variable.
*
* TODO(terry): Might want to optimize if the other top-level nodes have no
* control structures (with, each, if, etc.). We could
* synthesize a root node and create all the top-level nodes
* under the root node with one innerHTML.
*/
void emitConstructHtml(var elem,
[String scopeName = "",
String parentName = "parent",
var varIndex = 0,
bool immediateNestedEach = false]) {
if (elem is TemplateElement) {
CGStatement stmt = pushStatement(elem, parentName);
emitElement(elem, scopeName, stmt.hasGlobalVariable ?
stmt.variableName : varIndex);
} else {
emitElement(elem, scopeName, varIndex, immediateNestedEach);
}
}
/* Any references to products.sales needs to be remaped to item.sales
* for now it's a hack look for first dot and replace with item.
*/
String eachIterNameToItem(String iterName) {
String newName = iterName;
var dotForIter = iterName.indexOf('.');
if (dotForIter >= 0) {
newName = "_item${iterName.substring(dotForIter)}";
}
return newName;
}
emitExpressions(TemplateExpression elem, String scopeName) {
StringBuffer func = new StringBuffer();
String newExpr = elem.expression;
bool anyNesting = isNestedNamedBlock();
if (scopeName.length > 0 && !anyNesting) {
// In a block #command need the scope passed in.
add("\$\{inject_${expressions.length}(_item)\}");
func.add("\n String inject_${expressions.length}(var _item) {\n");
// Escape all single-quotes, this expression is embedded as a string
// parameter for the call to safeHTML.
newExpr = _resolveNames(newExpr.replaceAll("'", "\\'"), "_item");
} else {
// Not in a block #command item isn't passed in.
add("\$\{inject_${expressions.length}()\}");
func.add("\n String inject_${expressions.length}() {\n");
if (anyNesting) {
func.add(defineScopes());
}
}
// Construct the active scope names for name resolution.
func.add(" return safeHTML('\$\{${newExpr}\}');\n");
func.add(" }\n");
expressions.add(func.toString());
}
emitCall(TemplateCall elem, String scopeName) {
pushStatement(elem, scopeName);
}
emitEach(TemplateEachCommand elem, String iterType, String iterName,
var parentVarOrIdx, bool nestedImmediateEach, [String itemName = null]) {
TemplateDocument docFrag = elem.documentFragment;
int eachIndex = eachs.length;
eachs.add("");
StringBuffer funcBuff = new StringBuffer();
// Prepare function call "each_N(iterName," parent param computed later.
String funcName = "each_${eachIndex}";
funcBuff.add(" ${funcName}(${iterType} items, Element parent) {\n");
String paramName = injectParamName(itemName);
if (paramName == null) {
world.error("Use a different local name; ${itemName} is reserved.");
}
funcBuff.add(" for (var ${paramName} in items) {\n");
if (!pushBlock(6, CGBlock.EACH, itemName)) {
world.error("Error at ${elem}");
}
addScope(6, funcBuff, itemName);
TemplateElement docFragChild = docFrag.children[0];
var children = docFragChild.isFragment ?
docFragChild.children : docFrag.children;
for (var child in children) {
// If any immediate children of the parent #each is an #each then
// so we need to pass the outer #each parent not the last statement's
// variableName when calling the nested #each.
bool eachChild = (child is TemplateEachCommand);
emitConstructHtml(child, iterName, parentVarOrIdx, 0, eachChild);
}
funcBuff.add(codeBody);
removeScope(6, funcBuff, itemName);
popBlock();
funcBuff.add(" }\n");
funcBuff.add(" }\n");
eachs[eachIndex] = funcBuff.toString();
// If nested each then we want to pass the parent otherwise we'll use the
// varName.
var varName = nestedImmediateEach ? "parent" : lastBlockVarName;
pushExactStatement(elem, parentVarOrIdx);
// Setup call to each func as "each_n(xxxxx, " the parent param is filled
// in later when we known the parent variable.
String eachParam =
(itemName == null) ? eachIterNameToItem(iterName) : iterName;
add("${funcName}(${eachParam}, ${varName})");
}
emitWith(TemplateWithCommand elem, String withType, String withName,
var parentVarIndex, [String itemName = null]) {
TemplateDocument docFrag = elem.documentFragment;
int withIndex = withs.length;
withs.add("");
StringBuffer funcBuff = new StringBuffer();
// Prepare function call "each_N(iterName," parent param computed later.
String funcName = "with_${withIndex}";
String paramName = injectParamName(itemName);
if (paramName == null) {
world.error("Use a different local name; ${itemName} is reserved.");
}
funcBuff.add(" ${funcName}(${withType} ${paramName}, Element parent) {\n");
if (!pushBlock(4, CGBlock.WITH, itemName)) {
world.error("Error at ${elem}");
}
TemplateElement docFragChild = docFrag.children[0];
var children = docFragChild.isFragment ?
docFragChild.children : docFrag.children;
for (var child in children) {
emitConstructHtml(child, withName, "parent");
}
addScope(4, funcBuff, itemName);
funcBuff.add(codeBody);
removeScope(4, funcBuff, itemName);
popBlock();
funcBuff.add(" }\n");
withs[withIndex] = funcBuff.toString();
// Compute parent node variable before pushing with statement.
String parentVarName = lastBlockVarName;
pushExactStatement(elem, parentVarIndex);
// Setup call to each func as "each_n(xxxxx, " the parent param is filled
// in later when we known the parent variable.
add("${funcName}(${withName}, ${parentVarName})");
}
String get lastBlockVarName {
var varName;
if (lastBlock != null && lastBlock.anyStatements) {
varName = lastBlock.last.variableName;
} else {
varName = "_fragment";
}
return varName;
}
String injectParamName(String name) {
// Local name _item is reserved.
if (name != null && name == "_item") {
return null; // Local name is not valid.
}
return (name == null) ? "_item" : name;
}
addScope(int indent, StringBuffer buff, String item) {
String spaces = Codegen.spaces(indent);
if (item == null) {
item = "_item";
}
buff.write("${spaces}_scopes[\"${item}\"] = ${item};\n");
}
removeScope(int indent, StringBuffer buff, String item) {
String spaces = Codegen.spaces(indent);
if (item == null) {
item = "_item";
}
buff.write("${spaces}_scopes.remove(\"${item}\");\n");
}
String defineScopes() {
StringBuffer buff = new StringBuffer();
// Construct the active scope names for name resolution.
List<String> names = activeBlocksLocalNames();
if (names.length > 0) {
buff.write(" // Local scoped block names.\n");
for (String name in names) {
buff.write(" var ${name} = _scopes[\"${name}\"];\n");
}
buff.write("\n");
}
return buff.toString();
}
}