#!/usr/bin/env python3
import sys
import xml.etree.ElementTree as ET
import re
import copy
from xml.sax.saxutils import escape
from Kabelliste_csv import generate_kabelliste
import io
import os

# Grafik-/Stil-Konstanten
PEN_SHAPE = {'widthF': '1', 'style': 'SolidLine', 'color': '#000000'}
BRUSH_NO = {'style': 'NoBrush', 'color': '#000000'}
BRUSH_HEADER_GRAY = {'style': 'SolidPattern', 'color': '#c0c0c0'}
# FONT_ROW bleibt ein Attribut (wird zusätzlich durch HTML gesteuert)
FONT_ROW = "Noto Sans,8,-1,5,50,0,0,0,0,0"
FONT_HEADER_SANS = "Sans Serif,8,-1,0,50,0,0,0,0,0"
FONT_HEADER_NOTO = "Noto Sans,9,-1,5,50,0,0,0,0,0"

# Kopfbereich / Tabellen-Layout (angepasst)
data_first_y1 = 69.9139
row_height = 20.0861  # 90 - 69.9139
max_rows_per_page = 33

# Tabellenspalten (Koordinaten der Shapes; Reihenfolge von links nach rechts)
TABLE_CELLS = [
    (20, 55),     # 0: fortlaufende Nummer
    (55, 105),    # 1: Seite
    (105, 195),   # 2: Kabelnummer
    (195, 320),   # 3: Kabeltyp (angepasst)
    (320, 405),   # 4: Startpunkt (angepasst)
    (405, 490),   # 5: Zielpunkt (angepasst)
    (490, 735),   # 6: Funktion (angepasst)
    (735, 805),   # 7: empty
    (805, 865),   # 8: empty
    (865, 950),   # 9: empty
    (950, 1090),  # 10: empty
]

# Vordefinierte X-Positionen (aus deinem Beispiel) für die ersten 7 Zellen (genaue x aus deinem snippet)
CELL_TEXT_POS_X = [20, 55, 105, 195, 320, 405, 490]

# Hilfsfunktionen
def remove_namespaces(xml_string):
    """
    Robustes Entfernen von XML-Namespaces:
    - Versucht zuerst ElementTree.iterparse zu verwenden und entfernt
      Namespaces aus Tags und Attributnamen sauber (sicherer).
    - Falls iterparse fehlschlägt, fällt ein vorsichtiger Regex-Fallback
      zurück, der Präfixe vor Tags und Attributen entfernt.
    """
    try:
        it = ET.iterparse(io.StringIO(xml_string))
        for _, el in it:
            # Tag: "{ns}tagname" -> "tagname"
            if isinstance(el.tag, str) and '}' in el.tag:
                el.tag = el.tag.split('}', 1)[1]
            # Attribute: "{ns}attr" -> "attr" oder "prefix:attr" -> "attr"
            for attr in list(el.attrib):
                # falls Attr-Name im "{ns}attr" Format
                if '}' in attr:
                    new_attr = attr.split('}', 1)[1]
                    el.attrib[new_attr] = el.attrib.pop(attr)
                # falls Attr-Name mit "prefix:name"
                elif ':' in attr:
                    new_attr = attr.split(':', 1)[1]
                    el.attrib[new_attr] = el.attrib.pop(attr)
        root = it.root
        return ET.tostring(root, encoding='unicode')
    except Exception:
        # Fallback: sichere, gezielte Regex-Bereinigung
        s = re.sub(r'\sxmlns(:\w+)?="[^"]+"', "", xml_string)   # entferne xmlns-Decls
        # entferne Präfix vor Tags (z. B. <svg:tag -> <tag)
        s = re.sub(r'(<\/?)[A-Za-z0-9_-]+:', r'\1', s)
        # entferne Präfix vor Attributnamen (z. B. sodipodi:docname="..." -> docname="...")
        s = re.sub(r'([ \t\r\n])[A-Za-z0-9_-]+:', r'\1', s)
        return s

def find_max_order(root):
    max_order = 0
    for diag in root.findall('.//diagram'):
        try:
            o = int(diag.get('order', '0'))
            max_order = max(max_order, o)
        except:
            pass
    return max_order

def find_property_value(root, name):
    if root is None:
        return ''
    for prop in root.findall('.//property'):
        if (prop.get('name') and prop.get('name').lower() == name.lower()) or (prop.get('name') == name):
            val = prop.get('value')
            if val is None:
                val = (prop.text or '').strip()
            return val or ''
    for k, v in root.attrib.items():
        if k.lower() == name.lower():
            return v
    return ''

