Version 0.5.7.1 .

svn merge -c 22591 https://dart.googlecode.com/svn/branches/bleeding_edge/dart dart

Review URL: https://codereview.chromium.org//14651030

git-svn-id: http://dart.googlecode.com/svn/trunk@22592 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index 0e2dbb5..cf0bbde 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -7716,8 +7716,9 @@
   void set model(value) {
     _ensureTemplate();
 
+    var syntax = TemplateElement.syntax[attributes['syntax']];
     _model = value;
-    _addBindings(this, model);
+    _addBindings(this, model, syntax);
   }
 
   // TODO(jmesserly): const set would be better
@@ -14772,17 +14773,6 @@
 
   TemplateInstance _templateInstance;
 
-  // TODO(arv): Consider storing all "NodeRareData" on a single object?
-  int __instanceTerminatorCount;
-  int get _instanceTerminatorCount {
-    if (__instanceTerminatorCount == null) return 0;
-    return __instanceTerminatorCount;
-  }
-  set _instanceTerminatorCount(int value) {
-    if (value == 0) value = null;
-    __instanceTerminatorCount = value;
-  }
-
   /** Gets the template instance that instantiated this node, if any. */
   @Experimental
   TemplateInstance get templateInstance =>
@@ -24984,11 +24974,61 @@
 // more Dart-friendly.
 @Experimental
 abstract class CustomBindingSyntax {
+  /**
+   * This syntax method allows for a custom interpretation of the contents of
+   * mustaches (`{{` ... `}}`).
+   *
+   * When a template is inserting an instance, it will invoke this method for
+   * each mustache which is encountered. The function is invoked with four
+   * arguments:
+   *
+   * - [model]: The data context for which this instance is being created.
+   * - [path]: The text contents (trimmed of outer whitespace) of the mustache.
+   * - [name]: The context in which the mustache occurs. Within element
+   *   attributes, this will be the name of the attribute. Within text,
+   *   this will be 'text'.
+   * - [node]: A reference to the node to which this binding will be created.
+   *
+   * If the method wishes to handle binding, it is required to return an object
+   * which has at least a `value` property that can be observed. If it does,
+   * then MDV will call [Node.bind on the node:
+   *
+   *     node.bind(name, retval, 'value');
+   *
+   * If the 'getBinding' does not wish to override the binding, it should return
+   * null.
+   */
   // TODO(jmesserly): I had to remove type annotations from "name" and "node"
   // Normally they are String and Node respectively. But sometimes it will pass
   // (int name, CompoundBinding node). That seems very confusing; we may want
   // to change this API.
-  getBinding(model, String path, name, node);
+  getBinding(model, String path, name, node) => null;
+
+  /**
+   * This syntax method allows a syntax to provide an alterate model than the
+   * one the template would otherwise use when producing an instance.
+   *
+   * When a template is about to create an instance, it will invoke this method
+   * The function is invoked with two arguments:
+   *
+   * - [template]: The template element which is about to create and insert an
+   *   instance.
+   * - [model]: The data context for which this instance is being created.
+   *
+   * The template element will always use the return value of `getInstanceModel`
+   * as the model for the new instance. If the syntax does not wish to override
+   * the value, it should simply return the `model` value it was passed.
+   */
+  getInstanceModel(Element template, model) => model;
+
+  /**
+   * This syntax method allows a syntax to provide an alterate expansion of
+   * the [template] contents. When the template wants to create an instance,
+   * it will call this method with the template element.
+   *
+   * By default this will call `template.createInstance()`.
+   */
+  getInstanceFragment(Element template) => template.createInstance();
 }
 
 /** The callback used in the [CompoundBinding.combinator] field. */
@@ -25356,7 +25396,7 @@
   if (node is Element) {
     _addAttributeBindings(node, model, syntax);
   } else if (node is Text) {
-    _parseAndBind(node, node.text, 'text', model, syntax);
+    _parseAndBind(node, 'text', node.text, model, syntax);
   }
 
   for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
@@ -25370,11 +25410,11 @@
     if (value == '' && (name == 'bind' || name == 'repeat')) {
       value = '{{}}';
     }
-    _parseAndBind(element, value, name, model, syntax);
+    _parseAndBind(element, name, value, model, syntax);
   });
 }
 
