Documentation
¶
Overview ¶
Package html reads HTML and XHTML into the richdoc document model.
It is the piece the ebook formats were missing: EPUB's content documents are XHTML, MOBI's text is HTML, and the body of nearly every HTML email is HTML. One reader serves all three, which is the whole argument for a document model — see github.com/go-richdoc/richdoc.
The parse is delegated ¶
To golang.org/x/net/html, the reference Go implementation of the HTML5 parsing algorithm, exactly as github.com/go-richdoc/markdown delegates to goldmark and github.com/go-richdoc/rst to go-docutils. Nothing here hand-rolls a tokenizer. go-webengine/engine builds its own DOM from the same package, and its DOM stays there: that tree exists to carry CSS and layout, which a document model has no use for.
⛔ Parsing XHTML with the HTML parser is also the SAFE choice, not a shortcut. An XML parser reads a DOCTYPE, and a DOCTYPE is where entity expansion and external entity references live; the HTML5 algorithm has a fixed entity table and no DTD at all, so an EPUB cannot ask this reader to fetch a file or to expand a billion laughs.
What it maps onto ¶
h1..h6 a heading, with the id attribute as its anchor p a paragraph ul, ol, li a list; ol's start attribute is kept dl, dt, dd a paragraph in bold, then a block quotation pre a code block, language from class="language-x" blockquote a block quotation table a table: caption, header row, colspan and rowspan hr a thematic break img an image a a link, or an anchor when it has an id and no href em, i, strong, b emphasis and strong del, s, strike strikethrough code, kbd, samp, tt inline code br a line break svg, math kept VERBATIM as raw content, with its format named
⛔ What it drops, and why that is the interesting part ¶
script, style, noscript, template and the head's own contents are dropped COMPLETELY. They are the reason naive converters emit a page of CSS declarations as the document's first paragraph: a stylesheet is text in the tree and is not content. Comments go the same way.
Whitespace is collapsed the way HTML collapses it — any run of spaces, tabs and newlines becomes one space, and a block's leading and trailing space is dropped — except inside pre, where it is kept byte for byte.
What richdoc has no word for is kept where keeping it is honest: svg and math become github.com/go-richdoc/richdoc.RawBlock or RawInline with the format named, so a writer that knows them can render them and one that does not can say so. sub, sup, mark and span keep their text and lose their marking, because losing the 2 of H2O to say "there was a subscript here" is the worse trade.
Nothing here is CGO, and it builds for GOOS=js.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Limits ¶
type Limits struct {
// MaxDepth is how deeply elements may nest before the reader refuses.
//
// ⛔ This bounds OUR walk, not the parse. golang.org/x/net/html builds its
// tree without recursion and happily returns one a thousand elements deep;
// what would overflow a stack is this package walking it. The guard is
// therefore where the recursion is, which is the opposite of the rule for
// encoding/xml — there the decoder recurses and a guard in our own code
// never runs.
MaxDepth int
// MaxNodes is how many nodes the reader will consider in total. A deeply
// nested document is one shape of abuse; a flat document with four million
// empty spans is the other, and MaxDepth says nothing about it.
MaxNodes int
// MaxInputBytes is how much source this reader will take.
//
// ⛔ THE TWO CEILINGS ABOVE BOUND THE WALK AND NOT THE PARSE, and that is
// not a bound on memory. golang.org/x/net/html builds its whole tree before
// conv.spend is called once, and it has no ceiling of its own to fall back
// on: its only limit is Tokenizer.SetMaxBuf, which html.Parse does not set
// (x/net v0.62.0, token.go:149 and :1278; parse.go and node.go hold no
// depth or node bound at all). Measured: 100 000 <br> and 800 000, both
// refused at node 1 001 of a caller's ceiling, cost 12 MB and 96 MB — a
// ratio of 8.0 for 8x the source. The refusal arrived; the allocation had
// already happened, and nothing capped it.
//
// 64 MiB is what the rest of the family uses, and the population agrees:
// over 123 369 real HTML files on one machine the biggest is 19.87 MB
// (Chromium's licence page) and p99.9 is 1.77 MB.
MaxInputBytes int
// MaxSourceElements is how many tags the source may contain, counted by the
// tokenizer before any tree is built, so that a hostile document is refused
// without being parsed.
//
// ⛔ IT IS ITS OWN CEILING AND NOT A PROXY FOR MaxNodes. It cannot be
// derived from one: tags in <head> are never walked, so a document could
// hold a million of them and reach MaxNodes at zero — a source-side count
// is no lower bound on what the walk will count, and a guard that refuses
// what the thing it protects would accept is worse than no guard. So this
// bounds the SOURCE, says so, and takes its number from the population:
// the widest of 123 369 real files holds 98 961 elements, and 1 << 20
// leaves that an order of magnitude of room.
MaxSourceElements int
}
Limits bound what a document may ask for. A zero field means the default, so Limits{} is the default everywhere.