import os import json import datetime import pandas as pd import ezodf from openpyxl import Workbook from openpyxl.utils import get_column_letter from openpyxl.styles import Alignment import logging logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") # ---------------- SPOT-Baumstruktur ---------------- class Node: def __init__(self, name, node_type="category", id=None): self.name = name self.id = id self.type = node_type # "category", "subcategory", "word" self.children = [] def add_child(self, child): self.children.append(child) def to_dict(self): if self.type == "word": return self.name return { "id": self.id, "name": self.name, "type": self.type, "children": [c.to_dict() for c in self.children] } @staticmethod def from_dict(d): if isinstance(d, str): return Node(d, "word") node = Node(d["name"], d.get("type", "category"), d.get("id")) node.children = [Node.from_dict(c) for c in d.get("children", [])] return node # ---------------- Funktionen zum Laden ---------------- def load_excel_or_ods(input_file, master_sheet="Masterstruktur"): ext = os.path.splitext(input_file)[1].lower() engine = "openpyxl" if ext in [".xlsx", ".xls"] else "odf" xls = pd.ExcelFile(input_file, engine=engine) sheet_names = [s for s in xls.sheet_names if s != master_sheet] dfs = {s: pd.read_excel(xls, sheet_name=s, engine=engine) for s in sheet_names} master_df = pd.read_excel(xls, sheet_name=master_sheet, engine=engine) return master_df, dfs # ---------------- Baum aus Sheet erstellen ---------------- def process_sheet_to_tree(df): df = df.fillna("").astype(str) tree_nodes = [] current_cat = None current_sub = None for idx, row in df.iterrows(): id_val = row.get("ID", "").strip() uk_val = row.get("Unterkategorie", "").strip() uuk_val = row.get("Unterunterkategorie", "").strip() word_val = row.get("Wort/Vokabel", "").strip() if id_val: current_cat = Node(uk_val or word_val, "category", id=id_val) tree_nodes.append(current_cat) current_sub = None elif uuk_val: current_sub = Node(uuk_val, "subcategory") if current_cat: current_cat.add_child(current_sub) elif word_val: word_node = Node(word_val, "word") if current_sub: current_sub.add_child(word_node) elif current_cat: current_cat.add_child(word_node) return tree_nodes # ---------------- SPOT laden/speichern ---------------- def save_spot_json(tree_nodes, file_path): with open(file_path, "w", encoding="utf-8") as f: json.dump([n.to_dict() for n in tree_nodes], f, indent=2, ensure_ascii=False) logging.info(f"SPOT gespeichert: {file_path}") def load_spot_json(file_path): with open(file_path, "r", encoding="utf-8") as f: data = json.load(f) return [Node.from_dict(n) for n in data] # ---------------- Export in Excel ---------------- def export_spot_to_excel(tree_nodes, output_file): wb = Workbook() wb.remove(wb.active) for node in tree_nodes: ws = wb.create_sheet(title=node.name[:31]) row_idx = 1 # Kategorie ws.cell(row=row_idx, column=1, value=node.id) ws.cell(row=row_idx, column=2, value=node.name) row_idx += 1 for sub in node.children: if sub.type == "subcategory": ws.cell(row=row_idx, column=3, value=sub.name) row_idx += 1 for word in sub.children: ws.cell(row=row_idx, column=4, value=word.name) row_idx += 1 elif sub.type == "word": ws.cell(row=row_idx, column=4, value=sub.name) row_idx += 1 # Spaltenbreiten anpassen for col_idx, col_letter in enumerate(["A","B","C","D"],1): ws.column_dimensions[col_letter].width = 20 for r in range(1,row_idx): ws.cell(r,col_idx).alignment = Alignment(horizontal='left') wb.save(output_file) logging.info(f"Excel exportiert: {output_file}") # ---------------- Export in ODS ---------------- def export_spot_to_ods(tree_nodes, output_file): doc = ezodf.newdoc(doctype="ods", filename=output_file) for node in tree_nodes: sheet = ezodf.Sheet(node.name[:31], size=(len(node.children)+10,4)) doc.sheets += sheet sheet[0,0].set_value("ID") sheet[0,1].set_value("Unterkategorie") sheet[0,2].set_value("Unterunterkategorie") sheet[0,3].set_value("Wort/Vokabel") row_idx = 1 sheet[row_idx,0].set_value(node.id) sheet[row_idx,1].set_value(node.name) row_idx +=1 for sub in node.children: if sub.type == "subcategory": sheet[row_idx,2].set_value(sub.name) row_idx +=1 for word in sub.children: sheet[row_idx,3].set_value(word.name) row_idx +=1 elif sub.type == "word": sheet[row_idx,3].set_value(sub.name) row_idx +=1 doc.save() logging.info(f"ODS exportiert: {output_file}") # ---------------- CLI-Funktionen zum Editieren ---------------- def add_category(tree_nodes, cat_id, cat_name): tree_nodes.append(Node(cat_name, "category", id=cat_id)) logging.info(f"Kategorie hinzugefügt: {cat_id} {cat_name}") def add_subcategory(tree_nodes, cat_id, sub_name): for cat in tree_nodes: if cat.id == cat_id: cat.add_child(Node(sub_name, "subcategory")) logging.info(f"Unterkategorie hinzugefügt: {sub_name} in {cat_id}") return def add_word(tree_nodes, cat_id, sub_name, word_name): for cat in tree_nodes: if cat.id == cat_id: for sub in cat.children: if sub.name == sub_name: sub.add_child(Node(word_name, "word")) logging.info(f"Wort hinzugefügt: {word_name} unter {sub_name}") return # ---------------- HAUPTPROGRAMM ---------------- def main(): INPUT_FILE = "NV_MASTER.ods" # Beispielpfad OUTPUT_SPOT = "nv_spot.json" today = datetime.datetime.today().strftime("%y.%m.%d") OUTPUT_EXCEL = f"NV_MASTER_SPOT_{today}.xlsx" OUTPUT_ODS = f"NV_MASTER_SPOT_{today}.ods" master_df, dfs = load_excel_or_ods(INPUT_FILE) spot_tree = [] for sheet, df in dfs.items(): spot_tree.extend(process_sheet_to_tree(df)) save_spot_json(spot_tree, OUTPUT_SPOT) # Beispiel: Editieren # add_category(spot_tree, "10.1", "Neue Kategorie") # add_subcategory(spot_tree, "10.1", "Neue Unterunterkategorie") # add_word(spot_tree, "10.1", "Neue Unterunterkategorie", "Neues Wort") export_spot_to_excel(spot_tree, OUTPUT_EXCEL) export_spot_to_ods(spot_tree, OUTPUT_ODS) logging.info("SPOT-Workflow abgeschlossen.") if __name__ == "__main__": main()