evidence-source/src/markdown/extract.ts
tegwick 97437dfa18
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Failing after 15m18s
Implement ESRC-WP-0002/0003/0004: HTML/MD ingest, metadata, recovery
Add ingestHtml and ingestMarkdown with ADR-0003 pageless offset semantics,
PDF intrinsic metadata extraction with caller-wins merge (WP-0003), and
citation recovery primitives including re-ingest reconcile, local quote
search, and pluggable discovery hooks (ADR-0004). Mark all three workplans
finished with contract tests (80 passing).
2026-07-09 01:48:28 +02:00

53 lines
No EOL
1.7 KiB
TypeScript

/**
* Markdown text extraction → canonical text + gap-free offset map.
*
* Implements ADR-0003 for the `markdown-rendered` representation. Markup is
* stripped to plain text before `normalize()` is applied.
*/
import { normalize, type OffsetMap } from "@citation-evidence/engine/shared";
import { buildWholeTextOffsetMap } from "../shared/offset-map";
const FENCED_CODE = /```[\s\S]*?```/g;
const INLINE_CODE = /`([^`]+)`/g;
const IMAGE = /!\[([^\]]*)\]\([^)]+\)/g;
const LINK = /\[([^\]]+)\]\([^)]+\)/g;
const HEADING = /^#{1,6}\s+/gm;
const BLOCKQUOTE = /^>\s?/gm;
const LIST_MARKER = /^[\s]*[-*+]\s+/gm;
const ORDERED_LIST = /^[\s]*\d+\.\s+/gm;
const EMPHASIS = /(\*\*|__)(.*?)\1/g;
const ITALIC = /(\*|_)([^*_]+)\1/g;
const STRIKETHROUGH = /~~(.*?)~~/g;
const HORIZONTAL_RULE = /^[-*_]{3,}\s*$/gm;
export interface MarkdownExtractionResult {
readonly canonicalText: string;
readonly offsetMap: OffsetMap;
}
export function extractMarkdown(source: string): MarkdownExtractionResult {
const rawText = markdownToPlainText(source);
const canonicalText = normalize(rawText).text;
return {
canonicalText,
offsetMap: buildWholeTextOffsetMap(canonicalText),
};
}
function markdownToPlainText(source: string): string {
let text = source;
text = text.replace(FENCED_CODE, "\n\n");
text = text.replace(INLINE_CODE, "$1");
text = text.replace(IMAGE, "$1");
text = text.replace(LINK, "$1");
text = text.replace(HEADING, "");
text = text.replace(BLOCKQUOTE, "");
text = text.replace(LIST_MARKER, "");
text = text.replace(ORDERED_LIST, "");
text = text.replace(EMPHASIS, "$2");
text = text.replace(ITALIC, "$2");
text = text.replace(STRIKETHROUGH, "$1");
text = text.replace(HORIZONTAL_RULE, "\n\n");
return text;
}