8

I wanted to redefine \item temporarily, but also wanted to allow it to behave as much as the normal \item should. So, I thought by closely following the definition of \item, I would get the results I wanted. However the following MWE doesn't recognize the optional argument.

\documentclass{article}
\pagestyle{empty}
\begin{document}

\begin{enumerate}
\begingroup
  \makeatletter
  \def\item{\@ifnextchar [\@item{\@noitemargtrue \@item[\@itemlabel]} \rule[-2ex]{0.8pt}{5ex}\endgroup}%%'
  \makeatother
\item[test]   A
\item B
\item C
\item D
\end{enumerate}

\end{document}

Why is it failing?

A.Ellett
  • 50,533

3 Answers3

6

Take a look at the definition of \@ifnextchar (taken from latex.ltx):

\long\def\@ifnextchar#1#2#3{%
  \let\reserved@d=#1%
  \def\reserved@a{#2}%
  \def\reserved@b{#3}%
  \futurelet\@let@token\@ifnch}

It stores the three arguments in three separate macros identified by \reserved@?. Then, it calls \futurelet\@let@token\@ifnch. The behaviour of \futurelet (as described in Where do I find \futurelet's nasty behaviour documented?) is:

TeX also allows the construction \futurelet\cs<token1><token2>, which has the effect of \let\cs = <token2><token1><token2>.

In a simplified case, a redefinition of \item to have anything following the \@ifnextchar construction would cause problems, since <token2> will no be [ anymore.


How would you get around this limitation? Don't use \@ifnextchar directly. Here's a naive approach using a traditional \renewcommand instead:

enter image description here

\documentclass{article}
\pagestyle{empty}
\begin{document}

\begin{enumerate}
  \begingroup
  \makeatletter
  \let\olditem\item
  \renewcommand{\item}[1][\@empty]{%
    \ifx#1\@empty
      \olditem%
    \else
      \@item[#1] \rule[-2ex]{0.8pt}{5ex}\endgroup%
    \fi}%
  \makeatother
  \item[abc]   A
  \item B
  \item[xyz] C
  \item D
\end{enumerate}

\end{document}
Werner
  • 603,163
  • Your code seems to be missing a \fi, but it nevertheless compiles. What's happening that you don't seem to need the \fi to complete \ifx? – A.Ellett Aug 03 '13 at 12:38
  • You are missing the \fi if you don't pass the optional argument to \item. It seems weird that LaTeX doesn't see this error when the optional argument is present. – A.Ellett Aug 03 '13 at 12:45
  • @A.Ellett: That was a fluke. Adding a non \item[..] as the first \item reports an error (missing \fi). – Werner Aug 03 '13 at 15:45
4

As Werner points out, you cannot add anything 'after' \@ifnextchar. You also need to make sure that \@item is followed by a [, so you can't just add the rule to both branches of the \@ifnextchar result. At the same time, I'd avoid using a \begingroup outside a macro and an \endgroup inside one, if possible. That leads to a solution of the form:

\documentclass{article}
\usepackage{etoolbox}
\pagestyle{empty}
\tracingpatches
\begin{document}

\begin{enumerate}
  \let\origitem\item
    \makeatletter
  \let\orig@item\@item
  \patchcmd{\@item}{\ignorespaces}
    {%
      \rule[-2ex]{0.8pt}{5ex}%
      \let\@item\orig@item
      \ignorespaces
    }
    {}{}
  \def\item{%
    \let\item\origitem
    \@ifnextchar [%]
      \@item
      {\@noitemargtrue \@item[\@itemlabel]}%
  }
  \makeatother
\item[test] A
\item B
\item C
\item D
\end{enumerate}

\end{document}

Notice that the use of \item will restore the original definition, as will use of \@item, but without a group.

Joseph Wright
  • 259,911
  • 34
  • 706
  • 1,036
  • I like this idea of patching \@item. But once you've patched \@item there's no need for \def\item{...} since the contents of the definition (apart from your \let\item\origitem) is the definition as I found it in latex.ltx. – A.Ellett Aug 03 '13 at 12:43
0

Though not stated in the original question, ultimately I wanted something a bit more dynamic: namely, I wanted the redefinition of \item to be able to persist for more than just its first occurrence. I also wanted something that will behave as expected if nested within another list.

Prior to seeing @werner 's and @wright 's solutions, this is what I wound up working out:

\documentclass{article}
\makeatletter
\let\ae@original@item\item
\newenvironment{myenum}[1]
  {\begin{enumerate}
     \let\item\ae@original@item
     \def\ae@how@far@advanced{1}%%'
     \newif\ifae@unfinished@
     \begingroup
     \ae@unfinished@true
     \def\ae@item[##1]{%%'
       \@item[##1] \rule[-2ex]{0.8pt}{5ex}%%'
       \ifnum\ae@how@far@advanced=#1\relax
         \ae@unfinished@false\endgroup
       \fi
       \edef\ae@how@far@advanced{\number\numexpr\ae@how@far@advanced+1\relax}\ignorespaces}
     \def\item{\@ifnextchar [%%]
        \ae@item{\@noitemargtrue \ae@item[\@itemlabel]}}%%'
  }%%'
  {\ifae@unfinished@\endgroup\fi
   \end{enumerate}}
\makeatother

\begin{document}

\begin{myenum}{5}
  \item A
  \item [test]   B
  \item C
    \begin{myenum}{2}
      \item Bc
      \item A
      \item C
      \item D
    \end{myenum}
  \item D
  \item E
  \item F
\end{myenum}

\end{document}

enter image description here

I suppose that \ae@how@far@advanced could really have been a counter. But, I nevertheless need something since I'm counting occurrences of \item and not the value of enumi (and family), which isn't advanced when \item[...] is used.

I like both the solutions offered. @wright 's solution nicely shows how to avoid my use of \begingroup and \endgroup. But, I'd prefer not to have call another package. @werner 's solution is very elegant in how it avoids the call to \@ifnextchar and manages to test for an empty argument.

I chose to post my own solution because I eventually saw how I was

  1. misunderstanding when and what is getting expanded and
  2. misunderstanding that \@ifnextchar was not ever going to see the next character after my macro since I'd thrown a bunch of noise in the way.

But I was able work out a solution with \@ifnextchar and to properly redirect things.

A.Ellett
  • 50,533