endTagFormatting method
void
endTagFormatting(
- 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);
}
}