/**
* Opt-in HTML helpers for the chrischall MCP fleet.
*
* Isolated behind the `@chrischall/mcp-utils/html` subpath because it pulls the
* heavy `node-html-parser` dependency — lean API-only MCPs shouldn't pay for it.
*
* Consolidates the HTML-scraping primitives that were independently
* re-implemented across the realty cohort (homes / redfin / compass /
* zillow / onehome) and a couple of content MCPs (opentable, infinitecampus):
*
* - `parsePropertyTable` / `findLinksUnderHeading` — heading-anchored DOM
* scraping for the SSR property-detail tables and link lists.
* - `extractJsonFromHtml` — the balanced-brace `__INITIAL_STATE__` walker
* (opentable / compass / realty); regex can't handle nested objects and
* escaped strings.
* - `extractPlainTextFromHtml` — dependency-free script/style strip + entity
* decode used to render Infinite Campus message bodies as plain text.
* - `urlToPath` / `locationToSlug` / `buildIdExtractor` — the small URL atoms
* that were byte-identical across the cohort.
*/
import { type HTMLElement } from 'node-html-parser';
export type { HTMLElement };
/** A scraped HTML table: column headers plus row-major cell text. */
export interface PropertyTable {
/** Trimmed text of each header cell, in document order. */
headers: string[];
/** Each body row as an array of trimmed cell strings, in document order. */
rows: string[][];
}
/**
* Locate the `
` under the heading matching `heading` and return its
* column headers and body rows.
*
* Header cells are scoped to `` when present (falling back to all `| `
* in the table). Body rows collect **both** ` | ` and ` | ` because the realty
* portals use ` | ` for the leading cell of every data row (the
* year / date column) — dropping those would silently shift every column left
* and corrupt the parsed record.
*
* Cell text has internal whitespace collapsed to single spaces and is trimmed.
*
* @param html Raw page HTML (or an HTML fragment).
* @param heading Case-insensitive substring of the heading above the table.
* @returns The parsed table, or `null` when no matching heading+table is found.
*
* @example parsePropertyTable(detailHtml, 'Tax History')
* // { headers: ['Year', 'Property Tax'], rows: [['2023', '$1,200'], ...] }
*/
export declare function parsePropertyTable(html: string | HTMLElement, heading: string): PropertyTable | null;
/**
* Find every `` (or `selector`-matching element) that follows the first
* heading matching `heading`, up to — but not including — the next sibling
* heading. Useful for the "Homes for Sale Near" link lists at the bottom of a
* detail page.
*
* Collects both direct-sibling anchors and anchors nested inside the
* intervening sibling elements (lists, cards), in document order.
*
* @param root Raw HTML string or an already-parsed root element.
* @param heading Case-insensitive substring of the heading to anchor on.
* @param selector CSS selector for the elements to collect (default `'a'`).
* @returns The matching elements, or an empty array when the heading is absent.
*/
export declare function findLinksUnderHeading(root: string | HTMLElement, heading: string, selector?: string): HTMLElement[];
/**
* Extract an embedded JSON state object from a server-rendered HTML page by
* walking the balanced brace/string structure after a marker.
*
* Handles both rendering forms seen across the fleet:
* 1. `window.__INITIAL_STATE__ = {...};` — a JS assignment in a ` |