#!/usr/bin/env python3 """ PowerON Markdown to HTML Converter Konvertiert Markdown-Dateien zu HTML mit PowerON-Styling """ import os import sys import argparse from pathlib import Path import re # Einfache Markdown-zu-HTML Konvertierung ohne externe Abhängigkeiten def _convert_markdown_to_html(markdown_text): """Einfache Markdown-zu-HTML Konvertierung ohne externe Bibliotheken""" # HTML-Escaping def escape_html(text): return (text.replace('&', '&') .replace('<', '<') .replace('>', '>') .replace('"', '"') .replace("'", ''')) # Code-Blöcke zuerst verarbeiten (vor anderen Formatierungen) def process_code_blocks(text): # Fenced code blocks (```) def replace_code_block(match): language = match.group(1) or '' code = match.group(2) return f'
{escape_html(code)}
' text = re.sub(r'```(\w+)?\n(.*?)\n```', replace_code_block, text, flags=re.DOTALL) # Inline code (`) text = re.sub(r'`([^`]+)`', r'\1', text) return text # Headers def process_headers(text): text = re.sub(r'^###### (.*?)$', r'
\1
', text, flags=re.MULTILINE) text = re.sub(r'^##### (.*?)$', r'
\1
', text, flags=re.MULTILINE) text = re.sub(r'^#### (.*?)$', r'

\1

', text, flags=re.MULTILINE) text = re.sub(r'^### (.*?)$', r'

\1

', text, flags=re.MULTILINE) text = re.sub(r'^## (.*?)$', r'

\1

', text, flags=re.MULTILINE) text = re.sub(r'^# (.*?)$', r'

\1

', text, flags=re.MULTILINE) return text # Listen def process_lists(text): lines = text.split('\n') in_ul = False in_ol = False result = [] for line in lines: # Ungeordnete Listen if re.match(r'^\s*[-*+]\s+', line): if in_ol: result.append('') in_ol = False if not in_ul: result.append('') in_ul = False if not in_ol: result.append('
    ') in_ol = True item = re.sub(r'^\s*\d+\.\s+', '', line) result.append(f'
  1. {item}
  2. ') else: if in_ul: result.append('') in_ul = False if in_ol: result.append('
') in_ol = False result.append(line) if in_ul: result.append('') if in_ol: result.append('') return '\n'.join(result) # Links und Bilder def process_links_and_images(text): # Bilder ![alt](url) text = re.sub(r'!\[([^\]]*)\]\(([^)]+)\)', r'\1', text) # Links [text](url) text = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'\1', text) return text # Bold und Italic def process_emphasis(text): # Bold **text** text = re.sub(r'\*\*(.*?)\*\*', r'\1', text) # Italic *text* text = re.sub(r'\*(.*?)\*', r'\1', text) return text # Blockquotes def process_blockquotes(text): lines = text.split('\n') in_quote = False result = [] for line in lines: if line.strip().startswith('>'): if not in_quote: result.append('
') in_quote = True quote_text = line.strip()[1:].strip() result.append(f'

{quote_text}

') else: if in_quote: result.append('
') in_quote = False result.append(line) if in_quote: result.append('') return '\n'.join(result) # Tabellen def process_tables(text): lines = text.split('\n') result = [] i = 0 while i < len(lines): line = lines[i].strip() # Prüfe ob es eine Tabellenzeile ist (enthält |) if '|' in line and not line.startswith('<'): table_lines = [] j = i # Sammle alle aufeinanderfolgenden Tabellenzeilen while j < len(lines) and '|' in lines[j].strip() and not lines[j].strip().startswith('<'): table_lines.append(lines[j].strip()) j += 1 if len(table_lines) >= 2: # Mindestens Header + Separator # Erstelle HTML-Tabelle table_html = [''] # Header-Zeile header_cells = [cell.strip() for cell in table_lines[0].split('|')[1:-1]] table_html.append('') for cell in header_cells: table_html.append(f'') table_html.append('') # Separator-Zeile überspringen if len(table_lines) > 1 and '---' in table_lines[1]: data_start = 2 else: data_start = 1 # Daten-Zeilen if len(table_lines) > data_start: table_html.append('') for row in table_lines[data_start:]: if '|' in row: data_cells = [cell.strip() for cell in row.split('|')[1:-1]] table_html.append('') for cell in data_cells: table_html.append(f'') table_html.append('') table_html.append('') table_html.append('
{cell}
{cell}
') result.append('\n'.join(table_html)) i = j - 1 # -1 weil i am Ende des Loops erhöht wird else: result.append(f'

{line}

') else: result.append(f'

{line}

') i += 1 return '\n'.join(result) # Horizontale Linien def process_hr(text): text = re.sub(r'^---$', '
', text, flags=re.MULTILINE) return text # Paragraphen - Jeder Zeilenumbruch wird zu einem

Tag def process_paragraphs(text): lines = text.split('\n') result = [] for line in lines: line = line.strip() if line: # Nur wenn es nicht schon ein HTML-Tag ist if not re.match(r'<[h1-6]|{line}

') else: result.append(line) else: # Leere Zeilen werden zu leeren

Tags result.append('

') return '\n'.join(result) # Verarbeitung in der richtigen Reihenfolge text = markdown_text # Code-Blöcke zuerst (vor anderen Formatierungen) text = process_code_blocks(text) # Headers text = process_headers(text) # Tabellen text = process_tables(text) # Blockquotes text = process_blockquotes(text) # Listen text = process_lists(text) # Links und Bilder text = process_links_and_images(text) # Emphasis text = process_emphasis(text) # Horizontale Linien text = process_hr(text) # Paragraphen text = process_paragraphs(text) return text class PowerONHTMLConverter: def __init__(self, css_file="poweron-styles.css"): self.css_file = css_file def create_html_template(self, title, content, css_path=None): """Erstellt HTML-Template mit PowerON-Styling""" if css_path is None: css_path = self.css_file html_template = f""" {title} | PowerON
{content}
""" return html_template def convert_markdown_file(self, input_file, output_file=None, css_path=None): """Konvertiert eine Markdown-Datei zu HTML""" input_path = Path(input_file) if not input_path.exists(): raise FileNotFoundError(f"Markdown-Datei nicht gefunden: {input_file}") # Output-Datei bestimmen if output_file is None: output_file = input_path.with_suffix('.html') else: output_file = Path(output_file) # Markdown lesen und konvertieren with open(input_path, 'r', encoding='utf-8') as f: markdown_content = f.read() # Titel aus Markdown extrahieren (erste H1) title_match = re.search(r'^#\s+(.+)$', markdown_content, re.MULTILINE) title = title_match.group(1) if title_match else input_path.stem # Markdown zu HTML konvertieren html_content = _convert_markdown_to_html(markdown_content) # HTML-Template erstellen full_html = self.create_html_template(title, html_content, css_path) # HTML-Datei schreiben with open(output_file, 'w', encoding='utf-8') as f: f.write(full_html) print(f"Konvertiert: {input_file} -> {output_file}") return output_file def convert_directory(self, input_dir, output_dir=None, css_path=None): """Konvertiert alle Markdown-Dateien in einem Verzeichnis""" input_path = Path(input_dir) if not input_path.exists(): raise FileNotFoundError(f"Verzeichnis nicht gefunden: {input_dir}") if output_dir is None: output_dir = input_path / "html_output" else: output_dir = Path(output_dir) output_dir.mkdir(exist_ok=True) # Alle .md Dateien finden md_files = list(input_path.glob("**/*.md")) if not md_files: print(f"⚠️ Keine Markdown-Dateien gefunden in: {input_dir}") return print(f"📁 Konvertiere {len(md_files)} Markdown-Dateien...") for md_file in md_files: # Relativen Pfad beibehalten relative_path = md_file.relative_to(input_path) output_file = output_dir / relative_path.with_suffix('.html') # Verzeichnis erstellen falls nötig output_file.parent.mkdir(parents=True, exist_ok=True) try: self.convert_markdown_file(md_file, output_file, css_path) except Exception as e: print(f"❌ Fehler bei {md_file}: {e}") def main(): parser = argparse.ArgumentParser( description="PowerON Markdown zu HTML Konverter", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Beispiele: python md_to_html_converter.py document.md python md_to_html_converter.py document.md -o output.html python md_to_html_converter.py -d ./docs -o ./html_output python md_to_html_converter.py document.md -c ../styles/custom.css """ ) parser.add_argument('input', nargs='?', help='Markdown-Datei oder Verzeichnis') parser.add_argument('-o', '--output', help='Output-Datei oder -verzeichnis') parser.add_argument('-d', '--directory', help='Verzeichnis mit Markdown-Dateien konvertieren') parser.add_argument('-c', '--css', default='poweron-styles.css', help='CSS-Datei (Standard: poweron-styles.css)') parser.add_argument('--version', action='version', version='PowerON MD to HTML Converter 1.0') args = parser.parse_args() if not args.input and not args.directory: parser.print_help() return try: converter = PowerONHTMLConverter(args.css) if args.directory: converter.convert_directory(args.directory, args.output, args.css) else: converter.convert_markdown_file(args.input, args.output, args.css) print("Konvertierung abgeschlossen!") except Exception as e: print(f"Fehler: {e}") sys.exit(1) if __name__ == "__main__": main()