Как да използвате python-docx за замяна на текст в документ на Word и запазване

Модулът oodocx, споменат на същата страница, препраща потребителя към папка /examples, която изглежда не е там.
Прочетох документацията на python-docx 0.7.2, плюс всичко, което можах да намеря в Stackoverflow по темата , така че, моля, вярвайте, че съм си направил „домашното“.

Python е единственият език, който знам (начинаещ+, може би средно напреднал), така че, моля, не предполагайте никакви познания по C, Unix, xml и т.н.

Задача: Отворете документ на ms-word 2007+ с един ред текст в него (за да опростите нещата) и заменете всяка „ключова“ дума в речника, която се среща в този ред текст с нейната речникова стойност. След това затворете документа, като запазите всичко останало същото.

Ред от текст (например) „Ще останем в стаите на морето.“

from docx import Document

document = Document('/Users/umityalcin/Desktop/Test.docx')

Dictionary = {‘sea’: “ocean”}

sections = document.sections
for section in sections:
    print(section.start_type)

#Now, I would like to navigate, focus on, get to, whatever to the section that has my
#single line of text and execute a find/replace using the dictionary above.
#then save the document in the usual way.

document.save('/Users/umityalcin/Desktop/Test.docx')

Не виждам нищо в документацията, което да ми позволява да направя това - може би е там, но не го разбирам, защото всичко не е изписано на моето ниво.

Следвах други предложения на този сайт и се опитах да използвам по-ранни версии на модула (https://github.com/mikemaccana/python-docx), за който се предполага, че има „методи като replace, advReplace“, както следва: отварям изходния код в интерпретатора на python и добавям следното в края (това е, за да избегна сблъсъци с вече инсталираната версия 0.7.2):

document = opendocx('/Users/umityalcin/Desktop/Test.docx')
words = document.xpath('//w:r', namespaces=document.nsmap)
for word in words:
    if word in Dictionary.keys():
        print "found it", Dictionary[word]
        document = replace(document, word, Dictionary[word])
savedocx(document, coreprops, appprops, contenttypes, websettings,
    wordrelationships, output, imagefiledict=None) 

Изпълнението на това води до следното съобщение за грешка:

NameError: името 'coreprops' не е дефинирано

Може би се опитвам да направя нещо, което не може да се направи, но ще съм благодарен за вашата помощ, ако пропускам нещо просто.

Ако това има значение, използвам 64-битовата версия на Enthought's Canopy на OSX 10.9.3


person user2738815    schedule 17.07.2014    source източник


Отговори (8)


Текущата версия на python-docx няма функция search() или replace() функция. Те се изискват доста често, но имплементацията за общия случай е доста трудна и все още не се е издигнала до върха на изоставането.

Няколко души обаче постигнаха успех, като направиха това, от което се нуждаят, използвайки вече наличните съоръжения. Ето един пример. Между другото няма нищо общо със секциите :)

for paragraph in document.paragraphs:
    if 'sea' in paragraph.text:
        print paragraph.text
        paragraph.text = 'new text containing ocean'

За да търсите и в Таблици, ще трябва да използвате нещо като:

for table in document.tables:
    for row in table.rows:
        for cell in row.cells:
            for paragraph in cell.paragraphs:
                if 'sea' in paragraph.text:
                    paragraph.text = paragraph.text.replace("sea", "ocean")

Ако следвате този път, вероятно ще откриете доста бързо какви са сложностите. Ако замените целия текст на абзац, това ще премахне всяко форматиране на ниво знаци, като дума или фраза с удебелен шрифт или курсив.

Между другото, кодът от отговора на @wnnmaw е за наследената версия на python-docx и изобщо няма да работи с версии след 0.3.0.

person scanny    schedule 17.07.2014
comment
Благодаря за пояснението; спестява много време. Ще бъда сред множеството, които чакат тези функции да се издигнат до върха на списъка, докато се опитвам да направя това, което трябва да направя с наследената версия. Между другото, има ли нещо в текущата версия, което би ми позволило да изтрия думата море в параграфа и да вмъкна друга дума на нейно място? Вероятно не, защото ако бяха налични, дори аз бих могъл да напиша заместваща функция... Поздрави - person user2738815; 18.07.2014
comment
Съвсем правилно. Щеше да е лесно, ако беше така. Проблемът възниква, защото sea може да бъде в елемент <w:t> сам по себе си, разделен на две или дори три и може дори да се появи в различни изпълнения (<w:r> елементи, родител на елемента t). Замяната на дума изисква прекомпозиране на елементите, които я съдържат. Има много възможни случаи и правила, регулиращи как да го сглобите отново, без да го прецакате. Ако случаят е прост, можете да минете с просто пренаписване на текста, но иначе това е доста голяма работа. Не забравяйте да гласувате и да приемете отговора, ако сте доволни :) - person scanny; 18.07.2014
comment
Очевидно не мога да гласувам, защото ми липсва репутация, но оценявам помощта ви и поставих отметка, за да приема отговора. за разбирането - person user2738815; 18.07.2014
comment
За справка - това е действителната дискусия в Github относно този проблем: github.com/ python-openxml/python-docx/issues/30 - person Grzegorz Oledzki; 12.04.2015

