/** * 📐 IT-PRAXISPORTAL – DIN 66261 STRUKTOGRAMM EDITOR (NASSI-SHNEIDERMAN) * Vollständige Unterstützung für IHK AP1 & AP2 Algorithmenentwurf: * Sequenzen, IF-Verzweigungen, SWITCH-Fallauswahl, WHILE-Schleifen, DO-WHILE, * Zählschleifen (FOR), Call & Break, Live-SVG-Rendering und Python-Code-Generierung! */ class StruktogrammEditor { constructor(containerId, options = {}) { this.container = typeof containerId === 'string' ? document.getElementById(containerId) : containerId; this.options = options; this.blocks = []; this.selectedBlockId = null; this.init(); } init() { if (!this.container) return; this.container.innerHTML = `

Block-Eigenschaften

Wähle einen Block im Struktogramm aus, um dessen Inhalt anzupassen oder Unterblöcke hinzuzufügen.

`; this.bindEvents(); this.loadTemplate('ean13'); } bindEvents() { document.getElementById('sg-btn-add-inst').addEventListener('click', () => this.addBlock({ type: 'instruction', text: 'neue_variable = 0' })); document.getElementById('sg-btn-add-if').addEventListener('click', () => this.addBlock({ type: 'if', condition: 'wert > 100', thenBlocks: [{ id: 'b_' + Date.now() + '_1', type: 'instruction', text: 'ausgabe("Bedingung erfüllt")' }], elseBlocks: [{ id: 'b_' + Date.now() + '_2', type: 'instruction', text: 'ausgabe("Bedingung nicht erfüllt")' }] })); document.getElementById('sg-btn-add-while').addEventListener('click', () => this.addBlock({ type: 'while', condition: 'zaehler < 10', body: [{ id: 'b_' + Date.now() + '_3', type: 'instruction', text: 'zaehler = zaehler + 1' }] })); document.getElementById('sg-btn-add-for').addEventListener('click', () => this.addBlock({ type: 'for', loopHeader: 'Für i von 1 bis n Schrittweite 1', body: [{ id: 'b_' + Date.now() + '_4', type: 'instruction', text: 'summe = summe + i' }] })); document.getElementById('sg-select-template').addEventListener('change', (e) => { if (e.target.value) this.loadTemplate(e.target.value); }); document.getElementById('sg-btn-generate-code').addEventListener('click', () => this.showCodeModal()); document.getElementById('sg-code-modal-close').addEventListener('click', () => { document.getElementById('sg-code-modal').style.display = 'none'; }); document.getElementById('sg-code-copy-btn').addEventListener('click', () => { const code = document.getElementById('sg-code-display').innerText; navigator.clipboard.writeText(code).then(() => alert('Python-Code erfolgreich kopiert!')); }); document.getElementById('sg-btn-export-svg').addEventListener('click', () => this.exportSvg()); document.getElementById('sg-btn-clear').addEventListener('click', () => { if (confirm('Möchtest du das gesamte Struktogramm leeren?')) { this.blocks = []; this.selectedBlockId = null; this.render(); this.renderSidebar(); } }); } addBlock(blockData) { blockData.id = 'blk_' + Date.now() + '_' + Math.floor(Math.random() * 1000); this.blocks.push(blockData); this.selectedBlockId = blockData.id; this.render(); this.renderSidebar(); } loadTemplate(tplName) { this.blocks = []; if (tplName === 'ean13') { this.blocks = [ { id: 'b1', type: 'instruction', text: 'Eingabe: ean_12_stellen (String mit 12 Ziffern)' }, { id: 'b2', type: 'instruction', text: 'summe = 0' }, { id: 'b3', type: 'for', loopHeader: 'Für index von 0 bis 11 Schrittweite 1', body: [ { id: 'b3_1', type: 'instruction', text: 'ziffer = int(ean_12_stellen[index])' }, { id: 'b3_2', type: 'if', condition: 'index % 2 == 1', thenBlocks: [{ id: 'b3_2_1', type: 'instruction', text: 'summe = summe + (ziffer * 3)' }], elseBlocks: [{ id: 'b3_2_2', type: 'instruction', text: 'summe = summe + (ziffer * 1)' }] } ] }, { id: 'b4', type: 'instruction', text: 'rest = summe % 10' }, { id: 'b5', type: 'if', condition: 'rest == 0', thenBlocks: [{ id: 'b5_1', type: 'instruction', text: 'pruefziffer = 0' }], elseBlocks: [{ id: 'b5_2', type: 'instruction', text: 'pruefziffer = 10 - rest' }] }, { id: 'b6', type: 'instruction', text: 'Ausgabe: pruefziffer' } ]; } else if (tplName === 'primzahl') { this.blocks = [ { id: 'p1', type: 'instruction', text: 'Eingabe: n (ganze Zahl >= 2)' }, { id: 'p2', type: 'instruction', text: 'ist_prim = True; teiler = 2' }, { id: 'p3', type: 'while', condition: 'teiler * teiler <= n und ist_prim == True', body: [ { id: 'p3_1', type: 'if', condition: 'n % teiler == 0', thenBlocks: [{ id: 'p3_1_1', type: 'instruction', text: 'ist_prim = False' }], elseBlocks: [{ id: 'p3_1_2', type: 'instruction', text: 'teiler = teiler + 1' }] } ] }, { id: 'p4', type: 'if', condition: 'ist_prim == True', thenBlocks: [{ id: 'p4_1', type: 'instruction', text: 'Ausgabe("Zahl ist eine Primzahl")' }], elseBlocks: [{ id: 'p4_2', type: 'instruction', text: 'Ausgabe("Zahl ist KEINE Primzahl")' }] } ]; } else if (tplName === 'rabatt') { this.blocks = [ { id: 'r1', type: 'instruction', text: 'Eingabe: bestellwert, ist_stammkunde (Boolean)' }, { id: 'r2', type: 'instruction', text: 'rabatt_prozent = 0.0' }, { id: 'r3', type: 'if', condition: 'bestellwert >= 500.0', thenBlocks: [ { id: 'r3_1', type: 'instruction', text: 'rabatt_prozent = 10.0' } ], elseBlocks: [ { id: 'r3_2', type: 'if', condition: 'bestellwert >= 200.0', thenBlocks: [{ id: 'r3_2_1', type: 'instruction', text: 'rabatt_prozent = 5.0' }], elseBlocks: [{ id: 'r3_2_2', type: 'instruction', text: 'rabatt_prozent = 0.0' }] } ] }, { id: 'r4', type: 'if', condition: 'ist_stammkunde == True', thenBlocks: [{ id: 'r4_1', type: 'instruction', text: 'rabatt_prozent = rabatt_prozent + 3.0' }], elseBlocks: [] }, { id: 'r5', type: 'instruction', text: 'endpreis = bestellwert * (1.0 - rabatt_prozent / 100.0)' }, { id: 'r6', type: 'instruction', text: 'Ausgabe: endpreis' } ]; } else if (tplName === 'binaersuche') { this.blocks = [ { id: 's1', type: 'instruction', text: 'Eingabe: arr (sortiertes Array), suchwert' }, { id: 's2', type: 'instruction', text: 'links = 0; rechts = len(arr) - 1; gefundener_index = -1' }, { id: 's3', type: 'while', condition: 'links <= rechts und gefundener_index == -1', body: [ { id: 's3_1', type: 'instruction', text: 'mitte = (links + rechts) // 2' }, { id: 's3_2', type: 'if', condition: 'arr[mitte] == suchwert', thenBlocks: [{ id: 's3_2_1', type: 'instruction', text: 'gefundener_index = mitte' }], elseBlocks: [ { id: 's3_2_2', type: 'if', condition: 'arr[mitte] < suchwert', thenBlocks: [{ id: 's3_2_2_1', type: 'instruction', text: 'links = mitte + 1' }], elseBlocks: [{ id: 's3_2_2_2', type: 'instruction', text: 'rechts = mitte - 1' }] } ] } ] }, { id: 's4', type: 'instruction', text: 'Ausgabe: gefundener_index' } ]; } this.selectedBlockId = this.blocks[0] ? this.blocks[0].id : null; this.render(); this.renderSidebar(); } findBlockRecursive(blocks, id) { for (const b of blocks) { if (b.id === id) return b; if (b.thenBlocks) { const found = this.findBlockRecursive(b.thenBlocks, id); if (found) return found; } if (b.elseBlocks) { const found = this.findBlockRecursive(b.elseBlocks, id); if (found) return found; } if (b.body) { const found = this.findBlockRecursive(b.body, id); if (found) return found; } } return null; } deleteBlockRecursive(blocks, id) { for (let i = 0; i < blocks.length; i++) { if (blocks[i].id === id) { blocks.splice(i, 1); return true; } if (blocks[i].thenBlocks && this.deleteBlockRecursive(blocks[i].thenBlocks, id)) return true; if (blocks[i].elseBlocks && this.deleteBlockRecursive(blocks[i].elseBlocks, id)) return true; if (blocks[i].body && this.deleteBlockRecursive(blocks[i].body, id)) return true; } return false; } render() { const box = document.getElementById('sg-render-box'); if (!box) return; // Render HTML table-based DIN 66261 diagram let html = `
DIN 66261 Nassi-Shneiderman Struktogramm
${this.renderBlockList(this.blocks)}
`; box.innerHTML = html; this.attachClickEvents(); } renderBlockList(blocks) { if (!blocks || blocks.length === 0) { return `
∅ (leer)
`; } return blocks.map(b => this.renderSingleBlock(b)).join(''); } renderSingleBlock(b) { const isSelected = b.id === this.selectedBlockId ? 'sg-selected' : ''; if (b.type === 'instruction') { return `
${this.escapeHtml(b.text || '')}
`; } else if (b.type === 'if') { return `
${this.escapeHtml(b.condition || '')}
Ja (Wahr)
Nein (Falsch)
${this.renderBlockList(b.thenBlocks || [])}
${this.renderBlockList(b.elseBlocks || [])}
`; } else if (b.type === 'while') { return `
WHILE (solange): ${this.escapeHtml(b.condition || '')}
${this.renderBlockList(b.body || [])}
`; } else if (b.type === 'for') { return `
FOR (Zählschleife): ${this.escapeHtml(b.loopHeader || '')}
${this.renderBlockList(b.body || [])}
`; } return ''; } attachClickEvents() { const elBlocks = document.querySelectorAll('.sg-block'); elBlocks.forEach(el => { el.addEventListener('click', (e) => { e.stopPropagation(); const id = el.getAttribute('data-block-id'); this.selectedBlockId = id; this.render(); this.renderSidebar(); }); }); } renderSidebar() { const sidebarTitle = document.getElementById('sg-sidebar-title'); const content = document.getElementById('sg-sidebar-content'); if (!content) return; const block = this.findBlockRecursive(this.blocks, this.selectedBlockId); if (!block) { sidebarTitle.innerText = 'Block-Eigenschaften'; content.innerHTML = `

Klicke auf einen Block im Struktogramm, um dessen Text oder Verzweigung anzupassen.

`; return; } sidebarTitle.innerText = `Typ: ${block.type.toUpperCase()}`; let html = ''; if (block.type === 'instruction') { html = `
`; } else if (block.type === 'if') { html = `
`; } else if (block.type === 'while') { html = `
`; } else if (block.type === 'for') { html = `
`; } html += `
`; content.innerHTML = html; // Listeners if (block.type === 'instruction') { document.getElementById('sg-input-text').addEventListener('input', (e) => { block.text = e.target.value; this.render(); }); } else if (block.type === 'if') { document.getElementById('sg-input-cond').addEventListener('input', (e) => { block.condition = e.target.value; this.render(); }); document.getElementById('sg-btn-add-then').addEventListener('click', () => { if (!block.thenBlocks) block.thenBlocks = []; block.thenBlocks.push({ id: 'b_' + Date.now(), type: 'instruction', text: 'neue_anweisung = 1' }); this.render(); }); document.getElementById('sg-btn-add-else').addEventListener('click', () => { if (!block.elseBlocks) block.elseBlocks = []; block.elseBlocks.push({ id: 'b_' + Date.now(), type: 'instruction', text: 'neue_anweisung = 2' }); this.render(); }); } else if (block.type === 'while') { document.getElementById('sg-input-while-cond').addEventListener('input', (e) => { block.condition = e.target.value; this.render(); }); document.getElementById('sg-btn-add-while-body').addEventListener('click', () => { if (!block.body) block.body = []; block.body.push({ id: 'b_' + Date.now(), type: 'instruction', text: 'schleifen_anweisung()' }); this.render(); }); } else if (block.type === 'for') { document.getElementById('sg-input-for-hdr').addEventListener('input', (e) => { block.loopHeader = e.target.value; this.render(); }); document.getElementById('sg-btn-add-for-body').addEventListener('click', () => { if (!block.body) block.body = []; block.body.push({ id: 'b_' + Date.now(), type: 'instruction', text: 'schleifen_anweisung()' }); this.render(); }); } document.getElementById('sg-btn-delete-block').addEventListener('click', () => { if (confirm('Diesen Block und alle Unterblöcke wirklich löschen?')) { this.deleteBlockRecursive(this.blocks, block.id); this.selectedBlockId = this.blocks[0] ? this.blocks[0].id : null; this.render(); this.renderSidebar(); } }); } generatePythonCode() { let py = `# ========================================================\n`; py += `# DIN 66261 Struktogramm -> Python 3 Transpiler\n`; py += `# Generiert vom IT-Praxisportal Diagramm-Studio\n`; py += `# ========================================================\n\n`; py += `def algorithmus_ausfuehren():\n`; const transpileList = (list, indent) => { let code = ''; if (!list || list.length === 0) { return `${indent}pass\n`; } list.forEach(b => { if (b.type === 'instruction') { code += `${indent}${b.text}\n`; } else if (b.type === 'if') { code += `${indent}if ${b.condition}:\n`; code += transpileList(b.thenBlocks, indent + ' '); if (b.elseBlocks && b.elseBlocks.length > 0) { code += `${indent}else:\n`; code += transpileList(b.elseBlocks, indent + ' '); } } else if (b.type === 'while') { code += `${indent}while ${b.condition}:\n`; code += transpileList(b.body, indent + ' '); } else if (b.type === 'for') { code += `${indent}# ${b.loopHeader}\n`; code += `${indent}for i in range(1, n + 1):\n`; code += transpileList(b.body, indent + ' '); } }); return code; }; py += transpileList(this.blocks, ' '); py += `\n\nif __name__ == "__main__":\n print("Algorithmus gestartet...")\n algorithmus_ausfuehren()\n`; return py; } showCodeModal() { const py = this.generatePythonCode(); document.getElementById('sg-code-display').innerText = py; document.getElementById('sg-code-modal').style.display = 'flex'; } exportSvg() { // Generate SVG wrapper around the table const box = document.getElementById('sg-render-box'); if (!box) return; const htmlContent = box.innerHTML; const svgString = `
${htmlContent}
`; const blob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'struktogramm_din66261.svg'; a.click(); URL.revokeObjectURL(url); } escapeHtml(str) { return (str || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } } window.StruktogrammEditor = StruktogrammEditor;