def clone_template_diagram(root, new_order):
    template = root.find('.//diagram')
    if template is None:
        raise ValueError("Keine <diagram>-Vorlage in der .qet Datei gefunden.")
    attrs = {k: template.get(k, '') for k in [
        'cols','colsize','rows','rowsize','width','height',
        'date','titleblocktemplate','displayAt','auto_page_num',
        'author','folio','locmach','version'
    ]}
    # Setze gewünschte Standardwerte / Überschreibungen
    attrs['displaycols'] = 'false'
    attrs['displayrows'] = 'false'
    attrs['auto_page_num'] = template.get('auto_page_num', attrs.get('auto_page_num', ''))
    attrs['order'] = str(new_order)
    attrs['title'] = 'Kabelliste'
    # Neu erzeugen
    new_diag = ET.Element('diagram', attrs)
    props = template.find('properties')
    if props is not None:
        new_diag.append(copy.deepcopy(props))
    # Folio / Folienname auf "Listen" setzen (wie gewünscht)
    new_diag.set('folio', 'Listen')
    ET.SubElement(new_diag, 'inputs')
    ET.SubElement(new_diag, 'shapes')
    return new_diag

def make_cell_html(text):
    # Standard HTML für zentrierte Noto Sans Darstellung (span size 6pt für Tabellenzellen)
    return (
        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">'
        '<html><head><meta name="qrichtext" content="1" /><style type="text/css">'
        'p, li { white-space: pre-wrap; }'
        '</style></head>'
        '<body style=" font-family:\'Noto Sans\'; font-size:8pt; font-weight:400; font-style:normal;">'
        f'<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; text-align:center;">'
        f'<span style=" font-size:6pt;">{escape(str(text))}</span></p></body></html>'
    )

# Neues Hilfs-Template: Noto Sans, aber span 10pt für Kopf-Infos (plant & Seite number)
def make_header_10_html(text):
    return (
        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">'
        '<html><head><meta name="qrichtext" content="1" /><style type="text/css">'
        'p, li { white-space: pre-wrap; }'
        '</style></head>'
        '<body style=" font-family:\'Noto Sans\'; font-size:10pt; font-weight:400; font-style:normal;">'
        f'<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">'
        f'<span style=" font-size:10pt;">{escape(str(text))}</span></p></body></html>'
    )

# ------------------------------------------------------------------------
# Extraktion der elementInformation location-Einträge (wieder eingefügt)
def extract_location_entries(root):
    """
    Sammelt alle <elementInformation name="location"> nodes mit Text != '+'.
    Liefert Liste von dicts mit Feldern:
      { 'val': <location_text>, 'blob': <concat aller relevanten Texte/attribs>, 'parent_tag': <parent.tag> }
    """
    entries = []
    if root is None:
        return entries

    # parent map, damit wir parent eines Elements schnell finden
    parent_map = {c: p for p in root.iter() for c in p}

    for elem in root.findall('.//elementInformation'):
        name_attr = (elem.get('name') or '').strip()
        if name_attr.lower() != 'location':
            continue
        val = (elem.text or '').strip()
        # ignore single '+' or empty
        if val == '' or val == '+':
            continue

        parent = parent_map.get(elem)
        blob_parts = []
        if parent is not None:
            # parent tag + attributes
            blob_parts.append(parent.tag or '')
            for k, v in parent.attrib.items():
                if v:
                    blob_parts.append(str(v))
            # texts of parent and descendants (exclude elementInformation own text)
            for sub in parent.iter():
                if sub is elem:
                    continue
                if sub.text and sub.text.strip():
                    blob_parts.append(sub.text.strip())
                for k, v in sub.attrib.items():
                    if v:
                        blob_parts.append(str(v))
        else:
            blob_parts.append(val)
            for k, v in elem.attrib.items():
                if v:
                    blob_parts.append(str(v))

        search_blob = ' '.join([p for p in blob_parts if p is not None and str(p).strip() != ''])
        entries.append({'val': val, 'blob': search_blob, 'parent_tag': parent.tag if parent is not None else ''})

    return entries
# ------------------------------------------------------------------------