Имах нужда от нещо, което да замени регулярните изрази в docx. Взех скен отговор. За обработка на стил използвах отговор от: Python docx Замяна на низ в абзац при запазване на стила добавено рекурсивно извикване за обработка на вложени таблици. и измисли нещо като това:

import re
from docx import Document

def docx_replace_regex(doc_obj, regex , replace):

    for p in doc_obj.paragraphs:
        if regex.search(p.text):
            inline = p.runs
            # Loop added to work with runs (strings with same style)
            for i in range(len(inline)):
                if regex.search(inline[i].text):
                    text = regex.sub(replace, inline[i].text)
                    inline[i].text = text

    for table in doc_obj.tables:
        for row in table.rows:
            for cell in row.cells:
                docx_replace_regex(cell, regex , replace)



regex1 = re.compile(r"your regex")
replace1 = r"your replace string"
filename = "test.docx"
doc = Document(filename)
docx_replace_regex(doc, regex1 , replace1)
doc.save('result1.docx')

За да преминете през речника:

for word, replacement in dictionary.items():
    word_re=re.compile(word)
    docx_replace_regex(doc, word_re , replacement)

Имайте предвид, че това решение ще замени регулярния израз само ако целият регулярен израз има същия стил в документа.

Освен това, ако текстът е редактиран след запазване на същия стил, текстът може да бъде в отделни серии. Например, ако отворите документ, който има низ "testabcd" и го промените на "test1abcd" и запишете, дори тестото да е със същия стил, в този случай има 3 отделни изпълнения "test", "1" и "abcd" замяната на test1 няма да работи.

Това е за проследяване на промените в документа. За да го маржирате в едно изпълнение, в Word трябва да отидете в „Опции“, „Център за доверие“ и в „Опции за поверителност“ да премахнете дебелината на „Съхраняване на произволни числа за подобряване на точността на комбиниране“ и да запишете документа.

person szum    schedule 16.03.2017
comment
Това работи в границите, които споменахте, и аз го подкрепих. Все пак би било полезно да редактирате кода си, за да покажете как можете да му подадете речник. Проверих го; може да се направи, но има нужда от въртене с регулярния израз. Не искам да публикувам отделен отговор. Благодаря - person user2738815; 23.03.2017
comment
Актуализиран с примерен речник и добавено описание как да обединявате редакциите в едно изпълнение. наздраве - person szum; 24.03.2017
comment
Благодаря ти. Използвам 2.7 и word_re = re.compile(word) повдига грешка. Вместо това word_re = re.compile(str(word) работи. Не знам дали това е разлика, свързана с версията, защото не знам как работи python 3. - person user2738815; 25.03.2017
comment
Благодаря @szum за вашето решение, то работи перфектно, но забелязвам, че игнорира текста на wordart или текста в текстовите полета, можете ли да надградите върху него, за да добавите поддръжка за това. - person Johnn Kaita; 16.12.2020
comment
Имам по-долу грешка: ~~~ Traceback (последното последно извикване): gen_docx(вход, речник, изход) docx_replace_regex(документ, word_re, заместване) текст = regex.sub(замяна, вграден[i].текст) шаблон = _compile_repl (template, pattern) return sre_parse.parse_template(repl, pattern) s = Tokenizer(source) string = str(string, 'latin1') TypeError: декодиране до str: нужда от обект, подобен на байтове, намерени int ~~~ Имате ли знаете как да решите? Благодаря. - person Steven Lee; 18.03.2021

Получих много помощ от отговорите от по-рано, но за мен кодът по-долу функционира така, както простата функция за намиране и замяна в word би свършила работа. Надявам се това да помогне.

#!pip install python-docx
#start from here if python-docx is installed
from docx import Document
#open the document
doc=Document('./test.docx')
Dictionary = {"sea": "ocean", "find_this_text":"new_text"}
for i in Dictionary:
    for p in doc.paragraphs:
        if p.text.find(i)>=0:
            p.text=p.text.replace(i,Dictionary[i])
#save changed document
doc.save('./test.docx')

Горното решение има ограничения. 1) Параграфът, съдържащ „find_this_text“, ще стане обикновен текст без формат, 2) контекстните контроли, които са в същия абзац с „find_this_text“, ще бъдат изтрити и 3) „find_this_text“ в контекстните контроли или таблици ще да не се променят.

