import type { EdgeVocabulary } from "./types"; const BACKTICK_RE = /`([^`]+)`/g; /** * Parses markdown tables containing edge vocabulary definitions. * * Expected format: * | System-Typ (Canonical) | Inverser Typ | Erlaubte Aliasse (User) | Beschreibung ... | * | **`caused_by`** | `resulted_in` | `ausgelöst_durch`, `wegen`, ... | ... | * * Rules: * - Extract all backticked tokens from each row * - First token = canonical (may be wrapped in ** **) * - Second token = inverse (optional) * - Remaining tokens = aliases (skip if cell contains "(Kein Alias)") * - Skip rows with less than 1 token (with warning counter) * - Store canonical as-is, but use lowercase for lookup keys */ export function parseEdgeVocabulary(md: string): EdgeVocabulary { const lines = md.split(/\r?\n/); const byCanonical = new Map(); const aliasToCanonical = new Map(); let skippedRows = 0; for (const line of lines) { // Skip header separator rows (e.g., "| :--- | :--- |") if (/^\s*\|[\s:|-]+\|\s*$/.test(line)) { continue; } // Only process table rows if (!line.trim().startsWith("|")) { continue; } // Extract all backticked tokens const tokens: string[] = []; let match: RegExpExecArray | null; BACKTICK_RE.lastIndex = 0; while ((match = BACKTICK_RE.exec(line)) !== null) { if (match[1]) { const token = match[1].trim(); if (token) { tokens.push(token); } } } // Skip rows with no tokens or only one token (need at least canonical) if (tokens.length < 1) { skippedRows++; continue; } // Check if aliases cell contains "(Kein Alias)" const hasNoAliases = /\(Kein Alias\)/i.test(line); const canonical = tokens[0]; if (!canonical) { skippedRows++; continue; } const inverse = tokens.length >= 2 && tokens[1] ? tokens[1] : undefined; // Extract aliases: all tokens after the first two, but only if not "(Kein Alias)" const aliases: string[] = []; if (!hasNoAliases && tokens.length > 2) { for (let i = 2; i < tokens.length; i++) { const alias = tokens[i]; if (alias) { aliases.push(alias); } } } // Store canonical entry byCanonical.set(canonical, { canonical, inverse, aliases, }); // Build alias-to-canonical mapping (case-insensitive keys) for (const alias of aliases) { if (alias) { const lowerAlias = alias.toLowerCase(); if (!aliasToCanonical.has(lowerAlias)) { aliasToCanonical.set(lowerAlias, canonical); } } } } if (skippedRows > 0) { console.warn(`parseEdgeVocabulary: Skipped ${skippedRows} rows with insufficient tokens`); } return { byCanonical, aliasToCanonical, }; }