def draw_header(diag, plant_value, projekt_value, page_index):
    shapes = diag.find('shapes')
    inputs = diag.find('inputs')

    # 1) Großes oberes Rechteck (oben)
    big_shape = ET.SubElement(shapes, 'shape', {
        'type':'Rectangle','x1':'20','y1':'15','x2':'1090','y2':'40',
        'closed':'0','is_movable':'1','z':'0','rx':'0','ry':'0'
    })
    ET.SubElement(big_shape, 'pen', PEN_SHAPE)
    ET.SubElement(big_shape, 'brush', BRUSH_NO)  # NoBrush color #000000 wie gewünscht

    # 2) Inputs im oberen Bereich (dynamische & statische)
    # "Kabelliste:" (Klartext) - bereits 10pt
    html_kabelliste = (
        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" '
        '"http://www.w3.org/TR/REC-html40/strict.dtd">'
        '<html><head><meta name="qrichtext" content="1" />'
        '<style type="text/css">p, li { white-space: pre-wrap; }</style>'
        '</head>'
        '<body style=" font-family:\'Sans Serif\'; font-size:10pt; font-weight:400; font-style:normal;">'
        '<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">'
        '<span style=" font-size:10pt;">Kabelliste:</span></p></body></html>'
    )
    ET.SubElement(inputs, 'input', {'font': FONT_HEADER_SANS, 'rotation':'0', 'x':'25', 'y':'15', 'text':html_kabelliste})

    # Plant (aus Diagramm einsetzen) — 10pt
    html_plant_10 = make_header_10_html(plant_value or '')
    ET.SubElement(inputs, 'input', {'font': FONT_HEADER_SANS, 'rotation':'0', 'x':'95', 'y':'15', 'text':html_plant_10})

    # Projekt (property name="projekt") — 10pt
    html_projekt = (
        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" '
        '"http://www.w3.org/TR/REC-html40/strict.dtd">'
        '<html><head><meta name="qrichtext" content="1" />'
        '<style type="text/css">p, li { white-space: pre-wrap; }</style>'
        '</head>'
        '<body style=" font-family:\'Sans Serif\'; font-size:10pt; font-weight:400; font-style:normal;">'
        f'<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">'
        f'<span style=" font-size:10pt;">{escape(projekt_value or "")}</span></p></body></html>'
    )
    ET.SubElement(inputs, 'input', {'font': FONT_HEADER_SANS, 'rotation':'0', 'x':'480', 'y':'15', 'text':html_projekt})

    # "Seite:" Label oben rechts (10pt)
    html_seite_label = (
        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" '
        '"http://www.w3.org/TR/REC-html40/strict.dtd">'
        '<html><head><meta name="qrichtext" content="1" />'
        '<style type="text/css">p, li { white-space: pre-wrap; }</style>'
        '</head>'
        '<body style=" font-family:\'Sans Serif\'; font-size:10pt; font-weight:400; font-style:normal;">'
        '<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">'
        '<span style=" font-size:10pt;">Blatt.:</span></p></body></html>'
    )
    ET.SubElement(inputs, 'input', {'font': FONT_HEADER_SANS, 'rotation':'0', 'x':'1015', 'y':'15', 'text':html_seite_label})

    # Laufende Seitenzahl der erzeugten Kabelliste (10pt, nach rechts versetzt)
    html_seite_number_10 = make_header_10_html(str(page_index))
    ET.SubElement(inputs, 'input', {'font': FONT_HEADER_SANS, 'rotation':'0', 'x':'1055', 'y':'15', 'text':html_seite_number_10})

    # Zusätzliche Header Inputs (genaue Werte wie von dir vorgegeben)
    statics = [
        ("Checkliste", FONT_HEADER_NOTO, 855, 35),
        ("Feldanschluss", FONT_HEADER_NOTO, 865, 50),
        ("Verlegt", FONT_HEADER_NOTO, 810, 50),
        ("Kabelverlegung", FONT_HEADER_NOTO, 360, 35),
        ("Kabellänge", FONT_HEADER_NOTO, 735, 50),
        ("Nr.", FONT_HEADER_NOTO, 25, 45),
        ("Seite", FONT_HEADER_NOTO, 60, 45),
        ("Kabelnummer", FONT_HEADER_NOTO, 105, 45),
        ("Startpunkt", FONT_HEADER_NOTO, 335, 50),
        ("Zielpunkt", FONT_HEADER_NOTO, 420, 50),          # angepasst auf 420
        ("Kabeltyp", FONT_HEADER_NOTO, 235, 45),
        ("Funktion", FONT_HEADER_NOTO, 590, 45),           # angepasst auf 590
        ("Schaltschrankanschluss", FONT_HEADER_NOTO, 950, 50),
    ]
    for text, font, x, y in statics:
        html = (
            '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" '
            '"http://www.w3.org/TR/REC-html40/strict.dtd">'
            '<html><head><meta name="qrichtext" content="1" />'
            '<style type="text/css">p, li { white-space: pre-wrap; }</style>'
            '</head>'
            f'<body style=" font-family:\'Noto Sans\'; font-size:9pt; font-weight:400; font-style:normal;">'
            f'<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{escape(text)}</p></body></html>'
        )
        ET.SubElement(inputs, 'input', {'font': FONT_HEADER_NOTO, 'rotation':'0', 'x':str(x), 'y':str(y), 'text':html})

    # Graue Kästchen (SolidPattern #c0c0c0) - genaue Koordinaten wie gewünscht
    grey_boxes = [
        (20, 40, 55, 69.9019),
        (55, 40, 105, 69.9019),
        (105, 40, 195, 69.9019),
        (195, 40, 320, 69.9019),   # angepasst
        (320, 40, 490, 54.9019),   # angepasst (y2 54.9019 wie dein snippet)
        (490, 40, 735, 69.9019),   # angepasst
        (735, 55, 805, 69.9019),
        (805, 55, 865, 69.9019),
        (865, 55, 950, 69.9019),
        (950, 55, 1090, 69.9019),
        (735, 40, 1090, 54.9019),
        (320, 55, 405, 69.9019),   # angepasst (320..405)
        (405, 55, 490, 69.9019),   # angepasst (405..490)
    ]
    for x1, y1, x2, y2 in grey_boxes:
        s = ET.SubElement(shapes, 'shape', {
            'type':'Rectangle','x1':str(x1),'y1':str(y1),
            'x2':str(x2),'y2':str(y2),
            'closed':'0','is_movable':'1','z':'0','rx':'0','ry':'0'
        })
        ET.SubElement(s, 'pen', PEN_SHAPE)
        ET.SubElement(s, 'brush', BRUSH_HEADER_GRAY)

