Curated source subset assembled by klammertext-dev's doc/make_dist.sh: the Klammermachine (mac), the Standard Klammer Set (sks), the commands (com), editor plugins and install guides (doc), a test subset (tst), and lib/bin placeholders. Builds with 'make -C com'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
309 lines
11 KiB
Python
309 lines
11 KiB
Python
import functools
|
|
import collections
|
|
import re
|
|
import sys
|
|
import traceback
|
|
import pprint
|
|
|
|
import kutil
|
|
import klammer_base
|
|
import html_util
|
|
from html_util import E
|
|
import latex_util
|
|
from sequences import Sequences
|
|
import table_cell
|
|
import font
|
|
|
|
def extend(lst, count, fill=None):
|
|
if isinstance(lst, str):
|
|
lst = lst.strip().split("\\s+")
|
|
if fill is None:
|
|
fill = lst[-1] if lst else ""
|
|
return lst + ([fill] * (count - len(lst)))
|
|
|
|
def parse_hpos(target, pos):
|
|
if '"' in pos:
|
|
pos_list = []
|
|
parts = pos.split('"')
|
|
i = 0
|
|
while i < len(parts):
|
|
pos_list += parts[i].split()
|
|
i += 1
|
|
if i == len(parts):
|
|
break
|
|
#pos_list.append(f"{{{parts[i]}}}")
|
|
pos_list.append(kutil.parse_length(target, f'"{parts[i]}"')[0])
|
|
i += 1
|
|
else:
|
|
# pos_list = pos.split()
|
|
pos_list = pos
|
|
#print("pos_list:", pos_list)
|
|
return pos_list
|
|
|
|
class Table(klammer_base.Klammer_base):
|
|
id = 0
|
|
def __init__(self, K):
|
|
super().__init__(K)
|
|
# pprint.pprint(self.__dict__)
|
|
if self.grid:
|
|
self.vline = "all"
|
|
self.hline = "all"
|
|
self.number = self.number == "true"
|
|
self.rows = kutil.rest_args(self.rows, 2)
|
|
self.row_count = len(self.rows)
|
|
if self.header:
|
|
self.hline += f" 1 {self.row_count}"
|
|
self.row_size = max([len(e) for e in self.rows])
|
|
self.s_vline = Sequences(self.row_size + 1, self.row_count - 1, self.vline)
|
|
self.s_hline = Sequences(self.row_count + 1, self.row_size - 1, self.hline)
|
|
self.s_rowspan = Sequences(self.row_size, self.row_count, self.rowspan)
|
|
self.s_colspan = Sequences(self.row_count, self.row_size, self.colspan)
|
|
self.cell_hpos = extend(parse_hpos(self.K_target, self.cell_hpos.split()), self.row_size)
|
|
#self.cell_hpos = self.cell_hpos.split(";")
|
|
self.font = extend(self.font, self.row_size)
|
|
self.make_cells(self.rows)
|
|
|
|
def span_count(self, span_seq, row_i, col_i):
|
|
result = 0
|
|
row_seq = span_seq[row_i]
|
|
if row_seq:
|
|
for range in row_seq.ranges:
|
|
if range[0] == col_i:
|
|
result = range[1] - range[0] + 1
|
|
return result
|
|
|
|
def remove_redundant_borders(self):
|
|
remove_right = []
|
|
for row_i in range(self.row_count):
|
|
for cell_i in range(self.row_size):
|
|
a = self.cells[row_i][cell_i]
|
|
b = self.cells[row_i][cell_i+1]
|
|
if a.border.right and b.border.left:
|
|
a.border.right = False
|
|
a.border.right_all = False
|
|
|
|
def column_width_text(self):
|
|
self.column_widths = []
|
|
# print("column_width_text:", len(self.cells), len(self.cells[0]))
|
|
for col_i in range(len(self.cells[0])):
|
|
longest = ""
|
|
for row_i in range(len(self.cells)):
|
|
cell = self.cells[row_i][col_i]
|
|
if cell is not None:
|
|
cell_text = cell.text
|
|
#lines = [e.strip() for e in cell_text.split("\\newline")]
|
|
lines = [e.strip() for e in cell_text.split("\newline")]
|
|
lines = sorted(lines, key=len)
|
|
longest_in_line = lines[-1]
|
|
if len(longest_in_line) > len(longest):
|
|
longest = longest_in_line
|
|
self.column_widths.append(longest)
|
|
# kutil.msg("column_widths:")
|
|
# print(self.column_widths)
|
|
|
|
def make_cells(self, rows):
|
|
result = []
|
|
cells = []
|
|
for row_i, row in enumerate(rows):
|
|
row_cells = []
|
|
for cell_i, cell in enumerate(row):
|
|
rspan = self.span_count(self.s_rowspan, row_i, cell_i)
|
|
cspan = self.span_count(self.s_colspan, row_i, cell_i)
|
|
font = self.font[cell_i]
|
|
if row_i == 0 and self.header:
|
|
font = self.header_font
|
|
row_cells.append(
|
|
table_cell.Cell(
|
|
cell,
|
|
font, self.cell_hpos[cell_i],
|
|
self.s_hline.has(row_i, cell_i),
|
|
self.s_vline.has(cell_i+1, row_i),
|
|
self.s_hline.has(row_i+1, cell_i),
|
|
self.s_vline.has(cell_i, row_i),
|
|
self.s_vline.sequences.get(cell_i),
|
|
self.s_vline.sequences.get(cell_i+1),
|
|
rspan, cspan))
|
|
cells.append(row_cells)
|
|
widths = [len(e) for e in cells]
|
|
max_width = max(widths)
|
|
self.cells = [extend(row, max_width, None) for row in cells] \
|
|
if max_width != min(widths) else cells
|
|
self.column_width_text()
|
|
|
|
# HTML
|
|
|
|
def html(self):
|
|
result = ""
|
|
for row in self.cells:
|
|
row_html = ""
|
|
for cell in row:
|
|
row_html += cell.html().strip() + "\n"
|
|
result += E("tr").body(row_html).str()
|
|
result = E("table").body(result)
|
|
if self.number or self.caption:
|
|
result = html_util.add_caption(
|
|
result, "Table", self.number, self.caption, self.caption_font)
|
|
else:
|
|
result = result.str()
|
|
return result
|
|
|
|
# LaTeX
|
|
|
|
def tex_hpos(self):
|
|
def par_format(s, justification):
|
|
command = {"l" : "raggedright",
|
|
"c" : "centering",
|
|
"r" : "raggedleft"}[justification]
|
|
return f">{{\\{command}}}p{{{s}}}"
|
|
|
|
widths = []
|
|
hpos_pat = re.compile("(f|(?:0?(\\.\\d+)(t))|\\*)?([lcr]?)")
|
|
|
|
def parse(s, width_text):
|
|
match = hpos_pat.match(s)
|
|
# print("MATCH:", match, match.groups())
|
|
width, frac, table, just = match.groups()
|
|
width = width or "f"
|
|
just = just or "l"
|
|
if width and width[0] == "{":
|
|
width = f"\\widthof{{{s}}}"
|
|
elif table == "t":
|
|
width = f"{frac}\\tablewidth"
|
|
elif width == "f":
|
|
width = f"\\widthof{{{width_text}}}"
|
|
|
|
if width != "*":
|
|
widths.append(width)
|
|
|
|
result = par_format(width, just) if width != "*" else s
|
|
# print("PARSE:", result)
|
|
return result
|
|
|
|
# print("self.column_widths:", len(self.column_widths), self.column_widths)
|
|
# return extend([parse(e) for e in self.cell_hpos], self.row_size)
|
|
hpos_list = []
|
|
for i, hpos in enumerate(extend(self.cell_hpos, self.row_size)):
|
|
# print(f" Loop {i}:", hpos)
|
|
if i >= len(self.column_widths):
|
|
print(f"Warning: Ignoring table column width: {hpos}")
|
|
else:
|
|
hpos_list.append(parse(hpos, self.column_widths[i]))
|
|
|
|
# print("hpos_list:", hpos_list)
|
|
fill_count = sum([1 if "*" in e else 0 for e in hpos_list])
|
|
# print("fill_count:", fill_count)
|
|
|
|
if fill_count > 0:
|
|
margins = f"(\\tabcolsep * {2 * len(hpos_list)})"
|
|
# print("MARGINS:", margins)
|
|
#expr = "\\linewidth - " + " - ".join(widths) + str("
|
|
if fill_count == len(hpos_list):
|
|
expr = f"{1/fill_count}\\tablewidth"
|
|
else:
|
|
#expr = f"(\\textwidth - {margins} - {' - '.join(widths)}) / {fill_count}"
|
|
expr = f"(\\tablewidth - {' - '.join(widths)}) / {fill_count}"
|
|
# print(expr)
|
|
result = []
|
|
for h in hpos_list:
|
|
if h[0] == "*":
|
|
just = h[1] if len(h) > 1 else "l"
|
|
result.append(par_format(expr, just))
|
|
else:
|
|
result.append(h)
|
|
else:
|
|
result = hpos_list
|
|
# print("tex_hpos:", result)
|
|
|
|
return result
|
|
|
|
def tex_column_spec(self):
|
|
parts = [""] * (self.row_size * 2 + 1)
|
|
for i in self.s_vline.sequences:
|
|
parts[i * 2] = "|"
|
|
for i, hpos in enumerate(self.tex_hpos()):
|
|
parts[i * 2 + 1] = hpos
|
|
# print("tex_column_spec:", "".join(parts))
|
|
return "".join(parts)
|
|
|
|
def tex_hline(self, index):
|
|
hline = ""
|
|
bottom = index == self.row_count
|
|
if bottom:
|
|
index -= 1
|
|
count = 0
|
|
for i, cell in enumerate(self.cells[index]):
|
|
has_border = cell.border.bottom if bottom else cell.border.top
|
|
if has_border:
|
|
hline += f"\\cline{{{i+1}-{i+1}}} "
|
|
count += 1
|
|
#if count == self.row_size:
|
|
# hline = "\\hline"
|
|
return hline.strip() + "\n"
|
|
|
|
def tex_rows(self):
|
|
result = ""
|
|
for row_i, row in enumerate(self.cells):
|
|
result += self.tex_hline(row_i)
|
|
tab = ""
|
|
for cell_i, cell in enumerate(row):
|
|
result += tab + cell.tex()
|
|
tab = " & "
|
|
#result += " \\\\\n"
|
|
result += " \\tabularnewline\n"
|
|
result += self.tex_hline(self.row_count)
|
|
return result
|
|
|
|
def get_width(self):
|
|
box = "\\savebox{\\tablebox}{%\n"
|
|
box += "\\begin{tabular}{"
|
|
box += self.tex_column_spec()
|
|
box += "}\n"
|
|
box += self.tex_rows()
|
|
box += "\\end{tabular}}\n"
|
|
result = box + "\\setlength{\\tableboxwidth}{\\wd\\tablebox}\n"
|
|
# result += "\\the\\tableboxwidth\n\n"
|
|
# print(result)
|
|
return result
|
|
|
|
def make_caption(self):
|
|
column_count = str(len(self.rows[0]))
|
|
caption = latex_util.make_caption_text(
|
|
self.number, "Table", self.caption, self.caption_font, self.caption_font_size)
|
|
result = "\\multicolumn{" + column_count + "}{c}{\\parbox{\\tableboxwidth}{"
|
|
result += "\\vspace*{8pt}\\centering\\small\\em "
|
|
result += caption
|
|
result += "}} \\\\ \\endlastfoot\n"
|
|
return result
|
|
|
|
def tex(self):
|
|
result = self.get_width()
|
|
result += "\\vspace*{-.75\\baselineskip}"
|
|
# result = ""
|
|
result += "\\renewcommand*{\\arraystretch}{1.3}\n"
|
|
if self.allow_break:
|
|
result += "\\vspace*{12pt}\n"
|
|
result += "\\begin{longtable}{"
|
|
result += self.tex_column_spec()
|
|
result += "}\n"
|
|
if self.allow_break:
|
|
result += self.make_caption()
|
|
result += self.tex_rows()
|
|
result += "\\end{longtable}\n"
|
|
|
|
if not self.allow_break:
|
|
if self.number or self.caption:
|
|
result = latex_util.add_caption(result, "Table", self.number, self.caption, "\\tablewidth")
|
|
else:
|
|
result = latex_util.caption_wrapper(result, "center")
|
|
|
|
name = f"Reference-Table-{Table.id}"
|
|
result = f"\\hypertarget{{{name}}}{{}}\\label{{Label-{name}}}\n{result}"
|
|
result = f"\\setlength{{\\tablewidth}}{{\\textwidth - {2 * self.row_count}\\tabcolsep}}\n" + result
|
|
# result += "\\vspace*{-8pt}"
|
|
result = re.sub(r"\newline", r"\\\\", result)
|
|
return result
|
|
|
|
def txt(self):
|
|
return "TXT"
|
|
|