-void _parseAndBind(Node node, String text, String name, model,
+void _parseAndBind(Node node, String name, String text, model,
     CustomBindingSyntax syntax) {
 
   var tokens = _parseMustacheTokens(text);
@@ -25509,105 +25549,12 @@
   _removeAllBindingsRecursively(child);
 }
 
-class _InstanceCursor {
-  final Element _template;
-  Node _terminator;
-  Node _previousTerminator;
-  int _previousIndex = -1;
-  int _index = 0;
-
-  _InstanceCursor(this._template, [index]) {
-    _terminator = _template;
-    if (index != null) {
-      while (index-- > 0) {
-        next();
-      }
-    }
-  }
-
-  void next() {
-    _previousTerminator = _terminator;
-    _previousIndex = _index;
-    _index++;
-
-    while (_index > _terminator._instanceTerminatorCount) {
-      _index -= _terminator._instanceTerminatorCount;
-      _terminator = _terminator.nextNode;
-      if (_terminator is Element && _terminator.tagName == 'TEMPLATE') {
-        _index += _instanceCount(_terminator);
-      }
-    }
-  }
-
-  void abandon() {
-    assert(_instanceCount(_template) > 0);
-    assert(_terminator._instanceTerminatorCount > 0);
-    assert(_index > 0);
-
-    _terminator._instanceTerminatorCount--;
-    _index--;
-  }
-
-  void insert(fragment) {
-    assert(_template.parentNode != null);
-
-    _previousTerminator = _terminator;
-    _previousIndex = _index;
-    _index++;
-
-    _terminator = fragment.$dom_lastChild;
-    if (_terminator == null) _terminator = _previousTerminator;
-    _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode);
-
-    _terminator._instanceTerminatorCount++;
-    if (_terminator != _previousTerminator) {
-      while (_previousTerminator._instanceTerminatorCount >
-              _previousIndex) {
-        _previousTerminator._instanceTerminatorCount--;
-        _terminator._instanceTerminatorCount++;
-      }
-    }
-  }
-
-  void remove() {
-    assert(_previousIndex != -1);
-    assert(_previousTerminator != null &&
-           (_previousIndex > 0 || _previousTerminator == _template));
-    assert(_terminator != null && _index > 0);
-    assert(_template.parentNode != null);
-    assert(_instanceCount(_template) > 0);
-
-    if (_previousTerminator == _terminator) {
-      assert(_index == _previousIndex + 1);
-      _terminator._instanceTerminatorCount--;
-      _terminator = _template;
-      _previousTerminator = null;
-      _previousIndex = -1;
-      return;
-    }
-
-    _terminator._instanceTerminatorCount--;
-
-    var parent = _template.parentNode;
-    while (_previousTerminator.nextNode != _terminator) {
-      _removeTemplateChild(parent, _previousTerminator.nextNode);
-    }
-    _removeTemplateChild(parent, _terminator);
-
-    _terminator = _previousTerminator;
-    _index = _previousIndex;
-    _previousTerminator = null;
-    _previousIndex = -1;  // 0?
-  }
-}
-
 
 class _TemplateIterator {
   final Element _templateElement;
-  int instanceCount = 0;
-  List iteratedValue;
-  bool observing = false;
+  final List<Node> terminators = [];
   final CompoundBinding inputs;
+  List iteratedValue;
 
   StreamSubscription _sub;
   StreamSubscription _valueBinding;
@@ -25650,9 +25597,74 @@
     }
   }
 
-  // TODO(jmesserly): port MDV v3.
-  getInstanceModel(model, syntax) => model;
-  getInstanceFragment(syntax) => _templateElement.createInstance();
+  Node getTerminatorAt(int index) {
+    if (index == -1) return _templateElement;
+    var terminator = terminators[index];
+    if (terminator is! Element) return terminator;
+
+    var subIterator = terminator._templateIterator;
+    if (subIterator == null) return terminator;
+
+    return subIterator.getTerminatorAt(subIterator.terminators.length - 1);
+  }
+
+  void insertInstanceAt(int index, Node fragment) {
+    var previousTerminator = getTerminatorAt(index - 1);
+    var terminator = fragment.$dom_lastChild;
+    if (terminator == null) terminator = previousTerminator;
+
+    terminators.insert(index, terminator);
+    var parent = _templateElement.parentNode;
+    parent.insertBefore(fragment, previousTerminator.nextNode);
+  }
+
+  void removeInstanceAt(int index) {
+    var previousTerminator = getTerminatorAt(index - 1);
+    var terminator = getTerminatorAt(index);
+    terminators.removeAt(index);
+
+    var parent = _templateElement.parentNode;
+    while (terminator != previousTerminator) {
+      var node = terminator;
+      terminator = node.previousNode;
+      _removeTemplateChild(parent, node);
+    }
+  }
+
+  void removeAllInstances() {
+    if (terminators.length == 0) return;
+
+    var previousTerminator = _templateElement;
+    var terminator = getTerminatorAt(terminators.length - 1);
+    terminators.length = 0;
+
+    var parent = _templateElement.parentNode;
+    while (terminator != previousTerminator) {
+      var node = terminator;
+      terminator = node.previousNode;
+      _removeTemplateChild(parent, node);
+    }
+  }
+
+  void clear() {
+    unobserve();
+    removeAllInstances();
+    iteratedValue = null;
+  }
+
+  getInstanceModel(model, syntax) {
+    if (syntax != null) {
+      return syntax.getInstanceModel(_templateElement, model);
+    }
+    return model;
+  }
+
+  getInstanceFragment(syntax) {
+    if (syntax != null) {
+      return syntax.getInstanceFragment(_templateElement);
+    }
+    return _templateElement.createInstance();
+  }
 
   void _handleChanges(List<ListChangeRecord> splices) {
     var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']];
@@ -25661,9 +25673,7 @@
       if (splice is! ListChangeRecord) continue;
 
       for (int i = 0; i < splice.removedCount; i++) {
-        var cursor = new _InstanceCursor(_templateElement, splice.index + 1);
-        cursor.remove();
-        instanceCount--;
+        removeInstanceAt(splice.index);
       }
 
       for (var addIndex = splice.index;
@@ -25671,14 +25681,13 @@
           addIndex++) {
 
         var model = getInstanceModel(iteratedValue[addIndex], syntax);
+
         var fragment = getInstanceFragment(syntax);
 
         _addBindings(fragment, model, syntax);
         _addTemplateInstanceRecord(fragment, model);
 
-        var cursor = new _InstanceCursor(_templateElement, addIndex);
-        cursor.insert(fragment);
-        instanceCount++;
+        insertInstanceAt(addIndex, fragment);
       }
     }
   }
