@schweito: Eingerückt wird per Konvention vier Leerzeichen pro Ebene.
Üblich ist eine `import`-Anweisung pro Modul.
`Inches` wird importiert, aber nirgends verwendet.
Namen sollten keine kryptischen Abkürzungen enthalten, oder gar nur aus solchen bestehen. Wenn man `itendifier` meint, sollte man nicht nur `ident` schreiben und wenn bei `description` nicht nur `desc`. Warum haben die Listen einen Präfix der eine übliche Abkürzung für WordPress ist? Steht das für `work_package`? So etwas sollte man nicht raten müssen.
Nummerieren von Namen ist ebenfalls keine gute Idee. Da will man sich entweder sinnvollere Namen überlegen, oder gar keine einzelnen Werte sondern eine Datenstruktur verwenden. Oft eine Liste.
Warum fängt die Nummerierung überhaupt bei 2 an? Der Punkt im Programmablauf an dem `t2` in jedem Schleifendurchlauf bestimmt wird, ist auch unsinnig, denn es wird ja nur der allerletzte Wert verwendet. Das kann man dann auch *einmal* *nach* der Schleife ermitteln. Das gleiche gilt für `t3`.
`time.time()` ist nur bedingt geeignet für Zeitmessungen. `time.monotonic()` oder `time.perf_counter()` sind dafür geeignet/vorgesehen.
Namen sollten erst definiert werden wenn sie verwendet werden, und nicht am Anfang mal ”auf Vorrat”. Also `wp_ident` und `wp_desc` erst vor der Schleife in der die Daten in diese Listen eingetragen werden. Die Werte von `ident` und `desc` die am Anfang definiert werden, werden nirgends verwendet. Warum werden die also überhaupt definiert an der Stelle?
Das Zieldokument wird erstellt, bevor die Daten aus der Quelle extrahiert wurden. Das würde ich auch nicht machen. Es ist übersichtlicher die Arbeitsschritte tatsächlich nacheinander zu machen und nicht zu mischen. Dann liesse sich das auch einfacher in einzelne Funktionen aufteilen, wenn der Code zu umwangreich wird, oder man einzelne Schritte separat testbar machen möchte.
Die beiden Listen enthalten offensichtlich Daten, die elementweise zusammengehören. Die sollten also gar nicht erst in zwei verschiedenen Listen landen, sondern in *einer*. Zum Beispiel eine Liste mit Tupeln, die jeweils die beiden zusammengehörenden Informationen enthalten.
`work_package` ist kein guter Name für einen regulären Ausdruck, denn der repräsentiert ja nicht das Arbeitspaket selbst. *Dafür* wird der Name im *gleichen* Namensraum, dann ja auch noch mal verwendet. Namen sollten nicht im gleichen Kontext für verschiedene Dinge verwendet werden, das ist verwirrend.
`match.groups()` liefert ein Tupel welches im Programm in eine Zeichenkettendarstellung umgewandelt wird. Das ist falsch. Man operiert nicht mit Zeichenkettenoperationen auf der Darstellung eines Tupels herum. Warum wird dort nicht einfach das eine Element aus dem Tupel heraus geholt? Oder besser: warum ist der reguläre Ausdruck nicht gleich mit zwei Gruppen formuliert, so dass man das Ergebnis nicht an einem Leerzeichen trennen muss, um an die beiden Teilinformationen zu kommen?
Das ist zudem ineffizient geschrieben, die exakt gleiche `split()`-Operation zweimal zu machen, um einmal an den Teil vor dem Leerzeichen und dann an den Teil nach dem Leerzeichen zu kommen. Üblicherweise würde man hier auch `partition()` statt `split()` verwenden.
Der reguläre Ausdruck ist ein bisschen merkwürdig. Dir ist klar, dass der am Anfang nicht nur "AP" erlaubt, sondern auch "AA", "PP", und "PA"? Ist das echt so gewollt, oder soll da nur "AP" für "Arbeitspaket" erkannt werden‽
Dann sind zwischen den Zifferngruppen auch mehrere aufeinanderfolgende Punkte erlaubt. Also "AA1..." oder "PP2..3...." würden auch erkannt. Es ist auch ein bisschen ”unsymmetrisch”/inkonsistent, dass "AP1." und "AP2.3." erkannt werden, also erlaubt sind, "AP1" ohne abschliessenden Punkt nicht, "AP2.3" ohne abschliessenden Punkt dann aber wieder doch.
Beim erstellen der zweiten Tabelle sind `item` und `desc` bereits Zeichenketten. Es macht keinen Sinn da noch einmal `str()` mit aufzurufen.
Das zusammenstückeln von Zeichenketten und Werten mittels ``+`` und `str()` ist eher BASIC als Python. Dafür gibt es die `format()`-Methode auf Zeichenketten und f-Zeichenkettenliterale.
`os.system()` sollte man nicht mehr verwenden. Die Dokumentation dort verweist auf das `subprocess`-Modul.
Es werden zum Speichern und zum starten der Textverarbeitung zwei unterschiedliche Dateinamen verwendet. Also einmal mit und einmal ohne den Pfad. Das kann funktionieren, muss es aber nicht.
Zwischenstand (ungetestet):
Code: Alles auswählen
#!/usr/bin/env python3
import re
import subprocess
import time
import docx
import docx2txt
def main():
text = docx2txt.process("C:/Users/korre/Documents/Tabelle.docx")
work_packages = [
match.groups()
for match in re.finditer(
r"^(AP\d+(?:\.\d+)*) (.*)", text, re.MULTILINE
)
]
parse_end_timestamp = time.perf_counter()
document = docx.Document()
document.add_heading("Balkenplan", 1)
for cell, heading in zip(
document.add_table(rows=1, cols=5, style="Table Grid").rows[0].cells,
["Arbeitspakete", "Beschreibung", "1. Jahr", "2. Jahr", "3. Jahr"],
):
cell.text = heading
print("Processing...\n")
print("[1] Parsing input file and creating table...")
table = document.add_table(rows=0, cols=14, style="Table Grid")
for work_package in work_packages:
for cell, content in zip(table.add_row().cells, work_package):
cell.text = content
rows_created_timestamp = time.perf_counter()
print("[2] Adjusting cell widths...\n")
completed_timestamp = time.perf_counter()
print("Summary:")
print(
f"[1] Parsing and creating rows ({len(work_packages)})"
f" elapsed time: {rows_created_timestamp - parse_end_timestamp:.2f}s"
)
print(
f"[2] Adjusting Geometry and Spacing:"
f" elapsed time: {completed_timestamp - rows_created_timestamp:.2f}s)"
)
print(f"total: {completed_timestamp - parse_end_timestamp:.2f}s)")
filename = "C:/Users/korre/Documents/test.docx"
document.save(filename)
subprocess.run(["start", filename], check=True)
if __name__ == "__main__":
main()
Zum Tabellenproblem: Da würde man *eine* Tabelle für erstellen, mit 14 Spalten und bei den letzten drei Überschriften jeweils vier Zellen zusammenfassen. Also so wie man das manuell auch machen würde.