12

From the pgfkeys.code.tex file:

% This is useful:

\def\pgfkeys@ifcsname#1\endcsname#2\else#3\fi{\expandafter\ifx\csname#1\endcsnam
e\relax#3\else#2\fi}%
\ifx\eTeXrevision\undefined%
\else%
  \expandafter\let\expandafter\pgfkeys@ifcsname\csname ifcsname\endcsname%
\fi

The command \pgfkeys@ifcsname is used internally to check if a key exists. If etex is present, it is simply \ifcsname. If not, then it is a fallback which (in the cases where it is used) is deemed good enough.

However, it doesn't work well with nested conditionals. It is sometimes used via another command:

\long\def\pgfkeysifdefined#1#2#3{\pgfkeys@ifcsname pgfk@#1\endcsname#2\else#3\fi}

The problem here is that if #2 or #3 contains another conditional then because \pgfkeys@ifcsname uses macro parameter matching instead of conditional matching, then an \else or \fi inside #2 or #3 can get matched instead of the given ones. Adding braces wouldn't work since that would add an extra set of braces.

Now, I intend reporting this on the PGF bug list, but thought I'd have a go at coming up with a replacement first.

I thought of:

\def\pgfkeys@ifcsname#1\endcsname{\ifx\csname#1\endcsname\relax\expandafter\iffalse\else\expandafter\iftrue\fi}

So my question: does this suffer from any more problems that the original didn't already suffer from?

(So the fact that \csname undefinedcommand\endcsname makes \undefinedcommand into \relax is not an objection to the new definition as it already exists in the original one.)

13

The PGF code is wrong under many respects.

  1. Delimiting arguments with \else and \fi doesn't help when the macro is used nested in other conditionals.

  2. The macro gives different results if the argument is relax, depending whether e-TeX is available or not.

I'd use the usual indirect approach:

\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}
\def\@pgfkeys@csname#1\endcsname{%
  TT\fi
  \expandafter\ifx\csname#1\endcsname\relax
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  \iffalse\iftrue
  }

to be used as

\if\@pgfkeys@csname something\endcsname
   true case%
\else
   false case%
\fi

There is no way to define a “macro conditional” that behaves correctly in nested conditional. The other approach is to use argument conditionals in LaTeX style with the usual \@firstoftwo and \@secondoftwo, basically the same as

\@ifundefined{macro}
  {<not defined or \relax case>}
  {<defined and not \relax case>}

It's easy to "reverse" this to get an \@ifdefined macro, just exchange \@firstoftwo and \@secondoftwo in the definition of \@ifundefined.

See Is a TeX macro defined?

11

It is important to note that regardless of the definition of \pgfkeys@ifcsname, it cannot be used within another TeX conditional safely: trying to do \if[...]\pgfkeys@ifcsname...\endcsname...\fi...\fi will lead to the first \fi closing the first \fi closing the \if, and the second \fi being unused. However, the current definition of \pgfkeys@ifcsname does not even let other TeX conditionals be used within its branches, and this can be fixed.


A small comment first: you probably forgot to put \expandafter before the \ifx in your definition, to expand the \csname. Let's put it back:

\def\pgfkeys@ifcsname#1\endcsname
  {%
    \expandafter\ifx\csname#1\endcsname\relax
      \expandafter\iffalse
    \else
      \expandafter\iftrue
    \fi
  }

The layout I chose is what is intended: if the \csname#1\endcsname construction yields \relax, then we want to call \iffalse and take the false branch which follows (because the command #1 was not defined), otherwise call \iftrue. This, unfortunately, fails.

  • If the command does not exist, the \ifx test is true, TeX sees \expandafter, expands \else, which causes it to jump to the matching \fi, but... there is no matching \fi, since TeX sees \iftrue\fi as a nested conditional.
  • If the command exists, the \ifx test is false and tries to skip to the following \fi. But it sees an \iffalse conditional, an \iftrue, the \fi matching \iftrue, and there would have to be two more \fi to close the \if...s.

Several approaches exist to avoid mixing up conditionals:

\def\pgfkeys@ifcsname#1\endcsname
  {%
    \expandafter\ifx\csname#1\endcsname\relax
      \expandafter\@firstoftwo
    \else
      \expandafter\@secondoftwo
    \fi
    \iffalse\iftrue
  }

with \@firstoftwo and \@secondoftwo defined as in LaTeX, \long\def\@firstoftwo#1#2{#1} and \long\def\@secondoftwo#1#2{#2}. Or

\def\pgfkeys@ifcsname#1\endcsname
  {%
    \csname
      \expandafter\ifx\csname#1\endcsname\relax
        iffalse%
      \else
        iftrue%
      \fi
    \endcsname
  }

To be extra robust, one can even avoid using a parameter delimited by \endcsname at the price of using an auxiliary function:

\def\pgfkeys@ifcsname{\expandafter\pgfkeys@ifcsname@\csname}
\long\def\pgfkeys@ifcsname@#1%
  {%
    \csname
      \ifx#1\relax
        iffalse%
      \else
        iftrue%
      \fi
    \endcsname
  }

Here I decided to make \pgfkeys@ifcsname@ long, in case the control sequence that is tested happens to be \par.

  • @DavidCarlisle See my edit above the line. – Bruno Le Floch Jun 3 '13 at 12: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.