@@ -25689,31 +25698,12 @@
     _sub = null;
   }
 
-  void clear() {
-    unobserve();
-
-    iteratedValue = null;
-    if (instanceCount == 0) return;
-
-    for (var i = 0; i < instanceCount; i++) {
-      var cursor = new _InstanceCursor(_templateElement, 1);
-      cursor.remove();
-    }
-
-    instanceCount = 0;
-  }
-
   void abandon() {
     unobserve();
     _valueBinding.cancel();
     inputs.dispose();
   }
 }
-
-int _instanceCount(Element element) {
-  var templateIterator = element._templateIterator;
-  return templateIterator != null ? templateIterator.instanceCount : 0;
-}
 // Copyright (c) 2012, 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.
diff --git a/sdk/lib/html/dartium/html_dartium.dart b/sdk/lib/html/dartium/html_dartium.dart
index e549cb6..3c83672 100644
--- a/sdk/lib/html/dartium/html_dartium.dart
+++ b/sdk/lib/html/dartium/html_dartium.dart
@@ -7999,8 +7999,9 @@
   void set model(value) {
     _ensureTemplate();
 
+    var syntax = TemplateElement.syntax[attributes['syntax']];
     _model = value;
-    _addBindings(this, model);
+    _addBindings(this, model, syntax);
   }
 
   // TODO(jmesserly): const set would be better
@@ -15723,17 +15724,6 @@
 
   TemplateInstance _templateInstance;
 
-  // TODO(arv): Consider storing all "NodeRareData" on a single object?
-  int __instanceTerminatorCount;
-  int get _instanceTerminatorCount {
-    if (__instanceTerminatorCount == null) return 0;
-    return __instanceTerminatorCount;
-  }
-  set _instanceTerminatorCount(int value) {
-    if (value == 0) value = null;
-    __instanceTerminatorCount = value;
-  }
-
   /** Gets the template instance that instantiated this node, if any. */
   @Experimental
   TemplateInstance get templateInstance =>
@@ -26677,11 +26667,61 @@
 // more Dart-friendly.
 @Experimental
 abstract class CustomBindingSyntax {
+  /**
+   * This syntax method allows for a custom interpretation of the contents of
+   * mustaches (`{{` ... `}}`).
+   *
+   * When a template is inserting an instance, it will invoke this method for
+   * each mustache which is encountered. The function is invoked with four
+   * arguments:
+   *
+   * - [model]: The data context for which this instance is being created.
+   * - [path]: The text contents (trimmed of outer whitespace) of the mustache.
+   * - [name]: The context in which the mustache occurs. Within element
+   *   attributes, this will be the name of the attribute. Within text,
+   *   this will be 'text'.
+   * - [node]: A reference to the node to which this binding will be created.
+   *
+   * If the method wishes to handle binding, it is required to return an object
+   * which has at least a `value` property that can be observed. If it does,
+   * then MDV will call [Node.bind on the node:
+   *
+   *     node.bind(name, retval, 'value');
+   *
+   * If the 'getBinding' does not wish to override the binding, it should return
+   * null.
+   */
   // TODO(jmesserly): I had to remove type annotations from "name" and "node"
   // Normally they are String and Node respectively. But sometimes it will pass
   // (int name, CompoundBinding node). That seems very confusing; we may want
   // to change this API.
-  getBinding(model, String path, name, node);
+  getBinding(model, String path, name, node) => null;
+
+  /**
+   * This syntax method allows a syntax to provide an alterate model than the
+   * one the template would otherwise use when producing an instance.
+   *
+   * When a template is about to create an instance, it will invoke this method
+   * The function is invoked with two arguments:
+   *
+   * - [template]: The template element which is about to create and insert an
+   *   instance.
+   * - [model]: The data context for which this instance is being created.
+   *
+   * The template element will always use the return value of `getInstanceModel`
+   * as the model for the new instance. If the syntax does not wish to override
+   * the value, it should simply return the `model` value it was passed.
+   */
+  getInstanceModel(Element template, model) => model;
+
+  /**
+   * This syntax method allows a syntax to provide an alterate expansion of
+   * the [template] contents. When the template wants to create an instance,
+   * it will call this method with the template element.
+   *
+   * By default this will call `template.createInstance()`.
+   */
+  getInstanceFragment(Element template) => template.createInstance();
 }
 
 /** The callback used in the [CompoundBinding.combinator] field. */
@@ -27049,7 +27089,7 @@
   if (node is Element) {
     _addAttributeBindings(node, model, syntax);
   } else if (node is Text) {
-    _parseAndBind(node, node.text, 'text', model, syntax);
+    _parseAndBind(node, 'text', node.text, model, syntax);
   }
 
   for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
@@ -27063,11 +27103,11 @@
     if (value == '' && (name == 'bind' || name == 'repeat')) {
       value = '{{}}';
     }
