Initial commit
This commit is contained in:
618
pythonpath/synopse/builder.py
Normal file
618
pythonpath/synopse/builder.py
Normal file
@@ -0,0 +1,618 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Erzeugt aus dem Block-Modell ein neues Writer-Dokument mit Synopse-Tabelle.
|
||||
|
||||
Drei Darstellungsarten der Aenderungsseite:
|
||||
|
||||
cumulative - eine Spalte mit der Endfassung
|
||||
per_author - je Person eine eigene Spalte
|
||||
stacked - eine Spalte, darin die Fassungen der Personen untereinander
|
||||
"""
|
||||
|
||||
import uno
|
||||
|
||||
from synopse.redline_model import (extract_blocks, const, enum,
|
||||
UNKNOWN_AUTHOR)
|
||||
from synopse.i18n import t
|
||||
|
||||
PARAGRAPH_BREAK = const("com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK", 0)
|
||||
UL_SINGLE = const("com.sun.star.awt.FontUnderline.SINGLE", 1)
|
||||
UL_NONE = const("com.sun.star.awt.FontUnderline.NONE", 0)
|
||||
SO_SINGLE = const("com.sun.star.awt.FontStrikeout.SINGLE", 1)
|
||||
SO_NONE = const("com.sun.star.awt.FontStrikeout.NONE", 0)
|
||||
ITALIC = enum("com.sun.star.awt.FontSlant", "ITALIC")
|
||||
UPRIGHT = enum("com.sun.star.awt.FontSlant", "NONE")
|
||||
|
||||
COL_ORIGINAL = "\x00original"
|
||||
COL_FINAL = "\x00final"
|
||||
COL_STACKED = "\x00stacked"
|
||||
COL_INDEX = "\x00index"
|
||||
|
||||
AUTHOR_COLORS = [0x1A5FB4, 0xA51D2D, 0x26A269, 0x9141AC,
|
||||
0xC64600, 0x3D3846, 0x005F5F, 0x813D9C]
|
||||
NEUTRAL = 0x000000
|
||||
GREY = 0x707070
|
||||
FINAL_COLOR = 0x5E5C64
|
||||
HEADER_BG = 0xE0E0E0
|
||||
|
||||
NOTE_SIZE = 8.5
|
||||
HEAD_SIZE = 9.0
|
||||
BODY_SIZE = 10.0
|
||||
|
||||
ELLIPSIS = " \u2026 "
|
||||
|
||||
|
||||
class Options(object):
|
||||
mode = "cumulative" # "cumulative" | "per_author" | "stacked"
|
||||
show_deletions = True # geloeschten Text durchgestrichen mitfuehren
|
||||
show_notes = True # Anmerkungen uebernehmen
|
||||
show_quotes = True # Bezugstext kommentierter Stellen zitieren
|
||||
elide = True # unveraenderte Passagen kuerzen
|
||||
show_final = True # Endfassung zusaetzlich ausweisen
|
||||
elide_context = 40 # verbleibende Zeichen je Seite
|
||||
only_changed = False # nur geaenderte Absaetze ausgeben
|
||||
landscape = True
|
||||
numbering = True
|
||||
|
||||
|
||||
# -- Hilfsfunktionen ------------------------------------------------------
|
||||
|
||||
def _cell_name(col, row):
|
||||
return "%s%d" % (chr(ord("A") + col), row + 1)
|
||||
|
||||
|
||||
def _author_color(author, authors):
|
||||
if isinstance(author, int):
|
||||
return author # Vergleichsmodus: feste Farbe
|
||||
try:
|
||||
return AUTHOR_COLORS[sorted(authors).index(author) % len(AUTHOR_COLORS)]
|
||||
except (ValueError, AttributeError, TypeError):
|
||||
return NEUTRAL
|
||||
|
||||
|
||||
def _author_label(author):
|
||||
"""Anzeigename eines Bearbeiters."""
|
||||
return t("text.unknown") if author == UNKNOWN_AUTHOR else author
|
||||
|
||||
|
||||
def _tint(color, factor=0.90):
|
||||
"""Heller Farbton derselben Farbe als Hintergrund fuer Anmerkungen."""
|
||||
out = 0
|
||||
for shift in (16, 8, 0):
|
||||
channel = (color >> shift) & 0xFF
|
||||
channel = int(channel + (255 - channel) * factor)
|
||||
out |= min(channel, 255) << shift
|
||||
return out
|
||||
|
||||
|
||||
def _line_struct():
|
||||
for name in ("com.sun.star.table.BorderLine2",
|
||||
"com.sun.star.table.BorderLine"):
|
||||
try:
|
||||
return uno.createUnoStruct(name)
|
||||
except Exception:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def _border(color, width=50):
|
||||
line = _line_struct()
|
||||
if line is None:
|
||||
return None
|
||||
line.Color = color
|
||||
line.LineWidth = width # 1/100 mm
|
||||
line.OuterLineWidth = width
|
||||
line.InnerLineWidth = 0
|
||||
line.LineDistance = 0
|
||||
line.LineStyle = 0 # SOLID
|
||||
return line
|
||||
|
||||
|
||||
def _no_border():
|
||||
line = _line_struct()
|
||||
if line is None:
|
||||
return None
|
||||
line.LineWidth = 0
|
||||
line.OuterLineWidth = 0
|
||||
try:
|
||||
line.LineStyle = 5 # NONE
|
||||
except Exception:
|
||||
pass
|
||||
return line
|
||||
|
||||
|
||||
# -- Spalteninhalt --------------------------------------------------------
|
||||
|
||||
def _split_first_word(text):
|
||||
"""(erstes Wort mit fuehrendem Leerraum, Rest)."""
|
||||
lead = len(text) - len(text.lstrip())
|
||||
head, rest = text[:lead], text[lead:]
|
||||
cut = rest.find(" ")
|
||||
if cut < 0:
|
||||
return head + rest, ""
|
||||
return head + rest[:cut], rest[cut:]
|
||||
|
||||
|
||||
def _split_last_word(text):
|
||||
"""(Rest, letztes Wort mit nachfolgendem Leerraum)."""
|
||||
body = text.rstrip()
|
||||
trail = text[len(body):]
|
||||
cut = body.rfind(" ")
|
||||
if cut < 0:
|
||||
return "", body + trail
|
||||
return body[:cut + 1], body[cut + 1:] + trail
|
||||
|
||||
|
||||
def _neighbour(seq, index, step):
|
||||
"""Naechster tatsaechlich vorhandener Nachbar mit Textinhalt."""
|
||||
j = index + step
|
||||
while 0 <= j < len(seq):
|
||||
entry = seq[j]
|
||||
if entry["keep"] and entry["text"].strip():
|
||||
return j
|
||||
j += step
|
||||
return None
|
||||
|
||||
|
||||
def _clean_runs(block, key, opts):
|
||||
"""Fassung ohne durchgestrichenen Text, aber mit lesbarer Streichung.
|
||||
|
||||
- unmittelbar ersetzter Text: nur die Neufassung wird hervorgehoben
|
||||
- ersatzlos gestrichener Abschnitt: Hinweis statt leerer Zelle
|
||||
- Streichung innerhalb eines Satzes: das letzte verbliebene Wort davor
|
||||
und das erste danach werden hervorgehoben, damit die Nahtstelle
|
||||
erkennbar bleibt
|
||||
"""
|
||||
seq = []
|
||||
for seg in block.segments:
|
||||
inserted = bool(seg.inserted_by)
|
||||
if key == COL_FINAL:
|
||||
keep = seg.in_final()
|
||||
author = (seg.inserted_by[0] if inserted else
|
||||
(seg.deleted_by[0] if seg.deleted_by else None))
|
||||
removed = not keep and seg.in_original()
|
||||
else:
|
||||
keep = seg.in_version_of(key)
|
||||
author = key
|
||||
removed = not keep and seg.in_original() and key in seg.deleted_by
|
||||
seq.append({"keep": keep, "removed": removed, "text": seg.text,
|
||||
"kind": "insert" if (keep and inserted) else "equal",
|
||||
"author": author})
|
||||
|
||||
dropped = [i for i, e in enumerate(seq) if e["removed"]]
|
||||
if not dropped:
|
||||
return [(e["text"], e["kind"], e["author"]) for e in seq if e["keep"]]
|
||||
|
||||
if not "".join(e["text"] for e in seq if e["keep"]).strip():
|
||||
return [(t("text.removed"), "removed", seq[dropped[0]]["author"])]
|
||||
|
||||
gap_before, gap_after = {}, {}
|
||||
for i in dropped:
|
||||
prev_i = _neighbour(seq, i, -1)
|
||||
next_i = _neighbour(seq, i, 1)
|
||||
replaced = ((prev_i is not None and seq[prev_i]["kind"] == "insert")
|
||||
or (next_i is not None and seq[next_i]["kind"] == "insert"))
|
||||
if replaced:
|
||||
continue # die Neufassung traegt die Markierung
|
||||
author = seq[i]["author"]
|
||||
if prev_i is not None and seq[prev_i]["kind"] == "equal":
|
||||
gap_before[prev_i] = author
|
||||
if next_i is not None and seq[next_i]["kind"] == "equal":
|
||||
gap_after[next_i] = author
|
||||
|
||||
runs = []
|
||||
for i, entry in enumerate(seq):
|
||||
if not entry["keep"]:
|
||||
continue
|
||||
if entry["kind"] != "equal":
|
||||
runs.append((entry["text"], entry["kind"], entry["author"]))
|
||||
continue
|
||||
after, before = gap_after.get(i), gap_before.get(i)
|
||||
first, rest = (_split_first_word(entry["text"])
|
||||
if after is not None else ("", entry["text"]))
|
||||
head, last = (_split_last_word(rest)
|
||||
if before is not None else (rest, ""))
|
||||
if first:
|
||||
runs.append((first, "gap", after))
|
||||
if head:
|
||||
runs.append((head, "equal", None))
|
||||
if last:
|
||||
runs.append((last, "gap", before))
|
||||
return runs
|
||||
|
||||
|
||||
def _runs(block, key, opts, show_deletions=None):
|
||||
"""Liste von (text, kind, author). kind: equal | insert | delete.
|
||||
|
||||
*show_deletions* uebersteuert die Option; damit laesst sich die
|
||||
Endfassung als reiner Beschlusstext ohne Streichungen ausgeben.
|
||||
"""
|
||||
if show_deletions is None:
|
||||
show_deletions = opts.show_deletions
|
||||
if key != COL_ORIGINAL and not show_deletions:
|
||||
return _clean_runs(block, key, opts)
|
||||
runs = []
|
||||
for seg in block.segments:
|
||||
if key == COL_ORIGINAL:
|
||||
if seg.in_original():
|
||||
runs.append((seg.text, "equal", None))
|
||||
continue
|
||||
|
||||
if key == COL_FINAL:
|
||||
keep = seg.in_final()
|
||||
ins, dele = seg.inserted_by, seg.deleted_by
|
||||
author = ins[0] if ins else (dele[0] if dele else None)
|
||||
show_deleted = seg.in_original()
|
||||
else:
|
||||
keep = seg.in_version_of(key)
|
||||
author = key
|
||||
show_deleted = seg.in_original() and key in seg.deleted_by
|
||||
|
||||
if keep:
|
||||
runs.append((seg.text, "insert" if seg.inserted_by else "equal", author))
|
||||
elif show_deletions and show_deleted:
|
||||
runs.append((seg.text, "delete", author))
|
||||
return runs
|
||||
|
||||
|
||||
def _merge(runs):
|
||||
"""Benachbarte Laeufe gleicher Formatierung zusammenfassen."""
|
||||
out = []
|
||||
for text, kind, author in runs:
|
||||
if out and out[-1][1] == kind and out[-1][2] == author:
|
||||
out[-1][0] += text
|
||||
else:
|
||||
out.append([text, kind, author])
|
||||
return [tuple(r) for r in out]
|
||||
|
||||
|
||||
def _head(text, n):
|
||||
cut = text[:n]
|
||||
space = cut.rfind(" ")
|
||||
return (cut[:space] if space > n // 2 else cut).rstrip()
|
||||
|
||||
|
||||
def _tail(text, n):
|
||||
cut = text[-n:]
|
||||
space = cut.find(" ")
|
||||
return (cut[space + 1:] if 0 <= space < n // 2 else cut).lstrip()
|
||||
|
||||
|
||||
def _elide(runs, context):
|
||||
"""Lange unveraenderte Passagen auf den Kontext um die Aenderungen kuerzen."""
|
||||
runs = _merge(runs)
|
||||
if not any(k != "equal" for _t, k, _a in runs):
|
||||
return runs # nichts geaendert: nichts kuerzen
|
||||
out = []
|
||||
for i, (text, kind, author) in enumerate(runs):
|
||||
if kind != "equal" or len(text) <= 2 * context + len(ELLIPSIS):
|
||||
out.append((text, kind, author))
|
||||
continue
|
||||
before = any(k != "equal" for _t, k, _a in runs[:i])
|
||||
after = any(k != "equal" for _t, k, _a in runs[i + 1:])
|
||||
if before and after:
|
||||
new = _head(text, context) + ELLIPSIS + _tail(text, context)
|
||||
elif after:
|
||||
new = ELLIPSIS.lstrip() + _tail(text, context)
|
||||
elif before:
|
||||
new = _head(text, context) + ELLIPSIS.rstrip()
|
||||
else:
|
||||
new = text
|
||||
out.append((new, kind, author))
|
||||
return out
|
||||
|
||||
|
||||
def _active_authors(block, opts):
|
||||
"""Personen, die in diesem Block ueberhaupt in Erscheinung treten."""
|
||||
found = set()
|
||||
for seg in block.segments:
|
||||
for _rtype, author in seg.stack:
|
||||
found.add(author)
|
||||
if opts.show_notes:
|
||||
for note in block.notes:
|
||||
found.add(note.author)
|
||||
return found
|
||||
|
||||
|
||||
def _body(block, key, opts, elide=False):
|
||||
runs = _runs(block, key, opts)
|
||||
if elide and opts.elide:
|
||||
runs = _elide(runs, opts.elide_context)
|
||||
return runs
|
||||
|
||||
|
||||
def _cell_items(block, key, opts, authors):
|
||||
"""Inhalt einer Zelle als Folge von ('body'|'heading'|'note', payload)."""
|
||||
if key == COL_ORIGINAL:
|
||||
return [("body", _runs(block, key, opts))]
|
||||
|
||||
if key == COL_FINAL:
|
||||
# Die Endfassung wird ungekuerzt gezeigt: sie ist der Wortlaut,
|
||||
# der beschlossen werden soll. Streichungen entfallen dabei, sobald
|
||||
# sie an anderer Stelle der Zeile ohnehin sichtbar sind.
|
||||
clean = None if opts.mode == "cumulative" else False
|
||||
items = [("body", _runs(block, key, opts, show_deletions=clean))]
|
||||
if opts.show_notes and opts.mode == "cumulative":
|
||||
# In den anderen Modi stehen die Anmerkungen bereits bei der
|
||||
# jeweiligen Person und wuerden sich hier wiederholen.
|
||||
items += [("note", n) for n in block.notes]
|
||||
return items
|
||||
|
||||
if key == COL_STACKED:
|
||||
editors = set()
|
||||
for seg in block.segments:
|
||||
for _rtype, author in seg.stack:
|
||||
editors.add(author)
|
||||
items = []
|
||||
for author in sorted(_active_authors(block, opts)):
|
||||
note_only = author not in editors
|
||||
color = _author_color(author, authors)
|
||||
suffix = t("text.noteonly") if note_only else ""
|
||||
items.append(("heading", (_author_label(author), suffix, color)))
|
||||
if not note_only:
|
||||
# Wer hier nur kommentiert hat, bekommt keine Textfassung:
|
||||
# sie waere mit der Ursprungsfassung identisch.
|
||||
items.append(("body", _body(block, author, opts, elide=True)))
|
||||
if opts.show_notes:
|
||||
items += [("note", n) for n in block.notes_of(author)]
|
||||
if items and opts.show_final:
|
||||
items.append(("heading", (t("col.final"), "", FINAL_COLOR)))
|
||||
items.append(("body", _runs(block, COL_FINAL, opts,
|
||||
show_deletions=False)))
|
||||
return items
|
||||
|
||||
# eigene Spalte je Person
|
||||
items = [("body", _body(block, key, opts, elide=True))]
|
||||
if opts.show_notes:
|
||||
items += [("note", n) for n in block.notes_of(key)]
|
||||
return items
|
||||
|
||||
|
||||
# -- Schreiben ------------------------------------------------------------
|
||||
|
||||
def _set_border(cur, name, line, distance=0):
|
||||
"""Absatzrahmen setzen; ohne verfuegbare Struktur wird er ausgelassen."""
|
||||
if line is None:
|
||||
return
|
||||
try:
|
||||
setattr(cur, name, line)
|
||||
setattr(cur, name + "Distance", distance)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _reset_para(cur):
|
||||
cur.ParaBackColor = -1
|
||||
cur.ParaLeftMargin = 0
|
||||
cur.ParaRightMargin = 0
|
||||
cur.ParaTopMargin = 0
|
||||
cur.ParaBottomMargin = 0
|
||||
_set_border(cur, "LeftBorder", _no_border())
|
||||
_set_border(cur, "TopBorder", _no_border())
|
||||
|
||||
|
||||
def _heading_para(cur, color, first):
|
||||
_reset_para(cur)
|
||||
cur.ParaTopMargin = 0 if first else 220
|
||||
cur.ParaBottomMargin = 40
|
||||
if not first:
|
||||
_set_border(cur, "TopBorder", _border(color, 20), 90)
|
||||
|
||||
|
||||
def _note_para(cur, color):
|
||||
_reset_para(cur)
|
||||
cur.ParaBackColor = _tint(color)
|
||||
cur.ParaLeftMargin = 200
|
||||
cur.ParaRightMargin = 100
|
||||
cur.ParaTopMargin = 150
|
||||
cur.ParaBottomMargin = 60
|
||||
_set_border(cur, "LeftBorder", _border(color), 120)
|
||||
|
||||
|
||||
def _char(cur, color=NEUTRAL, size=BODY_SIZE, weight=100.0,
|
||||
posture=UPRIGHT, underline=UL_NONE, strikeout=SO_NONE):
|
||||
cur.CharColor = color
|
||||
cur.CharHeight = size
|
||||
cur.CharWeight = weight
|
||||
if posture is not None:
|
||||
cur.CharPosture = posture
|
||||
cur.CharUnderline = underline
|
||||
cur.CharStrikeout = strikeout
|
||||
|
||||
|
||||
def _insert(text, cur, value):
|
||||
for i, part in enumerate(value.split("\n")):
|
||||
if i:
|
||||
text.insertControlCharacter(cur, PARAGRAPH_BREAK, False)
|
||||
if part:
|
||||
text.insertString(cur, part, False)
|
||||
|
||||
|
||||
def _write_cell(cell, items, authors, opts, bold=False):
|
||||
text = cell.getText()
|
||||
text.setString("")
|
||||
cur = text.createTextCursor()
|
||||
_reset_para(cur)
|
||||
wrote = False
|
||||
|
||||
for kind, payload in items:
|
||||
if kind == "body":
|
||||
runs = _merge(payload)
|
||||
if not runs:
|
||||
continue
|
||||
cur.gotoEnd(False)
|
||||
if wrote:
|
||||
text.insertControlCharacter(cur, PARAGRAPH_BREAK, False)
|
||||
_reset_para(cur)
|
||||
for value, rkind, author in runs:
|
||||
color = _author_color(author, authors) if author else NEUTRAL
|
||||
weight = 150.0 if bold else 100.0
|
||||
if rkind == "insert":
|
||||
_char(cur, color, BODY_SIZE, weight, underline=UL_SINGLE)
|
||||
elif rkind == "delete":
|
||||
_char(cur, color, BODY_SIZE, weight, strikeout=SO_SINGLE)
|
||||
elif rkind == "gap":
|
||||
# Nahtstelle einer Streichung: fett in der Autorenfarbe
|
||||
_char(cur, color, BODY_SIZE, 150.0)
|
||||
elif rkind == "removed":
|
||||
_char(cur, color, BODY_SIZE, weight, posture=ITALIC)
|
||||
else:
|
||||
_char(cur, NEUTRAL, BODY_SIZE, weight)
|
||||
_insert(text, cur, value)
|
||||
wrote = True
|
||||
|
||||
elif kind == "heading":
|
||||
label, suffix, color = payload
|
||||
cur.gotoEnd(False)
|
||||
if wrote:
|
||||
text.insertControlCharacter(cur, PARAGRAPH_BREAK, False)
|
||||
_heading_para(cur, color, not wrote)
|
||||
_char(cur, color, HEAD_SIZE, 150.0)
|
||||
_insert(text, cur, label)
|
||||
if suffix:
|
||||
_char(cur, GREY, HEAD_SIZE, 100.0, posture=ITALIC)
|
||||
_insert(text, cur, suffix)
|
||||
wrote = True
|
||||
|
||||
elif kind == "note":
|
||||
note = payload
|
||||
color = _author_color(note.author, authors)
|
||||
cur.gotoEnd(False)
|
||||
if wrote:
|
||||
text.insertControlCharacter(cur, PARAGRAPH_BREAK, False)
|
||||
_note_para(cur, color)
|
||||
|
||||
label = _author_label(note.author)
|
||||
if note.resolved:
|
||||
label += t("text.resolved")
|
||||
_char(cur, color, NOTE_SIZE, 150.0)
|
||||
_insert(text, cur, label + ": ")
|
||||
|
||||
quote = note.short_quote() if opts.show_quotes else ""
|
||||
if quote:
|
||||
_char(cur, GREY, NOTE_SIZE, 100.0, posture=ITALIC)
|
||||
_insert(text, cur, "\u201e%s\u201c \u2013 " % quote)
|
||||
|
||||
_char(cur, GREY if note.resolved else NEUTRAL, NOTE_SIZE, 100.0)
|
||||
_insert(text, cur, note.content or "(ohne Text)")
|
||||
wrote = True
|
||||
|
||||
|
||||
def _set_widths(table, weights):
|
||||
seps = table.TableColumnSeparators
|
||||
if not seps:
|
||||
return
|
||||
total = float(sum(weights))
|
||||
pos, new = 0.0, []
|
||||
for i, sep in enumerate(seps):
|
||||
if i >= len(weights) - 1:
|
||||
break
|
||||
pos += weights[i] / total * 10000.0
|
||||
sep.Position = int(pos)
|
||||
new.append(sep)
|
||||
if new:
|
||||
table.TableColumnSeparators = tuple(new)
|
||||
|
||||
|
||||
def _new_writer(ctx, landscape):
|
||||
desktop = ctx.ServiceManager.createInstanceWithContext(
|
||||
"com.sun.star.frame.Desktop", ctx)
|
||||
doc = desktop.loadComponentFromURL("private:factory/swriter", "_blank", 0, ())
|
||||
if landscape:
|
||||
style = doc.StyleFamilies.getByName("PageStyles").getByName("Standard")
|
||||
if not style.IsLandscape:
|
||||
w, h = style.Width, style.Height
|
||||
style.IsLandscape = True
|
||||
style.Width, style.Height = h, w
|
||||
for name in ("LeftMargin", "RightMargin"):
|
||||
try:
|
||||
setattr(style, name, 1000)
|
||||
except Exception:
|
||||
pass
|
||||
return doc
|
||||
|
||||
|
||||
# -- Aufbau ---------------------------------------------------------------
|
||||
|
||||
def _columns(opts, authors):
|
||||
cols, weights = [], []
|
||||
if opts.numbering:
|
||||
cols.append((t("col.number"), COL_INDEX))
|
||||
weights.append(0.4)
|
||||
cols.append((t("col.original"), COL_ORIGINAL))
|
||||
weights.append(3.0)
|
||||
|
||||
if opts.mode == "cumulative":
|
||||
cols.append((t("col.final"), COL_FINAL))
|
||||
weights.append(3.0)
|
||||
elif opts.mode == "stacked":
|
||||
cols.append((t("col.changes"), COL_STACKED))
|
||||
weights.append(4.0)
|
||||
else:
|
||||
for author in sorted(authors):
|
||||
cols.append((_author_label(author), author))
|
||||
weights.append(3.0)
|
||||
if opts.show_final:
|
||||
cols.append((t("col.final"), COL_FINAL))
|
||||
weights.append(3.0)
|
||||
return cols, weights
|
||||
|
||||
|
||||
def build(ctx, source_doc, opts):
|
||||
blocks, edit_authors, note_authors = extract_blocks(source_doc)
|
||||
authors = set(edit_authors)
|
||||
if opts.show_notes:
|
||||
authors |= note_authors
|
||||
if not authors:
|
||||
raise RuntimeError(t("msg.nochanges"))
|
||||
|
||||
if opts.only_changed or opts.mode == "stacked":
|
||||
# Im gestapelten Modus bleiben unveraenderte Absaetze ohnehin leer.
|
||||
blocks = [b for b in blocks if b.changed] if opts.only_changed else blocks
|
||||
if not blocks:
|
||||
raise RuntimeError(t("msg.noparagraphs"))
|
||||
|
||||
cols, weights = _columns(opts, authors)
|
||||
|
||||
doc = _new_writer(ctx, opts.landscape)
|
||||
body = doc.getText()
|
||||
cur = body.createTextCursor()
|
||||
try:
|
||||
cur.ParaStyleName = "Heading 1"
|
||||
except Exception:
|
||||
pass
|
||||
name = getattr(source_doc, "Title", "") or ""
|
||||
body.insertString(cur, t("doc.title.named", name) if name
|
||||
else t("doc.title"), False)
|
||||
body.insertControlCharacter(cur, PARAGRAPH_BREAK, False)
|
||||
try:
|
||||
cur.ParaStyleName = "Default Paragraph Style"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
table = doc.createInstance("com.sun.star.text.TextTable")
|
||||
table.initialize(len(blocks) + 1, len(cols))
|
||||
body.insertTextContent(cur, table, False)
|
||||
try:
|
||||
table.RepeatHeadline = True
|
||||
table.HeaderRowCount = 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for c, (label, _key) in enumerate(cols):
|
||||
cell = table.getCellByName(_cell_name(c, 0))
|
||||
cell.BackColor = HEADER_BG
|
||||
_write_cell(cell, [("body", [(label, "equal", None)])],
|
||||
authors, opts, bold=True)
|
||||
|
||||
for r, block in enumerate(blocks, start=1):
|
||||
for c, (_label, key) in enumerate(cols):
|
||||
cell = table.getCellByName(_cell_name(c, r))
|
||||
if key == COL_INDEX:
|
||||
_write_cell(cell, [("body", [(str(r), "equal", None)])],
|
||||
authors, opts)
|
||||
else:
|
||||
_write_cell(cell, _cell_items(block, key, opts, authors),
|
||||
authors, opts)
|
||||
|
||||
_set_widths(table, weights)
|
||||
return doc
|
||||
Reference in New Issue
Block a user