mindnet_obsidian/src/vocab/parseEdgeVocabulary.ts

106 lines
2.9 KiB
TypeScript
Raw Normal View History

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<string, { canonical: string; inverse?: string; aliases: string[] }>();
const aliasToCanonical = new Map<string, string>();
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,
};
}