endTagFormatting method

void endTagFormatting(
  1. EndTagToken token
)

The much-feared adoption agency algorithm.

Implementation

void endTagFormatting(EndTagToken token) {
  // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#adoptionAgency
  // TODO(jmesserly): the comments here don't match the numbered steps in the
  // updated spec. This needs a pass over it to verify that it still matches.
  // In particular the html5lib Python code skiped "step 4", I'm not sure why.
  // XXX Better parseError messages appreciated.
  var outerLoopCounter = 0;
  while (outerLoopCounter < 8) {
    outerLoopCounter += 1;

    // Step 1 paragraph 1
    final formattingElement =
        tree.elementInActiveFormattingElements(token.name);
    if (formattingElement == null ||
        (tree.openElements.contains(formattingElement) &&
            !tree.elementInScope(formattingElement.localName))) {
      parser.parseError(
          token.span, 'adoption-agency-1.1', {'name': token.name});
      return;
      // Step 1 paragraph 2
    } else if (!tree.openElements.contains(formattingElement)) {
      parser.parseError(
          token.span, 'adoption-agency-1.2', {'name': token.name});
      tree.activeFormattingElements.remove(formattingElement);
      return;
    }

    // Step 1 paragraph 3
    if (formattingElement != tree.openElements.last) {
      parser.parseError(
          token.span, 'adoption-agency-1.3', {'name': token.name});
    }

    // Step 2
    // Start of the adoption agency algorithm proper
    final afeIndex = tree.openElements.indexOf(formattingElement);
    Element? furthestBlock;
    for (var element in slice(tree.openElements, afeIndex)) {
      if (specialElements.contains(getElementNameTuple(element))) {
        furthestBlock = element;
        break;
      }
    }
    // Step 3
    if (furthestBlock == null) {
      var element = tree.openElements.removeLast();
      while (element != formattingElement) {
        element = tree.openElements.removeLast();
      }
      element.endSourceSpan = token.span;
      tree.activeFormattingElements.remove(element);
      return;
    }

    final commonAncestor = tree.openElements[afeIndex - 1];

    // Step 5
    // The bookmark is supposed to help us identify where to reinsert
    // nodes in step 12. We have to ensure that we reinsert nodes after
    // the node before the active formatting element. Note the bookmark
    // can move in step 7.4
    var bookmark = tree.activeFormattingElements.indexOf(formattingElement);

    // Step 6
    var lastNode = furthestBlock;
    var node = furthestBlock;
    var innerLoopCounter = 0;

    var index = tree.openElements.indexOf(node);
    while (innerLoopCounter < 3) {
      innerLoopCounter += 1;

      // Node is element before node in open elements
      index -= 1;
      node = tree.openElements[index];
      if (!tree.activeFormattingElements.contains(node)) {
        tree.openElements.remove(node);
        continue;
      }
      // Step 6.3
      if (node == formattingElement) {
        break;
      }
      // Step 6.4
      if (lastNode == furthestBlock) {
        bookmark = tree.activeFormattingElements.indexOf(node) + 1;
      }
      // Step 6.5
      //cite = node.parent
      final clone = node.clone(false);
      // Replace node with clone
      tree.activeFormattingElements[
          tree.activeFormattingElements.indexOf(node)] = clone;
      tree.openElements[tree.openElements.indexOf(node)] = clone;
      node = clone;

      // Step 6.6
      // Remove lastNode from its parents, if any
      if (lastNode.parentNode != null) {
        lastNode.parentNode!.nodes.remove(lastNode);
      }
      node.nodes.add(lastNode);
      // Step 7.7
      lastNode = node;
      // End of inner loop
    }

    // Step 7
    // Foster parent lastNode if commonAncestor is a
    // table, tbody, tfoot, thead, or tr we need to foster parent the
    // lastNode
    if (lastNode.parentNode != null) {
      lastNode.parentNode!.nodes.remove(lastNode);
    }

    if (const ['table', 'tbody', 'tfoot', 'thead', 'tr']
        .contains(commonAncestor.localName)) {
      final nodePos = tree.getTableMisnestedNodePosition();
      nodePos[0]!.insertBefore(lastNode, nodePos[1]);
    } else {
      commonAncestor.nodes.add(lastNode);
    }

    // Step 8
    final clone = formattingElement.clone(false);

    // Step 9
    furthestBlock.reparentChildren(clone);

    // Step 10
    furthestBlock.nodes.add(clone);

    // Step 11
    tree.activeFormattingElements.remove(formattingElement);
    tree.activeFormattingElements
        .insert(min(bookmark, tree.activeFormattingElements.length), clone);

    // Step 12
    tree.openElements.remove(formattingElement);
    tree.openElements
        .insert(tree.openElements.indexOf(furthestBlock) + 1, clone);
  }
}