def draw_data_rows(diag, rows, page_index, global_counter, location_entries):
    """
    Zeichnet Datenzeilen. location_entries ist die Liste aus extract_location_entries(root).
    Differenziert zwischen location_for_kabel (präfix für Kabelnummer) und
    location_for_point (präfix für Ziel/Startpunkt).
    """
    shapes = diag.find('shapes')
    inputs = diag.find('inputs')

    # Schlüsselwortlisten für Fallback-Heuristiken
    cable_keywords = ['conductor','conductorname','cable','kabel','leitung','wire','leiter']
    point_keywords = ['terminal','anschluss','device','component','element','bauteil','slot','position','panel','schrank','anschlussbezeichnung']

    for idx, row in enumerate(rows):
        row = list(row)
        if len(row) < 6:
            row += [''] * (6 - len(row))

        # Aus generate_kabelliste erwartet: [Seite, Kabelnummer, Startpunkt, Zielpunkt, Kabeltyp, Funktion]
        page_val_from_row = row[0] if len(row) > 0 else ''
        kabelnummer = str(row[1]) if len(row) > 1 else ''
        startpunkt = str(row[2]) if len(row) > 2 else ''
        zielpunkt = str(row[3]) if len(row) > 3 else ''
        kabeltyp = str(row[4]) if len(row) > 4 else ''
        funktion = str(row[5]) if len(row) > 5 else ''

        # 1) Direkter location-Wert aus der Row (generate_kabelliste) -> gilt nur für Kabel
        direct_location = ''
        for candidate in row:
            if isinstance(candidate, str) and '+' in candidate and candidate.strip() != '+':
                direct_location = candidate.strip()
                break

        # Initialisiere differenzierte locations
        location_for_kabel = ''
        location_for_point = ''

        # Wenn generate_kabelliste schon einen location liefert, nehme ihn für das Kabel
        if direct_location:
            location_for_kabel = direct_location

        # 2) Heuristische Suche in location_entries (nur wenn noch nichts direkt gesetzt)
        kn = kabelnummer.strip()
        if kn:
            for entry in location_entries:
                loc_val = entry['val']
                blob = entry.get('blob', '') or ''
                try:
                    # exakte token-Suche im blob -> zu Kabel zuordnen
                    if re.search(r'(^|[^A-Za-z0-9])' + re.escape(kn) + r'($|[^A-Za-z0-9])', blob, flags=re.IGNORECASE):
                        if not location_for_kabel:
                            location_for_kabel = loc_val
                        # don't break, maybe another entry is more specific for point
                except Exception:
                    pass

        # 3) Prüfung für Start/Zielpunkt: wenn deren Text als EXAKTES TOKEN im blob vorkommt, weise dem point-Loc zu
        # (streng, damit nicht Location der ersten Zeile auf alle übertragen wird.)
        if startpunkt:
            for entry in location_entries:
                blob = entry.get('blob', '') or ''
                try:
                    if re.search(r'(^|[^A-Za-z0-9])' + re.escape(startpunkt) + r'($|[^A-Za-z0-9])', blob, flags=re.IGNORECASE):
                        location_for_point = entry['val']
                        break
                except Exception:
                    continue

        if zielpunkt and not location_for_point:
            for entry in location_entries:
                blob = entry.get('blob', '') or ''
                try:
                    if re.search(r'(^|[^A-Za-z0-9])' + re.escape(zielpunkt) + r'($|[^A-Za-z0-9])', blob, flags=re.IGNORECASE):
                        location_for_point = entry['val']
                        break
                except Exception:
                    continue

        # 4) Fallbacks: falls noch nichts für kabel gefunden, suche nach Kabel-Schlüsselwörtern
        if not location_for_kabel:
            for entry in location_entries:
                blob_l = (entry.get('blob','') or '').lower()
                if any(k in blob_l for k in cable_keywords):
                    location_for_kabel = entry['val']
                    break

        # Hinweis: kein allgemeines Fallback für location_for_point, damit nicht die Location der ersten
        # Zeile ungewollt auf alle Zielpunkte übertragen wird.

        # Berechne y-Koordinaten
        y1 = data_first_y1 + idx * row_height
        y2 = y1 + row_height

        # Zeichne die Shapes für diese Zeile anhand TABLE_CELLS (NoBrush)
        for (x1, x2) in TABLE_CELLS:
            s = ET.SubElement(shapes, 'shape', {
                'type':'Rectangle','x1':str(x1),'y1':str(y1),
                'x2':str(x2),'y2':str(y2),
                'closed':'0','is_movable':'1','z':'0','rx':'0','ry':'0'
            })
            ET.SubElement(s, 'pen', PEN_SHAPE)
            ET.SubElement(s, 'brush', BRUSH_NO)

        # Werte für die Zellen zusammenstellen
        cell_values = [''] * len(TABLE_CELLS)
        cell_values[0] = str(global_counter)
        if page_val_from_row is not None and str(page_val_from_row).strip() != '':
            cell_values[1] = str(page_val_from_row)
        else:
            cell_values[1] = str(page_index)

        # Kabelnummer: prefix nur mit location_for_kabel (wenn vorhanden und nicht nur '+')
        if location_for_kabel and location_for_kabel.strip() and location_for_kabel.strip() != '+':
            prefix = location_for_kabel.strip()
            if not prefix.endswith('-') and not kabelnummer.startswith('-'):
                prefix = prefix + '-'
            cell_values[2] = f"{prefix}{kabelnummer}"
        else:
            cell_values[2] = kabelnummer

        cell_values[3] = kabeltyp
        cell_values[4] = startpunkt

        # Zielpunkt nur mit location_for_point (streng)
        if location_for_point and location_for_point.strip() and location_for_point.strip() != '+':
            prefix_p = location_for_point.strip()
            if not prefix_p.endswith('-') and not str(zielpunkt).startswith('-'):
                prefix_p = prefix_p + '-'
            cell_values[5] = f"{prefix_p}{zielpunkt}"
        else:
            cell_values[5] = zielpunkt

        cell_values[6] = funktion

        # Erzeuge Text-Inputs innerhalb der Kästchen (Positionierung aus CELL_TEXT_POS_X)
        for col_index, text_value in enumerate(cell_values):
            x1, x2 = TABLE_CELLS[col_index]
            if col_index < len(CELL_TEXT_POS_X):
                tx = CELL_TEXT_POS_X[col_index]
            else:
                tx = x1 + 5
            ty = round(data_first_y1 + idx * row_height)

            # Tabellen-HTML nutzt make_cell_html -> span 6pt
            html = make_cell_html(text_value)
            ET.SubElement(inputs, 'input', {
                'font': FONT_ROW, 'rotation':'0',
                'x':str(tx), 'y':str(ty), 'text':html
            })

        global_counter += 1

    return global_counter

