Skripte programmieren für LibreOffice – Teil 3

LibreOffice Writer – Makros in der Praxis

LibreOffice Writer – Makros in der Praxis

Skripte programmieren für LibreOffice – Teil 3

LibreOffice Writer – Makros in der Praxis


Makros können auf ein Writer-Dokument angewandt werden, indem auf Werkzeuge des Textmoduls zurückgegriffen wird. Ähnlich wie in LibreOffice Calc kommen in LibreOffice Writer Tabellen zum Einsatz. Obwohl sich damit einfache Berechnungen vornehmen lassen, liegt der Hauptfokus sowohl bei Textinhalten als auch Tabellen eindeutig auf komplexen Textformatierungen.

In einem Writer-Dokument wird Textinhalt hauptsächlich in Absätzen organisiert, wobei es bei LibreOffice Writer vorrangig darum geht, Text formatiert auszugeben. Teil 3 dieser Serie über Makros ist der LibreOffice-Anwendung Writer gewidmet. Neben simplen Aufgaben wie dem Einfügen von Text an die Position des Cursors werden fortgeschrittene Themen wie die Textformatierung und die Erstellung eines neuen Dokuments behandelt. Der in Teil 3 vorgestellte Code ist zu Python 3.12.6 kompatibel und läuft unter LibreOffice Fresh mit der Version 24.2.6.2.

Da im Internet nicht gerade viele Beispiele zur Makroprogrammierung in Python abrufbar sind, hat es sich beim Writer als Best Practice erwiesen, den Python-Code auf Basis eines bestehenden BASIC-Codes zu erstellen. So existieren auf der Wiki-Seite von PyUNO eine Reihe von Beispielcodes [1]. Neben dem viel zitierten E-Book von Andrew Pitonyak [2] hat StarOffice selbst ein Tutorial veröffentlicht [3].

Fallbeispiel: Application-Tool

Als Fallbeispiel dient ein Anwendungsfall aus der Personalabteilung. Konkret geht es darum, Bewerber:innen auf eine Stelle entweder eine Zu- oder Absage zu verschicken. Dabei werden zunächst die Daten des Bewerbers mit Hilfe eines in Python erstellten Formulars erfasst. Sobald der Personalleiter den Typ des Antwortschreibens festlegt, generiert das Application-Tool einen Brief, indem es die entsprechenden Textbausteine aus einer Textdatei einliest und diese entsprechend anordnet sowie formatiert ausgibt.

Obwohl als Ausgangspunkt des Python-Makros eine leere Vorlage mit der Endung .ott eingesetzt wird, kommen dennoch benutzerdefinierte oder überarbeitete Stile zum Einsatz, wobei die Einstellungen manuell erfolgen (Abb. 1).

minosi_libreoffice_1

Abb. 1: Die Vorlage besteht aus einer leeren Seite und enthält sowohl eigene als auch angepasste Stile

Um die Lesbarkeit des Codes zu gewährleisten, wird für das Makro eine eigene Python-Klasse mit dem Namen ApplicationModule erstellt (Listing 1). Unterhalb der Klassendefinition erscheinen die Klassenvariablen, auf die das Makro später zugreift. So werden für die Zu- und Absage boolesche Variablen benutzt. Generell ist der Brief in Abschnitte unterteilt, deren Namen durch Schlüssel beginnend mit key identifiziert werden. Speziell für die Iteration der Schlüssel sind diese zusammen mit den Stilbezeichnungen als Tupel in der Liste paragraphs abgelegt. Zusätzlich sind die im Verlauf des Makros veränderlichen Werte wie die Adresse des Bewerbers oder die Stellenbezeichnung als Klassenvariablen definiert. Damit sich die Fensterelemente mittels Iteration ins Formular einfügen lassen, werden die Bezeichnungen oder Überschriften durch Klassenvariablen definiert. Die Bezeichnungen sind zusammen mit der Typdefinition des jeweiligen Fensterelements, d. h. als Tupel in der Liste labels abgelegt.

Listing 1

import uno
from time import sleep
from datetime import datetime
import os
import signal
from tkinter import *
from tkcalendar import Calendar
from tktimepicker import SpinTimePickerOld
from tktimepicker import constants

