Re-working @UiField and Widget replacement to use DOM walking rather then IDs.

Microbenchmarks show ~10% speedup in Firefox/Webkit browsers.   AdWords saw little change on Firefox and potential 3% improvement in startup time on chrome.

Some additional constraints on UiBinders:
+ To simplify handling of paragraph, we disallow block-level fields and inside of <p> elements.
+ TD tags MUST be withing a TR to simplify DOM walking.

Review at http://gwt-code-reviews.appspot.com/241801

Review by: rjrjr@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7816 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/api-checker/config/gwt20_21userApi.conf b/tools/api-checker/config/gwt20_21userApi.conf
index 1959dcd..399a5fb 100644
--- a/tools/api-checker/config/gwt20_21userApi.conf
+++ b/tools/api-checker/config/gwt20_21userApi.conf
@@ -43,6 +43,7 @@
 :com/google/gwt/rpc/client/impl/ClientWriterFactory.java\
 :com/google/gwt/rpc/client/impl/EscapeUtil.java\
 :com/google/gwt/rpc/linker/*.java\
+:com/google/gwt/uibinder/client/UiBinderUtil.java\
 :com/google/gwt/uibinder/attributeparsers/*.java\
 :com/google/gwt/uibinder/elementparsers/*.java\
 :com/google/gwt/uibinder/testing/*.java\
diff --git a/user/src/com/google/gwt/dom/client/DOMImpl.java b/user/src/com/google/gwt/dom/client/DOMImpl.java
index dc61ea2..da3117a 100644
--- a/user/src/com/google/gwt/dom/client/DOMImpl.java
+++ b/user/src/com/google/gwt/dom/client/DOMImpl.java
@@ -240,6 +240,23 @@
     return node.nodeType;
   }-*/;
 
+  /**
+   * Get the child of a parent ignoring all text nodes that might be children of the parent.
+   * Returns null if the index is out of bounds.
+   */
+  public Node getNonTextChild(Node parent, int index) {
+    int i = 0;    
+    for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {     
+      if (child.getNodeType() == Node.ELEMENT_NODE) {
+        if (i == index) {
+          return child.cast();
+        }
+        i++;
+      }
+    }
+    return null;
+  }
+  
   public native Element getParentElement(Node node) /*-{
     var parent = node.parentNode;
     if (!parent || parent.nodeType != 1) {
@@ -361,4 +378,6 @@
   public native String toString(Element elem) /*-{
     return elem.outerHTML;
   }-*/;
+  
+ 
 }
diff --git a/user/src/com/google/gwt/dom/client/DOMImplIE6.java b/user/src/com/google/gwt/dom/client/DOMImplIE6.java
index 843f931..2f75ce9 100644
--- a/user/src/com/google/gwt/dom/client/DOMImplIE6.java
+++ b/user/src/com/google/gwt/dom/client/DOMImplIE6.java
@@ -24,6 +24,8 @@
   private static boolean isIE6;
   private static boolean isIE6Detected;
 
+  
+  
   /**
    * Check if the browser is IE6 or IE7.
    * 
@@ -84,6 +86,11 @@
         / getZoomMultiple(doc) + doc.getScrollTop());
   }
 
+  @Override 
+  public native Node getNonTextChild(Node parent, int index) /*-{
+    return parent.children[index];
+  }-*/;
+  
   @Override
   public int getScrollLeft(Element elem) {
     if (isRTL(elem)) {
diff --git a/user/src/com/google/gwt/dom/client/Element.java b/user/src/com/google/gwt/dom/client/Element.java
index d3b3692..b157a94 100644
--- a/user/src/com/google/gwt/dom/client/Element.java
+++ b/user/src/com/google/gwt/dom/client/Element.java
@@ -719,4 +719,6 @@
      // on some browsers.
      this.title = title || '';
    }-*/;
+  
+  
 }
diff --git a/user/src/com/google/gwt/dom/client/Node.java b/user/src/com/google/gwt/dom/client/Node.java
index 44ff0c8..aea2ee1 100644
--- a/user/src/com/google/gwt/dom/client/Node.java
+++ b/user/src/com/google/gwt/dom/client/Node.java
@@ -168,6 +168,16 @@
   }-*/;
 
   /**
+   * Get the child by index ignoring all text node children.
+   * Returns null if the index is out of bounds.
+   * 
+   * @param index The child index ignoring text node children
+   */
+  public final Node getNonTextChild(int index) {
+    return DOMImpl.impl.getNonTextChild(this, index);
+  }
+  
+  /**
    * The Document object associated with this node. This is also the
    * {@link Document} object used to create new nodes.
    */
@@ -183,7 +193,7 @@
   public final Element getParentElement() {
     return DOMImpl.impl.getParentElement(this);
   }