def inject_kabelliste(input_qet, output_qet):
    # Lese Datei
    xml = open(input_qet, 'r', encoding='utf-8').read()
    # Entferne Namespaces (lokale Funktion oben)
    xml = remove_namespaces(xml)

    # Helper: nur stray & escapen
    def escape_stray_ampersands(s: str) -> str:
        return re.sub(r'&(?!amp;|lt;|gt;|quot;|apos;|#\d+;|#x[0-9A-Fa-f]+;)', '&amp;', s)

    # Versuche robustes Parsen
    try:
        root = ET.fromstring(xml)
    except ET.ParseError:
        # 1) & escapen
        xml2 = escape_stray_ampersands(xml)
        try:
            root = ET.fromstring(xml2)
            xml = xml2
        except ET.ParseError:
            # 2) Steuerzeichen entfernen und nochmal
            xml3 = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F]', '', xml2)
            try:
                root = ET.fromstring(xml3)
                xml = xml3
            except ET.ParseError as err:
                # Kontext ausgeben, damit wir die problematische Stelle sehen können
                print("XML parse failed after cleanups:", str(err), file=sys.stderr)
                try:
                    ln, col = err.position
                    lines = xml3.splitlines()
                    start = max(0, ln-4)
                    end = min(len(lines), ln+2)
                    context = "\n".join(f"{i+1:6d}: {lines[i]}" for i in range(start, end))
                    print(f"Error position: line {ln} col {col}\nContext:\n{context}", file=sys.stderr)
                except Exception:
                    print("No position available. First 2000 chars of cleaned XML:", file=sys.stderr)
                    print(xml3[:2000], file=sys.stderr)
                raise

    # --- ab hier ist root verfügbar und der alte Ablauf läuft weiter ---
    data_rows = generate_kabelliste(input_qet)
    total = len(data_rows)
    max_rows = max_rows_per_page
    pages = (total + max_rows - 1) // max_rows

    base_order = find_max_order(root)

    # Lese Plant aus dem ersten <diagram ... plant="...">, fallback auf property
    first_diag = root.find('.//diagram')
    plant_value = ''
    if first_diag is not None:
        plant_value = first_diag.get('plant', '') or first_diag.get('Plant', '')
    if not plant_value:
        plant_value = find_property_value(root, 'plant') or find_property_value(root, 'Plant')

    # Lese Projekt-Werte (falls vorhanden) — jetzt primär aus <project title="...">
    projekt_value = ''
    try:
        # Fall 1: root selbst ist <project> und hat title
        if isinstance(root, ET.Element) and root.tag.lower() == 'project' and root.get('title'):
            projekt_value = root.get('title').strip()
        else:
            # Fall 2: suche explizit nach einem <project>-Element irgendwo im Dokument
            proj_elem = root.find('.//project')
            if proj_elem is not None and proj_elem.get('title'):
                projekt_value = proj_elem.get('title').strip()
    except Exception:
        projekt_value = ''

    # Fallback: alte property-Suche (Kompatibilität)
    if not projekt_value:
        projekt_value = find_property_value(root, 'projekt') or find_property_value(root, 'Projekt')

    # Letzter Fallback: Dateiname ohne Endung
    if not projekt_value:
        try:
            projekt_value = os.path.splitext(os.path.basename(input_qet))[0]
        except Exception:
            projekt_value = ''


    # ---- NEW: extrahiere location entries einmal pro Datei ----
    location_entries = extract_location_entries(root)
    # -----------------------------------------------------------

    # Start der fortlaufenden Nummer über alle Seiten
    global_counter = 1

    for p in range(pages):
        chunk = data_rows[p*max_rows:(p+1)*max_rows]
        new_diag = clone_template_diagram(root, base_order + p + 1)

        # Setze plant-Attribut in die neu erzeugte Seite
        if plant_value:
            new_diag.set('plant', plant_value)

        # Kopf zeichnen (Plant & Projekt einsetzen)
        draw_header(new_diag, plant_value, projekt_value, p+1)

        # Tabellenzeilen & Inhalte zeichnen -> global_counter wird aktualisiert
        global_counter = draw_data_rows(new_diag, chunk, p+1, global_counter, location_entries)

        # Anhängen
        root.append(new_diag)

    # Schreibe Ergebnis
    ET.ElementTree(root).write(output_qet, encoding='utf-8', xml_declaration=True)
    print(f"Kabelliste mit {total} Einträgen auf {pages} Seiten erstellt: {output_qet}")


if __name__=='__main__':
    if len(sys.argv)!=3:
        print("Usage: python3 Kabelliste_qet.py <in.qet> <out.qet>")
        sys.exit(1)
    inject_kabelliste(sys.argv[1], sys.argv[2])
