diff --git a/sphinx/templates/latex/longtable.tex_t b/sphinx/templates/latex/longtable.tex_t new file mode 100644 index 000000000..694483ce2 --- /dev/null +++ b/sphinx/templates/latex/longtable.tex_t @@ -0,0 +1,21 @@ +\begin{longtable}<%= table.get_colspec() %> +<%- if table.caption -%> +\caption{<%= ''.join(table.caption) %>}<%= labels %>\\ +<% endif -%> +\hline +<%= ''.join(table.header) -%> +\endfirsthead + +\multicolumn{<%= table.colcount %>}{c}% +{{\tablecontinued{\tablename\ \thetable{} -- <%= _('continued from previous page') %>}}} \\ +\hline +<%= ''.join(table.header) -%> +\endhead + +\hline \multicolumn{<%= table.colcount %>}{|r|}{{\tablecontinued{<%= _('Continued on next page') %>}}} \\ \hline +\endfoot + +\endlastfoot + +<%= ''.join(table.body) -%> +\end{longtable} diff --git a/sphinx/templates/latex/tabular.tex_t b/sphinx/templates/latex/tabular.tex_t new file mode 100644 index 000000000..7448d6c1f --- /dev/null +++ b/sphinx/templates/latex/tabular.tex_t @@ -0,0 +1,12 @@ +<%- if table.caption -%> +\begin{threeparttable} +\capstart\caption{<%= ''.join(table.caption) %>}<%= labels %> +<%- endif %> +\noindent\begin{tabular}<%= table.get_colspec() -%> +\hline +<%= ''.join(table.header) -%> +<%= ''.join(table.body) -%> +\end{tabular} +<%- if table.caption -%> +\end{threeparttable} +<%- endif -%> diff --git a/sphinx/templates/latex/tabulary.tex_t b/sphinx/templates/latex/tabulary.tex_t new file mode 100644 index 000000000..959eadcbd --- /dev/null +++ b/sphinx/templates/latex/tabulary.tex_t @@ -0,0 +1,12 @@ +<%- if table.caption -%> +\begin{threeparttable} +\capstart\caption{<%= ''.join(table.caption) %>}<%= labels %> +<%- endif %> +\noindent\begin{tabulary}{\linewidth}<%= table.get_colspec() -%> +\hline +<%= ''.join(table.header) -%> +<%= ''.join(table.body) -%> +\end{tabulary} +<%- if table.caption -%> +\end{threeparttable} +<%- endif -%> diff --git a/sphinx/util/template.py b/sphinx/util/template.py index 01d365994..f6db8034b 100644 --- a/sphinx/util/template.py +++ b/sphinx/util/template.py @@ -43,8 +43,10 @@ class FileRenderer(BaseRenderer): class SphinxRenderer(FileRenderer): - def __init__(self): - super(SphinxRenderer, self).__init__(os.path.join(package_dir, 'templates')) + def __init__(self, template_path=None): + if template_path is None: + template_path = os.path.join(package_dir, 'templates') + super(SphinxRenderer, self).__init__(template_path) @classmethod def render_from_file(cls, filename, context): @@ -53,7 +55,8 @@ class SphinxRenderer(FileRenderer): class LaTeXRenderer(SphinxRenderer): def __init__(self): - super(LaTeXRenderer, self).__init__() + template_path = os.path.join(package_dir, 'templates', 'latex') + super(LaTeXRenderer, self).__init__(template_path) # use JSP/eRuby like tagging instead because curly bracket; the default # tagging of jinja2 is not good for LaTeX sources. diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 0799e9237..ab716e86c 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -46,7 +46,6 @@ BEGIN_DOC = r''' ''' -DEFAULT_TEMPLATE = 'latex/content.tex_t' URI_SCHEMES = ('mailto:', 'http:', 'https:', 'ftp:') SECNUMDEPTH = 3 @@ -316,19 +315,49 @@ class ShowUrlsTransform(object): class Table(object): - def __init__(self): - # type: () -> None - self.classes = [] + def __init__(self, node): + # type: (nodes.table) -> None + self.header = [] # type: List[unicode] + self.body = [] # type: List[unicode] + self.classes = node.get('classes', []) # type: List[unicode] self.col = 0 self.colcount = 0 - self.colspec = None # type: unicode - self.colwidths = [] # type: List[int] + self.colspec = None # type: unicode + self.colwidths = [] # type: List[int] self.rowcount = 0 - self.had_head = False self.has_problematic = False self.has_verbatim = False - self.caption = None # type: List[unicode] - self.longtable = False + self.caption = None # type: List[unicode] + + def is_longtable(self): + # type: () -> bool + return self.rowcount > 30 or 'longtable' in self.classes + + def get_table_type(self): + # type: () -> unicode + if self.is_longtable(): + return 'longtable' + elif self.has_verbatim: + return 'tabular' + elif self.has_problematic and not self.colspec: + return 'tabular' + else: + return 'tabulary' + + def get_colspec(self): + # type: () -> unicode + if self.colspec: + return self.colspec + elif self.colwidths and 'colwidths-given' in self.classes: + total = sum(self.colwidths) + colspecs = ['\\X{%d}{%d}' % (width, total) for width in self.colwidths] + return '{|%s|}\n' % '|'.join(colspecs) + elif self.has_problematic: + return '{|*{%d}{\\X{1}{%d}|}}\n' % (self.colcount, self.colcount) + elif self.is_longtable(): + return '{|' + ('l|' * self.colcount) + '}\n' + else: + return '{|' + ('L|' * self.colcount) + '}\n' def escape_abbr(text): @@ -606,7 +635,7 @@ class LaTeXTranslator(nodes.NodeVisitor): if path.exists(template_path): return LaTeXRenderer().render(template_path, self.elements) else: - return LaTeXRenderer().render(DEFAULT_TEMPLATE, self.elements) + return LaTeXRenderer().render('content.tex_t', self.elements) def hypertarget(self, id, withdoc=True, anchor=True): # type: (unicode, bool, bool) -> unicode @@ -1166,95 +1195,29 @@ class LaTeXTranslator(nodes.NodeVisitor): raise UnsupportedError( '%s:%s: nested tables are not yet implemented.' % (self.curfilestack[-1], node.line or '')) - self.table = Table() - self.table.classes = node['classes'] - self.table.longtable = 'longtable' in node['classes'] - self.tablebody = [] # type: List[unicode] - self.tableheaders = [] # type: List[unicode] - # Redirect body output until table is finished. - self.pushbody(self.tablebody) + self.table = Table(node) + if self.next_table_colspec: + self.table.colspec = '{%s}\n' % self.next_table_colspec + self.next_table_colspec = None self.restrict_footnote(node) def depart_table(self, node): # type: (nodes.Node) -> None - if self.table.rowcount > 30: - self.table.longtable = True - self.popbody() - if not self.table.longtable and self.table.caption is not None: - self.body.append('\n\n\\begin{threeparttable}\n' - '\\capstart\\caption{') - for caption in self.table.caption: - self.body.append(caption) - self.body.append('}') - for id in self.pop_hyperlink_ids('table'): - self.body.append(self.hypertarget(id, anchor=False)) - if node['ids']: - self.body.append(self.hypertarget(node['ids'][0], anchor=False)) - if self.table.longtable: - self.body.append('\n\\begin{longtable}') - endmacro = '\\end{longtable}\n\n' - elif self.table.has_verbatim: - self.body.append('\n\\noindent\\begin{tabular}') - endmacro = '\\end{tabular}\n\n' - elif self.table.colspec: - self.body.append('\n\\noindent\\begin{tabulary}{\\linewidth}') - endmacro = '\\end{tabulary}\n\n' - elif self.table.has_problematic or self.table.colwidths: - self.body.append('\n\\noindent\\begin{tabular}') - endmacro = '\\end{tabular}\n\n' - else: - self.body.append('\n\\noindent\\begin{tabulary}{\\linewidth}') - endmacro = '\\end{tabulary}\n\n' - if self.table.colspec: - self.body.append(self.table.colspec) - elif self.table.colwidths and 'colwidths-given' in self.table.classes: - total = sum(self.table.colwidths) - colspec = ['\\X{%d}{%d}' % (width, total) - for width in self.table.colwidths] - self.body.append('{|%s|}\n' % '|'.join(colspec)) - elif self.table.has_problematic: - colspec = ('*{%d}{\\X{1}{%d}|}' % - (self.table.colcount, self.table.colcount)) - self.body.append('{|' + colspec + '}\n') - elif self.table.longtable: - self.body.append('{|' + ('l|' * self.table.colcount) + '}\n') - else: - self.body.append('{|' + ('L|' * self.table.colcount) + '}\n') - if self.table.longtable and self.table.caption is not None: - self.body.append(u'\\caption{') - for caption in self.table.caption: - self.body.append(caption) - self.body.append('}') - for id in self.pop_hyperlink_ids('table'): - self.body.append(self.hypertarget(id, anchor=False)) - if node['ids']: - self.body.append(self.hypertarget(node['ids'][0], anchor=False)) - self.body.append(u'\\\\\n') - if self.table.longtable: - self.body.append('\\hline\n') - self.body.extend(self.tableheaders) - self.body.append('\\endfirsthead\n\n') - self.body.append('\\multicolumn{%s}{c}%%\n' % self.table.colcount) - self.body.append(r'{{\tablecontinued{\tablename\ \thetable{} -- %s}}} \\' - % _('continued from previous page')) - self.body.append('\n\\hline\n') - self.body.extend(self.tableheaders) - self.body.append('\\endhead\n\n') - self.body.append(r'\hline \multicolumn{%s}{|r|}{{\tablecontinued{%s}}} \\ \hline' - % (self.table.colcount, - _('Continued on next page'))) - self.body.append('\n\\endfoot\n\n') - self.body.append('\\endlastfoot\n\n') - else: - self.body.append('\\hline\n') - self.body.extend(self.tableheaders) - self.body.extend(self.tablebody) - self.body.append(endmacro) - if not self.table.longtable and self.table.caption is not None: - self.body.append('\\end{threeparttable}\n\n') + labels = '' # type: unicode + for labelid in self.pop_hyperlink_ids('table'): + labels += self.hypertarget(labelid, anchor=False) + if node['ids']: + labels += self.hypertarget(node['ids'][0], anchor=False) + + table_type = self.table.get_table_type() + table = LaTeXRenderer().render(table_type + '.tex_t', + dict(table=self.table, labels=labels)) + self.body.append("\n\n") + self.body.append(table) + self.body.append("\n\n") + self.unrestrict_footnote(node) self.table = None - self.tablebody = None def visit_colspec(self, node): # type: (nodes.Node) -> None @@ -1276,25 +1239,19 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_thead(self, node): # type: (nodes.Node) -> None - self.table.had_head = True - if self.next_table_colspec: - self.table.colspec = '{%s}\n' % self.next_table_colspec - self.next_table_colspec = None - # Redirect head output until header is finished. see visit_tbody. - self.body = self.tableheaders + self.pushbody(self.table.header) # Redirect head output until header is finished. def depart_thead(self, node): # type: (nodes.Node) -> None - pass + self.popbody() def visit_tbody(self, node): # type: (nodes.Node) -> None - if not self.table.had_head: - self.visit_thead(node) - self.body = self.tablebody + self.pushbody(self.table.body) # Redirect body output until table is finished. def depart_tbody(self, node): # type: (nodes.Node) -> None + self.popbody() self.remember_multirow = {} self.remember_multirowcol = {} diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index a7fa95168..534270e45 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -841,10 +841,8 @@ def test_latex_table(app, status, warning): # table having :widths: option table = tables['table having :widths: option'] - assert ('\\noindent\\begin{tabulary}{\\linewidth}{' - '|p{\\dimexpr(\\linewidth-\\arrayrulewidth)*30/100-2\\tabcolsep-\\arrayrulewidth\\relax}' - '|p{\\dimexpr(\\linewidth-\\arrayrulewidth)*70/100-2\\tabcolsep-\\arrayrulewidth\\relax}|}' - in table) + assert ('\\noindent\\begin{tabulary}{\\linewidth}' + '{|\\X{30}{100}|\\X{70}{100}|}' in table) assert ('\\hline\n' '\\sphinxstylethead{\\relax \nheader1\n\\unskip}\\relax &' '\\sphinxstylethead{\\relax \nheader2\n\\unskip}\\relax' in table) @@ -869,22 +867,15 @@ def test_latex_table(app, status, warning): # table having verbatim table = tables['table having verbatim'] - assert ('\\noindent\\begin{tabular}{|*{2}{' - 'p{\\dimexpr(\\linewidth-\\arrayrulewidth)/2-2\\tabcolsep-\\arrayrulewidth\\relax}|}}\n' - '\\hline' in table) + assert ('\\noindent\\begin{tabular}{|*{2}{\\X{1}{2}|}}\n\\hline' in table) # table having problematic cell table = tables['table having problematic cell'] - assert ('\\noindent\\begin{tabular}{|*{2}{' - 'p{\\dimexpr(\\linewidth-\\arrayrulewidth)/2-2\\tabcolsep-\\arrayrulewidth\\relax}|}}\n' - '\\hline' in table) + assert ('\\noindent\\begin{tabular}{|*{2}{\\X{1}{2}|}}\n\\hline' in table) # table having both :widths: and problematic cell table = tables['table having both :widths: and problematic cell'] - assert ('\\noindent\\begin{tabular}{' - '|p{\\dimexpr(\\linewidth-\\arrayrulewidth)*30/100-2\\tabcolsep-\\arrayrulewidth\\relax}' - '|p{\\dimexpr(\\linewidth-\\arrayrulewidth)*70/100-2\\tabcolsep-\\arrayrulewidth\\relax}|}' - in table) + assert ('\\noindent\\begin{tabular}{|\\X{30}{100}|\\X{70}{100}|}' in table) # longtable table = tables['longtable'] @@ -909,10 +900,7 @@ def test_latex_table(app, status, warning): # longtable having :widths: option table = tables['longtable having :widths: option'] - assert ('\\begin{longtable}{' - '|p{\\dimexpr(\\linewidth-\\arrayrulewidth)*30/100-2\\tabcolsep-\\arrayrulewidth\\relax}' - '|p{\\dimexpr(\\linewidth-\\arrayrulewidth)*70/100-2\\tabcolsep-\\arrayrulewidth\\relax}|}' - in table) + assert ('\\begin{longtable}{|\\X{30}{100}|\\X{70}{100}|}' in table) # longtable having caption table = tables['longtable having caption'] @@ -921,19 +909,12 @@ def test_latex_table(app, status, warning): # longtable having verbatim table = tables['longtable having verbatim'] - assert ('\\begin{longtable}{|*{2}{' - 'p{\\dimexpr(\\linewidth-\\arrayrulewidth)/2-2\\tabcolsep-\\arrayrulewidth\\relax}|}}\n' - '\\hline' in table) + assert ('\\begin{longtable}{|*{2}{\\X{1}{2}|}}\n\\hline' in table) # longtable having problematic cell table = tables['longtable having problematic cell'] - assert ('\\begin{longtable}{|*{2}{' - 'p{\\dimexpr(\\linewidth-\\arrayrulewidth)/2-2\\tabcolsep-\\arrayrulewidth\\relax}|}}\n' - '\\hline' in table) + assert ('\\begin{longtable}{|*{2}{\\X{1}{2}|}}\n\\hline' in table) # longtable having both :widths: and problematic cell table = tables['longtable having both :widths: and problematic cell'] - assert ('\\begin{longtable}{' - '|p{\\dimexpr(\\linewidth-\\arrayrulewidth)*30/100-2\\tabcolsep-\\arrayrulewidth\\relax}' - '|p{\\dimexpr(\\linewidth-\\arrayrulewidth)*70/100-2\\tabcolsep-\\arrayrulewidth\\relax}|}' - in table) + assert ('\\begin{longtable}{|\\X{30}{100}|\\X{70}{100}|}' in table)