3

I have a basic command I created, aimed to simply bold and move a text to gain time :

\newcommand*\Cote[1]{\footnotesize\emph{\textbf{#1}}}

When I want this result, I still have to use this command \Cote{My text}. I would like to automatize this, because the text I want to replace has always the same structure, which is to know a capital D with numbers next to it : D3 or D89 or D1023.

So I want a new command, which applies only when a number follows my letter D. Like this :

\newcommand*\D[1]{\footnotesize\emph{\textbf{#1}}}

but only when a number follows. Otherwise lots of my words would be replace and it would be a mess.

I tried to use a \ifnum0 command inside \newcommand, but unsuccessfully. Same failure trying to ferret around in other topics, like Conditionally replacing sequences of characters Any ideas ?

I use LuaLatex. A big thanks in advance !

Bernard
  • 271,350
Gaspalet
  • 165
  • 7
  • 1
    So what would the output look like if you have \D{1234} and \D{abcd}? Wouldn't you just use \D with numbers necessarily? Regardless, if you're using LuaLaTeX, you don't even have to use \D{<num>} as you should be able to intercept the input sequence and replace any occurrence of D<num> with D<num> (in bold). – Werner Jul 28 '21 at 20:58
  • LaTeX3 allows working with regex. Here it would be an easy way to achieve your goal. Take a look at this answer. – projetmbc Jul 29 '21 at 23:30
  • 1
    You wish to have a macro. At the time of expanding/carrying out a macro, everything is about tokens in TeX, not about numbers. What sets of tokens are to follow the explicit character-token D of catcode 11(letter) in cases where the formatting is to be applied to the D of catcode 11 and these following tokens? What about D\seven0 while \def\seven{7} is in effect? What about D\number`\A-- \\A` is an alphabetic constant and therefore a valid TeX--quantity denoting 65(dec)? – Ulrich Diez Jul 30 '21 at 14:26

3 Answers3

3

Here is a routine

\SurroundDDigits{⟨formatting command which takes one undelimited argument⟩}{⟨tokens⟩}

which in a tail-recursive loop examines ⟨tokens⟩ without expanding them. Tail-recursion/expansion is driven by \romannumeral until all tokens are examined. When all tokens are examined, then a TeX-⟨number⟩-quantity of value 0 trailed by the tokens forming by the result is delivered, yielding that TeX, due to encountering that ⟨number⟩-quantity, stops gathering a ⟨number⟩ for \romannumeral and for the gathered ⟨number⟩-quantity of value 0 delivers no token in return as 0 is not a positive number.

Sequences of pattern
D11 + non-empty sequence of tokens that are elements of {012, 112, 212, 312, 412, 512, 612, 712, 812, 912}
in the result are nested between curly braces ({1 and }2) whereof the left curly brace is preceded by ⟨formatting command which takes one undelimited argument⟩.

As a side-effect in the result

  • any explicit character-token of category code 1(begin-group) is replaced by an explicit { of category-code 1 and
  • any explicit character-token of category code 2(end-group) is replaced by an explicit } of category-code 2.

Due to \romannumeral-expansion the result is delivered by triggering two expansion-steps/after two "hits" with \expandafter.

I did my best to ensure that expansion will not be disturbed within alignments/tables etc, but no warranties.

For the sake of having fun I did not use any ε-TeX- or LuaTeX- or pdfTeX-extensions or the like and I avoided any \if...\else...\fi- and any \csname..\endcsname-expression.
Thus (unbalanced parts of) such expressions occurring within ⟨tokens⟩ does not disturb expansion.

Exclamation-mark as argument-delimiter is used a lot in one place.
This might probably bite you in case the uppercase code or lowercase code of the exclamation-mark is changed and the course of expansion-action is intercepted right at the corresponding stage for applying \uppercase/\lowercase. Due to expansion being driven via \romannumeral until obtaining the result this is extremely unlikely to happen if possible at all.

Usage is at your own risk.

\errorcontextlines=10000
\makeatletter
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, \UD@Exchange,
%% \UD@removespace, \UD@stopromannumeral, \UD@CheckWhetherNull,
%% \UD@CheckWhetherBrace, \UD@CheckWhetherLeadingExplicitSpace,
%% \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@removespace{\UD@Exchange{ }{\def\UD@removespace}{}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
  \expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument is blank (empty or only spaces):
%%-----------------------------------------------------------------------------
%% -- Take advantage of the fact that TeX discards space tokens when
%%    "fetching" _un_delimited arguments: --
%% \UD@CheckWhetherBlank{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that
%%                        argument which is to be checked is blank>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked is not blank>}%
\newcommand\UD@CheckWhetherBlank[1]{%
  \romannumeral\expandafter\expandafter\expandafter\UD@secondoftwo
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#1{}{}}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \CheckWhetherBrace{<Argument which is to be checked>}%
%%                   {<Tokens to be delivered in case that argument
%%                     which is to be checked has a leading
%%                     explicit catcode-1-character-token>}%
%%                   {<Tokens to be delivered in case that argument
%%                     which is to be checked does not have a
%%                     leading explicit catcode-1-character-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
  \romannumeral\expandafter\UD@secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@firstoftwo}{%
  \expandafter\UD@stopromannumeral\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingExplicitSpace{<Argument which is to be checked>}%
%%                                     {<Tokens to be delivered in case <argument
%%                                       which is to be checked> does have a
%%                                       leading explicit space-token>}%
%%                                     {<Tokens to be delivered in case <argument
%%                                       which is to be checked> does not have a
%%                                       a leading explicit space-token>}%
\newcommand\UD@CheckWhetherLeadingExplicitSpace[1]{%
  \romannumeral\UD@CheckWhetherNull{#1}%
  {\expandafter\UD@stopromannumeral\UD@secondoftwo}%
  {%
    % Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
    % and thus do not disturb when the test is carried out within \halign/\valign:
    \expandafter\UD@firstoftwo\expandafter{%
      \expandafter\expandafter\expandafter\UD@stopromannumeral
      \romannumeral\expandafter\UD@secondoftwo
      \string{\UD@CheckWhetherLeadingExplicitSpaceB.#1 }{}%
    }{}%
  }%
}%
\@ifdefinable\UD@CheckWhetherLeadingExplicitSpaceB{%
  \long\def\UD@CheckWhetherLeadingExplicitSpaceB#1 {%
    \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
    {\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
    {\expandafter\expandafter\expandafter\UD@stopromannumeral
     \expandafter\expandafter\expandafter}%
     \expandafter\UD@secondoftwo\expandafter{\string}%
  }%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%%   \UD@ExtractFirstArg{ABCDE} yields  {A}
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  {{AB}}
%%
%% Due to \romannumeral-expansion the result is delivered after two 
%% expansion-steps/after "hitting" \UD@ExtractFirstArg with \expandafter
%% twice.
%%
%% \UD@ExtractFirstArg's argument must not be blank.
%% This case can be cranked out via \UD@CheckWhetherBlank before calling
%% \UD@ExtractFirstArg.
%%
%% Use frozen-\relax as delimiter for speeding things up.
%% I chose frozen-\relax because David Carlisle pointed out in
%% <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \UD@ExtractFirstArg's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
%%.............................................................................
\@ifdefinable\UD@RemoveTillFrozenrelax{%
  \expandafter\expandafter\expandafter\UD@Exchange
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\ifnum0=0\fi}%
  {\long\def\UD@RemoveTillFrozenrelax#1#2}{{#1}}%
}%
\expandafter\UD@PassFirstToSecond\expandafter{%
  \romannumeral\expandafter
  \UD@PassFirstToSecond\expandafter{\romannumeral
    \expandafter\expandafter\expandafter\UD@Exchange
    \expandafter\expandafter\expandafter{%
    \expandafter\expandafter\ifnum0=0\fi}{\UD@stopromannumeral#1}%
  }{%
    \UD@stopromannumeral\romannumeral\UD@ExtractFirstArgLoop
  }%
}{%
  \newcommand\UD@ExtractFirstArg[1]%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\UD@stopromannumeral#1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillFrozenrelax#1}}%
}%
%====================================================================
\@ifdefinable\UD@gobbletoexclam{\long\def\UD@gobbletoexclam#1!{}}%
\@ifdefinable\UD@ChooseDDigit{%
  \long\def\UD@ChooseDDigit#1!1!2!3!4!5!6!7!8!9!0!D!#2#3!!!!{#2}%
}%
\newcommand\UD@CollectDDigit[5]{%
  % #1 token to examine
  % #2 pending D
  % #3 digits gathered as probable argument of formatting-command
  % #4 formatting-command, takes 1 undelimited argument
  % #5 tokens forming the result gathered so far
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletoexclam#1!}%
  {%
    \UD@ChooseDDigit
    !#1!2!3!4!5!6!7!8!9!0!D!{\UD@firstoftwo}% digit 1
    !1!#1!3!4!5!6!7!8!9!0!D!{\UD@firstoftwo}% digit 2
    !1!2!#1!4!5!6!7!8!9!0!D!{\UD@firstoftwo}% digit 3
    !1!2!3!#1!5!6!7!8!9!0!D!{\UD@firstoftwo}% digit 4
    !1!2!3!4!#1!6!7!8!9!0!D!{\UD@firstoftwo}% digit 5
    !1!2!3!4!5!#1!7!8!9!0!D!{\UD@firstoftwo}% digit 6
    !1!2!3!4!5!6!#1!8!9!0!D!{\UD@firstoftwo}% digit 7
    !1!2!3!4!5!6!7!#1!9!0!D!{\UD@firstoftwo}% digit 8
    !1!2!3!4!5!6!7!8!#1!0!D!{\UD@firstoftwo}% digit 9
    !1!2!3!4!5!6!7!8!9!#1!D!{\UD@firstoftwo}% digit 0
    !1!2!3!4!5!6!7!8!9!0!#1!{\expandafter\UD@firstoftwo\UD@secondoftwo}% character D
    !1!2!3!4!5!6!7!8!9!0!D!{\expandafter\UD@secondoftwo\UD@secondoftwo}!!!!%neither D nor digit
  }{\expandafter\UD@secondoftwo\UD@secondoftwo}%%neither D nor digit
  {% digit
    \UD@CheckWhetherNull{#2}{%
      \UD@stopromannumeral\UD@DDigitReplaceloop{}{}{#4}{#5#1}%
    }{%
      \UD@stopromannumeral\UD@DDigitReplaceloop{#2}{#3#1}{#4}{#5}%
    }%
  }{%
    {% character D
      \UD@CheckWhetherNull{#2}{%
        \UD@PassFirstToSecond{#5}%
      }{%
        \UD@CheckWhetherNull{#3}{%
          \UD@PassFirstToSecond{#5#2}%
        }{%
          \UD@PassFirstToSecond{#5#4{#2#3}}%
        }%
      }%
      {\UD@stopromannumeral\UD@DDigitReplaceloop{#1}{}{#4}}%
    }{% neither D nor digit
      \UD@CheckWhetherNull{#2}{%
        \UD@PassFirstToSecond{#5#1}%
      }{%
        \UD@CheckWhetherNull{#3}{%
          \UD@PassFirstToSecond{#5#2#1}%
        }{%
          \UD@PassFirstToSecond{#5#4{#2#3}#1}%
        }%
      }%
      {\UD@stopromannumeral\UD@DDigitReplaceloop{}{}{#4}}%
    }%
  }%
}%
\newcommand\UD@DDigitReplaceloop[5]{%
  % #1 pending D
  % #2 digits gathered as probable argument of formatting-command
  % #3 formatting-command, takes 1 undelimited argument
  % #4 tokens forming the result gathered so far
  % #5 remaining token list to process
  \UD@CheckWhetherNull{#5}{%
    \UD@CheckWhetherNull{#1}{\UD@stopromannumeral#4#2}{%
      \UD@CheckWhetherNull{#2}{\UD@stopromannumeral#4#1}{\UD@stopromannumeral#4#3{#1#2}}%
    }%
  }{%
    \UD@CheckWhetherBrace{#5}{%
      \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#5}{%
        \expandafter\UD@PassFirstToSecond\expandafter{%
          \romannumeral
          \UD@CheckWhetherNull{#1}{%
            \UD@PassFirstToSecond{\UD@stopromannumeral#4#2}%
          }{%
            \UD@CheckWhetherNull{#2}{%
              \UD@PassFirstToSecond{\UD@stopromannumeral#4#1}%
            }{%
              \UD@PassFirstToSecond{\UD@stopromannumeral#4#3{#1#2}}%
            }%
          }%
          {%
            \expandafter\UD@PassFirstToSecond
            \expandafter{%
              \romannumeral
              \expandafter\expandafter\expandafter\UD@PassFirstToSecond
              \UD@ExtractFirstArg{#5}{\UD@DDigitReplaceloop{}{}{#3}{}}%
            }%
          }%
        }{%
          \UD@DDigitReplaceloop{}{}{#3}%
        }%
      }%
    }{%
      \UD@CheckWhetherLeadingExplicitSpace{#5}{%
        \expandafter\UD@PassFirstToSecond\expandafter{\UD@removespace#5}{%
          \UD@CheckWhetherNull{#1}{%
             \UD@PassFirstToSecond{#4 }%
          }{%
            \UD@CheckWhetherNull{#2}{%
              \UD@PassFirstToSecond{#4#1 }%
            }{%
              \UD@PassFirstToSecond{#4#3{#1#2} }%
            }%
          }%
          {\UD@DDigitReplaceloop{}{}{#3}}%
        }%
      }{%
        \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#5}{%
          \expandafter\UD@firstoftwo\expandafter{%
            \romannumeral
            \expandafter\expandafter\expandafter
            \UD@CollectDDigit\UD@ExtractFirstArg{#5}{#1}{#2}{#3}{#4}%
          }{}%
        }%
      }%
    }%
  }%
}%
\newcommand\SurroundDDigits[2]{%
  \romannumeral\UD@DDigitReplaceloop{}{}{#1}{}{#2}%
}%
\makeatother

\documentclass{article}

\begin{document}

\begin{verbatim} \expandafter\expandafter\expandafter\def \expandafter\expandafter\expandafter\test \expandafter\expandafter\expandafter{% \SurroundDDigits{\textbf}{% Axfd D12FF 23{AxfdD12FF2 3{Axf dD12FF23{}f\relax D114 6ffasdfa}f\relax D1146ff asdfa}f\relax D1146ffa sdfa% }% }% {\ttfamily \meaning\test} \end{verbatim}

\noindent yields: \vskip\partopsep\vskip\topsep

\expandafter\expandafter\expandafter\def \expandafter\expandafter\expandafter\test \expandafter\expandafter\expandafter{% \SurroundDDigits{\textbf}{% Axfd D12FF 23{AxfdD12FF2 3{Axf dD12FF23{}f\relax D114 6ffasdfa}f\relax D1146ff asdfa}f\relax D1146ffa sdfa% }% }% {\ttfamily \meaning\test}

\vskip\partopsep\vskip\topsep \noindent\hrule \vskip\partopsep\vskip\topsep

\begin{verbatim} \newcommand\MyFontCommand[1]{{\footnotesize\emph{\textbf{#1}}}}% \SurroundDDigits{\MyFontCommand}{% Axfd D12FF 23{AxfdD12FF2 3{Axf dD12FF23{}f\relax D114 6ffasdfa}f\relax D1146ff asdfa}f\relax D1146ffa sdfa% }% \end{verbatim}

\noindent yields: \vskip\partopsep\vskip\topsep

\newcommand\MyFontCommand[1]{{\footnotesize\emph{\textbf{#1}}}}% \SurroundDDigits{\MyFontCommand}{% Axfd D12FF 23{AxfdD12FF2 3{Axf dD12FF23{}f\relax D114 6ffasdfa}f\relax D1146ff asdfa}f\relax D1146ffa sdfa% }%

\end{document}

enter image description here

Ulrich Diez
  • 28,770
3

This tokencycle seems to do the job. As you can see, intervening macros and groups in the input stream have no ill effects on the result.

\documentclass{article}
\usepackage{tokcycle,xcolor}
\Characterdirective{\ifx D#1\Cotetest{D}\else\addcytoks{#1}\fi}
\newcommand\Cotetest[1]{\tcpeek\z
  \ifx0\z\Cotetrue\else
  \ifx1\z\Cotetrue\else
  \ifx2\z\Cotetrue\else
  \ifx3\z\Cotetrue\else
  \ifx4\z\Cotetrue\else
  \ifx5\z\Cotetrue\else
  \ifx6\z\Cotetrue\else
  \ifx7\z\Cotetrue\else
  \ifx8\z\Cotetrue\else
  \ifx9\z\Cotetrue\else\addcytoks{#1}\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi}
\newcommand\Cotetrue{%
  \tcpop\z
  \expandafter\Cote\expandafter{\z}%
  \Cotetest{}}
\def\Cote#1{\addcytoks{\bgroup\footnotesize\emph{\textbf{#1}}\egroup}}
\begin{document}
\tokencyclexpress
D7C2
D93
\textcolor{red}{D123abc}
Dcf
D 
D\today
\endtokencyclexpress
\end{document}

enter image description here

SUPPLEMENT

In response to an OP comment, desiring to retain the "D" in the altered font, in the event of subsequent digits, I might do it this way:

\documentclass{article}
\usepackage{tokcycle,xcolor}
\Characterdirective{\ifx D#1\Cotetest{D}\else\addcytoks{#1}\fi}
\newcommand\Cotetest[1]{\tcpeek\z
  \ifx0\z\Cotetrue{#1}\else
  \ifx1\z\Cotetrue{#1}\else
  \ifx2\z\Cotetrue{#1}\else
  \ifx3\z\Cotetrue{#1}\else
  \ifx4\z\Cotetrue{#1}\else
  \ifx5\z\Cotetrue{#1}\else
  \ifx6\z\Cotetrue{#1}\else
  \ifx7\z\Cotetrue{#1}\else
  \ifx8\z\Cotetrue{#1}\else
  \ifx9\z\Cotetrue{#1}\else\addcytoks{#1}\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi}
\newcommand\Cotetrue[1]{%
  \tcpop\z
  \expandafter\Cote\expandafter{\expandafter#1\z}%
  \Cotetest{\empty}}
\def\Cote#1{\addcytoks{\bgroup\footnotesize\emph{\textbf{#1}}\egroup}}
\begin{document}
\tokencyclexpress
D7C2
D93
\textcolor{red}{D123abc}
Dcf
D 
D\today
\endtokencyclexpress
\end{document}

enter image description here

  • Looks like it answers quite perfectly! Thank you, I wouldn't have achieved this on my own. I'd like the same, with the "D" still displayed (I only want the font settings to be changed). Could it be done with this command? : \def\Cote#1{\addcytoks{\bgroupD\footnotesize\emph{\textbf{#1}}\egroup}} – Gaspalet Jul 31 '21 at 11:42
  • 1
    I'm discovering the tokcycle package, it's incredible. – Gaspalet Jul 31 '21 at 11:47
  • @Gaspalet \def\Cote#1{\addcytoks{\bgroup D\footnotesize\emph{\textbf{#1}}\egroup}} will insert D in front of each digit of the number. That D will not be formatted like the digits of the number. – Ulrich Diez Jul 31 '21 at 18:06
  • @Gaspalet Thank you for the compliment. I have added a supplement to my answer, which I think gives you what you desire. – Steven B. Segletes Jul 31 '21 at 18:14
  • @Gaspalet Make sure you check out the "Examples" documentation that shows various usages. https://ctan.org/pkg/tokcycle – Steven B. Segletes Jul 31 '21 at 18:22
2

Using Steven B. Segletes' idea of initiating a \Cotetest-\Cotetrue-loop in case of encountering a character D, you can do something like this which does not apply the formatting to each character of D<decimal digits>-sequences but does apply the formatting after gathering the entire sequence:

\documentclass{article}
\usepackage{tokcycle,xcolor}
\Characterdirective{\ifx D#1\Cotetest{D}{0}\else\addcytoks{#1}\fi}
\newcommand\Cotetest[2]{%
  % #1 - character-token D + digits gathered so far for formatting
  % #2 - flag: = 0 -> no digits are gathered yet / 
  %            =/= 0 -> some digits were gathered in previous iterations
  \tcpeek\z
  \ifx0\z\Cotetrue{#1}\else
  \ifx1\z\Cotetrue{#1}\else
  \ifx2\z\Cotetrue{#1}\else
  \ifx3\z\Cotetrue{#1}\else
  \ifx4\z\Cotetrue{#1}\else
  \ifx5\z\Cotetrue{#1}\else
  \ifx6\z\Cotetrue{#1}\else
  \ifx7\z\Cotetrue{#1}\else
  \ifx8\z\Cotetrue{#1}\else
  \ifx9\z\Cotetrue{#1}\else
  \ifnum#2=0 \addcytoks{#1}\else\addcytoks{\bgroup\footnotesize\emph{\textbf{#1}}\egroup}\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi}
\newcommand\Cotetrue[1]{%
  % #1 character-token D + digits gathered so far for formatting
  \tcpop\z
  \expandafter\expandafter\expandafter\Cotetest
  \expandafter\expandafter\expandafter{\expandafter\exchange\expandafter{\z}{#1}}{1}%
}%
\newcommand\exchange[2]{#2#1}
% Let \one be an implicit character token denoting 1 of catcode 12(other):
\let\one=1
\begin{document}
\tokencyclexpress
D\one7
D7\one1C2
D93
\textcolor{red}{D123abc}
Dcf
D 
D\today
\endtokencyclexpress
\end{document}

enter image description here

A variant which cranks out implicit characters as something that is not considered a digit of a number:

\documentclass{article}
\usepackage{tokcycle,xcolor}
\Characterdirective{\ifx D#1\Cotetest{D}{0}\else\addcytoks{#1}\fi}
\newcommand\Cotetest[2]{%
  % #1 - character-token D + digits gathered so far for formatting
  % #2 - flag: = 0 -> no digits are gathered yet / 
  %            =/= 0 -> some digits were gathered in previous 
  \tcpeek\z
  \ifx0\z\Cotetrue{#1}{#2}{0}\else
  \ifx1\z\Cotetrue{#1}{#2}{1}\else
  \ifx2\z\Cotetrue{#1}{#2}{2}\else
  \ifx3\z\Cotetrue{#1}{#2}{3}\else
  \ifx4\z\Cotetrue{#1}{#2}{4}\else
  \ifx5\z\Cotetrue{#1}{#2}{5}\else
  \ifx6\z\Cotetrue{#1}{#2}{6}\else
  \ifx7\z\Cotetrue{#1}{#2}{7}\else
  \ifx8\z\Cotetrue{#1}{#2}{8}\else
  \ifx9\z\Cotetrue{#1}{#2}{9}\else
  \ifnum#2=0 \addcytoks{#1}\else\addcytoks{\bgroup\footnotesize\emph{\textbf{#1}}\egroup}\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi}
\newcommand\Cotetrue[3]{%
  % #1 - character-token D + digits gathered so far for formatting
  % #2 - flag: = 0 -> no digits are gathered yet / 
  %            =/= 0 -> some digits were gathered in previous
  % #3 - explicit digit character-token to compare for cranking out
  %      implicit character tokens
  \tcpop\z
  \begingroup
  \def\tempa{#3}%
  \expandafter\endgroup\csname @\ifx\tempa\z first\else second\fi oftwo\endcsname
  {%
    \expandafter\expandafter\expandafter\Cotetest
    \expandafter\expandafter\expandafter{\expandafter\exchange\expandafter{\z}{#1}}{1}%
  }{%
   \ifnum#2=0 
      \addcytoks[2]{\expandafter\exchange\expandafter{\z}{#1}}%
   \else
      \addcytoks[2]{\expandafter\exchange\expandafter{\z}{\bgroup\footnotesize\emph{\textbf{#1}}\egroup}}%
   \fi
  }%
}%
\newcommand\exchange[2]{#2#1}
% Let \one be an implicit character token denoting 1 of catcode 12(other):
\let\one=1
\begin{document}
\tokencyclexpress
D\one7
D7\one1C2
D93
\textcolor{red}{D123abc}
Dcf
D 
D\today
\endtokencyclexpress
\end{document}

enter image description here

Ulrich Diez
  • 28,770
  • 2
    Note that \expandafter\expandafter\expandafter\addcytoks\expandafter\expandafter\expandafter{\expandafter\exchange\expandafter{\z}{#1}} can be more concisely written as \addcytoks[2]{\expandafter\exchange\expandafter{\z}{#1}}. The optional argument [n] to \addcytoks performs n expansions to the mandatory argument, before placing the result in the \cytoks token list. – Steven B. Segletes Jul 31 '21 at 20:26
  • I would upvote again, if I could – Steven B. Segletes Jul 31 '21 at 20:31
  • 2
    I'm just happy to have people learning and using the tokcycle package. I think it is a versatile tool for building various user applications. – Steven B. Segletes Jul 31 '21 at 20:37
  • A big thanks to both of you ! This tokcycle writing is new to me, so I'm going to take a few days to digest it, be it sounds promising. I am really gratefull. – Gaspalet Aug 01 '21 at 09:42