7

With the substr (or xstring) package you can, of course, identify whether a control sequence contains some text string:

\documentclass{article}
\usepackage{substr}

\def\somecontrolseq{some silly text}

\begin{document}
\IfSubStringInString{silly}{\somecontrolseq}
  {true}
  {false}
\end{document}

But, what if \somecontrolseq contained itself control sequences you want to check for like in the following non-working example?

\documentclass{article}
\usepackage{substr}

\def\somecontrolseq{\macro{silly} text}
\def\macro#1{some #1}

\begin{document}
\IfSubStringInString{\detokenize{\macro}}{\somecontrolseq}
  {true}
  {false}
\end{document}

I see problems concering the catcodes of the \somecontrolseq content and \detokenize{\macro}, and (probabily more crucial) the expansion of \somecontrolseq.

  • With other words, you want to check if a token contains a cs? Unexpanded, of course – user31729 Jul 18 '15 at 14:56
  • 1
    (Untested) Perhaps the \noexpandarg feature of xstring could provide some help – user31729 Jul 18 '15 at 15:00
  • 1
    @GustavoMezzetti, I dont want to compare the names of the control sequences, but if one appears in the replacement text of another. – Ruben Jul 18 '15 at 19:41
  • 1
    But the two problems are related. If you still cannot see why, I can post an answer based on the technique of delimited arguments (this is what I was hinting to with my previous comment); but I am reluctant to do so, since I believe that the two answers that has already been given are quite satisfactory. – GuM Jul 18 '15 at 22:30
  • 1
    @GustavoMezzetti, ok, fair enough. Now I see what the "somehow" was meant to be ;) When writing the question I thought of an answer that used delimited arguments to loop over the elements of the parent control sequence. But, I wasn't totally sure how to get though it in detail. For all I care you could add another answer. Not only for me to see what you have in mind, but also for possible future readers who are interested in learning more about this topic. – Ruben Jul 19 '15 at 1:20
8

Luckily I discovered that the two problems mentioned in the OP are really the only ones and that they are solvable straight forward, after(!) trying out lots of other things :)

The expansion problem is solved by a simple \expandafter "before" detokenizing the result (i.e. \detokenize\expandafter{\somecontrolseq}), which again solves the problem of the catcodes, as both the substring I search for and the compared string now have catcode 12.

The example

\documentclass{article}
\usepackage{substr}

\def\somecontrolseq{\macro{silly} text}
\def\macro#1{some #1}

\begin{document}
\IfSubStringInString{\detokenize{\macro}}{\detokenize\expandafter{\somecontrolseq}}
  {true}
  {false}
\end{document}

will compile to simply

true

as requsted.

  • Such an exquisite answer! Thank you for this piece of TeX art! – ByteBiter Jul 27 '15 at 20:54
4

I’m adding this answer only to justify my assertion that the present question is “somehow related” to How can I check whether two control sequences have the same name? (see the comments).

Werner’s solution is great, but it has a slight drawback and a characteristic that, depending on what you want to do, can turn out to be a useful feature, or not.

The useful feature, that you might want to disable, is that it finds the control sequence being sought for at any level of expansion; the drawback is that the test is not purely expandable, since it is executed in the stomach.

The following approach uses two steps: in the first step, that requires digestion (since it needs to execute assignments), the name to be searched for is defined; having done that—and this is the second step—a purely expandable test can be performed to find out whether the replacement text of another macro contains the control sequence that has been declared before. The test does not expand the replacement text any further (that is, only one level of expansion is considered).

\documentclass{article}

\makeatletter

% Check for availability of names:
\@ifdefinable\MacroDoesNotContainSavedName{}
\@ifdefinable\@MacroDoesNotContainSavedName{}

\newcommand*\DefineMacroNameToBeSearched[1]{%
  \def\@MacroDoesNotContainSavedName##1#1##2\@@@{%
    \ifx\@empty##2\@empty % if ##2 is empty
  }%
  \def\MacroDoesNotContainSavedName##1{%
    TT\fi
    \expandafter\@MacroDoesNotContainSavedName##1#1\@@@
  }%
}
% \newcommand*\checkdefinitions{%
%   \show\MacroDoesNotContainSavedName
%   \show\@MacroDoesNotContainSavedName
% }

\makeatother

\newcommand*{\DonaldDuck}{Quack!}
\newcommand*{\Duckburg}{%
  Ducks living in Duckburg: \UncleScrooge, \DonaldDuck,
  \Huey, \Dewey, \Louie, and many others.%
}
\newcommand*{\Micetown}{%
  Mices living in Micetown: \MickeyMouse, \MinnieMouse,~\ldots
}
\newcommand*{\MinnieMouse}{%
  I'm secretly in love with \DonalDuck!%
}



\begin{document}

\DefineMacroNameToBeSearched{\DonaldDuck}
% \DefineMacroNameToBeSearched{\MickeyMouse}
% \DefineMacroNameToBeSearched{\Huey}
% \checkdefinitions

Test for Duckburg: the macro
\if\MacroDoesNotContainSavedName{\Duckburg}%
  DOESN'T CONTAIN%
\else
  CONTAINS%
\fi
\ the saved name.

Test for Micetown: the macro
\if\MacroDoesNotContainSavedName{\Micetown}%
  DOESN'T CONTAIN%
\else
  CONTAINS%
\fi
\ the saved name.

\end{document}

You may uncomment various section of this code to see different behaviors. Some of the commented-out code serves diagnostic purposes.

This code requires nothing more than the original Knuth’s TeX. The test is completely independent of the meaning, and type (macro, primitive command, …), of the control sequence being searched for, which can also be undefined. Drawback: you cannot reliably test for the presence of a macro named “\@empty”; this can be cured in the usual way with \detokenize (without destroying the property of the test of being purely expandable).

3

If the containment macro doesn't do anything special (that would survive a group), you can set the contents in a box. This allows you to test for something without anything being printed:

true
false

\documentclass{article}

\def\somecontrolseq{\macro{silly} text}
\def\macro#1{some #1}

\newif\ifmacroexists
% \testifcontainsmacro{<macro>}{<another macro>}
\newcommand{\testifcontainsmacro}[2]{{
  \macroexistsfalse
  \def#1{\global\macroexiststrue}%
  \setbox0=\hbox{#2}%
  \ifmacroexists
    true
  \else
    false
  \fi
}}

\begin{document}

\testifcontainsmacro{\macro}{\somecontrolseq}

\testifcontainsmacro{\Macro}{\somecontrolseq}

\end{document}

The idea is to redefine the sought-after macro to set a boolean flag, after which you set the containment macro inside a box (that you never use). If the flag has been set, the macro exists within the containment (regardless of the expansion level), otherwise it doesn't.

The grouping of \testifcontainsmacro restores any setting to its original values, unless it's meant to survive the group.

Of course, one drawback is if you only want to test for (say) the first expansion, and not subsequent ones...

  • Not bad! I like the box approach. – Ruben Jul 18 '15 at 19:42

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.