-    _parseAndBind(element, value, name, model, syntax);
+    _parseAndBind(element, name, value, model, syntax);
   });
 }
 
-void _parseAndBind(Node node, String text, String name, model,
+void _parseAndBind(Node node, String name, String text, model,
     CustomBindingSyntax syntax) {
 
   var tokens = _parseMustacheTokens(text);
@@ -27202,105 +27242,12 @@
   _removeAllBindingsRecursively(child);
 }
 
-class _InstanceCursor {
-  final Element _template;
-  Node _terminator;
-  Node _previousTerminator;
-  int _previousIndex = -1;
-  int _index = 0;
-
-  _InstanceCursor(this._template, [index]) {
-    _terminator = _template;
-    if (index != null) {
-      while (index-- > 0) {
-        next();
-      }
-    }
-  }
-
-  void next() {
-    _previousTerminator = _terminator;
-    _previousIndex = _index;
-    _index++;
-
-    while (_index > _terminator._instanceTerminatorCount) {
-      _index -= _terminator._instanceTerminatorCount;
-      _terminator = _terminator.nextNode;
-      if (_terminator is Element && _terminator.tagName == 'TEMPLATE') {
-        _index += _instanceCount(_terminator);
-      }
-    }
-  }
-
-  void abandon() {
-    assert(_instanceCount(_template) > 0);
-    assert(_terminator._instanceTerminatorCount > 0);
-    assert(_index > 0);
-
-    _terminator._instanceTerminatorCount--;
-    _index--;
-  }
-
-  void insert(fragment) {
-    assert(_template.parentNode != null);
-
-    _previousTerminator = _terminator;
-    _previousIndex = _index;
-    _index++;
-
-    _terminator = fragment.$dom_lastChild;
-    if (_terminator == null) _terminator = _previousTerminator;
-    _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode);
-
-    _terminator._instanceTerminatorCount++;
-    if (_terminator != _previousTerminator) {
-      while (_previousTerminator._instanceTerminatorCount >
-              _previousIndex) {
-        _previousTerminator._instanceTerminatorCount--;
-        _terminator._instanceTerminatorCount++;
-      }
-    }
-  }
-
-  void remove() {
-    assert(_previousIndex != -1);
-    assert(_previousTerminator != null &&
-           (_previousIndex > 0 || _previousTerminator == _template));
-    assert(_terminator != null && _index > 0);
-    assert(_template.parentNode != null);
-    assert(_instanceCount(_template) > 0);
-
-    if (_previousTerminator == _terminator) {
-      assert(_index == _previousIndex + 1);
-      _terminator._instanceTerminatorCount--;
-      _terminator = _template;
-      _previousTerminator = null;
-      _previousIndex = -1;
-      return;
-    }
-
-    _terminator._instanceTerminatorCount--;
-
-    var parent = _template.parentNode;
-    while (_previousTerminator.nextNode != _terminator) {
-      _removeTemplateChild(parent, _previousTerminator.nextNode);
-    }
-    _removeTemplateChild(parent, _terminator);
-
-    _terminator = _previousTerminator;
-    _index = _previousIndex;
-    _previousTerminator = null;
-    _previousIndex = -1;  // 0?
-  }
-}
-
 
 class _TemplateIterator {
   final Element _templateElement;
-  int instanceCount = 0;
-  List iteratedValue;
-  bool observing = false;
+  final List<Node> terminators = [];
   final CompoundBinding inputs;
+  List iteratedValue;
 
   StreamSubscription _sub;
   StreamSubscription _valueBinding;
@@ -27343,9 +27290,74 @@
     }
   }
 
-  // TODO(jmesserly): port MDV v3.
-  getInstanceModel(model, syntax) => model;
-  getInstanceFragment(syntax) => _templateElement.createInstance();
+  Node getTerminatorAt(int index) {
+    if (index == -1) return _templateElement;
+    var terminator = terminators[index];
+    if (terminator is! Element) return terminator;
+
+    var subIterator = terminator._templateIterator;
+    if (subIterator == null) return terminator;
+
+    return subIterator.getTerminatorAt(subIterator.terminators.length - 1);
+  }
+
+  void insertInstanceAt(int index, Node fragment) {
+    var previousTerminator = getTerminatorAt(index - 1);
+    var terminator = fragment.$dom_lastChild;
+    if (terminator == null) terminator = previousTerminator;
+
+    terminators.insert(index, terminator);
+    var parent = _templateElement.parentNode;
+    parent.insertBefore(fragment, previousTerminator.nextNode);
+  }
+
+  void removeInstanceAt(int index) {
+    var previousTerminator = getTerminatorAt(index - 1);
+    var terminator = getTerminatorAt(index);
+    terminators.removeAt(index);
+
+    var parent = _templateElement.parentNode;
+    while (terminator != previousTerminator) {
+      var node = terminator;
+      terminator = node.previousNode;
+      _removeTemplateChild(parent, node);
+    }
+  }
+
+  void removeAllInstances() {
+    if (terminators.length == 0) return;
+
+    var previousTerminator = _templateElement;
+    var terminator = getTerminatorAt(terminators.length - 1);
+    terminators.length = 0;
+
+    var parent = _templateElement.parentNode;
+    while (terminator != previousTerminator) {
+      var node = terminator;
+      terminator = node.previousNode;
+      _removeTemplateChild(parent, node);
+    }
+  }
+
+  void clear() {
+    unobserve();
+    removeAllInstances();
+    iteratedValue = null;
+  }
+
+  getInstanceModel(model, syntax) {
+    if (syntax != null) {
+      return syntax.getInstanceModel(_templateElement, model);
+    }
+    return model;
+  }
+
+  getInstanceFragment(syntax) {
+    if (syntax != null) {
+      return syntax.getInstanceFragment(_templateElement);
+    }
+    return _templateElement.createInstance();
+  }
 
   void _handleChanges(List<ListChangeRecord> splices) {
     var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']];
@@ -27354,9 +27366,7 @@
       if (splice is! ListChangeRecord) continue;
 
       for (int i = 0; i < splice.removedCount; i++) {
-        var cursor = new _InstanceCursor(_templateElement, splice.index + 1);
-        cursor.remove();
-        instanceCount--;
+        removeInstanceAt(splice.index);
       }
 
       for (var addIndex = splice.index;
@@ -27364,14 +27374,13 @@
           addIndex++) {
 
         var model = getInstanceModel(iteratedValue[addIndex], syntax);
+
         var fragment = getInstanceFragment(syntax);
 
         _addBindings(fragment, model, syntax);
         _addTemplateInstanceRecord(fragment, model);
 
-        var cursor = new _InstanceCursor(_templateElement, addIndex);
-        cursor.insert(fragment);
-        instanceCount++;
+        insertInstanceAt(addIndex, fragment);
       }
     }
   }
