2

I would like to define a command that counts the number of a substring in the input and apply corresponding operation according to the result.

For example, in the following code, the command should apply the corresponding rule according to the number of . in the input.

\documentclass{article}
\usepackage{xcolor}
\begin{document}
\ExplSyntaxOn

\cs_new:Nn \mymodule_apply_title_numbering_style_variant:n { \tl_set:Nx \l_tmpa_tl { #1 }

%% If #1 contains no '.'
\Huge\color{black}
%% If #1 contains one '.'
\large\color{black!80}
%% If #1 contains two '.'
\normalsize\color{black!60}
%% If #1 contains three '.'
\footnotesize\color{black!30}

\l_tmpa_tl

}

\mymodule_apply_title_numbering_style_variant:n { 1 }\par \mymodule_apply_title_numbering_style_variant:n { 1.1 }\par \mymodule_apply_title_numbering_style_variant:n { 1.1.1 }\par \mymodule_apply_title_numbering_style_variant:n { 1.1.1.1 }

\end{document}

How can this be achieved?

Jinwen
  • 8,518
  • Wouldn't it be possible to format the numbers according to their title level for example using a package like titlesec? – Jasper Habicht May 23 '22 at 16:46
  • @JasperHabicht You are right. However, in one of my document, there are some other things (like theorems) that share the counter with section/subsection, which require the same style for the numbering. Also, to allow users using \numberwithin and \numberwithout to change the numbering level in the middle of the document, I cannot define the style to be a static one. Thus the question. – Jinwen May 23 '22 at 16:50
  • 1
    I am still struggling with expl3 syntax, but this could help you: https://tex.stackexchange.com/q/336294/47927 – Jasper Habicht May 23 '22 at 17:10

3 Answers3

4

A few remarks on Jinwen's answer:

  1. \int_case:nn expects an ⟨integer expression⟩ as its first argument. Since \seq_count:N \l_tmpa_seq recursively expands to an ⟨integer denotation⟩, \seq_count:N \l_tmpa_seq - 1 is a valid ⟨integer expression⟩ with the desired value; therefore, neither the \int_eval:n nor the parentheses in the referenced answer are needed. In other words, you can use:

    \seq_set_split:Nnn \l_tmpa_seq { . } {#1}
    \int_case:nn { \seq_count:N \l_tmpa_seq - 1 }
      {
        ...
      }
    
  2. Here is another way using \regex_count:nnN to count the periods and \int_case:nnF (rather than \int_case:nn) to implement an “else” clause:

    \documentclass{article}
    \usepackage{xcolor}
    

    \begin{document} \ExplSyntaxOn

    \cs_new_protected:Nn \mymodule_apply_title_numbering_style_variant:n { \group_begin: \regex_count:nnN { . } {#1} \l_tmpa_int \int_case:nnF { \l_tmpa_int } { { 0 } { \Huge\color{black} } { 1 } { \large\color{black!80} } { 2 } { \normalsize\color{black!60} } } { \footnotesize\color{black!30} } #1 \group_end: }

    \mymodule_apply_title_numbering_style_variant:n { 1 }\par \mymodule_apply_title_numbering_style_variant:n { 1.1 }\par \mymodule_apply_title_numbering_style_variant:n { 1.1.1 }\par \mymodule_apply_title_numbering_style_variant:n { 1.1.1.1 }\par \mymodule_apply_title_numbering_style_variant:n { 1.1.1.1.1 }\par \mymodule_apply_title_numbering_style_variant:n { 1.1.1.1.1.1 }

    \ExplSyntaxOff \end{document}

    enter image description here

    Note: the l3regex regular expression \. matches a period with any category code. If you want to match, for instance, catcode-12 periods only (i.e., the “normal” ones), use the \cO\. regular expression instead.

  3. Your function is best defined with \cs_new_protected:Nn rather than \cs_new:Nn, because it can't do its work in expansion-only contexts (like inside \edef, \write and many others; see for instance the “annex” here for some explanations). One reason for this is that neither \seq_set_split:Nnn (in your version) nor \regex_count:nnN (in mine) is expandable—look for the stars in the left margin of interface3.pdf.

  4. The two counting techniques are not equivalent, because \seq_set_split:Nnn creates braced-balanced items, whereas \regex_count:nnN just walks through the specified token list with no particular consideration for braces. As a consequence, with 1{.}1 as input for your function, the technique based on \seq_set_split:Nnn finds no period, whereas the one based on \regex_count:nnN finds exactly one.

frougon
  • 24,283
  • 1
  • 32
  • 55
2

The code below is a first attempt, see the accepted answer of @fougon for the clarification and improvement.


Here is a solution I just found out, thanks to the link provided by Jasper Habicht in his comment to the question.

\documentclass{article}
\usepackage{xcolor}
\begin{document}
\ExplSyntaxOn

\cs_new:Nn \mymodule_apply_title_numbering_style_variant:n { \group_begin: \seq_set_split:Nnn \l_tmpa_seq { . } { #1 } \int_case:nn { \int_eval:n {(\seq_count:N \l_tmpa_seq) - 1 } } { { 0 } { \Huge\color{black} } { 1 } { \large\color{black!80} } { 2 } { \normalsize\color{black!60} } { 3 } { \footnotesize\color{black!30} } } #1 \group_end: }

\mymodule_apply_title_numbering_style_variant:n { 1 }\par \mymodule_apply_title_numbering_style_variant:n { 1.1 }\par \mymodule_apply_title_numbering_style_variant:n { 1.1.1 }\par \mymodule_apply_title_numbering_style_variant:n { 1.1.1.1 }

\end{document}

Jinwen
  • 8,518
  • The difference between this and the accepted answer is that your method counts only the periods at the outer level, while Frougon's method doesn't distinguish between group levels. So your method would count two periods in 1.{2.3}.4, whereas Frougon's would count three of them. You should really remove the \int_eval:n function that serves no purpose. – egreg May 24 '22 at 20:03
  • @egreg Thank you again for the clarification. Indeed I have switched to Frougon's \regex_count method in my document, which is better than this approach. I shall however keep my problematic code here since the accepted answer is exactly explaining how to fix and improve it. – Jinwen May 24 '22 at 20:11
1

In OpTeX, we can do this:

\def\countdot#1{\tmpnum=0 \ea\foreach\detokenize{#1}\do{\ifx.##1\incr\tmpnum\fi}}
\def\titlestyle#1{%
   \countdot{#1}% number of dots is saved in \tmpnum
   \bgroup
   \ifcase\tmpnum \typosize[17/]\or 
                  \typosize[14/]\setgreycolor{.2}\or
                  \typosize[10/]\setgreycolor{.4}\else
                  \typosize[8/]\setgreycolor{.7}\fi
   #1\par\egroup
}

% test: \titlestyle {1} \titlestyle {1.1} \titlestyle {1.1.1} \titlestyle {1.1.1.1} \titlestyle {1.1.1.1.1} \titlestyle {1.1.1.1.1.1}

\bye

The result is the same as in the accepted answer.

wipet
  • 74,238