person poin    schedule 30.04.2020
comment
Това е невероятно! - person Jem; 13.06.2020
comment
Това е перфектно :) Благодаря ви много. - person Manthan_Admane; 09.07.2020
comment
О, maaan....Точно от това имах нужда... Благодаря ви, алооооооотт.... - person Pragyan Choudhury; 05.08.2020
comment
Това свети. Благодаря ти човече. Това също не работи на масата - person mending3; 02.02.2021
comment
Точно от каквото имах нужда. Благодаря! - person Danny Blaker; 08.06.2021

Споделянето на малък скрипт, който написах - ми помага да генерирам правни .docx договори с променливи, като същевременно запазвам оригиналния стил.

pip install python-docx

Пример:

from docx import Document
import os


def main():
    template_file_path = 'employment_agreement_template.docx'
    output_file_path = 'result.docx'

    variables = {
        "${EMPLOEE_NAME}": "Example Name",
        "${EMPLOEE_TITLE}": "Software Engineer",
        "${EMPLOEE_ID}": "302929393",
        "${EMPLOEE_ADDRESS}": "דרך השלום מנחם בגין דוגמא",
        "${EMPLOEE_PHONE}": "+972-5056000000",
        "${EMPLOEE_EMAIL}": "[email protected]",
        "${START_DATE}": "03 Jan, 2021",
        "${SALARY}": "10,000",
        "${SALARY_30}": "3,000",
        "${SALARY_70}": "7,000",
    }

    template_document = Document(template_file_path)

    for variable_key, variable_value in variables.items():
        for paragraph in template_document.paragraphs:
            replace_text_in_paragraph(paragraph, variable_key, variable_value)

        for table in template_document.tables:
            for col in table.columns:
                for cell in col.cells:
                    for paragraph in cell.paragraphs:
                        replace_text_in_paragraph(paragraph, variable_key, variable_value)

    template_document.save(output_file_path)


def replace_text_in_paragraph(paragraph, key, value):
    if key in paragraph.text:
        inline = paragraph.runs
        for item in inline:
            if key in item.text:
                item.text = item.text.replace(key, value)


if __name__ == '__main__':
    main()

въведете описание на изображението тук

person Jossef Harush    schedule 31.03.2021

За случая с таблицата трябваше да променя отговора на @scanny на:

for table in doc.tables:
    for col in table.columns:
        for cell in col.cells:
            for p in cell.paragraphs:

за да работи. Наистина, това изглежда не работи с текущото състояние на API:

for table in document.tables:
    for cell in table.cells:

Същият проблем с кода от тук: https://github.com/python-openxml/python-docx/issues/30#issuecomment-38658149

person Basj    schedule 27.04.2020

Центърът за разработка на Office има запис, в който разработчик е публикувал (понастоящем лицензиран от MIT) описание на няколко алгоритма, които изглежда предлагат решение за това (макар и в C# и изискват пренасяне):" Публикуване на MS Dev Center

person Soferio    schedule 21.01.2018
comment
Много интересно Соферио! Благодаря много, че спомена това; Ще го проуча внимателно за евентуално включване в библиотеката :) - person scanny; 03.05.2019

Проблемът с втория ви опит е, че не сте дефинирали параметрите, от които savedocx се нуждае. Трябва да направите нещо подобно преди да запазите:

relationships = docx.relationshiplist()
title = "Document Title"
subject = "Document Subject"
creator = "Document Creator"
keywords = []

coreprops = docx.coreproperties(title=title, subject=subject, creator=creator,
                       keywords=keywords)
