15

Is there a package or simple command that will insert an \hline after every line in a tabular environment? It is rather annoying to do it all manually, and it seems like there should be a simple way of doing this (it seems to be a relatively common thing to want to do).

Is there something that does this?

Edit: In case it does not go without saying, I would also like a line above the first row as well.

Clarification: I currently have something that looks like this:

\documentclass[12pt]{article}
\usepackage[english]{babel}
\usepackage{amsmath,amsthm}
\usepackage{amsfonts}

\begin{document}
\begin{tabular}{l}
\hline
a\\
\hline
a\\
\hline
a\\
\hline
a\\
\hline
a\\
\hline
\end{tabular}

\end{document}

I would like to have something that would remove the need for me to type \hline so many times.

Troy
  • 13,741
soandos
  • 2,211
  • 2
  • 23
  • 35

7 Answers7

12

to make a table line followed by a line the markup is \\\hline That's only 8 characters so it's rather hard to come up with a less intrusive markup, given that you have to mark the end of row manually anyway. Perhaps \def\nl{\\\hline} then you only need the three characters \nl at the end of each row.

David Carlisle
  • 757,742
10

How does this TeX code generate automatic \hline's in tabular environments?

Automatic Horizonal Lines in a Nutshell

  • Intent is to keep unmodified syntax in the tabular environment, yet alter formatting globally.
  • This will change the meaning of \\ for all tabular environments (that use \@tabularcr).
  • \@tabularcr is the macro you are looking for. It is what \\ represents in tabular rows except when using the array package, see Array Package Changes below.

    1. Clone it: \let \clone@tabularcr \@tabularcr
    2. Reset it: \def\@tabularcr{\clone@tabularcr\hline}

Note that you might be better off using \midrule from the booktabs package instead of \hline. Also note that redefining \\ may mean that you might run into difficulties trying to use \bottomrule, because you might end up with undesired double lines below the last row. There is a way to detect the last row, but it is finicky. I will add it when I get around to it.

Code

\documentclass{article}
\usepackage{fontspec}
\catcode`@=11 % or \makeatletter to change category code of @ to 11 and temporarily to access kernel macro \@tabularcr
\let \clone@tabularcr \@tabularcr
\def\@tabularcr{\clone@tabularcr\hline}
\catcode`@=12 % or \makeatother to restore category code of @ to 12
\begin{document}
\begin{tabular}{lll}
Meaning & col2 & col3 \\
\meaning\\ & col2 & col3 \\
col1 & col2 & col3 \\
\end{tabular}
\end{document}

Array Package Changes

Note that the array package (and any package that loads array e.g. \RequirePackage{array}) changes the definition of \\ to

\relax \iffalse {\fi \ifnum 0=‘}\fi \@ifstar \@xarraycr \@xarraycr

\@tabularcr becomes \@xarraycr. So just replace \@tabularcr with \@xarraycr in the code above for compatibility with the array package.


Notes

In tabular environments, \\ is redefined in the LaTeX kernel at the beginning/end of the tabular environment.

Relevant Snippets from latex.ltx

See latex.ltx (LaTeX kernel) for details.

\def\@tabular{\leavevmode \hbox \bgroup $\let\@acol\@tabacol
   \let\@classz\@tabclassz
   \let\@classiv\@tabclassiv \let\\\@tabularcr\@tabarray}% sets \\ = \@tabularcr

% defines \@tabularcr to look for starred version
\def\@tabularcr{% 
  {\ifnum0=`}\fi\@ifstar\@xtabularcr\@xtabularcr}

% some starred/non-starred code for dealing with table rows
\def\@xtabularcr{\@ifnextchar[\@argtabularcr{\ifnum0=`{\fi}\cr}}
\def\@argtabularcr[#1]{%
  \ifnum0=`{\fi}%
    \ifdim #1>\z@
      \unskip\@xargarraycr{#1}%
    \else
      \@yargarraycr{#1}%
    \fi}
7

The tabu package offer an \everyrow{\hline} command.

However be aware, that the tabu package is not yet supported if you want to go to html output via htlatex/tex4ht.

Koni
  • 226
  • 3
    Man, this answer should be at the very top... – Georg P. Oct 25 '19 at 19:44
  • 3
    tabu right now is not supported at all. It's not been actively maintained since 2011. Source: https://github.com/tabu-issues-for-future-maintainer/tabu – facetus Feb 11 '21 at 09:26
3

If you are used to vim and vim-latex you could add this to your ~/.vim/ftplugin/tex.vim

:call IMAP('ETA', "\\begin{table}[<+htbp+>]\<CR>\\centering\<CR>\\begin{tabular}{<+dimensions+>}\<CR>\\hline\<CR><++>\<CR>\\end{tabular}\<CR>\\caption{<+Caption text+>}\<CR>\\label{tab:<+label+>}\<CR>\\end{table}<++>", 'tex')
:call IMAP('LHL', "\\\\ \\hline\<CR>", 'tex')

Then, if you type ETA (like Environment TAble) while you are in the insertion mode, it will be automaticaly replaced by :

\begin{table}[<+htbp+>]
  \centering
  \begin{tabular}{<+dimensions+>}
    \hline
    <++>
  \end{tabular}
  \caption{<+Caption text+>}
  \label{tab:<+label+>}
\end{table}<++>

And if you type LHL (like Line HLine) it will automatically add \\ \hline at the end of the current line and begin a new line.

2

A solution with tabularray package:

\documentclass[12pt]{article}

\usepackage{tabularray}

\begin{document}

\begin{tblr}{ colspec = {lcr}, hlines, } a & b & c \ a & b & c \ a & b & c \ a & b & c \ a & b & c \ a & b & c \ \end{tblr}

\end{document}

enter image description here

L.J.R.
  • 10,932
1

With the environment {NiceTabular} of nicematrix, you have a key hlines for that purpose.

\documentclass[12pt]{article}
\usepackage[english]{babel}
\usepackage{amsmath,amsthm}
\usepackage{amsfonts}
\usepackage{nicematrix}

\begin{document} \begin{NiceTabular}{l}[hlines] a\ a\ a\ a\ a\ \end{NiceTabular}

\end{document}

You need several compilations (because nicematrix uses PGF/Tikz nodes under the hood).

Output of the above code

F. Pantigny
  • 40,250
1

This code is similar to things I do in an unpublished package of mine that I used for several documents, most of this should therefore be relatively tested; though I never really used these nested. Still, use at your own risk.

Just from looking at the code, nesting harray in htabular and vice versa will use the wrong specific hooks in the nested environment, so there is at least one known bug for nesting.


The following provides two environments (htabular and harray) which are patches of the tabular and array environments. It adds a few hooks to them (and keeps track of the current line):

  • begin is used before the alignment starts
  • start is used before the first line inside the alignment (might contain \noalign)
  • bol is used at the begin of every line (in the first line after start, might contain \noalign))
  • eol is used at the end of every line (inside \\ such that it's part of the last used column)
  • lastline is used at the same place eol would've been used if the last line of the alignment wasn't ended with \\
  • stop is used after the last line (might contain \noalign)
  • end is used after the alignment ends

Each hook is provided three times:

  • common hooks
  • hooks only used inside htabular
  • hooks only used inside harray

The common hooks are always executed before the specific ones.

Hooks can be set and cleared via the macros:

  • \htabAddToHook[<specific>]{<hook>}{<code>} adds the <code> to the respective <hook>, if the optional argument is omitted the generic hook is used, else you can either specify tabular or array in it to set the specific one
  • \htabClearHook<*>[<specific>]{<hook>} clears the respective <hook>, if the optional star is given for each tabular, array and the generic ones, else if the optional argument is used for those specific one (either tabular or array) and if both is omitted the generic one
  • \htabClearAllHooks<*>[<specific>] clears all hooks of either tabular, array or the generic ones (or all if the star is given)

The row count can be accessed via htabrow, which is accessible via \value/\arabic/\roman/etc. (but is no real LaTeX-counter).

\documentclass{article}

\usepackage{array} \usepackage{etoolbox}

\ExplSyntaxOn \makeatletter % row counter \int_new:N \g_htab_row_int \cs_new_eq:NN \c@htabrow \g_htab_row_int \seq_new:N \g__htab_row_stack_seq \cs_new_protected:Npn __htab_push_row:% >>= { \seq_gpush:NV \g__htab_row_stack_seq \g_htab_row_int \int_gzero:N \g_htab_row_int }% =<< \cs_new_protected:Npn __htab_pop_row:% >>= { \group_begin: \seq_gpop:NN \g__htab_row_stack_seq \l_tmpa_tl \int_gset:Nn \g_htab_row_int \l_tmpa_tl \group_end: }% =<< \seq_const_from_clist:Nn \c__htab_hook_names_seq% >>= { start, stop, bol, eol, begin, end, lastline }% =<<

% hooks \seq_map_inline:Nn \c__htab_hook_names_seq % >>= { \clist_map_inline:nn { tabular, array } { \tl_new:c { l_htab_hook_ ##1/#1 tl } } \tl_new:c { l_htab_hook #1 tl } }% =<< \cs_new:Npn \htab@hook@start@@% >>= { \l_htab_hook_start_tl }% =<< \cs_new:Npn \htab@hook@stop@@% >>= { \l_htab_hook_stop_tl }% =<< \cs_new:Npn \htab@hook@bol@@% >>= { \noalign { \int_gincr:N \g_htab_row_int } \l_htab_hook_bol_tl }% =<< \cs_new:Npn \htab@hook@eol@@% >>= { \l_htab_hook_eol_tl }% =<< \cs_new:Npn \htab@hook@lastline@@% >>= { \l_htab_hook_lastline_tl }% =<< \cs_new:Npn \htab@hook@begin@@% >>= { \l_htab_hook_begin_tl }% =<< \cs_new:Npn \htab@hook@end@@% >>= { \l_htab_hook_end_tl }% =<< \cs_new_protected:Npn __htab_add_hooks:n #1% >>= { \seq_map_inline:Nn \c__htab_hook_names_seq { \tl_put_right:cv { l_htab_hook ##1 tl } { l_htab_hook #1/##1 _tl } } }% =<< \cs_generate_variant:Nn \tl_put_right:Nn { cv }

% patches \msg_new:nnn { htab } { patch~failed } { Patching~ of~ #1 failed. } \msg_new:nnn { htab } { unknown~hook } { Unknown~ hook~ #1. } \cs_new_protected:Npn __htab_pretocmd:Nn #1 #2% >>= { \pretocmd #1 {#2} {} { \msg_error:nnn { htab } { patch~failed } {#1} } }% =<< \cs_new_protected:Npn __htab_apptocmd:Nn #1 #2% >>= { \apptocmd #1 {#2} {} { \msg_error:nnn { htab } { patch~failed } {#1} } }% =<< \cs_new_protected:Npn __htab_patchcmd:Nnn #1 #2 #3% >>= { \patchcmd #1 {#2} {#3} {} { \msg_error:nnn { htab } { patch~failed} {#1} } }% =<< \cs_new_protected:Npn __htab_backup:% >>= { \cs_set_eq:NN __htab_unpatched_@@array: @@array \cs_set_eq:NN __htab_unpatched_@arraycr: @arraycr \cs_set_eq:NN __htab_unpatched_@xarraycr: @xarraycr \cs_set_eq:NN __htab_unpatched_@xargarraycr: @xargarraycr \cs_set_eq:NN __htab_unpatched_@yargarraycr: @yargarraycr \cs_set_eq:NN __htab_unpatched_tabular: \tabular \cs_set_eq:NN __htab_unpatched_endtabular: \endtabular \cs_set_eq:NN __htab_unpatched_array: \array \cs_set_eq:NN __htab_unpatched_endarray: \endarray }% =<< \cs_new_protected:Npn \htab@unpatch@@% >>= { \cs_set_eq:NN @@array __htab_unpatched_@@array: \cs_set_eq:NN @arraycr __htab_unpatched_@arraycr: \cs_set_eq:NN @xarraycr __htab_unpatched_@xarraycr: \cs_set_eq:NN @xargarraycr __htab_unpatched_@xargarraycr: \cs_set_eq:NN @yargarraycr __htab_unpatched_@yargarraycr: \cs_set_eq:NN \tabular __htab_unpatched_tabular: \cs_set_eq:NN \endtabular __htab_unpatched_endtabular: \cs_set_eq:NN \array __htab_unpatched_array: \cs_set_eq:NN \endarray __htab_unpatched_endarray: }% =<< % due to etoolbox restrictions names of macros that are patched in don't follow % the expl3 naming conventions \cs_new_protected:Npn __htab_patch:n #1% >>= { \bool_lazy_or:nnT { \cs_if_eq_p:NN __htab_unpatched_tabular: \tabular } { !\cs_if_exist_p:N __htab_unpatched_tabular: } { __htab_backup: __htab_pretocmd:Nn \tabular \htab@unpatch@@ __htab_pretocmd:Nn \array \htab@unpatch@@ __htab_pretocmd:Nn @arraycr \htab@hook@eol@@ __htab_apptocmd:Nn @yargarraycr \htab@hook@bol@@ __htab_apptocmd:Nn @xargarraycr \htab@hook@bol@@ __htab_patchcmd:Nnn @xarraycr \cr { \cr \htab@hook@bol@@ } __htab_patchcmd:Nnn \endarray \crcr { \crcr \htab@hook@stop@@ } __htab_patchcmd:Nnn @@array \cr { \cr \noexpand \htab@hook@start@@ \noexpand \htab@hook@bol@@ } __htab_add_hooks:n {#1} } }% =<<

\cs_new_protected:Npn \htab_add_to_hook:nnn #1#2#3% >>= { \tl_if_exist:cTF { l_htab_hook_ #1#2 tl } { \tl_put_right:cn { l_htab_hook #1#2 tl } {#3} } { \msg_error:nnn { htab } { unknown~hook } {#1#2} } }% =<< \NewDocumentCommand \htabAddToHook { o m m }% >>= { \tl_if_novalue:nTF {#1} { \htab_add_to_hook:nnn {} } { \htab_add_to_hook:nnn { #1/ } } {#2} {#3} }% =<< \cs_new_protected:Npn \htab_clear_hook:n #1% >>= { \tl_clear:c { l_htab_hook #1 _tl } }% =<< \NewDocumentCommand \htabClearHook { s o m }% >>= { \IfBooleanTF {#1} { \htab_clear_hook:n { tabular/#3 } \htab_clear_hook:n { array/#3 } \htab_clear_hook:n {#3} } { \tl_if_novalue:nTF {#2} { \htab_clear_hook:n {#3} } { \htab_clear_hook:n { #2/#3 } } } }% =<< \cs_new_protected:Npn \htab_clear_all_hooks:n #1% >>= { \seq_map_inline:Nn \c__htab_hook_names_seq { \htab_clear_hook:n { #1##1 } } }% =<< \NewDocumentCommand \htabClearAllHooks { s o }% >>= { \IfBooleanTF {#1} { \htab_clear_all_hooks:n { tabular/ } \htab_clear_all_hooks:n { array/ } \htab_clear_all_hooks:n {} } { \tl_if_novalue:nTF {#2} { \htab_clear_all_hooks:n {} } { \htab_clear_all_hooks:n { #2/ } } } }% =<<

\NewDocumentEnvironment { htabular } {}% >>= { __htab_push_row: __htab_patch:n { tabular } \htab@hook@begin@@ __htab_unpatched_tabular: } { \ifvmode\else\expandafter\htab@hook@lastline@@\fi \endtabular \htab@hook@end@@ __htab_pop_row: }% =<< \NewDocumentEnvironment { harray } {}% >>= { __htab_push_row: __htab_patch:n { array } __htab_unpatched_array: } { \ifvmode\else\expandafter\htab@hook@eol@@\fi \endarray __htab_pop_row: }% =<< \makeatother \ExplSyntaxOff

% this might be a good idea, this way a htabular and harray always behave as % if the last line was ended with \\ (so should give consistent results) \htabAddToHook{lastline}{\}

\usepackage{booktabs}% just for an example

\newcommand\smartmidrule[1] % maybe this isn't really smart :P {% \ifnum\value{htabrow}=\numexpr#1\relax \midrule \fi }

\newcolumntype\pseudoenum{@{\hskip\tabcolsep\arabic{htabrow}.\hskip2\tabcolsep}}

\begin{document} \noindent Putting \verb|\hline| in \texttt{bol}:\par \htabAddToHook[tabular]{bol}{\hline} \begin{htabular}{ll} a & b \ c & d % would have no \hline without the \htabAddToHook{lastline}{\} \end{htabular}

\noindent array is not affected since \texttt{[tabular]} was used:\par $\begin{harray}{ll} a & b \ c & d \end{harray}$

\bigskip \noindent Autostyling with \texttt{booktabs} macros and putting \texttt{end} in \texttt{eol}:\par \htabClearHook*{bol} \htabAddToHook{start}{\toprule} \htabAddToHook{bol}{\smartmidrule{2}} \htabAddToHook{stop}{\bottomrule} \htabAddToHook{eol}{\unskip end} \begin{htabular}{ll} my & headrow \ a & b \ c & d \ \end{htabular}

\bigskip \noindent Nested \texttt{tabular}s aren't affected:\par \htabClearHook{eol} \begin{htabular}{ll} my & headrow \ \begin{tabular}{|c|}a\A\end{tabular} & b \ c & d \end{htabular}

\bigskip \noindent Nested \texttt{htabular}s don't break the row count (but are affected):\par \htabClearAllHooks* \htabAddToHook[tabular]{start}{\hline} \htabAddToHook[tabular]{stop}{\hline} \begin{htabular}{\pseudoenum ll} Some & tabular \ points & \begin{htabular}{\pseudoenum c@{}}making\absolutely\end{htabular} \ no & sense \end{htabular} \end{document}

enter image description here

Skillmon
  • 60,462