https://visualhunt.com/photo/221786/

Edycja nagłówka *.docx w języku Python

python.org

Jakiś czas temu podczas pisania skryptu automatyzującego generowanie raportów trafiłem na problem edycji nagłówka w plikach *.docx. Projekt realizowałem wykorzystując język Python 3.5 z biblioteką python-docx w środowisku Windows 7. Choć bibliotekę mogę ocenić bardzo dobrze, szczególnie za (moim zdaniem) niski próg wejścia, jest to jeszcze względnie młoda wersja (aktualnie v0.8.6) nieposiadająca mocno rozbudowanego API. Przykładowo biblioteka nie umożliwia edycji nagłówka dokumentu, co leżało w obszarze moich zainteresowań.

Rozwiązanie nie jest idealne, lecz w niektórych przypadkach może okazać się wystarczające, aby osiągnąć założony cel. Metoda wykorzystuje budowę pliku DOCX, który jest archiwum ZIP plików XML, pomiędzy które podzielona jest treść i struktura dokumentu. Każdy plik *.docx możemy rozpakować za pomocą menadżera archiwów i podejrzeć jego zawartość. Jeśli w dokumencie dodany został wcześniej nagłówek, w folderze musimy poszukać plików podobnych do header1.xml.

Poniżej zamieszczam przykładowy kod zamieniający tekst znajdujący się w nagłówku („TEXT_TO_CHANGE”) na nowy napis („Nowy tekst w nagłówku”). Skrypt w pierwszej kolejności rozpakowuje archiwum ZIP, po czym iterując po wszystkich plikach, wyszukuje tego odpowiedzialnego za nagłówek (w tym wypadku header2.xml). Jeśli szukana fraza zostanie znaleziona, tekst umieszczony wcześniej w nagłówku będzie zmieniony. Na koniec skrypt ponownie kompresuje wszystkie pliki do archiwum ZIP i zapisuje pod nazwą output_file.docx.

import zipfile
import sys
 
 
def main(argv):
    input_file = 'input_file.docx'
    output_file = 'output_file.docx'
    header_text = "Nowy tekst w nagłówku"
    edit_header(input_file, output_file, header_text)
 
 
def edit_header(input_file, output_file, header_text):
    zin = zipfile.ZipFile(input_file, 'r')
    zout = zipfile.ZipFile(str(output_file), 'w')
 
    for item in zin.infolist():
        buffer = zin.read(item.filename)
        if item.filename == 'word/header2.xml':
            document_xml = str(buffer).replace('TEXT_TO_CHANGE', header_text)
            document_xml = document_xml.replace("b'", '').replace("'", '')
            document_xml = document_xml.replace("\\r\\n", '')
            zout.writestr(item.filename, document_xml)
        else:
            zout.writestr(item.filename, buffer)
 
    zout.close()
    zin.close()
 
if __name__ == "__main__":
    main(sys.argv)

Aby plik wyjściowy był prawidłowo zinterpretowany przez edytor tekstu należy z pliku header2.xml usunąć ciąg b’, znak ' oraz ciąg CR/LF. Dodatkowo należy mieć na uwadze odmienne działanie w różnych systemach operacyjnych, gdzie ciąg oznaczający koniec linii będzie inny.

Powyższy przykład był testowany w MS Office, jednak jeśli korzystamy np. z LibreOffice, struktura pliku DOCX po rozpakowaniu może być inna. Plik input_file.docx został zapisany w MS Office i zawiera w sobie pliki header1.xml, header2.xml (zawierający treść naszego nagłówka) oraz header3.xml. Wystarczy, że plik otworzymy w LibreOffice i zapiszemy (nawet bez jakichkolwiek zmian), „archiwum DOCX” zawierało będzie tylko header1.xml.

Powyższy przykład należy do tych najprostszych. W przypadku, gdy w dokumencie źródłowym nie było wcześniej wstawionego nagłówka, nie wystarczy jedynie dodać pliku header1.xml. Konieczna jest również aktualizacja zależności w pliku document.xml.rels.

Śledząc ruch na GitHubie w projekcie python-docx można zauważyć, że biblioteka jest ciągle rozwijana. W dokumentacji projektu znaleźć można rozważania dotyczące nowych funkcjonalności, w tym również opcje związane z nagłówkiem i stopką. Z informacji, które udało mi się wyczytać wynika, że funkcjonalność już działa i jest w fazie testów, jednak nie trafiła jeszcze do gałęzi master.

Literatura obowiązkowa:
python-docx documentation
An Informal Introduction to DOCX
Add page header and footer to Python-docx file in Django hack

 
Załączniki:

input_file.docx
output_file.docx

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *