[Tex/LaTex] How to check if a column type is defined

columnsconditionalstables

Column types for tables can be defined and overwritten with \newcolumntype.
I would like to know if a column type is already defined for an example which shall only be executed in the case where the columntype is defined.

EDIT:
Similar to the answer of egreg I had also created this code:

\newcommand\isColumntypeDefined[1]{%
  \providebool{\tpl@coltype@#1}
  \ifcsdef{NC@find@\string#1}{
    \setboolean{\tpl@coltype@#1}{true}%
  }{%
    \setboolean{\tpl@coltype@#1}{false}
  }%
  \boolean{\tpl@coltype@#1}%
}

but that fails because \tpl@coltype@#1 does not create a valid command. This problem is related to latex @temps(wa) commands.
It would be great to know how to make this code compileable.

EDIT2: I changed the whole code to etoolbox definitions and finally ended with this

% Create list of predefined columntypes
\expandafter\let\csname columntype@l\endcsname\@empty
\expandafter\let\csname columntype@c\endcsname\@empty
\expandafter\let\csname columntype@r\endcsname\@empty
\expandafter\let\csname columntype@p\endcsname\@empty
\expandafter\let\csname columntype@m\endcsname\@empty
\expandafter\let\csname columntype@b\endcsname\@empty

\newcommand\CheckIfColumntypeDefined[1]{%
  \providebool{tpl@coltype@#1}
  \ifcsdef{NC@find@\string#1}%
    {\setbool{tpl@coltype@#1}{true}}%
    {\ifcsdef{columntype@\string#1}
      {\setbool{tpl@coltype@#1}{true}}%
      {\setbool{tpl@coltype@#1}{false}}%
    }%
}

\newcommand\isColumntypeDefined[1]{tpl@coltype@#1}

\newcommand\IfColumntypeDefined[3]{%
  \CheckIfColumntypeDefined{#1}
  \ifboolexpr{ bool{\isColumntypeDefined{#1}} }{#2}{#3}%
}

Best Answer

The instruction

\newcolumntype{N}{...}

defines the command \NC@find@N. You can check it with

\ifcsname NC@find@\string#1\endcsname

A \safenewcolumntype command can be defined by

\makeatletter
\newcommand\@gobbleoptandone[2][]{}
\expandafter\let\csname safenct@l\endcsname\@empty
\expandafter\let\csname safenct@c\endcsname\@empty
\expandafter\let\csname safenct@r\endcsname\@empty
\expandafter\let\csname safenct@p\endcsname\@empty
\expandafter\let\csname safenct@m\endcsname\@empty
\expandafter\let\csname safenct@b\endcsname\@empty
\expandafter\let\csname safenct@@\endcsname\@empty
\expandafter\let\csname safenct@!\endcsname\@empty
\expandafter\let\csname safenct@|\endcsname\@empty
\expandafter\let\csname safenct@<\endcsname\@empty
\expandafter\let\csname safenct@>\endcsname\@empty
\expandafter\let\csname safenct@=\endcsname\@empty
\def\safenct@error#1{\PackageError{mypackage}
    {Column type `\string#1' already defined}
    {The column type you're trying to define is already\MessageBreak
     existent; I'll ignore this new definition}}
\newcommand{\safenewcolumntype}[1]{%
  \@tempswafalse
  \ifcsname NC@find@\string#1\endcsname
    \safenct@error{#1}\@tempswatrue
  \fi
  \ifcsname safenct@#1\endcsname
    \safenct@error{#1}\@tempswatrue
  \fi
  \if@tempswa
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi{\@gobbleoptandone}{\newcolumntype{#1}}%
}
\makeatother

If the column type is already defined we put before the rest of the token list the command \@gobbleoptandone that will gobble the optional argument and the mandatory one: \safenewcolumntype{l}[1]{...} would become

\@gobbleoptandone[1]{...}

and all is good.

Otherwise we put \newcolumntype{#1} that will perform its duty. Of course it would be better to patch the original \newcolumntype command.

Patching the original definition

With the code between \makeatletter and \makeatother, we redefine the \newcolumntype command in such a way that it checks whether the column type is already defined or not and, in the former case, it disallows redefining the column type. One could define also a \renewcolumntype command, but it doesn't seem this important.

\documentclass{article}
\usepackage{array}

\makeatletter
\newcommand\@gobbleoptandone[2][]{}
\def\newcolumntype#1{%
  \@tempswafalse
  \edef\NC@char{\string#1}%
  \@ifundefined{NC@find@\NC@char}%
    {\@tfor\next:=<>clrmbp@!|\do
      {\if\next\NC@char
         \PackageError{safenct}{`\NC@char' is a primitive column type}
           {You're trying to redefine a primitive column type;\MessageBreak
            the redefinition will be ignored}%
         \global\@safenct@deftrue
       \fi}%
    \if@tempswa\else\NC@list\expandafter{\the\NC@list\NC@do#1}\fi}%
    {\PackageError{safenct}{Column `\NC@char' is already defined}
       {This column type is already defined;\MessageBreak
        the redefinition will be ignored}%
     \@tempswatrue}%
  \if@tempswa
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {\@gobbleoptandone}
  {\@namedef{NC@find@\NC@char}##1#1{\NC@{##1}}%
   \@ifnextchar[{\newcol@{\NC@char}}{\newcol@{\NC@char}[0]}}
}

\makeatother

\newcolumntype{N}{>{Y}l<{Z}}
\newcolumntype{N}{r}
\newcolumntype{l}{r}

\begin{document}
\begin{tabular}{N}
xxxx\\
x\\
xx
\end{tabular}
\end{document}

When \newcolumntype{x} is scanned, the routine sets the scratch conditional to false and checks if x is among the primitive column types and, if it is, issues an error message and sets the temporary conditional to true. Then it checks whether x is among the "derived" column types, that is, defined via \newcolumntype itself. If it is, the scratch conditional is again set to false. Finally, as in the code presented before, the proper action is taken: if the conditional is false the following part of the code for \newcolumntype is gobbled, otherwise the definition is performed.

Related Question