Recursive AI

Parse HTML fragments

All onehtml recipes

Parse a snippet of HTML, such as table rows or list items, in the context of the element it belongs in, so you get the elements a browser would create and not a whole html and body document.

The HTML parser builds different trees for the same markup depending on where it sits. <tr><td> inside a tbody makes rows, while the same markup inside a div loses its table tags. Document.ParseFragment runs the fragment-parsing algorithm (the one behind innerHTML) with a context element you name.

C#
using RecursiveAI.OneHtml;
using RecursiveAI.OneHtml.Editing;
 
// A fragment is parsed as if it were the inner HTML of a context element
var tbody = new FragmentContext(ElementNamespace.Html, "tbody");
Document rows = Document.ParseFragment("<tr><td>Story time<td>Sat 10:30<tr><td>Repair café<td>Sun 14:00", tbody);
Console.WriteLine($"Fragment: {rows.IsFragment}, rows: {rows.Root.QuerySelectorAll("tr").Count}");
Console.WriteLine(rows.SerializeChildren(rows.Root));
 
// The same markup in a <div> context: table parts without a table are dropped
Document loose = Document.ParseFragment("<tr><td>Story time<td>Sat 10:30", new FragmentContext(ElementNamespace.Html, "div"));
Console.WriteLine(loose.SerializeChildren(loose.Root));
 
// Take the context from an element of a real page
Document page = Document.Parse(File.ReadAllBytes("events.html"));
Node list = page.Root.QuerySelector("nav ul");
Document items = Document.ParseFragment("<li><a href=/kids/>Kids</a>", FragmentContext.FromElement(list));
Console.WriteLine(items.Root.QuerySelector("a").GetAttribute("href"));
 
// Inserting markup into a page parses it in the target's context for you
EditResult result = page.Apply(new[] { Edit.Append(list, "<li><a href=\"/kids/\">Kids</a></li>") });
Node newList = result.Document!.Root.QuerySelector("nav ul");
string written = result.Document.Source.Text[newList.Source.Range.Start..newList.Source.EndTag.End];
Console.WriteLine(written);
Text
Fragment: True, rows: 2
<tr><td>Story time</td><td>Sat 10:30</td></tr><tr><td>Repair café</td><td>Sun 14:00</td></tr>
Story timeSat 10:30
/kids/
<ul>
        <li><a href="/books/">Books</a></li>
        <li><a href="/events/" aria-current="page">Events</a></li>
        <li><a href='/visit/'>Visit</a></li>
        <li><a href="/kids/">Kids</a></li>
      </ul>

Notes:

  • new FragmentContext(ElementNamespace.Html, "tbody") names the context directly. FragmentContext.FromElement(node) takes it from an element of a parsed page, including whether it sits inside a form and the page’s quirks mode.
  • The result is a Document with IsFragment set. Its Root holds the parsed nodes, so you can query them as usual. SerializeChildren(Root) writes them back out as HTML.
  • You rarely need ParseFragment to insert markup. Edit.Append, Prepend, InsertBefore, InsertAfter, Replace and Wrap already parse the markup you pass in the target’s context. They also write it in the file’s own style: here the new <li> was put on its own line with the list’s indentation, and the single-quoted href='/visit/' next to it is unchanged.
  • An element’s <template> content is a separate fragment, reached through node.TemplateContent.

More on how inserted markup is placed and written: Editing.