-
+  
   /**
    * The parent of this node. All nodes except Document may have a parent.
    * However, if a node has just been created and not yet added to the tree, or
diff --git a/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java b/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java
index 4b8c1ed..b6487f3 100644
--- a/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java
+++ b/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java
@@ -18,67 +18,14 @@
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.Node;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.UIObject;
 
 /**
  * Static helper methods used by UiBinder. These methods are likely to move,
  * so please don't use them for non-UiBinder code.
  */
 public class UiBinderUtil {
-  /**
-   * Temporary attachment record that keeps track of where an element was
-   * before attachment.  Use the detach method to put things back.
-   *
-   */
-  public static class TempAttachment {
-    private final Element element;
-    private final Element origParent;
-    private final Element origSibling;    
-    
-    private TempAttachment(Element origParent, Element origSibling, 
-        Element element) {
-      this.origParent = origParent;
-      this.origSibling = origSibling;
-      this.element = element;
-    }
-    
-    /**
-     * Restore to previous DOM state before attachment.
-     */
-    public void detach() {
-      // Put the panel's element back where it was.
-      if (origParent != null) {
-        origParent.insertBefore(element, origSibling);
-      } else {
-        orphan(element);
-      }
-    }
-  }  
   
   private static Element hiddenDiv;
-  
-  /**
-   * Attaches the element to the dom temporarily.  Keeps track of where it is 
-   * attached so that things can be put back latter.
-   * 
-   * @return attachment record which can be used for reverting back to previous
-   *         DOM state
-   */
-  public static TempAttachment attachToDom(Element element) {
-    // TODO(rjrjr) This is copied from HTMLPanel. Reconcile
-    ensureHiddenDiv();
-    
-    // Hang on to the panel's original parent and sibling elements so that it
-    // can be replaced.
-    Element origParent = element.getParentElement();
-    Element origSibling = element.getNextSiblingElement();
-
-    // Attach the panel's element to the hidden div.
-    hiddenDiv.appendChild(element);
-    
-    return new TempAttachment(origParent, origSibling, element);
-  }
 
   public static Element fromHtml(String html) {
     ensureHiddenDiv();
@@ -87,13 +34,57 @@
     orphan(newbie);
     return newbie;
   }
-
+  
+  public static Node getChild(Node node, int child) {
+    return node.getChild(child);
+  }
+  
+  public static Node getNonTextChild(Node node, int child) {
+    return node.getNonTextChild(child);
+  }
+  
+  public static Node getTableChild(Node node, int child) {
+    // If the table, has a tbody inside...
+    Element table = (Element)node;
+    Element firstChild = table.getNonTextChild(0).cast();
+    if ("tbody".equalsIgnoreCase(firstChild.getTagName())) {
+      return firstChild.getNonTextChild(child);
+    } else {
+      return table.getNonTextChild(child);
+    }
+  }
+  
+  public static native Element lookupNodeByTreeIndicies(Element parent, String query,
+      String xpath) /*-{
+    if (parent.querySelector) {
+      return parent.querySelector(query);
+    } else {
+      return parent.ownerDocument.evaluate(
+        xpath, parent, null, XPathResult.ANY_TYPE, null).iterateNext();
+    }
+  }-*/;
+  
+  public static native Element lookupNodeByTreeIndiciesIE(Element parent, int[] indicies) /*-{
+  var currentNode = parent;
+  for(var i = 0; i < indicies.length; i = i + 1) {
+    currentNode = currentNode.children[indicies[i]];
+  }
+  return currentNode;
+}-*/;
+  
+  public static native Element lookupNodeByTreeIndiciesUsingQuery(Element parent, String query) /*-{
+    return parent.querySelector(query);
+  }-*/; 
+  
+  public static native Element lookupNodeByTreeIndiciesUsingXpath(Element parent, String xpath) /*-{
+    return parent.ownerDocument.evaluate(
+        xpath, parent, null, XPathResult.ANY_TYPE, null).iterateNext();
+  }-*/; 
+  
   private static void ensureHiddenDiv() {
     // If the hidden DIV has not been created, create it.
     if (hiddenDiv == null) {
-      hiddenDiv = Document.get().createDivElement();
-      UIObject.setVisible(hiddenDiv, false);
-      RootPanel.getBodyElement().appendChild(hiddenDiv);
+      hiddenDiv = Document.get().createDivElement();      
     }
   }
 
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/CustomButtonParser.java b/user/src/com/google/gwt/uibinder/elementparsers/CustomButtonParser.java
index 5fade12..774e8c7 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/CustomButtonParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/CustomButtonParser.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.DomCursor;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 import com.google.gwt.uibinder.rebind.XMLElement;
 import com.google.gwt.uibinder.rebind.XMLElement.Interpreter;
@@ -67,7 +68,9 @@
 
         HtmlInterpreter interpreter = HtmlInterpreter.newInterpreterForUiObject(
             writer, fieldName);
-        String innerHtml = child.consumeInnerHtml(interpreter);
+        DomCursor cursor = writer.beginDomSection(fieldName + ".getElement()");
+        String innerHtml = child.consumeInnerHtml(interpreter, cursor);
+        writer.endDomSection();
         if (innerHtml.length() > 0) {
           writer.addStatement("%s.%s().setHTML(\"%s\");", fieldName,
               faceNameGetter(faceName), innerHtml);
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/DialogBoxParser.java b/user/src/com/google/gwt/uibinder/elementparsers/DialogBoxParser.java
index 00dfd26..36e2d54 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/DialogBoxParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/DialogBoxParser.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.DomCursor;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 import com.google.gwt.uibinder.rebind.XMLElement;
 import com.google.gwt.user.client.ui.DialogBox;
@@ -40,7 +41,9 @@
 
         HtmlInterpreter interpreter = HtmlInterpreter.newInterpreterForUiObject(
             writer, fieldName);
-        caption = child.consumeInnerHtml(interpreter);
+        DomCursor cursor = writer.beginDomSection(fieldName + ".getElement()");
+        caption = child.consumeInnerHtml(interpreter, cursor);
+        writer.endDomSection();
       } else {
         if (body != null) {
           writer.die("In %s, may have only one widget, but found %s and %s",
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/DomElementParser.java b/user/src/com/google/gwt/uibinder/elementparsers/DomElementParser.java
index 95d9745..0bdc543 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/DomElementParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/DomElementParser.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.DomCursor;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 import com.google.gwt.uibinder.rebind.XMLElement;
 
@@ -35,10 +36,12 @@
 
     interpreter.interpretElement(elem);
 
-    writer.beginAttachedSection(fieldName);
-    String html = elem.consumeOpeningTag() + elem.consumeInnerHtml(interpreter)
+    DomCursor cursor = writer.beginDomSection(fieldName);
+    
+    String html = elem.consumeOpeningTag() + elem.consumeInnerHtml(interpreter,
+        cursor)
         + elem.getClosingTag();
-    writer.endAttachedSection();
+    writer.endDomSection();
     writer.setFieldInitializer(fieldName, String.format(
         "(%1$s) UiBinderUtil.fromHtml(\"%2$s\")",
         type.getQualifiedSourceName(), html));
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java
index 8c2f3c1..71a6d18 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java
@@ -37,17 +37,8 @@
       throws UnableToCompleteException {
     String fieldName = writer.declareFieldIfNeeded(elem);
     if (fieldName != null) {
-      String token = writer.declareDomField(fieldName, element);
-
-      if (elem.hasAttribute("id")) {
-        writer.die(String.format(
-            "Cannot declare id=\"%s\" and %s=\"%s\" on the same element",
-            elem.consumeRawAttribute("id"), writer.getUiFieldAttributeName(),
-            fieldName));
-      }
-
-      elem.setAttribute("id", token);
-    }
+      writer.declareDomField(fieldName, elem.getLocalName());
+    }    
 
     /*
      * Return null because we don't want to replace the dom element with any
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/HTMLPanelParser.java b/user/src/com/google/gwt/uibinder/elementparsers/HTMLPanelParser.java
index 83d65f7..e8d0991 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/HTMLPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/HTMLPanelParser.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.uibinder.elementparsers.HtmlMessageInterpreter.PlaceholderInterpreterProvider;
+import com.google.gwt.uibinder.rebind.DomCursor;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 import com.google.gwt.uibinder.rebind.XMLElement;
 import com.google.gwt.uibinder.rebind.messages.MessageWriter;
@@ -45,10 +46,10 @@
      */
     HtmlInterpreter htmlInterpreter = makeHtmlInterpreter(fieldName, writer);
 
-    writer.beginAttachedSection(fieldName + ".getElement()");
+    DomCursor cursor = writer.beginDomSection(fieldName + ".getElement()");
     String html = elem.consumeInnerHtml(InterpreterPipe.newPipe(
-        widgetInterpreter, htmlInterpreter));
-    writer.endAttachedSection();
+        widgetInterpreter, htmlInterpreter), cursor);
+    writer.endDomSection();
 
     /*
      * HTMLPanel has no no-arg ctor, so we have to generate our own, using the
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/HasHTMLParser.java b/user/src/com/google/gwt/uibinder/elementparsers/HasHTMLParser.java
index 601c03d..80cdda9 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/HasHTMLParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/HasHTMLParser.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.DomCursor;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 import com.google.gwt.uibinder.rebind.XMLElement;
 
@@ -28,11 +29,12 @@
   public void parse(XMLElement elem, String fieldName, JClassType type,
       UiBinderWriter writer) throws UnableToCompleteException {
 
+    writer.addInitComment("HasHtmlParser.parse");
     HtmlInterpreter interpreter =
       HtmlInterpreter.newInterpreterForUiObject(writer, fieldName);
-    writer.beginAttachedSection(fieldName + ".getElement()");
-    String html = elem.consumeInnerHtml(interpreter);
-    writer.endAttachedSection();
+    DomCursor cursor = writer.beginDomSection(fieldName + ".getElement()");
+    String html = elem.consumeInnerHtml(interpreter, cursor);
+    writer.endDomSection();
     // TODO(jgw): throw an error if there's a conflicting 'html' attribute.
     if (html.trim().length() > 0) {
       writer.genStringPropertySet(fieldName, "HTML", html);
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/HtmlMessageInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/HtmlMessageInterpreter.java
index f21ac6d..24555fb 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/HtmlMessageInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/HtmlMessageInterpreter.java
@@ -73,7 +73,8 @@
       }
 
       MessageWriter message = messages.newMessage(elem);
-      message.setDefaultMessage(elem.consumeInnerHtml(phiProvider.get(message)));
+      message.setDefaultMessage(elem.consumeInnerHtml(phiProvider.get(message),
+          uiWriter.getCurrentDomCursor()));
       return uiWriter.tokenForExpression(messages.declareMessage(message));
     }
 
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/HtmlPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/HtmlPlaceholderInterpreter.java
index 0819505..3966680 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/HtmlPlaceholderInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/HtmlPlaceholderInterpreter.java
@@ -16,6 +16,7 @@
 package com.google.gwt.uibinder.elementparsers;
 
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.DomCursor;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 import com.google.gwt.uibinder.rebind.XMLElement;
 import com.google.gwt.uibinder.rebind.messages.MessageWriter;
@@ -64,12 +65,15 @@
        * This recursive innerHtml call has already been escaped. Hide it in a
        * token to avoid double escaping
        */
-      String body = tokenator.nextToken(elem.consumeInnerHtml(this));
-
+      DomCursor currentDomCursor = uiWriter.getCurrentDomCursor();
+      String body = tokenator.nextToken(elem.consumeInnerHtml(this, 
+          currentDomCursor));      
+      
       String closeTag = elem.getClosingTag();
       String closePlaceholder =
           nextPlaceholder(name + "End", closeTag, closeTag);
 
+      currentDomCursor.advanceChild();
       return openPlaceholder + body + closePlaceholder;
     }
 
@@ -79,7 +83,8 @@
   @Override
   protected String consumePlaceholderInnards(XMLElement elem)
       throws UnableToCompleteException {
-    return elem.consumeInnerHtml(fieldAndComputed);
+    return elem.consumeInnerHtml(fieldAndComputed, 
+        uiWriter.getCurrentDomCursor());
   }
 
   /**
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/StackLayoutPanelParser.java b/user/src/com/google/gwt/uibinder/elementparsers/StackLayoutPanelParser.java
index 7feb8ad..43384ad 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/StackLayoutPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/StackLayoutPanelParser.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JEnumType;
 import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.uibinder.rebind.DomCursor;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 import com.google.gwt.uibinder.rebind.XMLElement;
 import com.google.gwt.user.client.ui.StackLayoutPanel;
@@ -75,7 +76,9 @@
         HtmlInterpreter htmlInt = HtmlInterpreter.newInterpreterForUiObject(
             writer, fieldName);
         String size = children.header.consumeRequiredDoubleAttribute("size");
-        String html = children.header.consumeInnerHtml(htmlInt);
+        DomCursor cursor = writer.beginDomSection(fieldName + ".getElement()");
+        String html = children.header.consumeInnerHtml(htmlInt, cursor);
+        writer.endDomSection();
         writer.addStatement("%s.add(%s, \"%s\", true, %s);", fieldName,
             childFieldName, html, size);
       } else if (children.customHeader != null) {
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/TabLayoutPanelParser.java b/user/src/com/google/gwt/uibinder/elementparsers/TabLayoutPanelParser.java
index a6880a9..cb3aceb 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/TabLayoutPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/TabLayoutPanelParser.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JEnumType;
 import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.uibinder.rebind.DomCursor;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 import com.google.gwt.uibinder.rebind.XMLElement;
 import com.google.gwt.user.client.ui.TabLayoutPanel;
@@ -80,7 +81,9 @@
       if (children.header != null) {
         HtmlInterpreter htmlInt = HtmlInterpreter.newInterpreterForUiObject(
             writer, fieldName);
-        String html = children.header.consumeInnerHtml(htmlInt);
+        DomCursor cursor = writer.beginDomSection(fieldName + ".getElement()");
+        String html = children.header.consumeInnerHtml(htmlInt, cursor);
+        writer.endDomSection();
         writer.addStatement("%s.add(%s, \"%s\", true);", fieldName,
             childFieldName, html);
       } else if (children.customHeader != null) {
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/TabPanelParser.java b/user/src/com/google/gwt/uibinder/elementparsers/TabPanelParser.java
index 627512b..45902b8 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/TabPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/TabPanelParser.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.DomCursor;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 import com.google.gwt.uibinder.rebind.XMLElement;
 
@@ -59,7 +60,9 @@
         if (tabChild.getLocalName().equals(TAG_TABHTML)) {
           HtmlInterpreter interpreter = HtmlInterpreter.newInterpreterForUiObject(
               writer, fieldName);
-          tabHTML = tabChild.consumeInnerHtml(interpreter);
+          DomCursor cursor = writer.beginDomSection(fieldName + ".getElement()");
+          tabHTML = tabChild.consumeInnerHtml(interpreter, cursor);
+          writer.endDomSection();
         } else {
           if (childFieldName != null) {
             writer.die("%s may only have a single child widget", child);
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
index 686cb2a..6edc8e4 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
@@ -68,30 +68,26 @@
 
   public String interpretElement(XMLElement elem) 
       throws UnableToCompleteException {
-    if (uiWriter.isWidgetElement(elem)) {
-      // Allocate a local variable to hold the dom id for this widget. Note
-      // that idHolder is a local variable reference, not a string id. We
-      // have to generate the ids at runtime, not compile time, or else
-      // we'll reuse ids for any template rendered more than once.
-      String idHolder = uiWriter.declareDomIdHolder();
-      String childField = uiWriter.parseElementToField(elem);
-      uiWriter.ensureFieldAttached(fieldName);
+    if (uiWriter.isWidgetElement(elem)) { 
       
-      String elementPointer = idHolder + "Element";
+      String tag = getLegalPlaceholderTag(elem);
+      
+      String childField = uiWriter.parseElementToField(elem);
+      
+      String elementPointer = "element" + uiWriter.getUniqueId();
       uiWriter.addInitStatement(
-          "com.google.gwt.user.client.Element %s = " +
-          "com.google.gwt.dom.client.Document.get().getElementById(%s).cast();",
-          elementPointer, idHolder);
-      // Delay replacing the placeholders with the widgets until after 
-      // detachment so as not to end up attaching the widget to the DOM 
-      // unnecessarily
-      uiWriter.addDetachStatement(
+          "com.google.gwt.user.client.Element %s = %s;",
+          elementPointer, uiWriter.getDomAccessExpression(elementPointer, tag));
+
+      uiWriter.addInitStatement(
           "%1$s.addAndReplaceElement(%2$s, %3$s);", 
           fieldName, childField, elementPointer);      
 
+      
+      // Increment DOM cursor based on the tag we are adding.
+      uiWriter.getCurrentDomCursor().advanceChild();
       // Create an element to hold the widget.
-      String tag = getLegalPlaceholderTag(elem);
-      return "<" + tag + " id='\" + " + idHolder + " + \"'></" + tag + ">";
+      return "<" + tag + "></" + tag + ">";
     }
     return null;
   }
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
index 4080258..23a65a1 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.uibinder.rebind.DomCursor;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 import com.google.gwt.uibinder.rebind.XMLElement;
 import com.google.gwt.uibinder.rebind.messages.MessageWriter;
@@ -61,10 +62,10 @@
   private int serial = 0;
   private final String ancestorExpression;
   private final String fieldName;
-  private final Map<String, XMLElement> idToWidgetElement =
-    new HashMap<String, XMLElement>();
-  private final Set<String> idIsHasHTML = new HashSet<String>();
-  private final Set<String> idIsHasText = new HashSet<String>();
+  private final Map<String, XMLElement> elementHolderToWidgetElement = 
+      new HashMap<String, XMLElement>();
+  private final Set<String> elementIsHasHTML = new HashSet<String>();
+  private final Set<String> elementIsHasText = new HashSet<String>();
 
   WidgetPlaceholderInterpreter(String fieldName, UiBinderWriter writer,
       MessageWriter message, String ancestorExpression) {
@@ -81,6 +82,7 @@
       return super.interpretElement(elem);
     }
 
+    uiWriter.addInitComment("WidgetPlaceholderInterpreter.interpretElement");
     JClassType type = uiWriter.findFieldType(elem);
     TypeOracle oracle = uiWriter.getOracle();
 
@@ -90,39 +92,46 @@
       name = "widget" + (++serial);
     }
 
-    String idHolder = uiWriter.declareDomIdHolder();
-    idToWidgetElement.put(idHolder, elem);
+    DomCursor cursor = uiWriter.getCurrentDomCursor();
+    String elementHolder = cursor.getAccessExpression();
+    // We are going to generate another element right here, so we advance the child pointer.
+    cursor.advanceChild();
+    elementHolderToWidgetElement.put(elementHolder, elem);
 
     if (oracle.findType(HasHTML.class.getName()).isAssignableFrom(type)) {
-      return handleHasHTMLPlaceholder(elem, name, idHolder);
+      return handleHasHTMLPlaceholder(elem, name, elementHolder);
     }
 
     if (oracle.findType(HasText.class.getName()).isAssignableFrom(type)) {
-      return handleHasTextPlaceholder(elem, name, idHolder);
+      return handleHasTextPlaceholder(elem, name, elementHolder);
    }
 
-    return handleOpaqueWidgetPlaceholder(name, idHolder);
+    return handleOpaqueWidgetPlaceholder(name);
   }
 
+ 
   /**
    * Called by {@link XMLElement#consumeInnerHtml} after all elements
    * have been handed to {@link #interpretElement}.
    */
   @Override
   public String postProcess(String consumed) throws UnableToCompleteException {
-    for (String idHolder : idToWidgetElement.keySet()) {
-      XMLElement childElem = idToWidgetElement.get(idHolder);
+    for (Map.Entry<String, XMLElement> entry : elementHolderToWidgetElement.entrySet()) {
+      String element = entry.getKey();
+      XMLElement childElem = entry.getValue();
       String childField = uiWriter.parseElementToField(childElem);
 
-      genSetWidgetTextCall(idHolder, childField);
-      uiWriter.addInitStatement("%1$s.addAndReplaceElement(%2$s, %3$s);",
-          fieldName, childField, idHolder);
+      genSetWidgetTextCall(element, childField);
+      
+      uiWriter.addInitStatement("%1$s.addAndReplaceElement(%2$s, " +
+          "(com.google.gwt.user.client.Element)%3$s);",
+          fieldName, childField, element);
     }
     /*
      * We get used recursively, so this will be called again. Empty the map 
      * or else we'll re-register things.
      */
-    idToWidgetElement.clear();
+    elementHolderToWidgetElement.clear();
     return super.postProcess(consumed);
   }
 
@@ -132,34 +141,36 @@
     return closePlaceholder;
   }
 
-  private String genOpenTag(String name, String idHolder) {
-    String openTag = String.format("<span id='\" + %s + \"'>", idHolder);
+  private String genOpenTag(String name) {
+    String openTag = "<span>";
     String openPlaceholder =
         nextPlaceholder(name + "Begin", "<span>", openTag);
     return openPlaceholder;
   }
 
-  private void genSetWidgetTextCall(String idHolder, String childField) {
-    if (idIsHasText.contains(idHolder)) {
+  private void genSetWidgetTextCall(String elementHolder, String childField) {
+    if (elementIsHasText.contains(elementHolder)) {
       uiWriter.addInitStatement(
-          "%s.setText(%s.getElementById(%s).getInnerText());", childField,
-          fieldName, idHolder);
+          "%s.setText(((com.google.gwt.user.client.Element)%s).getInnerText());", 
+          childField, elementHolder);
     }
-    if (idIsHasHTML.contains(idHolder)) {
+    if (elementIsHasHTML.contains(elementHolder)) {
       uiWriter.addInitStatement(
-          "%s.setHTML(%s.getElementById(%s).getInnerHTML());", childField,
-          fieldName, idHolder);
+          "%s.setHTML(((com.google.gwt.user.client.Element)%s).getInnerHTML());", 
+          childField, elementHolder);
     }
   }
 
   private String handleHasHTMLPlaceholder(XMLElement elem, String name,
-      String idHolder) throws UnableToCompleteException {
-    idIsHasHTML.add(idHolder);
-    String openPlaceholder = genOpenTag(name, idHolder);
+      String elementHolder) throws UnableToCompleteException {
+    elementIsHasHTML.add(elementHolder);
+    String openPlaceholder = genOpenTag(name);
 
+    DomCursor cursor = uiWriter.beginDomSection(elementHolder);
     String body =
         elem.consumeInnerHtml(new HtmlPlaceholderInterpreter(uiWriter,
-            message, ancestorExpression));
+            message, ancestorExpression), cursor);
+    uiWriter.endDomSection();
     String bodyToken = tokenator.nextToken(body);
 
     String closePlaceholder = genCloseTag(name);
@@ -167,9 +178,9 @@
   }
 
   private String handleHasTextPlaceholder(XMLElement elem, String name,
-      String idHolder) throws UnableToCompleteException {
-    idIsHasText.add(idHolder);
-    String openPlaceholder = genOpenTag(name, idHolder);
+      String elementHolder) throws UnableToCompleteException {
+    elementIsHasText.add(elementHolder);
+    String openPlaceholder = genOpenTag(name);
 
     String body =
         elem.consumeInnerText(new TextPlaceholderInterpreter(uiWriter,
@@ -180,8 +191,8 @@
     return openPlaceholder + bodyToken + closePlaceholder;
   }
 
-  private String handleOpaqueWidgetPlaceholder(String name, String idHolder) {
-    String tag = String.format("<span id='\" + %s + \"'></span>", idHolder);
+  private String handleOpaqueWidgetPlaceholder(String name) {
+    String tag = "<span></span>";
     String placeholder = nextPlaceholder(name, "<span></span>", tag);
     return placeholder;
   }
diff --git a/user/src/com/google/gwt/uibinder/rebind/DomCursor.java b/user/src/com/google/gwt/uibinder/rebind/DomCursor.java
new file mode 100644
index 0000000..76e3f00
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/DomCursor.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * DOM Cursor keeps track of a given path in the DOM.  This is useful for
+ * plucking out nodes from a DOM tree.
+ */
+public class DomCursor {
+  
+  private static class PathComponent {    
+    
+    private int childIndex;
+    private boolean isTableWithoutTbody;
+    
+    public PathComponent(XMLElement element) {
+      this.childIndex = 0;
+      this.isTableWithoutTbody = "table".equalsIgnoreCase(element.getLocalName());
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) return true;
+      if (obj == null) return false;
+      if (!(obj instanceof PathComponent)) return false;
+      PathComponent other = (PathComponent) obj;      
+      if (childIndex != other.childIndex) return false;
+      if (isTableWithoutTbody != other.isTableWithoutTbody) return false;
+      return true;
+    }
+    
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;      
+      result = prime * result + childIndex;
+      result = prime * result + (isTableWithoutTbody ? 1231 : 1237);
+      return result;
+    }
+    
+    public void incrementIndex() {
+      childIndex++;
+    }    
+  }
+  
+  private class ParagraphTracking {
+    private LinkedList<Boolean> paragraphUnSafeNodes = new LinkedList<Boolean>();
+    
+    public void beginUnsafeTag() {
+      paragraphUnSafeNodes.addLast(true);
+    }
+    
+    public void endUnsafeTag() {
+      paragraphUnSafeNodes.removeLast();
+    }
+    
+    public boolean isSafeForField() {
+      return paragraphUnSafeNodes.isEmpty();
+    }
+  }
+  
+  // Tags that cause P tags to end weirdly violating XHTML 
+  // http://dev.w3.org/html5/spec/Overview.html#parsing-main-inbody
+  private static final Set<String> PARAGRAPH_UNSAFE_NODES = new HashSet<String>(Arrays.asList(
+    new String[] {"address", "article", "aside", "blockquote", "center", "details", "dir", "div", 
+    "dl", "fieldset", "figure", "footer", "header", "hgroup", "menu", "nav", "ol", "p", "section",
+    "ul"}));
+  private static final Set<String> TABLE_SECTIONS = new HashSet<String>(Arrays.asList(new String[] {
+    "thead", "tbody", "tfoot"}));
+  private static final String NON_TEXT_LOOKUP_METHOD = "UiBinderUtil.getNonTextChild";
+  private static final String STANDARD_LOOKUP_METHOD = "UiBinderUtil.getChild";
+  private static final String TABLE_NON_TEXT_LOOKUP_METHOD = "UiBinderUtil.getTableChild";
+
+  // Cache of path to a local variable that contains that.
+  private Map<LinkedList<PathComponent>, String> domPathCache = 
+    new HashMap<LinkedList<PathComponent>, String>();
+  private LinkedList<ParagraphTracking> paragraphs = new LinkedList<ParagraphTracking>();
+  private final String parent;
+  private LinkedList<PathComponent> pathComponents = new LinkedList<PathComponent>();
+  private boolean preserveWhitespaceNodes = true;
+  private boolean walkingTextChildNodes = false;
+  private final UiBinderWriter writer;
+  
+  public DomCursor(String parent, UiBinderWriter writer) {
+    this.parent = parent;   
+    this.writer = writer;
+  }
+  
+  public void advanceChild() {
+    pathComponents.getLast().incrementIndex();
+    writer.addInitComment("advance DomCursor %s", this.toString());
+  }
+  
+  public void advanceChildForWhitespaceText() {
+    if (preserveWhitespaceNodes) {
+      advanceTextChild();
+    }
+  }
+  
+  public void advanceTextChild() {
+    if (walkingTextChildNodes) {
+      advanceChild();
+    }
+  }
+  
+  /**
+   * Finish visiting this subtree of the DOM.
+   */
+  public void finishChild(XMLElement elem) {
+    if (!writer.getMessages().isMessage(elem) &&
+        !isPlaceholderElement(elem)) {
+      pathComponents.removeLast();
+      writer.addInitComment("finish DomCursor %s", this.toString());
+      String tag = elem.getLocalName();
+      if ("p".equalsIgnoreCase(tag)) {
+        endParagraph();
+      }
+      if (isInsideParagraph() && isUnsafeParagraphTag(tag)) {
+        paragraphs.getLast().endUnsafeTag();        
+      }      
+    }
+  }
+  
+
+  public String getAccessExpression() throws UnableToCompleteException {
+    return getAccessExpression(null, null);
+  }
+  
+  /**
+   * Returns a Java expression for referencing the given node.
+   * 
+   * @param localVar Optional variable that will be used to cache the result of the expression.
+   *        If there is no applicable local variable, callers can pass null.
+   *
+   * @param tag Optional tag name of the tag that would be referenced.  This can be null.
+   * @return Java access expression.
+   * @throws UnableToCompleteException 
+   */
+  public String getAccessExpression(String localVar, String tag) 
+      throws UnableToCompleteException {
+    // P tags cause all sorts of problems with hierarchy as the HTML spec has lots of weird
+    // semantics for block elements inside of P tags.
+    if (!safeForExpression()) {
+      writer.die("UiBinder no longer allows certain addressable  " +
+            "elements inside of <p> tags because of browser " +
+            "inconsistency, consider using DIV instead");
+    }
+    
+    String result = getDomWalkAccessExpression(tag);
+    if (localVar != null) {
+      domPathCache.put(new LinkedList<PathComponent>(pathComponents), localVar);
+      return result;
+    }
+    
+    String varName = "intermediate" + writer.getUniqueId();
+    writer.addInitStatement("com.google.gwt.dom.client.Node %s = %s;", varName, result); 
+    domPathCache.put(new LinkedList<PathComponent>(pathComponents), varName);
+    return varName;
+  }
+  
+  /**
+   * Visit a child subtree of the DOM.
+   * @throws UnableToCompleteException 
+   */
+  public void visitChild(XMLElement elem) throws UnableToCompleteException {
+    if (!writer.getMessages().isMessage(elem) &&
+        !isPlaceholderElement(elem)) {
+      // If we do see an actual tbody in the uibinder, we should stop trying to account for the
+      // automatic one that the browser will insert.
+      String tag = elem.getLocalName();
+      if ("tbody".equalsIgnoreCase(tag)) {
+        pathComponents.getLast().isTableWithoutTbody = false;
+      }
+      if ("td".equalsIgnoreCase(tag) && !"tr".equalsIgnoreCase(elem.getParent().getLocalName())) {
+        writer.die("TD tags must be inside of a TR tag");
+      }
+      pathComponents.addLast(new PathComponent(elem));
+      writer.addInitComment("visit DomCursor %s", this.toString());
+      
+      if (isInsideParagraph() && isUnsafeParagraphTag(tag)) {
+        paragraphs.getLast().beginUnsafeTag();        
+      }
+      if ("p".equalsIgnoreCase(tag)) {
+        beginParagraph();
+      }
+    }    
+  }
+  
+  private void beginParagraph() {
+    paragraphs.addLast(new ParagraphTracking());
+  }
+  
+  private void endParagraph() {
+    paragraphs.removeLast();
+  }
+  
+  private String getDomWalkAccessExpression(String tag) {  
+    writer.addInitComment("DomWalkAccess %s", this.toString());
+    
+    // First look and see if we have any part of the path in our variable cache.
+    int end = pathComponents.size();
+    String var = null;
+    for (; end > 0; --end) {
+      var = domPathCache.get(pathComponents.subList(0, end));
+      if (var != null) {
+        break;
+      }
+    }
+    
+    // Next, do the remaining DOM walking
+    StringBuilder builder = new StringBuilder();
+    if (var != null) {
+      builder.append(var);
+    } else {
+      builder.append(parent);
+    }
+    for (int i = end; i < pathComponents.size(); ++i) {
+      PathComponent component = pathComponents.get(i);
+      if (i < (pathComponents.size() - 1)) {
+        // For partial paths, create an intermediate variable that can be reused
+        // by other elements that need to walk.
+        String varName = "intermediate" + writer.getUniqueId();
+        writer.addInitStatement("com.google.gwt.dom.client.Node %s = %s(%s, %d);", 
+            varName, getLookupMethod(component, tag), builder.toString(), 
+            component.childIndex);
+        domPathCache.put(new LinkedList<PathComponent>(pathComponents.subList(0, i + 1)), varName);
+        builder = new StringBuilder(varName);
+      } else {
+        builder.insert(0, getLookupMethod(component, tag) + "(");
+        builder.append(", ").append(component.childIndex).append(")");        
+      }
+    }
+    builder.append(".cast()");
+    return builder.toString();
+  }
+  
+  private String getLookupMethod(PathComponent component, String tag) {
+    if (walkingTextChildNodes) {
+      return STANDARD_LOOKUP_METHOD;
+    }
+    if (component.isTableWithoutTbody && !isValidDirectTableChild(tag)) {
+      return TABLE_NON_TEXT_LOOKUP_METHOD;
+    }
+    return NON_TEXT_LOOKUP_METHOD;
+  }
+  
+  private String getQuery() {
+    StringBuilder query = new StringBuilder();
+
+    for (PathComponent component : pathComponents) {
+      if (query.length() > 0) {
+        query.append(" > ");
+      }
+      query.append(":nth-child(").append(component.childIndex + 1).append(")");
+    }
+    return query.toString();
+  }
+  
+  /**
+   * Get an access expression using XPATH or CSS query.  This is currently disabled as it isn't
+   * as fast as walking by hand.
+   * @return
+   */
+  private String getQueryAccessExpression() {  
+    StringBuilder builder = new StringBuilder("UiBinderUtil.lookupNodeByTreeIndicies(");
+    builder.append(parent);
+    builder.append(",\"");
+    builder.append(getQuery());
+    builder.append("\",\"");
+    builder.append(getXpath());
+    builder.append("\").cast()");
+    return builder.toString();
+  }
+  
+  private String getXpath() {
+    StringBuilder xpath = new StringBuilder();
+    for (PathComponent component : pathComponents) {        
+      xpath.append("/*[").append(component.childIndex + 1).append("]");
+    }
+    return xpath.toString();
+  }
+  
+  private boolean isInsideParagraph() {
+    return !paragraphs.isEmpty();
+  }
+
+  private boolean isPlaceholderElement(XMLElement elem) {
+    return "ph".equalsIgnoreCase(elem.getLocalName());
+  }
+  
+  private boolean isUnsafeParagraphTag(String tag) {
+    return PARAGRAPH_UNSAFE_NODES.contains(tag.toLowerCase());
+  }
+  
+  private boolean isValidDirectTableChild(String tag) {
+    return tag != null && TABLE_SECTIONS.contains(tag.toLowerCase());
+  }
+  
+  private boolean safeForExpression() {
+    for (ParagraphTracking tracking : paragraphs) {
+      if (!tracking.isSafeForField()) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java b/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java
index 4eae5dd..5fc6d17 100644
--- a/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java
+++ b/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2008 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -19,38 +19,62 @@
 import com.google.gwt.uibinder.rebind.XMLElement.Interpreter;
 
 import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
 
 class GetInnerHtmlVisitor extends GetEscapedInnerTextVisitor {
 
   /**
    * Recursively gathers an HTML representation of the children of the given
-   * Elem, and stuffs it into the given StringBuffer. Applies the interpreter to
+   * Element, and stuffs it into the given StringBuffer. Applies the interpreter to
    * each descendant, and uses the writer to report errors.
    */
   public static void getEscapedInnerHtml(Element elem, StringBuffer buffer,
-      Interpreter<String> interpreter, XMLElementProvider writer)
-      throws UnableToCompleteException {
-    new ChildWalker().accept(elem, new GetInnerHtmlVisitor(buffer, interpreter,
-        writer));
+      Interpreter<String> interpreter, XMLElementProvider writer, DomCursor domCursor, 
+      MortalLogger logger)
+      throws UnableToCompleteException {   
+    XMLElement xmlElement = writer.get(elem);
+    domCursor.visitChild(xmlElement);
+    new ChildWalker().accept(elem, new GetInnerHtmlVisitor(buffer, interpreter, writer, domCursor,
+        logger));
+    domCursor.finishChild(xmlElement);
   }
+  
+  private DomCursor domCursor;
+  private final MortalLogger logger;
 
-  private GetInnerHtmlVisitor(StringBuffer buffer,
-      Interpreter<String> interpreter, XMLElementProvider writer) {
+  private GetInnerHtmlVisitor(StringBuffer buffer, Interpreter<String> interpreter,
+      XMLElementProvider writer, DomCursor domCursor, MortalLogger logger) {
     super(buffer, interpreter, writer);
+    this.domCursor = domCursor;
+    this.logger = logger;
   }
-
+  
   @Override
   public void visitElement(Element elem) throws UnableToCompleteException {
     XMLElement xmlElement = elementProvider.get(elem);
     String replacement = interpreter.interpretElement(xmlElement);
+
     if (replacement != null) {
       buffer.append(replacement);
       return;
     }
 
-    // TODO(jgw): Ditch the closing tag when there are no children.
-    buffer.append(xmlElement.consumeOpeningTag());
-    getEscapedInnerHtml(elem, buffer, interpreter, elementProvider);
-    buffer.append(xmlElement.getClosingTag());
+    Node parent = elem.getParentNode();        
+    buffer.append(xmlElement.consumeOpeningTag());    
+    getEscapedInnerHtml(elem, buffer, interpreter, elementProvider, domCursor, logger);
+    buffer.append(xmlElement.getClosingTag());       
+    domCursor.advanceChild();
+  }
+
+  @Override
+  public void visitText(Text t) {
+    int startLength = buffer.length();
+    super.visitText(t);
+    if (buffer.length() != startLength && !buffer.toString().matches("^\\s*$")) {
+      if (buffer.toString().substring(startLength).matches("^\\s*$")) {
+        domCursor.advanceTextChild();
+      }
+    }
   }
 }
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index a013d27..a7ba90a 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -105,7 +105,7 @@
     text = text.replaceAll(">", "&gt;");
 
     if (!preserveWhitespace) {
-      text = text.replaceAll("\\s+", " ");
+      text = text.replaceAll("\\s+", " ");      
     }
 
     return escapeTextForJavaStringLiteral(text);
@@ -201,22 +201,13 @@
   private String gwtPrefix;
 
   private String rendered;
-
+  
   /**
    * Stack of element variable names that have been attached.
    */
-  private final LinkedList<String> attachSectionElements = new LinkedList<String>();
-  /**
-   * Maps from field element name to the temporary attach record variable name.
-   */
-  private final Map<String, String> attachedVars = new HashMap<String, String>();
-  private int nextAttachVar = 0;
-
-  /**
-   * Stack of statements to be executed after we detach the current attach
-   * section.
-   */
-  private final LinkedList<List<String>> detachStatementsStack = new LinkedList<List<String>>();
+  private final LinkedList<DomCursor> domSectionElements = 
+      new LinkedList<DomCursor>();
+  
   private final AttributeParsers attributeParsers;
   private final BundleAttributeParsers bundleParsers;
 
@@ -267,18 +258,13 @@
   }
 
   /**
-   * Add a statement to be executed right after the current attached element is
-   * detached. This is useful for doing things that might be expensive while the
-   * element is attached to the DOM.
-   * 
+   * Adds a comment in the current init stream.  Useful for debugging generated code.
    * @param format
-   * @param args
-   * @see #beginAttachedSection(String)
+   * @param params
    */
-  public void addDetachStatement(String format, Object... args) {
-    detachStatementsStack.getFirst().add(String.format(format, args));
+  public void addInitComment(String format, Object... params) {
+    addInitStatement("// " + format, params);
   }
-
   /**
    * Add a statement to be run after everything has been instantiated, in the
    * style of {@link String#format}.
@@ -296,50 +282,35 @@
   }
 
   /**
-   * Begin a section where a new attachable element is being parsed--that is,
-   * one that will be constructed as a big innerHTML string, and then briefly
-   * attached to the dom to allow fields accessing its to be filled (at the
+   * Begin a section where a new DOM tree is being parsed--that is,
+   * one that will be constructed as a big innerHTML string, and then walked to
+   * allow fields accessing its to be filled (at the
    * moment, HasHTMLParser, HTMLPanelParser, and DomElementParser.).
    * <p>
-   * Succeeding calls made to {@link #ensureAttached} and
-   * {@link #ensureFieldAttached} must refer to children of this element, until
-   * {@link #endAttachedSection} is called.
    * 
    * @param element Java expression for the generated code that will return the
-   *          dom element to be attached.
+   *          DOM element to be attached.
    */
-  public void beginAttachedSection(String element) {
-    attachSectionElements.addFirst(element);
-    detachStatementsStack.addFirst(new ArrayList<String>());
+  public DomCursor beginDomSection(String element) {
+    DomCursor cursor = new DomCursor(element, this);
+    domSectionElements.addFirst(cursor);
+    addInitComment("writer.beginAttachedSection %s", cursor.toString());
+    return cursor;
   }
 
   /**
-   * Declare a field that will hold an Element instance. Returns a token that
-   * the caller must set as the id attribute of that element in whatever
-   * innerHTML expression will reproduce it at runtime.
-   * <P>
-   * In the generated code, this token will be replaced by an expression to
-   * generate a unique dom id at runtime. Further code will be generated to be
-   * run after widgets are instantiated, to use that dom id in a getElementById
-   * call and assign the Element instance to its field.
+   * Declare a field that will hold an Element instance. 
    * 
    * @param fieldName The name of the field being declared
-   * @param parentElementExpression an expression to be evaluated at runtime,
-   *          which will return an Element that is an ancestor of this one
-   *          (needed by the getElementById call mentioned above).
+   * @param tag The tag name of the DOM element that is being referenced.
+   * @throws UnableToCompleteException 
    */
-  public String declareDomField(String fieldName, String parentElementExpression)
-      throws UnableToCompleteException {
-    ensureAttached(parentElementExpression);
-    String name = declareDomIdHolder();
+  public void declareDomField(String fieldName, String tag) throws UnableToCompleteException {    
     setFieldInitializer(fieldName, "null");
-    addInitStatement(
-        "%s = com.google.gwt.dom.client.Document.get().getElementById(%s).cast();",
-        fieldName, name);
-    addInitStatement("%s.removeAttribute(\"id\");", fieldName);
-    return tokenForExpression(name);
+    addInitStatement("%s = %s;", fieldName, 
+        domSectionElements.getFirst().getAccessExpression(fieldName, tag));
   }
-
+  
   /**
    * Declare a variable that will be filled at runtime with a unique id, safe
    * for use as a dom element's id attribute.
@@ -347,13 +318,15 @@
    * @return that variable's name.
    */
   public String declareDomIdHolder() throws UnableToCompleteException {
-    String domHolderName = "domId" + domId++;
+    String domHolderName = "domId" + getUniqueId();
     FieldWriter domField = fieldManager.registerField(
         oracle.findType(String.class.getName()), domHolderName);
     domField.setInitializer("com.google.gwt.dom.client.Document.get().createUniqueId()");
     return domHolderName;
   }
 
+
+  
   /**
    * Declares a field of the given type name, returning the name of the declared
    * field. If the element has a field or id attribute, use its value.
@@ -427,48 +400,11 @@
    * End the current attachable section. This will detach the element if it was
    * ever attached and execute any detach statements.
    * 
-   * @see #beginAttachedSection(String)
+   * @see #beginDomSection(String)
    */
-  public void endAttachedSection() {
-    String elementVar = attachSectionElements.removeFirst();
-    List<String> detachStatements = detachStatementsStack.removeFirst();
-    if (attachedVars.containsKey(elementVar)) {
-      String attachedVar = attachedVars.remove(elementVar);
-      addInitStatement("%s.detach();", attachedVar);
-      for (String statement : detachStatements) {
-        addInitStatement(statement);
-      }
-    }
-  }
-
-  /**
-   * Ensure that the specified element is attached to the DOM.
-   * 
-   * @param element variable name of element to be attached
-   * @see #beginAttachedSection(String)
-   */
-  public void ensureAttached(String element) {
-    String attachSectionElement = attachSectionElements.getFirst();
-    if (!attachedVars.containsKey(attachSectionElement)) {
-      String attachedVar = "attachRecord" + nextAttachVar;
-      addInitStatement(
-          "UiBinderUtil.TempAttachment %s = UiBinderUtil.attachToDom(%s);",
-          attachedVar, attachSectionElement);
-      attachedVars.put(attachSectionElement, attachedVar);
-      nextAttachVar++;
-    }
-  }
-
-  /**
-   * Ensure that the specified field is attached to the DOM. The field must hold
-   * an object that responds to Element getElement(). Convenience wrapper for
-   * {@link ensureAttached}<code>(field + ".getElement()")</code>.
-   * 
-   * @param field variable name of the field to be attached
-   * @see #beginAttachedSection(String)
-   */
-  public void ensureFieldAttached(String field) {
-    ensureAttached(field + ".getElement()");
+  public void endDomSection() {
+    DomCursor cursor = domSectionElements.removeFirst();
+    addInitComment("writer.endAttachedSection %s", cursor.toString());
   }
 
   /**
@@ -536,14 +472,35 @@
   public ImplicitClientBundle getBundleClass() {
     return bundleClass;
   }
+  
+  /**
+   * Get the current cursor for the DOM.  This can be used for accessing expressions, or walking
+   * subtrees.
+   */
+  public DomCursor getCurrentDomCursor() {
+    return domSectionElements.getFirst();
+  }
 
   /**
+   * Returns an expression for accessing the current position in the DOM.
+   * @param varName optional variable where the result of this expression will be stored for 
+   *        cacheing
+   * @param tag optional name of the HTML tag we are using the expression for.
+   * @throws UnableToCompleteException 
+   */
+  public String getDomAccessExpression(String varName, String tag) 
+      throws UnableToCompleteException {
+    addInitComment("getDomAccessExpression %s", domSectionElements.getFirst().toString());
+    return domSectionElements.getFirst().getAccessExpression(varName, tag);
+  }
+  
+  /**
    * @return The logger, at least until we get get it handed off to parsers via
    *         constructor args.
    */
   public MortalLogger getLogger() {
     return logger;
-  }
+  }  
 
   /**
    * Get the {@link MessagesWriter} for this UI, generating it if necessary.
@@ -567,6 +524,10 @@
     return gwtPrefix + ":field";
   }
 
+  public int getUniqueId() {
+    return domId++;
+  }
+  
   public boolean isBinderElement(XMLElement elem) {
     String uri = elem.getNamespaceUri();
     return uri != null && UiBinderGenerator.BINDER_URI.equals(uri);
@@ -723,15 +684,11 @@
    * 
    * @throws UnableToCompleteException
    */
-  private void ensureAttachmentCleanedUp() throws UnableToCompleteException {
-    if (!attachSectionElements.isEmpty()) {
+  private void ensureCursorCleanedUp() {
+    if (!domSectionElements.isEmpty()) {
       throw new IllegalStateException("Attachments not cleaned up: "
-          + attachSectionElements);
-    }
-    if (!detachStatementsStack.isEmpty()) {
-      throw new IllegalStateException("Detach not cleaned up: "
-          + detachStatementsStack);
-    }
+          + domSectionElements);
+    }    
   }
 
   /**
@@ -897,7 +854,7 @@
         new PrintWriter(stringWriter));
     writeBinder(niceWriter, rootField);
 
-    ensureAttachmentCleanedUp();
+    ensureCursorCleanedUp();
     return stringWriter.toString();
   }
 
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
index b9b9377..ae1c1db 100644
--- a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
@@ -379,13 +379,15 @@
    * @param interpreter Called for each element, expected to return a string
    *          replacement for it, or null if it should be left as is
    */
-  public String consumeInnerHtml(Interpreter<String> interpreter)
+  public String consumeInnerHtml(Interpreter<String> interpreter,
+      DomCursor domCursor)
       throws UnableToCompleteException {
     if (interpreter == null) {
       throw new NullPointerException("interpreter must not be null");
     }
     StringBuffer buf = new StringBuffer();
-    GetInnerHtmlVisitor.getEscapedInnerHtml(elem, buf, interpreter, provider);
+    GetInnerHtmlVisitor.getEscapedInnerHtml(elem, buf, interpreter, provider,
+        domCursor, logger);
 
     clearChildren(elem);
     return buf.toString().trim();
@@ -395,9 +397,11 @@
    * Refines {@link #consumeInnerHtml(Interpreter)} to handle
    * PostProcessingInterpreter.
    */
-  public String consumeInnerHtml(PostProcessingInterpreter<String> interpreter)
+  public String consumeInnerHtml(PostProcessingInterpreter<String> interpreter,
+      DomCursor domCursor)
       throws UnableToCompleteException {
-    String html = consumeInnerHtml((Interpreter<String>) interpreter);
+    String html = consumeInnerHtml((Interpreter<String>) interpreter, 
+        domCursor);
     return interpreter.postProcess(html);
   }
 
diff --git a/user/test/com/google/gwt/dom/client/ElementTest.java b/user/test/com/google/gwt/dom/client/ElementTest.java
index 5806456..cb7dfc6 100644
--- a/user/test/com/google/gwt/dom/client/ElementTest.java
+++ b/user/test/com/google/gwt/dom/client/ElementTest.java
@@ -286,6 +286,23 @@
     assertEquals("foo", nodes.getItem(0).getInnerText());
     assertEquals("bar", nodes.getItem(1).getInnerText());
   }
+  
+  public void testGetNonTextElement() {
+    DivElement div = Document.get().createDivElement();
+    Text text1 = Document.get().createTextNode("my text");
+    DivElement innerDiv = Document.get().createDivElement();
+    Text text2 = Document.get().createTextNode(" ");
+    SpanElement span = Document.get().createSpanElement();
+    Text text3 = Document.get().createTextNode("my text2");
+    div.appendChild(text1);
+    div.appendChild(innerDiv);
+    div.appendChild(text2);
+    div.appendChild(span);
+    div.appendChild(text3);
+    
+    assertEquals(innerDiv, div.getNonTextChild(0));
+    assertEquals(span, div.getNonTextChild(1));
+  }
 
   public void testHasAttribute() {
     DivElement div = Document.get().createDivElement();
diff --git a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
index e828dba..58e834c 100644
--- a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
+++ b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
@@ -31,6 +31,7 @@
 import com.google.gwt.uibinder.elementparsers.StackLayoutPanelParserTest;
 import com.google.gwt.uibinder.elementparsers.TabLayoutPanelParserTest;
 import com.google.gwt.uibinder.elementparsers.UIObjectParserTest;
+import com.google.gwt.uibinder.rebind.DomCursorTest;
 import com.google.gwt.uibinder.rebind.FieldWriterOfGeneratedCssResourceTest;
 import com.google.gwt.uibinder.rebind.GwtResourceEntityResolverTest;
 import com.google.gwt.uibinder.rebind.HandlerEvaluatorTest;
@@ -51,6 +52,7 @@
     TestSuite suite = new TestSuite("UiBinder tests that require the JRE");
 
     // rebind
+    suite.addTestSuite(DomCursorTest.class);
     suite.addTestSuite(FieldWriterOfGeneratedCssResourceTest.class);
     suite.addTestSuite(GwtResourceEntityResolverTest.class);
     suite.addTestSuite(HandlerEvaluatorTest.class);
diff --git a/user/test/com/google/gwt/uibinder/client/UiBinderUtilTest.java b/user/test/com/google/gwt/uibinder/client/UiBinderUtilTest.java
index 4eded61..1fbfe60 100644
--- a/user/test/com/google/gwt/uibinder/client/UiBinderUtilTest.java
+++ b/user/test/com/google/gwt/uibinder/client/UiBinderUtilTest.java
@@ -97,10 +97,10 @@
   }
 
   private void findAndAssertTextBeforeFirstChild(Element div, String id,
-      String firstText) {
-    UiBinderUtil.TempAttachment t = UiBinderUtil.attachToDom(div);
+      String firstText) {    
+    Document.get().getBody().appendChild(div);
     Element child = Document.get().getElementById(id);
-    t.detach();
+    Document.get().getBody().removeChild(div);    
     assertStartsWith(child.getInnerHTML(), firstText + "<");
   }
 
@@ -146,9 +146,9 @@
       findAndAssertTextBeforeFirstChild(div, ableId, ableText);
       findAndAssertTextBeforeFirstChild(div, bakerId, bakerText);
       findAndAssertTextBeforeFirstChild(div, charlieId, charlieText);
-      UiBinderUtil.TempAttachment t = UiBinderUtil.attachToDom(div);
+      Document.get().getBody().appendChild(div);
       Element e = Document.get().getElementById(deltaId);
-      t.detach();
+      Document.get().getBody().removeChild(div);
       assertEquals(deltaText, e.getInnerText());
     } finally {
       // tearDown isn't reliable enough, e.g. doesn't fire when exceptions
diff --git a/user/test/com/google/gwt/uibinder/rebind/DomCursorTest.java b/user/test/com/google/gwt/uibinder/rebind/DomCursorTest.java
new file mode 100644
index 0000000..ccc4bbd
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/DomCursorTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+
+import junit.framework.TestCase;
+
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+
+/**
+ * Test for DomCuror.
+ */
+public class DomCursorTest extends TestCase {
+
+  private static final String PARENT = "parent";
+  
+  private UiBinderWriter writer;
+  private DomCursor cursor;
+  
+  @Override
+  public void setUp() {
+    writer = org.easymock.classextension.EasyMock.createMock(
+        UiBinderWriter.class);
+    org.easymock.classextension.EasyMock.expect(writer.getUniqueId()).andStubAnswer(new IAnswer<Integer>() {
+      private int nextId = 1;
+      @Override
+      public Integer answer() throws Throwable {
+        return nextId++;
+      }      
+    });
+    MessagesWriter message = new MessagesWriter("ui", null, "", "", "");
+    org.easymock.classextension.EasyMock.expect(writer.getMessages()).andStubReturn(message);
+    writer.addInitComment((String) EasyMock.notNull(), EasyMock.notNull());
+    org.easymock.classextension.EasyMock.expectLastCall().asStub();
+    cursor = new DomCursor(PARENT, writer);
+  }
+  
+  public void testAccessExpressions() throws Exception {    
+    verifyInitAssignment(writer, "UiBinderUtil.getNonTextChild(parent, 0).cast()", 1);    
+    verifyInitAssignment(writer, "UiBinderUtil.getNonTextChild(parent, 1).cast()", 2);
+    verifyInitAssignment(writer, 
+        "UiBinderUtil.getNonTextChild(intermediate2, 0).cast()", 3);
+    verifyInitAssignment(writer, "UiBinderUtil.getNonTextChild(parent, 2).cast()", 4);    
+    org.easymock.classextension.EasyMock.replay(writer);
+    
+    // parent
+    cursor.visitChild(makeElement("div")); 
+    // parent's first child
+    assertEquals(intermediate(1), cursor.getAccessExpression());  
+
+    cursor.advanceChild(); 
+    assertEquals(intermediate(2), cursor.getAccessExpression());
+    // intermediate 2, parent's second child
+    XMLElement span = makeElement("span");  
+    cursor.visitChild(span);
+    // intermediate 3, intermediate 2's first child
+    assertEquals(intermediate(3), cursor.getAccessExpression());  
+    cursor.finishChild(span);
+    
+    cursor.advanceChild();
+    // intermediate 4, parent's third child
+    assertEquals(intermediate(4), cursor.getAccessExpression());
+    
+    org.easymock.classextension.EasyMock.verify(writer);
+  }
+  
+  public void testParagraphsWith() throws Exception {
+    writer.die((String) EasyMock.anyObject());
+    org.easymock.classextension.EasyMock.expectLastCall().andThrow(new UnableToCompleteException());
+    org.easymock.classextension.EasyMock.replay(writer);
+    
+    cursor.visitChild(makeElement("p"));
+    XMLElement span = makeElement("span");
+    cursor.visitChild(span);
+    cursor.finishChild(span);
+    cursor.visitChild(makeElement("div"));    
+    
+    try {
+      cursor.getAccessExpression();
+      fail("Expected exception about block elements inside paragraphs");
+    } catch (Exception e) {
+      // Expected
+    }
+    org.easymock.classextension.EasyMock.verify(writer);
+  }
+  
+  public void testTables() throws UnableToCompleteException {
+    
+    verifyInitAssignment(writer, intermediate(1), "UiBinderUtil.getNonTextChild", "parent", 0);
+    verifyInitAssignment(writer, "UiBinderUtil.getTableChild(intermediate1, 0).cast()", 2);    
+    verifyInitAssignment(writer, "UiBinderUtil.getNonTextChild(intermediate2, 0).cast()", 3);    
+    verifyInitAssignment(writer, intermediate(4), "UiBinderUtil.getNonTextChild", "parent", 1);    
+    verifyInitAssignment(writer, "UiBinderUtil.getNonTextChild(intermediate4, 0).cast()", 5);    
+    verifyInitAssignment(writer, "UiBinderUtil.getNonTextChild(intermediate5, 0).cast()", 6);
+    
+    org.easymock.classextension.EasyMock.replay(writer);
+    XMLElement div = makeElement("div");
+    cursor.visitChild(div);
+    
+    XMLElement table1 = makeElement("table");
+    cursor.visitChild(table1);
+    assertEquals(intermediate(2), cursor.getAccessExpression());
+    
+    XMLElement tr = makeElement("tr");
+    cursor.visitChild(tr);
+    assertEquals(intermediate(3), cursor.getAccessExpression());
+    
+    cursor.finishChild(tr);
+    cursor.finishChild(table1);
+    cursor.advanceChild();
+    XMLElement table2 = makeElement("table");
+    cursor.visitChild(table2);
+    
+    XMLElement tbody = makeElement("tbody");
+    cursor.visitChild(tbody);
+    cursor.finishChild(tbody);
+    assertEquals(intermediate(5), cursor.getAccessExpression());
+    
+    XMLElement tr2 = makeElement("tr");
+    cursor.visitChild(tr2);
+    assertEquals(intermediate(6), cursor.getAccessExpression());
+    cursor.finishChild(tr2);
+    
+    XMLElement td = makeElement("td");
+    try {
+      cursor.visitChild(td);
+      fail("Expected exception about tds inside tables without trs");
+    } catch (Exception e) {
+      // expected
+    }
+    org.easymock.classextension.EasyMock.verify(writer);
+  }
+
+  private String intermediate(int count) {
+    return "intermediate" + count;
+  }
+  
+  private XMLElement makeElement(String tag) {
+    NamedNodeMap attributes = EasyMock.createNiceMock(NamedNodeMap.class);
+    Element element = EasyMock.createNiceMock(Element.class);
+    EasyMock.expect(element.getLocalName()).andStubReturn(tag);
+    EasyMock.expect(element.getTagName()).andStubReturn(tag);
+    EasyMock.expect(element.getAttributes()).andStubReturn(attributes);
+    EasyMock.replay(element, attributes);
+    return new XMLElement(element, null, null, null, null, null);
+  }
+  
+  private void verifyInitAssignment(UiBinderWriter writer, String expr, int intermediateCount) {
+    writer.addInitStatement("com.google.gwt.dom.client.Node %s = %s;", 
+        "intermediate" + intermediateCount, expr);
+  }
+  
+  private void verifyInitAssignment(UiBinderWriter writer, String var, String method, String parent,
+      int index) {
+    writer.addInitStatement("com.google.gwt.dom.client.Node %s = %s(%s, %d);", var, method, parent,
+        index);
+  } 
+}