@@ -27382,31 +27391,12 @@
     _sub = null;
   }
 
-  void clear() {
-    unobserve();
-
-    iteratedValue = null;
-    if (instanceCount == 0) return;
-
-    for (var i = 0; i < instanceCount; i++) {
-      var cursor = new _InstanceCursor(_templateElement, 1);
-      cursor.remove();
-    }
-
-    instanceCount = 0;
-  }
-
   void abandon() {
     unobserve();
     _valueBinding.cancel();
     inputs.dispose();
   }
 }
-
-int _instanceCount(Element element) {
-  var templateIterator = element._templateIterator;
-  return templateIterator != null ? templateIterator.instanceCount : 0;
-}
 // Copyright (c) 2013, 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.
diff --git a/tests/html/binding_syntax_test.dart b/tests/html/binding_syntax_test.dart
index ce1c847..0aa7732 100644
--- a/tests/html/binding_syntax_test.dart
+++ b/tests/html/binding_syntax_test.dart
@@ -5,6 +5,7 @@
 library binding_syntax_test;
 
 import 'dart:async';
+import 'dart:collection';
 import 'dart:html';
 import 'package:mdv_observe/mdv_observe.dart';
 import 'package:unittest/html_config.dart';
@@ -54,24 +55,93 @@
 
     var testSyntax = new TestBindingSyntax();
     TemplateElement.syntax['Test'] = testSyntax;
+    try {
+      var div = createTestHtml(
+          '<template bind syntax="Test">{{ foo }}' +
+          '<template bind>{{ foo }}</template></template>');
+      recursivelySetTemplateModel(div, model);
+      deliverChangeRecords();
+      expect(div.nodes.length, 4);
+      expect(div.nodes.last.text, 'bar');
+      expect(div.nodes[2].tagName, 'TEMPLATE');
+      expect(div.nodes[2].attributes['syntax'], 'Test');
 
-    var div = createTestHtml(
-        '<template bind syntax="Test">{{ foo }}' +
-        '<template bind>{{ foo }}</template></template>');
-    recursivelySetTemplateModel(div, model);
-    deliverChangeRecords();
-    expect(div.nodes.length, 4);
-    expect(div.nodes.last.text, 'bar');
-    expect(div.nodes[2].tagName, 'TEMPLATE');
-    expect(div.nodes[2].attributes['syntax'], 'Test');
+      expect(testSyntax.log, [
+        [model, '', 'bind', 'TEMPLATE'],
+        [model, 'foo', 'text', null],
+        [model, '', 'bind', 'TEMPLATE'],
+        [model, 'foo', 'text', null],
+      ]);
+    } finally {
+      TemplateElement.syntax.remove('Test');
+    }
+  });
 
