| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- """Accept all tracked changes in a DOCX file using LibreOffice.
- Requires LibreOffice (soffice) to be installed.
- """
- import argparse
- import logging
- import shutil
- import subprocess
- from pathlib import Path
- from office.soffice import get_soffice_env
- logger = logging.getLogger(__name__)
- LIBREOFFICE_PROFILE = "/tmp/libreoffice_docx_profile"
- MACRO_DIR = f"{LIBREOFFICE_PROFILE}/user/basic/Standard"
- ACCEPT_CHANGES_MACRO = """<?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
- <script:module xmlns:script="http://openoffice.org/2000/script" script:name="Module1" script:language="StarBasic">
- Sub AcceptAllTrackedChanges()
- Dim document As Object
- Dim dispatcher As Object
- document = ThisComponent.CurrentController.Frame
- dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
- dispatcher.executeDispatch(document, ".uno:AcceptAllTrackedChanges", "", 0, Array())
- ThisComponent.store()
- ThisComponent.close(True)
- End Sub
- </script:module>"""
- def accept_changes(
- input_file: str,
- output_file: str,
- ) -> tuple[None, str]:
- input_path = Path(input_file)
- output_path = Path(output_file)
- if not input_path.exists():
- return None, f"Error: Input file not found: {input_file}"
- if not input_path.suffix.lower() == ".docx":
- return None, f"Error: Input file is not a DOCX file: {input_file}"
- try:
- output_path.parent.mkdir(parents=True, exist_ok=True)
- shutil.copy2(input_path, output_path)
- except Exception as e:
- return None, f"Error: Failed to copy input file to output location: {e}"
- if not _setup_libreoffice_macro():
- return None, "Error: Failed to setup LibreOffice macro"
- cmd = [
- "soffice",
- "--headless",
- f"-env:UserInstallation=file://{LIBREOFFICE_PROFILE}",
- "--norestore",
- "vnd.sun.star.script:Standard.Module1.AcceptAllTrackedChanges?language=Basic&location=application",
- str(output_path.absolute()),
- ]
- try:
- result = subprocess.run(
- cmd,
- capture_output=True,
- text=True,
- timeout=30,
- check=False,
- env=get_soffice_env(),
- )
- except subprocess.TimeoutExpired:
- return (
- None,
- f"Successfully accepted all tracked changes: {input_file} -> {output_file}",
- )
- if result.returncode != 0:
- return None, f"Error: LibreOffice failed: {result.stderr}"
- return (
- None,
- f"Successfully accepted all tracked changes: {input_file} -> {output_file}",
- )
- def _setup_libreoffice_macro() -> bool:
- macro_dir = Path(MACRO_DIR)
- macro_file = macro_dir / "Module1.xba"
- if macro_file.exists() and "AcceptAllTrackedChanges" in macro_file.read_text():
- return True
- if not macro_dir.exists():
- subprocess.run(
- [
- "soffice",
- "--headless",
- f"-env:UserInstallation=file://{LIBREOFFICE_PROFILE}",
- "--terminate_after_init",
- ],
- capture_output=True,
- timeout=10,
- check=False,
- env=get_soffice_env(),
- )
- macro_dir.mkdir(parents=True, exist_ok=True)
- try:
- macro_file.write_text(ACCEPT_CHANGES_MACRO)
- return True
- except Exception as e:
- logger.warning(f"Failed to setup LibreOffice macro: {e}")
- return False
- if __name__ == "__main__":
- parser = argparse.ArgumentParser(
- description="Accept all tracked changes in a DOCX file"
- )
- parser.add_argument("input_file", help="Input DOCX file with tracked changes")
- parser.add_argument(
- "output_file", help="Output DOCX file (clean, no tracked changes)"
- )
- args = parser.parse_args()
- _, message = accept_changes(args.input_file, args.output_file)
- print(message)
- if "Error" in message:
- raise SystemExit(1)
|