mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
latex: Refactor spanning cells
This commit is contained in:
parent
029e9908c0
commit
8b2c92d54f
@ -15,6 +15,7 @@
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from os import path
|
from os import path
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from six import itervalues, text_type
|
from six import itervalues, text_type
|
||||||
from docutils import nodes, writers
|
from docutils import nodes, writers
|
||||||
@ -321,17 +322,19 @@ class Table(object):
|
|||||||
self.body = [] # type: List[unicode]
|
self.body = [] # type: List[unicode]
|
||||||
self.classes = node.get('classes', []) # type: List[unicode]
|
self.classes = node.get('classes', []) # type: List[unicode]
|
||||||
self.col = 0
|
self.col = 0
|
||||||
|
self.row = 0
|
||||||
self.colcount = 0
|
self.colcount = 0
|
||||||
self.colspec = None # type: unicode
|
self.colspec = None # type: unicode
|
||||||
self.colwidths = [] # type: List[int]
|
self.colwidths = [] # type: List[int]
|
||||||
self.rowcount = 0
|
|
||||||
self.has_problematic = False
|
self.has_problematic = False
|
||||||
self.has_verbatim = False
|
self.has_verbatim = False
|
||||||
self.caption = None # type: List[unicode]
|
self.caption = None # type: List[unicode]
|
||||||
|
self.cells = defaultdict(int) # type: Dict[Tuple[int, int], int]
|
||||||
|
self.cell_id = 0
|
||||||
|
|
||||||
def is_longtable(self):
|
def is_longtable(self):
|
||||||
# type: () -> bool
|
# 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):
|
def get_table_type(self):
|
||||||
# type: () -> unicode
|
# type: () -> unicode
|
||||||
@ -361,6 +364,52 @@ class Table(object):
|
|||||||
else:
|
else:
|
||||||
return '{|' + ('l|' * self.colcount) + '}\n'
|
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):
|
def escape_abbr(text):
|
||||||
# type: (unicode) -> unicode
|
# type: (unicode) -> unicode
|
||||||
@ -417,8 +466,6 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
self.in_parsed_literal = 0
|
self.in_parsed_literal = 0
|
||||||
self.compact_list = 0
|
self.compact_list = 0
|
||||||
self.first_param = 0
|
self.first_param = 0
|
||||||
self.remember_multirow = {} # type: Dict[int, int]
|
|
||||||
self.remember_multirowcol = {} # type: Dict[int, int]
|
|
||||||
|
|
||||||
# determine top section level
|
# determine top section level
|
||||||
if builder.config.latex_toplevel_sectioning:
|
if builder.config.latex_toplevel_sectioning:
|
||||||
@ -1254,72 +1301,53 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
def depart_tbody(self, node):
|
def depart_tbody(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
self.popbody()
|
self.popbody()
|
||||||
self.remember_multirow = {}
|
|
||||||
self.remember_multirowcol = {}
|
|
||||||
|
|
||||||
def visit_row(self, node):
|
def visit_row(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
self.table.col = 0
|
self.table.col = 0
|
||||||
for key, value in self.remember_multirow.items():
|
|
||||||
if not value and key in self.remember_multirowcol:
|
# fill column if first one is a wide-multirow
|
||||||
del self.remember_multirowcol[key]
|
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):
|
def depart_row(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
self.body.append('\\\\\n')
|
self.body.append('\\\\\n')
|
||||||
if any(self.remember_multirow.values()):
|
cells = [self.table.cell(self.table.row, i) for i in range(self.table.colcount)]
|
||||||
linestart = 1
|
underlined = [cell.row + cell.height == self.table.row + 1 for cell in cells]
|
||||||
col = self.table.colcount
|
if all(underlined):
|
||||||
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:
|
|
||||||
self.body.append('\\hline')
|
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):
|
def visit_entry(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
if self.table.col == 0:
|
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:
|
|
||||||
self.body.append('&')
|
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 = ''
|
context = ''
|
||||||
if 'morecols' in node:
|
if cell.width > 1:
|
||||||
self.body.append('\\multicolumn{')
|
self.body.append('\\multicolumn{%d}' % cell.width)
|
||||||
self.body.append(str(node.get('morecols') + 1))
|
if self.table.col == 0:
|
||||||
if self.table.col == 1:
|
self.body.append('{|l|}{\\relax ')
|
||||||
self.body.append('}{|l|}{\\relax ')
|
|
||||||
else:
|
else:
|
||||||
self.body.append('}{l|}{\\relax ')
|
self.body.append('{l|}{\\relax ')
|
||||||
context += '\\unskip}\\relax '
|
context += '\\unskip}\\relax '
|
||||||
if 'morerows' in node:
|
if cell.height > 1:
|
||||||
self.body.append('\\multirow{')
|
self.body.append('\\multirow{%d}{*}{\\relax ' % cell.height)
|
||||||
self.body.append(str(node.get('morerows') + 1))
|
|
||||||
self.body.append('}{*}{\\relax ')
|
|
||||||
context += '\\unskip}\\relax '
|
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
|
if (('morecols' in node or 'morerows' in node) and
|
||||||
(len(node) > 2 or len(node.astext().split('\n')) > 2)):
|
(len(node) > 2 or len(node.astext().split('\n')) > 2)):
|
||||||
self.in_merged_cell = 1
|
self.in_merged_cell = 1
|
||||||
@ -1333,16 +1361,6 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
else:
|
else:
|
||||||
self.body.append('\\sphinxstylethead{\\relax ')
|
self.body.append('\\sphinxstylethead{\\relax ')
|
||||||
context += '\\unskip}\\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:
|
if len(node.traverse(nodes.paragraph)) >= 2:
|
||||||
self.table.has_problematic = True
|
self.table.has_problematic = True
|
||||||
self.context.append(context)
|
self.context.append(context)
|
||||||
@ -1361,6 +1379,17 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
self.body.append(line)
|
self.body.append(line)
|
||||||
self.body.append(self.context.pop()) # header
|
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):
|
def visit_acks(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
# this is a list in the source, but should be rendered as a
|
# this is a list in the source, but should be rendered as a
|
||||||
|
@ -12,6 +12,23 @@ cell2-1 cell2-2
|
|||||||
cell3-1 cell3-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
|
table having :widths: option
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
@ -839,6 +839,23 @@ def test_latex_table(app, status, warning):
|
|||||||
assert ('\\hline\ncell3-1\n&\ncell3-2\n\\\\' in table)
|
assert ('\\hline\ncell3-1\n&\ncell3-2\n\\\\' in table)
|
||||||
assert ('\\hline\n\\end{tabulary}' 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 having :widths: option
|
||||||
table = tables['table having :widths: option']
|
table = tables['table having :widths: option']
|
||||||
assert ('\\noindent\\begin{tabular}{|\\X{30}{100}|\\X{70}{100}|}' in table)
|
assert ('\\noindent\\begin{tabular}{|\\X{30}{100}|\\X{70}{100}|}' in table)
|
||||||
|
Loading…
Reference in New Issue
Block a user