Guided Navigation (GND) is the JSON tree this library extracts from HTML/XHTML, before turning it into utterances (see Utterance Extraction).
import { makeGnd } from "@readium/speech";
const gnd = makeGnd(`
<section epub:type="chapter">
<h1>Chapter One</h1>
<p>It was a dark and stormy night.</p>
</section>
`);
// gnd.guided: GndObject[]function makeGnd(input: string, mediaType?: GndMediaType): GndDocument;
interface GndDocument {
links?: unknown[];
guided: GndObject[];
}mediaType is "text/html" | "application/xhtml+xml". Omit it to sniff from input (XML declaration, xmlns:epub, XHTML doctype → XHTML; else HTML).
Skip the GndDocument wrapper by calling parseMarkup(html): GndObject[] directly.
Parsing uses the native DOMParser — no HTML/XML library bundled or loaded at runtime.
type GndRole = string; // open-ended, see roles.ts
interface GndText {
language: string;
plain?: string;
ssml?: string;
}
interface GndObject {
role?: GndRole[];
text?: string | GndText;
description?: string;
imgref?: string;
audioref?: string;
videoref?: string;
textref?: string;
id?: string;
children?: GndObject[];
}rolecan have multiple entries: tag name, ARIArole,epub:typeall contribute, in that order (e.g.<section epub:type="chapter">→["section", "chapter"]).role="presentation"/"none"overrides everything to["presentation"].textis a plain string when unformatted, aGndTextwhen it needs SSML (inline formatting, a language shift, or an embedded footnote/pagebreak/image mid-sentence).ssmlmarks embedded objects with a<readium:noteref id="..." />-style placeholder whoseidmatches a sibling inchildren.imgref/audioref/videorefare a media element'ssrc.textrefis anhref— reused for navigational-list items (toc,index...),noteref/backlink/biblioref/glossref, and plain links.descriptionis a node's accessible name (aria-label,alt,<figcaption>...) when it differs from its visible text.- Empty/presentational/
aria-hidden/hiddencontent and role-less wrapper<div>s are dropped from the tree, not kept as empty nodes. - A block whose only child has no role/id of its own gets that child's text/refs hoisted into it:
<p><a href="...">Cover</a></p>→{ role: ["paragraph"], text: "Cover", textref: "..." }, not a nested anonymous child.
Both are read out of narrative order, so the converter handles them specially:
noteref(<a role="doc-noteref" href="#note1">) resolves itshrefand embeds the target's whole subtree aschildren— no second lookup needed. Unresolvable hrefs get a plaintextrefchild instead. The footnote is suppressed from also appearing at its original location.pagebreak(<span epub:type="pagebreak" title="42">) carries its label astext. Mid-sentence, it's a<readium:pagebreak id="..." />placeholder in that sentence'sssml, with the pagebreak node as a siblingchildrenentry.
Three independent sources, mapped in src/gnd/roles.ts:
- Element type —
<h1>→heading1,<nav>→navigation,<blockquote>→blockquote, etc. - ARIA role —
role="doc-chapter"→chapter,role="figure"→figure, etc.role="heading"reads its level fromaria-level(default2). epub:type—epub:type="chapter"→chapter,epub:type="pagebreak"→pagebreak, etc. (XHTML only, see below).
epub:type only means anything in namespace-aware XHTML. Pass a complete XHTML document (xmlns:epub on the root):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">
<head><meta charset="utf-8"/><title>...</title></head>
<body>
<section epub:type="chapter">...</section>
</body>
</html>ARIA roles and native elements work as plain HTML fragments.
fixtures/ is a language-agnostic conformance suite for this stage plus utterance extraction — see fixtures/README.md and Testing.