/** * SimonUp Core Compiler (Core.js) - Version 1.2b.005 * Eine moderne, fluss- und blockorientierte Auszeichnungssprache. */ class SimonUp { constructor() { this.plugins = []; this.headings = []; this.counters = [0, 0, 0, 0, 0, 0]; this.currentFootnotes = []; // Sammelt Fußnoten für den aktuellen Abschnitt } use(plugin) { this.plugins.push(plugin); return this; } parse(text) { this.headings = []; this.counters = [0, 0, 0, 0, 0, 0]; this.currentFootnotes = []; let html = text; if (this.plugins && this.plugins.length > 0) { for (let i = 0; i < this.plugins.length; i++) { if (this.plugins[i].before) { html = this.plugins[i].before(html); } } } // Phase 1: Strukturelle Blöcke isolieren html = this._parseBlocks(html); // Phase 2: Das Inhaltsverzeichnis nachträglich injizieren html = this._injectTOC(html); if (this.plugins && this.plugins.length > 0) { for (let i = 0; i < this.plugins.length; i++) { if (this.plugins[i].after) { html = this.plugins[i].after(html); } } } return html; } _parseBlocks(text) { const lines = text.replace(/\r\n/g, '\n').split('\n'); let result = []; let inBlock = false; let blockType = null; let blockMeta = ""; let blockBuffer = []; for (let i = 0; i < lines.length; i++) { let line = lines[i]; let trimmed = line.trim(); // 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(' '); if (spaceIndex !== -1) { blockType = fullHeader.substring(0, spaceIndex).toLowerCase(); blockMeta = fullHeader.substring(spaceIndex).trim(); } else { blockType = fullHeader.toLowerCase(); blockMeta = ""; } blockBuffer = []; 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); } // 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 { result.push(this._parseInline(line)); } } 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); } _renderBlock(type, meta, lines) { const content = lines.join('\n'); let self = this; switch (type) { case 'toc': let tocWrapper = '
\n'; if (meta) { tocWrapper += `

${meta}

\n`; } if (content.trim()) { const intro = lines.map(function(l) { return self._parseInline(l); }).join('
'); tocWrapper += `

${intro}

\n`; } tocWrapper += 'STRENG_GEHEIMER_TOC_PLATZHALTER\n'; tocWrapper += '
'; return tocWrapper; case 'c': const langClass = meta ? ` class="language-${meta}"` : ''; const escapedCode = content.replace(/&/g, '&').replace(//g, '>'); return `
${escapedCode}
`; case 'q': const qContent = lines.map(function(l) { return self._parseInline(l); }).join('
'); return `
${qContent}
`; case 'b': const bContent = lines.map(function(l) { return self._parseInline(l); }).join('
'); return `
${bContent}
`; case 'f': const fSummary = meta || 'Details'; const fContent = lines.map(function(l) { return self._parseInline(l); }).join('
'); return `
\n${fSummary}\n

${fContent}

\n
`; case 'i': const iColor = meta || '#ff6347'; const iContent = lines.map(function(l) { return self._parseInline(l); }).join('
'); return `
\n${iContent}\n
`; case 't': return this._renderTable(meta, lines); default: const defContent = lines.map(function(l) { return self._parseInline(l); }).join('
'); return `
${defContent}
`; } } _renderTable(meta, lines) { let tableHtml = '\n'; let colCount = 0; if (meta) { let metaParts = meta.trim().split(/\s+/); if (metaParts.length === 1 && metaParts[0].endsWith('x')) { colCount = parseInt(metaParts[0]); } else { colCount = metaParts.length; } } 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); tableHtml += ` \n`; cellIndex++; if (cellIndex === currentConfiguredCols && i < rawCells.length - 1) { tableHtml += '\n\n'; cellIndex = 0; } } tableHtml += '\n
${parsedCell}
'; return tableHtml; } _parseInline(text) { if (!text) return ''; let html = text; const escapes = []; html = html.replace(/\\(.)/g, function(match, char) { escapes.push(char); return `__ESCAPED_CHAR_${escapes.length - 1}__`; }); // Ü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)); const fullTitle = `${numStr} ${title}`; const id = this._slugify(fullTitle); this.headings.push({ level: parseInt(level), text: title, id: id }); return `${fullTitle}${rest}`; }); html = html.replace(/^([1-6])-#\s*(.*)/gm, (match, level, titleAndRest) => { let { title, rest } = this._extractHeadingRest(titleAndRest); return `${title}${rest}`; }); html = html.replace(/^([1-6])#\s*(.*)/gm, (match, level, titleAndRest) => { let { title, rest } = this._extractHeadingRest(titleAndRest); const id = this._slugify(title); this.headings.push({ level: parseInt(level), text: title, id: id }); return `${title}${rest}`; }); // 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'); const replaceOutsideTags = (regex, replacement) => { const masterRegex = new RegExp(/(<[^>]+>)/g.source + '|' + regex.source, 'g'); html = html.replace(masterRegex, (match, p1) => { if (p1) return p1; const innerMatch = regex.exec(match); if (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]); } return res; } return match; }); }; // Exakte Trennung für Fett und Medium (Verhindert das Überschneiden) replaceOutsideTags(/\*\*([^*]+?)(?:\/\*\*|\/|\*\*|$)/, '$1'); replaceOutsideTags(/\*([^*]+?)(?:\/\*|\/|\*|$)/, '$1'); const inlineRules = [ { tag: 'em', symbol: '%' }, { tag: 'u', symbol: '_' }, { tag: 'del', symbol: '~' }, { tag: 'mark', symbol: '=' }, { tag: 'sup', symbol: '\\^' }, { tag: 'sub', symbol: '°' }, { tag: 'code', symbol: '`' } ]; for (let k = 0; k < inlineRules.length; k++) { let rule = inlineRules[k]; let tagSimple = rule.tag.split(' ')[0]; replaceOutsideTags(new RegExp(rule.symbol + '(.+?)(?:\\/' + rule.symbol + '|\\/|' + rule.symbol + '|$)'), `<${rule.tag}>$1`); } // Fußnoten abfangen und registrieren 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(/__ESCAPED_CHAR_(\d+)__/g, function(match, idx) { return escapes[idx]; }); return html; } // 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 = ""; const closeIdx = text.indexOf('/#'); if (closeIdx !== -1) { title = text.substring(0, closeIdx); rest = text.substring(closeIdx + 2); } return { title: title.trim(), rest: rest }; } _getAutoNumber(level) { let idx = level - 1; this.counters[idx]++; for (let i = idx + 1; i < 6; i++) { this.counters[i] = 0; } let activeParts = this.counters.slice(0, level); return activeParts.join('.') + '.'; } _slugify(text) { return text.toLowerCase() .replace(/[^a-z0-9\s-]/g, '') .replace(/\s+/g, '-'); } _injectTOC(html) { const placeholder = 'STRENG_GEHEIMER_TOC_PLATZHALTER'; if (!html.includes(placeholder)) return html; if (this.headings.length === 0) return html.replace(placeholder, ''); let tocListHtml = '
\n\n'.repeat(diffFinal); } tocListHtml += '\n
'; return html.replace(placeholder, tocListHtml); } _processParagraphsAndLists(elements) { let processed = []; let paragraphBuffer = []; let listBuffer = []; let currentListType = null; function flushList() { if (listBuffer.length > 0) { let items = listBuffer.map(function(item) { return "
  • " + item + "
  • "; }).join("\n"); processed.push("<" + currentListType + " style='margin-bottom: 12px; margin-left: 20px;'>\n" + items + "\n"); listBuffer = []; currentListType = null; } } function flushParagraph() { if (paragraphBuffer.length > 0) { let pContent = paragraphBuffer.join("
    "); processed.push("

    " + pContent + "

    "); paragraphBuffer = []; } } for (let i = 0; i < elements.length; i++) { let element = elements[i]; if (!element) continue; 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("<") && !/^(||||||||||||