latex: Refactor spanning cells

This commit is contained in:
Takeshi KOMIYA 2017-02-05 00:23:35 +09:00
parent 029e9908c0
commit 8b2c92d54f
3 changed files with 126 additions and 63 deletions

View File

@ -15,6 +15,7 @@
import re
import sys
from os import path
from collections import defaultdict
from six import itervalues, text_type
from docutils import nodes, writers
@ -321,17 +322,19 @@ class Table(object):
self.body = [] # type: List[unicode]
self.classes = node.get('classes', []) # type: List[unicode]
self.col = 0
self.row = 0
self.colcount = 0
self.colspec = None # type: unicode
self.colwidths = [] # type: List[int]
self.rowcount = 0
self.has_problematic = False
self.has_verbatim = False
self.caption = None # type: List[unicode]
self.cells = defaultdict(int) # type: Dict[Tuple[int, int], int]
self.cell_id = 0
def is_longtable(self):
# type: () -> bool
return self.rowcount > 30 or 'longtable' in self.classes
return self.row > 30 or 'longtable' in self.classes
def get_table_type(self):
# type: () -> unicode
@ -361,6 +364,52 @@ class Table(object):
else:
return '{|' + ('l|' * self.colcount) + '}\n'
def add_cell(self, height, width):
self.cell_id += 1
for col in range(width):
for row in range(height):
assert self.cells[(self.row + row, self.col + col)] == 0
self.cells[(self.row + row, self.col + col)] = self.cell_id
def cell(self, row=None, col=None):
try:
if row is None:
row = self.row
if col is None:
col = self.col
return TableCell(self, row, col)
except IndexError:
return None
class TableCell(object):
def __init__(self, table, row, col):
if table.cells[(row, col)] == 0:
raise IndexError
self.table = table
self.cell_id = table.cells[(row, col)]
for n in range(row + 1):
if table.cells[(row - n, col)] == self.cell_id:
self.row = row - n
for n in range(col + 1):
if table.cells[(row, col - n)] == self.cell_id:
self.col = col - n
@property
def width(self):
width = 0
while self.table.cells[(self.row, self.col + width)] == self.cell_id:
width += 1
return width
@property
def height(self):
height = 0
while self.table.cells[(self.row + height, self.col)] == self.cell_id:
height += 1
return height
def escape_abbr(text):
# type: (unicode) -> unicode
@ -417,8 +466,6 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.in_parsed_literal = 0
self.compact_list = 0
self.first_param = 0
self.remember_multirow = {} # type: Dict[int, int]
self.remember_multirowcol = {} # type: Dict[int, int]
# determine top section level
if builder.config.latex_toplevel_sectioning:
@ -1254,72 +1301,53 @@ class LaTeXTranslator(nodes.NodeVisitor):
def depart_tbody(self, node):
# type: (nodes.Node) -> None
self.popbody()
self.remember_multirow = {}
self.remember_multirowcol = {}
def visit_row(self, node):
# type: (nodes.Node) -> None
self.table.col = 0
for key, value in self.remember_multirow.items():
if not value and key in self.remember_multirowcol:
del self.remember_multirowcol[key]
# fill column if first one is a wide-multirow
cell = self.table.cell(self.table.row, 0)
if cell and cell.row != self.table.row: # bottom part of multirow cell
self.table.col += cell.width
if cell.width > 1: # use \multicolumn for wide multirow cell
self.body.append('\\multicolumn{%d}{|l|}{}\\relax ' % cell.width)
def depart_row(self, node):
# type: (nodes.Node) -> None
self.body.append('\\\\\n')
if any(self.remember_multirow.values()):
linestart = 1
col = self.table.colcount
for col in range(1, self.table.col + 1):
if self.remember_multirow.get(col):
if linestart != col:
linerange = str(linestart) + '-' + str(col - 1)
self.body.append('\\cline{' + linerange + '}')
linestart = col + 1
if self.remember_multirowcol.get(col, 0):
linestart += self.remember_multirowcol[col]
if linestart <= col:
linerange = str(linestart) + '-' + str(col)
self.body.append('\\cline{' + linerange + '}')
else:
cells = [self.table.cell(self.table.row, i) for i in range(self.table.colcount)]
underlined = [cell.row + cell.height == self.table.row + 1 for cell in cells]
if all(underlined):
self.body.append('\\hline')
self.table.rowcount += 1
else:
i = 0
underlined.extend([False]) # sentinel
while i < len(underlined):
if underlined[i] is True:
j = underlined[i:].index(False)
self.body.append('\\cline{%d-%d}' % (i + 1, i + j))
i += j
i += 1
self.table.row += 1
def visit_entry(self, node):
# type: (nodes.Node) -> None
if self.table.col == 0:
while self.remember_multirow.get(self.table.col + 1, 0):
self.table.col += 1
self.remember_multirow[self.table.col] -= 1
if self.remember_multirowcol.get(self.table.col, 0):
extracols = self.remember_multirowcol[self.table.col]
self.body.append('\\multicolumn{')
self.body.append(str(extracols + 1))
self.body.append('}{|l|}{}\\relax ')
self.table.col += extracols
self.body.append('&')
else:
if self.table.col > 0:
self.body.append('&')
self.table.col += 1
self.table.add_cell(node.get('morerows', 0) + 1, node.get('morecols', 0) + 1)
cell = self.table.cell()
context = ''
if 'morecols' in node:
self.body.append('\\multicolumn{')
self.body.append(str(node.get('morecols') + 1))
if self.table.col == 1:
self.body.append('}{|l|}{\\relax ')
if cell.width > 1:
self.body.append('\\multicolumn{%d}' % cell.width)
if self.table.col == 0:
self.body.append('{|l|}{\\relax ')
else:
self.body.append('}{l|}{\\relax ')
self.body.append('{l|}{\\relax ')
context += '\\unskip}\\relax '
if 'morerows' in node:
self.body.append('\\multirow{')
self.body.append(str(node.get('morerows') + 1))
self.body.append('}{*}{\\relax ')
if cell.height > 1:
self.body.append('\\multirow{%d}{*}{\\relax ' % cell.height)
context += '\\unskip}\\relax '
self.remember_multirow[self.table.col] = node.get('morerows')
if 'morecols' in node:
if 'morerows' in node:
self.remember_multirowcol[self.table.col] = node.get('morecols')
self.table.col += node.get('morecols')
if (('morecols' in node or 'morerows' in node) and
(len(node) > 2 or len(node.astext().split('\n')) > 2)):
self.in_merged_cell = 1
@ -1333,16 +1361,6 @@ class LaTeXTranslator(nodes.NodeVisitor):
else:
self.body.append('\\sphinxstylethead{\\relax ')
context += '\\unskip}\\relax '
while self.remember_multirow.get(self.table.col + 1, 0):
self.table.col += 1
self.remember_multirow[self.table.col] -= 1
context += '&'
if self.remember_multirowcol.get(self.table.col, 0):
extracols = self.remember_multirowcol[self.table.col]
context += '\\multicolumn{'
context += str(extracols + 1)
context += '}{l|}{}\\relax '
self.table.col += extracols
if len(node.traverse(nodes.paragraph)) >= 2:
self.table.has_problematic = True
self.context.append(context)
@ -1361,6 +1379,17 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.body.append(line)
self.body.append(self.context.pop()) # header
cell = self.table.cell()
self.table.col += cell.width
# fill column if next one is a wide-multirow
nextcell = self.table.cell()
if nextcell and nextcell.row != self.table.row: # bottom part of multirow cell
self.table.col += nextcell.width
self.body.append('&')
if nextcell.width > 1: # use \multicolumn for wide multirow cell
self.body.append('\\multicolumn{%d}{l|}{}\\relax ' % nextcell.width)
def visit_acks(self, node):
# type: (nodes.Node) -> None
# this is a list in the source, but should be rendered as a

