public override bool Process(Token t, HtmlTreeBuilder tb)
 {
     if (t.IsStartTag && StringSet.Create("caption table tbody tfoot thead tr td th").Contains(t.AsStartTag().Name))
     {
         tb.Error(this);
         tb.Process(new Token.EndTag("select"));
         return(tb.Process(t));
     }
     else if (t.IsEndTag && StringSet.Create("caption table tbody tfoot thead tr td th").Contains(t.AsEndTag().Name))
     {
         tb.Error(this);
         if (tb.InTableScope(t.AsEndTag().Name))
         {
             tb.Process(new Token.EndTag("select"));
             return(tb.Process(t));
         }
         else
         {
             return(false);
         }
     }
     else
     {
         return(tb.Process(t, InSelect));
     }
 }
Beispiel #2
0
 /**
  *   11.2.5.2 Closing elements that have implied end tags<p/>
  *   When the steps below require the UA to generate implied end tags, then, while the current node is a dd element, a
  *   dt element, an li element, an option element, an optgroup element, a p element, an rp element, or an rt element,
  *   the UA must pop the current node off the stack of open elements.
  *
  *   @param excludeTag If a step requires the UA to generate implied end tags but lists an element to exclude from the
  *   process, then the UA must perform the above steps as if that element was not in the above list.
  */
 public void GenerateImpliedEndTags(string excludeTag)
 {
     while ((excludeTag != null && !CurrentElement.NodeName.Equals(excludeTag)) &&
            StringSet.Create("dd dt li option optgroup p rp rt").Contains(CurrentElement.NodeName))
     {
         Pop();
     }
 }
            public override bool Process(Token t, HtmlTreeBuilder tb)
            {
                if (t.IsEndTag && t.AsEndTag().Name.Equals("caption"))
                {
                    Token.EndTag endTag = t.AsEndTag();
                    string       name   = endTag.Name;

                    if (!tb.InTableScope(name))
                    {
                        tb.Error(this);
                        return(false);
                    }
                    else
                    {
                        tb.GenerateImpliedEndTags();
                        if (!tb.CurrentElement.NodeName.Equals("caption"))
                        {
                            tb.Error(this);
                        }

                        tb.PopStackToClose("caption");
                        tb.ClearFormattingElementsToLastMarker();
                        tb.Transition(InTable);
                    }
                }
                else if ((t.IsStartTag &&
                          StringSet.Create("caption col colgroup tbody td tfoot th thead tr").Contains(t.AsStartTag().Name) ||
                          t.IsEndTag && t.AsEndTag().Name.Equals("table"))
                         )
                {
                    tb.Error(this);
                    bool processed = tb.Process(new Token.EndTag("caption"));
                    if (processed)
                    {
                        return(tb.Process(t));
                    }
                }
                else if (t.IsEndTag && StringSet.Create("body col colgroup html tbody td tfoot th thead tr").Contains(t.AsEndTag().Name))
                {
                    tb.Error(this);
                    return(false);
                }
                else
                {
                    return(tb.Process(t, InBody));
                }

                return(true);
            }
