Как использовать 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]()
не забудьте параметризировать и заменить вещи