Source code for ooodev.utils.info

# coding: utf-8
# Python conversion of Info.java by Andrew Davison, ad@fivedots.coe.psu.ac.th
# See Also: https://fivedots.coe.psu.ac.th/~ad/jlop/
from __future__ import annotations
import sys
import contextlib
import datetime
from enum import Enum, IntFlag
from pathlib import Path
import mimetypes
from typing import TYPE_CHECKING, Any, Tuple, List, cast, overload, Optional, Set
from typing import Type, TypeVar, Union

import uno

from com.sun.star.uno import XInterface
from com.sun.star.awt import XToolkit
from com.sun.star.beans import XHierarchicalPropertySet
from com.sun.star.beans import XPropertySet
from com.sun.star.container import XContentEnumerationAccess
from com.sun.star.container import XNameAccess
from com.sun.star.container import XNameContainer
from com.sun.star.deployment import XPackageInformationProvider
from com.sun.star.document import XDocumentPropertiesSupplier
from com.sun.star.document import XTypeDetection
from com.sun.star.frame import XModule
from com.sun.star.lang import XMultiServiceFactory
from com.sun.star.lang import XServiceInfo
from com.sun.star.lang import XTypeProvider
from com.sun.star.reflection import XIdlReflection
from com.sun.star.style import XStyleFamiliesSupplier
from com.sun.star.util import XChangesBatch


from ooo.dyn.beans.property_value import PropertyValue
from ooo.dyn.beans.property_concept import PropertyConceptEnum
from ooo.dyn.beans.the_introspection import theIntrospection
from ooo.dyn.lang.locale import Locale  # struct

from ooodev.utils.decorator.deprecated import deprecated
from ooodev.loader.inst.service import Service as LoService
from ooodev.utils import date_time_util as mDate
from ooodev.utils import file_io as mFileIO
from ooodev.loader import lo as mLo
from ooodev.utils import props as mProps
from ooodev.units import unit_convert as mConvert
from ooodev.events.args.event_args import EventArgs
from ooodev.events.event_singleton import _Events
from ooodev.events.lo_named_event import LoNamedEvent
from ooodev.exceptions import ex as mEx
from ooodev.meta.static_meta import StaticProperty, classproperty
from ooodev.utils.kind.info_paths_kind import InfoPathsKind as InfoPathsKind
from ooodev.utils.sys_info import SysInfo
from ooodev.proto import uno_proto

if TYPE_CHECKING:
    from com.sun.star.awt import FontDescriptor
    from com.sun.star.beans import XPropertyContainer
    from com.sun.star.document import XDocumentProperties
    from com.sun.star.reflection import XIdlMethod
    from .type_var import PathOrStr
else:
    PathOrStr = Any

if sys.version_info >= (3, 10):
    from typing import TypeGuard, TypeAlias
else:
    from typing_extensions import TypeGuard, TypeAlias


_T = TypeVar("_T")
ClassInfo: TypeAlias = Union[Type[_T], Tuple["ClassInfo[_T]", ...]]


