From aa4e6d565ba4a3ce40edf2eef1b78d45925ee755 Mon Sep 17 00:00:00 2001 From: gabriele-cusato Date: Tue, 24 Mar 2026 00:49:51 +0100 Subject: [PATCH] corretto parsing, ora funziona anche per le tabelle con il carattere // al posto di <> --- CLAUDE.md | 13 ++++ HandTranscriptMd/src/embed.ts | 4 ++ HandTranscriptMd/src/md-parser.ts | 25 ++++--- HandTranscriptMd/src/parser.test.ts | 105 +++++++++++++++------------- HandTranscriptMd/src/settings.ts | 68 +++++++++--------- 5 files changed, 121 insertions(+), 94 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ccae81e..6b5ea72 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -223,6 +223,19 @@ cd C:\Projects\pluginObsidian\handWrittenMarkdownConverter\HandTranscriptMd; nod ### Task aperti +- **Bug `` multi-riga** — `src/md-parser.ts` → `expandKeywords`, case `TABLE`: + - Le intestazioni vengono generate correttamente ma le righe dati vengono lasciate invariate + - Esempio input: + ``` +
Col1, Col2, Col3 + val1, val2, val3 + val4, val5, val6 +
+ ``` + - Output atteso: tabella completa con intestazioni + righe dati + - Output attuale: intestazioni ok, poi le righe dati restano come testo grezzo (`val1,val2,val3`) seguite da `| |` e `|---|` + - Probabile causa: il loop di raccolta righe in `case 'TABLE'` non legge correttamente le righe successive + - **Keyword personalizzate nelle impostazioni** — `src/settings.ts` + `src/md-parser.ts`: - Permettere all'utente di aggiungere keyword custom nella pagina impostazioni - Ogni keyword custom ha: nome (es. `FIRMA`), output markdown (es. `— Mario Rossi`) diff --git a/HandTranscriptMd/src/embed.ts b/HandTranscriptMd/src/embed.ts index ade9374..b78f33e 100644 --- a/HandTranscriptMd/src/embed.ts +++ b/HandTranscriptMd/src/embed.ts @@ -349,6 +349,8 @@ async function doConvertWiki( const rawText = await recognizer.recognize(base64); if (!rawText.trim()) throw new Error('Nessun testo riconosciuto'); const markdown = parseMarkdown(rawText); + // In modalità debug mostra il testo grezzo restituito da Gemini (prima del parsing) + if (plugin.settings.debugMode) new Notice(`[DEBUG] Testo grezzo Gemini:\n${rawText}`, 30000); await archiveSvgByPath(svgPath, plugin); await replaceWikiEmbedWithMarkdown(svgPath, markdown, sourcePath, plugin); new Notice('Conversione completata!'); @@ -375,6 +377,8 @@ async function doConvert( if (!rawText.trim()) { new Notice('Nessun testo riconosciuto'); return; } const markdown = parseMarkdown(rawText); + // In modalità debug mostra il testo grezzo restituito da Gemini (prima del parsing) + if (plugin.settings.debugMode) new Notice(`[DEBUG] Testo grezzo Gemini:\n${rawText}`, 30000); await archiveSvg(data, plugin); await replaceEmbedWithMarkdown(ctx, data, markdown, plugin); new Notice('Conversione completata!'); diff --git a/HandTranscriptMd/src/md-parser.ts b/HandTranscriptMd/src/md-parser.ts index f62b465..53353cd 100644 --- a/HandTranscriptMd/src/md-parser.ts +++ b/HandTranscriptMd/src/md-parser.ts @@ -57,8 +57,8 @@ export function normalizeMarkdownSymbols(rawText: string): string { * Applica le correzioni su una singola riga (non dentro blocchi codice). */ function normalizeLine(line: string): string { - // Righe keyword — non modificare, le gestisce expandKeywords - if (/^<[A-Za-z0-9_]+/i.test(line.trim())) return line; + // Righe keyword //KEYWORD — non modificare, le gestisce expandKeywords + if (/^\/\/[A-Za-z0-9_]/i.test(line.trim())) return line; // Rimuove spazi finali (non iniziali: servono per le liste annidate) line = line.trimEnd(); @@ -194,10 +194,10 @@ export function expandKeywords(text: string, fnStart = 1): string { const line = lines[i]; const trimmed = line.trim(); - // Pattern: contenuto oppure contenuto (es. ) - // Il parametro extra (linguaggio per CODEBLOCK) è dentro le <>: - // Il contenuto segue > con opzionale : e/o spazio (es.

CIAO,

:CIAO) - const kw = trimmed.match(/^<([A-Za-z0-9_]+)(?:\s+([^>]*?))?\s*>[\s:]*(.*)/i); + // Pattern: //KEYWORD contenuto oppure //KEYWORD: contenuto (colon opzionale) + // Es: //H1 CIAO, //LIST a, b, c, //CODEBLOCK js, //HR + // Il contenuto segue il nome keyword separato da spazio e/o ':' + const kw = trimmed.match(/^\/\/([A-Za-z0-9_]+)\s*:?\s*(.*)/i); if (!kw) { out.push(line); @@ -206,8 +206,7 @@ export function expandKeywords(text: string, fnStart = 1): string { } const keyword = kw[1].toUpperCase(); // nome keyword normalizzato - const extra = (kw[2] ?? '').trim(); // parametro aggiuntivo (es. linguaggio CODEBLOCK) - const content = (kw[3] ?? '').trim(); // contenuto dopo ':' + const content = (kw[2] ?? '').trim(); // contenuto dopo la keyword switch (keyword) { @@ -287,9 +286,9 @@ export function expandKeywords(text: string, fnStart = 1): string { case 'INDENT': out.push(` ${content}`); break; // --- CODEBLOCK multi-riga (termina con riga vuota) --- - // Sintassi: _CODEBLOCK js: (linguaggio nel parametro opzionale prima di ':') + // Sintassi: //CODEBLOCK js (linguaggio dopo il nome keyword) case 'CODEBLOCK': { - const lang = extra; // es. "js", "python", "" + const lang = content; // es. "js", "python", "" i++; const codeLines: string[] = []; // Raccoglie righe fino alla prima riga vuota o fine testo @@ -314,9 +313,9 @@ export function expandKeywords(text: string, fnStart = 1): string { } // --- TABLE multi-riga --- - // Header:

Col1, Col2, Col3 + // Header: //TABLE Col1, Col2, Col3 // Righe: val1, val2, val3 (una per riga, virgola come separatore) - // Fine:
(tag di chiusura) oppure riga vuota / senza virgola + // Fine: //TABLE (tag di chiusura) oppure riga vuota / senza virgola case 'TABLE': { const headers = content.split(',').map(h => h.trim()); const rows: string[][] = []; @@ -324,7 +323,7 @@ export function expandKeywords(text: string, fnStart = 1): string { while (i < lines.length) { const rowLine = lines[i].trim(); // Tag di chiusura: "
" senza contenuto - if (/^
\s*$/i.test(rowLine)) { i++; break; } + if (/^\/\/TABLE\s*$/i.test(rowLine)) { i++; break; } // Fine implicita: riga vuota o riga senza virgola if (!rowLine || !rowLine.includes(',')) break; rows.push(rowLine.split(',').map(c => c.trim())); diff --git a/HandTranscriptMd/src/parser.test.ts b/HandTranscriptMd/src/parser.test.ts index b9ea15e..54b13c5 100644 --- a/HandTranscriptMd/src/parser.test.ts +++ b/HandTranscriptMd/src/parser.test.ts @@ -104,88 +104,93 @@ describe('normalizeMarkdownSymbols — blocchi codice protetti', () => { // ============================================================================= describe('expandKeywords — titoli', () => { - expect(expand('

Titolo')).toBe('# Titolo'); - expect(expand('

Sezione')).toBe('## Sezione'); - expect(expand('

Sub')).toBe('### Sub'); - expect(expand('

Piccolo')).toBe('#### Piccolo'); + expect(expand('//H1 Titolo')).toBe('# Titolo'); + expect(expand('//H2 Sezione')).toBe('## Sezione'); + expect(expand('//H3 Sub')).toBe('### Sub'); + expect(expand('//H4 Piccolo')).toBe('#### Piccolo'); }); describe('expandKeywords — inline style', () => { - expect(expand(' parola')).toBe('**parola**'); - expect(expand(' corsivo')).toBe('*corsivo*'); - expect(expand(' entrambi')).toBe('***entrambi***'); - expect(expand(' barrato')).toBe('~~barrato~~'); - expect(expand(' evidenziato')).toBe('==evidenziato=='); - expect(expand(' var x')).toBe('`var x`'); + expect(expand('//B parola')).toBe('**parola**'); + expect(expand('//I corsivo')).toBe('*corsivo*'); + expect(expand('//BI entrambi')).toBe('***entrambi***'); + expect(expand('//S barrato')).toBe('~~barrato~~'); + expect(expand('//HL evidenziato')).toBe('==evidenziato=='); + expect(expand('//CODE var x')).toBe('`var x`'); }); describe('expandKeywords — liste', () => { - expect(expand(' a, b, c')).toBe('- a\n- b\n- c'); - expect(expand(' a, b, c')).toBe('1. a\n2. b\n3. c'); - expect(expand(' task1, task2')).toBe('- [ ] task1\n- [ ] task2'); + expect(expand('//LIST a, b, c')).toBe('- a\n- b\n- c'); + expect(expand('//NUMLIST a, b, c')).toBe('1. a\n2. b\n3. c'); + expect(expand('//CHECK task1, task2')).toBe('- [ ] task1\n- [ ] task2'); }); describe('expandKeywords — tabella', () => { - expect(expand('

A, B, C')).toContain('| A | B | C |'); - expect(expand('
A, B, C')).toContain('|---|---|---|'); + expect(expand('//TABLE A, B, C')).toContain('| A | B | C |'); + expect(expand('//TABLE A, B, C')).toContain('|---|---|---|'); + // Tabella con righe dati e tag di chiusura + const tableInput = '//TABLE Col1, Col2, Col3\nval1, val2, val3\nval4, val5, val6\n//TABLE'; + const tableOut = expand(tableInput); + expect(tableOut).toContain('| Col1 | Col2 | Col3 |'); + expect(tableOut).toContain('| val1 | val2 | val3 |'); + expect(tableOut).toContain('| val4 | val5 | val6 |'); }); describe('expandKeywords — callout', () => { - expect(expand(' testo')).toBe('> [!NOTE]\n> testo'); - expect(expand(' testo')).toBe('> [!WARNING]\n> testo'); - expect(expand(' testo')).toBe('> [!TIP]\n> testo'); - expect(expand(' testo')).toBe('> [!INFO]\n> testo'); - expect(expand(' testo')).toBe('> [!ERROR]\n> testo'); - expect(expand(' testo')).toBe('> [!IMPORTANT]\n> testo'); - expect(expand(' testo')).toBe('> testo'); + expect(expand('//NOTE testo')).toBe('> [!NOTE]\n> testo'); + expect(expand('//WARN testo')).toBe('> [!WARNING]\n> testo'); + expect(expand('//TIP testo')).toBe('> [!TIP]\n> testo'); + expect(expand('//INFO testo')).toBe('> [!INFO]\n> testo'); + expect(expand('//ERROR testo')).toBe('> [!ERROR]\n> testo'); + expect(expand('//IMPORTANT testo')).toBe('> [!IMPORTANT]\n> testo'); + expect(expand('//QUOTE testo')).toBe('> testo'); }); describe('expandKeywords — link e immagini', () => { - expect(expand(' Google, https://google.com')).toBe('[Google](https://google.com)'); - expect(expand(' logo, https://example.com/img.png')).toBe('![logo](https://example.com/img.png)'); + expect(expand('//LINK Google, https://google.com')).toBe('[Google](https://google.com)'); + expect(expand('//IMG logo, https://example.com/img.png')).toBe('![logo](https://example.com/img.png)'); }); describe('expandKeywords — separatori', () => { - expect(expand('
')).toBe('---'); - expect(expand('')).toBe('---'); + expect(expand('//HR')).toBe('---'); + expect(expand('//SEP')).toBe('---'); }); describe('expandKeywords — footnote', () => { - expect(expand(' questa è una nota')).toBe('[^1]: questa è una nota'); + expect(expand('//FN questa è una nota')).toBe('[^1]: questa è una nota'); // Due footnote consecutive: numeri 1 e 2 - expect(expand(' prima\n seconda')).toBe('[^1]: prima\n[^2]: seconda'); + expect(expand('//FN prima\n//FN seconda')).toBe('[^1]: prima\n[^2]: seconda'); // Partenza custom - expect(expand(' nota', 5)).toBe('[^5]: nota'); + expect(expand('//FN nota', 5)).toBe('[^5]: nota'); }); describe('expandKeywords — math', () => { - expect(expand(' E=mc^2')).toBe('$E=mc^2$'); + expect(expand('//MATH E=mc^2')).toBe('$E=mc^2$'); }); describe('expandKeywords — tag e date/ora', () => { - expect(expand(' progetto')).toBe('#progetto'); - expect(expand(' due parole')).toBe('#due_parole'); - // , testo')).toBe('~~testo~~'); // alias STRIKE = S - expect(expand('
')).toBe('---'); // lowercase - expect(expand('
A, B')).toContain('| A | B |'); // mixed case + expect(expand('//bold testo')).toBe('**testo**'); // alias BOLD = B + expect(expand('//BOLD testo')).toBe('**testo**'); + expect(expand('//strike testo')).toBe('~~testo~~'); // alias STRIKE = S + expect(expand('//hr')).toBe('---'); // lowercase + expect(expand('//Table A, B')).toContain('| A | B |'); // mixed case }); -describe('expandKeywords — colon opzionale dopo >', () => { - expect(expand('testo')).toBe('**testo**'); // nessun separatore - expect(expand('

Titolo')).toBe('# Titolo'); // nessun separatore - expect(expand(': testo')).toBe('**testo**'); // con colon +describe('expandKeywords — colon opzionale', () => { + expect(expand('//B: testo')).toBe('**testo**'); // con colon + expect(expand('//H1: Titolo')).toBe('# Titolo'); // con colon }); describe('expandKeywords — CODEBLOCK multi-riga', () => { - const input = '\nconsole.log(\'ciao\')\nconst x = 1\n'; + const input = '//CODEBLOCK js\nconsole.log(\'ciao\')\nconst x = 1\n'; const out = expand(input); expect(out).toContain('```js'); expect(out).toContain('console.log(\'ciao\')'); @@ -193,7 +198,7 @@ describe('expandKeywords — CODEBLOCK multi-riga', () => { }); describe('expandKeywords — indent', () => { - expect(expand(' testo')).toBe(' testo'); + expect(expand('//INDENT testo')).toBe(' testo'); }); // ============================================================================= @@ -201,10 +206,16 @@ describe('expandKeywords — indent', () => { // ============================================================================= describe('pipeline completa', () => { - expect(parse('#Ciao\n a, b')).toBe('# Ciao\n- a\n- b'); - expect(parse('##sezione\n parola chiave')).toBe('## Sezione\n**parola chiave**'); + expect(parse('#Ciao\n//LIST a, b')).toBe('# Ciao\n- a\n- b'); + expect(parse('##sezione\n//B parola chiave')).toBe('## Sezione\n**parola chiave**'); // Il blocco codice non viene toccato dalla normalizzazione expect(parse('```\n#non toccare\n```')).toBe('```\n#non toccare\n```'); + // Tabella con righe dati attraverso la pipeline completa + const tableInput = '//TABLE Col1, Col2, Col3\nval1, val2, val3\nval4, val5, val6\n//TABLE'; + const tableOut = parse(tableInput); + expect(tableOut).toContain('| Col1 | Col2 | Col3 |'); + expect(tableOut).toContain('| val1 | val2 | val3 |'); + expect(tableOut).toContain('| val4 | val5 | val6 |'); }); // ============================================================================= diff --git a/HandTranscriptMd/src/settings.ts b/HandTranscriptMd/src/settings.ts index 5d163bb..a668c28 100644 --- a/HandTranscriptMd/src/settings.ts +++ b/HandTranscriptMd/src/settings.ts @@ -216,45 +216,45 @@ export class HandwritingSettingTab extends PluginSettingTab { cls: 'hwm_keyword-summary', }); details.createEl('p', { - text: 'Scrivi queste keyword nel disegno per generare la struttura markdown corrispondente. Tutte sono case-insensitive (

