v0.5.6
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
# -*- 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"])
|
||||
Reference in New Issue
Block a user