From ccc955472a7738188f004438102ab1f222ea511c Mon Sep 17 00:00:00 2001 From: simonpipe Date: Thu, 9 Jul 2026 18:00:25 +0200 Subject: [PATCH] Update Core.js --- Core.js | 278 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 175 insertions(+), 103 deletions(-) diff --git a/Core.js b/Core.js index d07c52d..15ff84c 100644 --- a/Core.js +++ b/Core.js @@ -1,5 +1,5 @@ /** - * SimonUp Core Compiler (Core.js) + * SimonUp Core Compiler (Core.js) - Version 1.2 * Eine moderne, fluss- und blockorientierte Auszeichnungssprache. */ class SimonUp { @@ -26,6 +26,7 @@ class SimonUp { let html = text; + // "Before"-Plugins ausführen for (const plugin of this.plugins) { if (plugin.before) html = plugin.before(html); } @@ -33,6 +34,7 @@ class SimonUp { html = this._parseBlocks(html); html = this._injectTOC(html); + // "After"-Plugins ausführen for (const plugin of this.plugins) { if (plugin.after) html = plugin.after(html); } @@ -41,7 +43,7 @@ class SimonUp { } /** - * INTERN: Verarbeitet strukturelle Blöcke zeilenweise + * INTERN: Verarbeitet strukturelle Blöcke und freie Strukturen zeilenweise */ _parseBlocks(text) { const lines = text.replace(/\r\n/g, '\n').split('\n'); @@ -54,6 +56,7 @@ class SimonUp { for (let i = 0; i < lines.length; i++) { let line = lines[i]; + // 1. Block-Start erkennen (&typ meta) if (!inBlock && line.startsWith('&') && line.trim().length > 1) { inBlock = true; const fullHeader = line.substring(1).trim(); @@ -70,6 +73,7 @@ class SimonUp { continue; } + // 2. Block-Ende erkennen (einzelnes &) if (inBlock && line.trim() === '&') { result.push(this._renderBlock(blockType, blockMeta, blockBuffer)); inBlock = false; @@ -78,18 +82,31 @@ class SimonUp { continue; } + // 3. Inhalt sammeln oder freie Zeilen verarbeiten if (inBlock) { blockBuffer.push(line); } else { - if (line.trim() === '___') { + let trimmed = line.trim(); + + if (trimmed === '___') { + // Horizontale Linie result.push('
'); + } else if (trimmed.startsWith('- ')) { + // Freier Listenpunkt (Ungeordnet) + result.push(`${this._parseInline(line.replace(/^\s*-\s+/, ''))}`); + } else if (/^\d+\.\s+/.test(trimmed)) { + // Freier Listenpunkt (Nummeriert) + let cleanLine = line.replace(/^\s*\d+\.\s+/, ''); + result.push(`${this._parseInline(cleanLine)}`); } else { + // Normaler Fließtext result.push(this._parseInline(line)); } } } - return this._processParagraphs(result); + // Freie Listenpunkte zusammenfassen und Absätze bauen + return this._processParagraphsAndLists(result); } /** @@ -100,25 +117,23 @@ class SimonUp { switch (type) { case 'toc': - let tocHtml = `
\n`; - if (meta) tocHtml += `

${meta}

\n`; - if (content.trim()) { - const intro = lines.map(l => this._parseInline(l)).join('
'); - tocHtml += `

${intro}

\n`; + // Platzhalter für das spätere Injektions-Verfahren hinterlassen + let tocWrapper = ``; + if (meta || content.trim()) { + tocWrapper = `
\n`; + if (meta) tocWrapper += `

${meta}

\n`; + if (content.trim()) { + const intro = lines.map(l => this._parseInline(l)).join('
'); + tocWrapper += `

${intro}

\n`; + } + tocWrapper += `\n
`; } - tocHtml += `\n
`; - return tocHtml; - - case '-': - return ''; - - case '+': - return '
    \n' + lines.filter(l => l.trim()).map(l => `
  1. ${this._parseInline(l)}
  2. `).join('\n') + '\n
'; + return tocWrapper; case 'c': const langClass = meta ? ` class="language-${meta}"` : ''; - const poolCode = content.replace(/&/g, '&').replace(//g, '>'); - return `
${poolCode}
`; + const escapedCode = content.replace(/&/g, '&').replace(//g, '>'); + return `
${escapedCode}
`; case 'q': return `
${lines.map(l => this._parseInline(l)).join('
')}
`; @@ -130,11 +145,17 @@ class SimonUp { return `
\n${meta || 'Details'}\n

${lines.map(l => this._parseInline(l)).join('
')}

\n
`; case 'i': - return `
\n${lines.map(l => this._parseInline(l)).join('
')}\n
`; + return `
\n${lines.map(l => this._parseInline(l)).join('
')}\n
`; case 't': return this._renderTable(meta, lines); + case '>': + return `
${lines.map(l => this._parseInline(l)).join('
')}
`; + + case '>>': + return `
${lines.map(l => this._parseInline(l)).join('
')}
`; + default: return `
${lines.map(l => this._parseInline(l)).join('
')}
`; } @@ -148,113 +169,137 @@ class SimonUp { let currentCells = []; let cellBuffer = []; + // Parsing-Logik für Multi-line-Zellen via Pipe (|) for (let line of lines) { - if (line.trim() === '') continue; - let parts = line.split('|'); for (let i = 0; i < parts.length; i++) { let part = parts[i]; - if (i === parts.length - 1 && part.trim() === '') { - if (cellBuffer.length > 0 || part !== '') { + + if (i === parts.length - 1) { + if (part.trim() !== '') { cellBuffer.push(part); } - if (cellBuffer.length > 0) { + if (line.endsWith('|') && cellBuffer.length > 0) { currentCells.push(cellBuffer.join('\n')); cellBuffer = []; } - tableHtml += '\n' + currentCells.map(c => `${this._parseInline(c.trim())}`).join('\n') + '\n\n'; - currentCells = []; } else { cellBuffer.push(part); - if (i < parts.length - 1) { - currentCells.push(cellBuffer.join('\n')); - cellBuffer = []; - } + currentCells.push(cellBuffer.join('\n')); + cellBuffer = []; } } + + if (line.endsWith('|') && currentCells.length > 0) { + tableHtml += '\n' + currentCells.map(c => `${this._parseInline(c.trim()).replace(/\n/g, '
')}`).join('\n') + '\n\n'; + currentCells = []; + } } tableHtml += ''; return tableHtml; } /** - * INTERN: Verarbeitet Fluss-Formatierungen (Inline) mittels RegEx + * INTERN: Verarbeitet Fluss-Formatierungen (Inline) mittels V1.2-RegEx */ _parseInline(text) { if (!text) return ''; let html = text; - // 1. ESCAPE: Temporär schützen - html = html.replace(/\\(.)/g, (match, char) => ``); + // 1. ESCAPE: Steuerzeichen sichern (Schutz vor doppelter Evaluierung) + const escapes = []; + html = html.replace(/\\(.)/g, (match, char) => { + escapes.push(char); + return ``; + }); - // 2. ÜBERSCHRIFTEN (Flusssicher angepasst) - - // Regel A: Automatische Nummerierung (3#x Titel 3#x Restlicher Text) - html = html.replace(/([1-6])#x\s*([^#]+?)\s*\1#x(.*)/g, (match, level, title, rest) => { - const lvl = parseInt(level); - const numStr = this._getAutoNumber(lvl); - const fullTitle = `${numStr} ${title.trim()}`; + // 2. UNIVERSAL-RESET: Verarbeitet den "//"-Reset im Satz + html = html.replace(/\/\/(\s|$)/g, '$1'); + + // 3. ÜBERSCHRIFTEN (V1.2 konform: Startet mit X#, schließt optional mit /# oder Zeilenende) + const processHeading = (pattern, callback) => { + let match; + while ((match = pattern.exec(html)) !== null) { + const fullMatch = match[0]; + const level = parseInt(match[1]); + const titleAndRest = match[2]; + + // Schließzeichen /# suchen, sonst bis Zeilenende parsen + let title = titleAndRest; + let rest = ""; + const closeIdx = titleAndRest.indexOf('/#'); + if (closeIdx !== -1) { + title = titleAndRest.substring(0, closeIdx); + rest = titleAndRest.substring(closeIdx + 2); + } + + const result = callback(level, title.trim(), rest); + html = html.substring(0, match.index) + result + html.substring(match.index + fullMatch.length); + } + }; + + // 3a. Automatische Nummerierung (1x# bis 6x#) + processHeading(/^([1-6])x#\s*(.*)/g, (level, title, rest) => { + const numStr = this._getAutoNumber(level); + const fullTitle = `${numStr} ${title}`; const id = this._slugify(fullTitle); - - this.headings.push({ level: lvl, text: fullTitle, id: id }); - return '' + fullTitle + '' + rest; + this.headings.push({ level, text: fullTitle, id }); + return `${fullTitle}${rest}`; }); - // Regel B: Vom TOC ausgeschlossene Titel (3#- Titel 3#- Restlicher Text) - html = html.replace(/([1-6])#-\s*([^#]+?)\s*\1#-(.*)/g, (match, level, title, rest) => { - const lvl = parseInt(level); - return '' + title.trim() + '' + rest; + // 3b. Nicht im TOC erwünscht (1-# bis 6-#) + processHeading(/^([1-6])-#\s*(.*)/g, (level, title, rest) => { + return `${title}${rest}`; }); - // Regel C: Manuelle Nummerierung (3# Titel 3# Restlicher Text) - html = html.replace(/([1-6])#\s*([^#]+?)\s*\1#(.*)/g, (match, level, title, rest) => { - const lvl = parseInt(level); - const id = this._slugify(title.trim()); - - this.headings.push({ level: lvl, text: title.trim(), id: id }); - return '' + title.trim() + '' + rest; + // 3c. Standard-Überschrift (1# bis 6#) + processHeading(/^([1-6])#\s*(.*)/g, (level, title, rest) => { + const id = this._slugify(title); + this.headings.push({ level, text: title, id }); + return `${title}${rest}`; }); - // Regel D: Das schnelle, solitäre # für H1 (# Titel # Restlicher Text) - html = html.replace(/#([^#]+)#(.*)/g, (match, title, rest) => { - const id = this._slugify(title.trim()); - this.headings.push({ level: 1, text: title.trim(), id: id }); - return '

' + title.trim() + '

' + rest; + // 4. INLINE SCHLIESS-LOGIK (V1.2: ZeichenText/Zeichen) + const inlineRules = [ + { tag: 'strong', symbol: '\\*\\*' }, // Fett (**Text/**) + { tag: 'span style="font-weight: 500;"', symbol: '\\*' }, // Medium (*Text/*) + { tag: 'em', symbol: '%' }, // Kursiv (%Text/%) + { tag: 'u', symbol: '_' }, // Unterstrichen (_Text/_) + { tag: 'del', symbol: '~' }, // Durchgestrichen (~Text/~) + { tag: 'mark', symbol: '=' }, // Highlight (=Text/=) + { tag: 'sup', symbol: '\\^' }, // Hochgestellt (^Text/^) + { tag: 'sub', symbol: '°' }, // Tiefgestellt (°Text/°) + { tag: 'code', symbol: '`' } // Inline-Code (`Text/`) + ]; + + inlineRules.forEach(rule => { + // Regulärer Match: Symbol -> Text -> /Symbol + let regex = new RegExp(`${rule.symbol}(.+?)\/${rule.symbol}`, 'g'); + html = html.replace(regex, `<${rule.tag}>$1`); + + // Fallback für Universal-Reset: Offenes Symbol bis zum let resetRegex = new RegExp(`${rule.symbol}(.+?)(?=)`, 'g'); + html = html.replace(resetRegex, `<${rule.tag}>$1`); + }); + html = html.replace(//g, ''); // Aufgeräumte Reset-Tags + + // 5. AUSRICHTUNG IM FLUSS (>c Text />) + const alignMap = { l: 'left', r: 'right', c: 'center', j: 'justify' }; + html = html.replace(/>([lrcj])\s+(.+?)\s*\/>/g, (m, dir, content) => { + return `
${content}
`; }); - // 3. STANDARD FLUSS-FORMATIERUNGEN (In priorisierter Reihenfolge) - html = html.replace(/\*\*([^*]+)\*\*/g, '$1'); // Fett - html = html.replace(/\/([^/]+)\//g, '$1'); // Kursiv - html = html.replace(/_([^_]+)_/g, '$1'); // Unterstrichen - html = html.replace(/~([^~]+)~/g, '$1'); // Durchgestrichen - html = html.replace(/=([^=]+)=/g, '$1'); // Highlight - html = html.replace(/\^([^^]+)\^/g, '$1'); // Hochgestellt - html = html.replace(/°([^°]+)°/g, '$1'); // Tiefgestellt - html = html.replace(/`([^`]+)`/g, '$1'); // Inline-Code - - // 3.1 MEDIUM-FORMATIERUNG (Sucht nach *, ignoriert aber bereits generiertes HTML) - html = html.replace(/(?]*)\*([^*<\/>]+)\*(?![^<]*>)/g, '$1'); - - // 4. AUSRICHTUNG & EINRÜCKUNG (Basiszeichen '>') - html = html.replace(/>l ([^>]+)>/g, '
$1
'); - html = html.replace(/>r ([^>]+)>/g, '
$1
'); - html = html.replace(/>c ([^>]+)>/g, '
$1
'); - html = html.replace(/>j ([^>]+)>/g, '
$1
'); - html = html.replace(/>tt ([^>]+)>/g, '
$1
'); - html = html.replace(/>t ([^>]+)>/g, '
$1
'); - - // 5. LINKS & MEDIEN + // 6. LINKS & MEDIEN html = html.replace(/!\[([^\]]+)\s+w:(\d+)\]\(([^)]+)\)/g, '$1'); html = html.replace(/!\[([^\]]+)\]\(([^)]+)\)/g, '$1'); html = html.replace(/\[([^\]]+)\]\{([^}]+)\}/g, '$1'); html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); - // 6. FUSSNOTEN + // 7. FUSSNOTEN html = html.replace(/\^x\[([^\]]+)\]\^/g, '[*]'); html = html.replace(/\^(\d+)\[([^\]]+)\]\^/g, '[$1]'); - // 7. ESCAPE ZURÜCKWANDELN - html = html.replace(/<\!--SU_ESC_(\d+)-->/g, (match, code) => String.fromCharCode(parseInt(code))); + // 8. ESCAPES wiederherstellen + html = html.replace(//g, (match, idx) => escapes[idx]); return html; } @@ -280,12 +325,11 @@ class SimonUp { } /** - * INTERN: Erzeugt die fertige HTML-Liste des TOC und injiziert sie in den Platzhalter + * INTERN: Erzeugt die fertige HTML-Liste des TOC und injiziert sie in den Block */ _injectTOC(html) { - if (this.headings.length === 0) { - return html.replace('', ''); - } + if (!html.includes('')) return html; + if (this.headings.length === 0) return html.replace('', ''); let tocListHtml = '
\n\n'.repeat(currentLevel - heading.level); } currentLevel = heading.level; - tocListHtml += `
  • ${heading.text}
  • \n`; } @@ -310,32 +353,61 @@ class SimonUp { } /** - * INTERN: Analysiert leere Zeilen, um Absätze (

    ) und Zeilenumbrüche (
    ) zu trennen + * INTERN: Fasst freie Listen-Elemente zusammen und generiert Absätze (

    ) */ - _processParagraphs(elements) { + _processParagraphsAndLists(elements) { let processed = []; let paragraphBuffer = []; + let listBuffer = []; + let currentListType = null; // 'ul' oder 'ol' + + const flushList = () => { + if (listBuffer.length > 0) { + processed.push(`<${currentListType}>\n` + listBuffer.map(item => `

  • ${item}
  • `).join('\n') + `\n`); + listBuffer = []; + currentListType = null; + } + }; + + const flushParagraph = () => { + if (paragraphBuffer.length > 0) { + processed.push(`

    ${paragraphBuffer.join('
    ')}

    `); + paragraphBuffer = []; + } + }; for (let element of elements) { - if (element.startsWith('<') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('') && !element.startsWith(' 0) { - processed.push(`

    ${paragraphBuffer.join('
    ')}

    `); - paragraphBuffer = []; + let isUlItem = element.startsWith(''); + let isOlItem = element.startsWith(''); + + if (isUlItem || isOlItem) { + flushParagraph(); + let type = isUlItem ? 'ul' : 'ol'; + let content = isUlItem ? element.replace(/<\/?ul-item>/g, '') : element.replace(/<\/?ol-item>/g, ''); + + if (currentListType && currentListType !== type) { + flushList(); } + currentListType = type; + listBuffer.push(content); + } else if (element.startsWith('<') && !/^(
    |||||||||||)/i.test(element)) { + // Strukturelles Block-Element (z.B. ,
    ) + flushList(); + flushParagraph(); processed.push(element); } else if (element.trim() === '') { - if (paragraphBuffer.length > 0) { - processed.push(`

    ${paragraphBuffer.join('
    ')}

    `); - paragraphBuffer = []; - } + // Leerzeile bricht Listen und Absätze auf + flushList(); + flushParagraph(); } else { + // Fließtext mündet im Absatz-Buffer + flushList(); paragraphBuffer.push(element); } } - if (paragraphBuffer.length > 0) { - processed.push(`

    ${paragraphBuffer.join('
    ')}

    `); - } + flushList(); + flushParagraph(); return processed.join('\n'); }