2

The macro \test below is used to test if its argument is blank, and it gives unexpected result.

What's wrong with my code?

Example:

\documentclass{article}
\usepackage{tikz,etoolbox}
\begin{document}
\pgfkeys{aaa/.initial={}}
\newcommand{\test}[1]{%
  \expandafter\ifblank\expandafter{#1}{blank}{#1}%
}
<\pgfkeysvalueof{/aaa}>% Typeset "<>"
|\test{\pgfkeysvalueof{/aaa}}|%I think it should be "|blank|", but actually it typesets "||". Why?
\end{document}
Rmano
  • 40,848
  • 3
  • 64
  • 125
lyl
  • 2,727

5 Answers5

4

\pgfkeysvalueof doesn't expand in one step, so a single \expandafter can't work here. You need to force expansion: probably easiest using the 'roman numeral trick':

\newcommand{\test}[1]{%
  \expandafter\ifblank\expandafter{\romannumeral-`Q#1 }{blank}{#1}%
}
Joseph Wright
  • 259,911
  • 34
  • 706
  • 1,036
  • How can I learn the number of steps to expand? – lyl May 17 '22 at 09:02
  • @lyl That depends on the implementation: in general commands don't specify how many expansions are required, hence using a 'forced' approach here. Some low-level stuff does specify expansion steps, e.g. bits of expl3, some of the Heiko Oberdiek bundle. – Joseph Wright May 17 '22 at 09:04
4

Rommannumeral trick does full expansion until there is something unexpandable. But if the argument is in the format bla\macro and the \macro expands to nk then rommannumeral trick fails. So, I suggest to use \expanded primitive:

\def\test#1{\expandafter\ifblank\expandafter{\expanded{#1}}{blank}{#1}}
wipet
  • 74,238
  • Thank you @wipet! And where does \expanded come from? – lyl May 17 '22 at 11:57
  • 2
    I thought about \expanded, but as we don't know the nature of the value to be tested, I'd avoid if possible. Also, for a blank test, it's unimportant what stops the expansion: anything which stops \romannumeral will count as 'not blank' – Joseph Wright May 17 '22 at 12:20
  • @lyl \expanded was originally proposed for pdfTeX 1.50 but was made public first in LuaTeX; it's been in all major engines for a few years now – Joseph Wright May 17 '22 at 12:34
  • Many thanks all! – lyl May 17 '22 at 12:37
  • Why does \expandafter\ifblank\expandafter{\expanded{\ }}{blank}{not blank} typeset not blank? – lyl May 24 '22 at 07:13
2

It's a one-liner with expl3:

\documentclass{article}
\usepackage[T1]{fontenc}

\usepackage{tikz}

\pgfkeys{aaa/.initial={}}

\ExplSyntaxOn \NewExpandableDocumentCommand{\test}{m} { \tl_if_blank:eTF { #1 } {blank} {#1} } \ExplSyntaxOff

\begin{document}

<\pgfkeysvalueof{/aaa}>% Typeset "<>"

|\test{\pgfkeysvalueof{/aaa}}|

\pgfkeys{aaa=xyz}

<\pgfkeysvalueof{/aaa}>% Typeset "<>"

|\test{\pgfkeysvalueof{/aaa}}|

\end{document}

enter image description here

egreg
  • 1,121,712
1

\pgfkeysvalueof{<full key>} need three expansions

According to its definition, it takes three expansions for \pgfkeysvalueof{<full key>} to expand to the value held in <full key>.

% run `latexdef -p pgfkeys -s \pgfkeysvalueof` and you'll get

% pgfkeys.code.tex, line 172: \def\pgfkeysvalueof#1{\csname pgfk@#1\endcsname}

  • step one, \pgfkeysvalueof expands to its replacement text, which is in the form of \csname ... \endcsname.
  • step two, \csname ... \endcsname expands to a control sequence that holds the value of <full key>. For /aaa, it's \pgfk@/aaa.
  • step three, that control sequence expands to its replacement text. For /aaa, the expansion result is empty.

The exact definition of \pgfkeysvalueof will change in the next release, see https://github.com/pgf-tikz/pgf/pull/1132, but the number of steps will remain the same.

Therefore

\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{\pgfkeysvalueof{/aaa}}

works.

\pgfkeysValueOf that takes only two steps

In general,

  • case 1: if the string to be tested is the replacement text of a macro, then OP's \test works for this macro, like \test{\cmd};
  • case 2: if the string to be tested is the (finite steps of) expansion of replace text of a macro, then in theory we can define a command \cmdExpanded, based on \cmd, that only need two expansions. That means, \expandafter\test\expandafter{\cmdExpanded}}} works.

The \pgfkeysValueOf provided in example below belongs to case 2.

In addition, if expandability is not the key point, then pgfkeys's \pgfkeysgetvalue{<full key>}{<macro>} is another choice, for example

\pgfkeysgetvalue{/aaa}{\temp} % here \temp belongs to case 1
|\test{\temp}|

Example, with test cases to distinguish "blank value" from "non-blank value that expands to blank":

\documentclass{article}
\usepackage{tikz,etoolbox}

% OP's definiton \newcommand{\test}[1]{% \expandafter\ifblank\expandafter{#1}{blank}{#1}% }

% egreg's answer \ExplSyntaxOn \NewExpandableDocumentCommand{\testExpl}{m} { \tl_if_blank:eTF { #1 } {blank} {#1} } \ExplSyntaxOff

% wipet's answer \def\testExpanded#1{% \expandafter\ifblank\expandafter{\expanded{#1}}{blank}{#1}}

% joseph's answer \newcommand{\testRomannumeral}[1]{% \expandafter\ifblank\expandafter{\romannumeral-`Q#1 }{blank}{#1}% }

% my solution 1, expand three steps \newcommand{\testThreeExps}[1]{% \expandafter\expandafter\expandafter\test \expandafter\expandafter\expandafter{#1}% }

\begin{document} \ttfamily

% three tests \pgfkeys{ aaa/.initial={}, % blank: empty bbb/.initial={{ }}, % blank: a space ccc/.initial={\empty}, % non-blank: expand to empty ddd/.initial={\space}, % non-blank: expand to a space eee/.initial={eee}, % non-blank: expand to non-blank }

\leavevmode\llap{Expected}% \foreach \j in {aaa, bbb, ccc, ddd, eee} {% % use \pgfkeysgetvalue, then one step expansion \pgfkeysgetvalue{/\j}{\temp}% \makebox[2cm]{|\test{\temp}|}% }\bigskip

\foreach \i in {\test, \testExpl, \testExpanded, \testRomannumeral, \testThreeExps} {% \leavevmode\llap{\detokenize\expandafter{\i}}% \foreach \j in {aaa, bbb, ccc, ddd, eee} {% \makebox[2cm]{|\i{\pgfkeysvalueof{/\j}}|}% } \par }

% \pgfkeysValueOf{<full key>} takes one step to expansion to <full key>'s value \newcommand{\pgfkeysValueOf}[1]{% \expanded{\unexpanded \expandafter\expandafter\expandafter\expandafter \expandafter\expandafter\expandafter{\pgfkeysvalueof{#1}}}% }

\leavevmode\hspace*{-4cm}% \verb|\expandafter\test\expandafter{\pgfkeysValueOf{<full key>}}|\par% \foreach \j in {aaa, bbb, ccc, ddd, eee} {% \makebox[2cm]{|\expandafter\test\expandafter{\pgfkeysValueOf{/\j}}|}% }

\end{document}

enter image description here

Note the test cases distinguish "blank key value" from "non-blank key value that expands to blank". Values of /aaa and /bbb are both blank, but values of /ccc and /ddd are neither blank, though they both expand to blank.

muzimuzhi Z
  • 26,474
  • Thank you so much!!! And, instead of aaa/.initial={}, only define a key, not to give it a value like aaa/.initial, is there a better way to detect whether a key has been allocated value? – lyl May 18 '22 at 01:36
  • Sorry I didn't get what "instead of aaa/.initial={}, only define a key, not to give it a value like aaa/.initial" mean. Also, are your keys only defined by handler /.initial and never by handler /.code and its friends? – muzimuzhi Z May 18 '22 at 06:23
  • In the form of aaa/.initial={}, the key "aaa" is allocated a value of empty. In the form of aaa/.initial, nothing is allocated to "aaa". In this case, I guess the output of \pgfkeysvalueof{aaa} will be \relax(or \empty? I'm not sure). If it's \relax, is there a better way to detect it? – lyl May 18 '22 at 07:12
  • 1
    According to how /initial is defined (\pgfkeys{/handlers/.initial/.code=\pgfkeyssetvalue{\pgfkeyscurrentpath}{#1}}, see pgfkeys.code.tex) and the doc for /.code, the default value of /.initial is \pgfkeysnovalue, which is defined by \def\pgfkeysnovalue{\pgfkeys@novalue} with \def\pgfkeys@novalue{}. That means, aaa/.initial is equivalent to aaa/.initial=\pgfkeysnovalue. – muzimuzhi Z May 18 '22 at 07:37
  • Then, how to detect this condition(only aaa/.initial, nothing is allocated to "aaa")? I want it gives blank. – lyl Jun 29 '22 at 06:39
  • 1
    Whenever a key is defined, it always stores some value, possibly empty. With \pgfkeys{a/.initial, b/.initial=\pgfkeysnovalue, c/.initial=}, /a and /b (internally macros \pgfk@/a and \pgfk@/b) both hold \pgfkeysnovalue, hence are indistinguishable. And /c holds nothing, equivalent to \@namedef{pgfk@/c}{}. Thus strictly speaking, you can't detect if a key aaa is defined by aaa/.initial and never used. In real life, you may assume that no one will use aaa/.initial=\pgfkeysnovalue and determine whether the value of key is \pgfkeysnovalue. – muzimuzhi Z Jun 29 '22 at 15:30
1

Another thing you can do is to test if the key typeset to a size-0 box (this is a mixed answer with the XY question here).

But notice that an explicit space is not the same thing as a blank content...

\documentclass{article}
\usepackage[T1]{fontenc}%<- you forgot this!
\usepackage{tikz,etoolbox}
\newsavebox{\measbox}
\newcommand{\test}[1]{%
    \savebox{\measbox}{#1}%
    \ifdim\wd\measbox>0pt \usebox\measbox \else blank\fi
}
\begin{document}
\pgfkeys{aaa/.initial={}}
<\pgfkeysvalueof{/aaa}>% Typesets "<>" (with the T1 fontenc, otherwise ¡?)

|\test{\pgfkeysvalueof{/aaa}}|% typesets |blank|

\pgfkeys{aaa=\bfseries}

|\test{\pgfkeysvalueof{/aaa}}|% typesets |blank|

\pgfkeys{aaa=~}

|\test{\pgfkeysvalueof{/aaa}}|% typesets | |

\pgfkeys{aaa=\bfseries ~}

|\test{\pgfkeysvalueof{/aaa}}|% typesets | | but slightly wider \end{document}

output from the snippet above

Rmano
  • 40,848
  • 3
  • 64
  • 125
  • Many thanks for this idea!! Then how to refer explicit space as blank? I mean the input of white space(s} will also get the typeset blank. – lyl Jun 29 '22 at 08:19
  • I do not know if it's really possible. TeX sees any typesettable box as a bunch of character boxes; the fact that there is something printed in them depend on the font (and TeX does not know; for what he knows, ~ can be a blank or not --- and sometimes it is not, really, like when you activate visible spaces in tt fonts). But you can try asking another question (you can use my snippet above for the question if you want, no problem, just cite it correctly) – Rmano Jun 29 '22 at 08:26