13

In my earlier solution for Multivariate polynomial long division using LaTeX I had wrapped \cmidrule in a macro as its syntax alowing to shorten the end points was not standard:

\newcommand*{\CMidRule}[3]{\cmidrule[\cmidrulewidth](l{#1}r{#2}){#3}}%

I wanted to use xparse to to allow for the two shift parameters to be optional, but then ran into problems.

  1. Using this only once per line works just fine as illustrated in the above linked question. However, an attempt to use this for two separate \cmidrule is problematic in that a vertical shift is produced between subsequent uses (as illustrated in the third table below).

    enter image description here

  2. Furthermore my attempt to use \NewDocumentCommand from the xparse package to allow for two optional parameters does not even compile (hence have commented out the line in the fourth table). The error message is:

    Misplaced \noalign.
    \cmidrule ->\noalign 
                        {\ifnum 0=`}\fi \@ifnextchar [{\@cmidrule }{\@cmidrule ...
     l.40 \CMidRuleX{0.0ex}
                           {0.0ex}{1-1}\CMidRuleX{0.0ex}{0.0ex}{2-2}
    

References:

Question:

  • How do I properly define \NewDocumentCommand{\CMidRuleX}{O{0.0ex} O{0.0ex} m}{}?

Code:

\documentclass{article}
\usepackage{booktabs}
\usepackage{xparse}

\newcommand*{\CMidRule}[3]{\cmidrule[\cmidrulewidth](l{#1}r{#2}){#3}}%

\NewDocumentCommand{\CMidRuleX}{%
    O{0.0ex}% #1 = left adjust
    O{0.0ex}% #1 = right adjust
    m%   #3 = columns to span
    }{%
    \cmidrule[\cmidrulewidth](l{#1}r{#2}){#3}%
}%


\begin{document}
\section*{Direct use of cmidrule works just fine}
\begin{tabular}{rc}
  Left & Right \\
  \cmidrule{1-1}\cmidrule{2-2}
    2  &   3   \\
\end{tabular}
\hspace{1.0cm}
\begin{tabular}{rc}
  Left & Right \\
  \cmidrule[\cmidrulewidth](l{0.0ex}r{0.5ex}){1-1}\cmidrule[\cmidrulewidth](l{0.5ex}r{0.0ex}){2-2}
    2  &   3   \\
\end{tabular}
%
\bigskip
\section*{Wrapping cdmidrule in a macro does not work}
\begin{tabular}{rc}
  Left & Right \\
  \CMidRule{0.0ex}{0.5ex}{1-1}\CMidRule{0.5ex}{0.0ex}{2-2}
    2  &   3   \\
\end{tabular}
\hspace{1.0cm}
\begin{tabular}{rc}
  Left & Right \\
  %\CMidRuleX[0.0ex][0.0ex]{1-1}\CMidRuleX[0.0ex][0.0ex]{2-2}
    2  &   3   \\
\end{tabular}
\end{document}
2
  • On the fact that the two rules fail to align, you can see that there is a look-ahead in operation if you simply place the code you have inside a simple macro (\def\test{\cmidrule[\cmidrulewidth](l{0.5ex}r{0.0ex}){2-2}}). This will fail to line up, even though it 'should', as the first \cmidrule does not 'look' for a hidden following one.
    – Joseph Wright
    Jul 15, 2012 at 16:21
  • Related Question: What is wrong with this TeX macro. Oct 31, 2012 at 18:10

1 Answer 1

14

In tables, the material which is inserted between rows, for instance rules, is added using the TeX primitive \noalign. Namely, a simplistic definition for \hline could be \noalign{\hrule}: "insert a horizontal rule (\hrule) independently of the current alignment". This \noalign should appear immediately after the \cr primitive, used inside \\, which ends a line.

As I explained in a recent answer about how and why TeX looks for \noalign, TeX searches for \noalign after each \cr by expanding recursively the first token which follows \cr. It stops at any non-expandable token or any protected macro.

The issue is that in your case, TeX does not try hard enough, and fails to find the \noalign hidden in \CMidRuleX. In fact, looking for optional arguments robustly is not expandable, it requires assignments, and macros such as \hrule or \cmidrule have to play tricks on TeX to succeed in taking optional arguments.

First option in your case: use \DeclareExpandableDocumentCommand to create an expandable command. Searching for optional arguments will not be quite robust anymore, but it should be fine since your macro ends with a mandatory argument.

\DeclareExpandableDocumentCommand{\CMidRuleX}{%
    O{0.0ex}% #1 = left adjust
    O{0.0ex}% #1 = right adjust
    m%   #3 = columns to span
}{%
    \cmidrule[\cmidrulewidth](l{#1}r{#2}){#3}%
}%

Second option: play some tricks on TeX. Start with \noalign\bgroup to enter the no-alignment world, then look for optional arguments. After the optional arguments are found, end your \noalign group with \egroup, and insert the \cmidrule business.

\newcommand{\CMidRuleY}{\noalign\bgroup\CMidRuleYaux}
\NewDocumentCommand{\CMidRuleYaux}{%
    O{0.0ex}% #1 = left adjust
    O{0.0ex}% #1 = right adjust
    m%   #3 = columns to span
}{%
    \egroup
    \cmidrule[\cmidrulewidth](l{#1}r{#2}){#3}%
}%

Neither of those solutions solves the issue that two \cmidrule in a row fail to align. The only way for those to align is that the first \cmidrule must know that it is followed by another \cmidrule. Since we are hiding \cmidrule in macros, we must check for further \cmidrule ourselves.

\documentclass{article}
\usepackage{xparse, booktabs}
\newcommand{\CMidRuleZ}{\noalign\bgroup\CMidRuleZaux{}}
\ExplSyntaxOn
\makeatletter
\NewDocumentCommand{\CMidRuleZaux}{
    m % Material to reinsert before cmidrule.
    O{0.0ex} % #1 = left adjust
    O{0.0ex} % #1 = right adjust
    m  %   #3 = columns to span
}{
    \peek_meaning_remove_ignore_spaces:NTF \CMidRuleZ
      { \CMidRuleZaux { #1 \cmidrule[\cmidrulewidth](l{#2}r{#3}){#4} } }
      { \egroup #1 \cmidrule[\cmidrulewidth](l{#2}r{#3}){#4} }
}
\makeatother
\ExplSyntaxOff
\begin{document}
\begin{tabular}{ccccccccc}
  a & b & c & d & e & f & g & h \\
  \CMidRuleZ{1-2}
  \CMidRuleZ{4-5}
  \CMidRuleZ{7-8}
\end{tabular}
\end{document}

EDIT: changed the approach to collect all \CMidRuleZ first, then call the \cmidrule consecutively.

2
  • This works fine if only one \CMidRuleZ is used: \CMidRuleZ[0.0ex][0.0ex]{1-1} (although the table appears to be vertically shifted down). But, when I try to use it twice \CMidRuleZ[0.0ex][0.0ex]{1-1}\CMidRuleZ[0.0ex][0.0ex]{2-2} I get Missing } inserted.. Does this perhaps require TeXLive2012? Jul 15, 2012 at 21:34
  • @PeterGrill No, the code was just completely wrong. Jul 16, 2012 at 12:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .