v0.5.6
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
"""Headless soffice CLI helpers (convert/import) for UNO post-process."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from .uno_helpers import file_url_from_path
|
||||
from .uno_profile import ensure_profile_seeded, profile_dir
|
||||
|
||||
|
||||
def soffice_cli_path(soffice_path: str) -> str:
|
||||
"""On Windows prefer ``soffice.com`` — ``soffice.exe`` opens the Start Center GUI."""
|
||||
path = Path(soffice_path)
|
||||
if sys.platform == "win32":
|
||||
com = path.with_name("soffice.com")
|
||||
if com.is_file():
|
||||
return str(com)
|
||||
return str(path)
|
||||
|
||||
|
||||
def _base_args(soffice_path: str, profile_root: Path) -> list[str]:
|
||||
ensure_profile_seeded(profile_root)
|
||||
return [
|
||||
soffice_cli_path(soffice_path),
|
||||
f"-env:UserInstallation={file_url_from_path(str(profile_root))}",
|
||||
"--headless",
|
||||
"--nologo",
|
||||
"--norestore",
|
||||
"--nodefault",
|
||||
"--nofirststartwizard",
|
||||
"--nolockcheck",
|
||||
]
|
||||
|
||||
|
||||
def convert_file(
|
||||
soffice_path: str,
|
||||
*,
|
||||
engine: str,
|
||||
src: str,
|
||||
out_dir: str,
|
||||
convert_to: str,
|
||||
timeout_s: float = 300.0,
|
||||
) -> None:
|
||||
"""Run ``soffice --convert-to …`` and wait until it finishes."""
|
||||
profile_root = profile_dir(engine)
|
||||
cmd = [
|
||||
*_base_args(soffice_path, profile_root),
|
||||
"--convert-to",
|
||||
convert_to,
|
||||
"--outdir",
|
||||
os.path.abspath(out_dir),
|
||||
os.path.abspath(src),
|
||||
]
|
||||
completed = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout_s,
|
||||
cwd=str(Path(soffice_path).parent),
|
||||
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0) if sys.platform == "win32" else 0,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
tail = (completed.stderr or completed.stdout or "").strip()
|
||||
if len(tail) > 500:
|
||||
tail = tail[-500:]
|
||||
raise RuntimeError(
|
||||
f"convert-to {convert_to} failed (code {completed.returncode})"
|
||||
+ (f": {tail}" if tail else "")
|
||||
)
|
||||
stem = Path(src).stem
|
||||
expected = Path(out_dir) / f"{stem}.docx"
|
||||
if convert_to.startswith("docx") and not expected.is_file():
|
||||
found = sorted(Path(out_dir).glob("*.docx"))
|
||||
if len(found) == 1:
|
||||
return
|
||||
raise RuntimeError(
|
||||
f"convert-to {convert_to}: output missing ({expected.name})"
|
||||
+ (f", got {[p.name for p in found]}" if found else "")
|
||||
)
|
||||
|
||||
|
||||
def import_docx_to_odt(soffice_path: str, *, engine: str, docx_path: str, out_dir: str) -> Path:
|
||||
"""OpenOffice imports DOCX more reliably via native ODT round-trip."""
|
||||
for spec in ("odt", 'odt:"writer8"'):
|
||||
try:
|
||||
convert_file(
|
||||
soffice_path,
|
||||
engine=engine,
|
||||
src=docx_path,
|
||||
out_dir=out_dir,
|
||||
convert_to=spec,
|
||||
)
|
||||
odt = Path(out_dir) / f"{Path(docx_path).stem}.odt"
|
||||
if odt.is_file():
|
||||
return odt
|
||||
except RuntimeError:
|
||||
continue
|
||||
raise RuntimeError(f"OpenOffice: не удалось импортировать DOCX → ODT: {docx_path}")
|
||||
|
||||
|
||||
def export_odt_to_docx(soffice_path: str, *, engine: str, odt_path: str, out_dir: str) -> Path:
|
||||
# OpenOffice on Windows treats spaces in ``docx:MS Word 2007 XML`` as extra
|
||||
# file arguments (Word / 2007 / XML). Use plain ``docx`` there.
|
||||
if engine == "openoffice":
|
||||
specs = ("docx",)
|
||||
else:
|
||||
specs = ("docx", "MS Word 2007 XML")
|
||||
for spec in specs:
|
||||
try:
|
||||
convert_file(
|
||||
soffice_path,
|
||||
engine=engine,
|
||||
src=odt_path,
|
||||
out_dir=out_dir,
|
||||
convert_to=spec,
|
||||
)
|
||||
docx = Path(out_dir) / f"{Path(odt_path).stem}.docx"
|
||||
if docx.is_file():
|
||||
return docx
|
||||
except RuntimeError:
|
||||
continue
|
||||
raise RuntimeError(f"OpenOffice: не удалось экспортировать ODT → DOCX: {odt_path}")
|
||||
Reference in New Issue
Block a user