/** * SimonUp Core Compiler (Core.js) - Version 1.2 * Eine moderne, fluss- und blockorientierte Auszeichnungssprache. */ class SimonUp { constructor() { this.plugins = []; this.headings = []; this.counters = [0, 0, 0, 0, 0, 0]; } /** * 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]; 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); } } } html = this._parseBlocks(html); 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; } /** * INTERN: Verarbeitet strukturelle Blöcke und freie Strukturen zeilenweise */ _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]; if (!inBlock && line.startsWith('&') && line.trim().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; } if (inBlock && line.trim().replace(/\u00A0/g, ' ') === '&') { result.push(this._renderBlock(blockType, blockMeta, blockBuffer)); inBlock = false; blockType = null; blockMeta = ""; continue; } if (inBlock) { blockBuffer.push(line); } 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)); } } } 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; switch (type) { case 'toc': let tocWrapper = ''; if (meta || content.trim()) { 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 += '\n
'; } 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}
`; } } /** * INTERN: Hilfsfunktion zum Rendern von Multi-line-Tabellen und Galerien */ _renderTable(meta, lines) { let tableHtml = '\n'; let currentCells = []; let cellBuffer = []; let self = this; for (let i = 0; i < lines.length; i++) { let line = lines[i]; let parts = line.split('|'); for (let j = 0; j < parts.length; j++) { let part = parts[j]; if (j === parts.length - 1) { if (part.trim() !== '') { cellBuffer.push(part); } if (line.endsWith('|') && cellBuffer.length > 0) { currentCells.push(cellBuffer.join('\n')); cellBuffer = []; } } else { cellBuffer.push(part); currentCells.push(cellBuffer.join('\n')); cellBuffer = []; } } if (line.endsWith('|') && currentCells.length > 0) { let rowContent = currentCells.map(function(c) { let parsedCell = self._parseInline(c.trim()).replace(/\n/g, '
'); return ``; }).join('\n'); tableHtml += `\n${rowContent}\n\n`; currentCells = []; } } tableHtml += '
${parsedCell}
'; return tableHtml; } /** * INTERN: Verarbeitet Fluss-Formatierungen (Inline) mittels V1.2-RegEx */ _parseInline(text) { if (!text) return ''; let html = text; const escapes = []; html = html.replace(/\\(.)/g, function(match, char) { escapes.push(char); return ''; }); html = html.replace(/\/\/(( \s|$))/g, '$1'); let self = this; const processHeading = function(pattern, callback) { let match; while ((match = pattern.exec(html)) !== null) { const fullMatch = match[0]; const level = parseInt(match[1]); const titleAndRest = match[2]; 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); } }; processHeading(/^([1-6])x#\s*(.*)/g, function(level, title, rest) { const numStr = self._getAutoNumber(level); const fullTitle = `${numStr} ${title}`; const id = self._slugify(fullTitle); self.headings.push({ level: level, text: fullTitle, id: id }); return `${fullTitle}${rest}`; }); processHeading(/^([1-6])-#\s*(.*)/g, function(level, title, rest) { return `${title}${rest}`; }); processHeading(/^([1-6])#\s*(.*)/g, function(level, title, rest) { const id = self._slugify(title); self.headings.push({ level: level, text: title, id: id }); return `${title}${rest}`; }); const inlineRules = [ { tag: 'strong', symbol: '\\*\\*' }, { tag: 'span style="font-weight: 500;"', symbol: '\\*' }, { 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]; let regex = new RegExp(rule.symbol + '(.+?)\\\\' + rule.symbol, 'g'); html = html.replace(regex, `<${rule.tag}>$1`); let resetRegex = new RegExp(rule.symbol + '(.+?)(?=)', 'g'); html = html.replace(resetRegex, `<${rule.tag}>$1`); } html = html.replace(//g, ''); 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(/!\[([^\]]+)\s+w:(\d+)\]\(([^)]+)\)/g, '$1'); html = html.replace(/!\[([^\]]+)\]\(([^)]+)\)/g, '$1'); html = html.replace(/\[([^\]]+)\]\{([^}]+)\}/g, '$1'); html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); html = html.replace(/\^x\[([^\]]+)\]\^x/g, '[*]'); html = html.replace(/\^(\d+)\[([^\]]+)\]\^x/g, '[$1]'); html = html.replace(//g, function(match, idx) { return escapes[idx]; }); return html; } /** * INTERN: Errechnet die nächste fortlaufende Kapitelnummer */ _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('.') + '.'; } /** * 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) { if (!html.includes('')) { return html; } if (this.headings.length === 0) { return html.replace('', ''); } let tocListHtml = '
\n\n'.repeat(diffFinal); } tocListHtml += '\n
'; return html.replace('', tocListHtml); } /** * SimonUp Core Compiler (Core.js) - Version 1.2 * Eine moderne, fluss- und blockorientierte Auszeichnungssprache. */ class SimonUp { constructor() { this.plugins = []; this.headings = []; this.counters = [0, 0, 0, 0, 0, 0]; } /** * 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]; 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); } } } html = this._parseBlocks(html); 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; } /** * INTERN: Verarbeitet strukturelle Blöcke und freie Strukturen zeilenweise */ _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]; if (!inBlock && line.startsWith('&') && line.trim().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; } if (inBlock && line.trim().replace(/\u00A0/g, ' ') === '&') { result.push(this._renderBlock(blockType, blockMeta, blockBuffer)); inBlock = false; blockType = null; blockMeta = ""; continue; } if (inBlock) { blockBuffer.push(line); } 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)); } } } 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; switch (type) { case 'toc': let tocWrapper = ''; if (meta || content.trim()) { 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 += '\n
'; } 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}
`; } } /** * INTERN: Hilfsfunktion zum Rendern von Multi-line-Tabellen und Galerien */ _renderTable(meta, lines) { let tableHtml = '\n'; let currentCells = []; let cellBuffer = []; let self = this; for (let i = 0; i < lines.length; i++) { let line = lines[i]; let parts = line.split('|'); for (let j = 0; j < parts.length; j++) { let part = parts[j]; if (j === parts.length - 1) { if (part.trim() !== '') { cellBuffer.push(part); } if (line.endsWith('|') && cellBuffer.length > 0) { currentCells.push(cellBuffer.join('\n')); cellBuffer = []; } } else { cellBuffer.push(part); currentCells.push(cellBuffer.join('\n')); cellBuffer = []; } } if (line.endsWith('|') && currentCells.length > 0) { let rowContent = currentCells.map(function(c) { let parsedCell = self._parseInline(c.trim()).replace(/\n/g, '
'); return ``; }).join('\n'); tableHtml += `\n${rowContent}\n\n`; currentCells = []; } } tableHtml += '
${parsedCell}
'; return tableHtml; } /** * INTERN: Verarbeitet Fluss-Formatierungen (Inline) mittels V1.2-RegEx */ _parseInline(text) { if (!text) return ''; let html = text; const escapes = []; html = html.replace(/\\(.)/g, function(match, char) { escapes.push(char); return ''; }); html = html.replace(/\/\/(( \s|$))/g, '$1'); let self = this; const processHeading = function(pattern, callback) { let match; while ((match = pattern.exec(html)) !== null) { const fullMatch = match[0]; const level = parseInt(match[1]); const titleAndRest = match[2]; 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); } }; processHeading(/^([1-6])x#\s*(.*)/g, function(level, title, rest) { const numStr = self._getAutoNumber(level); const fullTitle = `${numStr} ${title}`; const id = self._slugify(fullTitle); self.headings.push({ level: level, text: fullTitle, id: id }); return `${fullTitle}${rest}`; }); processHeading(/^([1-6])-#\s*(.*)/g, function(level, title, rest) { return `${title}${rest}`; }); processHeading(/^([1-6])#\s*(.*)/g, function(level, title, rest) { const id = self._slugify(title); self.headings.push({ level: level, text: title, id: id }); return `${title}${rest}`; }); const inlineRules = [ { tag: 'strong', symbol: '\\*\\*' }, { tag: 'span style="font-weight: 500;"', symbol: '\\*' }, { 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]; let regex = new RegExp(rule.symbol + '(.+?)\\\\' + rule.symbol, 'g'); html = html.replace(regex, `<${rule.tag}>$1`); let resetRegex = new RegExp(rule.symbol + '(.+?)(?=)', 'g'); html = html.replace(resetRegex, `<${rule.tag}>$1`); } html = html.replace(//g, ''); 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(/!\[([^\]]+)\s+w:(\d+)\]\(([^)]+)\)/g, '$1'); html = html.replace(/!\[([^\]]+)\]\(([^)]+)\)/g, '$1'); html = html.replace(/\[([^\]]+)\]\{([^}]+)\}/g, '$1'); html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); html = html.replace(/\^x\[([^\]]+)\]\^x/g, '[*]'); html = html.replace(/\^(\d+)\[([^\]]+)\]\^x/g, '[$1]'); html = html.replace(//g, function(match, idx) { return escapes[idx]; }); return html; } /** * INTERN: Errechnet die nächste fortlaufende Kapitelnummer */ _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('.') + '.'; } /** * 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) { if (!html.includes('')) { return html; } if (this.headings.length === 0) { return html.replace('', ''); } let tocListHtml = '
\n\n'.repeat(diffFinal); } tocListHtml += '\n
'; return html.replace('', tocListHtml); } /** * INTERN: Fasst freie Listen-Elemente zusammen und generiert Absätze (p) */ _processParagraphsAndLists(elements) { let processed = []; let paragraphBuffer = []; let listBuffer = []; let currentListType = null; const flushList = function() { if (listBuffer.length > 0) { let items = listBuffer.map(function(item) { return `
  • ${item}
  • `; }).join('\n'); processed.push(`<${currentListType}>\n${items}\n`); listBuffer = []; currentListType = null; } }; const flushParagraph = function() { if (paragraphBuffer.length > 0) { let pContent = paragraphBuffer.join('
    '); processed.push(`

    ${pContent}

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