dvFindableHtml function
content with each paragraph in a section the browser's find searches.
A paragraph here is any element found at container level that is not a
container itself -- a heading, a paragraph, a link on its own line, a code
block, a whole list -- wrapped in <section hidden="until-found" data-dv-anchor="N">, N counting from zero in document order. Wrapped
rather than given the attribute: until-found does nothing on an inline
element, and an <li> or a <p> given attributes would stop being the
<li>/<p> every other reader of the block matches on.
A list is one section rather than one per item, because a <section>
between <ul> and <li> is not a list any more. The runtime maps a match
to the rendered paragraph by its text, so a coarser section costs a little
precision and nothing else.
Implementation
String dvFindableHtml(String content) {
final StringBuffer out = StringBuffer();
int anchor = 0;
int cursor = 0;
// The leaf being written, if any: its name, and how deep inside another of
// the same name the scanner is -- a list inside a list item.
String? leaf;
int depth = 0;
for (final RegExpMatch match in _tag.allMatches(content)) {
final bool closing = match.group(1) == '/';
final String name = match.group(2)!.toLowerCase();
final bool selfClosing = match.group(3) == '/' || _void.contains(name);
if (leaf != null) {
// Inside a section: only its own end tag matters.
if (name == leaf && !selfClosing) {
depth += closing ? -1 : 1;
if (depth == 0) {
out
..write(content.substring(cursor, match.end))
..write('</section>');
cursor = match.end;
leaf = null;
}
}
continue;
}
if (closing || selfClosing || _containers.contains(name)) continue;
// A paragraph starts here.
out
..write(content.substring(cursor, match.start))
..write(_openSection(anchor++));
cursor = match.start;
leaf = name;
depth = 1;
}
out.write(content.substring(cursor));
// An element never closed -- somebody's template, not the semantics tree --
// still ends inside its section, rather than leaving one open to swallow
// whatever the page writes after the block.
if (leaf != null) out.write('</section>');
return out.toString();
}