# -*- coding: utf-8 -*- """Pure table-split helpers for UNO worker (Py2.7 + Py3, standalone, no pywin32).""" from __future__ import print_function, unicode_literals import os import re import sys try: from urllib.parse import quote except ImportError: from urllib import quote as _urllib_quote def quote(value, safe="/:@"): if isinstance(value, bytes): value = value.decode("utf-8") return _urllib_quote(value.encode("utf-8"), safe) DOCX_FILTER_CANDIDATES = { "libre": ("Office Open XML Text", "MS Word 2007 XML", "Word 2007 XML"), "openoffice": ( "MS Word 2007 XML", "Office Open XML Text", "Word 2007 XML", ), } DOCX_SAVE_FILTER_SPECS = { "libre": ( "Office Open XML Text", "MS Word 2007 XML", ), "openoffice": ( "MS Word 2007 XML", "Office Open XML Text", ), } MAX_PASSES = 20 # UNO enum numeric values (``from com.sun.star.*`` is unavailable in some LO/OO Pythons). BREAK_NONE = 0 BREAK_PAGE_BEFORE = 3 CONTROL_PARAGRAPH_BREAK = 0 _UNO_MISSING = object() try: _TEXT_TYPE = unicode except NameError: _TEXT_TYPE = str def as_unicode(value): """Coerce value to a Unicode string (safe on Python 2.7 + 3).""" if value is None: return _TEXT_TYPE() if isinstance(value, _TEXT_TYPE): return value if isinstance(value, bytes): for encoding in ("utf-8", sys.getfilesystemencoding(), "mbcs"): if not encoding: continue try: return value.decode(encoding) except (UnicodeDecodeError, LookupError): continue return value.decode("utf-8", "replace") try: return _TEXT_TYPE(value) except Exception: return _TEXT_TYPE(repr(value)) def uno_error_text(exc): """Readable UNO/Python exception text (Py2 often has empty str(exc)).""" for attr in ("Message", "Context", "Name"): try: val = getattr(exc, attr, None) if val: text = as_unicode(val) if text: return text except Exception: continue text = as_unicode(exc) if text: return text return repr(exc) def uno_call(obj, base, default=_UNO_MISSING): """Invoke UNO getter ``getBase()`` or read attribute ``Base`` (OO Py2 vs LO Py3).""" getter = "get" + base for access in (getter, base): try: member = getattr(obj, access) return member() if callable(member) else member except Exception: continue if default is not _UNO_MISSING: return default raise AttributeError(base) _RE_FLAGS = re.IGNORECASE if sys.version_info[0] < 3: _RE_FLAGS |= re.UNICODE _CAPTION_RE = re.compile( r"^(?:Продолжение\s+)?" r"(?PТаблица|Таблицы|Листинг|Листинга)\s+" r"(?P[\d.]+)", _RE_FLAGS, ) _CONTINUATION_RE = re.compile( r"^Продолжение\s+(?:Таблицы|Листинга)\b", _RE_FLAGS ) class CaptionInfo(object): __slots__ = ("kind", "number", "is_continuation") def __init__(self, kind, number, is_continuation=False): self.kind = kind self.number = number self.is_continuation = is_continuation def parse_caption_text(text): raw = (text or "").replace("\r", "").replace("\x07", "").strip() if not raw: return None first = raw.split("\n", 1)[0].strip() m = _CAPTION_RE.match(first) if not m: return None kind_raw = m.group("kind").lower() if kind_raw.startswith("табл"): kind = "table" elif kind_raw.startswith("лист"): kind = "listing" else: return None return CaptionInfo( kind=kind, number=m.group("number"), is_continuation=bool(_CONTINUATION_RE.match(first)), ) def find_page_break_row(page_numbers): if len(page_numbers) < 2: return None for i in range(1, len(page_numbers)): if page_numbers[i] > page_numbers[i - 1]: row_1based = i + 1 if row_1based <= 1: return None return row_1based return None def is_orphan_header_break(break_at, had_header): return bool(had_header and break_at == 2) def is_caption_orphaned(caption_page, first_row_page): if caption_page is None or first_row_page is None: return False return caption_page < first_row_page def continuation_label(kind, number): if kind == "listing": return "Продолжение Листинга {0}".format(number) return "Продолжение Таблицы {0}".format(number) def caption_style_name(kind): return "Caption Listing" if kind == "listing" else "Название таблицы" def file_url_from_path(path): abs_path = os.path.abspath(path) posix = abs_path.replace("\\", "/") if len(posix) >= 2 and posix[1] == ":": return "file:///" + quote(posix, safe="/:@") return "file://" + quote(posix, safe="/:@") def path_to_file_url(uno, path): abs_path = os.path.abspath(path) try: url = uno.systemPathToFileUrl(abs_path) if url: return url except Exception: pass return file_url_from_path(abs_path) def docx_filter_candidates(engine): return DOCX_FILTER_CANDIDATES.get(engine, DOCX_FILTER_CANDIDATES["libre"]) def docx_save_filter_specs(engine): return DOCX_SAVE_FILTER_SPECS.get(engine, DOCX_SAVE_FILTER_SPECS["libre"])