Beispiel #4
0
        public bool IsSpecial(DomContainer el)
        {
            // TODO: mathml's mi, mo, mn
            // TODO: svg's foreigObject, desc, title
            string name = el.NodeName;

            return(StringSet.Create(@"address applet area article aside base basefont bgsound
                                   blockquote body br button caption center col colgroup command dd
                                   details dir div dl dt embed fieldset figcaption figure footer for
                                   frame frameset h1 h2 h3 h4 h5 h6 head header hgroup hr html
                                   iframe img input isindex li link listing marquee menu meta nav
                                   noembed noframes noscript object ol p param plaintext pre script
                                   section select style summary table tbody td textarea tfoot th thead
                                   title tr ul wbr xmp").Contains(name));
        }
            public override bool Process(Token t, HtmlTreeBuilder tb)
            {
                if (t.IsDoctype)
                {
                    tb.Error(this);
                }
                else if (t.IsStartTag && t.AsStartTag().Name.Equals("html"))
                {
                    return(tb.Process(t, InBody));
                }
                else if (t.IsEndTag && t.AsEndTag().Name.Equals("noscript"))
                {
                    tb.Pop();
                    tb.Transition(InHead);
                }
                else if (IsWhitespace(t) ||
                         t.IsComment ||
                         (t.IsStartTag && StringSet.Create("basefont bgsound link meta noframes style").Contains(t.AsStartTag().Name)))
                {
                    return(tb.Process(t, InHead));
                }
                else if (t.IsEndTag && t.AsEndTag().Name.Equals("br"))
                {
                    return(anythingElse(t, tb));
                }
                else if ((t.IsStartTag && StringUtil.In(t.AsStartTag().Name, "head", "noscript")) || t.IsEndTag)
                {
                    tb.Error(this);
                    return(false);
                }
                else
                {
                    return(anythingElse(t, tb));
                }

                return(true);
            }
            public override bool Process(Token t, HtmlTreeBuilder tb)
            {
                switch (t.Type)
                {
                case TokenType.StartTag:
                    Token.StartTag startTag = t.AsStartTag();
                    string         name     = startTag.Name;

                    if (name.Equals("tr"))
                    {
                        tb.ClearStackToTableBodyContext();
                        tb.Insert(startTag);
                        tb.Transition(InRow);
                    }
                    else if (StringUtil.In(name, "th", "td"))
                    {
                        tb.Error(this);
                        tb.Process(new Token.StartTag("tr"));
                        return(tb.Process(startTag));
                    }
                    else if (StringSet.Create("caption col colgroup tbody tfoot thead").Contains(name))
                    {
                        return(ExitTableBody(t, tb));
                    }
                    else
                    {
                        return(AnythingElse(t, tb));
                    }

                    break;

                case TokenType.EndTag:
                    Token.EndTag endTag = t.AsEndTag();
                    name = endTag.Name;

                    if (StringUtil.In(name, "tbody", "tfoot", "thead"))
                    {
                        if (!tb.InTableScope(name))
                        {
                            tb.Error(this);
                            return(false);
                        }
                        else
                        {
                            tb.ClearStackToTableBodyContext();
                            tb.Pop();
                            tb.Transition(InTable);
                        }
                    }
                    else if (name.Equals("table"))
                    {
                        return(ExitTableBody(t, tb));
                    }
                    else if (StringSet.Create("body caption col colgroup html td th tr").Contains(name))
                    {
                        tb.Error(this);
                        return(false);
                    }
                    else
                    {
                        return(AnythingElse(t, tb));
                    }

                    break;

                default:
                    return(AnythingElse(t, tb));
                }
                return(true);
            }
Beispiel #7
0
            public override bool Process(Token t, HtmlTreeBuilder tb)
            {
                if (t.IsEndTag)
                {
                    Token.EndTag endTag = t.AsEndTag();
                    string       name   = endTag.Name;

                    if (name.In("td", "th"))
                    {
                        if (!tb.InTableScope(name))
                        {
                            tb.Error(this);
                            tb.Transition(InRow); // might not be in scope if empty: <td /> and processing fake end tag
                            return(false);
                        }

                        tb.GenerateImpliedEndTags();
                        if (!tb.CurrentElement.NodeName.Equals(name))
                        {
                            tb.Error(this);
                        }

                        tb.PopStackToClose(name);
                        tb.ClearFormattingElementsToLastMarker();
                        tb.Transition(InRow);
                    }
                    else if (StringUtil.In(name, "body", "caption", "col", "colgroup", "html"))
                    {
                        tb.Error(this);
                        return(false);
                    }
                    else if (StringUtil.In(name, "table", "tbody", "tfoot", "thead", "tr"))
                    {
                        if (!tb.InTableScope(name))
                        {
                            tb.Error(this);
                            return(false);
                        }

                        CloseCell(tb);
                        return(tb.Process(t));
                    }
                    else
                    {
                        return(AnythingElse(t, tb));
                    }
                }
                else if (t.IsStartTag &&
                         StringSet.Create("caption col colgroup tbody td tfoot th thead tr").Contains(t.AsStartTag().Name))
                {
                    if (!(tb.InTableScope("td") || tb.InTableScope("th")))
                    {
                        tb.Error(this);
                        return(false);
                    }
                    CloseCell(tb);
                    return(tb.Process(t));
                }
                else
                {
                    return(AnythingElse(t, tb));
                }

                return(true);
            }
            private Nullable <bool> HandleStartTag(Token t, HtmlTreeBuilder tb)
            {
                Token.StartTag startTag = t.AsStartTag();
                string         name     = startTag.Name;

                if (name.Equals("html"))
                {
                    tb.Error(this);
                    // merge attributes onto real html
                    var html = tb.Stack.First();
                    foreach (HtmlAttribute attribute in startTag.Attributes)
                    {
                        if (!html.HasAttribute(attribute.Name))
                        {
                            html.Attributes.Add(attribute);
                        }
                    }
                }
                else if (StringSet.Create("base basefont bgsound command link meta noframes script style title").Contains(name))
                {
                    return(tb.Process(t, InHead));
                }
                else if (name.Equals("body"))
                {
                    tb.Error(this);
                    DescendableLinkedList <DomContainer> stack = tb.Stack;
                    if (stack.Count == 1 || (stack.Count > 2 && !stack.ElementAt(1).NodeName.Equals("body")))
                    {
                        // only in fragment case
                        return(false); // ignore
                    }
                    else
                    {
                        tb.FramesetOK = false;
                        var body = stack.First();
                        foreach (HtmlAttribute attribute in startTag.Attributes)
                        {
                            if (!body.HasAttribute(attribute.Name))
                            {
                                body.Attributes.Add(attribute);
                            }
                        }
                    }
                }
                else if (name.Equals("frameset"))
                {
                    tb.Error(this);
                    DescendableLinkedList <DomContainer> stack = tb.Stack;
                    if (stack.Count == 1 || (stack.Count > 2 && !stack.ElementAt(1).NodeName.Equals("body")))
                    {
                        // only in fragment case
                        return(false); // ignore
                    }
                    else if (!tb.FramesetOK)
                    {
                        return(false); // ignore frameset
                    }
                    else
                    {
                        var second = stack.ElementAt(1);
                        if (second.Parent != null)
                        {
                            second.Remove();
                        }

                        // pop up to html element
                        while (stack.Count > 1)
                        {
                            stack.RemoveLast();
                        }

                        tb.Insert(startTag);
                        tb.Transition(InFrameset);
                    }
                }
                else if (StringSet.Create(
                             @"address article aside blockquote center details dir div dl
                                           fieldset figcaption figure footer header hgroup menu nav ol
                                           p section summary ul").Contains(name))
                {
                    if (tb.InButtonScope("p"))
                    {
                        tb.Process(new Token.EndTag("p"));
                    }
                    tb.Insert(startTag);
                }
                else if (HeadingTags.Contains(name))
                {
                    if (tb.InButtonScope("p"))
                    {
                        tb.Process(new Token.EndTag("p"));
                    }

                    if (HeadingTags.Contains(tb.CurrentElement.NodeName))
                    {
                        tb.Error(this);
                        tb.Pop();
                    }
                    tb.Insert(startTag);
                }
                else if (StringUtil.In(name, "pre", "listing"))
                {
                    if (tb.InButtonScope("p"))
                    {
                        tb.Process(new Token.EndTag("p"));
                    }

                    tb.Insert(startTag);
                    // TODO: ignore LF if next token
                    tb.FramesetOK = false;
                }
                else if (name.Equals("form"))
                {
                    if (tb.FormElement != null)
                    {
                        tb.Error(this);
                        return(false);
                    }
                    if (tb.InButtonScope("p"))
                    {
                        tb.Process(new Token.EndTag("p"));
                    }

                    HtmlElement form = tb.Insert(startTag);
                    tb.FormElement = form;
                }
                else if (name.Equals("li"))
                {
                    tb.FramesetOK = false;

                    DescendableLinkedList <DomContainer> stack = tb.Stack;
                    for (int i = stack.Count - 1; i > 0; i--)
                    {
                        var el = stack.ElementAt(i); // TODO Performance of this?
                        if (el.NodeName.Equals("li"))
                        {
                            tb.Process(new Token.EndTag("li"));
                            break;
                        }

                        if (tb.IsSpecial(el) && !StringUtil.In(el.NodeName, "address", "div", "p"))
                        {
                            break;
                        }
                    }
                    if (tb.InButtonScope("p"))
                    {
                        tb.Process(new Token.EndTag("p"));
                    }
                    tb.Insert(startTag);
                }
                else if (StringUtil.In(name, "dd", "dt"))
                {
                    tb.FramesetOK = false;
                    DescendableLinkedList <DomContainer> stack = tb.Stack;
                    for (int i = stack.Count - 1; i > 0; i--)
                    {
                        var el = stack.ElementAt(i);
                        if (StringUtil.In(el.NodeName, "dd", "dt"))
                        {
                            tb.Process(new Token.EndTag(el.NodeName));
                            break;
                        }
                        if (tb.IsSpecial(el) && !StringUtil.In(el.NodeName, "address", "div", "p"))
                        {
                            break;
                        }
                    }

                    if (tb.InButtonScope("p"))
                    {
                        tb.Process(new Token.EndTag("p"));
                    }
                    tb.Insert(startTag);
                }
                else if (name.Equals("plaintext"))
                {
                    if (tb.InButtonScope("p"))
                    {
                        tb.Process(new Token.EndTag("p"));
                    }
                    tb.Insert(startTag);
                    tb.tokeniser.Transition(TokeniserState.PLAINTEXT); // once in, never gets out
                }
                else if (name.Equals("button"))
                {
                    if (tb.InButtonScope("button"))
                    {
                        // close and reprocess
                        tb.Error(this);
                        tb.Process(new Token.EndTag("button"));
                        tb.Process(startTag);
                    }
                    else
                    {
                        tb.ReconstructFormattingElements();
                        tb.Insert(startTag);
                        tb.FramesetOK = false;
                    }
                }
                else if (name.Equals("a"))
                {
                    if (tb.GetActiveFormattingElement("a") != null)
                    {
                        tb.Error(this);
                        tb.Process(new Token.EndTag("a"));

                        // still on stack?
                        var remainingA = tb.GetFromStack("a");
                        if (remainingA != null)
                        {
                            tb.RemoveFromActiveFormattingElements(remainingA);
                            tb.RemoveFromStack(remainingA);
                        }
                    }
                    tb.ReconstructFormattingElements();
                    HtmlElement a = tb.Insert(startTag);
                    tb.PushActiveFormattingElements(a);
                }
                else if (StringSet.Create("b big code em font i s small strike strong tt u").Contains(name))
                {
                    tb.ReconstructFormattingElements();
                    HtmlElement el = tb.Insert(startTag);
                    tb.PushActiveFormattingElements(el);
                }
                else if (name.Equals("nobr"))
                {
                    tb.ReconstructFormattingElements();
                    if (tb.InScope("nobr"))
                    {
                        tb.Error(this);
                        tb.Process(new Token.EndTag("nobr"));
                        tb.ReconstructFormattingElements();
                    }
                    HtmlElement el = tb.Insert(startTag);
                    tb.PushActiveFormattingElements(el);
                }
                else if (StringUtil.In(name, "applet", "marquee", "object"))
                {
                    tb.ReconstructFormattingElements();
                    tb.Insert(startTag);
                    tb.InsertMarkerToFormattingElements();
                    tb.FramesetOK = false;
                }
                else if (name.Equals("table"))
                {
                    if (tb.Document.QuirksMode != QuirksMode.Quirks && tb.InButtonScope("p"))
                    {
                        tb.Process(new Token.EndTag("p"));
                    }
                    tb.Insert(startTag);
                    tb.FramesetOK = false;
                    tb.Transition(InTable);
                }
                else if (StringSet.Create("area br embed img keygen wbr").Contains(name))
                {
                    tb.ReconstructFormattingElements();
                    tb.InsertEmpty(startTag);
                    tb.FramesetOK = false;
                }
                else if (name.Equals("input"))
                {
                    tb.ReconstructFormattingElements();
                    HtmlElement el = tb.InsertEmpty(startTag);

                    if (!el.Attribute("type").Equals("hidden", StringComparison.OrdinalIgnoreCase))
                    {
                        tb.FramesetOK = false;
                    }
                }
                else if (StringUtil.In(name, "param", "source", "track"))
                {
                    tb.InsertEmpty(startTag);
                }
                else if (name.Equals("hr"))
                {
                    if (tb.InButtonScope("p"))
                    {
                        tb.Process(new Token.EndTag("p"));
                    }
                    tb.InsertEmpty(startTag);
                    tb.FramesetOK = false;
                }
                else if (name.Equals("image"))
                {
                    // we're not supposed to ask.
                    startTag.Name = "img";
                    return(tb.Process(startTag));
                }
                else if (name.Equals("isindex"))
                {
                    // how much do we care about the early 90s?
                    tb.Error(this);
                    if (tb.FormElement != null)
                    {
                        return(false);
                    }

                    tb.tokeniser.AcknowledgeSelfClosingFlag();
                    tb.Process(new Token.StartTag("form"));

                    if (startTag.Attributes.Contains("action"))
                    {
                        HtmlElement form = tb.FormElement;
                        form.Attribute("action", startTag.Attributes["action"]);
                    }

                    tb.Process(new Token.StartTag("hr"));
                    tb.Process(new Token.StartTag("label"));

                    // hope you like english.
                    string prompt = startTag.Attributes.Contains("prompt") ?
                                    startTag.Attributes["prompt"] :
                                    "This is a searchable index. Enter search keywords: ";

                    tb.Process(new Token.Character(prompt));

                    // input
                    var inputStToken = new Token.StartTag("input");
                    HtmlAttributeCollection inputAttribs = inputStToken.Attributes;
                    inputAttribs["name"] = "isindex";

                    foreach (HtmlAttribute attr in startTag.Attributes)
                    {
                        if (!StringUtil.In(attr.Name.LocalName, "name", "action", "prompt"))
                        {
                            inputAttribs.Add(attr);
                        }
                    }

                    tb.Process(inputStToken);
                    tb.Process(new Token.EndTag("label"));
                    tb.Process(new Token.StartTag("hr"));

                    tb.Process(new Token.EndTag("form"));
                }
                else if (name.Equals("textarea"))
                {
                    tb.Insert(startTag);
                    // TODO: If the next token is a U+000A LINE FEED (LF) char token, then ignore that token and move on to the next one. (Newlines at the start of textarea elements are ignored as an authoring convenience.)
                    tb.tokeniser.Transition(TokeniserState.Rcdata);
                    tb.MarkInsertionMode();
                    tb.FramesetOK = false;
                    tb.Transition(Text);
                }
                else if (name.Equals("xmp"))
                {
                    if (tb.InButtonScope("p"))
                    {
                        tb.Process(new Token.EndTag("p"));
                    }
                    tb.ReconstructFormattingElements();
                    tb.FramesetOK = false;
                    HandleRawtext(startTag, tb);
                }
                else if (name.Equals("iframe"))
                {
                    tb.FramesetOK = false;
                    HandleRawtext(startTag, tb);
                }
                else if (name.Equals("noembed"))
                {
                    // also handle noscript if script enabled
                    HandleRawtext(startTag, tb);
                }
                else if (name.Equals("select"))
                {
                    tb.ReconstructFormattingElements();
                    tb.Insert(startTag);
                    tb.FramesetOK = false;

                    HtmlTreeBuilderState state = tb.State;
                    if (state.Equals(InTable) || state.Equals(InCaption) || state.Equals(InTableBody) || state.Equals(InRow) || state.Equals(InCell))
                    {
                        tb.Transition(InSelectInTable);
                    }
                    else
                    {
                        tb.Transition(InSelect);
                    }
                }
                else if (StringUtil.In(name, "optgroup", "option"))
                {
                    if (tb.CurrentElement.NodeName.Equals("option"))
                    {
                        tb.Process(new Token.EndTag("option"));
                    }
                    tb.ReconstructFormattingElements();
                    tb.Insert(startTag);
                }
                else if (StringUtil.In(name, "rp", "rt"))
                {
                    if (tb.InScope("ruby"))
                    {
                        tb.GenerateImpliedEndTags();
                        if (!tb.CurrentElement.NodeName.Equals("ruby"))
                        {
                            tb.Error(this);
                            tb.PopStackToBefore("ruby"); // i.e. close up to but not include name
                        }
                        tb.Insert(startTag);
                    }
                }
                else if (name.Equals("math"))
                {
                    tb.ReconstructFormattingElements();
                    // TODO: handle A start tag whose tag name is "math" (i.e. foreign, mathml)
                    tb.Insert(startTag);
                    tb.tokeniser.AcknowledgeSelfClosingFlag();
                }
                else if (name.Equals("svg"))
                {
                    tb.ReconstructFormattingElements();
                    // TODO: handle A start tag whose tag name is "svg" (xlink, svg)
                    tb.Insert(startTag);
                    tb.tokeniser.AcknowledgeSelfClosingFlag();
                }
                else if (StringSet.Create("caption col colgroup frame head tbody td tfoot th thead tr").Contains(name))
                {
                    tb.Error(this);
                    return(false);
                }
                else
                {
                    tb.ReconstructFormattingElements();
                    tb.Insert(startTag);
                }

                return(null);
            }
            private bool?HandleEndTag(Token t, HtmlTreeBuilder tb)
            {
                Token.EndTag endTag = t.AsEndTag();
                string       name   = endTag.Name;

                if (name.Equals("body"))
                {
                    if (!tb.InScope("body"))
                    {
                        tb.Error(this);
                        return(false);
                    }
                    else
                    {
                        // TODO: error if stack contains something not dd, dt, li, optgroup, option, p, rp, rt, tbody, td, tfoot, th, thead, tr, body, html
                        tb.Transition(AfterBody);
                    }
                }
                else if (name.Equals("html"))
                {
                    bool notIgnored = tb.Process(new Token.EndTag("body"));
                    if (notIgnored)
                    {
                        return(tb.Process(endTag));
                    }
                }
                else if (StringSet.Create(@"address article aside blockquote button center details dir div
                                dl fieldset figcaption figure footer header hgroup listing menu
                                nav ol pre section summary ul").Contains(name))
                {
                    // TODO: refactor these lookups
                    if (!tb.InScope(name))
                    {
                        // nothing to close
                        tb.Error(this);
                        return(false);
                    }
                    else
                    {
                        tb.GenerateImpliedEndTags();
                        if (!tb.CurrentElement.NodeName.Equals(name))
                        {
                            tb.Error(this);
                        }
                        tb.PopStackToClose(name);
                    }
                }
                else if (name.Equals("form"))
                {
                    HtmlElement currentForm = tb.FormElement;
                    tb.FormElement = null;

                    if (currentForm == null || !tb.InScope(name))
                    {
                        tb.Error(this);
                        return(false);
                    }
                    else
                    {
                        tb.GenerateImpliedEndTags();
                        if (!tb.CurrentElement.NodeName.Equals(name))
                        {
                            tb.Error(this);
                        }
                        // remove currentForm from stack. will shift anything under up.
                        tb.RemoveFromStack(currentForm);
                    }
                }
                else if (name.Equals("p"))
                {
                    if (!tb.InButtonScope(name))
                    {
                        tb.Error(this);
                        tb.Process(new Token.StartTag(name)); // if no p to close, creates an empty <p></p>
                        return(tb.Process(endTag));
                    }
                    else
                    {
                        tb.GenerateImpliedEndTags(name);
                        if (!tb.CurrentElement.NodeName.Equals(name))
                        {
                            tb.Error(this);
                        }
                        tb.PopStackToClose(name);
                    }
                }
                else if (name.Equals("li"))
                {
                    if (!tb.InListItemScope(name))
                    {
                        tb.Error(this);
                        return(false);
                    }
                    else
                    {
                        tb.GenerateImpliedEndTags(name);
                        if (!tb.CurrentElement.NodeName.Equals(name))
                        {
                            tb.Error(this);
                        }
                        tb.PopStackToClose(name);
                    }
                }
                else if (StringUtil.In(name, "dd", "dt"))
                {
                    if (!tb.InScope(name))
                    {
                        tb.Error(this);
                        return(false);
                    }
                    else
                    {
                        tb.GenerateImpliedEndTags(name);
                        if (!tb.CurrentElement.NodeName.Equals(name))
                        {
                            tb.Error(this);
                        }
                        tb.PopStackToClose(name);
                    }
                }
                else if (HeadingTags.Contains(name))
                {
                    if (!tb.InScope(HeadingTags))
                    {
                        tb.Error(this);
                        return(false);
                    }
                    else
                    {
                        tb.GenerateImpliedEndTags(name);
                        if (!tb.CurrentElement.NodeName.Equals(name))
                        {
                            tb.Error(this);
                        }
                        tb.PopStackToClose(HeadingTags);
                    }
                }
                else if (name.Equals("sarcasm"))
                {
                    // *sigh*
                    return(AnyOtherEndTag(t, tb));
                }
                else if (StringSet.Create("a b big code em font i nobr s small strike strong tt u").Contains(name))
                {
                    // Adoption Agency Algorithm.
OUTER:
                    for (int i = 0; i < 8; i++)
                    {
                        var formatEl = tb.GetActiveFormattingElement(name);
                        if (formatEl == null)
                        {
                            return(AnyOtherEndTag(t, tb));
                        }

                        else if (!tb.OnStack(formatEl))
                        {
                            tb.Error(this);
                            tb.RemoveFromActiveFormattingElements(formatEl);
                            return(true);
                        }
                        else if (!tb.InScope(formatEl.NodeName))
                        {
                            tb.Error(this);
                            return(false);
                        }
                        else if (tb.CurrentElement != formatEl)
                        {
                            tb.Error(this);
                        }

                        DomContainer furthestBlock                 = null;
                        DomContainer commonAncestor                = null;
                        bool         seenFormattingElement         = false;
                        DescendableLinkedList <DomContainer> stack = tb.Stack;

                        for (int si = 0; si < stack.Count; si++)
                        {
                            DomContainer el = stack.ElementAt(si);
                            if (el == formatEl)
                            {
                                commonAncestor        = stack.ElementAt(si - 1);
                                seenFormattingElement = true;
                            }
                            else if (seenFormattingElement && tb.IsSpecial(el))
                            {
                                furthestBlock = el;
                                break;
                            }
                        }

                        if (furthestBlock == null)
                        {
                            tb.PopStackToClose(formatEl.NodeName);
                            tb.RemoveFromActiveFormattingElements(formatEl);
                            return(true);
                        }

                        // TODO: Let a bookmark note the position of the formatting element in the list of active formatting elements relative to the elements on either side of it in the list.
                        // does that mean: int pos of format el in list?
                        DomContainer node     = furthestBlock;
                        DomContainer lastNode = furthestBlock;

INNER:
                        for (int j = 0; j < 3; j++)
                        {
continueINNER:
                            if (tb.OnStack(node))
                            {
                                node = tb.AboveOnStack(node);
                            }

                            if (!tb.IsInActiveFormattingElements(node))   // note no bookmark check
                            {
                                tb.RemoveFromStack(node);
                                goto continueINNER;
                            }
                            else if (node == formatEl)
                            {
                                goto breakINNER;
                            }

                            HtmlElement replacement = new HtmlElement(node.NodeName);
                            tb.ReplaceActiveFormattingElement(node, replacement);
                            tb.ReplaceOnStack(node, replacement);
                            node = replacement;

                            if (lastNode == furthestBlock)
                            {
                                // TODO: move the aforementioned bookmark to be immediately after the new node in the list of active formatting elements.
                                // not getting how this bookmark both straddles the element above, but is inbetween here...
                            }
                            if (lastNode.Parent != null)
                            {
                                lastNode.Remove();
                            }
                            node.Append(lastNode);

                            lastNode = node;
                        }
breakINNER:

                        if (StringUtil.In(commonAncestor.NodeName, "table", "tbody", "tfoot", "thead", "tr"))
                        {
                            if (lastNode.Parent != null)
                            {
                                lastNode.Remove();
                            }
                            tb.InsertInFosterParent(lastNode);
                        }
                        else
                        {
                            if (lastNode.Parent != null)
                            {
                                lastNode.Remove();
                            }
                            commonAncestor.Append(lastNode);
                        }

                        HtmlElement adopter    = new HtmlElement(name);
                        var         childNodes = furthestBlock.ChildNodes.ToArray();
                        foreach (var childNode in childNodes)
                        {
                            adopter.Append(childNode); // append will reparent. thus the clone to avvoid concurrent mod.
                        }

                        furthestBlock.Append(adopter);
                        tb.RemoveFromActiveFormattingElements(formatEl);
                        // TODO: insert the new element into the list of active formatting elements at the position of the aforementioned bookmark.
                        tb.RemoveFromStack(formatEl);
                        tb.InsertOnStackAfter(furthestBlock, adopter);
                    }
                }
                else if (StringUtil.In(name, "applet", "marquee", "object"))
                {
                    if (!tb.InScope("name"))
                    {
                        if (!tb.InScope(name))
                        {
                            tb.Error(this);
                            return(false);
                        }

                        tb.GenerateImpliedEndTags();
                        if (!tb.CurrentElement.NodeName.Equals(name))
                        {
                            tb.Error(this);
                        }
                        tb.PopStackToClose(name);
                        tb.ClearFormattingElementsToLastMarker();
                    }
                }
                else if (name.Equals("br"))
                {
                    tb.Error(this);
                    tb.Process(new Token.StartTag("br"));
                    return(false);
                }
                else
                {
                    return(AnyOtherEndTag(t, tb));
                }

                return(null);
            }
            public override bool Process(Token t, HtmlTreeBuilder tb)
            {
                if (t.IsStartTag)
                {
                    Token.StartTag startTag = t.AsStartTag();
                    string         name     = startTag.Name;

                    if (StringUtil.In(name, "th", "td"))
                    {
                        tb.ClearStackToTableRowContext();
                        tb.Insert(startTag);
                        tb.Transition(InCell);
                        tb.InsertMarkerToFormattingElements();
                    }
                    else if (StringSet.Create("caption col colgroup tbody tfoot thead tr").Contains(name))
                    {
                        return(HandleMissingTr(t, tb));
                    }
                    else
                    {
                        return(AnythingElse(t, tb));
                    }
                }
                else if (t.IsEndTag)
                {
                    Token.EndTag endTag = t.AsEndTag();
                    string       name   = endTag.Name;

                    if (name.Equals("tr"))
                    {
                        if (!tb.InTableScope(name))
                        {
                            tb.Error(this); // frag
                            return(false);
                        }

                        tb.ClearStackToTableRowContext();
                        tb.Pop(); // tr
                        tb.Transition(InTableBody);
                    }
                    else if (name.Equals("table"))
                    {
                        return(HandleMissingTr(t, tb));
                    }
                    else if (StringUtil.In(name, "tbody", "tfoot", "thead"))
                    {
                        if (!tb.InTableScope(name))
                        {
                            tb.Error(this);
                            return(false);
                        }

                        tb.Process(new Token.EndTag("tr"));
                        return(tb.Process(t));
                    }
                    else if (StringSet.Create("body caption col colgroup html td th").Contains(name))
                    {
                        tb.Error(this);
                        return(false);
                    }
                    else
                    {
                        return(AnythingElse(t, tb));
                    }
                }
                else
                {
                    return(AnythingElse(t, tb));
                }

                return(true);
            }