-    expect(testSyntax.log, [
-      [model, 'foo', 'text', null],
-      [model, '', 'bind', 'TEMPLATE'],
-      [model, 'foo', 'text', null],
-    ]);
+  test('getInstanceModel', () {
+    var model = toObservable([{'foo': 1}, {'foo': 2}, {'foo': 3}]
+        .map(toSymbolMap));
 
-    TemplateElement.syntax.remove('Test');
+    var testSyntax = new TestModelSyntax();
+    testSyntax.altModels.addAll([{'foo': 'a'}, {'foo': 'b'}, {'foo': 'c'}]
+        .map(toSymbolMap));
+
+    TemplateElement.syntax['Test'] = testSyntax;
+    try {
+
+      var div = createTestHtml(
+          '<template repeat syntax="Test">' +
+          '{{ foo }}</template>');
+
+      var template = div.nodes[0];
+      recursivelySetTemplateModel(div, model);
+      deliverChangeRecords();
+
+      expect(div.nodes.length, 4);
+      expect(div.nodes[0].tagName, 'TEMPLATE');
+      expect(div.nodes[1].text, 'a');
+      expect(div.nodes[2].text, 'b');
+      expect(div.nodes[3].text, 'c');
+
+      expect(testSyntax.log, [
+        [template, model[0]],
+        [template, model[1]],
+        [template, model[2]],
+      ]);
+
+    } finally {
+      TemplateElement.syntax.remove('Test');
+    }
+  });
+
+  // Note: this test was original, not a port of an existing test.
+  test('getInstanceFragment', () {
+    var model = toSymbolMap({'foo': 'bar'});
+
+    var testSyntax = new WhitespaceRemover();
+    TemplateElement.syntax['Test'] = testSyntax;
+    try {
+      var div = createTestHtml(
+          '''<template bind syntax="Test">
+            {{ foo }}
+            <template bind>
+              {{ foo }}
+            </template>
+          </template>''');
+
+      recursivelySetTemplateModel(div, model);
+      deliverChangeRecords();
+
+      expect(testSyntax.trimmed, 2);
+      expect(testSyntax.removed, 1);
+
+      expect(div.nodes.length, 4);
+      expect(div.nodes.last.text, 'bar');
+      expect(div.nodes[2].tagName, 'TEMPLATE');
+      expect(div.nodes[2].attributes['syntax'], 'Test');
+
+    } finally {
+      TemplateElement.syntax.remove('Test');
+    }
   });
 
   test('Basic', () {
@@ -115,6 +185,7 @@
     var test2Log = TemplateElement.syntax['Test2'].log;
 
     expect(testLog, [
+      [model, '', 'bind', 'TEMPLATE'],
       [model, 'foo', 'text', null],
       [model, '', 'bind', 'TEMPLATE']
     ]);
@@ -126,6 +197,8 @@
   });
 }
 
+// TODO(jmesserly): mocks would be cleaner here.
+
 class TestBindingSyntax extends CustomBindingSyntax {
   var log = [];
 
@@ -134,6 +207,49 @@
   }
 }
 
+class TestModelSyntax extends CustomBindingSyntax {
+  var log = [];
+  var altModels = new ListQueue();
+
+  getInstanceModel(template, model) {
+    log.add([template, model]);
+    return altModels.removeFirst();
+  }
+}
+
+// Note: this isn't a very smart whitespace handler. A smarter one would only
+// trim indentation, not all whitespace.
+// See "trimOrCompact" in the web_ui Pub package.
+class WhitespaceRemover extends CustomBindingSyntax {
+  int trimmed = 0;
+  int removed = 0;
+
+  DocumentFragment getInstanceFragment(Element template) {
+    var instance = template.createInstance();
+    var walker = new TreeWalker(instance, NodeFilter.SHOW_TEXT);
+
+    var toRemove = [];
+    while (walker.nextNode() != null) {
+      var node = walker.currentNode;
+      var text = node.text.replaceAll('\n', '').trim();
+      if (text.length != node.text.length) {
+        if (text.length == 0) {
+          toRemove.add(node);
+        } else {
+          trimmed++;
+          node.text = text;
+        }
+      }
+    }
+
+    for (var node in toRemove) node.remove();
+    removed += toRemove.length;
+
+    return instance;
+  }
+}
+
+
 class TimesTwoSyntax extends CustomBindingSyntax {
   getBinding(model, path, name, node) {
     path = path.trim();
diff --git a/tools/VERSION b/tools/VERSION
index ee428f2..9c1bb6f 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -1,4 +1,4 @@
 MAJOR 0
 MINOR 5
 BUILD 7
-PATCH 0
+PATCH 1
diff --git a/tools/dom/src/TemplateBindings.dart b/tools/dom/src/TemplateBindings.dart
index f0a57e9..5192927 100644
--- a/tools/dom/src/TemplateBindings.dart
+++ b/tools/dom/src/TemplateBindings.dart
@@ -44,11 +44,61 @@
 // more Dart-friendly.
 @Experimental
 abstract class CustomBindingSyntax {
+  /**
+   * This syntax method allows for a custom interpretation of the contents of
+   * mustaches (`{{` ... `}}`).
+   *
+   * When a template is inserting an instance, it will invoke this method for
+   * each mustache which is encountered. The function is invoked with four
+   * arguments:
+   *
+   * - [model]: The data context for which this instance is being created.
+   * - [path]: The text contents (trimmed of outer whitespace) of the mustache.
+   * - [name]: The context in which the mustache occurs. Within element
+   *   attributes, this will be the name of the attribute. Within text,
+   *   this will be 'text'.
+   * - [node]: A reference to the node to which this binding will be created.
+   *
+   * If the method wishes to handle binding, it is required to return an object
+   * which has at least a `value` property that can be observed. If it does,
+   * then MDV will call [Node.bind on the node:
+   *
+   *     node.bind(name, retval, 'value');
+   *
+   * If the 'getBinding' does not wish to override the binding, it should return
+   * null.
+   */
   // TODO(jmesserly): I had to remove type annotations from "name" and "node"
   // Normally they are String and Node respectively. But sometimes it will pass
   // (int name, CompoundBinding node). That seems very confusing; we may want
   // to change this API.
-  getBinding(model, String path, name, node);
+  getBinding(model, String path, name, node) => null;
+
+  /**
+   * This syntax method allows a syntax to provide an alterate model than the
+   * one the template would otherwise use when producing an instance.
+   *
+   * When a template is about to create an instance, it will invoke this method
+   * The function is invoked with two arguments:
+   *
+   * - [template]: The template element which is about to create and insert an
+   *   instance.
+   * - [model]: The data context for which this instance is being created.
+   *
+   * The template element will always use the return value of `getInstanceModel`
+   * as the model for the new instance. If the syntax does not wish to override
+   * the value, it should simply return the `model` value it was passed.
+   */
+  getInstanceModel(Element template, model) => model;
+
+  /**
+   * This syntax method allows a syntax to provide an alterate expansion of
+   * the [template] contents. When the template wants to create an instance,
+   * it will call this method with the template element.
+   *
+   * By default this will call `template.createInstance()`.
+   */
+  getInstanceFragment(Element template) => template.createInstance();
 }
 
 /** The callback used in the [CompoundBinding.combinator] field. */
@@ -416,7 +466,7 @@
   if (node is Element) {
     _addAttributeBindings(node, model, syntax);
   } else if (node is Text) {
-    _parseAndBind(node, node.text, 'text', model, syntax);
+    _parseAndBind(node, 'text', node.text, model, syntax);
   }
 
   for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
@@ -430,11 +480,11 @@
     if (value == '' && (name == 'bind' || name == 'repeat')) {
       value = '{{}}';
     }
-    _parseAndBind(element, value, name, model, syntax);
+    _parseAndBind(element, name, value, model, syntax);
   });
 }
 
