210 lines
5.5 KiB
Python
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"])
|