Removing a backslash from a character sequence
Joseph has given a working solution. I'd like to explain what goes wrong with your code.
First attempt
\newcommand\removebs[1]{\if#1\char92\else#1\fi}
\newcommand\macroname[1]{%
\expandafter\removebs\detokenize{#1}}
With \macroname{\relax}
you get
\expandafter\removebs\detokenize{\relax}
and then (using •
to separate tokens and <space>
do denote a space token)
\removebs • \ • r • e • l • a • x • <space>
which becomes
\if • \ • \char • 9 • 2 • \else • \ • \fi • r • e • l • a • x • <space>
and the comparison is between a category code 12 backslash and the token \char
, which of course leads to false.
The code \char92
are instruction to print character 92 from the current font.
One might correct the code by checking against a real category code 12 backslash:
\makeatletter
\newcommand{\removebs}[1]{\if#1\@backslashchar\else#1\fi}
\makeatother
but the space produced by \detokenize
after \relax
would remain.
Second attempt
{
\catcode`\|=0
|catcode`|\=12
|global|def|removebs#1{|if#1\|else#1|fi}
}
This works because it just implements the check against a category code 12 backslash, but it's not necessary, as the token is already available as \@backslashchar
in the LaTeX kernel.
An alternative way, without global definitions and category changes, can be
\begingroup\lccode`\|=`\\
\lowercase{\endgroup\def\removebs#1{\if#1|\else#1\fi}}
where the only token that is converted to its lowercase equivalent is |
(to a backslash).
Proposed code
% \makeatletter
% \newcommand{\removebs}[1]{\if#1\@backslashchar\else#1\fi}
% \makeatother
\begingroup\lccode`\|=`\\
\lowercase{\endgroup\def\removebs#1{\if#1|\else#1\fi}}
\newcommand{\macroname}[1]{\expandafter\removebs\string#1}
Since all you want is to get the argument letters, it doesn't matter if the first letter in \macroname{itemize}
has category code 12.
TeX works with tokens, and what you need to know is that a control sequence such as \foo
is not tested by \if
as a series of characters, but as a single 'unit'. There a a few primitives that will turn control sequences back into single tokens: \detokenize
does this for a set of tokens, while \string
does so for a single token. The latter is also available in all TeX versions (\detokenize
requires e-TeX).
As you observe, detokenizing the input allows you to do a comparison to a category code 'other' \
. An alternative approach would be to test if #1
is a control sequence, using the fact that \ifcat
treats all (unexpanded) control sequences as identical. You can then use \string
to convert to multiple tokens, and \@gobble
to remove the very first character:
\documentclass{article}
\makeatletter
\newcommand{\removeabs}[1]{%
\ifcat\relax\noexpand#1%
\expandafter\expandafter\expandafter\@gobble\expandafter\string
\fi
#1%
}
\makeatother
\begin{document}
\removeabs{foo}
\removeabs{\bar}
\end{document}
The above is all fine as long as \escapechar
is printable and not a space. Using some code from LaTeX3 'translated' back to primitives, you can set things up to be robust for all escape characters:
\documentclass{article}
\makeatletter
\newcommand{\removeabs}[1]{%
\ifcat\relax\noexpand#1%
\expandafter\removeabs@aux@i
\fi
#1%
}
\newcommand*{\removeabs@aux@i}{%
\romannumeral
\if\string\ \removeabs@aux@ii\fi
\expandafter\removeabs@aux@iii\string
}
\newcommand{\removeabs@aux@ii}{}
\long\def\removeabs@aux@ii#1\removeabs@aux@iii{%
-\number\fi\expandafter\z@
}
\newcommand{\removeabs@aux@iii}[1]{\z@}
\makeatother
\begin{document}
\removeabs{foo}
\removeabs{\bar}
\end{document}
(See the implementation of \cs_to_str:N
in the LaTeX3 docs for what is going on here!)