Werkzeug zur Erkennung defekter Tags in ODT-Vorlagen

Hallo!

Ich migriere gerade alle ODT-Vorlagen aus meinem alten System hin zu Dolibarr und musste dazu natürlich auch die entsprechenden Tags zur automatischen Ersetzung einbauen. Das Problem war bei mir wie bei vielen auch, dass man in LibreOffice nicht sieht, ob ein Tag in der XML-Struktur zerfallen ist (und damit von Dolibarr nicht erkannt und ersetzt wird) oder in Ordnung ist. Da mich das ziemlich genervt hat, habe ich mir ein entsprechendes Tool geschrieben, das einfach die Vorlage einliest und schaut, welche Tags ok sind und welche nicht. So sind fehler sehr schnell behoben.

Das Ganze ist ein Python-Skript und wird sehr einfach in der Kommandozeile aufgerufen:

python odt-tag-analyzer.py odt-Vorlage.odt

Die Ausgabe ist dann so ähnlich wie hier:

=== Dolibarr ODT Tag Analyse ===

Datei: ET_Invoice_de.odt
Gefundene rohe Tags: 24
Geprüfte komplexe Tags: 24

---- POTENTIELL PROBLEMATISCHE TAGS ----

[1]
RAW:     {contact_fullname}
CLEANED: {contact_fullname}

[2]
RAW:     {contact_address}
CLEANED: {contact_address}

[3]
RAW:     {contact_zip}
CLEANED: {contact_zip}

[4]
RAW:     {contact_town}
CLEANED: {contact_town}

[5]
RAW:     {contact_country}
CLEANED: {contact_country}

[6]
RAW:     {mycompany_zip}
CLEANED: {mycompany_zip}

[7]
RAW:     {mycompany_town}
CLEANED: {mycompany_town}

[8]
RAW:     {mycompany_country}
CLEANED: {mycompany_country}

[9]
RAW:     {mycompany_phone}
CLEANED: {mycompany_phone}

[10]
RAW:     {mycompany_email}
CLEANED: {mycompany_email}

[11]
RAW:     {mycompany_web}
CLEANED: {mycompany_web}

[12]
RAW:     {object_ref}
CLEANED: {object_ref}

[13]
RAW:     {company_customercode}
CLEANED: {company_customercode}

[14]
RAW:     {object_date_</text:span><text:span text:style-name="T17">locale</text:span><text:span text:style-name="T13">}
CLEANED: {object_date_locale}

...

Bei Tag 14 sieht man hier beispielhaft, dass LibreOffice hier intern aus dem Tag zwei XML-Einträge gemacht hat, womit die Ersetzung natürlich nicht mehr funktioniert.

Mir hat das Tool beim Umzug sehr geholfen - wobei man das sicherlich noch deutlich verbessern könnte. Man könnte das Ganze eventuell auch als Makro in LibreOffice direkt laufen lassen.

Vielleicht hilft es ja dem einen oder anderen :slight_smile:

Viele Grüße,
Christoph

Oh, ich sehe gerade, dass er hier Probleme mit der Formatierung des Programmcodes hat.

Kann ich den irgendwie anhängen?

Edit: Ah, jetzt klappt es. Hier dier Code:

import zipfile
import re
import sys
from xml.etree import ElementTree as ET
from collections import defaultdict


class ODTTagAnalyzer:
    def __init__(self, path):
        self.path = path
        self.xml = None
        self.tags = []
        self.issues = []

    def extract_xml(self):
        with zipfile.ZipFile(self.path, 'r') as z:
            self.xml = z.read('content.xml').decode('utf-8', errors='ignore')

    def strip_xml_tags(self, text):
        # entfernt XML-Tags, lässt Inhalt stehen
        return re.sub(r'<[^>]+>', '', text)

    def extract_raw_tags(self):
        # einfache direkte Tags
        return re.findall(r'\{[^{}]+\}', self.xml)

    def extract_fragmented_tags(self):
        """
        erkennt zerlegte Tags wie:
        {<text:span>abc</text:span><text:span>def</text:span>}
        """
        pattern = re.compile(r'\{.*?\}', re.DOTALL)
        matches = pattern.findall(self.xml)

        cleaned = []
        for m in matches:
            inner = self.strip_xml_tags(m)
            cleaned.append((m, inner))
        return cleaned

    def analyze(self):
        self.extract_xml()

        raw = self.extract_raw_tags()
        fragmented = self.extract_fragmented_tags()

        # 1. normale Tags
        for t in raw:
            self.tags.append(t)

        # 2. fragmentierte Tags prüfen
        for original, cleaned in fragmented:
            issue = {
                "original": original,
                "cleaned": cleaned,
                "valid": self.is_valid_tag(cleaned)
            }
            self.issues.append(issue)

    def is_valid_tag(self, tag):
        # einfache heuristik
        if not tag:
            return False
        if ' ' in tag:
            return False
        if '<' in tag or '>' in tag:
            return False
        return True

    def report(self):
        print("\n=== Dolibarr ODT Tag Analyse ===\n")

        print(f"Datei: {self.path}")
        print(f"Gefundene rohe Tags: {len(self.tags)}")
        print(f"Geprüfte komplexe Tags: {len(self.issues)}\n")

        print("---- POTENTIELL PROBLEMATISCHE TAGS ----\n")

        for i, issue in enumerate(self.issues, 1):
            print(f"[{i}]")
            print(f"RAW:     {issue['original']}")
            print(f"CLEANED: {issue['cleaned']}\n")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Verwendung: python odt_tag_analyzer.py odt-template.odt")
        sys.exit(1)

    analyzer = ODTTagAnalyzer(sys.argv[1])
    analyzer.analyze()
    analyzer.report()
2 „Gefällt mir“