Files
md_to_gost/md2gost/office/uno_helpers.py
T
Igor20264 daa84f8bce
Example document / build (push) Has been cancelled
Python application / build (push) Has been cancelled
v0.5.6
2026-09-22 18:49:09 +03:00

210 lines
5.5 KiB
Python

# -*- 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<kind>Таблица|Таблицы|Листинг|Листинга)\s+"
r"(?P<number>[\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"])