7

In answering another question, I ran into some issues trying to use a combination of \def and \char. Consider the following example:

\documentclass{minimal}
\usepackage{etoolbox}
\usepackage{xstring}
\makeatletter
\def\title@A{a A}
\def\title@B{b B}

\def\gettitle#1{
  \IfInteger{#1}{%
    \def\@tmp{\char#1}%
    \ifcsundef{title@\@tmp}{\@tmp}{\csname title@\@tmp\endcsname}%
  }{%
    \ifcsundef{title@#1}{#1}{\csname title@#1\endcsname}%
  }
}
\begin{document}
\gettitle{65}  \gettitle{A}

\gettitle{66}  \gettitle{B}

\gettitle{67}  \gettitle{C}
\end{document}

Output:

A a A

B b B

C C

It seems like \@tmp does not expand to be a single character, but instead expand to \char 65, which then doesn't work in \csname ... \endcsname. I thought that using \edef\@tmp{\char#1} would work, but it seems like \char is one of those functions that does not get expanded.

So in the end, is it possible to have \@tmp expand to A and not \char 65?

10

This can be done via the \lowercase trick:

\documentclass{minimal}
\usepackage{etoolbox}
\usepackage{xstring}
\makeatletter

\begingroup
  \catcode0=12 %
  \gdef\chrdef#1#2{%
    \begingroup
      \lccode0=\numexpr(#2)\relax
    \lowercase{\endgroup
      \def#1{^^@}%
    }%
  }%
\endgroup

\def\title@A{a A}
\def\title@B{b B}

\def\gettitle#1{
  \IfInteger{#1}{%
    \chrdef\@tmp{#1}%
    \ifcsundef{title@\@tmp}{\@tmp}{\csname title@\@tmp\endcsname}%
  }{%
    \ifcsundef{title@#1}{#1}{\csname title@#1\endcsname}%
  }
}
\begin{document}
\gettitle{65}  \gettitle{A}

\gettitle{66}  \gettitle{B}

\gettitle{67}  \gettitle{C}
\end{document}

Result

\lowercase (a not expandable command) converts the argument character token to its lower case character. This is configured by \lccode. The only exception is zero. If the \lccode is zero for a character, then the character remains unchanged. Therefore the example uses the character with code zero, which can be expressed as ^^@ in TeX notation. However, this character has usually the category code of invalid, there it is changed to 12 (other) in the outside group. Since the changes to \lccode should be reverted afterwards, the macro also uses a group.

8

The \char primitive isn't expandable. In XeTeX and LuaTeX we have \Uchar, which is and so can generate any character. For pdfTeX, with only 256 possible outcomes, we can set up a look-up table. Something like

\catcode`\@=11 %
\ifdefined\Uchar
  \def\Xchar#1{%
    \detokenize\expandafter{\Uchar\numexpr #1\relax}%
  }
\else
  \catcode`\^^@=12 %
  \begingroup
    \gdef\Xchar#1{%
     \romannumeral-`\0\expandafter\Xchar@aux\expandafter{\numexpr #1\relax}%
    }
    \gdef\Xchar@aux#1{%
      \ifcsname Xchar@\romannumeral #1\endcsname
        \csname Xchar@\romannumeral #1\endcsname
      \else
        \errmessage{Character out of range}%
      \fi
    }
    \count@=-1 %
    \lccode`\X=`\X %
    \loop
      \advance\count@ by 1 %
      \ifnum\count@<256 %
      \lccode`\^^@ = \count@ %
      \lowercase{%
        \expandafter\gdef\csname Xchar@\romannumeral\count@\endcsname{^^@}%
      }
    \repeat
  \endgroup
\fi
\catcode`\@=12 %

provide \Xchar to do the job. A more sophisticated version of the same idea can be used to provide different category code characters, and is implemented as \char_generate:nn in expl3. (The above is expandable at point-of-use as the lookup table is generated in advance.)

  • 1
    I'll have to analyse this answer some more to understand it... but since you mention \char_generate:nn, does that mean that there is a nice(r) LaTeX3 way of doing expanding \def\@tmp{\char#1}? – JP-Ellis Feb 27 '16 at 8:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.