View File

@ -12,6 +12,23 @@ cell2-1 cell2-2
cell3-1 cell3-2
======= =======
grid table
----------
+---------+---------+---------+
| header1 | header2 | header3 |
+=========+=========+=========+
| cell1-1 | cell1-2 | cell1-3 |
+---------+ +---------+
| cell2-1 | | cell2-2 |
+ +---------+---------+
| | cell3-2 |
+---------+ |
| cell4-1 | |
+---------+---------+---------+
| cell5-1 |
+---------+---------+---------+
table having :widths: option
----------------------------

View File

@ -839,6 +839,23 @@ def test_latex_table(app, status, warning):
assert ('\\hline\ncell3-1\n&\ncell3-2\n\\\\' in table)
assert ('\\hline\n\\end{tabulary}' in table)
# grid table
table = tables['grid table']
assert ('\\noindent\\begin{tabulary}{\\linewidth}{|L|L|L|}' in table)
assert ('\\hline\n'
'\\sphinxstylethead{\\relax \nheader1\n\\unskip}\\relax &'
'\\sphinxstylethead{\\relax \nheader2\n\\unskip}\\relax &'
'\\sphinxstylethead{\\relax \nheader3\n\\unskip}\\relax \\\\' in table)
assert ('\\hline\ncell1-1\n&\\multirow{2}{*}{\\relax \ncell1-2\n\\unskip}\\relax &\n'
'cell1-3\n\\\\' in table)
assert ('\\cline{1-1}\\cline{3-3}\\multirow{2}{*}{\\relax \ncell2-1\n\\unskip}\\relax &&\n'
'cell2-2\n\\\\' in table)
assert ('\\cline{2-3}&\\multicolumn{2}{l|}{\\relax \\multirow{2}{*}{\\relax \n'
'cell3-2\n\\unskip}\\relax \\unskip}\\relax \\\\' in table)
assert ('\\cline{1-1}\ncell4-1\n&\\multicolumn{2}{l|}{}\\relax \\\\' in table)
assert ('\\hline\\multicolumn{3}{|l|}{\\relax \ncell5-1\n\\unskip}\\relax \\\\\n'
'\\hline\n\\end{tabulary}' in table)
# table having :widths: option
table = tables['table having :widths: option']
assert ('\\noindent\\begin{tabular}{|\\X{30}{100}|\\X{70}{100}|}' in table)