我有一组 .docx 文档,其中使用“跟踪更改”功能执行了文本修改。
对于我集中的每个
source.docx
文件,我想以编程方式运行两个操作:
换句话说,我想运行以下管道:
source.docx
-> sources-all-changes-rejected.docx
-> source-all-rejected-plaintext.txt
source.docx
-> sources-all-changes-accepted.docx
-> source-all-accepted-plaintext.txt
有没有办法做到这一点,例如使用
soffice --headless
?
我尝试了受 Python - 使用 win32com.client 接受 Word 文档中的所有更改启发的解决方案。这种方法很有效,注意使用绝对路径并保存为 txt 文档https://learn.microsoft.com/en-us/office/vba/api/word.wdsaveformat。所以我有一个函数,它采用
pathlib
路径 file_path
并根据需要写入纯文本文档:
def output_track_changed_version(file_path, action):
doc.TrackRevisions = False
# Delete all comments
if doc.Comments.Count >= 1:
doc.DeleteAllComments()
# Accept/reject all revisions
doc.Revisions.AcceptAll()
changed_text = doc.Content.Text
doc.Undo()
doc.Revisions.RejectAll()
original_text = doc.Content.Text
# [CUT: code to dump changed/original strings to file and then...]
doc.Close(False, False, False)
word.Application.Quit()
但是我不想坚持
win32com.client
,更喜欢基于 LibreOffice 的解决方案 + Python,可以轻松在 Linux VM 上设置。
我尝试了受 Python - 使用 win32com.client 接受 Word 文档中的所有更改启发的解决方案。这种方法很有效,注意使用绝对路径并保存为 txt 文档https://learn.microsoft.com/en-us/office/vba/api/word.wdsaveformat。所以我有一个函数,它采用
pathlib
路径 file_path
并根据需要写入纯文本文档:
def output_track_changed_version(file_path, action):
word = win32.gencache.EnsureDispatch("Word.Application")
word.Visible = False
print(file_path.absolute())
doc = word.Documents.Open(str(file_path.absolute()))
doc.Activate()
word.ActiveDocument.TrackRevisions = False
# Delete all comments
if word.ActiveDocument.Comments.Count >= 1:
word.ActiveDocument.DeleteAllComments()
# Accept all revisions
if action == "accept":
word.ActiveDocument.Revisions.AcceptAll()
word.ActiveDocument.SaveAs2(str((Path("accept-all") / file_path.name).absolute()) + ".txt", FileFormat=7)
elif action == "reject":
word.ActiveDocument.Revisions.RejectAll()
word.ActiveDocument.SaveAs2(str((Path("reject-all") / file_path.name).absolute()) + ".txt", FileFormat=7)
doc.Close(False)
word.Application.Quit()
但是我仍然更喜欢基于
soffice
的解决方案。
至于生成接受所有更改的文档,您可能知道没有直接命令来执行此操作。您所要做的就是创建一个宏来执行此过程。对我来说,我希望它在 docker 容器中运行,作为一个无头环境,您需要在没有 GUI 的情况下启动 libreoffice。 为此,我创建了一个supervisord.conf 文件以使用虚拟屏幕启动 libreoffice,这是我的文件 (supervisord.conf):
[supervisord]
nodaemon=true
logfile=/var/log/supervisord.log
pidfile=/var/run/supervisord.pid
[program:Xvfb]
command=/usr/bin/Xvfb :1 -screen 0 1024x768x16
autostart=true
autorestart=true
stdout_logfile=/var/log/xvfb.log
stderr_logfile=/var/log/xvfb_err.log
[program:libreoffice]
command=/usr/bin/libreoffice --headless --accept="socket,host=0.0.0.0,port=2002;urp;StarOffice.ServiceManager"
autostart=true
autorestart=true
stdout_logfile=/var/log/libreoffice.log
stderr_logfile=/var/log/libreoffice_err.log
那么你可以使用以下命令运行它:
supervisord -c /etc/supervisor/conf.d/supervisord.conf
这将以无头模式启动 libreoffice 应用程序,并允许您的宏连接到该应用程序。
我用来打开文档并接受所有跟踪更改的宏是:
import sys
sys.path.append('/usr/lib/python3/dist-packages')
import uno
import os
def load_document(desktop, file_path):
file_url = uno.systemPathToFileUrl(os.path.abspath(file_path))
return desktop.loadComponentFromURL(file_url, "_blank", 0, ())
def accept_all_changes(file_path):
# Get the UNO component context from the running LibreOffice instance
local_context = uno.getComponentContext()
# Create the UnoUrlResolver
resolver = local_context.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", local_context)
# Connect to the running LibreOffice instance
ctx = resolver.resolve("uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext")
# Get the ServiceManager from the context
smgr = ctx.ServiceManager
# Get the desktop (main entry point for most actions)
desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", ctx)
# Load the specified document
model = load_document(desktop, file_path)
if model is None:
raise Exception(f"Failed to load document: {file_path}")
dispatcher = smgr.createInstanceWithContext("com.sun.star.frame.DispatchHelper", ctx)
controller = model.getCurrentController()
args = []
dispatcher.executeDispatch(controller.Frame,
".uno:ShowTrackedChanges", "", 0, tuple(args))
dispatcher.executeDispatch(controller.Frame,
".uno:AcceptAllTrackedChanges", "", 0, tuple(args))
model.store()
model.close(True)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python accept_changes_2.py <file_path>")
sys.exit(1)
file_path = sys.argv[1]
accept_all_changes(file_path)
然后你可以使用以下命令运行这个宏:
python path/to/macro.py tmp/filename.docx
文件保存在您传递到宏中的同一目录/文件名中。 在拒绝所有更改的情况下,请使用名为“.uno:RejectAllTrackedChanges”的调度命令
所有命令的列表可以在这里找到: https://wiki.documentfoundation.org/Development/DispatchCommands#Script_example
我不知道它是否解决了您的问题,但是您可以使用 docx 库(使用
pip install python-docx
命令安装)来解决此任务。
示例:
import docx
#rejected changes
doc = docx.Document('source.docx')
doc.save('sources-all-changes-rejected.docx')
#txt
r_text = []
for par in doc.paragraphs:
r_text.append(par.text)
r_text = '\n'.join(r_text)
filename = 'source-all-rejected-plaintext.txt'
with open(filename, 'w') as r_txt:
r_txt.write(r_text)
#accepted changes
for par in doc.paragraphs:
#do changes
pass
#txt
a_text = []
for par in doc.paragraphs:
a_text.append(par.text)
a_text = '\n'.join(a_text)
filename = 'source-all-accepted-plaintext.txt'
with open(filename, 'w') as a_txt:
a_txt.write(a_text)
#docx
doc.save('sources-all-changes-accepted.docx')
然后您可以循环遍历集合中的所有文件。