[docs]class Info(metaclass=StaticProperty): REG_MOD_FNM = "registrymodifications.xcu" NODE_PRODUCT = "/org.openoffice.Setup/Product" NODE_L10N = "/org.openoffice.Setup/L10N" NODE_OFFICE = "/org.openoffice.Setup/Office" NODE_PATHS = (NODE_PRODUCT, NODE_L10N) # MIME_FNM = "mime.types" # from https://wiki.openoffice.org/wiki/Documentation/DevGuide/OfficeDev/Properties_of_a_Filter
[docs] class Filter(IntFlag): """ Every filter inside LibreOffice is specified by the properties of this enum. """ IMPORT = 0x00000001 """This filter is used only for internal purposes and so can be used only in API calls. Users won't see it ever. """ EXPORT = 0x00000002 """The filter supports the service com.sun.star.document.ExportFilter. It will be shown in the dialog "File-Export". If the filter also has the "IMPORT" flag set, it will be shown in the dialog "File-Save". This makes sure that a format that a user chooses in the save dialog can be loaded again. The same is not guaranteed for a format chosen in "File-Export". """ TEMPLATE = 0x00000004 """Filter denotes a template filter (means, by default all documents opened by it become an "untitled" one) """ INTERNAL = 0x00000008 """This filter is used only for internal purposes and so can be used only in API calls. Users won't see it ever. """ TEMPLATEPATH = 0x00000010 """Must always be set together with "TEMPLATE" to make this feature flag work; soon becoming deprecated""" OWN = 0x00000020 """The filter is a native Apache OpenOffice format (ODF or otherwise).""" ALIEN = 0x00000040 """The filter may lose some information upon saving. """ DEFAULT = 0x00000100 """This is the "best" filter for the document type it works on that is guaranteed not so lose any data on export. By default this filter will be used or suggested for every storing process unless the user has chosen a different default filter in the options dialog.""" SUPPORTSSELECTION = 0x00000400 """Filter can export only the selected part of a document. This information enables Apache OpenOffice to enable a corresponding check box in the "File-Export" dialog.""" NOTINFILEDIALOG = 0x00001000 """This filter will not be shown in the file dialog's filter list""" NOTINCHOOSER = 0x00002000 """This filter will not be shown in the dialog box for choosing a filter in case Apache OpenOffice was not able to detect one""" READONLY = 0x00010000 """All documents imported by this filter will automatically be in read-only state""" PREFERRED = 0x10000000 """The filter is preferred in case of multiple filters for the same file type exist in the configuration""" THIRDPARTYFILTER = 0x00080000 """ The filter is a UNO component filter, as opposed to the internal C++ filters. This is an artifact that will vanish over time. AKA: 3RDPARTYFILTER """
[docs] class RegPropKind(Enum): PROPERTY = 1 VALUE = 2
[docs] @staticmethod def get_fonts() -> Tuple[FontDescriptor, ...]: """ Gets fonts. |lo_unsafe| Returns: Tuple[FontDescriptor, ...]: Font Descriptors """ xtoolkit = mLo.Lo.create_instance_mcf(XToolkit, "com.sun.star.awt.Toolkit", raise_err=True) device = xtoolkit.createScreenCompatibleDevice(0, 0) if device is None: mLo.Lo.print("Could not access graphical output device") return () return device.getFontDescriptors()
[docs] @classmethod def get_font_names(cls) -> List[str]: """ Gets font names. |lo_unsafe| Returns: List[str]: Font names """ fds = cls.get_fonts() if fds is None: return [] names_set = {fd.Name for fd in fds} return sorted(names_set)
[docs] @classmethod def get_font_descriptor(cls, name: str, style: str) -> FontDescriptor | None: """ Gets font descriptor for a font name with a font style such as ``Bold Italic``. This method is useful for obtaining a font descriptor for a specific font name and style, which can be used for various purposes such as setting the font of text in a document or application. |lo_unsafe| Args: name (str): Font Name style (str): Font Style Returns: FontDescriptor: Instance if found; Otherwise None. .. versionadded:: 0.9.0 """ if not name: return None if not style: return None style = style.casefold() fds = cls.get_fonts() return next( (fd for fd in fds if fd.Name == name and fd.StyleName.casefold() == style), None, )
[docs] @staticmethod def get_font_mono_name() -> str: """ Gets a general font such as ``Courier New`` (windows) or ``Liberation Mono``. |lo_safe| Returns: str: Font Name See Also: `Fonts <https://wiki.documentfoundation.org/Fonts>`_ on Document Foundation’s wiki """ pf = SysInfo.get_platform() if pf == SysInfo.PlatformEnum.WINDOWS: return "Courier New" else: return "Liberation Mono" # Metrically compatible with Courier New
[docs] @staticmethod def get_font_general_name() -> str: """ Gets a general font such as ``Times New Roman`` (windows) or ``Liberation Serif``. |lo_safe| Returns: str: Font Name See Also: `Fonts <https://wiki.documentfoundation.org/Fonts>`_ on Document Foundation’s wiki """ pf = SysInfo.get_platform() if pf == SysInfo.PlatformEnum.WINDOWS: return "Times New Roman" else: return "Liberation Serif" # Metrically compatible with Times New Roman
[docs] @classmethod def get_reg_mods_path(cls) -> str: """ Get registered modifications path. |lo_unsafe| Returns: str: registered modifications path """ user_cfg_dir = mFileIO.FileIO.url_to_path(cls.get_paths("UserConfig")) parent_path = user_cfg_dir.parent return str(parent_path / cls.REG_MOD_FNM)
@overload @classmethod def get_reg_item_prop(cls, item: str) -> str: ... @overload @classmethod def get_reg_item_prop(cls, item: str, prop: str) -> str: ... @overload @classmethod def get_reg_item_prop(cls, item: str, prop: str, node: str) -> str: ... @overload @classmethod def get_reg_item_prop(cls, item: str, *, kind: Info.RegPropKind) -> str: ... @overload @classmethod def get_reg_item_prop(cls, item: str, *, kind: Info.RegPropKind, idx: int) -> str: ... @overload @classmethod def get_reg_item_prop(cls, item: str, prop: str, *, idx: int) -> str: ... @overload @classmethod def get_reg_item_prop(cls, item: str, prop: str, node: str, kind: Info.RegPropKind) -> str: ...
[docs] @classmethod def get_reg_item_prop( cls, item: str, prop: str = "", node: Optional[str] = None, kind: Info.RegPropKind = RegPropKind.PROPERTY, idx: int = 0, ) -> str: """ Gets value from ``registrymodifications.xcu``. |lo_safe| Args: item (str): item name prop (str): property value node (str): node kind (Info.RegPropKind): property or value idx: (int): index of value to return Raises: ValueError: if unable to get value Returns: str: value from ``registrymodifications.xcu``. e.g. ``Writer/MailMergeWizard``, ``None``, ``MailAddress`` Note: Often is it not necessary to get value from registry. Which is not available in macro mode. Often the configuration option can be gotten via :py:meth:`~.info.Info.get_config`. See Also: :py:meth:`~.info.Info.get_config` """ # sourcery skip: merge-else-if-into-elif, raise-specific-error, remove-unnecessary-else, swap-if-else-branches # return value from "registrymodifications.xcu" # windows C:\Users\user\AppData\Roaming\LibreOffice\4\user\registrymodifications.xcu # e.g. val = Info.get_reg_item_prop("Calc/Calculate/Other", "DecimalPlaces") # e.g. val = Info.get_reg_item_prop("Logging/Settings", "LogLevel", "org.openoffice.logging.sdbc.DriverManager") # e.g. val = Info.get_reg_item_prop("Writer/Layout/Other/TabStop", kind=Info.RegPropKind.VALUE, idx=1) # This xpath doesn't deal with all cases in the XCU file, which sometimes # has many node levels between the item and the prop if mLo.Lo.is_macro_mode: raise mEx.NotSupportedMacroModeError("get_reg_item_prop() is not supported from a macro") try: import xml.etree.ElementTree as ET # type: ignore[import] except ImportError as e: raise Exception("get_reg_item_prop() requires xml python package") from e try: fnm = cls.get_reg_mods_path() tree = ET.parse(fnm) xpath = "" if not prop: kind = Info.RegPropKind.VALUE if node is None: if kind == Info.RegPropKind.PROPERTY: xpath = f'.//item[@oor:path="/org.openoffice.Office.{item}"]/prop[@oor:name="{prop}"]/value' elif kind == Info.RegPropKind.VALUE: xpath = f'.//item[@oor:path="/org.openoffice.Office.{item}"]/value' else: if kind == Info.RegPropKind.PROPERTY: xpath = f'.//item[@oor:path="/org.openoffice.Office.{item}"]/node[@oor:name="{node}"]/prop[@oor:name="{prop}"]/value' elif kind == Info.RegPropKind.VALUE: xpath = f".//item[/prop[@oor:name='{item}']/node[@oor:name='{node}']/value" if not xpath: raise ValueError("Unable to create xpath. Recheck arguments.") value = tree.findall(xpath, namespaces={"oor": "http://openoffice.org/2001/registry"}) if not value: raise Exception("Item Property not found") else: try: first = value[idx] except IndexError: raise if first.text is None: raise Exception("Item Property is None (?)") value = first.text.strip() if value == "": raise Exception("Item Property is white space (?)") return value except Exception as e: raise ValueError("Unable to get value from registrymodifications.xcu") from e
@overload @classmethod def get_config(cls, node_str: str) -> Any: """ Get config. |lo_unsafe| Args: node_str (str): node string Raises: ConfigError: if unable to get config Returns: object: config """ ... @overload @classmethod def get_config(cls, node_str: str, node_path: str) -> Any: """ Get config. |lo_unsafe| Args: node_str (str): node string node_path (str): node_path Raises: ConfigError: if unable to get config Returns: object: config """ ...
[docs] @classmethod def get_config(cls, node_str: str, node_path: Optional[str] = None) -> Any: """ Get config. |lo_unsafe| Args: node_str (str): node string node_path (str): node_path Raises: ConfigError: if unable to get config Returns: object: config See Also: :ref:`ch03` Example: .. code-block:: python props = mLo.Lo.qi( XPropertySet, mInfo.Info.get_config(node_str="Other", node_path="/org.openoffice.Office.Writer/Layout/"), ) ts_val = props.getPropertyValue("TabStop") # int val """ # props = Lo.qi(XPropertySet, Info.get_config(node_str='Data', node_path="/org.openoffice.UserProfile/")) # Props.show_obj_props("User Data", props) try: if node_path is None: return cls._get_config2(node_str=node_str) return cls._get_config1(node_str=node_str, node_path=node_path) except Exception as e: msg = f"Unable to get configuration for '{node_str}'" if node_path is not None: msg += f" with path: '{node_path}'" raise mEx.ConfigError(msg) from e
@classmethod def _get_config1(cls, node_str: str, node_path: str): """LO UN-Safe Method""" props = cls.get_config_props(node_path) return mProps.Props.get(props, node_str) @classmethod def _get_config2(cls, node_str: str) -> Any: """LO UN-Safe Method""" for node_path in cls.NODE_PATHS: with contextlib.suppress(mEx.PropertyNotFoundError): return cls._get_config1(node_str=node_str, node_path=node_path) raise mEx.ConfigError(f"{node_str} not found in common node paths")
[docs] @staticmethod def get_config_props(node_path: str) -> XPropertySet: """ Get config properties. |lo_unsafe| Args: node_path (str): nod path Raises: PropertyError: if unable to get get property set Returns: XPropertySet: Property set """ try: con_prov = mLo.Lo.create_instance_mcf( XMultiServiceFactory, "com.sun.star.configuration.ConfigurationProvider", raise_err=True, ) p = mProps.Props.make_props(nodepath=node_path) ca = con_prov.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess", p) return mLo.Lo.qi(XPropertySet, ca, True) except Exception as e: raise mEx.PropertyError(node_path, f"Unable to access config properties for\n\n '{node_path}'") from e
[docs] @staticmethod def get_paths(setting: str | InfoPathsKind) -> str: """ Gets access to LO's predefined paths. |lo_unsafe| Args: setting (str | InfoPathsKind): property value Raises: ValueError: if unable to get paths Returns: str: paths Note: There are two different groups of properties. One group stores only a single path and the other group stores two or more paths - separated by a semicolon. Some setting values (as listed in the OpenOffice docs for PathSettings) are found in :py:class:`~.kind.info_paths_kind.InfoPathsKind` See Also: - :py:class:`~.kind.info_paths_kind.InfoPathsKind` - :py:meth:`Info.get_dirs` - :ref:`ch03` - `Wiki Path Settings <https://wiki.openoffice.org/w/index.php?title=Documentation/DevGuide/OfficeDev/Path_Settings>`_ """ # access LO's predefined paths. There are two different groups of properties. # One group stores only a single path and the other group stores two or # more paths - separated by a semicolon. See # https://wiki.openoffice.org/w/index.php?title=Documentation/DevGuide/OfficeDev/Path_Settings # Some setting values (as listed in the OpenOffice docs for PathSettings): # Addin, AutoCorrect, AutoText, Backup, Basic, Bitmap, # Config, Dictionary, Favorite, Filter, Gallery, # Graphic, Help, Linguistic, Module, Palette, Plugin, # Storage, Temp, Template, UIConfig, UserConfig, # UserDictionary (deprecated), Work # Replaced by thePathSetting in LibreOffice 4.3 try: prop_set = mLo.Lo.create_instance_mcf(XPropertySet, "com.sun.star.util.PathSettings", raise_err=True) result = prop_set.getPropertyValue(str(setting)) if result is None: raise ValueError(f"getPropertyValue() for {setting} yielded None") return str(result) except Exception as e: raise ValueError(f"Could not find paths for: {setting}") from e
[docs] @classmethod def get_dirs(cls, setting: str) -> List[Path]: """ Gets dirs paths from settings. |lo_unsafe| Args: setting (str): setting Returns: List[str]: List of paths See Also: :py:meth:`~.Info.get_paths` `Wiki Path Settings <https://wiki.openoffice.org/w/index.php?title=Documentation/DevGuide/OfficeDev/Path_Settings>`_ .. versionchanged:: 0.11.14 - Added support for expanding macros. """ try: paths = cls.get_paths(setting) except ValueError: mLo.Lo.print(f"Could not find paths for '{setting}'") return [] paths_arr = paths.split(";") if len(paths_arr) == 0: mLo.Lo.print(f"Could not split paths for '{setting}'") return [mFileIO.FileIO.uri_to_path(mFileIO.FileIO.uri_absolute(mFileIO.FileIO.expand_macro(paths)))] return [ mFileIO.FileIO.uri_to_path(mFileIO.FileIO.uri_absolute(mFileIO.FileIO.expand_macro(el))) for el in paths_arr ]
[docs] @classmethod def get_office_dir(cls) -> str: """ Gets file path to the office dir. e.g. ``"C:\\Program Files (x86)\\LibreOffice 7"``. |lo_unsafe| Raises: ValueError: if unable to obtain office path. Returns: str: Path as string See Also: :ref:`ch03` """ try: addin_dir = cls.get_paths("Addin") # e.g. 'file:///C:/Program%20Files/LibreOffice/program/../program/addin addin_path = str(mFileIO.FileIO.uri_to_path(addin_dir)) # e.g. C:\Program%20Files\LibreOffice\program\addin try: idx = addin_path.index("program") except ValueError: mLo.Lo.print("Could not extract office path") return addin_path p = Path(addin_path[:idx]) # e.g. 'C:\Program%20Files\LibreOffice\ return str(p) except Exception as e: raise ValueError("Unable to get office dir") from e
[docs] @classmethod def get_office_theme(cls) -> str: """ Gets the Current LibreOffice Theme. |lo_unsafe| Returns: str: LibreOffice Theme Name such as ``LibreOffice Dark`` Note: Older Version of LibreOffice will return empty string. .. versionadded:: 0.9.1 """ try: return str( cls.get_config( node_str="CurrentColorScheme", node_path="/org.openoffice.Office.ExtendedColorScheme/ExtendedColorScheme", ) ) except mEx.ConfigError: # most likely pre LO 7.4 return ""
[docs] @classmethod def is_office_theme(cls, name: str) -> bool: """ Gets if the theme is available in LibreOffice. |lo_unsafe| Returns: bool: LibreOffice Theme Name such as ``My Dark Theme`` Note: Older Version of LibreOffice will return ``False``. .. versionadded:: 0.50.0 """ if not name: return False try: props = cls.get_config_props(f"/org.openoffice.Office.UI/ColorScheme/ColorSchemes/{name}") return props.Name == name # type: ignore except mEx.PropertyError: # most likely pre LO 7.4 return False
[docs] @classmethod def create_configuration_view(cls, path: str) -> XHierarchicalPropertySet: """ Create Configuration View. |lo_unsafe| Args: path (str): path Raises: ConfigError: if unable to create configuration view Returns: XHierarchicalPropertySet: Property Set """ try: con_prov = mLo.Lo.create_instance_mcf( XMultiServiceFactory, "com.sun.star.configuration.ConfigurationProvider" ) if con_prov is None: raise mEx.MissingInterfaceError(XMultiServiceFactory) _props = mProps.Props.make_props(nodepath=path) root = con_prov.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess", _props) # cls.show_services(obj_name="ConfigurationAccess", obj=root) ps = mLo.Lo.qi(XHierarchicalPropertySet, root) if ps is None: raise mEx.MissingInterfaceError(XHierarchicalPropertySet) return ps except Exception as e: raise mEx.ConfigError(f"Unable to get configuration view for '{path}'") from e
# =================== update configuration settings ================
[docs] @staticmethod def set_config_props(node_path: str) -> XPropertySet: """ Get config properties. |lo_unsafe| Args: node_path (str): Node path of properties Raises: mEx.ConfigError: if Unable to get config properties Returns: XPropertySet: Property Set """ try: con_prov = mLo.Lo.create_instance_mcf( XMultiServiceFactory, "com.sun.star.configuration.ConfigurationProvider" ) if con_prov is None: raise mEx.MissingInterfaceError(XMultiServiceFactory) _props = mProps.Props.make_props(nodepath=node_path) ca = con_prov.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess", _props) ps = mLo.Lo.qi(XPropertySet, ca) if ps is None: raise mEx.MissingInterfaceError(XPropertySet) return ps except Exception as e: raise mEx.ConfigError(f"Unable to set configuration property for '{node_path}'") from e
[docs] @classmethod def set_config(cls, node_path: str, node_str: str, val: Any) -> bool: """ Sets config. |lo_unsafe| Args: node_path (str): node path node_str (str): node name val (object): node value Returns: bool: True on success; Otherwise, False """ with contextlib.suppress(Exception): props = cls.set_config_props(node_path=node_path) if props is None: return False mProps.Props.set_property(props, node_str, val) secure_change = mLo.Lo.qi(XChangesBatch, props) if secure_change is None: raise mEx.MissingInterfaceError(XChangesBatch) secure_change.commitChanges() return True return False
# =================== getting info about a document ====================
[docs] @staticmethod def get_name(fnm: PathOrStr) -> str: """ Gets the file's name from the supplied string minus the extension. |lo_safe| Args: fnm (PathOrStr): File path Raises: ValueError: If fnm is empty string ValueError: If fnm is not a file Returns: str: File name minus the extension """ if fnm == "": raise ValueError("Empty string") p = Path(fnm) if not p.is_file(): raise ValueError(f"Not a file: '{fnm}'") if not p.suffix: mLo.Lo.print(f"No extension found for '{fnm}'") return p.stem return p.stem
[docs] @staticmethod def get_ext(fnm: PathOrStr) -> str | None: """ Gets file extension without the ``.``. |lo_safe| Args: fnm (PathOrStr): file path Raises: ValueError: If fnm is empty string Returns: str | None: Extension if Found; Otherwise, None """ return mFileIO.FileIO.get_ext(fnm)
[docs] @staticmethod def get_unique_fnm(fnm: PathOrStr) -> str: """ If a file called fnm already exists, then a number is added to the name so the filename is unique. |lo_safe| Args: fnm (str): file path Returns: str: unique file path """ p = Path(fnm) fname = p.stem ext = p.suffix i = 1 while p.exists(): name = f"{fname}{i}{ext}" p = p.parent / name i += 1 return str(p)
[docs] @staticmethod def get_doc_type(fnm: PathOrStr) -> str: """ Gets doc type from file path. |lo_unsafe| Args: fnm (PathOrStr): File Path Raises: ValueError: if Unable to get doc type Returns: str: Doc Type. """ try: x_detect = mLo.Lo.create_instance_mcf( XTypeDetection, "com.sun.star.document.TypeDetection", raise_err=True ) if not mFileIO.FileIO.is_openable(fnm): raise mEx.UnOpenableError(fnm) url_str = str(mFileIO.FileIO.fnm_to_url(fnm)) media_desc = (mProps.Props.make_prop_value(name="URL", value=url_str),) # even thought queryTypeByDescriptor reports to return a string # for some reason I am getting a tuple with the first value as the expected result. result = x_detect.queryTypeByDescriptor(media_desc, True) if result is None: raise mEx.UnKnownError("queryTypeByDescriptor() is an unknown result") return result if isinstance(result, str) else result[0] except Exception as e: raise ValueError(f"unable to get doc type for '{fnm}'") from e
[docs] @classmethod def report_doc_type(cls, doc: Any) -> mLo.Lo.DocType: """ Prints doc type to console and return doc type. |lo_safe| Args: doc (object): office document Returns: Lo.DocType: Document type. """ doc_type = mLo.Lo.DocType.UNKNOWN if cls.is_doc_type(obj=doc, doc_type=mLo.Lo.Service.WRITER): mLo.Lo.print("A Writer document") doc_type = mLo.Lo.DocType.WRITER elif cls.is_doc_type(obj=doc, doc_type=mLo.Lo.Service.IMPRESS): mLo.Lo.print("A Impress document") doc_type = mLo.Lo.DocType.IMPRESS elif cls.is_doc_type(obj=doc, doc_type=mLo.Lo.Service.DRAW): mLo.Lo.print("A Draw document") doc_type = mLo.Lo.DocType.DRAW elif cls.is_doc_type(obj=doc, doc_type=mLo.Lo.Service.CALC): mLo.Lo.print("A Calc document") doc_type = mLo.Lo.DocType.CALC elif cls.is_doc_type(obj=doc, doc_type=mLo.Lo.Service.BASE): mLo.Lo.print("A Base document") doc_type = mLo.Lo.DocType.BASE elif cls.is_doc_type(obj=doc, doc_type=mLo.Lo.Service.MATH): mLo.Lo.print("A Math document") doc_type = mLo.Lo.DocType.MATH else: mLo.Lo.print("Unknown document") return doc_type
[docs] @classmethod def doc_type_service(cls, doc: Any) -> mLo.Lo.Service: """ Prints service type to console and return service type. |lo_safe| Args: doc (Any): office document Returns: Lo.Service: Service type """ if cls.is_doc_type(obj=doc, doc_type=LoService.WRITER): mLo.Lo.print("A Writer document") return mLo.Lo.Service.WRITER elif cls.is_doc_type(obj=doc, doc_type=LoService.IMPRESS): mLo.Lo.print("A Impress document") return mLo.Lo.Service.IMPRESS elif cls.is_doc_type(obj=doc, doc_type=LoService.DRAW): mLo.Lo.print("A Draw document") return mLo.Lo.Service.DRAW elif cls.is_doc_type(obj=doc, doc_type=LoService.CALC): mLo.Lo.print("A Calc document") return mLo.Lo.Service.CALC elif cls.is_doc_type(obj=doc, doc_type=LoService.BASE): mLo.Lo.print("A Base document") return mLo.Lo.Service.BASE elif cls.is_doc_type(obj=doc, doc_type=LoService.MATH): mLo.Lo.print("A Math document") return mLo.Lo.Service.MATH else: mLo.Lo.print("Unknown document") return mLo.Lo.Service.UNKNOWN
[docs] @classmethod def is_doc_type(cls, obj: Any, doc_type: LoService | str) -> bool: """ Gets if doc is a particular doc type. |lo_safe| Args: obj (object): office document doc_type (Service, str): Doc type or service name such as ``com.sun.star.text.TextDocument``. Returns: bool: ``True`` if obj matches; Otherwise, ``False`` """ return cls.support_service(obj, doc_type)
[docs] @staticmethod def get_implementation_name(obj: Any) -> str: """ Gets implementation name such as ``com.sun.star.comp.deployment.PackageInformationProvider``. |lo_safe| Args: obj (object): uno object that implements XServiceInfo Raises: ValueError: if unable to get implementation name Returns: str: implementation name """ try: si = mLo.Lo.qi(XServiceInfo, obj, True) return si.getImplementationName() except Exception as e: raise ValueError("Could not get service information") from e
[docs] @staticmethod def get_identifier(obj: Any) -> str: """ Gets identifier name such as ``com.sun.star.text.TextDocument``. |lo_safe| Args: obj (object): uno object that implements XModule Raises: ValueError: if unable to get identifier name Returns: str: identifier name """ try: x_mod = mLo.Lo.qi(XModule, obj, True) return x_mod.getIdentifier() except Exception as e: raise ValueError("Could not get service information") from e
[docs] @staticmethod def get_mime_type(fnm: PathOrStr) -> str: """ Get mime type for a file path. |lo_safe| Args: fnm (PathOrStr): file path Returns: str: Mime type of file if found. Defaults to 'application/octet-stream' """ default = "application/octet-stream" mt = mimetypes.guess_type(fnm) if mt is None: mLo.Lo.print("unable to find mimetype") return default if mt[0] is None: mLo.Lo.print("unable to find mimetype") return default return str(mt[0])
[docs] @staticmethod def mime_doc_type(mime_type: str) -> mLo.Lo.DocType: """ Gets document type from mime type. |lo_safe| Args: mime_type (str): mime type Returns: Lo.DocType: Document type. If mime_type is unknown then 'DocType.UNKNOWN' """ if mime_type is None or not mime_type: return mLo.Lo.DocType.UNKNOWN if "vnd.oasis.opendocument.text" in mime_type: return mLo.Lo.DocType.WRITER if "vnd.oasis.opendocument.base" in mime_type: return mLo.Lo.DocType.BASE if "vnd.oasis.opendocument.spreadsheet" in mime_type: return mLo.Lo.DocType.CALC if ( "vnd.oasis.opendocument.graphics" in mime_type or "vnd.oasis.opendocument.image" in mime_type or "vnd.oasis.opendocument.chart" in mime_type ): return mLo.Lo.DocType.DRAW if "vnd.oasis.opendocument.presentation" in mime_type: return mLo.Lo.DocType.IMPRESS if "vnd.oasis.opendocument.formula" in mime_type: return mLo.Lo.DocType.MATH return mLo.Lo.DocType.UNKNOWN
[docs] @staticmethod def is_image_mime(mime_type: str) -> bool: """ Gets if mime-type is a known image type. |lo_safe| Args: mime_type (str): mime type e.g. ``application/x-openoffice-bitmap`` Returns: bool: True if known mime-type; Otherwise False """ if mime_type is None or not mime_type: return False if mime_type.startswith("image/"): return True return bool(mime_type.startswith("application/x-openoffice-bitmap"))
# ------------------------ services, interfaces, methods info ---------------------- @overload @classmethod def get_service_names(cls) -> List[str]: """ Gets service names. |lo_unsafe| Returns: List[str]: List of service names. """ ... @overload @classmethod def get_service_names(cls, service_name: str) -> List[str]: """ Gets service names. |lo_unsafe| Args: service_name (str): service name. Returns: List[str]: List of service names. """ ...
[docs] @classmethod def get_service_names(cls, service_name: Optional[str] = None) -> List[str]: """ Gets service names. |lo_unsafe| Args: service_name (str): service name. Raises: Exception: If error occurs. Returns: List[str]: List of service names. """ if service_name is None: return cls._get_service_names1() return cls._get_service_names2(service_name=service_name)
@staticmethod def _get_service_names1() -> List[str]: """LO UN-safe method""" mc_factory = mLo.Lo.get_component_factory() if mc_factory is None: return [] return sorted(mc_factory.getAvailableServiceNames()) @staticmethod def _get_service_names2(service_name: str) -> List[str]: """LO Un-safe method""" names: List[str] = [] try: enum_access = mLo.Lo.qi(XContentEnumerationAccess, mLo.Lo.get_component_factory(), True) x_enum = enum_access.createContentEnumeration(service_name) while x_enum.hasMoreElements(): si = mLo.Lo.qi(XServiceInfo, x_enum.nextElement(), True) names.append(si.getImplementationName()) except Exception as e: mLo.Lo.print(f"Could not collect service names for: {service_name}") raise e if not names: mLo.Lo.print(f"No service names found for: {service_name}") return names names.sort() return names
[docs] @staticmethod def get_services(obj: Any) -> List[str]: """ Gets service names. |lo_safe| Args: obj (object): obj that implements XServiceInfo Returns: List[str]: service names """ with contextlib.suppress(AttributeError): return sorted(obj.SupportedServiceNames) # type: ignore try: si = mLo.Lo.qi(XServiceInfo, obj, True) names = si.getSupportedServiceNames() return sorted(names) except Exception as e: mLo.Lo.print("Unable to get services") mLo.Lo.print(f" {e}") raise e
[docs] @classmethod def show_services(cls, obj_name: str, obj: Any) -> None: """ Prints services to console. |lo_safe| Args: obj_name (str): service name obj (object): obj that implements XServiceInfo """ services = cls.get_services(obj=obj) if services is None: print(f"No supported services found for {obj_name}") return print(f"{obj_name} Supported Services ({len(services)})") for service in services: print(f"'{service}'")
[docs] @staticmethod def support_service(obj: Any, *service: str | LoService) -> bool: """ Gets if ``obj`` supports a service. |lo_safe| Args: obj (object): Object to check for supported service *service (str, LoService): Variable length argument list of UNO namespace strings such as ``com.sun.star.configuration.GroupAccess`` Returns: bool: ``True`` if ``obj`` supports any passed in service; Otherwise, ``False`` """ if obj is None: return False if not service: return False result = False try: if not hasattr(obj, "supportsService"): return False si = cast(XServiceInfo, obj) for srv in service: result = si.supportsService(str(srv)) # type: ignore if result: break except Exception as e: mLo.Lo.print("Errors ocurred in support_service(). Returning False") mLo.Lo.print(f" {e}") return result
[docs] @staticmethod def get_available_services(obj: Any) -> List[str]: """ Gets available services for obj. |lo_safe| Args: obj (object): obj that implements ``XMultiServiceFactory`` interface. Raises: Exception: If unable to get services. Returns: List[str]: List of services. .. versionchanged:: 0.34.0 Now also supports XServiceInfo """ # sourcery skip: raise-specific-error services: List[str] = [] try: sf = mLo.Lo.qi(XMultiServiceFactory, obj) if sf is None: sf = mLo.Lo.qi(XServiceInfo, obj, True) service_names = sf.getSupportedServiceNames() else: service_names = sf.getAvailableServiceNames() services.extend(service_names) services.sort() except Exception as e: mLo.Lo.print(e) raise Exception() from e return services
[docs] @staticmethod def get_interface_types(target: object) -> Tuple[object, ...]: """ Get interface types. |lo_safe| Args: target (object): object that implements XTypeProvider interface. Raises: Exception: If unable to get services. Returns: Tuple[object, ...]: Tuple of interfaces. """ # sourcery skip: raise-specific-error try: tp = mLo.Lo.qi(XTypeProvider, target, True) return tp.getTypes() except Exception as e: mLo.Lo.print("Unable to get interface types") mLo.Lo.print(f" {e}") raise Exception() from e
@overload @classmethod def get_interfaces(cls, target: object) -> List[str]: """ Gets interfaces. |lo_safe| Args: target: (object): object that implements XTypeProvider. Returns: List[str]: List of interfaces. """ ... @overload @classmethod def get_interfaces(cls, type_provider: XTypeProvider) -> List[str]: """ Gets interfaces. |lo_safe| Args: type_provider (XTypeProvider): type provider. Returns: List[str]: List of interfaces """ ...
[docs] @classmethod def get_interfaces(cls, *args, **kwargs) -> List[str]: """ Gets interfaces. |lo_safe| Args: target: (object): object that implements ``XTypeProvider``. type_provider (XTypeProvider): type provider. Raises: Exception: If unable to get interfaces. Returns: List[str]: List of interfaces. """ # sourcery skip: raise-specific-error ordered_keys = (1,) kargs_len = len(kwargs) count = len(args) + kargs_len def get_kwargs() -> dict: ka = {} if kargs_len == 0: return ka valid_keys = ("target", "typeProvider") check = all(key in valid_keys for key in kwargs) if not check: raise TypeError("get_interfaces() got an unexpected keyword argument") keys = ("target", "typeProvider") for key in keys: if key in kwargs: ka[1] = kwargs[key] break return ka if count != 1: raise TypeError("get_interfaces() got an invalid number of arguments") kargs = get_kwargs() for i, arg in enumerate(args): kargs[ordered_keys[i]] = arg try: if mLo.Lo.is_uno_interfaces(kargs[1], XTypeProvider): type_provider = cast(XTypeProvider, kargs[1]) else: type_provider = mLo.Lo.qi(XTypeProvider, kargs[1], True) types = cast(Tuple[uno.Type, ...], type_provider.getTypes()) # use a set to exclude duplicate names names_set: Set[str] = set() for t in types: names_set.add(t.typeName) type_names = sorted(list(names_set)) return type_names except Exception as e: mLo.Lo.print("Unable to get interfaces") mLo.Lo.print(f" {e}") raise Exception() from e
[docs] @classmethod def is_interface_obj(cls, obj: Any) -> bool: """ Gets is an object contains interfaces. |lo_safe| Args: obj (Any): Object to check. Returns: bool: ``True`` if obj contains interface; Otherwise, ``False``. """ if obj is None: return False x = mLo.Lo.qi(XInterface, obj) return x is not None
# with contextlib.suppress(mEx.MissingInterfaceError): # interfaces = cls.get_interfaces(obj) # result = len(interfaces) > 0 # return result
[docs] @staticmethod def is_struct(obj: Any) -> bool: """ Gets if an object is a UNO Struct. |lo_safe| Args: obj (Any): Object to check. Returns: bool: ``True`` if obj is Struct; Otherwise, ``False`` """ if obj is None: return False if not hasattr(obj, "typeName"): return False with contextlib.suppress(Exception): t = cast(uno_proto.UnoType, uno.getTypeByName(obj.typeName)) return t.typeClass.value == "STRUCT" return False
[docs] @classmethod def is_same(cls, obj1: Any, obj2: Any) -> bool: """ Determines if two Uno object are the same. |lo_safe| Args: obj1 (Any): First Uno object obj2 (Any): Second Uno Object Returns: bool: True if objects are the same; Otherwise False. """ # https://wiki.openoffice.org/wiki/Documentation/DevGuide/WritingUNO/XInterface # In C++, two objects are the same if their XInterface are the same. The queryInterface() for XInterface will have to # be called on both. In Java, check for the identity by calling the runtime function # com.sun.star.uni.UnoRuntime.areSame(). if cls.is_struct(obj1) and cls.is_struct(obj2): # types and attribute values must match if obj1.typeName != obj2.typeName: return False obj1_attrs = [s for s in dir(obj1.value) if not s.startswith("_")] return all(getattr(obj1, atr) == getattr(obj2, atr) for atr in obj1_attrs) elif cls.is_interface_obj(obj1) and cls.is_interface_obj(obj2): # https://ask.libreoffice.org/t/equalunoobjects-for-python/103855 # check if the same objects in memory id1 = id(obj1) id2 = id(obj2) if id1 == id2: return True return obj1 == obj2
[docs] @staticmethod def show_container_names(print_name: str, nc: XNameContainer): """ Prints Name Container elements to console. |lo_safe| Args: print_name (str): Name to display. Can be empty string. nc (XNameContainer): Name Container to print element names of. """ names = nc.getElementNames() if print_name: print(f"No. of Names in {print_name}: {len(names)}") else: print(f"No. of Names in Name Container: {len(names)}") for name in names: print(f" {name}") print()
[docs] @classmethod def show_interfaces(cls, obj_name: str, obj: Any) -> None: """ Prints interfaces in obj to console. |lo_safe| Args: obj_name (str): Name of object for printing obj (object): obj that contains interfaces. """ interfaces = cls.get_interfaces(obj) if not interfaces: print(f"No interfaces found for {obj_name}") return print(f"{obj_name} Interfaces ({len(interfaces)})") for s in interfaces: print(f" {s}")
[docs] @classmethod def show_enum_name_values(cls, obj: Any) -> None: """ Prints Enum Name and Values to console. |lo_safe| Args: obj (object): Object that contains enum values. Can be a Name such as ``com.sun.star.awt.MenuItemType`` or a UNO enum. """ if not obj: return from ooodev.utils.kind.enum_helper import EnumHelper name = "" try: if isinstance(obj, str): name = obj elif hasattr(obj, "typeName"): name = str(obj.typeName) enum_info = EnumHelper.get_enum_info(name) if not enum_info: print(f"No enum values found for {name}") return print(f"Enum Name: {name}") for k, v in enum_info.get_name_value_dict().items(): print(f" {k} = {v}") except Exception as e: print("Could not get enumeration") print(f" {e}")
[docs] @staticmethod def show_conversion_values(value: Any, frm: mConvert.UnitLength) -> None: """ Prints values of conversions to terminal. |lo_safe| Args: value (Any): Any numeric value frm (mConvert.Length): The unit type of ``value``. Note: Useful to help determine what different conversion of ``value`` are. .. versionadded:: 0.9.0 """ lengths = cast( List[mConvert.UnitLength], [ getattr(mConvert.UnitLength, x) for x in dir(mConvert.UnitLength) if x.isupper() and getattr(mConvert.UnitLength, x).value < mConvert.UnitLength.COUNT and getattr(mConvert.UnitLength, x).value >= 0 ], ) for length in lengths: try: result = mConvert.UnitConvert.convert(num=value, frm=frm, to=length) print(f"{length.name}:".ljust(10), round(result, 2)) except Exception: print(f'"{length.name}" does not convert')
[docs] @staticmethod def get_methods_obj(obj: Any, property_concept: PropertyConceptEnum | None = None) -> List[str]: """ Get Methods of an object such as a doc. |lo_safe| Args: obj (object): Object to get methods of. property_concept (PropertyConceptEnum | None, optional): Type of method to get. Defaults to PropertyConceptEnum.ALL. Raises: Exception: If unable to get Methods Returns: List[str]: List of method names found for obj """ # sourcery skip: raise-specific-error if property_concept is None: property_concept = PropertyConceptEnum.ALL try: intro = theIntrospection() # type: ignore result = intro.inspect(obj) methods = result.getMethods(int(property_concept)) lst = [meth.getName() for meth in methods] lst.sort() return lst except Exception as e: raise Exception("Could not get object Methods") from e
[docs] @staticmethod def get_methods(interface_name: str) -> List[str]: """ Get Interface Methods. |lo_unsafe| Args: interface_name (str): name of interface. Returns: List[str]: List of methods """ # sourcery skip: raise-specific-error # from com.sun.star.beans.PropertyConcept import ALL # ctx = XSCRIPTCONTEXT.getComponentContext() # smgr = ctx.getServiceManager() # intro = smgr.createInstance('com.sun.star.beans.Introspection') # document = XSCRIPTCONTEXT.getDocument() # result = intro.inspect(document) # methods = result.getMethods(ALL) # meth = methods[0] # meth is com.sun.star.reflection.XIdlMethod # meth.Name # queryInterface # # reflection = smgr.createInstanceWithContext('com.sun.star.reflection.CoreReflection', ctx) # See Also: https://tinyurl.com/y338vwhw#L492 # See Also: https://github.com/hanya/MRI/wiki/RunMRI#Python # See Also: https://tinyurl.com/y3m4tx9r#L268 reflection = mLo.Lo.create_instance_mcf( XIdlReflection, "com.sun.star.reflection.CoreReflection", raise_err=True ) fname = reflection.forName(interface_name) # returns type from name. if fname is None: mLo.Lo.print(f"Could not find the interface name: {interface_name}") return [] try: methods: Tuple[XIdlMethod, ...] = fname.getMethods() lst = [meth.getName() for meth in methods] lst.sort() return lst except Exception as e: raise Exception(f"Could not get Methods for: {interface_name}") from e
[docs] @classmethod def show_methods(cls, interface_name: str) -> None: """ Prints methods to console for an interface. |lo_unsafe| Args: interface_name (str): name of interface. """ methods = cls.get_methods(interface_name=interface_name) if len(methods) == 0: print(f"No methods found for '{interface_name}'") return print(f"{interface_name} Methods: {len(methods)}") for method in methods: print(f" {method}")
[docs] @classmethod def show_methods_obj(cls, obj: Any, property_concept: PropertyConceptEnum | None = None) -> None: """ Prints method to console for an object such as a doc. |lo_safe| Args: obj (object): Object to get methods of. property_concept (PropertyConceptEnum | None, optional): Type of method to get. Defaults to PropertyConceptEnum.ALL. """ methods = cls.get_methods_obj(obj=obj, property_concept=property_concept) if len(methods) == 0: print("No Object methods found") return print(f"Object Methods: {len(methods)}") for method in methods: print(f" {method}")
# -------------------------- style info --------------------------
[docs] @staticmethod def get_style_families(doc: Any) -> XNameAccess: """ Gets a list of style family names. |lo_safe| Args: doc (Any): office document. Raises: MissingInterfaceError: If Doc does not implement XStyleFamiliesSupplier interface. Exception: If unable to get style Families. Returns: XNameAccess: Style Families. """ # sourcery skip: raise-specific-error try: xsupplier = mLo.Lo.qi(XStyleFamiliesSupplier, doc) if xsupplier is None: raise mEx.MissingInterfaceError(XStyleFamiliesSupplier) return xsupplier.getStyleFamilies() except mEx.MissingInterfaceError: raise except Exception as e: raise Exception("Unable to get family style names") from e
[docs] @classmethod def get_style_family_names(cls, doc: Any) -> List[str]: """ Gets a sorted list of style family names. |lo_safe| Args: doc (Any): office document Raises: Exception: If unable to names. Returns: List[str]: List of style names """ # sourcery skip: raise-specific-error try: name_acc = cls.get_style_families(doc) names = name_acc.getElementNames() return sorted(names) except Exception as e: raise Exception("Unable to get family style names") from e
[docs] @classmethod def get_style_container(cls, doc: Any, family_style_name: str) -> XNameContainer: """ Gets style container of document for a family of styles. |lo_safe| Args: doc (Any): office document family_style_name (str): Family style name Raises: MissingInterfaceError: if unable to obtain XNameContainer interface Returns: XNameContainer: Style Family container """ name_acc = cls.get_style_families(doc) return mLo.Lo.qi(XNameContainer, name_acc.getByName(family_style_name), raise_err=True)
[docs] @classmethod def get_style_names(cls, doc: Any, family_style_name: str) -> List[str]: """ Gets a list of style names. |lo_safe| Args: doc (Any): office document. family_style_name (str): name of family style. Raises: Exception: If unable to access Style names. Returns: List[str]: List of style names. """ # sourcery skip: raise-specific-error try: style_container = cls.get_style_container(doc=doc, family_style_name=family_style_name) names = style_container.getElementNames() return sorted(names) except Exception as e: raise Exception("Could not access style names") from e
[docs] @classmethod def get_style_props(cls, doc: Any, family_style_name: str, prop_set_nm: str) -> XPropertySet: """ Get style properties for a family of styles. |lo_safe| Args: doc (Any): office document. family_style_name (str): name of family style. prop_set_nm (str): property set name. Raises: MissingInterfaceError: if a required interface cannot be obtained. Returns: XPropertySet: Property set. """ style_container = cls.get_style_container(doc, family_style_name) return mLo.Lo.qi(XPropertySet, style_container.getByName(prop_set_nm), True)
[docs] @classmethod def get_page_style_props(cls, doc: Any) -> XPropertySet: """ Gets style properties for page styles. |lo_safe| Args: doc (Any): office docs. Raises: MissingInterfaceError: if a required interface cannot be obtained. Returns: XPropertySet: property set. """ return cls.get_style_props(doc, "PageStyles", "Standard")
[docs] @classmethod def get_paragraph_style_props(cls, doc: Any) -> XPropertySet: """ Gets style properties for paragraph styles. |lo_safe| Args: doc (Any): office docs. Raises: MissingInterfaceError: if a required interface cannot be obtained. Returns: XPropertySet: property set. """ return cls.get_style_props(doc, "ParagraphStyles", "Standard")
# ----------------------------- document properties ----------------------
[docs] @classmethod def print_doc_properties(cls, doc: Any) -> None: """ Prints document properties to console. |lo_safe| Args: doc (Any): office document """ try: doc_props_supp = mLo.Lo.qi(XDocumentPropertiesSupplier, doc, True) dps = doc_props_supp.getDocumentProperties() cls.print_doc_props(dps=dps) ud_props = dps.getUserDefinedProperties() mProps.Props.show_obj_props("UserDefined Info", ud_props) except Exception as e: mLo.Lo.print("Unable to get doc properties") mLo.Lo.print(f" {e}") return
[docs] @classmethod def print_doc_props(cls, dps: XDocumentProperties) -> None: """ Prints doc properties to console. |lo_safe| Args: dps (XDocumentProperties): document properties. See Also: :py:meth:`~Info.print_doc_properties` """ print("Document Properties Info") print(f" Author: {dps.Author}") print(f" Title: {dps.Title}") print(f" Subject: {dps.Subject}") print(f" Description: {dps.Description}") print(f" Generator: {dps.Generator}") keys = dps.Keywords print(" Keywords: ") for keyword in keys: print(f" {keyword}") print(f" Modified by: {dps.ModifiedBy}") print(f" Printed by: {dps.PrintedBy}") print(f" Template Name: {dps.TemplateName}") print(f" Template URL: {dps.TemplateURL}") print(f" Autoload URL: {dps.AutoloadURL}") print(f" Default Target: {dps.DefaultTarget}") dps_lang = dps.Language loc = [ "unknown" if len(dps_lang.Language) == 0 else dps_lang.Language, "unknown" if len(dps_lang.Country) == 0 else dps_lang.Country, "unknown" if len(dps_lang.Variant) == 0 else dps_lang.Variant, ] print(f" Locale: {'; '.join(loc)}") print(f" Modification Date: {mDate.DateUtil.str_date_time(dps.ModificationDate)}") print(f" Creation Date: {mDate.DateUtil.str_date_time(dps.CreationDate)}") print(f" Print Date: {mDate.DateUtil.str_date_time(dps.PrintDate)}") print(f" Template Date: {mDate.DateUtil.str_date_time(dps.TemplateDate)}") doc_stats = dps.DocumentStatistics print(" Document statistics:") for nv in doc_stats: print(f" {nv.Name} = {nv.Value}") try: print(f" Autoload Secs: {dps.AutoloadSecs}") except Exception as e: print(f" Autoload Secs: {e}") try: print(f" Editing Cycles: {dps.EditingCycles}") except Exception as e: print(f" Editing Cycles: {e}") try: print(f" Editing Duration: {dps.EditingDuration}") except Exception as e: print(f" Editing Duration: {e}") print()
[docs] @staticmethod def set_doc_props(doc: Any, subject: str, title: str, author: str) -> None: """ Set document properties for subject, title, author. |lo_safe| Args: doc (Any): office document. subject (str): subject. title (str): title. author (str): author. Raises: PropertiesError: If unable to set properties. """ try: dp_supplier = mLo.Lo.qi(XDocumentPropertiesSupplier, doc, True) doc_props = dp_supplier.getDocumentProperties() doc_props.Subject = subject doc_props.Title = title doc_props.Author = author except Exception as e: raise mEx.PropertiesError("Unable to set doc properties") from e
[docs] @staticmethod def get_user_defined_props(doc: Any) -> XPropertyContainer: """ Gets user defined properties. |lo_safe| Args: doc (Any): office document. Raises: PropertiesError: if unable to access properties. Returns: XPropertyContainer: Property container. See Also: - :ref:`help_common_modules_info_get_user_defined_props`. """ try: dp_supplier = mLo.Lo.qi(XDocumentPropertiesSupplier, doc, True) dps = dp_supplier.getDocumentProperties() return dps.getUserDefinedProperties() except Exception as e: raise mEx.PropertiesError("Unable to get user defined props") from e
# ----------- installed package info -----------------
[docs] @staticmethod def get_pip() -> XPackageInformationProvider: """ Gets Package Information Provider. |lo_unsafe| Raises: MissingInterfaceError: if unable to obtain XPackageInformationProvider interface. Returns: XPackageInformationProvider: Package Information Provider. """ ctx = mLo.Lo.get_context() return mLo.Lo.qi( XPackageInformationProvider, ctx.getValueByName("/singletons/com.sun.star.deployment.PackageInformationProvider"), True, )
# return pip.get(mLo.Lo.get_context())
[docs] @classmethod def list_extensions(cls) -> None: """ Prints extensions to console. |lo_unsafe| """ try: pip = cls.get_pip() except mEx.MissingInterfaceError: print("No package info provider found") return exts_tbl = pip.getExtensionList() print("\nExtensions:") for i in range(len(exts_tbl)): print(f"{i+1}. ID: {exts_tbl[i][0]}") print(f" Version: {exts_tbl[i][1]}") print(f" Loc: {pip.getPackageLocation(exts_tbl[i][0])}") print()
[docs] @classmethod def get_extension_info(cls, id: str) -> Tuple[str, ...]: """ Gets info for an installed extension in LibreOffice. |lo_unsafe| Args: id (str): Extension id. Returns: Tuple[str, ...]: Extension info. """ try: pip = cls.get_pip() except mEx.MissingInterfaceError: mLo.Lo.print("No package info provider found") return () exts_tbl = pip.getExtensionList() mLo.Lo.print_table("Extension", exts_tbl) for el in exts_tbl: if el[0] == id: return el mLo.Lo.print(f"Extension {id} is not found") return ()
[docs] @classmethod def get_extension_loc(cls, id: str) -> str | None: """ Gets location for an installed extension in LibreOffice. |lo_unsafe| Args: id (str): Extension id. Returns: str | None: Extension location on success; Otherwise, None. """ try: pip = cls.get_pip() except mEx.MissingInterfaceError: mLo.Lo.print("No package info provider found") return None return pip.getPackageLocation(id)
[docs] @staticmethod def get_filter_names() -> Tuple[str, ...]: """ Gets filter names. |lo_unsafe| Returns: Tuple[str, ...]: Filter names. """ na = mLo.Lo.create_instance_mcf(XNameAccess, "com.sun.star.document.FilterFactory") if na is None: mLo.Lo.print("No Filter factory found") return () return na.getElementNames()
[docs] @staticmethod def get_filter_props(filter_nm: str) -> List[PropertyValue]: """ Gets filter properties. |lo_unsafe| Args: filter_nm (str): Filter Name. Returns: List[PropertyValue]: List of PropertyValue. """ na = mLo.Lo.create_instance_mcf(XNameAccess, "com.sun.star.document.FilterFactory") if na is None: mLo.Lo.print("No Filter factory found") return [] result = cast(Tuple[PropertyValue, ...], na.getByName(filter_nm)) if result is None: mLo.Lo.print(f"No props for filter: {filter_nm}") return [] return list(result)
[docs] @classmethod def is_import(cls, filter_flags: Info.Filter) -> bool: """ Gets if filter flags has ``Filter.IMPORT`` flag set. |lo_safe| Args: filter_flags (Filter): Flags. Returns: bool: ``True`` if flag is set; Otherwise, ``False``. """ return (filter_flags & cls.Filter.IMPORT) == cls.Filter.IMPORT
[docs] @classmethod def is_export(cls, filter_flags: Info.Filter) -> bool: """ Gets if filter flags has ``Filter.EXPORT`` flag set. |lo_safe| Args: filter_flags (Filter): Flags. Returns: bool: ``True`` if flag is set; Otherwise, ``False``. """ return (filter_flags & cls.Filter.EXPORT) == cls.Filter.EXPORT
[docs] @classmethod def is_template(cls, filter_flags: Info.Filter) -> bool: """ Gets if filter flags has ``Filter.TEMPLATE`` flag set. |lo_safe| Args: filter_flags (Filter): Flags. Returns: bool: ``True`` if flag is set; Otherwise, ``False``. """ return (filter_flags & cls.Filter.TEMPLATE) == cls.Filter.TEMPLATE
[docs] @classmethod def is_internal(cls, filter_flags: Info.Filter) -> bool: """ Gets if filter flags has ``Filter.INTERNAL`` flag set. |lo_safe| Args: filter_flags (Filter): Flags. Returns: bool: ``True`` if flag is set; Otherwise, ``False``. """ return (filter_flags & cls.Filter.INTERNAL) == cls.Filter.INTERNAL
[docs] @classmethod def is_template_path(cls, filter_flags: Info.Filter) -> bool: """ Gets if filter flags has ``Filter.TEMPLATEPATH`` flag set. |lo_safe| Args: filter_flags (Filter): Flags. Returns: bool: ``True`` if flag is set; Otherwise, ``False``. """ return (filter_flags & cls.Filter.TEMPLATEPATH) == cls.Filter.TEMPLATEPATH
[docs] @classmethod def is_own(cls, filter_flags: Info.Filter) -> bool: """ Gets if filter flags has ``Filter.OWN`` flag set. |lo_safe| Args: filter_flags (Filter): Flags. Returns: bool: ``True`` if flag is set; Otherwise, ``False``. """ return (filter_flags & cls.Filter.OWN) == cls.Filter.OWN
[docs] @classmethod def is_alien(cls, filter_flags: Info.Filter) -> bool: """ Gets if filter flags has ``Filter.ALIEN`` flag set. |lo_safe| Args: filter_flags (Filter): Flags. Returns: bool: ``True`` if flag is set; Otherwise, ``False``. """ return (filter_flags & cls.Filter.ALIEN) == cls.Filter.ALIEN
[docs] @classmethod def is_default(cls, filter_flags: Info.Filter) -> bool: """ Gets if filter flags has ``Filter.DEFAULT`` flag set. |lo_safe| Args: filter_flags (Filter): Flags. Returns: bool: ``True`` if flag is set; Otherwise, ``False``. """ return (filter_flags & cls.Filter.DEFAULT) == cls.Filter.DEFAULT
[docs] @classmethod def is_support_selection(cls, filter_flags: Info.Filter) -> bool: """ Gets if filter flags has ``Filter.SUPPORTSSELECTION`` flag set. |lo_safe| Args: filter_flags (Filter): Flags. Returns: bool: ``True`` if flag is set; Otherwise, ``False``. """ return (filter_flags & cls.Filter.SUPPORTSSELECTION) == cls.Filter.SUPPORTSSELECTION
[docs] @classmethod def is_not_in_file_dialog(cls, filter_flags: Info.Filter) -> bool: """ Gets if filter flags has ``Filter.NOTINFILEDIALOG`` flag set. |lo_safe| Args: filter_flags (Filter): Flags. Returns: bool: `True` if flag is set; Otherwise, ``False``. """ return (filter_flags & cls.Filter.NOTINFILEDIALOG) == cls.Filter.NOTINFILEDIALOG
[docs] @classmethod def is_not_in_chooser(cls, filter_flags: Info.Filter) -> bool: """ Gets if filter flags has ``Filter.NOTINCHOOSER`` flag set. |lo_safe| Args: filter_flags (Filter): Flags. Returns: bool: ``True`` if flag is set; Otherwise, ``False``. """ return (filter_flags & cls.Filter.NOTINCHOOSER) == cls.Filter.NOTINCHOOSER
[docs] @classmethod def is_read_only(cls, filter_flags: Info.Filter) -> bool: """ Gets if filter flags has ``Filter.READONLY`` flag set. |lo_safe| Args: filter_flags (Filter): Flags. Returns: bool: ``True`` if flag is set; Otherwise, ``False``. """ return (filter_flags & cls.Filter.READONLY) == cls.Filter.READONLY
[docs] @classmethod def is_third_party_filter(cls, filter_flags: Info.Filter) -> bool: """ Gets if filter flags has ``Filter.THIRDPARTYFILTER`` flag set. |lo_safe| Args: filter_flags (Filter): Flags. Returns: bool: ``True`` if flag is set; Otherwise, ``False``. """ return (filter_flags & cls.Filter.THIRDPARTYFILTER) == cls.Filter.THIRDPARTYFILTER
[docs] @classmethod def is_preferred(cls, filter_flags: Info.Filter) -> bool: """ Gets if filter flags has ``Filter.PREFERRED`` flag set. |lo_safe| Args: filter_flags (Filter): Flags. Returns: bool: ``True`` if flag is set; Otherwise, `False`. """ return (filter_flags & cls.Filter.PREFERRED) == cls.Filter.PREFERRED
[docs] @staticmethod def is_type_struct(obj: Any, type_name: str) -> bool: """ Gets if an object is a Uno Struct of matching type. |lo_safe| Args: obj (Any): Object to test if is struct. type_name (str): Type string such as ``com.sun.star.table.CellRangeAddress``. Returns: bool: ``True`` if ``obj`` is struct and ``obj`` matches ``type_name``; Otherwise, ``False``. """ if obj is None: return False return obj.typeName == type_name if hasattr(obj, "typeName") else False
[docs] @staticmethod def is_type_interface(obj: Any, type_name: str) -> bool: """ Gets if an object is a Uno interface of matching type. |lo_safe| Args: obj (object): Object to test if is interface type_name (str): Type string such as 'com.sun.star.uno.XInterface' Returns: bool: True if 'obj' is interface and 'obj' matches 'type_name'; Otherwise, False """ return mLo.Lo.is_uno_interfaces(obj, type_name)
# if obj is None: # return False # if hasattr(obj, "__pyunointerface__"): # return obj.__pyunointerface__ == type_name # elif hasattr(obj, "queryInterface"): # uno_t = uno.getTypeByName(type_name) # q_obj = obj.queryInterface(uno_t) # if q_obj is not None: # return True # return False
[docs] @staticmethod def is_type_enum(obj: Any, type_name: str) -> bool: """ Gets if an object is a UNO enum of matching type. |lo_safe| Args: obj (Any): Object to test if is uno enum type_name (str): Type string such as ``com.sun.star.sheet.GeneralFunction`` Returns: bool: True if ``obj`` is uno enum and ``obj`` matches ``type_name``; Otherwise, False """ if obj is None: return False return obj.typeName == type_name if hasattr(obj, "typeName") else False
[docs] @staticmethod def is_uno(obj: Any) -> bool: """ Gets if an object is a UNO object. |lo_safe| Args: obj (object): Object to check Returns: bool: ``True`` if is ``UNO`` object; Otherwise, ``False`` Note: This method only check for type ``pyuno``, therefore UNO ``struct`` are not considered UNO objects in this context. .. versionadded:: 0.9.0 """ with contextlib.suppress(Exception): return type(obj).__name__ == "pyuno" return False
[docs] @classmethod def is_instance(cls, obj: Any, class_or_tuple: ClassInfo[_T]) -> TypeGuard[_T]: """ Return whether an object is an instance of a class or of a subclass thereof. UNO object error when used with ``isinstance``. This method will return ``False`` if ``obj`` is a UNO object. |lo_safe| Args: obj (Any): Any object. If UNO object then comparison is done by ``Lo.is_uno_interfaces()``; Otherwise, built in ``isinstance`` is used. class_or_tuple (Any): A tuple, as in ``isinstance(x, (A, B, ...))``, may be given as the target to check against. This is equivalent to ``isinstance(x, A)`` or ``isinstance(x, B)`` or ... etc. When a UNO object is being compared then this should be a type or tuple of types that are expected by ``Lo.is_uno_interfaces()`` Returns: bool: ``True`` if is instance; Otherwise, ``False``. Example: .. code-block:: python def __delitem__(self, _item: int | str | DrawPage | XDrawPage) -> None: if mInfo.Info.is_instance(_item, int): self.delete_slide(_item) elif mInfo.Info.is_instance(_item, str): slide = super().get_by_name(_item) if slide is None: raise MissingNameError(f"Unable to find slide with name '{_item}'") super().remove(slide) elif mInfo.Info.is_instance(_item, mDrawPage.DrawPage): super().remove(_item.component) elif mInfo.Info.is_instance(_item, XDrawPage): super().remove(_item) else: raise TypeError(f"Unsupported type: {type(_item)}") See Also: :py:meth:`~ooodev.utils.lo.Lo.is_uno_interfaces` .. versionchanged:: 0.17.14 Add support to check UNO objects. .. versionadded:: 0.17.11 """ if cls.is_uno(obj): with contextlib.suppress(Exception): # Lo.is_uno_interfaces will handle not uno types gracefully if isinstance(class_or_tuple, tuple): return mLo.Lo.is_uno_interfaces(obj, *class_or_tuple) return mLo.Lo.is_uno_interfaces(obj, class_or_tuple) return False try: return isinstance(obj, class_or_tuple) except TypeError: return False
# region is_type_enum_multi() @overload @staticmethod def is_type_enum_multi(alt_type: str, enum_type: Type[Enum], enum_val: Enum) -> bool: ... @overload @staticmethod def is_type_enum_multi(alt_type: str, enum_type: Type[Enum], enum_val: str) -> bool: ... @overload @staticmethod def is_type_enum_multi(alt_type: str, enum_type: Type[Enum], enum_val: Enum, arg_name: str) -> bool: ... @overload @staticmethod def is_type_enum_multi(alt_type: str, enum_type: Type[Enum], enum_val: str, arg_name: str) -> bool: ...
[docs] @staticmethod def is_type_enum_multi(alt_type: str, enum_type: Type[Enum], enum_val: Enum | str, arg_name: str = "") -> bool: """ Gets if an multiple inheritance enum, such as a ``str, Enum`` is of expected type. |lo_safe| Args: alt_type (str): Alternative Type, In the case of a ``str, Enum`` this would be ``str`` enum_type (Type[Enum]): Expected Enum Type enum_val (Enum): Actual Enum Value arg_name (str, optional): Argument name used to pass enum value into method. Raises: TypeError: If ``arg_name`` is passed into this method and the type check fails. Returns: bool: ``True`` if ``enum_val`` is valid ``alt_type`` or ``enum_type``; Otherwise, ``False`` Example: .. code-block:: python >>> from ooodev.utils.kind import chart2_types as ct >>> val = ct.Area3dKind.PERCENT_STACKED_AREA_3D >>> print(is_enum_type("str", ct.ChartTemplateBase, val)) True >>> print(is_enum_type("str", ct.ChartTypeNameBase, val)) False >>> print(is_enum_type("str", ct.ChartTypeNameBase, val, "input_enum")) TypeError: Parameter "input_enum" must be of type "str" or "ChartTypeNameBase" """ if isinstance(enum_val, str): return True if type(enum_val).__name__ != alt_type and not isinstance(enum_val, enum_type): if arg_name: name = enum_type.__name__ raise TypeError(f'Parameter "{arg_name}" must be of type "{alt_type}" or "{name}"') else: return False return True
# endregion is_type_enum_multi()
[docs] @classmethod def get_type_name(cls, obj: Any) -> str | None: """ Gets type name such as ``com.sun.star.table.TableSortField`` from uno object. |lo_safe| Args: obj (Any): Uno object Returns: str | None: Full type name if found; Otherwise; None """ if hasattr(obj, "typeName"): return obj.typeName if hasattr(obj, "__ooo_full_ns__"): # ooouno object return obj.__ooo_full_ns__ return obj.__pyunointerface__ if hasattr(obj, "__pyunointerface__") else None
# parse_language_code
[docs] @classmethod def parse_language_code(cls, lang_code: str) -> Locale: """ Parses a language code into a ``Locale`` object. |lo_safe| Args: lang_code (str): Language code such as ``"en-US"`` Returns: Locale: ``Locale`` object Raises: ValueError: If ``lang_code`` is not valid. """ if not lang_code: raise ValueError("lang_code cannot be empty") lang_code = lang_code.lower() lang = "" country = "" variant = "" if "-" in lang_code: parts = lang_code.split("-", maxsplit=2) if len(parts) == 2: lang, country = parts variant = "" elif len(parts) == 3: lang, country, variant = parts else: lang = lang_code country = "" variant = "" if len(lang) != 2: raise ValueError(f"Invalid language code: {lang_code}") if country and len(country) != 2: raise ValueError(f"Invalid country code: {lang_code}") return Locale(lang, country.upper(), variant)
[docs] @classmethod @deprecated("Use parse_language_code") def parse_languange_code(cls, lang_code: str) -> Locale: """ Parses a language code into a ``Locale`` object. |lo_safe| Args: lang_code (str): Language code such as ``"en-US"`` Returns: Locale: ``Locale`` object Raises: ValueError: If ``lang_code`` is not valid. .. deprecated:: 0.9.4 Use :py:meth:`~.info.Info.parse_language_code` instead. """ return cls.parse_language_code(lang_code=lang_code)
@classproperty def language(cls) -> str: """ Gets the Current Language of the LibreOffice Instance. |lo_unsafe| Returns: str: Language string such as 'en-US' """ try: # this value is not expected to change in multi document mode. return cls._language except AttributeError: # sourcery skip: use-or-for-fallback lang = cls.get_config(node_str="ooLocale", node_path="/org.openoffice.Setup/L10N") if not lang: lang = cls.get_config( node_str="ooSetupSystemLocale", node_path="/org.openoffice.Setup/L10N", ) if not lang: # default to en-us lang = "en-US" cls._language = str(lang) return cls._language @classproperty def date_offset(cls) -> int: """ Gets the the date offset of the LibreOffice Instance. |lo_unsafe| Returns: int: Date offset as integer. """ # Get start date from Calc configuration try: # this value is not expected to change in multi document mode. return cls._date_offset except AttributeError: # sourcery skip: use-or-for-fallback year = 1899 month = 12 day = 30 with contextlib.suppress(Exception): date_info = cast( Any, cls.get_config(node_str="Date", node_path="/org.openoffice.Office.Calc/Calculate/Other") ) year = int(date_info.YY) month = int(date_info.MM) day = int(date_info.DD) cls._date_offset = datetime.date(year, month, day).toordinal() return cls._date_offset @classproperty def language_locale(cls) -> Locale: """ Gets the Current Language ``Locale`` of the LibreOffice Instance. |lo_unsafe| Returns: Locale: ``Locale`` object. .. versionadded:: 0.9.4 """ try: # this value is not expected to change in multi document mode. return cls._language_locale except AttributeError: cls._language_locale = cls.parse_language_code(cls.language) return cls._language_locale @classproperty def version(cls) -> str: """ Gets the running LibreOffice version. |lo_unsafe| Returns: str: version as string such as ``"7.3.4.2"`` Note: This property only works after Office is Loaded. """ try: return cls._version except AttributeError: # ooSetupVersionAboutBox long "7.3.4.2" # ooSetupVersion short "7.3" lang = cls.get_config(node_str="ooSetupVersionAboutBox") cls._version = str(lang) return cls._version @classproperty def version_info(cls) -> Tuple[int, ...]: """ Gets the running LibreOffice version. |lo_unsafe| Returns: tuple: version as tuple such as ``(7, 3, 4, 2)`` Note: This property only works after Office is Loaded. """ try: return cls._version_info except AttributeError: cls._version_info = tuple(int(s) for s in cls.version.split(".")) return cls._version_info
def _del_cache_attrs(source: object, e: EventArgs) -> None: # clears Write Attributes that are dynamically created data_attrs = ("_language", "_language_locale", "_version", "_version_info", "_date_offset") for attr in data_attrs: if hasattr(Info, attr): delattr(Info, attr) # subscribe to events that warrant clearing cached attribs _Events().on(LoNamedEvent.RESET, _del_cache_attrs) __all__ = ("Info",)