app = docx.appproperties()
content = docx.contenttypes()
web = docx.websettings()
word = docx.wordrelationships(relationships)
output = r"path\to\where\you\want\to\save"
person wnnmaw    schedule 17.07.2014
comment
Благодаря ви много, че се отзовахте. Веднага добавих вашия код преди запазването, като промених само изходния път на /Users/umityalcin/Desktop/ (Предполагам, че оставянето на заглавие и т.н. такова, каквото е, няма значение) Въпреки това, срещам допълнителни проблеми. Първо, тъй като не бях импортирал текущия docx модул (0.7.2), за да избегна грешки, интерпретаторът не разпозна docx. префикс. Така че импортирах модула - сега получавам това: AttributeError: 'module' object has no attribute 'relationshiplist'. Благодаря за отделеното време и помощта. - person user2738815; 17.07.2014
comment
Ах, добре, явно четенето не е моята силна страна :P Ако имате всички функции на docx в обхвата на вашата програма, нямате нужда от префикса docx., така че опитайте да го премахнете - person wnnmaw; 17.07.2014
comment
Е, поне кодирането не е вашата слаба страна; изглежда е мой :) След като последвах вашето предложение, все пак успях да получа тази грешка: savedocx(document, coreprops, appprops, contenttypes, websettings, wordrelationships, output, imagefiledict) 1061 ) 1062 -› 1063 assert os.path.isdir (template_dir) 1064 docxfile = zipfile.ZipFile( 1065 изход, режим='w', компресия=zipfile.ZIP_DEFLATED) AssertionError: - person user2738815; 18.07.2014

той отново промени API в docx py...

за здравия разум на всеки идващ тук:

import datetime
import os
from decimal import Decimal
from typing import NamedTuple

from docx import Document
from docx.document import Document as nDocument


class DocxInvoiceArg(NamedTuple):
  invoice_to: str
  date_from: str
  date_to: str
  project_name: str
  quantity: float
  hourly: int
  currency: str
  bank_details: str


class DocxService():
  tokens = [
    '@INVOICE_TO@',
    '@IDATE_FROM@',
    '@IDATE_TO@',
    '@INVOICE_NR@',
    '@PROJECTNAME@',
    '@QUANTITY@',
    '@HOURLY@',
    '@CURRENCY@',
    '@TOTAL@',
    '@BANK_DETAILS@',
  ]

  def __init__(self, replace_vals: DocxInvoiceArg):
    total = replace_vals.quantity * replace_vals.hourly
    invoice_nr = replace_vals.project_name + datetime.datetime.strptime(replace_vals.date_to, '%Y-%m-%d').strftime('%Y%m%d')
    self.replace_vals = [
      {'search': self.tokens[0], 'replace': replace_vals.invoice_to },
      {'search': self.tokens[1], 'replace': replace_vals.date_from },
      {'search': self.tokens[2], 'replace': replace_vals.date_to },
      {'search': self.tokens[3], 'replace': invoice_nr },
      {'search': self.tokens[4], 'replace': replace_vals.project_name },
      {'search': self.tokens[5], 'replace': replace_vals.quantity },
      {'search': self.tokens[6], 'replace': replace_vals.hourly },
      {'search': self.tokens[7], 'replace': replace_vals.currency },
      {'search': self.tokens[8], 'replace': total },
      {'search': self.tokens[9], 'replace': 'asdfasdfasdfdasf'},
    ]
    self.doc_path_template = os.path.dirname(os.path.realpath(__file__))+'/docs/'
    self.doc_path_output = self.doc_path_template + 'output/'
    self.document: nDocument = Document(self.doc_path_template + 'invoice_placeholder.docx')


  def save(self):
    for p in self.document.paragraphs:
      self._docx_replace_text(p)
    tables = self.document.tables
    self._loop_tables(tables)
    self.document.save(self.doc_path_output + 'testiboi3.docx')

  def _loop_tables(self, tables):
    for table in tables:
      for index, row in enumerate(table.rows):
        for cell in table.row_cells(index):
          if cell.tables:
            self._loop_tables(cell.tables)
          for p in cell.paragraphs:
            self._docx_replace_text(p)

        # for cells in column.
        # for cell in table.columns:

  def _docx_replace_text(self, p):
    print(p.text)
    for el in self.replace_vals:
      if (el['search'] in p.text):
        inline = p.runs
        # Loop added to work with runs (strings with same style)
        for i in range(len(inline)):
          print(inline[i].text)
          if el['search'] in inline[i].text:
            text = inline[i].text.replace(el['search'], str(el['replace']))
            inline[i].text = text
        print(p.text)

Тестов случай:

from django.test import SimpleTestCase
from docx.table import Table, _Rows

from toggleapi.services.DocxService import DocxService, DocxInvoiceArg


class TestDocxService(SimpleTestCase):

  def test_document_read(self):
    ds = DocxService(DocxInvoiceArg(invoice_to="""
    WAW test1
    Multi myfriend
    """,date_from="2019-08-01", date_to="2019-08-30", project_name='WAW', quantity=10.5, hourly=40, currency='USD',bank_details="""
    Paypal to:
    [email protected]"""))

    ds.save()

имате папки docs и docs/output/ в същата папка, където имате DocxService.py

e.g.

въведете описание на изображението тук

не забравяйте да параметризирате и замените неща

person Toskan    schedule 21.09.2019