#!/usr/bin/env python3 """ Generate static HTML catalog pages from SQL dump """ import re import json from pathlib import Path from html import escape # Language mappings LANG_NAMES = { 'DE': 'Deutsch', 'EN': 'English', 'FR': 'Français' } # Composer names for file generation COMPOSERS = ['Bach', 'Beethoven', 'Brahms', 'Buxtehude', 'Chopin', 'Mozart', 'Schumann', 'Wagner', 'Wieck'] # German mappings (from dataDE.inc.php) GENRE_DE = { "am": "Abendmusiken", "hoch": "Hochzeitsarien", "cas": "Kanons", "cantn": "Kantaten", "lit": "Liturgische Werke", "cla": "Werke für Klavier", "worg": "Werke für Orgel", "ws": "Werke für Streicher mit Basso continuo", "wom": "Werke ohne Musik", "art": "Die Kunst der Fuge", "cano": "Kanon", "cant": "Kantate", "cha": "Kammermusik", "chov": "Choral, vierstimmig", "con": "Konzert", "cpia": "Konzert für zwei bis vier Klaviere", "csol": "Konzert für ein oder mehrere Soloinstrumente", "hymn": "Lied", "lut": "Musik für Laute", "mass": "Messe", "mot": "Motette", "off": "Musikalisches Opfer", "ora": "Oratorium", "org": "Musik für Orgel", "over": "Ouvertüre", "pas": "Passionsmusik", "pian": "Musik für Klavier und Cembalo", "imos": "Instrumentalmusik für Orchester: Symphonie", "imoo": "Instrumentalmusik für Orchester: Ouverture", "imob": "Instrumentalmusik für Orchester: Ballettmusik", "imoa": "Instrumentalmusik für Orchester: Andere Werke", "imb": "Instrumentalmusik: für Blasinstrumente", "imsso": "Instrumentalmusik: für mehrere Soloinstrumente und Orchester", "imko": "Instrumentalmusik für Klavier und Orchester", "imvo": "Instrumentalmusik für Violine und Orchester", "kammk": "Instrumentalmusik: Kammermusik mit Klavier", "kamok": "Instrumentalmusik: Kammermusik ohne Klavier", "imkv": "Instrumentalmusik für Klavier zu vier Händen", "imkzs": "Instrumentalmusik für Klavier zu zwei Händen: Sonate", "imkzv": "Instrumentalmusik für Klavier zu zwei Händen: Variation", "imkzt": "Instrumentalmusik für Klavier zu zwei Händen: Tanz", "imkza": "Instrumentalmusik für Klavier zu zwei Händen: Andere Werke", "imsa": "Instrumentalmusik: Solostücke für andere Instrumente", "vmu": "Vokalmusik", "vmumo": "Vokalmusik: Messe, Oratorium", "vmuo": "Vokalmusik: Oper, Bühnenmusik", "vmub": "Vokalmusik: für eine oder mehrer Stimmen mit Begleitung", "vmuob": "Vokalmusik: für eine oder mehrer Stimmen ohne Begleitung", "vmuk": "Vokalmusik: Kanon", "vmus": "Vokalmusik: Musikalischer Scherz", "bal": "Ballade", "ein": "Einzelstück", "etu": "Etude", "fug": "Fuge, Kanon", "imp": "Impromptu", "kam": "Kammermusik", "lie": "Lied", "maz": "Mazurka", "noc": "Nocturne", "pol": "Polonaise", "pre": "Prélude", "ron": "Rondo", "sch": "Scherzo", "son": "Sonate", "var": "Variation", "wal": "Walzer", "vom": "Messe", "vol": "Litanei", "vog": "geistlicher Gesang", "voo": "Kantate, Oratorium", "vop": "Oper", "voa": "Arie, Duett, Trio, Quartett, mit oder ohne Begleitung", "vlk": "Lied mit Klavierbegleitung", "vok": "Kanon", "iou": "Ouvertüre", "isy": "Symphonie", "ise": "Serenade, Divertimento", "ima": "Marsch, Einzelsatz, kleineres Stück", "ita": "Tanz", "iks": "Konzert für Saiten- oder Blasinstrumente mit Orchester", "cqs": "Streichquintett, -quartett", "cds": "Streich-Duo, -Trio", "kor": "Musik für ein, zwei oder drei Klaviere und Orchester", "kqt": "Trio, Quartett, Quintett für Klavier oder Cembalo", "ksv": "Sonate für Tasteninstrument und Violine", "kss": "Sonate für Tasteninstrument und Streicher", "kvh": "Musik für Klavier oder Cembalo zu vier Händen", "ksp": "Sonate, Phantasie für Tasteninstrument", "kva": "Variationen für Tasteninstrument", "kks": "kleineres Stück für Tasteninstrument", "sio": "Sonate für verschiedene Instrumente und Orgel", "sup": "unvollendetes oder zweifelhaftes Werk", "arr": "Arrangement", "cho": "Chormusik", "exe": "Studienwerk", "kkm": "Klavier- und Kammermusik", "opu": "Oper (unvollendet)", "opv": "Oper", "orc": "Orchesterwerk", "sce": "Schauspielmusik", "the": "Einzelthema oder Melodie", "vor": "Arie mit Orchester", "xcp": "Kammermusik: Klarinette und Klavier", "xqc": "Kammermusik: Klarinettenquintett", "xpo": "Kammermusik: Klavier oder Orgel", "xpvh": "Kammermusik: Klavier zu vier Händen", "xtqqp": "Kammermusik: Klaviertrios, -quartette, -quintett", "xst": "Kammermusik: Streichinstrumente", "xvp": "Kammermusik: Violine und Klavier", "xvcp": "Kammermusik: Violoncello und Klavier", "xpd": "Kammermusik: zwei Klaviere", "xos": "Orchester mit Soloinstrument", "xow": "Orchesterwerke", "xch": "Vokalmusik: Chöre", "xdgp": "Vokalmusik: Duette mit Klavier", "xgp": "Vokalmusik: einstimmige Lieder mit Klavier", "xgins": "Vokalmusik: Lieder und Chöre mit mehreren Instrumenten", "xgmp": "Vokalmusik: mehrstimmige Gesänge mit Klavier oder Orgel", "xg": "Vokalmusik: mehrstimmige Gesänge ohne Begleitung", "ybew": "Bearbeitungen von Werken anderer Komponisten", "ybum": "Bühnenmusik", "yccf": "Chormusik a cappella, Frauenstimen", "yccg": "Chormusik a cappella, gemischte Stimmen", "yccm": "Chormusik a cappella, Männerstimmen", "ycco": "Chorwerke mit Orchester", "ydtg": "Duette und Trios für Gesang", "ykfs": "Kammermusik für Streicher", "ykmp": "Kammermusik mit Klavier", "ykmo": "Konzerte mit Orchester", "ylie": "Lieder", "yvgp": "Mehrstimmige Gesänge mit Klavier oder Orgel", "ypvh": "Musik für Klavier zu vier Händen", "ypzh": "Musik für Klavier zu zwei Händen", "ymzp": "Musik für zwei Klaviere", "ywfo": "Werke für Orchester", "yorg": "Werke für Orgel", "wschor": "Vokalmusik: Chöre", "wslied": "Lied", "wsorchkm": "Orchester/Kammermusik", "wspi": "Klavier" } TONART_DE = { "cdur": "C-Dur", "fdur": "F-Dur", "bdur": "B-Dur", "esdur": "Es-Dur", "asdur": "As-Dur", "desdur": "Des-Dur", "gesdur": "Ges-Dur", "cesdur": "Ces-Dur", "gdur": "G-Dur", "ddur": "D-Dur", "adur": "A-Dur", "edur": "E-Dur", "hdur": "H-Dur", "fisdur": "Fis-Dur", "cisdur": "Cis-Dur", "amoll": "a-Moll", "dmoll": "d-Moll", "gmoll": "g-Moll", "cmoll": "c-Moll", "fmoll": "f-Moll", "bmoll": "b-Moll", "esmoll": "es-Moll", "asmoll": "as-Moll", "emoll": "e-Moll", "hmoll": "h-Moll", "fismoll": "fis-Moll", "cismoll": "cis-Moll", "gismoll": "gis-Moll", "dismoll": "dis-Moll", "aismoll": "ais-Moll" } BESETZUNG_DE = { "-al-": "Alt", "-ba-": "Bass", "-bn-": "Fagott", "-cb-": "Kontrabass", "-cem-": "Cembalo", "-choir-": "Chor", "-cl-": "Klarinette", "-clo-": "Clarino", "-co-": "Horn", "-cont-": "Continuo", "-fl-": "Flöte", "-gh-": "Glasharmonica", "-ha-": "Harfe", "-lu-": "Laute", "-man-": "Mandoline", "-ob-": "Oboe", "-orch-": "Orchester", "-org-": "Orgel", "-pi-": "Klavier", "-so-": "Sopran", "-taille-": "Taille", "-tamburi-": "Tamburi", "-tb-": "Posaune", "-te-": "Tenor", "-tm-": "Pauke/Trommel", "-tp-": "Trompete", "-va-": "Viola", "-vadagamba-": "Viola da Gamba", "-vc-": "Violoncello", "-vn-": "Violine", "-vo-": "Stimme", "-vs-": "Stimmen", "-vnpic-": "Violino piccolo" } # English mappings (abbreviated for brevity - add full mappings based on dataEN.inc.php) GENRE_EN = {**GENRE_DE} # Simplified - should use full English translations TONART_EN = { "cdur": "C major", "fdur": "F major", "bdur": "B flat major", "esdur": "E flat major", "asdur": "A flat major", "desdur": "D flat major", "gesdur": "G flat major", "cesdur": "C flat major", "gdur": "G major", "ddur": "D major", "adur": "A major", "edur": "E major", "hdur": "B major", "fisdur": "F sharp major", "cisdur": "C sharp major", "amoll": "A minor", "dmoll": "D minor", "gmoll": "G minor", "cmoll": "C minor", "fmoll": "F minor", "bmoll": "B flat minor", "esmoll": "E flat minor", "asmoll": "A flat minor", "emoll": "E minor", "hmoll": "B minor", "fismoll": "F sharp minor", "cismoll": "C sharp minor", "gismoll": "G sharp minor", "dismoll": "D sharp minor", "aismoll": "A sharp minor" } BESETZUNG_EN = {**BESETZUNG_DE} # Simplified # French mappings (abbreviated - use full mappings) GENRE_FR = {**GENRE_DE} # Simplified TONART_FR = { "cdur": "ut majeur", "fdur": "fa majeur", "bdur": "si bémol majeur", "esdur": "mi bémol majeur", "asdur": "la bémol majeur", "desdur": "ré bémol majeur", "gesdur": "sol bémol majeur", "cesdur": "ut bémol majeur", "gdur": "sol majeur", "ddur": "ré majeur", "adur": "la majeur", "edur": "mi majeur", "hdur": "si majeur", "fisdur": "fa dièse majeur", "cisdur": "ut dièse majeur", "amoll": "la mineur", "dmoll": "ré mineur", "gmoll": "sol mineur", "cmoll": "ut mineur", "fmoll": "fa mineur", "bmoll": "si bémol mineur", "esmoll": "mi bémol mineur", "asmoll": "la bémol mineur", "emoll": "mi mineur", "hmoll": "si mineur", "fismoll": "fa dièse mineur", "cismoll": "ut dièse mineur", "gismoll": "sol dièse mineur", "dismoll": "ré dièse mineur", "aismoll": "la dièse mineur" } BESETZUNG_FR = {**BESETZUNG_DE} # Simplified # Mapping tables by language GENRE_MAP = {'DE': GENRE_DE, 'EN': GENRE_EN, 'FR': GENRE_FR} TONART_MAP = {'DE': TONART_DE, 'EN': TONART_EN, 'FR': TONART_FR} BESETZUNG_MAP = {'DE': BESETZUNG_DE, 'EN': BESETZUNG_EN, 'FR': BESETZUNG_FR} def parse_sql_dump(sql_file): """Parse SQL dump and extract work data""" print(f"Parsing {sql_file}...") with open(sql_file, 'r', encoding='latin-1') as f: lines = f.readlines() all_works = [] in_insert = False current_insert_lines = [] for line in lines: if line.startswith('INSERT INTO `daten` VALUES'): in_insert = True current_insert_lines = [] continue elif in_insert: if line.strip() == '' or line.startswith('/*!') or line.startswith('--'): continue if line.rstrip().endswith(';'): # End of this INSERT block current_insert_lines.append(line.rstrip(';\n')) # Process this block all_works.extend(parse_insert_block('\n'.join(current_insert_lines))) in_insert = False current_insert_lines = [] print(f" Processed INSERT block: {len(all_works)} total works so far") else: current_insert_lines.append(line.rstrip('\n')) print(f"Parsed {len(all_works)} works") return all_works def parse_insert_block(full_text): """Parse a single INSERT block""" works = [] # Split by '),\n(' to get individual records records = re.split(r'\),\s*\n\(', full_text) for record in records: # Clean up the record record = record.strip('()') # Parse the CSV-like values (handling quoted strings with commas) values = parse_values(record) if len(values) >= 32: # Ensure we have all fields work = { 'Komponist': values[0], 'TitelDE': values[1], 'TitelEN': values[2], 'TitelFR': values[3], 'TitelIT': values[4], 'TitelLA': values[5], 'TitelPL': values[6], 'IncipitDE': values[7], 'IncipitEN': values[8], 'IncipitFR': values[9], 'IncipitIT': values[10], 'IncipitLA': values[11], 'IncipitPL': values[12], 'Genre': values[13], 'Besetzung': values[14], 'Tonart': values[15], 'Jahr': values[16], 'WerkNr': values[17], 'BemerkungDE': values[18], 'BemerkungEN': values[19], 'BemerkungFR': values[20], 'sorWerkNr': values[32] if len(values) > 32 else 0 } works.append(work) return works def parse_values(record): """Parse SQL VALUES - handle quoted strings with commas/quotes""" values = [] current = [] in_quote = False i = 0 while i < len(record): char = record[i] if char == "'" and (i == 0 or record[i-1] != '\\'): in_quote = not in_quote i += 1 continue if char == ',' and not in_quote: val = ''.join(current).strip() # Remove quotes if present if val.startswith("'") and val.endswith("'"): val = val[1:-1] values.append(val) current = [] i += 1 # Skip whitespace after comma while i < len(record) and record[i] in ' \t': i += 1 continue # Handle escaped quotes if char == '\\' and i + 1 < len(record) and record[i+1] == "'": current.append("'") i += 2 continue current.append(char) i += 1 # Add last value if current: val = ''.join(current).strip() # Remove quotes if present if val.startswith("'") and val.endswith("'"): val = val[1:-1] values.append(val) return values def decode_genre(code, lang='DE'): """Decode genre code to text""" return GENRE_MAP[lang].get(code, code) def decode_tonart(code, lang='DE'): """Decode tonart (key) code to text""" return TONART_MAP[lang].get(code, code) def decode_besetzung(code, lang='DE'): """Decode besetzung (instrumentation) codes""" if not code: return '' instruments = [] for key, value in BESETZUNG_MAP[lang].items(): if key in code: instruments.append(value) return ', '.join(instruments) if instruments else code def generate_work_html(work, lang='DE'): """Generate HTML for a single work entry""" title_field = f'Titel{lang}' incipit_field = f'Incipit{lang}' bemerkung_field = f'Bemerkung{lang}' title = escape(work.get(title_field, '')) incipit = escape(work.get(incipit_field, '')) werknr = escape(work.get('WerkNr', '')) jahr = escape(work.get('Jahr', '')) genre = escape(decode_genre(work.get('Genre', ''), lang)) tonart = escape(decode_tonart(work.get('Tonart', ''), lang)) besetzung = escape(decode_besetzung(work.get('Besetzung', ''), lang)) bemerkung = escape(work.get(bemerkung_field, '')) html = '
{composer_display} - Werkverzeichnis{len(works)} {'Werke' if lang == 'DE' else 'Works' if lang == 'EN' else 'Œuvres'} |
| {labels['start']} | {labels['intro']} | {labels['bio']} | {labels['katalog']} | {labels['form']} | {labels['klav']} | {labels['quellen']} | {labels['impressum']} |