# Klasse zum Erstellen des Antwortschreibens
class ApplicationModule:
  # Typ des Antwortschreibens
  accepted = None
  # Zusage
  accepted_v = 1
  # Absage
  rejected_v = 0
  # Platzhalter in den Textbausteinen
  var1 = "XXX"
  var2 = "YYY"
  vars = [var1,var2]
  # Platzhalter für den Brief
  key1 = "dear"
  key2 = "subject"
  key3 = "acceptance1"
  key4 = "acceptance2"
  key5 = "acceptance3"
  key6 = "acceptance4"
  key7 = "rejection"
  key8 = "company"
  key9 = "greeting"
  key10 = "date"
  key11 = "applicant"
  # Stile
  text_style = "Body Text"
  signature_style = "Signature"
  subject_style = "Heading 1"
  # Default-Werte des Briefs
  link = "https://careers.dupont.com/de/de"
  applicant_first = ""
  applicant_last = ""
  applicant_address = ""
  applicant_street = ""
  applicant_zip = ""
  applicant_country = ""
  applicant_town = ""
  applicant_addition_address = ""
  position = "Giftmischerin"
  beginning = datetime.now()
  hr_responsible = ""
  hr_contact = ""
  # Fenster-Elemente
  base = None
  contains_both = 1
  only_label = 0
  contains_radiobutton = 2
  contains_calendar = 3
  contains_time = 4
  width_lbl = 25
  y_delta = 50
  width = 600
  height = 1000
  field_pad = 5
  submit_btn = None
  btn_label = "Submit"
  btn2_label = "Quit"
  headline_1 = "Applicant"
  headline_2 = "Position"
  headline_3 = "Type of Letter"
  first_name_lbl = "First Name:"
  last_name_lbl = "Last Name:"
  address_addition_lbl = "Supplement Address:"
  street_lbl = "Street:"
  zip_lbl = "Zip Code:"
  town_lbl = "Town:"
  country_lbl = "Country:"
  position_lbl = "Position:"
  hr_contact_lbl = "Contact Person"
  hr_responsible_lbl = "Responsible from HR Team:"
  beginning_lbl = "1st Working Day"
  start_time_lbl = "Start of Work"
  # Variablen der Fenster-Komponenten
  cal = None
  time_picker = None
  iVars = None
  # Beschriftung + Variablen der Radiobuttons
  values = {"Acceptance": accepted_v,
    "Rejection": rejected_v}
  # Enthält die Textbausteine
  boilerplates = {}
  # Listen
  labels = [(headline_1,only_label),(first_name_lbl,contains_both),(last_name_lbl,contains_both),(address_addition_lbl,contains_both),(street_lbl,contains_both),(zip_lbl,contains_both),(town_lbl,contains_both),(country_lbl,contains_both), (headline_2,only_label), (position_lbl,contains_both),(hr_contact_lbl,contains_both),(hr_responsible_lbl,contains_both), (headline_3,contains_radiobutton),(beginning_lbl,contains_calendar),(start_time_lbl,contains_time)]
  # enthält eine Liste von Tupeln: Label + Eingabefeld
  entries = []
  # Absätze des Briefs definieren
  paragraphs_part1 = [(key8,5,None),(key11,2,None),(key10,3,None),(key2,2,subject_style),(key1,2,text_style)]
  paragraphs_accepted = [(key3,0,text_style),(key4,0,text_style),(key5,0,text_style),(key6,3,text_style)]
  paragraphs_rejected = [(key7,3,text_style)]
  paragraphs_part2 = [(key9,1,signature_style)]
  paragraphs = []

  # Konstruktor
  def __init__(self):
    self.oDoc = open_writerdocument("letter-template.ott")
    self.oText = self.oDoc.Text
    self.oCursor = self.oText.createTextCursor()
    self.create_boilerplates()
    self.setupWindow()

Während der Initialisierung der Klasse ApplicationModule wird im Konstruktor zunächst die Writer-Vorlage eingelesen, indem auf unotools, die Bibliothek eines Drittanbieters, mit from unotools.component.writer import Writer zurückgegriffen wird.

So öffnet die Methode open_writerdocument (Listing 2) unter Angabe des Pfads nicht nur Textdokumente mit der Endung .odt, sondern die weiter oben genannte Vorlage. Danach erfolgt die Generierung des Textobjekts oText, um darüber Zugriff auf den Service Text zu erhalten [4]. Zur Bearbeitung von Textdokumenten ist der Textcursor oCursor erforderlich, damit sich an dessen Position Text platzieren lässt (Kasten: „XTextDocument“).

Listing 2

# Writer-Dokument öffnen
def open_writerdocument(path):
  context = connect(Socket('localhost', 8100))
  writer = Writer...