6.3 KiB
model
| model |
|---|
| mimo-v2-pro |
Plan: HTML-to-Markdown Transformations for Ingestion
Summary
Add configurable HTML-to-markdown transformations to formatHTMLForIngestion.ts to improve the quality of text stored in the vector index. Transformations convert ecmarkup/HTML elements (code blocks, cross-reference links, lists, inline code) to markdown syntax before the HTMLTextSplitter extracts plain text.
Config Interface
interface FormatConfig {
codeBlocks: {
block: string[]; // fenced code (pre>code, emu-eqn block)
inline: string[]; // inline backticks (var, emu-val, emu-const, emu-eqn inline, code)
grammar: string[]; // fenced bnf (emu-grammar)
};
links: string[]; // [text](#hash) (emu-xref a)
lists: {
ordered: string[]; // 1. 2. 3. (ol)
unordered: string[];// - item (ul)
};
tables: string[]; // markdown tables (table, emu-table)
}
Default config:
const DEFAULT_CONFIG: FormatConfig = {
codeBlocks: {
block: ["pre>code", "emu-eqn:not([class*='inline'])"],
inline: ["var", "emu-val", "emu-const", "emu-eqn.inline"],
// Note: standalone <code> (outside <pre>) is rare; handle with :not(pre > *) check in converter
grammar: ["emu-grammar"],
},
links: ["emu-xref a"],
lists: {
ordered: ["ol"],
unordered: ["ul"],
},
tables: ["table", "emu-table"],
};
Transformation Order (leaf-to-parent)
- Inline leaves (processed first, no children affected):
emu-xref a[href]→<span class="link-markdown">[text](#hash)</span>. Target the inner<a>tag (always has href, unlikeemu-xrefwhich may lack it).- Strip filename prefix from href (e.g.,
abstract-operations.html#sec-tonumber→#sec-tonumber) - Discard
e-user-codeclass. - Replace
<a>insideemu-xref(leaving wrapperemu-xrefto be ignored by text splitter)
- Strip filename prefix from href (e.g.,
var,emu-val,emu-const,code, inlineemu-eqn→<span class="inline-code">text</span>. Strip inner elements, plain text only.
- Block leaves:
pre>code→<pre class="code-markdown">```language\ntext\n```</pre>. Language fromclassattribute. Strip hljs spans. Plain text.- block
emu-eqn→<pre class="code-markdown">```\ntext\n```</pre>. No language tag. Strip inner elements.
- Structural parents (children already resolved):
emu-grammar→<pre class="code-markdown">```bnf\ntext\n```</pre>. Extract text from grammar inner tags (emu-nt, emu-t, etc.).ol→<pre class="list-markdown">1. item\n 1. nested</pre>. Nested via 2-space indentation.ul→<pre class="list-markdown">- item\n - nested</pre>. Nested via 2-space indentation.table,emu-table→<pre class="table-markdown">...existing markdown...</pre>. ExistingconvertTablesToMarkdownworks unchanged —.text()already extracts markdown text from pre-processed spans.
addNewlinesAfterBlocks($)(existing, unchanged — operates on remaining DOM elements)
Implementation Details
Approach: Cheerio only
All conversions use cheerio DOM manipulation (already in codebase). No external HTML-to-markdown library. Each conversion replaces DOM elements with text/markdown nodes. The existing HTMLTextSplitter processes the modified DOM unchanged.
New functions in setup/utils/formatHTMLForIngestion.ts
Each function receives cheerio.CheerioAPI and modifies the DOM in-place.
-
convertLinksToMarkdown($, selector: string)— selector default:emu-xref a[href]$(selector).each(...): get href, strip filename, get text, replaceWith<span>containing[text](#hash)- Safety: skip if href starts with
http
-
convertInlineCodeToMarkdown($, tags: string[])$(tag).each(...): get.text(), replaceWith<span>containing`text`- For
codetags: skip if parent ispre
-
convertBlockCodeToMarkdown($, tags: string[])- For
pre>code: get language fromclass, get.text()from code, replaceWith<pre>containing```lang\ntext\n``` - For
emu-eqnblock: get.text(), replaceWith<pre>containing```\ntext\n```
- For
-
convertGrammarToMarkdown($, selector: string)- Walk inner tags (emu-nt, emu-t, emu-rhs, emu-geq), build text representation
- ReplaceWith
<pre>containing```bnf\ntext\n```
-
convertListsToMarkdown($, ordered: string[], unordered: string[])- For each
ol/ul: recursive functionlistToMarkdown(elem, depth, isOrdered) - Process
lichildren, add1./-prefix with indentation - ReplaceWith
<pre>containing markdown text
- For each
-
formatForIngestion($, config?)— orchestrator, calls all in order
Modify existing functions
convertTablesToMarkdown($): No changes needed..text()already extracts markdown text from pre-processed<span>elements.
Modify setup/ingest.ts
- Replace individual calls to
convertTablesToMarkdown($)andaddNewlinesAfterBlocks($)with single call toformatForIngestion($). - The child
emu-clausereplacement and deep nesting cleanup remain as-is (beforeformatForIngestion).
Updated call order in buildSpecDocuments():
// 1. Child clause replacement (existing)
$section.children("emu-clause").each(...);
$section.find("emu-clause").remove();
// 2. All formatting transformations (single call)
formatForIngestion($);
Files to Modify
setup/utils/formatHTMLForIngestion.ts— add config interface, 5 new functions, orchestrating functionsetup/ingest.ts— replace individual calls withformatForIngestion($)call- New test file:
setup/utils/formatHTMLForIngestion.test.ts— unit tests for each transformation
No new dependencies. Cheerio only.
Verification
- Unit tests: Write tests for each transformation with sample HTML snippets from the spec:
emu-xref a→ markdown linkvar/emu-val/emu-const→ inline codepre>code→ fenced codeemu-grammar→ fenced bnfol/ulwith nesting → markdown liststable→ markdown table (with links inside cells preserved)- Full pipeline via
formatForIngestion
- Manual verification: Run
bun run setup/ingest.tsand inspect output chunks for correct markdown syntax - Run lint/typecheck:
bun run lintandbun run typecheck(if available)