11import os
22import platform
33import re
4+ import shutil
45import subprocess
56from collections .abc import Sequence
67from pathlib import Path
@@ -17,7 +18,7 @@ class LibreOfficeConverter:
1718 using LibreOffice in headless mode.
1819
1920 Requirements:
20- - LibreOffice 7.3 or later must be installed.
21+ - LibreOffice 7.1 or later must be installed.
2122 - Automatically finds LibreOffice in standard installation paths.
2223 - For custom installations, provide `executable_path` parameter.
2324
@@ -26,39 +27,72 @@ class LibreOfficeConverter:
2627 This makes it suitable for server environments and automated workflows.
2728 """
2829
29- def __init__ (self , executable_path : str | None = None ):
30+ def __init__ (self , executable_path : str | Path | None = None ):
3031 """Initialize converter with optional executable path.
3132
3233 Args:
33- executable_path: Path to LibreOffice executable . If None, searches
34- standard installation locations for each platform.
34+ executable_path: Path (or executable name) to LibreOffice. If None,
35+ searches standard installation locations for each platform.
3536
3637 Raises:
3738 FileNotFoundError: If LibreOffice executable cannot be found.
3839 ValueError: If LibreOffice version is below minimum requirement.
3940 """
40- self .executable_path = executable_path or self ._find_executable ()
41- if not self .executable_path :
42- raise FileNotFoundError ("Can't find LibreOffice executable." )
41+ self .executable_path = self ._resolve_executable_path (executable_path )
4342
4443 self ._verify_version ()
4544
46- def _find_executable (self ) -> str | None :
45+ def _resolve_executable_path (self , executable_path : str | Path | None ) -> Path :
46+ """Resolve the LibreOffice executable path."""
47+ if executable_path is None :
48+ found_executable = self ._find_executable ()
49+ if found_executable is None :
50+ raise FileNotFoundError ("Can't find LibreOffice executable." )
51+ return found_executable
52+
53+ executable = os .fspath (executable_path )
54+ expanded = os .path .expanduser (executable )
55+ candidate = Path (expanded )
56+ candidate_str = str (candidate )
57+ looks_like_path = (
58+ candidate .is_absolute ()
59+ or os .sep in candidate_str
60+ or (os .altsep is not None and os .altsep in candidate_str )
61+ )
62+ if looks_like_path :
63+ if candidate .is_file ():
64+ return candidate
65+ raise FileNotFoundError (
66+ f"LibreOffice executable not found at: { candidate } ."
67+ )
68+
69+ resolved_executable = shutil .which (executable )
70+ if resolved_executable is None :
71+ raise FileNotFoundError (f"Can't find LibreOffice executable: { executable } ." )
72+ return Path (resolved_executable )
73+
74+ def _find_executable (self ) -> Path | None :
4775 """Find LibreOffice executable in default locations."""
76+ for name in ("soffice" , "libreoffice" ):
77+ resolved = shutil .which (name )
78+ if resolved is not None :
79+ return Path (resolved )
80+
4881 system = platform .system ()
4982 if system not in DEFAULT_PATHS :
5083 raise RuntimeError (f"Unsupported operating system: { system } ." )
5184
5285 for path in DEFAULT_PATHS [system ]:
53- if os .path .isfile (path ):
54- return path
86+ candidate = Path (path )
87+ if candidate .is_file ():
88+ return candidate
5589 return None
5690
5791 def _verify_version (self ):
5892 """Verify LibreOffice version meets minimum requirement."""
5993 try :
6094 result = subprocess .run (
61- [self .executable_path , "--version" ],
95+ [str ( self .executable_path ) , "--version" ],
6296 capture_output = True ,
6397 text = True ,
6498 check = True ,
@@ -178,10 +212,8 @@ def _convert_single_file(
178212 "Use overwrite=True to force."
179213 )
180214
181- # executable_path is guaranteed to be non-None after __init__
182- assert self .executable_path is not None
183215 cmd = [
184- self .executable_path ,
216+ str ( self .executable_path ) ,
185217 "--invisible" ,
186218 "--headless" ,
187219 "--nologo" ,
0 commit comments