=

). Il contenuto segue direttamente dopo >.', + text: 'Scrivi queste keyword nel disegno per generare la struttura markdown corrispondente. Tutte sono case-insensitive (//h1 = //H1). Il contenuto segue dopo la keyword separato da uno spazio.', cls: 'setting-item-description', }); // Tabella keyword: [nome, sintassi, output] const KEYWORDS: [string, string, string][] = [ - ['

', '

Titolo', '# Titolo'], - ['

', '

Titolo', '## Titolo'], - ['

', '

Titolo', '### Titolo'], - ['

', '

Titolo', '#### Titolo'], - [' / ', ' testo', '**testo**'], - ['', ' testo', '*testo*'], - ['', ' testo', '***testo***'], - [' / ', ' testo', '~~testo~~'], - ['', ' testo', '==testo=='], - ['', ' testo', '`testo`'], - ['', '', '```js\n...\n```'], - ['', ' a, b, c', '- a\n- b\n- c'], - ['', ' a, b, c', '1. a\n2. b\n3. c'], - ['', ' a, b, c', '- [ ] a\n- [ ] b\n- [ ] c'], - ['

', '
Col1, Col2', '| Col1 | Col2 |\n|---|---|\n| ... |'], - ['', ' testo', '> [!NOTE]\n> testo'], - ['', ' testo', '> [!WARNING]\n> testo'], - ['', ' testo', '> [!TIP]\n> testo'], - ['', ' testo', '> [!INFO]\n> testo'], - ['', ' testo', '> [!ERROR]\n> testo'], - ['', ' testo', '> [!IMPORTANT]\n> testo'], - ['', ' testo', '> testo'], - ['', ' testo, url', '[testo](url)'], - ['', ' alt, url', '![alt](url)'], - ['
/ ', '
', '---'], - ['', ' nota a piè pagina', '[^1]: nota a piè pagina'], - ['', ' formula', '$formula$'], - ['', '', '$$\n...\n$$'], - ['', ' parola', '#parola'], - ['', '', 'YYYY-MM-DD'], - ['