From 3f0fe24875d5c04ef1b5063cf53f74b68b797c42 Mon Sep 17 00:00:00 2001 From: simonpipe Date: Fri, 10 Jul 2026 11:44:34 +0200 Subject: [PATCH] Update Core.js --- Core.js | 196 ++++++++++++++++++++++++++------------------------------ 1 file changed, 90 insertions(+), 106 deletions(-) diff --git a/Core.js b/Core.js index 2aeaa31..1dfe517 100644 --- a/Core.js +++ b/Core.js @@ -1,5 +1,5 @@ /** - * SimonUp Core Compiler (Core.js) - Version 1.2b.003 + * SimonUp Core Compiler (Core.js) - Version 1.2b.004 * Eine moderne, fluss- und blockorientierte Auszeichnungssprache. */ class SimonUp { @@ -7,22 +7,18 @@ class SimonUp { this.plugins = []; this.headings = []; this.counters = [0, 0, 0, 0, 0, 0]; + this.currentFootnotes = []; // Sammelt Fußnoten für den aktuellen Abschnitt } - /** - * Registriert eine Erweiterung (Plugin) - */ use(plugin) { this.plugins.push(plugin); return this; } - /** - * Hauptfunktion: Wandelt SimonUp-Text (.up) in valides HTML um - */ parse(text) { this.headings = []; this.counters = [0, 0, 0, 0, 0, 0]; + this.currentFootnotes = []; let html = text; @@ -34,10 +30,10 @@ class SimonUp { } } - // Phase 1: Blöcke und Inline-Strukturen parsen + // Phase 1: Strukturelle Blöcke isolieren html = this._parseBlocks(html); - // Phase 2: Das Inhaltsverzeichnis nachträglich in die vorbereitete Box einsetzen + // Phase 2: Das Inhaltsverzeichnis nachträglich injizieren html = this._injectTOC(html); if (this.plugins && this.plugins.length > 0) { @@ -51,9 +47,6 @@ class SimonUp { return html; } - /** - * INTERN: Verarbeitet strukturelle Blöcke und freie Strukturen zeilenweise - */ _parseBlocks(text) { const lines = text.replace(/\r\n/g, '\n').split('\n'); let result = []; @@ -64,9 +57,24 @@ class SimonUp { for (let i = 0; i < lines.length; i++) { let line = lines[i]; + let trimmed = line.trim(); - // Block-Start erkennen (&typ meta) - if (!inBlock && line.startsWith('&') && line.trim().length > 1) { + // Block-Ende erkennen mit /& + if (inBlock && trimmed.replace(/\u00A0/g, ' ') === '/&') { + result.push(this._renderBlock(blockType, blockMeta, blockBuffer)); + inBlock = false; + blockType = null; + blockMeta = ""; + continue; + } + + if (inBlock) { + blockBuffer.push(line); + continue; + } + + // Block-Start erkennen + if (line.startsWith('&') && trimmed.length > 1) { inBlock = true; const fullHeader = line.substring(1).trim(); const spaceIndex = fullHeader.indexOf(' '); @@ -82,46 +90,38 @@ class SimonUp { continue; } - // Block-Ende erkennen mit /& - if (inBlock && line.trim().replace(/\u00A0/g, ' ') === '/&') { - result.push(this._renderBlock(blockType, blockMeta, blockBuffer)); - inBlock = false; - blockType = null; - blockMeta = ""; - continue; + // Überschriften abfangen, um vorherige Fußnoten zu flushen + if (/^([1-6])(x|-)?#\s*/.test(trimmed)) { + let footnoteBlock = this._flushFootnotes(); + if (footnoteBlock) result.push(footnoteBlock); } - if (inBlock) { - blockBuffer.push(line); + // Freie Linien und Listen-Strukturen + if (trimmed === '___') { + result.push('
'); + } else if (trimmed.startsWith('- ')) { + let insideUl = this._parseInline(line.replace(/^\s*-\s+/, '')); + result.push(`${insideUl}`); + } else if (/^\d+\.\s+/.test(trimmed)) { + let cleanLine = line.replace(/^\s*\d+\.\s+/, ''); + let insideOl = this._parseInline(cleanLine); + result.push(`${insideOl}`); } else { - let trimmed = line.trim(); - - if (trimmed === '___') { - result.push('
'); - } else if (trimmed.startsWith('- ')) { - let insideUl = this._parseInline(line.replace(/^\s*-\s+/, '')); - result.push(`${insideUl}`); - } else if (/^\d+\.\s+/.test(trimmed)) { - let cleanLine = line.replace(/^\s*\d+\.\s+/, ''); - let insideOl = this._parseInline(cleanLine); - result.push(`${insideOl}`); - } else { - result.push(this._parseInline(line)); - } + result.push(this._parseInline(line)); } } - // Automatischer Schutz: Falls am Dateiende ein Block nicht geschlossen wurde if (inBlock && blockBuffer.length > 0) { result.push(this._renderBlock(blockType, blockMeta, blockBuffer)); } + // Letzte Fußnoten am Ende des Dokuments anfügen + let finalFootnotes = this._flushFootnotes(); + if (finalFootnotes) result.push(finalFootnotes); + return this._processParagraphsAndLists(result); } - /** - * INTERN: Erzeugt das HTML für einen geschlossenen Block - */ _renderBlock(type, meta, lines) { const content = lines.join('\n'); let self = this; @@ -172,12 +172,8 @@ class SimonUp { } } - /** - * INTERN: Robustes Rendern von Multi-line-Tabellen und Galerien - */ _renderTable(meta, lines) { let tableHtml = '\n'; - let colCount = 0; if (meta) { let metaParts = meta.trim().split(/\s+/); @@ -188,38 +184,35 @@ class SimonUp { } } - let fullContent = lines.join('\n'); - let rawCells = fullContent.split('|'); - - if (rawCells.length > 1 && rawCells[rawCells.length - 1].trim() === '') { - rawCells.pop(); - } + let processedLines = lines + .map(l => l.trim()) + .filter(l => l.length > 0) + .map(l => { + if (l.startsWith('|')) l = l.substring(1); + if (l.endsWith('|')) l = l.slice(0, -1); + return l; + }); + let fullContent = processedLines.join('|'); + let rawCells = fullContent.split('|'); let currentConfiguredCols = colCount || 1; let cellIndex = 0; tableHtml += '\n'; - for (let i = 0; i < rawCells.length; i++) { let cellText = rawCells[i].trim(); - let parsedCell = this._parseInline(cellText).replace(/\n/g, '
'); + let parsedCell = this._parseInline(cellText); tableHtml += ` \n`; - cellIndex++; - if (cellIndex === currentConfiguredCols && i < rawCells.length - 1) { tableHtml += '\n\n'; cellIndex = 0; } } - tableHtml += '\n
${parsedCell}
'; return tableHtml; } - /** - * INTERN: Verarbeitet Fluss-Formatierungen (Inline) ohne HTML-Zerstörung - */ _parseInline(text) { if (!text) return ''; let html = text; @@ -230,7 +223,7 @@ class SimonUp { return `__ESCAPED_CHAR_${escapes.length - 1}__`; }); - // 1. Überschriften ERST parsen (bevor HTML-Tags im Fluss entstehen) + // Überschriften parsen html = html.replace(/^([1-6])x#\s*(.*)/gm, (match, level, titleAndRest) => { let { title, rest } = this._extractHeadingRest(titleAndRest); const numStr = this._getAutoNumber(parseInt(level)); @@ -252,23 +245,19 @@ class SimonUp { return `${title}${rest}`; }); - // Medien & Links verarbeiten + // Medien & Links html = html.replace(/!\[([^\]]+)\s+w:(\d+)\]\(([^)]+)\)/g, '$1'); html = html.replace(/!\[([^\]]+)\]\(([^)]+)\)/g, '$1'); html = html.replace(/\[([^\]]+)\]\{([^}]+)\}/g, '$1'); html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); - // Hilfsfunktion: Führt Ersetzungen nur AUSSERHALB von HTML-Tags <...> aus const replaceOutsideTags = (regex, replacement) => { const masterRegex = new RegExp(/(<[^>]+>)/g.source + '|' + regex.source, 'g'); html = html.replace(masterRegex, (match, p1) => { - if (p1) return p1; // HTML-Tags überspringen - + if (p1) return p1; const innerMatch = regex.exec(match); if (innerMatch) { - if (typeof replacement === 'function') { - return replacement(...innerMatch); - } + if (typeof replacement === 'function') return replacement(...innerMatch); let res = replacement; for (let idx = 1; idx < innerMatch.length; idx++) { res = res.replace(new RegExp('\\$' + idx, 'g'), innerMatch[idx]); @@ -279,13 +268,10 @@ class SimonUp { }); }; - // 2. Formatierung für FETT (**text/** oder **text/) - replaceOutsideTags(/\*\*(.+?)(?:\/\*\*|\/|\*\*|$)/, '$1'); + // Exakte Trennung für Fett und Medium (Verhindert das Überschneiden) + replaceOutsideTags(/\*\*([^*]+?)(?:\/\*\*|\/|\*\*|$)/, '$1'); + replaceOutsideTags(/\*([^*]+?)(?:\/\*|\/|\*|$)/, '$1'); - // 3. Formatierung für MEDIUM (*text/* oder *text/) - replaceOutsideTags(/\*(.+?)(?:\/\*|\/|\*|$)/, '$1'); - - // 4. Standard Text-Inline-Rules via Symbol-Paar/Slash-Abschluss const inlineRules = [ { tag: 'em', symbol: '%' }, { tag: 'u', symbol: '_' }, @@ -299,20 +285,27 @@ class SimonUp { for (let k = 0; k < inlineRules.length; k++) { let rule = inlineRules[k]; let tagSimple = rule.tag.split(' ')[0]; - - // Unterstützt %text/% sowie das Fallback %text am Zeilenende replaceOutsideTags(new RegExp(rule.symbol + '(.+?)(?:\\/' + rule.symbol + '|\\/|' + rule.symbol + '|$)'), `<${rule.tag}>$1`); } - // Ausrichtungen & Fußnoten + // Fußnoten abfangen und registrieren statt nur als Title zu setzen + let self = this; + html = html.replace(/\^x\[([^\]]+)\]\^x/g, function(m, content) { + self.currentFootnotes.push(content); + const num = self.currentFootnotes.length; + return `[${num}]`; + }); + + html = html.replace(/\^(\d+)\[([^\]]+)\]\^x/g, function(m, num, content) { + self.currentFootnotes.push(`${num}: ${content}`); + return `[${num}]`; + }); + const alignMap = { 'l': 'left', 'r': 'right', 'c': 'center', 'j': 'justify' }; html = html.replace(/>([lrcj])\s+(.+?)\s*\/>/g, function(m, dir, content) { return `
${content}
`; }); - html = html.replace(/\^x\[([^\]]+)\]\^x/g, '[*]'); - html = html.replace(/\^(\d+)\[([^\]]+)\]\^x/g, '[$1]'); - html = html.replace(/__ESCAPED_CHAR_(\d+)__/g, function(match, idx) { return escapes[idx]; }); @@ -320,9 +313,18 @@ class SimonUp { return html; } - /** - * INTERN: Hilfsmethode zur Extraktion des optionalen /# Abschlusses bei Überschriften - */ + // Hilfsfunktion: Gibt gesammelte Fußnoten als HTML-Block aus und leert den Speicher + _flushFootnotes() { + if (this.currentFootnotes.length === 0) return null; + let html = '
\n'; + for (let i = 0; i < this.currentFootnotes.length; i++) { + html += `
${this.currentFootnotes[i]}
\n`; + } + html += '
'; + this.currentFootnotes = []; // Zurücksetzen fürs nächste Kapitel + return html; + } + _extractHeadingRest(text) { let title = text; let rest = ""; @@ -334,9 +336,6 @@ class SimonUp { return { title: title.trim(), rest: rest }; } - /** - * INTERN: Errechnet die nächste fortlaufende Kapitelnummer - */ _getAutoNumber(level) { let idx = level - 1; this.counters[idx]++; @@ -347,28 +346,16 @@ class SimonUp { return activeParts.join('.') + '.'; } - /** - * INTERN: Macht aus Text eine URL-konforme ID - */ _slugify(text) { return text.toLowerCase() .replace(/[^a-z0-9\s-]/g, '') .replace(/\s+/g, '-'); } - /** - * INTERN: Erzeugt die fertige HTML-Liste des TOC und injiziert sie in den Block - */ _injectTOC(html) { const placeholder = 'STRENG_GEHEIMER_TOC_PLATZHALTER'; - - if (!html.includes(placeholder)) { - return html; - } - - if (this.headings.length === 0) { - return html.replace(placeholder, ''); - } + if (!html.includes(placeholder)) return html; + if (this.headings.length === 0) return html.replace(placeholder, ''); let tocListHtml = '
\n