-void _parseAndBind(Node node, String text, String name, model,
+void _parseAndBind(Node node, String name, String text, model,
     CustomBindingSyntax syntax) {
 
   var tokens = _parseMustacheTokens(text);
@@ -569,105 +619,12 @@
   _removeAllBindingsRecursively(child);
 }
 
-class _InstanceCursor {
-  final Element _template;
-  Node _terminator;
-  Node _previousTerminator;
-  int _previousIndex = -1;
-  int _index = 0;
-
-  _InstanceCursor(this._template, [index]) {
-    _terminator = _template;
-    if (index != null) {
-      while (index-- > 0) {
-        next();
-      }
-    }
-  }
-
-  void next() {
-    _previousTerminator = _terminator;
-    _previousIndex = _index;
-    _index++;
-
-    while (_index > _terminator._instanceTerminatorCount) {
-      _index -= _terminator._instanceTerminatorCount;
-      _terminator = _terminator.nextNode;
-      if (_terminator is Element && _terminator.tagName == 'TEMPLATE') {
-        _index += _instanceCount(_terminator);
-      }
-    }
-  }
-
-  void abandon() {
-    assert(_instanceCount(_template) > 0);
-    assert(_terminator._instanceTerminatorCount > 0);
-    assert(_index > 0);
-
-    _terminator._instanceTerminatorCount--;
-    _index--;
-  }
-
-  void insert(fragment) {
-    assert(_template.parentNode != null);
-
-    _previousTerminator = _terminator;
-    _previousIndex = _index;
-    _index++;
-
-    _terminator = fragment.$dom_lastChild;
-    if (_terminator == null) _terminator = _previousTerminator;
-    _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode);
-
-    _terminator._instanceTerminatorCount++;
-    if (_terminator != _previousTerminator) {
-      while (_previousTerminator._instanceTerminatorCount >
-              _previousIndex) {
-        _previousTerminator._instanceTerminatorCount--;
-        _terminator._instanceTerminatorCount++;
-      }
-    }
-  }
-
-  void remove() {
-    assert(_previousIndex != -1);
-    assert(_previousTerminator != null &&
-           (_previousIndex > 0 || _previousTerminator == _template));
-    assert(_terminator != null && _index > 0);
-    assert(_template.parentNode != null);
-    assert(_instanceCount(_template) > 0);
-
-    if (_previousTerminator == _terminator) {
-      assert(_index == _previousIndex + 1);
-      _terminator._instanceTerminatorCount--;
-      _terminator = _template;
-      _previousTerminator = null;
-      _previousIndex = -1;
-      return;
-    }
-
-    _terminator._instanceTerminatorCount--;
-
-    var parent = _template.parentNode;
-    while (_previousTerminator.nextNode != _terminator) {
-      _removeTemplateChild(parent, _previousTerminator.nextNode);
-    }
-    _removeTemplateChild(parent, _terminator);
-
-    _terminator = _previousTerminator;
-    _index = _previousIndex;
-    _previousTerminator = null;
-    _previousIndex = -1;  // 0?
-  }
-}
-
 
 class _TemplateIterator {
   final Element _templateElement;
-  int instanceCount = 0;
-  List iteratedValue;
-  bool observing = false;
+  final List<Node> terminators = [];
   final CompoundBinding inputs;
+  List iteratedValue;
 
   StreamSubscription _sub;
   StreamSubscription _valueBinding;
@@ -710,9 +667,74 @@
     }
   }
 
