private WidgetChain walk(PageCompilingContext pc, List<Node> nodes) {
    WidgetChain chain = Chains.proceeding();

    for (Node n : nodes) chain.addWidget(widgetize(pc, n, walk(pc, n)));

    return chain;
  }
  /**
   * This method converts an XML element into a specific kind of widget. Special cases are the XML
   * widget, Header, @Require widget. Otherwise a standard widget is created.
   */
  @SuppressWarnings({"JavaDoc"})
  @NotNull
  private <N extends Node> Renderable widgetize(
      PageCompilingContext pc, N node, WidgetChain childsChildren) {
    if (node instanceof XmlDeclaration) {
      try {
        XmlDeclaration decl = (XmlDeclaration) node;
        return registry.xmlDirectiveWidget(decl.getWholeDeclaration(), pc.lexicalScopes.peek());
      } catch (ExpressionCompileException e) {
        pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));
      }
    }

    // Header widget is a special case, where we match by the name of the tag =(
    if ("head".equals(node.nodeName())) {
      try {
        return registry.headWidget(
            childsChildren, parseAttribs(node.attributes()), pc.lexicalScopes.peek());
      } catch (ExpressionCompileException e) {
        pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));
      }
    }

    String annotation = node.attr(ANNOTATION);

    // if there is no annotation, treat as a raw xml-widget (i.e. tag)
    if ((null == annotation) || 0 == annotation.trim().length())
      try {
        checkUriConsistency(pc, node);
        checkFormFields(pc, node);

        return registry.xmlWidget(
            childsChildren,
            node.nodeName(),
            parseAttribs(node.attributes()),
            pc.lexicalScopes.peek());
      } catch (ExpressionCompileException e) {
        pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));

        return Chains.terminal();
      }

    // Special case: is this annotated with @Require
    //   if so, tags in head need to be promoted to head of enclosing page.
    if (REQUIRE_WIDGET.equalsIgnoreCase(annotation.trim()))
      try {
        return registry.requireWidget(
            registry.xmlWidget(
                childsChildren,
                node.nodeName(),
                parseAttribs(node.attributes()),
                pc.lexicalScopes.peek()));
      } catch (ExpressionCompileException e) {
        pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));

        return Chains.terminal();
      }

    // If this is NOT a self-rendering widget, give it a child.
    // final String widgetName = node.attr(ANNOTATION_KEY).trim().toLowerCase());
    final String widgetName = node.attr(ANNOTATION_KEY).toLowerCase();

    if (!registry.isSelfRendering(widgetName))
      try {
        childsChildren =
            Chains.singleton(
                registry.xmlWidget(
                    childsChildren,
                    node.nodeName(),
                    parseAttribs(node.attributes()),
                    pc.lexicalScopes.peek()));
      } catch (ExpressionCompileException e) {
        pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));
      }

    // Recursively build widget from [Key, expression, child widgets].
    try {
      return registry.newWidget(
          widgetName, node.attr(ANNOTATION_CONTENT), childsChildren, pc.lexicalScopes.peek());
    } catch (ExpressionCompileException e) {
      pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));

      // This should never be used.
      return Chains.terminal();
    }
  }
  /** Walks the DOM recursively, and converts elements into corresponding sitebricks widgets. */
  @NotNull
  private <N extends Node> WidgetChain walk(PageCompilingContext pc, N node) {
    WidgetChain widgetChain = Chains.proceeding();
    for (Node n : node.childNodes()) {
      if (n instanceof Element) {
        final Element child = (Element) n;

        // push form if this is a form tag
        if (child.tagName().equals("form")) pc.form = (Element) n;

        // setup a lexical scope if we're going into a repeat widget (by reading the previous node)
        final boolean shouldPopScope = lexicalClimb(pc, child);

        // continue recursing down, perform a post-order, depth-first traversal of the DOM
        WidgetChain childsChildren;
        try {
          childsChildren = walk(pc, child);

          // process the widget itself into a Renderable with child tree
          widgetChain.addWidget(widgetize(pc, child, childsChildren));
        } finally {
          lexicalDescend(pc, child, shouldPopScope);
        }

      } else if (n instanceof TextNode) {
        TextNode child = (TextNode) n;
        Renderable textWidget;

        // setup a lexical scope if we're going into a repeat widget (by reading the previous node)
        final boolean shouldPopScope = lexicalClimb(pc, child);

        // construct the text widget
        try {
          textWidget = registry.textWidget(cleanHtml(n), pc.lexicalScopes.peek());

          // if there are no annotations, add the text widget to the chain
          if (!child.hasAttr(ANNOTATION_KEY)) {
            widgetChain.addWidget(textWidget);
          } else {
            // construct a new widget chain for this text node
            WidgetChain childsChildren = Chains.proceeding().addWidget(textWidget);

            // make a new widget for the annotation, making the text chain the child
            String widgetName = child.attr(ANNOTATION_KEY).toLowerCase();
            Renderable annotationWidget =
                registry.newWidget(
                    widgetName,
                    child.attr(ANNOTATION_CONTENT),
                    childsChildren,
                    pc.lexicalScopes.peek());
            widgetChain.addWidget(annotationWidget);
          }

        } catch (ExpressionCompileException e) {
          pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));
        }

        if (shouldPopScope) pc.lexicalScopes.pop();

      } else if ((n instanceof Comment) || (n instanceof DataNode)) {
        // process as raw text widget
        try {
          widgetChain.addWidget(registry.textWidget(cleanHtml(n), pc.lexicalScopes.peek()));
        } catch (ExpressionCompileException e) {

          pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));
        }
      } else if (n instanceof XmlDeclaration) {
        try {
          widgetChain.addWidget(
              registry.xmlDirectiveWidget(
                  ((XmlDeclaration) n).getWholeDeclaration(), pc.lexicalScopes.peek()));
        } catch (ExpressionCompileException e) {
          pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));
        }
      }
    }

    // return computed chain, or a terminal
    return widgetChain;
  }