# How to capture a colon in a macro?

I am trying to capture punctuation marks and I have a number of macros, two of which are shown in the minimal below:

\documentclass{article}
\usepackage[english]{babel}
\def\isColon#1{%
\ifx:#1
colon!
\else
Not a colon!
\fi}

\def\isPeriod#1{%
\ifx.#1
Period!
\else
Not a period!
\fi}

\begin{document}
\isColon:

\isPeriod.
\end{document}


If the babel language is changed to [french,english] the colon is not captured, as babel makes it active. What modifications are required to the isColon macro to work in both cases?

-

I'd use \if:

\documentclass{article}
\usepackage[english]{babel}
\def\isColon#1{%
\if:\noexpand#1%
colon!
\else
Not a colon!
\fi}

\def\isPeriod#1{%
\if.\noexpand#1%
Period!
\else
Not a period!
\fi}

\begin{document}
\isColon:

\isPeriod.
\end{document}


Another alternative would be \pdfstrcmp, but that is probably overkill here.

-
Note: The TeX \if test is used much less often than \ifx, but as it compares two character codes it is idea here. The \pdfstrcmp primitive, available in pdfTeX, XeTeX (as \strcmp) and in LuaTeX (using a small piece of Lua) does a general character-based comparison, and is therefore useful in cases where you have two strings and want to ignore category codes. –  Joseph Wright Dec 26 '10 at 19:07
\documentclass{article}
\usepackage[english,french]{babel}
\shorthandon{:}
\def\isColon#1{%
\ifx:#1
colon!
\else
Not a colon!
\fi}%

\def\isPeriod#1{%
\ifx.#1
Period!
\else
Not a period!
\fi}

\begin{document}
\isColon: \isColon;

\isPeriod.
\end{document}

-
This solution is not expandable: while this was not requested in the question, I think it's often useful to do it if possible. –  Joseph Wright Dec 26 '10 at 9:41
@Joseph: true, I changed the code –  Herbert Dec 26 '10 at 10:15

The problem is that outside of the macro, : is a macro expanding to \active@prefix :\normal@char: , but inside the macro, : is just the character :. I believe this is because Babel registers the catcode changes with \AtBeginDocument; moving the macro definition after \begin{document} works just fine. I also found that simply changing \ifx to \if worked fine without moving the macro definition. But I think the cleanest solution (though a slightly specific one) is probably to change :'s catcode to active for the definition of \isColon:

\catcode\:=\active
\def\isColon#1{%
\ifx:#1
colon!
\else
Not a colon!
\fi}
\catcode\:=12


Edit: In case : has its catcode changed by something else, you ought to use this version instead:

\begingroup
\catcode\:=\active
\gdef\isColon#1{%
\ifx:#1
colon!%
\else
Not a colon!%
\fi}
\endgroup


This resets the catcode of : to whatever it was before, not simply to 12.

-
@Antal: you are taking an incompatibility risk here when reverting : to catcode 12: maybe its catcode was changed by some other package. For these cases, I believe that it is better to put \begingroup before the catcode change, and \endgroup instead of reverting the catcode. Then we also need to change \def by \gdef. –  Bruno Le Floch Jan 14 '11 at 12:50
You're definitely right. I try to remember to do that, but not always with success. –  Antal S-Z Jan 14 '11 at 21:16
@Bruno,@Antal: you can also do the same without a catcode change and without a \gdef thanks to a lccode trick: \begingroup\lccode\~=\:\lowercase{\endgroup\def\isColon#1{\ifx~#1 colon!\else Not a colon!\fi}} –  Philippe Goutet Jan 14 '11 at 22:25
@Philippe: I've never seen that before—how does it work? –  Antal S-Z Jan 15 '11 at 4:18
@Antal: with the \lccode change, \lowercase replaces ~ with : but doesn't change the catcode, so you obtain a :` with catcode 13. More details in tex.stackexchange.com/questions/7359 –  Philippe Goutet Jan 15 '11 at 9:43