-  // TODO(jmesserly): port MDV v3.
-  getInstanceModel(model, syntax) => model;
-  getInstanceFragment(syntax) => _templateElement.createInstance();
+  Node getTerminatorAt(int index) {
+    if (index == -1) return _templateElement;
+    var terminator = terminators[index];
+    if (terminator is! Element) return terminator;
+
+    var subIterator = terminator._templateIterator;
+    if (subIterator == null) return terminator;
+
+    return subIterator.getTerminatorAt(subIterator.terminators.length - 1);
+  }
+
+  void insertInstanceAt(int index, Node fragment) {
+    var previousTerminator = getTerminatorAt(index - 1);
+    var terminator = fragment.$dom_lastChild;
+    if (terminator == null) terminator = previousTerminator;
+
+    terminators.insert(index, terminator);
+    var parent = _templateElement.parentNode;
+    parent.insertBefore(fragment, previousTerminator.nextNode);
+  }
+
+  void removeInstanceAt(int index) {
+    var previousTerminator = getTerminatorAt(index - 1);
+    var terminator = getTerminatorAt(index);
+    terminators.removeAt(index);
+
+    var parent = _templateElement.parentNode;
+    while (terminator != previousTerminator) {
+      var node = terminator;
+      terminator = node.previousNode;
+      _removeTemplateChild(parent, node);
+    }
+  }
+
+  void removeAllInstances() {
+    if (terminators.length == 0) return;
+
+    var previousTerminator = _templateElement;
+    var terminator = getTerminatorAt(terminators.length - 1);
+    terminators.length = 0;
+
+    var parent = _templateElement.parentNode;
+    while (terminator != previousTerminator) {
+      var node = terminator;
+      terminator = node.previousNode;
+      _removeTemplateChild(parent, node);
+    }
+  }
+
+  void clear() {
+    unobserve();
+    removeAllInstances();
+    iteratedValue = null;
+  }
+
+  getInstanceModel(model, syntax) {
+    if (syntax != null) {
+      return syntax.getInstanceModel(_templateElement, model);
+    }
+    return model;
+  }
+
+  getInstanceFragment(syntax) {
+    if (syntax != null) {
+      return syntax.getInstanceFragment(_templateElement);
+    }
+    return _templateElement.createInstance();
+  }
 
   void _handleChanges(List<ListChangeRecord> splices) {
     var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']];
@@ -721,9 +743,7 @@
       if (splice is! ListChangeRecord) continue;
 
       for (int i = 0; i < splice.removedCount; i++) {
-        var cursor = new _InstanceCursor(_templateElement, splice.index + 1);
-        cursor.remove();
-        instanceCount--;
+        removeInstanceAt(splice.index);
       }
 
       for (var addIndex = splice.index;
@@ -731,14 +751,13 @@
           addIndex++) {
 
         var model = getInstanceModel(iteratedValue[addIndex], syntax);
+
         var fragment = getInstanceFragment(syntax);
 
         _addBindings(fragment, model, syntax);
         _addTemplateInstanceRecord(fragment, model);
 
-        var cursor = new _InstanceCursor(_templateElement, addIndex);
-        cursor.insert(fragment);
-        instanceCount++;
+        insertInstanceAt(addIndex, fragment);
       }
     }
   }
@@ -749,28 +768,9 @@
     _sub = null;
   }
 
-  void clear() {
-    unobserve();
-
-    iteratedValue = null;
-    if (instanceCount == 0) return;
-
-    for (var i = 0; i < instanceCount; i++) {
-      var cursor = new _InstanceCursor(_templateElement, 1);
-      cursor.remove();
-    }
-
-    instanceCount = 0;
-  }
-
   void abandon() {
     unobserve();
     _valueBinding.cancel();
     inputs.dispose();
   }
 }
-
-int _instanceCount(Element element) {
-  var templateIterator = element._templateIterator;
-  return templateIterator != null ? templateIterator.instanceCount : 0;
-}
diff --git a/tools/dom/templates/html/impl/impl_Element.darttemplate b/tools/dom/templates/html/impl/impl_Element.darttemplate
index 89329db..e4faf0c 100644
--- a/tools/dom/templates/html/impl/impl_Element.darttemplate
+++ b/tools/dom/templates/html/impl/impl_Element.darttemplate
@@ -839,8 +839,9 @@
   void set model(value) {
     _ensureTemplate();
 
+    var syntax = TemplateElement.syntax[attributes['syntax']];
     _model = value;
-    _addBindings(this, model);
+    _addBindings(this, model, syntax);
   }
 
   // TODO(jmesserly): const set would be better
diff --git a/tools/dom/templates/html/impl/impl_Node.darttemplate b/tools/dom/templates/html/impl/impl_Node.darttemplate
index 935794b..04b03cd 100644
--- a/tools/dom/templates/html/impl/impl_Node.darttemplate
+++ b/tools/dom/templates/html/impl/impl_Node.darttemplate
@@ -272,17 +272,6 @@
 
   TemplateInstance _templateInstance;
 
-  // TODO(arv): Consider storing all "NodeRareData" on a single object?
-  int __instanceTerminatorCount;
-  int get _instanceTerminatorCount {
-    if (__instanceTerminatorCount == null) return 0;
-    return __instanceTerminatorCount;
-  }
-  set _instanceTerminatorCount(int value) {
-    if (value == 0) value = null;
-    __instanceTerminatorCount = value;
-  }
-
   /** Gets the template instance that instantiated this node, if any. */
   @Experimental
   TemplateInstance get templateInstance =>