2014-07-25 10 views
5

Ich bin mir nicht sicher, ob mir etwas offensichtlich fehlt, aber ich habe nichts dokumentiert darüber, wie man Word-Elemente (zum Beispiel Tabellen) an einer bestimmten Stelle in einem Dokument einfügen würde ?python-docx Einfügepunkt

ich ein bestehendes MS Word DOCX-Dokument bin Laden unter Verwendung:

my_document = Document('some/path/to/my/document.docx') 

Mein Anwendungsfall die ‚Position‘ von Lesezeichen oder Abschnitt im Dokument zu bekommen wäre und dann gehen Sie nachstehende Tabellen einfügen dieser Punkt.

Ich denke über eine API, die mir etwas in diese Richtung zu tun erlauben würde:

insertion_point = my_document.bookmarks['bookmark_name'].position 
my_document.add_table(rows=10, cols=3, position=insertion_point+1) 

ich sah, dass es Pläne gibt so etwas wie die ‚Reichweite‘ Ziel der MS Word-API zu implementieren Dies würde dieses Problem effektiv lösen. Gibt es in der Zwischenzeit eine Möglichkeit, die Objektmethoden document anzuweisen, wo die neuen Elemente eingefügt werden sollen?

Vielleicht kann ich etwas lxml-Code kleben, um einen Knoten zu finden und diesen Python-docx-Methoden zu übergeben? Jede Hilfe zu diesem Thema wäre sehr willkommen! Vielen Dank.

Antwort

8

Ich erinnerte mich an ein altes Sprichwort, "benutze die Quelle, Luke!", Und konnte es herausfinden. Ein Post von Python-Docx-Besitzer auf seiner Git-Projektseite gab mir auch einen Hinweis: https://github.com/python-openxml/python-docx/issues/7.

Das vollständige XML-Dokumentmodell kann mit der Eigenschaft _document_part._element erreicht werden. Es verhält sich genau wie ein lxml-etree-Element. Von dort ist alles möglich.

Um mein spezifisches Einfügepunktproblem zu lösen, habe ich ein temporäres docx.Document-Objekt erstellt, mit dem ich meinen generierten Inhalt speichern konnte.

import docx 
from docx.oxml.shared import qn 
tmp_doc = docx.Document() 

# Generate content in tmp_doc document 
tmp_doc.add_heading('New heading', 1) 
# more content generation using docx API. 
# ... 

# Reference the tmp_doc XML content 
tmp_doc_body = tmp_doc._document_part._element.body 
# You could pretty print it by using: 
#print(docx.oxml.xmlchemy.serialize_for_reading(tmp_doc_body)) 

Ich lud dann meine docx-Vorlage in ein zweites docx.Document Objekt (ein Lesezeichen namens ‚insertion_point‘ enthält).

Der nächste Schritt ist das Analysieren des XML-Dokuments, um den Index des Einfügepunkts zu finden. Ich definiert eine kleine Funktion für die Aufgabe in der Hand, die eine benannte Lesezeichen Mutterabsatzelement zurückgibt:

def get_bookmark_par_element(document, bookmark_name): 
""" 
Return the named bookmark parent paragraph element. If no matching 
bookmark is found, the result is '1'. If an error is encountered, '2' 
is returned. 
""" 
doc_element = document._document_part._element 
bookmarks_list = doc_element.findall('.//' + qn('w:bookmarkStart')) 
for bookmark in bookmarks_list: 
    name = bookmark.get(qn('w:name')) 
    if name == bookmark_name: 
     par = bookmark.getparent() 
     if not isinstance(par, docx.oxml.CT_P): 
      return 2 
     else: 
      return par 
return 1 

Die neu definierte Funktion toget das Lesezeichen ‚insertion_point‘ parent Absatz verwendet wurde. Die Fehlerkontrolle bleibt dem Leser überlassen.

bookmark_par = get_bookmark_par_element(doc, 'insertion_point') 

Wir haben jetzt bookmark_par die etree Index einfügen unsere tmp_doc generierte Inhalte am richtigen Ort einsetzen können:

bookmark_par_parent = bookmark_par.getparent() 
index = bookmark_par_parent.index(bookmark_par) + 1 
for child in tmp_doc_body: 
    bookmark_par_parent.insert(index, child) 
    index = index + 1 
bookmark_par_parent.remove(bookmark_par) 

Das Dokument nun abgeschlossen ist, der erzeugte Inhalt eingefügt wurde, an dem Lesezeichen Ort eines vorhandenes Word-Dokument.

# Save result 
# print(docx.oxml.xmlchemy.serialize_for_reading(doc_body)) 
doc.save('/some/path/generated_doc.docx') 

Ich hoffe, dass dies jemand helfen kann, da die Dokumentation diesbezüglich noch zu schreiben ist.

0

Vielen Dank, dass Sie sich die Zeit genommen haben, all dies zu erklären.

Ich ging mehr oder weniger das gleiche Problem durch. Mein besonderer Punkt war, wie man am Ende zwei oder mehr docx-Dokumente zusammenführt.

Es ist nicht gerade eine Lösung für Ihr Problem, aber hier ist die Funktion, die ich kam mit:

def combinate_word(main_file, files, output): 
    main_doc = Document(main_file) 
    for file in files: 
     sub_doc = Document(file) 

     for element in sub_doc._document_part.body._element: 
      main_doc._document_part.body._element.append(element) 

    main_doc.save(output) 

Leider ist es noch nicht möglich und auch nicht einfach, Bilder mit Python-docx zu kopieren. Ich falle zurück zu win32com ...

+0

Thanks for sharing! Ich musste noch nicht mit Bildern experimentieren, also bin ich mir nicht sicher über die Herausforderungen auf dieser Seite. – Apteryx

0

Sie setzen [Bild] als Zeichen in der Vorlage Dokument:

for paragraph in document.paragraphs: 
    if "[image]" in paragraph.text: 
     paragraph.text = paragraph.text.strip().replace("[image]", "") 

     run = paragraph.add_run() 
     run.add_picture(image_path, width=Inches(3)) 

Sie haben auch einen Absatz in einer Tabellenzelle haben. Finde einfach die Zelle und mach wie oben.

1

Python-docx Besitzer schlägt vor, wie eine Tabelle in der Mitte eines bestehenden Dokument einzufügen: https://github.com/python-openxml/python-docx/issues/156

Hier ist es mit einigen Verbesserungen:

import re 
from docx import Document 

def move_table_after(document, table, search_phrase): 
    regexp = re.compile(search_phrase) 
    for paragraph in document.paragraphs: 
     if paragraph.text and regexp.search(paragraph.text): 
      tbl, p = table._tbl, paragraph._p 
      p.addnext(tbl) 
      return paragraph 

if __name__ == '__main__': 
    document = Document('Existing_Document.docx')  
    table = document.add_table(rows=..., cols=...) 
    ... 
    move_table_after(document, table, "your search phrase")      
    document.save('Modified_Document.docx')