Pure, synchronous HTML extraction for the AEO Toolkit. Given an HTML string and the
URL it came from, it produces the shared ParsedHtml shape: document meta, OpenGraph
and Twitter cards, the heading tree, images with alt coverage, internal/external links,
content-quality signals (word count, FAQ/HowTo, question headings, structure counts),
and raw structured-data blocks. No network and no filesystem access — fetching is
the job of @advance-labs/crawler, and full schema.org validation is the job of
@advance-labs/schema-validator; this package only collects the raw blocks they need.
import { parseHtml } from '@advance-labs/html-parser';
const parsed = parseHtml(htmlString, 'https://example.com/page');
console.log(parsed.meta.title, parsed.meta.titleLength);
console.log(parsed.openGraph.complete);
console.log(parsed.imageAltCoverage); // 0..1
console.log(parsed.content.hasFaq, parsed.content.questionHeadingCount);
console.log(parsed.rawStructuredData); // [{ format: 'json-ld', data: {...} }, ...]Each extractor is also exported standalone (accepting an HTML string), so a consumer can pull a single signal without parsing the whole document:
import { extractMeta, extractHeadings } from '@advance-labs/html-parser';
const meta = extractMeta(htmlString, url);
const headings = extractHeadings(htmlString);| Export | Signature | Purpose |
|---|---|---|
parseHtml |
(html: string, url: string) => ParsedHtml |
Full extraction into the shared shape. |
extractMeta |
(html: string, url: string) => MetaTags |
Title/description (+lengths), canonical, robots, viewport, charset, lang, theme-color. |
extractOpenGraph |
(html: string, url: string) => OpenGraph |
OG tags; complete requires the og title/description/image/url quartet. |
extractTwitter |
(html: string, url: string) => TwitterCard |
Twitter card tags (accepts name= and property=). |
extractHeadings |
(html: string) => HeadingNode[] |
Heading tree h1–h6 in document order. |
extractImages |
(html: string, url: string) => ImageInfo[] |
<img> with resolved src, hasAlt, dimensions. |
extractLinks |
(html: string, url: string) => LinkInfo[] |
Anchors classified internal/external + nofollow. |
computeContentSignals |
(html: string) => ContentSignals |
Word count, FAQ/HowTo, question headings, paragraph/list/table counts. |
extractRawStructuredData |
(html: string) => RawStructuredDataBlock[] |
JSON-LD parsed; microdata/RDFa presence markers. |
isHeadingHierarchyValid |
(headings: HeadingNode[]) => boolean |
True when headings never skip a level downward. |
imageAltCoverage |
(images: ImageInfo[]) => number |
Fraction of images with alt text, 0..1. |
internalLinkCount / externalLinkCount |
(links: LinkInfo[]) => number |
Link counts by classification. |
isQuestionHeading |
(text: string) => boolean |
Whether a heading reads as a question. |
jsonLdHasType / collectJsonLdTypes |
see source | JSON-LD @type inspection helpers. |
All shapes are imported from @advance-labs/types; this package never redefines them.
hasFaqis set when an FAQ-flavored heading is present or an FAQPage/QAPage JSON-LD block exists.hasHowTolikewise combines headings and HowTo JSON-LD.- An empty
alt=""counts as not having descriptive alt text. - Relative URLs in
canonical,og:image,twitter:image,<img src>, and<a href>are resolved against the supplied page URL. Fragment-only (#...) anchors are skipped. - Malformed JSON-LD is silently skipped (it surfaces as a validation finding downstream, not a parser crash).
Implemented. All extractors are real (cheerio-backed) with no network/filesystem dependency and no stubs. Unit-tested with rich-page and sparse-page fixtures.