Как использовать 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 Canopy на OSX 10.9.3

Ответ 1

Текущая версия 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 cell in table.cells:
        for paragraph in cell.paragraphs:
            if 'sea' in paragraph.text:
               ...

Если вы преследуете этот путь, вы, вероятно, быстро узнаете, что такое сложности. Если вы замените весь текст абзаца, это приведет к удалению любого символьного форматирования, например слова или фразы, выделенной жирным или курсивом.

Кстати, код из @wnnmaw отвечает за устаревшую версию python-docx и не будет работать вообще с версиями после 0.3.0.

Ответ 2

Мне нужно что-то, чтобы заменить регулярные выражения в 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 вам нужно перейти в "Настройки", "Центр доверия" и "Параметры конфиденциальности", "Неверно" "Хранить случайные числа, чтобы улучшить совпадение точности" и сохранить документ.

Ответ 3

В Центре Office Dev есть запись, в которой разработчик опубликовал (в настоящее время лицензию MIT) описание пары алгоритмов, которые, как представляется, предлагают решение для этого (хотя и на С# и требуют портирования): " публикация MS Dev Center

Ответ 4

Проблема с вашей второй попыткой заключается в том, что вы не определили параметры, которые требуется 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"

Ответ 5

он снова изменил 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 = [
    '@[email protected]',
    '@[email protected]',
    '@[email protected]',
    '@[email protected]',
    '@[email protected]',
    '@[email protected]',
    '@[email protected]',
    '@[email protected]',
    '@[email protected]',
    '@[email protected]',
  ]

  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

например,

enter image description here

не забудьте параметризировать и заменить вещи