void endTagFormatting()

in lib/parser.dart [2038:2180]


  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);
    }
  }