OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 class CGBlock { |
| 6 int _blockType; // Code type of this block |
| 7 int _indent; // Number of spaces to prefix for each statement |
| 8 bool _inEach; // This block or any currently active blocks is a |
| 9 // #each. If so then any element marked with a |
| 10 // var attribute is repeated therefore the var |
| 11 // is a List type instead of an Element type. |
| 12 List<CGStatement> _stmts; |
| 13 int localIndex; // Local variable index (e.g., e0, e1, etc.) |
| 14 |
| 15 // Block Types: |
| 16 static final int CONSTRUCTOR = 0; |
| 17 static final int EACH = 1; |
| 18 static final int WITH = 2; |
| 19 |
| 20 CGBlock([this._indent = 4, |
| 21 this._blockType = CGBlock.CONSTRUCTOR, |
| 22 this._inEach = false]) : |
| 23 _stmts = new List<CGStatement>(), localIndex = 0 { |
| 24 assert(_blockType >= CGBlock.CONSTRUCTOR && _blockType <= CGBlock.WITH); |
| 25 } |
| 26 |
| 27 bool get isConstructor() => _blockType == CGBlock.CONSTRUCTOR; |
| 28 bool get isEach() => _blockType == CGBlock.EACH; |
| 29 bool get isWith() => _blockType == CGBlock.WITH; |
| 30 |
| 31 CGStatement push(var elem, var parentName, [bool exact = false]) { |
| 32 var varName; |
| 33 if (elem is TemplateElement && elem.hasVar) { |
| 34 varName = elem.varName; |
| 35 } else { |
| 36 varName = localIndex++; |
| 37 } |
| 38 |
| 39 CGStatement stmt = new CGStatement(elem, _indent, parentName, varName, |
| 40 exact, _inEach); |
| 41 _stmts.add(stmt); |
| 42 |
| 43 return stmt; |
| 44 } |
| 45 |
| 46 void pop() { |
| 47 _stmts.removeLast(); |
| 48 } |
| 49 |
| 50 void add(String value) { |
| 51 if (_stmts.last() != null) { |
| 52 _stmts.last().add(value); |
| 53 } |
| 54 } |
| 55 |
| 56 CGStatement get last() => _stmts.last(); |
| 57 |
| 58 /** |
| 59 * Returns mixed list of elements marked with the var attribute. If the |
| 60 * element is inside of a #each the name exposed is: |
| 61 * |
| 62 * List varName; |
| 63 * |
| 64 * otherwise it's: |
| 65 * |
| 66 * var varName; |
| 67 * |
| 68 * TODO(terry): For scalars var varName should be Element tag type e.g., |
| 69 * |
| 70 * DivElement varName; |
| 71 */ |
| 72 String get globalDeclarations() { |
| 73 StringBuffer buff = new StringBuffer(); |
| 74 for (final CGStatement stmt in _stmts) { |
| 75 buff.add(stmt.globalDeclaration()); |
| 76 } |
| 77 |
| 78 return buff.toString(); |
| 79 } |
| 80 |
| 81 /** |
| 82 * List of statement constructors for each var inside a #each. |
| 83 * |
| 84 * ${#each products} |
| 85 * <div var=myVar>...</div> |
| 86 * ${/each} |
| 87 * |
| 88 * returns: |
| 89 * |
| 90 * myVar = []; |
| 91 */ |
| 92 String get globalInitializers() { |
| 93 StringBuffer buff = new StringBuffer(); |
| 94 for (final CGStatement stmt in _stmts) { |
| 95 buff.add(stmt.globalInitializers()); |
| 96 } |
| 97 |
| 98 return buff.toString(); |
| 99 } |
| 100 |
| 101 String get codeBody() { |
| 102 StringBuffer buff = new StringBuffer(); |
| 103 |
| 104 for (final CGStatement stmt in _stmts) { |
| 105 buff.add(stmt.emitDartStatement()); |
| 106 } |
| 107 |
| 108 return buff.toString(); |
| 109 } |
| 110 } |
| 111 |
| 112 class CGStatement { |
| 113 bool _exact; // If True not HTML construct instead exact stmt |
| 114 bool _repeating; // Stmt in a #each this block or nested block. |
| 115 StringBuffer _buff; |
| 116 TemplateElement _elem; |
| 117 int _indent; |
| 118 var parentName; |
| 119 String varName; |
| 120 bool _globalVariable; |
| 121 bool _closed; |
| 122 |
| 123 CGStatement(this._elem, this._indent, this.parentName, var varNameOrIndex, |
| 124 [this._exact = false, this._repeating = false]) : |
| 125 _buff = new StringBuffer(), _closed = false { |
| 126 |
| 127 if (varNameOrIndex is String) { |
| 128 // We have the global variable name |
| 129 varName = varNameOrIndex; |
| 130 _globalVariable = true; |
| 131 } else { |
| 132 // local index generate local variable name. |
| 133 varName = "e${varNameOrIndex}"; |
| 134 _globalVariable = false; |
| 135 } |
| 136 } |
| 137 |
| 138 bool get hasGlobalVariable() => _globalVariable; |
| 139 String get variableName() => varName; |
| 140 |
| 141 String globalDeclaration() { |
| 142 if (hasGlobalVariable) { |
| 143 String spaces = Codegen.spaces(_indent); |
| 144 return (_repeating) ? |
| 145 " List ${varName}; // Repeated elements.\r" : " var ${varName};\r"; |
| 146 } |
| 147 |
| 148 return ""; |
| 149 } |
| 150 |
| 151 String globalInitializers() { |
| 152 if (hasGlobalVariable && _repeating) { |
| 153 return " ${varName} = [];\r"; |
| 154 } |
| 155 |
| 156 return ""; |
| 157 } |
| 158 |
| 159 void add(String value) { |
| 160 _buff.add(value); |
| 161 } |
| 162 |
| 163 bool get isClosed() => _closed; |
| 164 |
| 165 void close() { |
| 166 if (_elem is TemplateElement) { |
| 167 add("</${_elem.tagName}>"); |
| 168 } |
| 169 _closed = true; |
| 170 } |
| 171 |
| 172 String emitDartStatement() { |
| 173 StringBuffer statement = new StringBuffer(); |
| 174 |
| 175 String spaces = Codegen.spaces(_indent); |
| 176 |
| 177 if (_exact) { |
| 178 statement.add("${spaces}${_buff.toString()};\r"); |
| 179 } else { |
| 180 String localVar = ""; |
| 181 String tmpRepeat; |
| 182 if (hasGlobalVariable) { |
| 183 if (_repeating) { |
| 184 tmpRepeat = "tmp_${varName}"; |
| 185 localVar = "var "; |
| 186 } |
| 187 } else { |
| 188 localVar = "var "; |
| 189 } |
| 190 |
| 191 /* Emiting the following code fragment where varName is the attribute |
| 192 value for var= |
| 193 |
| 194 varName = new Element.html('HTML GOES HERE'); |
| 195 parent.elements.add(varName); |
| 196 |
| 197 for repeating elements in a #each: |
| 198 |
| 199 var tmp_nnn = new Element.html('HTML GOES HERE'); |
| 200 varName.add(tmp_nnn); |
| 201 parent.elements.add(tmp_nnn); |
| 202 |
| 203 for elements w/o var attribute set: |
| 204 |
| 205 var eNNN = new Element.html('HTML GOES HERE'); |
| 206 parent.elements.add(eNNN); |
| 207 */ |
| 208 if (tmpRepeat == null) { |
| 209 statement.add("${spaces}${localVar}${varName} = new Element.html('"); |
| 210 } else { |
| 211 statement.add("${spaces}${localVar}${tmpRepeat} = new Element.html('"); |
| 212 } |
| 213 statement.add(_buff.toString()); |
| 214 |
| 215 if (tmpRepeat == null) { |
| 216 statement.add( |
| 217 "');\r${spaces}${parentName}.elements.add(${varName});\r"); |
| 218 } else { |
| 219 statement.add( |
| 220 "');\r${spaces}${parentName}.elements.add(${tmpRepeat});\r"); |
| 221 statement.add("${spaces}${varName}.add(${tmpRepeat});\r"); |
| 222 } |
| 223 } |
| 224 |
| 225 return statement.toString(); |
| 226 } |
| 227 } |
| 228 |
| 229 class Codegen { |
| 230 static final String SPACES = " "; |
| 231 static String spaces(int numSpaces) { |
| 232 return SPACES.substring(0, numSpaces); |
| 233 } |
| 234 |
| 235 // TODO(terry): Before generating Dart class need a validate phase that |
| 236 // checks mangles all class names to be prefix with the |
| 237 // template name to avoid any class name collisions. Also, |
| 238 // investigate possible runtime check mode to insure that only |
| 239 // valid CSS class names are used (in case someone uses strings |
| 240 // and not the generated getters to the CSS class selector. This |
| 241 // mode would be slower would require that every class name set |
| 242 // (maybe jQuery too) is for a particular view (requires walking |
| 243 // the HTML tree looking for a parent template prefix that |
| 244 // matches the CSS prefix. (more thinking needed). |
| 245 static String generate(List<Template> templates, String filename) { |
| 246 List<String> fileParts = filename.split('.'); |
| 247 assert(fileParts.length == 2); |
| 248 filename = fileParts[0]; |
| 249 |
| 250 StringBuffer buff = new StringBuffer(); |
| 251 int injectId = 0; // Inject function id |
| 252 |
| 253 buff.add("// Generated Dart class from HTML template.\r"); |
| 254 buff.add("// DO NOT EDIT.\r\r"); |
| 255 |
| 256 buff.add("String safeHTML(String html) {\r"); |
| 257 buff.add(" // TODO(terry): Escaping for XSS vulnerabilities TBD.\r"); |
| 258 buff.add(" return html;\r"); |
| 259 buff.add("}\r\r"); |
| 260 |
| 261 String addStylesheetFuncName = "add_${filename}_templatesStyles"; |
| 262 |
| 263 for (final template in templates) { |
| 264 // Emit the template class. |
| 265 TemplateSignature sig = template.signature; |
| 266 buff.add(_emitClass(sig.name, sig.params, template.content, |
| 267 addStylesheetFuncName)); |
| 268 } |
| 269 |
| 270 // TODO(terry): Stylesheet aggregator should not be global needs to be |
| 271 // bound to this template file not global to the app. |
| 272 |
| 273 // Emit the stylesheet aggregator. |
| 274 buff.add("\r\r// Inject all templates stylesheet once into the head.\r"); |
| 275 buff.add("bool ${filename}_stylesheet_added = false;\r"); |
| 276 buff.add("void ${addStylesheetFuncName}() {\r"); |
| 277 buff.add(" if (!${filename}_stylesheet_added) {\r"); |
| 278 buff.add(" StringBuffer styles = new StringBuffer();\r\r"); |
| 279 |
| 280 buff.add(" // All templates stylesheet.\r"); |
| 281 |
| 282 for (final template in templates) { |
| 283 TemplateSignature sig = template.signature; |
| 284 buff.add(" styles.add(${sig.name}.stylesheet);\r"); |
| 285 } |
| 286 |
| 287 buff.add("\r ${filename}_stylesheet_added = true;\r"); |
| 288 |
| 289 buff.add(" document.head.elements.add(new Element.html('<style>" |
| 290 "\${styles.toString()}</style>'));\r"); |
| 291 buff.add(" }\r"); |
| 292 buff.add("}\r"); |
| 293 |
| 294 return buff.toString(); |
| 295 } |
| 296 |
| 297 static String _emitCSSSelectors(css.Stylesheet stylesheet) { |
| 298 if (stylesheet == null) { |
| 299 return ""; |
| 300 } |
| 301 |
| 302 List<String> classes = []; |
| 303 |
| 304 for (final production in stylesheet.topLevels) { |
| 305 if (production is css.IncludeDirective) { |
| 306 for (final topLevel in production.styleSheet.topLevels) { |
| 307 if (topLevel is css.RuleSet) { |
| 308 classes = css.Generate.computeClassSelectors(topLevel, classes); |
| 309 } |
| 310 } |
| 311 } else if (production is css.RuleSet) { |
| 312 classes = css.Generate.computeClassSelectors(production, classes); |
| 313 } |
| 314 } |
| 315 |
| 316 List<String> dartNames = []; |
| 317 |
| 318 for (final String knownClass in classes) { |
| 319 StringBuffer dartName = new StringBuffer(); |
| 320 List<String> splits = knownClass.split('-'); |
| 321 if (splits.length > 0) { |
| 322 dartName.add(splits[0]); |
| 323 for (int idx = 1; idx < splits.length; idx++) { |
| 324 String part = splits[idx]; |
| 325 // Character between 'a'..'z' mapped to 'A'..'Z' |
| 326 dartName.add("${part[0].toUpperCase()}${part.substring(1)}"); |
| 327 } |
| 328 dartNames.add(dartName.toString()); |
| 329 } |
| 330 } |
| 331 |
| 332 StringBuffer buff = new StringBuffer(); |
| 333 if (classes.length > 0) { |
| 334 assert(classes.length == dartNames.length); |
| 335 buff.add("\r // CSS class selectors for this template.\r"); |
| 336 for (int i = 0; i < classes.length; i++) { |
| 337 buff.add( |
| 338 " static String get ${dartNames[i]}() => \"${classes[i]}\";\r"); |
| 339 } |
| 340 } |
| 341 |
| 342 return buff.toString(); |
| 343 } |
| 344 |
| 345 static String _emitClass(String className, |
| 346 List<Map<Identifier, Identifier>> params, |
| 347 TemplateContent content, |
| 348 String addStylesheetFuncName) { |
| 349 StringBuffer buff = new StringBuffer(); |
| 350 |
| 351 // Emit the template class. |
| 352 buff.add("class ${className} {\r"); |
| 353 |
| 354 buff.add(" Element _fragment;\r\r"); |
| 355 |
| 356 bool anyParams = false; |
| 357 for (final param in params) { |
| 358 buff.add(" ${param['type']} ${param['name']};\r"); |
| 359 anyParams = true; |
| 360 } |
| 361 if (anyParams) buff.add("\r"); |
| 362 |
| 363 ElemCG ecg = new ElemCG(); |
| 364 |
| 365 ecg.pushBlock(); |
| 366 |
| 367 // TODO(terry): Only supports singlely rooted need to fix. |
| 368 ecg.emitConstructHtml(content.html.children[0], "", "_fragment"); |
| 369 |
| 370 // Create all element names marked with var. |
| 371 String decls = ecg.globalDeclarations; |
| 372 if (decls.length > 0) { |
| 373 buff.add("\r // Elements bound to a variable:\r"); |
| 374 buff.add("${decls}\r"); |
| 375 } |
| 376 |
| 377 // Create the constructor. |
| 378 buff.add(" ${className}("); |
| 379 bool firstParam = true; |
| 380 for (final param in params) { |
| 381 if (!firstParam) { |
| 382 buff.add(", "); |
| 383 } |
| 384 buff.add("this.${param['name']}"); |
| 385 firstParam = false; |
| 386 } |
| 387 buff.add(") {\r"); |
| 388 |
| 389 String initializers = ecg.globalInitializers; |
| 390 if (initializers.length > 0) { |
| 391 buff.add(" //Global initializers.\r"); |
| 392 buff.add("${initializers}\r"); |
| 393 } |
| 394 |
| 395 buff.add(" // Insure stylesheet for template exist in the document.\r"); |
| 396 buff.add(" ${addStylesheetFuncName}();\r\r"); |
| 397 |
| 398 buff.add(" _fragment = new Element.tag('div');\r"); |
| 399 |
| 400 buff.add(ecg.codeBody); // HTML for constructor to build. |
| 401 |
| 402 buff.add(" }\r\r"); // End constructor |
| 403 |
| 404 buff.add(" Element get root() => _fragment.nodes.first;\r"); |
| 405 |
| 406 // Emit all CSS class selectors: |
| 407 buff.add(_emitCSSSelectors(content.css)); |
| 408 |
| 409 // Emit the injection functions. |
| 410 buff.add("\r // Injection functions:"); |
| 411 for (final expr in ecg.expressions) { |
| 412 buff.add("${expr}"); |
| 413 } |
| 414 |
| 415 buff.add("\r // Each functions:\r"); |
| 416 for (var eachFunc in ecg.eachs) { |
| 417 buff.add("${eachFunc}\r"); |
| 418 } |
| 419 |
| 420 buff.add("\r // With functions:\r"); |
| 421 for (var withFunc in ecg.withs) { |
| 422 buff.add("${withFunc}\r"); |
| 423 } |
| 424 |
| 425 buff.add("\r // CSS for this template.\r"); |
| 426 buff.add(" static final String stylesheet = "); |
| 427 |
| 428 if (content.css != null) { |
| 429 buff.add("\'\'\'\r ${content.css.toString()}\r"); |
| 430 buff.add(" \'\'\';\r\r"); |
| 431 |
| 432 // TODO(terry): Emit all known selectors for this template. |
| 433 buff.add(" // Stylesheet class selectors:\r"); |
| 434 } else { |
| 435 buff.add("\"\";\r"); |
| 436 } |
| 437 |
| 438 buff.add("}\r"); // End class |
| 439 |
| 440 return buff.toString(); |
| 441 } |
| 442 } |
| 443 |
| 444 class ElemCG { |
| 445 // List of identifiers and quoted strings (single and double quoted). |
| 446 var identRe = const RegExp( |
| 447 "\s*('\"\\'\\\"[^'\"\\'\\\"]+'\"\\'\\\"|[_A-Za-z][_A-Za-z0-9]*)"); |
| 448 |
| 449 List<CGBlock> _cgBlocks; |
| 450 StringBuffer _globalDecls; // Global var declarations for all blocks. |
| 451 StringBuffer _globalInits; // Global List var initializtion for all |
| 452 // blocks in a #each. |
| 453 String funcCall; // Func call after element creation? |
| 454 List<String> expressions; // List of injection function declarations. |
| 455 List<String> eachs; // List of each function declarations. |
| 456 List<String> withs; // List of with function declarations. |
| 457 |
| 458 ElemCG() : |
| 459 expressions = [], |
| 460 eachs = [], |
| 461 withs = [], |
| 462 _cgBlocks = [], |
| 463 _globalDecls = new StringBuffer(), |
| 464 _globalInits = new StringBuffer(); |
| 465 |
| 466 bool get isLastBlockConstructor() { |
| 467 CGBlock block = _cgBlocks.last(); |
| 468 return block.isConstructor; |
| 469 } |
| 470 |
| 471 // Any current active #each blocks. |
| 472 bool anyEachBlocks(int blockToCreateType) { |
| 473 bool result = blockToCreateType == CGBlock.EACH; |
| 474 |
| 475 for (final CGBlock block in _cgBlocks) { |
| 476 if (block.isEach) { |
| 477 result = result || true; |
| 478 } |
| 479 } |
| 480 |
| 481 return result; |
| 482 } |
| 483 |
| 484 void pushBlock([int indent = 4, int blockType = CGBlock.CONSTRUCTOR]) { |
| 485 closeStatement(); |
| 486 _cgBlocks.add(new CGBlock(indent, blockType, anyEachBlocks(blockType))); |
| 487 } |
| 488 |
| 489 void popBlock() { |
| 490 _globalDecls.add(lastBlock.globalDeclarations); |
| 491 _globalInits.add(lastBlock.globalInitializers); |
| 492 _cgBlocks.removeLast(); |
| 493 } |
| 494 |
| 495 CGStatement pushStatement(var elem, var parentName) { |
| 496 return lastBlock.push(elem, parentName, false); |
| 497 } |
| 498 |
| 499 CGStatement pushExactStatement(var elem, var parentName) { |
| 500 return lastBlock.push(elem, parentName, true); |
| 501 } |
| 502 |
| 503 bool get isClosedStatement() => lastBlock.last.isClosed; |
| 504 |
| 505 void closeStatement() { |
| 506 if (lastBlock != null && lastBlock.last != null && |
| 507 !lastBlock.last.isClosed) { |
| 508 lastBlock.last.close(); |
| 509 } |
| 510 } |
| 511 |
| 512 String get lastVariableName() { |
| 513 if (lastBlock != null && lastBlock.last != null) { |
| 514 return lastBlock.last.variableName; |
| 515 } |
| 516 } |
| 517 |
| 518 CGBlock get lastBlock() => _cgBlocks.length > 0 ? _cgBlocks.last() : null; |
| 519 |
| 520 void add(String str) { |
| 521 _cgBlocks.last().add(str); |
| 522 } |
| 523 |
| 524 String get globalDeclarations() { |
| 525 assert(_cgBlocks.length == 1); // Only constructor body should be left. |
| 526 _globalDecls.add(lastBlock.globalDeclarations); |
| 527 return _globalDecls.toString(); |
| 528 } |
| 529 |
| 530 String get globalInitializers() { |
| 531 assert(_cgBlocks.length == 1); // Only constructor body should be left. |
| 532 _globalInits.add(lastBlock.globalInitializers); |
| 533 return _globalInits.toString(); |
| 534 } |
| 535 |
| 536 String get codeBody() { |
| 537 closeStatement(); |
| 538 return _cgBlocks.last().codeBody; |
| 539 } |
| 540 |
| 541 /* scopeName for expression |
| 542 * parentVarOrIndex if # it's a local variable if string it's an exposed |
| 543 * name (specified by the var attribute) for this element. |
| 544 * |
| 545 */ |
| 546 emitElement(var elem, |
| 547 [String scopeName = "", |
| 548 var parentVarOrIdx = 0, |
| 549 bool immediateNestedEach = false]) { |
| 550 if (elem is TemplateElement) { |
| 551 if (!elem.isFragment) { |
| 552 add("<${elem.tagName}${elem.attributesToString()}>"); |
| 553 } |
| 554 String prevParent = lastVariableName; |
| 555 for (var childElem in elem.children) { |
| 556 if (childElem is TemplateElement) { |
| 557 if (childElem.hasVar) { |
| 558 closeStatement(); |
| 559 emitConstructHtml(childElem, scopeName, prevParent, |
| 560 childElem.varName); |
| 561 closeStatement(); |
| 562 } else { |
| 563 closeStatement(); |
| 564 emitConstructHtml(childElem, scopeName, prevParent); |
| 565 closeStatement(); |
| 566 } |
| 567 } else { |
| 568 emitElement(childElem, scopeName, parentVarOrIdx); |
| 569 } |
| 570 } |
| 571 } else if (elem is TemplateText) { |
| 572 add("${elem.value}"); |
| 573 } else if (elem is TemplateExpression) { |
| 574 emitExpressions(elem, scopeName); |
| 575 } else if (elem is TemplateEachCommand) { |
| 576 // Signal to caller new block coming in, returns "each_" prefix |
| 577 emitEach(elem, "List", elem.listName.name, "parent", immediateNestedEach); |
| 578 } else if (elem is TemplateWithCommand) { |
| 579 // Signal to caller new block coming in, returns "each_" prefix |
| 580 emitWith(elem, "var", elem.objectName.name, "parent"); |
| 581 } |
| 582 } |
| 583 |
| 584 // TODO(terry): Hack prefixing all names with "${scopeName}." but don't touch |
| 585 // quoted strings. |
| 586 String _resolveNames(String expr, String prefixPart) { |
| 587 StringBuffer newExpr = new StringBuffer(); |
| 588 Iterable<Match> matches = identRe.allMatches(expr); |
| 589 |
| 590 int lastIdx = 0; |
| 591 for (Match m in matches) { |
| 592 if (m.start() > lastIdx) { |
| 593 newExpr.add(expr.substring(lastIdx, m.start())); |
| 594 } |
| 595 |
| 596 bool identifier = true; |
| 597 if (m.start() > 0) { |
| 598 int charCode = expr.charCodeAt(m.start() - 1); |
| 599 // Starts with ' or " then it's not an identifier. |
| 600 identifier = charCode != 34 /* " */ && charCode != 39 /* ' */; |
| 601 } |
| 602 |
| 603 String strMatch = expr.substring(m.start(), m.end()); |
| 604 if (identifier) { |
| 605 newExpr.add("${prefixPart}.${strMatch}"); |
| 606 } else { |
| 607 // Quoted string don't touch. |
| 608 newExpr.add("${strMatch}"); |
| 609 } |
| 610 lastIdx = m.end(); |
| 611 } |
| 612 |
| 613 if (expr.length > lastIdx) { |
| 614 newExpr.add(expr.substring(lastIdx)); |
| 615 } |
| 616 |
| 617 return newExpr.toString(); |
| 618 } |
| 619 |
| 620 /** |
| 621 * Construct the HTML each top-level node get's it's own variable. |
| 622 * |
| 623 * TODO(terry): Might want to optimize if the other top-level nodes have no |
| 624 * control structures (with, each, if, etc.). We could |
| 625 * synthesize a root node and create all the top-level nodes |
| 626 * under the root node with one innerHTML. |
| 627 */ |
| 628 void emitConstructHtml(var elem, |
| 629 [String scopeName = "", |
| 630 String parentName = "parent", |
| 631 var varIndex = 0, |
| 632 bool immediateNestedEach = false]) { |
| 633 if (elem is TemplateElement) { |
| 634 // Never look at the root node (fragment) get it's children. |
| 635 if (elem.isFragment) { |
| 636 elem = elem.children[0]; |
| 637 } |
| 638 |
| 639 CGStatement stmt = pushStatement(elem, parentName); |
| 640 emitElement(elem, scopeName, stmt.hasGlobalVariable ? |
| 641 stmt.variableName : varIndex); |
| 642 } else { |
| 643 emitElement(elem, scopeName, varIndex, immediateNestedEach); |
| 644 } |
| 645 } |
| 646 |
| 647 /* Any references to products.sales needs to be remaped to item.sales |
| 648 * for now it's a hack look for first dot and replace with item. |
| 649 */ |
| 650 String eachIterNameToItem(String iterName) { |
| 651 String newName = iterName; |
| 652 var dotForIter = iterName.indexOf('.'); |
| 653 if (dotForIter >= 0) { |
| 654 newName = "item${iterName.substring(dotForIter)}"; |
| 655 } |
| 656 |
| 657 return newName; |
| 658 } |
| 659 |
| 660 emitExpressions(TemplateExpression elem, String scopeName) { |
| 661 StringBuffer func = new StringBuffer(); |
| 662 |
| 663 String newExpr = elem.expression; |
| 664 if (scopeName.length > 0) { |
| 665 // In a block #command need the scope passed in. |
| 666 add("\$\{inject_${expressions.length}(item)\}"); |
| 667 func.add("\r String inject_${expressions.length}(var item) {\r"); |
| 668 // Escape all single-quotes, this expression is embedded as a string |
| 669 // parameter for the call to safeHTML. |
| 670 newExpr = _resolveNames(newExpr.replaceAll("'", "\\'"), "item"); |
| 671 } else { |
| 672 // Not in a block #command item isn't passed in. |
| 673 add("\$\{inject_${expressions.length}()\}"); |
| 674 func.add("\r String inject_${expressions.length}() {\r"); |
| 675 } |
| 676 |
| 677 func.add(" return safeHTML('\$\{${newExpr}\}');\r"); |
| 678 func.add(" }\r"); |
| 679 |
| 680 expressions.add(func.toString()); |
| 681 } |
| 682 emitEach(TemplateEachCommand elem, String iterType, String iterName, |
| 683 var parentVarOrIdx, bool nestedImmediateEach) { |
| 684 TemplateDocument docFrag = elem.documentFragment; |
| 685 |
| 686 int eachIndex = eachs.length; |
| 687 eachs.add(""); |
| 688 |
| 689 StringBuffer funcBuff = new StringBuffer(); |
| 690 // Prepare function call "each_N(iterName," parent param computed later. |
| 691 String funcName = "each_${eachIndex}"; |
| 692 |
| 693 funcBuff.add(" ${funcName}(${iterType} items, Element parent) {\r"); |
| 694 funcBuff.add(" for (var item in items) {\r"); |
| 695 |
| 696 pushBlock(6, CGBlock.EACH); |
| 697 |
| 698 TemplateElement docFragChild = docFrag.children[0]; |
| 699 var children = docFragChild.isFragment ? |
| 700 docFragChild.children : docFrag.children; |
| 701 for (var child in children) { |
| 702 // If any immediate children of the parent #each is an #each then |
| 703 // so we need to pass the outer #each parent not the last statement's |
| 704 // variableName when calling the nested #each. |
| 705 bool eachChild = (child is TemplateEachCommand); |
| 706 emitConstructHtml(child, iterName, parentVarOrIdx, 0, eachChild); |
| 707 } |
| 708 |
| 709 funcBuff.add(codeBody); |
| 710 |
| 711 popBlock(); |
| 712 |
| 713 funcBuff.add(" }\r"); |
| 714 funcBuff.add(" }\r"); |
| 715 |
| 716 eachs[eachIndex] = funcBuff.toString(); |
| 717 |
| 718 // If nested each then we want to pass the parent otherwise we'll use the |
| 719 // varName. |
| 720 var varName = nestedImmediateEach ? "parent" : lastBlock.last.variableName; |
| 721 |
| 722 pushExactStatement(elem, parentVarOrIdx); |
| 723 |
| 724 // Setup call to each func as "each_n(xxxxx, " the parent param is filled |
| 725 // in later when we known the parent variable. |
| 726 add("${funcName}(${eachIterNameToItem(iterName)}, ${varName})"); |
| 727 } |
| 728 |
| 729 emitWith(TemplateWithCommand elem, String withType, String withName, |
| 730 var parentVarIndex) { |
| 731 TemplateDocument docFrag = elem.documentFragment; |
| 732 |
| 733 int withIndex = withs.length; |
| 734 withs.add(""); |
| 735 |
| 736 StringBuffer funcBuff = new StringBuffer(); |
| 737 // Prepare function call "each_N(iterName," parent param computed later. |
| 738 String funcName = "with_${withIndex}"; |
| 739 |
| 740 funcBuff.add(" ${funcName}(${withType} item, Element parent) {\r"); |
| 741 |
| 742 pushBlock(CGBlock.WITH); |
| 743 |
| 744 TemplateElement docFragChild = docFrag.children[0]; |
| 745 var children = docFragChild.isFragment ? |
| 746 docFragChild.children : docFrag.children; |
| 747 for (var child in children) { |
| 748 emitConstructHtml(child, withName, "parent"); |
| 749 } |
| 750 |
| 751 funcBuff.add(codeBody); |
| 752 |
| 753 popBlock(); |
| 754 |
| 755 funcBuff.add(" }\r"); |
| 756 |
| 757 withs[withIndex] = funcBuff.toString(); |
| 758 |
| 759 var varName = lastBlock.last.variableName; |
| 760 |
| 761 pushExactStatement(elem, parentVarIndex); |
| 762 |
| 763 // Setup call to each func as "each_n(xxxxx, " the parent param is filled |
| 764 // in later when we known the parent variable. |
| 765 add("${funcName}(${withName}, ${varName})"); |
| 766 } |
| 767 } |
OLD | NEW |