1

I'm trying to build a command that displays a sort of checklist. Ideally, I only want to pass a set of boolean values to that command.

A naive MWE looks like this:

\documentclass{article}
\usepackage{tabularx}

\newcommand{\mychecklist}[5]{
    \begin{table}
        \begin{tabularx}{\textwidth}{|l|l|}
            param 1 & #1 \\
            param 2 & #2 \\
            param 3 & #3 \\
            param 4 & #4 \\
            param 5 & #5
        \end{tabularx}
    \end{table}
}

\begin{document}
\mychecklist{x}{}{x}{}{x}
\end{document}

However, I need to pass more than 9 parameters. How can I do this efficiently? Is there a solution that lets me pass a set of indices (eg. [1,3,5]) to the command and produce the desired result, a table with checkmarks in the respective positions?

azrael
  • 21

5 Answers5

2

Due to the 30 000-character-limit I had to divide this answer into four parts.

This is part 1 of my answer.

Part 1 consists of explanations on how to divide things into different sub-routines and then put together the desired mechanism.

Part 2 consists of several package files/.sty-files which can be loaded via \usepackage/\RequirePackage and which bring along some of the needed sub-routines.

Part 3 also consists of several package files/.sty-files which can be loaded via \usepackage/\RequirePackage and which bring along some of the needed sub-routines.

Part 4 delivers a LaTeX document which via \RequirePackage/\usepackage loads the packages with the needed sub-routines from part 2 and part 3. Within the head of that document, even before the preamble, these sub-routines are used for putting together the desired generic and non-generic user-level-macros.

For testing, save the package files/.sty-files provided in part 2 and part 3 and text.tex from part 4 within the same directory and compile test.tex with a LaTeX-engine.


You can take this question for a nice exercise in implementing things from scratch.

With this exercise you can exhibit useful (La)TeX programming techniques:

  • \romannumeral0-expansion as a means for triggering expansion until obtaining a desired result/a desired set of tokens.
    (I elaborated on \romannumeral0-expansion also in my answer to the question "How can I know the number of expandafters when appending to a csname macro?".)
  • Tail-recursion for iterating only by means of (macro-)expansion one after the other on the single elements of lists of comma-delimited arguments/of lists of non-delimited arguments.
  • Tail-recursion for accumulating a desired result/a desired set of tokens within a specific argument of a tail-recursive macro.
  • Nesting calls to a macro \UD@PassThirdBeforeFirstToSecond/exchanging macro arguments after having triggered desired expansion as a systematic approach for getting the (changed) arguments of a tail-recursive macro correctly in place within the token-stream before having (La)TeX call/expand the tail-recursive macro in question again.

With the desired command \mychecklist the "set of indices" [1,3,5] forms a list of comma-delimited arguments. Thus this question includes the task of handling a list of comma-delimited macro arguments.

Extracting an element, e.g., the first element, from a list of comma-delimited arguments (a comma-list) is an interesting task:

Several questions arise. E.g., the question about the treatment of leading and trailing space-tokens with such elements. E.g., the question about the handling of situations where a comma shall not serve as separator between two elements but shall be a component of one such element.

In the package-file UD_ExtractFirstCommaDelimitedArg_2019_09_03.sty I implemented the routine \UD@ExtractFirstCommaArg for extracting the first element of a comma-list in a way where all spaces that might surround the entire element get removed and afterwards one level of curly braces that might surround the entire element gets stripped off also.

This way you can have an entire element surrounded by braces for hiding

  • commas that shall not separate elements from each other but shall belong to the element in question itself.
  • leading and trailing spaces that shall not be removed from the element in question but shall belong to the element in question.

This approach is somewhat more universal than needed in this scenario. Nevertheless I think its worth presenting it as it can be quite useful in other scenarios.

The comma-list can contain spaces. These will be removed silently: [1 , 3 ,5, 7,9 ,]. The single numbers/indices can be nested in one level of curly braces: [1 , {3} ,5, {7},9 ,]. But, e.g., with [1 , 3 5 , {7},9 ,], the sequence 3 5 will be taken for one element of the comma-list. Due to the space between 3 and 5 that element does not form a digit-sequence/does not form a valid number and except from complaining about it via an error-message LaTeX will ignore it.


A possible workflow for a generic command

\mychecklistwithparameternames{⟨list of comma separated integer numbers in range 1..L⟩}%
                              {⟨list of L names of the L parameters⟩}%
                              {⟨name of macro that did call \mychecklistwithparameternames⟩}%
                              {⟨tokens to insert before the tabularx environment⟩}%
                              {⟨tokens to insert behind the tabularx environment⟩}%

could be:

⤷ That command expands to:

\DetectAmountOfParametersAndInitFlagsLoop{⟨list of L names of the L parameters (where elements get removed during the loop)⟩}% 
                                         {⟨to-be-constructed list of L flags (one flag for each of the L parameters)⟩}% initially empty.
                                         {⟨amount of parameters⟩}% initially 0.
                                         {⟨list of comma separated integer numbers in range 1..L⟩}%
                                         {⟨list of L names of the L parameters (where elements do not get removed so that it can be used after the loop)⟩}%
                                         {⟨name of macro that did call \mychecklistwithparameternames⟩}%
                                         {⟨tokens to insert before the tabularx environment⟩}%
                                         {⟨tokens to insert behind the tabularx environment⟩}%

\DetectAmountOfParametersAndInitFlagsLoop is to detect the ⟨amount of parameters⟩ and to create a ⟨list of L flags (one flag for each of the L parameters)⟩ in terms of a list of non-delimited arguments where each flag is initialized as an argument {0} :

\DetectAmountOfParametersAndInitFlagsLoop via tail-recursion—i.e., via calling itself again with its arguments changed—iterates on the ⟨list of L names of the L parameters (where elements get removed during the loop)⟩ until that list forms a non-delimited macro-argument which is blank ("blank" in this context means that the set of tokens which forms the argument either is empty or does contain space tokens only): Within each iteration remove the first element from that list and to the ⟨to-be-constructed list of L flags (one flag for each of the L parameters)⟩ add another flag-element {0} and increment the ⟨amount of parameters⟩.

(   This implies that \DetectAmountOfParametersAndInitFlagsLoop needs sub-routines for the following tasks:

  1. Checking whether a macro argument is blank.
    In the example test.tex from part 4 this is the routine \UD@CheckWhetherBlank from UD_Paraphernalia_2019_09_03.sty.
  2. Incrementing an integer number by 1.
    In the example test.tex from part 4 this is the routine \UD@Increment from UD_Increment_2019_09_03.sty.
  3. Removing an element from a list of non-delimited arguments.
    In the example test.tex from part 4 this is done via \UD@FirstOfTwo{}⟨list of non-delimited arguments⟩.   )

When that recursive iteration is done, i.e., when the ⟨list of L names of the L parameters (where elements get removed during the loop)⟩ is a blank non-delimited macro-argument, then terminate the \DetectAmountOfParametersAndInitFlagsLoop-tail-recursion and have another tail-recursive macro called, \SetFlagsNWarningsLoop:

⤷

\SetFlagsNWarningsLoop{⟨list of comma separated integer numbers in range 1..L⟩}%
                      {⟨list of L flags (one flag for each of the L parameters)⟩}% all now initialized "{0}".
                      {⟨list of warnings⟩}% initially empty.
                      {⟨amount of parameters⟩}%
                      {⟨list of L names of the L parameters⟩}%
                      {⟨name of macro that did call \mychecklistwithparameternames⟩}%
                      {⟨tokens to insert before the tabularx environment⟩}%
                      {⟨tokens to insert behind the tabularx environment⟩}%

\SetFlagsNWarningsLoop via tail-recursion iterates on the ⟨list of comma separated integer numbers in range 1..L⟩ for changing those flags in the ⟨List of L flags (one flag for each of the L parameters)⟩ to {1} whose numbers occur in the ⟨list of comma separated integer numbers in range 1..L⟩ :

As long as the ⟨list of comma separated integer numbers in range 1..L⟩ is not blank have \SetFlagsNWarningsLoop call itself again after having changed (and via nested-\UD@PassThirdBeforeFirstToSecond-technique having brought in place) its arguments as follows:

  • If the first element of the ⟨list of comma separated integer numbers in range 1..L⟩ is empty
    , then do nothing
    , else
    • if the first element of the ⟨list of comma separated integer numbers in range 1..L⟩ can be taken for a positive integer number K with 1 ≤ K ≤ ⟨amount of parameters⟩
      • , then replace the K-th element of the ⟨list of L flags (one for flag each of the L parameters)⟩ by the element {1}
      • , else add an entry to the ⟨list of warnings⟩.
  • Remove the first element of the ⟨list of comma separated integer numbers in range 1..L⟩.

When the ⟨list of comma separated integer numbers in range 1..L⟩ is blank, then terminate the \SetFlagsNWarningsLoop-tail-recursion by calling \TableRowsLoop.

(   This implies that \SetFlagsNWarningsLoop needs sub-routines for the following tasks:

  1. Checking whether a macro argument is empty.
    In the example test.tex from part 4 this is the routine \UD@CheckWhetherNull from UD_Paraphernalia_2019_09_03.sty.
  2. Checking whether a macro argument is blank.
    In the example test.tex from part 4 this is the routine \UD@CheckWhetherBlank from [UD_Paraphernalia_2019_09_03.sty].
  3. Extracting the first element of a comma separated list.
    In the example test.tex from part 4 this is the routine \UD@ExtractFirstCommaArg from UD_ExtractFirstCommaDelimitedArg_2019_09_03.sty.
  4. Removing the first element from a comma separated list.
    In the example test.tex from part 4 this is the macro \UD@GobbleToComma from UD_ExtractFirstCommaDelimitedArg_2019_09_03.sty.
  5. Checking whether an argument forms a positive integer within a specified range.
    In the example test.tex from part 4 this is the routine \UD@CheckWhetherDigitTokensInRangeOnly from UD_NumCheck_2019_09_03.sty.
  6. Replacing the K-th element of a list of non-delimited arguments by something else.
    In the example test.tex from part 4 this is the routine \UD@ReplaceKthArg from UD_ReplaceKthUndelimited_2019_09_03.sty.
  7. Raising a warning-message.
    In the example test.tex from part 4 this is the routine \UD@NotANumberInValidRangeError from UD_NumCheck_2019_09_03.sty.   )

\TableRowsLoop also is tail-recursive and needs to be called as follows:

⤷

\TableRowsLoop{⟨list of L flags (one flag for each of the L parameters)⟩}%
              {⟨list of L names of the L parameters⟩}%
              {⟨table-rows constructed so far⟩}% initially empty.
              {⟨list of warnings⟩}%
              {⟨tokens to insert before the tabular xenvironment⟩}%
              {⟨tokens to insert behind the tabularx environment⟩}%

\TableRowsLoop via tail-recursion iterates on the ⟨list of L flags (one flag for each of the L parameters)⟩ and on the ⟨list of L names of the L parameters⟩ and creates the table rows:

When ⟨list of L flags (one flag for each of the L parameters)⟩ is empty
, then

  • terminate the tail-recursive loop,
  • "spit out":
    • the ⟨tokens to insert before the tabularx environment⟩,
    • the ⟨table-rows constructed so far⟩, nested inside a table- and a tabular-environment,
    • the ⟨tokens to insert behind the tabularx environment⟩,
    • the ⟨list of warnings⟩

, else have \TableRowsLoop call itself again after having changed (and via nested-\UD@PassThirdBeforeFirstToSecond-technique having brought in place) its arguments as follows:

  • If the ⟨list of L flags (one flag for each of the L parameters)⟩ contains only one element (last element)
    • , then:
      If the first element of the ⟨list of L flags (one flag for each of the L parameters)⟩ denotes the number 0
      • , then add a sequence
        ⟨first element of the ⟨list of L names of the L parameters⟩⟩&
        to the ⟨table-rows constructed so far⟩
      • , else add a sequence
        ⟨first element of the ⟨list of L names of the L parameters⟩&x
        to the ⟨table-rows constructed so far⟩.
    • , else:
      If the first element of the ⟨list of L flags (one flag for each of the L parameters)⟩ denotes the number 0
      • , then add a sequence
        ⟨first element of the ⟨list of L names of the L parameters⟩⟩&\\
        to the ⟨table-rows constructed so far⟩
      • , else add a sequence
        ⟨first element of the ⟨list of L names of the L parameters⟩&x\\
        to the ⟨table-rows constructed so far⟩.
  • Remove the first element of the ⟨list of L flags (one flag for each of the L parameters)⟩.
  • Remove the first element of the ⟨list of L names of the L parameters⟩.

(   This implies that \TableRowsLoop needs sub-routines for the following tasks:

  1. Checking whether a non-delimited macro argument is empty.
    In the example test.tex from part 4 this is the routine \UD@CheckWhetherNull from UD_Paraphernalia_2019_09_03.sty.
  2. Extracting the first element of a list of non-delimited arguments.
    In the example test.tex from part 4 this is the routine \UD@ExtractFirstArg from UD_ExtractFirstUndelimitedArg_2019_09_03.sty.
  3. Checking whether a macro argument denotes the number "0".
    In the example test.tex from part 4 the routine \UD@CheckWhetherDigitTokensInRangeOnly from UD_NumCheck_2019_09_03.sty is used for this.
  4. Removing an element from a list of non-delimited arguments.
    In the example test.tex from part 4 this is done via \UD@FirstOfTwo{}⟨list of non-delimited arguments⟩.   )

The sub-routines provided in the package files, except \UD@NotANumberInValidRangeError from UD_NumCheck_2019_09_03.sty, due to \romannumeral0-expansion deliver their results after two expansion-steps/after two "hits" by \expandafter.

Thus with the code in the example test.tex from part 4 applying the nested-\UD@PassThirdBeforeFirstToSecond-technique for bringing arguments in place for calling the next loop-instance of a tail-recursive macro often needs to be combined with having \UD@PassThirdBeforeFirstToSecond's first argument "hit" by \expandafter twice before performing the exchange.
That's why in UD_Paraphernalia_2019_09_03.sty I implemented a helper-macro \UD@PassThirdBeforeTwiceExpandedFirstToSecond.

Based on the generic command \mychecklistwithparameternames you could define \mychecklist with syntax:

\mychecklist[⟨comma list⟩]%
            {⟨caption of the table⟩}%
            {⟨referencing label of the table⟩}

like this:

\newcommand\mychecklist[3][⟨comma list with defaults⟩]{%
  \mychecklistwithparameternames{#1}%
                                {{param 1}{param 2}..{param L}}%
                                {mychecklist}%
                                {\caption{#2}\label{#3}}% ← We want the caption before the tabular environment
                                {}%
}%

(In the example test.tex from part 4 not just \caption and \label will be delivered but it will be tested whether #2 (⟨caption⟩) and #3 (⟨label⟩) are empty and in case ⟨caption⟩ is empty while ⟨label⟩ is not, a warning-message will be delivered as ususally placing a referencing-label without a caption/without a sectioning command does not make much sense.)


Due to the 30 000-character-limit I had to divide this answer into four parts.

This is part 1 of my answer.

Part 1 consists of explanations on how to divide things into different sub-routines and then put together the desired mechanism.

Part 2 consists of several package files/.sty-files which can be loaded via \usepackage/\RequirePackage and which bring along some of the needed sub-routines.

Part 3 also consists of several package files/.sty-files which can be loaded via \usepackage/\RequirePackage and which bring along some of the needed sub-routines.

Part 4 delivers a LaTeX document which via \RequirePackage/\usepackage loads the packages with the needed sub-routines from part 2 and part 3. Within the head of that document, even before the preamble, these sub-routines are used for putting together the desired generic and non-generic user-level-macros.

For testing, save the package files/.sty-files provided in part 2 and part 3 and text.tex from part 4 within the same directory and compile test.tex with a LaTeX-engine.

Ulrich Diez
  • 28,770
1

As Ulrich Diez pointed out, this thread contained a suitable solution for my problem.

But this solution prohibits calling other macros with the arguments. I wrapped it in another command so I could add label and caption. like so:

\usepackage{xparse}
\usepackage{tabularx}

% I copied & pasted this part:
\ExplSyntaxOn
\NewDocumentCommand{\newlongcommand}{mm}
{% #1 = command to define, #2 = replacement text
    \cs_new:Npn #1 ##1
    {
        \tl_set:Nn \l__simon_args_tl { ##1 }
        #2
    }
}
\NewDocumentCommand{\Arg}{m}
{
    \tl_item:Nn \l__simon_args_tl { #1 }
}

\tl_new:N \l__simon_parse_args_tl
\ExplSyntaxOff

% the actual command
\newlongcommand{\coretable}{
\begin{tabularx}{\textwidth}{|llX|llX|llX|llX|llX|llX|}
    % my actual stuff
\end{tabularx}
}

% the wrapper
\newcommand{\featuretable}[4]{
\begin{table}[h]
    \caption{Features of #1}
    \label{#2}
    \coretable{#3}
    \vskip0.5em
    #4
\end{table}
}

This is called like

\featuretable{name}{label}{{A1}{A2}{A3}{A4}{B1}{B2}{B3}{C1}{C2}{C3}{C4}{C5}{C6}{D1}{D2}{D3}{E1}{E2}{F1}{F2}}{extra}
azrael
  • 21
1

Due to the 30 000-character-limit I had to divide this answer into four parts.

This is part 4 of my answer.

Part 1 consists of explanations on how to divide things into different sub-routines and then put together the desired mechanism.

Part 2 consists of several package files/.sty-files which can be loaded via \usepackage/\RequirePackage and which bring along some of the needed sub-routines.

Part 3 also consists of several package files/.sty-files which can be loaded via \usepackage/\RequirePackage and which bring along some of the needed sub-routines.

Part 4 delivers a LaTeX document which via \RequirePackage/\usepackage loads the packages with the needed sub-routines from part 2 and part 3. Within the head of that document, even before the preamble, these sub-routines are used for putting together the desired generic and non-generic user-level-macros.

For testing save the package files/.sty-files provided in part 2 and part 3 and text.tex from part 4 within the same directory and compile test.tex with a LaTeX-engine.


Text file with user-level macros and testing-document test.tex:

%%//////////////////////////////////////////////////////////////////////////////
%% AUTHOR
%%
%% Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%
%% LICENCE AND COPYRIGHT
%%
%% Copyright (C) 2019 by Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%..............................................................................
%% This work may be distributed and/or modified under the conditions of the
%% LaTeX Project Public Licence (LPPL), either version 1.3 of this license or 
%% (at your option) any later version. 
%% (The latest version of this license is in:
%%    http://www.latex-project.org/lppl.txt
%%  and version 1.3 or later is part of all distributions of 
%%  LaTeX version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included documentation nor for 
%% any other part/component of this work.
%% If something breaks, you usually may keep the pieces.
%%
%%//////////////////////////////////////////////////////////////////////////////
\RequirePackage{UD_Paraphernalia_2019_09_03}[2019/09/03]%
\RequirePackage{UD_ExtractFirstUndelimitedArg_2019_09_03}[2019/09/03]%
\RequirePackage{UD_ReplaceKthUndelimited_2019_09_03}[2019/09/03]%
\RequirePackage{UD_TrimSpaces_2019_09_03}[2019/09/03]%
\RequirePackage{UD_ExtractFirstCommaDelimitedArg_2019_09_03}[2019/09/03]%
\RequirePackage{UD_Increment_2019_09_03}[2019/09/03]%
\RequirePackage{UD_NumCheck_2019_09_03}[2019/09/03]%
%%//////////////////////////////////////////////////////////////////////////////
%% GENERIC USER-LEVEL-MACROS
%%
\makeatletter
%%==============================================================================
%% Automatic creation of a checklist-table for parameters from 
%% - a comma-list with index-numbers denoting parameters and
%% - a list of parameter-names.
%% - Tokens to insert before the tokens that form the tabularx-environment with
%%   the checklist table can be provided.
%% - Tokens to insert behind the tokens that form the tabularx-environment with
%%   the checklist table can be provided.
%%
%% Index number 1 occuring in the comma-list means that the parameter
%% whose name is the first element in the list of parameter-names is 
%% checked.
%% Index number K means that the parameter whose name is the K-th
%% element in the list of parameter-names is checked.
%%
%% That table comes as a table-environment holding a tabularx-environment.
%%
%% In case an error-message needs to be raised, the <name of macro that did 
%% call \mychecklistwithparameternames> is included into that message.
%%..............................................................................
%% \mychecklistwithparameternames{<list of comma separated 
%%                                 index-numbers>}%
%%                               {<list of undelimited arguments for L names 
%%                                 of the L parameters>}%
%%                               {<name of macro that did call 
%%                                 \mychecklistwithparameternames>}%
%%                               {<tokens to insert before the 
%%                                 tabularx environment>}%
%%                               {<tokens to insert behind the
%%                                 tabularx environment>}%
%%
%% ( Due to \romannumeral0-expansion, the tokens that form the table and
%%   probably some error-messages  will be delivered after
%%   two expansion-steps/after having \mychecklistwithparameternames 
%%   "hit" via two \expandafter(-chains).  )
%%==============================================================================
\newcommand\mychecklistwithparameternames[5]{%
  % #1 = <list of comma separated arguments>
  % #2 = <list of L names of the L parameters>
  % #3 = <name of macro that did call \mychecklistwithparameternames>
  % #4 = <tokens to insert before the tabularx environment>
  % #4 = <tokens to insert behind the tabularx environment>
  \romannumeral0%
  \DetectAmountOfParametersAndInitFlagsLoop{#2}{}{0}{#1}{#2}{#3}{#4}{#5}%
}%
\newcommand\DetectAmountOfParametersAndInitFlagsLoop[8]{%
  % #1 = <list of L names of the L parameters (where elements get removed 
  %       during the loop)>
  % #2 = <to-be-constructed list of L flags (one flag for each of the 
  %       L parameters)>
  % #3 = <amount of parameters>
  % #4 = <list of comma separated arguments>
  % #5 = <list of L names of the L parameters (where elements do not get
  %       removed so that it can be used after the loop)>
  % #6 = <name of macro that did call \mychecklistwithparameternames>
  % #7 = <tokens to insert before the tabularx environment>
  % #8 = <tokens to insert behind the tabularx environment>
  \UD@CheckWhetherBlank{#1}{%
    \SetFlagsNWarningsLoop{#4}{#2}{}{#3}{#5}{#6}{#7}{#8}%
  }{%
     \UD@PassThirdBeforeTwiceExpandedFirstToSecond{\UD@Increment{#3}}{%
       \UD@PassThirdBeforeFirstToSecond{#2{0}}{%
          \UD@SecondOfTwo{}%
       }%
     }%
     {%
       \expandafter\DetectAmountOfParametersAndInitFlagsLoop
       \expandafter{\UD@FirstOfTwo{}#1}%
     }%
     {#4}{#5}{#6}{#7}{#8}%
  }%
}%
\newcommand\SetFlagsNWarningsLoop[8]{%
  % #1 = <list of comma separated arguments>
  % #2 = <list of L flags (one flag for each of the L parameters)>
  % #3 = <list of warnings>
  % #4 = <amount of parameters>
  % #5 = <list of L names of the L parameters>
  % #6 = <name of macro that did call \mychecklistwithparameternames>
  % #7 = <tokens to insert before the tabularx environment>
  % #8 = <tokens to insert behind the tabularx environment>
  \UD@CheckWhetherBlank{#1}{%
    \TableRowsLoop{#2}{#5}{}{#3}{#7}{#8}%
  }{%
    \UD@PassThirdBeforeTwiceExpandedFirstToSecond{%
      \UD@ExtractFirstCommaArg{#1}%
    }{%
      \UD@SecondOfTwo{}%
    }{%
      \UD@CheckWhetherNull
    }{%
      \UD@Exchange{{#2}{#3}}%
    }{%
      \UD@PassThirdBeforeTwiceExpandedFirstToSecond{%
        \UD@ExtractFirstCommaArg{#1}%
      }{%
        \UD@SecondOfTwo{}%
      }{%
        \UD@CheckWhetherDigitTokensInRangeOnly
      }%
      {1}{#4}{%
        \UD@PassThirdBeforeFirstToSecond{#3}{%
          \expandafter\expandafter\expandafter
          \UD@PassThirdBeforeFirstToSecond
          \UD@ReplaceKthArg{\UD@ExtractFirstCommaArg{#1}}{1}{#2}{%
            \UD@SecondOfTwo{}%
          }%
        }%
      }{%
        \UD@PassThirdBeforeTwiceExpandedFirstToSecond{%
          \UD@PassThirdBeforeTwiceExpandedFirstToSecond{%
            \UD@ExtractFirstCommaArg{#1}%
          }{%
            #3\UD@NotANumberInValidRangeError
          }{#6}{optional}{1}{#4}%
        }{%
          \UD@PassThirdBeforeFirstToSecond{#2}{%
            \UD@SecondOfTwo{}%
          }%
        }%
      }%
    }%
    {%
      \expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToComma#1,}{%
        \SetFlagsNWarningsLoop{}%
      }{%
        \expandafter\SetFlagsNWarningsLoop\expandafter{\UD@GobbleToComma#1}%
      }%
    }%
    {#4}{#5}{#6}{#7}{#8}%
  }%
}%
\newcommand\TableRowsLoop[6]{%
  % #1 = <list of L flags (one flag for each of the L parameters)>
  % #2 = <list of L names of the L parameters>
  % #3 = <table-rows constructed so far>
  % #4 = <list of warnings>
  % #5 = <tokens to insert before the tabularx environment>
  % #6 = <tokens to insert behind the tabularx environment>
  \UD@CheckWhetherNull{#1}{%
    \UD@CheckWhetherNull{#3}{ }{ %<-This space must be!
      \begin{table}%
      #5%
      \begin{tabularx}{\textwidth}{|X|r|}%
      #3\\\hline
      \end{tabularx}%
      #6%
      \end{table}%
    }%
    #4%
  }{%
    \expandafter\UD@PassThirdBeforeFirstToSecond\expandafter{%
      \romannumeral0%
      \UD@PassThirdBeforeTwiceExpandedFirstToSecond{%
        \UD@ExtractFirstArg{#1}%
      }{%
        \UD@SecondOfTwo{}%
      }{%
        \UD@CheckWhetherDigitTokensInRangeOnly
      }{1}{1}{%
        \expandafter\UD@CheckWhetherNull\expandafter{\UD@FirstOfTwo{}#1}{%
          \UD@Exchange{&x}%
        }{%
          \UD@Exchange{&x\\}%
        }%
      }{%
        \expandafter\UD@CheckWhetherNull\expandafter{\UD@FirstOfTwo{}#1}{%
          \UD@Exchange{&}%
        }{%
          \UD@Exchange{&\\}%
        }%
      }%
      {%
        \expandafter\expandafter
        \expandafter            \UD@Exchange
        \expandafter\expandafter
        \expandafter{%
        \UD@ExtractFirstArg{#2}}{ #3\hline}%
      }%
    }{%
      \expandafter\UD@PassThirdBeforeFirstToSecond\expandafter{%
        \UD@FirstOfTwo{}#2%
      }{%
        \expandafter\UD@PassThirdBeforeFirstToSecond\expandafter{%
          \UD@FirstOfTwo{}#1%
        }{%
          \UD@SecondOfTwo{}%
        }%
      }%
    }%
    {\TableRowsLoop}{#4}{#5}{#6}%
  }%
}%
%%//////////////////////////////////////////////////////////////////////////////
%% NON-GENERIC USER-LEVEL-MACROS
%%
%%==============================================================================
%% Error-message in case label but no caption
%%..............................................................................
%% \LabelWithoutCaptionError{<name of command which triggers the error-message>}%
%%                          {<syntax descriptor of caption argument>}%
%%                          {<syntax descriptor of label argument>}%
%%
%% The <syntax descriptors are to hold a phrase like "fifth non-optional".
%%==============================================================================
\newcommand\LabelWithoutCaptionError[3]{%
  \begingroup
  \GenericError{%
    \@backslashchar#1\space\space\space\@spaces\@spaces\@spaces
  }{%
   \@backslashchar#1-error\on@line: Referencing-label without caption%
  }{%
    See the comments of this file for explanation.%
  }{%
    The #2 argument of \@backslashchar#1 denotes a phrase that goes inside%
    \MessageBreak
    a \string\caption\space -command.%
    \MessageBreak
    The #3 argument of \@backslashchar#1 denotes a phrase that goes inside%
    \MessageBreak
    a \string\label\space -command.%
    \MessageBreak
    You specified an empty caption and a non-empty label.%
    \MessageBreak
    Usually it does not make sense to specifa a label without a sectioning
    \MessageBreak
    command like \string\caption\space or \string\section\space to refer to.%
  }%
  \endgroup
}%

%%==============================================================================
%% Automatic creation of a checklist-table for a specific set of parameters from 
%% - a comma-list with index-numbers denoting parameters 
%% - and a caption.
%%
%% That table comes as a table-environment holding a 
%% tabularx-environment and (in case the caption-argument is not empty) 
%% a caption.
%%..............................................................................
%% \mychecklist[<list of comma separated index-numbers>]%
%%             {<caption of the table>}%
%%             {<referencing label of the table>}%
%%
%% ( Due to \romannumeral0-expansion, the tokens that form the table and
%%   probably some error-messages  will be delivered after
%%   two expansion-steps/after having \mychecklistwithparameternames 
%%   "hit" via two \expandafter(-chains).  )
%%==============================================================================
\newcommand\mychecklist[3][1,3 , 5]{%
  \mychecklistwithparameternames{#1}{%
     {param 01}%
     {param 02}%
     {param 03}%
     {param 04}%
     {param 05}%
     {param 06}%
     {param 07}%
     {param 08}%
     {param 09}%
     {param 10}%
     {param 11}%
     {param 12}%
     {param 13}%
     {param 14}%
     {param 15}%
  }%
  {mychecklist}%
  {%
    \UD@CheckWhetherNull{#2}{%
      \UD@CheckWhetherNull{#3}{}{%
        \LabelWithoutCaptionError{mychecklist}%
                                 {first non-optional}%
                                 {second non-optional}%
      }%
    }{%
      \caption{#2}%
    }%
    \UD@CheckWhetherNull{#3}{}{\label{#3}}%
  }%
  {}%
}%
%%//////////////////////////////////////////////////////////////////////////////
\makeatother
%%//////////////////////////////////////////////////////////////////////////////
%% DOCUMENTCLASS AND ADDITIONAL PACKAGES:
%%
\documentclass{article}

\usepackage[colorlinks]{hyperref} % <- only used for demonstrating referencing.
\usepackage{tabularx}

\begin{document}

% Did you realize that \nameref automatically removes the full stop (.) at 
% the end of the sentence that forms the caption? ;-)

Refrence to
\hyperref[SplendidTableLabel]{table~\ref*{SplendidTableLabel}}
which has the caption
``\nameref{SplendidTableLabel}''.

Refrence to
\hyperref[MarvellousTableLabel]{table~\ref*{MarvellousTableLabel}}
which has the caption
``\nameref{MarvellousTableLabel}''.

Refrence to
\hyperref[TreeTableLabelA]{table~\ref*{TreeTableLabelB}}
which has the caption 
``\hyperref[TreeTableLabelA]{\nameref*{TreeTableLabelB}}''.

\mychecklist{A splendid table.}{SplendidTableLabel}% As default 1,3 and 5 are 
                                                  % checked.

\mychecklist[1, 2, 3, 4 ,5 , 6, ,7, 8 ,9, 10 , 11, 12, 13 , 14 , 15]%
            {A marvellous table.}{MarvellousTableLabel}%

\mychecklistwithparameternames{1, 4, 5}{%
   {Birch tree}% = parameter 1
   {Cedar}% = parameter 2
   {Chestnut}% = parameter 3
   {Oak}% = parameter 4
   {Cypress}% = parameter 5
   {Elm tree}% = parameter 6
   {Fir tree}% = parameter 7
   {Hazel-nut tree}% = parameter 8
   {Willow}% = parameter 9
   {Beech}% = parameter 10
   {Maple}% = parameter 11
   {Linden tree}% = parameter 12
   {Pine}% = parameter 13
   {Peach tree}% = parameter 14
   {Olive tree}% = parameter 15
   {Joshua tree}% = parameter 16
   {Sequoia}% = parameter 17
   {Cotton Wood tree}% = parameter 18
}{%
  mychecklistwithparameternames%
}{%
  {%
    \centering
    \large
    \textbf{%
      \phantomsection
      \label{TreeTableLabelA}% <- for the hyperref-anchor/destination.
      Please check the trees!%
    }%
    \par
  }%
  \bigskip
  \noindent
}{%
  \caption{A table for checking trees.}%
  \label{TreeTableLabelB}% <- for the textual phrases.
}%

\end{document}

enter image description here

enter image description here


Due to the 30 000-character-limit I had to divide this answer into four parts.

This is part 4 of my answer.

Part 1 consists of explanations on how to divide things into different sub-routines and then put together the desired mechanism.

Part 2 consists of several package files/.sty-files which can be loaded via \usepackage/\RequirePackage and which bring along some of the needed sub-routines.

Part 3 also consists of several package files/.sty-files which can be loaded via \usepackage/\RequirePackage and which bring along some of the needed sub-routines.

Part 4 delivers a LaTeX document which via \RequirePackage/\usepackage loads the packages with the needed sub-routines from part 2 and part 3. Within the head of that document, even before the preamble, these sub-routines are used for putting together the desired generic and non-generic user-level-macros.

For testing save the package files/.sty-files provided in part 2 and part 3 and text.tex from part 4 within the same directory and compile test.tex with a LaTeX-engine.

Ulrich Diez
  • 28,770
1

Due to the 30 000-character-limit I had to divide this answer into four parts.

This is part 3 of my answer.

Part 1 consists of explanations on how to divide things into different sub-routines and then put together the desired mechanism.

Part 2 consists of several package files/.sty-files which can be loaded via \usepackage/\RequirePackage and which bring along some of the needed sub-routines.

Part 3 also consists of several package files/.sty-files which can be loaded via \usepackage/\RequirePackage and which bring along some of the needed sub-routines.

Part 4 delivers a LaTeX document which via \RequirePackage/\usepackage loads the packages with the needed sub-routines from part 2 and part 3. Within the head of that document, even before the preamble, these sub-routines are used for putting together the desired generic and non-generic user-level-macros.

For testing save the package files/.sty-files provided in part 2 and part 3 and text.tex from part 4 within the same directory and compile test.tex with a LaTeX-engine.


Package UD_ExtractFirstCommaDelimitedArg_2019_09_03.sty:

%%//////////////////////////////////////////////////////////////////////////////
%% AUTHOR
%%
%% Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%
%% LICENCE AND COPYRIGHT
%%
%% Copyright (C) 2019 by Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%..............................................................................
%% This work may be distributed and/or modified under the conditions of the
%% LaTeX Project Public Licence (LPPL), either version 1.3 of this license or 
%% (at your option) any later version. 
%% (The latest version of this license is in:
%%    http://www.latex-project.org/lppl.txt
%%  and version 1.3 or later is part of all distributions of 
%%  LaTeX version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included documentation nor for 
%% any other part/component of this work.
%% If something breaks, you usually may keep the pieces.
%%
%%//////////////////////////////////////////////////////////////////////////////
\NeedsTeXFormat{LaTeX2e}[1994/06/01]%
\ProvidesPackage{UD_ExtractFirstCommaDelimitedArg_2019_09_03}%
  [2019/09/03 v 1.0 Extract first item of comma-list. (Ulrich Diez)]%
\RequirePackage{UD_Paraphernalia_2019_09_03}[2019/09/03]%
\RequirePackage{UD_TrimSpaces_2019_09_03}[2019/09/03]%
%%//////////////////////////////////////////////////////////////////////////////
%% EXPANDABLE EXTRACTION OF FIRST ITEM OF COMMA-LIST
%%
%%------------------------------------------------------------------------------
%% Extract first inner comma-delimited argument:
%%
%% \UD@ExtractFirstCommaArg{<comma list>}
%%
%% yields:
%% 
%% <first item/first comma-delimited argument from <comma
%%  list> with surrounding spaces and one level of surroundinng
%%  braces removed if present>
%%
%% <comma-list> is considered a sequence of comma-delimited arguments.
%%
%% The <first item/first comma-delimited argument from <comma list>> 
%% will be extracted. The remainder of the <comma list> will be discarded.
%%
%% Then space tokens surrounding the <first item/first comma-delimited
%% argument from <comma list>> will be removed.
%%
%% Then one pair of braces surrounding the entire result of space-removal
%% will be removed if present!!!!!
%%
%% Then the result thereof, no matter if empty or not, will be delivered.
%%
%% This implies you can have <comma-delimited arguments> contain
%% emptiness or commas and spaces by nesting them into braces.
%%
%% Examples:
%%
%%   \UD@ExtractFirstCommaArg{} yields: emptiness/no token at all
%%
%%   \UD@ExtractFirstCommaArg{ } yields: emptiness/no token at all
%%
%%   \UD@ExtractFirstCommaArg{ A B } yields: A<space>B
%%
%%   \UD@ExtractFirstCommaArg{,A,B,C,D,E} yields: emptiness/no token at all
%%
%%   \UD@ExtractFirstCommaArg{{},A,B,C,D,E} yields: emptiness/no token at all
%%
%%   \UD@ExtractFirstCommaArg{A,B,C,D,E} yields: A
%%
%%   \UD@ExtractFirstCommaArg{{AB},C,D,E} yields: AB
%%
%%   \UD@ExtractFirstCommaArg{  AB  ,C,D,E} yields: AB
%%
%%   \UD@ExtractFirstCommaArg{  {AB}  ,C,D,E} yields: AB
%%
%%   \UD@ExtractFirstCommaArg{ { A, B } ,C,D,E} yields: <space>A,<space>B<space>
%%
%%   \UD@ExtractFirstCommaArg{ { {AB} } ,C,D,E} yields: <space>{AB}<space>
%% 
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having \UD@ExtractFirstCommaArg "hit" via
%%   two \expandafter(-chains).  )
%%..............................................................................
\@ifdefinable\UD@GobbleToComma{\long\def\UD@GobbleToComma#1,{}}%
\@ifdefinable\UD@RemoveComma{\long\def\UD@RemoveComma#1,{#1}}%
\@ifdefinable\UD@RemoveFromCommaTillUD@SelDOm{%
  \long\def\UD@RemoveFromCommaTillUD@SelDOm#1,#2\UD@SelDOm{#1,}%
}%
\newcommand\UD@ExtractFirstCommaArg[1]{%
  \romannumeral0%
  \UD@ExtractFirstCommaArgLoop{.#1,\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstCommaArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToComma#1}%
  {%
    \UD@FirstOfTwo{\expandafter\expandafter\expandafter}{} %
    \expandafter\UD@RemoveComma
    \romannumeral0\expandafter\UD@TrimTrailSpaceLoop
                  \expandafter{%
                  \romannumeral0%
                  \expandafter\expandafter\expandafter\UD@TrimAllLeadSpaceLoop
                  \expandafter\expandafter\expandafter{%
                  \expandafter\UD@GobbleDot\UD@RemoveComma#1}},%
  }%
  {%
    \expandafter\UD@ExtractFirstCommaArgLoop
    \expandafter{\UD@RemoveFromCommaTillUD@SelDOm#1}%
  }%
}%
\endinput
%%//////////////////////////////////////////////////////////////////////////////

Package UD_Increment_2019_09_03.sty:

%%//////////////////////////////////////////////////////////////////////////////
%% AUTHOR
%%
%% Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%
%% LICENCE AND COPYRIGHT
%%
%% Copyright (C) 2019 by Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%..............................................................................
%% This work may be distributed and/or modified under the conditions of the
%% LaTeX Project Public Licence (LPPL), either version 1.3 of this license or 
%% (at your option) any later version. 
%% (The latest version of this license is in:
%%    http://www.latex-project.org/lppl.txt
%%  and version 1.3 or later is part of all distributions of 
%%  LaTeX version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included documentation nor for 
%% any other part/component of this work.
%% If something breaks, you usually may keep the pieces.
%%
%%//////////////////////////////////////////////////////////////////////////////
\NeedsTeXFormat{LaTeX2e}[1994/06/01]%
\ProvidesPackage{UD_Increment_2019_09_03}%
  [2019/09/03 v 1.0 Expandably increment number without eTeX. (Ulrich Diez)]%
\RequirePackage{UD_Paraphernalia_2019_09_03}[2019/09/03]%
%%//////////////////////////////////////////////////////////////////////////////
%% EXPANDABLE INCREMENTING OF NATURAL NUMBER FORMED BY A SEQUENCE OF
%% EXPLICIT CATCODE-12-CHARACTER-TOKENS FROM THE SET {0,1,2,3,4,5,6,7,8,9}
%%==============================================================================
%% \UD@Increment{<natural number k as sequence of explicit catcode-12-character-
%%                tokens from the set 0123456789>}
%% ->
%% <natural number (k+1) as sequence of explicit catcode-12-character-tokens
%%  from the set 0123456789>
%%
%% Emptiness is interpreted as "0".
%%
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having \UD@Increment "hit" via
%%   two \expandafter(-chains).  )
%%..............................................................................
%% Examples: \UD@Increment{999} -> 1000
%%           \UD@Increment{0} -> 1
%%           \UD@Increment{} -> 1
%%==============================================================================
\newcommand\UD@Increment[1]{%
  % #1 = digit sequence to increment.
  \romannumeral0%
  % Start the loop for reversing the digit-sequence so that less significant
  % digits are at the beginning and not at the end of the digit sequence; when
  % reversing is done, apply \UD@IncrementFork{} and a terminating \relax to
  % start replacing least significant digits by digits whose value is larger
  % by 1:
  \UD@IncrementReverse{\UD@IncrementFork{}}{\relax}{}#1\relax
}%
%%------------------------------------------------------------------------------
%% Reverse digit sequence and prepend and append tokens afterwards:
%%------------------------------------------------------------------------------
\newcommand\UD@IncrementReverse[4]{%
  % #1 = tokens to prepend to reversed digit sequence.
  % #2 = tokens to append to reversed digit sequence.
  % #3 = digit sequence reversed so far.
  % #4 = first digit of remaining digit sequence to reverse.
  \ifx\relax#4%
    \expandafter\UD@FirstOfTwo
  \else
    \expandafter\UD@SecondOfTwo
  \fi
  {#1#3#2}{\UD@IncrementReverse{#1}{#2}{#4#3}}%
}%
\@ifdefinable\UD@IncrementSelect{%
  \long\def\UD@IncrementSelect#10123456789\relax#2#3!!{#2}%
}%
\newcommand\UD@IncrementFork[2]{%
  % #1 = carry-zeros collected so far
  % #2 = to-be incremented first digit/least significant digit of digit sequence
  \UD@IncrementSelect
  #2123456789\relax{\UD@IncrementReverse{ }{}{}#11}%<-this means the current
                                                  %   least significant digit is
                                                  %   0 and to be replaced by 1.
  0#223456789\relax{\UD@IncrementReverse{ }{}{}#12}%<-this means the current
                                                  %   least significant digit is
                                                  %   1 and to be replaced by 2.
  01#23456789\relax{\UD@IncrementReverse{ }{}{}#13}%<-this means the current
                                                  %   least significant digit is
                                                  %   2 and to be replaced by 3.
  012#2456789\relax{\UD@IncrementReverse{ }{}{}#14}%<-this means the current
                                                  %   least significant digit is
                                                  %   3 and to be replaced by 4.
  0123#256789\relax{\UD@IncrementReverse{ }{}{}#15}%<-this means the current
                                                  %   least significant digit is
                                                  %   4 and to be replaced by 5.
  01234#26789\relax{\UD@IncrementReverse{ }{}{}#16}%<-this means the current
                                                  %   least significant digit is
                                                  %   5 and to be replaced by 6.
  012345#2789\relax{\UD@IncrementReverse{ }{}{}#17}%<-this means the current
                                                  %   least significant digit is
                                                  %   6 and to be replaced by 7.
  0123456#289\relax{\UD@IncrementReverse{ }{}{}#18}%<-this means the current
                                                  %   least significant digit is
                                                  %   7 and to be replaced by 8.
  01234567#29\relax{\UD@IncrementReverse{ }{}{}#19}%<-this means the current
                                                  %   least significant digit is
                                                  %   8 and to be replaced by 9.
  012345678#2\relax{\UD@IncrementFork{#10}}%       <- this means the current
                                           %          least significant digit is
                                           %          9 and to be replaced by 0,
                                           %          which will be a carry-zero
                                           %          , and the next digit needs
                                           %          to be incremented.
  0123456789#2{\UD@IncrementReverse{ }{}{}#11\relax}%<-this means the natural
                                                    % number to increment
                                                    % consisted of digits "9" 
                                                    % only and the terminating
                                                    % \relax was encountered.
  0123456789\relax{\UD@IncrementReverse{ }{}{}#11#2}%<- this should not happen
                                                 %    as it means there is a
                                                 %    non-digit.
  !!%
}%
\endinput
%%//////////////////////////////////////////////////////////////////////////////

Package UD_NumCheck_2019_09_03.sty:

%%//////////////////////////////////////////////////////////////////////////////
%% AUTHOR
%%
%% Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%
%% LICENCE AND COPYRIGHT
%%
%% Copyright (C) 2019 by Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%..............................................................................
%% This work may be distributed and/or modified under the conditions of the
%% LaTeX Project Public Licence (LPPL), either version 1.3 of this license or 
%% (at your option) any later version. 
%% (The latest version of this license is in:
%%    http://www.latex-project.org/lppl.txt
%%  and version 1.3 or later is part of all distributions of 
%%  LaTeX version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included documentation nor for 
%% any other part/component of this work.
%% If something breaks, you usually may keep the pieces.
%%
%%//////////////////////////////////////////////////////////////////////////////
\NeedsTeXFormat{LaTeX2e}[1994/06/01]%
\ProvidesPackage{UD_NumCheck_2019_09_03}%
  [2019/09/03 v 1.0 Check whether argument is digit-sequence representing 
                                    a non-negative integer. (Ulrich Diez)]
\RequirePackage{UD_Paraphernalia_2019_09_03}[2019/09/03]%
\RequirePackage{UD_ExtractFirstUndelimitedArg_2019_09_03}[2019/09/03]%
\RequirePackage{UD_TrimSpaces_2019_09_03}[2019/09/03]%
%%//////////////////////////////////////////////////////////////////////////////
%% EXPANDABLE CHECKING WHETHER ARGUMENT IS CATCODE-12-DIGIT-SEQUENCE
%%    
%%==============================================================================
%% Check whether argument consists of a single catcode-12-digit:
%%..............................................................................
%% \UD@CheckWhetherDigit{<argument which is to be checked>}%
%%                      {<tokens to be delivered in case that argument
%%                        consists of a single catcode-12-digit>}%
%%                      {<tokens to be delivered in case that argument
%%                        does not consist of a single catcode-12-digit>}%
%%
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having \UD@CheckWhetherDigit "hit" via
%%   two \expandafter(-chains).  )
%%==============================================================================
\newcommand\UD@CheckWhetherDigit[1]{%
  \romannumeral0%
  \UD@CheckWhetherNoExclam{#1}{%
    \UD@@CheckWhetherDigit
    !#1!1!2!3!4!5!6!7!8!9!{\UD@FirstOfTwo}%
    !0!#1!2!3!4!5!6!7!8!9!{\UD@FirstOfTwo}%
    !0!1!#1!3!4!5!6!7!8!9!{\UD@FirstOfTwo}%
    !0!1!2!#1!4!5!6!7!8!9!{\UD@FirstOfTwo}%
    !0!1!2!3!#1!5!6!7!8!9!{\UD@FirstOfTwo}%
    !0!1!2!3!4!#1!6!7!8!9!{\UD@FirstOfTwo}%
    !0!1!2!3!4!5!#1!7!8!9!{\UD@FirstOfTwo}%
    !0!1!2!3!4!5!6!#1!8!9!{\UD@FirstOfTwo}%
    !0!1!2!3!4!5!6!7!#1!9!{\UD@FirstOfTwo}%
    !0!1!2!3!4!5!6!7!8!#1!{\UD@FirstOfTwo}%
    !0!1!2!3!4!5!6!7!8!9!{\UD@SecondOfTwo}%
    !!!!%
    {\UD@FirstOfTwo{\expandafter}{} \UD@FirstOfTwo}%
    {\UD@FirstOfTwo{\expandafter}{} \UD@SecondOfTwo}%
  }{%
    \UD@FirstOfTwo{\expandafter}{} \UD@SecondOfTwo
  }%
}%
\@ifdefinable\UD@@CheckWhetherDigit{%
  \long\def\UD@@CheckWhetherDigit#1!0!1!2!3!4!5!6!7!8!9!#2#3!!!!{#2}%
}%
%%------------------------------------------------------------------------------
%% Check whether argument contains no exclamation mark which is not nested in 
%% braces:
%%..............................................................................
%% \UD@CheckWhetherNoExclam{<argument which is to be checked>}%
%%                         {<tokens to be delivered in case that argument
%%                           contains no exclamation mark>}%
%%                         {<tokens to be delivered in case that argument
%%                           contains exclamation mark>}%
%%------------------------------------------------------------------------------
\@ifdefinable\UD@GobbleToExclam{\long\def\UD@GobbleToExclam#1!{}}%
\newcommand\UD@CheckWhetherNoExclam[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToExclam#1!}%
}%
%%==============================================================================
%% Check whether brace-balanced argument starts with a digit
%%..............................................................................
%% \UD@CheckWhetherFirstTokenIsDigit{<argument which is to be checked>}%
%%                      {<tokens to be delivered in case that 
%%                        <argument which is to be checked>'s first token 
%%                        is a digit>}%
%%                      {<tokens to be delivered in case that
%%                        <argument which is to be checked>'s first token 
%%                        is not a digit>}%
%%
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having \UD@CheckWhetherFirstTokenIsDigit "hit" 
%%   via two \expandafter(-chains).  )
%%==============================================================================
\newcommand\UD@CheckWhetherFirstTokenIsDigit[1]{%
  \romannumeral0%
  \UD@CheckWhetherNull{#1}{\UD@FirstOfTwo{\expandafter}{} \UD@SecondOfTwo}{%
    \UD@CheckWhetherBrace{#1}{\UD@FirstOfTwo{\expandafter}{} \UD@SecondOfTwo}{%
      \UD@CheckWhetherLeadingSpace{#1}{%
        \UD@FirstOfTwo{\expandafter}{} \UD@SecondOfTwo
      }{%
        \expandafter\expandafter\expandafter\UD@CheckWhetherDigit
        \expandafter\expandafter\expandafter{%
        \UD@ExtractFirstArg{#1}}%
        {\UD@FirstOfTwo{\expandafter}{} \UD@FirstOfTwo}%
        {\UD@FirstOfTwo{\expandafter}{} \UD@SecondOfTwo}%
      }%
    }%
  }%
}%
%%==============================================================================
%% Check whether argument does consist of digits only:
%%..............................................................................
%% \UD@CheckWhetherDigitTokensOnly{<argument which is to be checked>}%
%%                  {<tokens to be delivered in case that 
%%                    <argument which is to be checked> contains
%%                    only digits>}%
%%                  {<tokens to be delivered in case that 
%%                    <argument which is to be checked> is empty or also
%%                    contains tokens other than digits>}%
%%
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having  \UD@CheckWhetherDigitTokensOnly "hit" via
%%   two \expandafter(-chains).  )
%%==============================================================================
\newcommand\UD@CheckWhetherDigitTokensOnly[1]{%
  \romannumeral0%
  \UD@CheckWhetherNull{#1}{%
    \UD@FirstOfTwo{\expandafter}{} \UD@SecondOfTwo
  }{%
    \UD@@CheckWhetherDigitTokensOnly{#1}%
  }%
}%
\newcommand\UD@@CheckWhetherDigitTokensOnly[1]{%
  \UD@CheckWhetherNull{#1}{%
    \UD@FirstOfTwo{\expandafter}{} \UD@FirstOfTwo
  }{%
    \UD@CheckWhetherFirstTokenIsDigit{#1}{%
      \expandafter\UD@@CheckWhetherDigitTokensOnly
      \expandafter{\UD@FirstOfTwo{}#1}%
    }{%
      \UD@FirstOfTwo{\expandafter}{} \UD@SecondOfTwo
    }%
  }%
}%
%%==============================================================================
%% Check whether argument does consist of digits only that form a number
%% larger equal to A and smaller equal to B:
%%..............................................................................
%% \UD@CheckWhetherDigitTokensInRangeOnly{<argument which is to be checked>}%
%%                                       {<number A>}{<number B>}%
%%                  {<tokens to be delivered in case that 
%%                     <argument which is to be checked> contains
%%                    only digits forming a number in the range from A to B>}%
%%                  {<tokens to be delivered in case that 
%%                     <argument which is to be checked> is empty or also
%%                     contains tokens other than digits or whose digits form
%%                     a number not in the range from A to B>}%
%%
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having \UD@CheckWhetherDigitTokensInRangeOnly 
%%   "hit" via two \expandafter(-chains).  )
%%==============================================================================
\newcommand\UD@CheckWhetherDigitTokensInRangeOnly[3]{%
  \romannumeral0%
  \UD@CheckWhetherDigitTokensOnly{#1}{%
     \ifnum\expandafter\UD@FirstOfTwo\expandafter{\number#1}{}<%
           \expandafter\UD@FirstOfTwo\expandafter{\number#2}{} %
     \expandafter\UD@FirstOfTwo\else\expandafter\UD@SecondOfTwo\fi
     {%
       \UD@FirstOfTwo\expandafter{} \UD@SecondOfTwo
     }{%
       \ifnum\expandafter\UD@FirstOfTwo\expandafter{\number#1}{}>%
             \expandafter\UD@FirstOfTwo\expandafter{\number#3}{} %
       \expandafter\UD@FirstOfTwo\else\expandafter\UD@SecondOfTwo\fi
       {%
          \UD@FirstOfTwo\expandafter{} \UD@SecondOfTwo
       }{%
          \UD@FirstOfTwo\expandafter{} \UD@FirstOfTwo
       }%
     }%
  }{%
    \UD@FirstOfTwo\expandafter{} \UD@SecondOfTwo
  }%
}%
%%==============================================================================
%% Error-message in case argument/element does not consist of digits only that
%% form a number larger equal to A and smaller equal to B:
%%..............................................................................
%% \UD@NotANumberInValidRangeError{<name of command which triggers the error-
%%                                  message>}%
%%                                {<argument/element which is not a number>}%
%%                                {<syntax descriptor of argument>}%
%%                                {<lower bound A>}%
%%                                {<upper bound B>}%
%%
%% The <syntax descriptor of argument> is to hold a phrase like 
%% "fifth non-optional".
%%==============================================================================
\newcommand\UD@NotANumberInValidRangeError[5]{%
  \begingroup
  \toks@{#2}%
  \GenericError{%
    \@backslashchar#1\space\space\space\@spaces\@spaces\@spaces
  }{%
   \@backslashchar#1-error\on@line: Element `\the\toks@' is not a valid number%
  }{%
    See the comments of this file for explanation.%
  }{%
    The #3 argument of \@backslashchar#1 must be a comma-separated%
    \MessageBreak
    list of elements which consist only of digits denoting natural numbers%
    \MessageBreak
    in decimal notaton in range #4--#5%
  }%
  \endgroup
}%
\endinput
%%//////////////////////////////////////////////////////////////////////////////

Due to the 30 000-character-limit I had to divide this answer into four parts.

This is part 3 of my answer.

Part 1 consists of explanations on how to divide things into different sub-routines and then put together the desired mechanism.

Part 2 consists of several package files/.sty-files which can be loaded via \usepackage/\RequirePackage and which bring along some of the needed sub-routines.

Part 3 also consists of several package files/.sty-files which can be loaded via \usepackage/\RequirePackage and which bring along some of the needed sub-routines.

Part 4 delivers a LaTeX document which via \RequirePackage/\usepackage loads the packages with the needed sub-routines from part 2 and part 3. Within the head of that document, even before the preamble, these sub-routines are used for putting together the desired generic and non-generic user-level-macros.

For testing save the package files/.sty-files provided in part 2 and part 3 and text.tex from part 4 within the same directory and compile test.tex with a LaTeX-engine.

Ulrich Diez
  • 28,770
1

Due to the 30 000-character-limit I had to divide this answer into four parts.

This is part 2 of my answer.

Part 1 consists of explanations on how to divide things into different sub-routines and then put together the desired mechanism.

Part 2 consists of several package files/.sty-files which can be loaded via \usepackage/\RequirePackage and which bring along some of the needed sub-routines.

Part 3 also consists of several package files/.sty-files which can be loaded via \usepackage/\RequirePackage and which bring along some of the needed sub-routines.

Part 4 delivers a LaTeX document which via \RequirePackage/\usepackage loads the packages with the needed sub-routines from part 2 and part 3. Within the head of that document, even before the preamble, these sub-routines are used for putting together the desired generic and non-generic user-level-macros.

For testing save the package files/.sty-files provided in part 2 and part 3 and text.tex from part 4 within the same directory and compile test.tex with a LaTeX-engine.


Package UD_Paraphernalia_2019_09_03.sty:

%%//////////////////////////////////////////////////////////////////////////////
%% AUTHOR
%%
%% Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%
%% LICENCE AND COPYRIGHT
%%
%% Copyright (C) 2019 by Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%..............................................................................
%% This work may be distributed and/or modified under the conditions of the
%% LaTeX Project Public Licence (LPPL), either version 1.3 of this license or 
%% (at your option) any later version. 
%% (The latest version of this license is in:
%%    http://www.latex-project.org/lppl.txt
%%  and version 1.3 or later is part of all distributions of 
%%  LaTeX version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included documentation nor for 
%% any other part/component of this work.
%% If something breaks, you usually may keep the pieces.
%%//////////////////////////////////////////////////////////////////////////////
\NeedsTeXFormat{LaTeX2e}[1994/06/01]%
\ProvidesPackage{UD_Paraphernalia_2019_09_03}%
  [2019/09/03 v 1.0 Nice helper-macros often used by Ulrich Diez. (Ulrich Diez)]%
%%//////////////////////////////////////////////////////////////////////////////
%% PARAPHERNALIA
%% 
%%    \UD@FirstOfTwo, \UD@SecondOfTwo, \UD@Exchange, 
%%    \UD@PassThirdBeforeFirstToSecond, 
%%    \UD@PassThirdBeforeTwiceExpandedFirstToSecond, \UD@CheckWhetherNull, 
%%    \UD@CheckWhetherBrace, \UD@CheckWhetherBlank
%%    
%%==============================================================================
\newcommand\UD@FirstOfTwo[2]{#1}%
\newcommand\UD@SecondOfTwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}% !! \UD@Exchange will return the arguments with
                                % one level of surrounding braces removed if
                                % such braces that surround an argument entirely
                                % are present.
\newcommand\UD@PassThirdBeforeFirstToSecond[3]{#2{#3}{#1}}%
%%------------------------------------------------------------------------------
%% \UD@PassThirdBeforeTwiceExpandedFirstToSecond{<argument 1>}%
%%                                              {<argument 2>}%
%%                                              {<argument 3>}%
%% ->
%% <argument 2>{<argument 3>}{<argument 1 (hit by `\expandafter` twice)>}
%%
%% ( Due to \romannumeral0-expansion, the result will be delivered after two
%%   expansion-steps/after having \UD@PassThirdBeforeTwiceExpandedFirstToSecond 
%%   "hit" via two \expandafter(-chains).  )
%%------------------------------------------------------------------------------
\newcommand\UD@PassThirdBeforeTwiceExpandedFirstToSecond[2]{%
  \romannumeral0%
  \expandafter\expandafter\expandafter\UD@PassThirdBeforeFirstToSecond
  \expandafter\expandafter\expandafter{#1}{ #2}%
}%
%%------------------------------------------------------------------------------
%% 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>
%%
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having \UD@CheckWhetherNull "hit" via
%%   two \expandafter(-chains).  )
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\UD@SecondOfTwo\string{\expandafter
  \UD@SecondOfTwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@SecondOfTwo\string}\expandafter\UD@FirstOfTwo\expandafter{\expandafter
  \UD@SecondOfTwo\string}\UD@FirstOfTwo\expandafter{} %
  \UD@SecondOfTwo}{\UD@FirstOfTwo\expandafter{} \UD@FirstOfTwo}%
}%
%%------------------------------------------------------------------------------
%% Check whether argument is blank (empty or only spaces):
%%..............................................................................
%% -- Take advantage of the fact that TeX discards space tokens when
%%    "fetching" non-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}%
%%
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having \UD@CheckWhetherBlank "hit" via
%%   two \expandafter(-chains).  )
\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
%%..............................................................................
%% \UD@CheckWhetherBrace{<argument which is to be checked>}%
%%                      {<tokens to be delivered in case that argument
%%                        which is to be checked has leading
%%                        catcode-1-token>}%
%%                      {<tokens to be delivered in case that argument
%%                        which is to be checked has no leading
%%                        catcode-1-token>}%
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having \UD@CheckWhetherBrace "hit" via
%%   two \expandafter(-chains).  )
\newcommand\UD@CheckWhetherBrace[1]{%
  \romannumeral0\expandafter\UD@SecondOfTwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@FirstOfTwo\expandafter{\expandafter
  \UD@SecondOfTwo\string}\UD@FirstOfTwo\expandafter{} %
  \UD@FirstOfTwo}{\UD@FirstOfTwo\expandafter{} \UD@SecondOfTwo}%
}%
\endinput
%%//////////////////////////////////////////////////////////////////////////////

Package UD_ExtractFirstUndelimitedArg_2019_09_03.sty:

%%//////////////////////////////////////////////////////////////////////////////
%% AUTHOR
%%
%% Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%
%% LICENCE AND COPYRIGHT
%%
%% Copyright (C) 2019 by Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%..............................................................................
%% This work may be distributed and/or modified under the conditions of the
%% LaTeX Project Public Licence (LPPL), either version 1.3 of this license or 
%% (at your option) any later version. 
%% (The latest version of this license is in:
%%    http://www.latex-project.org/lppl.txt
%%  and version 1.3 or later is part of all distributions of 
%%  LaTeX version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included documentation nor for 
%% any other part/component of this work.
%% If something breaks, you usually may keep the pieces.
%%//////////////////////////////////////////////////////////////////////////////
\NeedsTeXFormat{LaTeX2e}[1994/06/01]%
\ProvidesPackage{UD_ExtractFirstUndelimitedArg_2019_09_03}%
  [2019/09/03 v 1.0 Extract first item of non-delimited-argument-list. (Ulrich Diez)]%
\RequirePackage{UD_Paraphernalia_2019_09_03}[2019/09/03]%
%%//////////////////////////////////////////////////////////////////////////////
%% EXPANDABLE EXTRACTION OF FIRST ITEM OF NON-DELIMITED-ARGUMENT-LIST
%% 
%%------------------------------------------------------------------------------
%% Extract first inner non-delimited argument:
%%..............................................................................
%%  \UD@ExtractFirstArg{ABCDE} yields  A
%%  \UD@ExtractFirstArg{{AB}CDE} yields  AB
%%     LaTeX does not gather spaces not nested in braces as (components of) 
%%     non-delimited arguments. Thus:
%%  \UD@ExtractFirstArg{ ABCDE} yields  A
%%  \UD@ExtractFirstArg{ {AB}CDE} yields  AB
%%  !!! The argument wherefrom the first inner non-delimited argument shall !!!
%%  !!! be extracted must itself not be blank.                              !!!
%%  !!! This means:                                                         !!!
%%  !!!    \UD@ExtractFirstArg{} and/or \UD@ExtractFirstArg{ } must not     !!!
%%  !!!    be performed. You can apply \UD@CheckWhetherBlank for checking   !!!
%%  !!!    whether the argument is blank.                                   !!!
%%
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having \UD@ExtractFirstArg "hit" via
%%   two \expandafter(-chains).  )
%%------------------------------------------------------------------------------
\@ifdefinable\UD@RemoveTillUD@SelDOm{%
  \long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
}%
\newcommand\UD@ExtractFirstArg[1]{%
  \romannumeral0%
  \UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@FirstOfTwo{}#1}%
  {\UD@Exchange#1{ }}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
\endinput
%%//////////////////////////////////////////////////////////////////////////////

Package UD_ReplaceKthUndelimited_2019_09_03.sty:

%%//////////////////////////////////////////////////////////////////////////////
%% AUTHOR
%%
%% Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%
%% LICENCE AND COPYRIGHT
%%
%% Copyright (C) 2019 by Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%..............................................................................
%% This work may be distributed and/or modified under the conditions of the
%% LaTeX Project Public Licence (LPPL), either version 1.3 of this license or 
%% (at your option) any later version. 
%% (The latest version of this license is in:
%%    http://www.latex-project.org/lppl.txt
%%  and version 1.3 or later is part of all distributions of 
%%  LaTeX version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included documentation nor for 
%% any other part/component of this work.
%% If something breaks, you usually may keep the pieces.
%%
%%//////////////////////////////////////////////////////////////////////////////
\NeedsTeXFormat{LaTeX2e}[1994/06/01]%
\ProvidesPackage{UD_ReplaceKthUndelimited_2019_09_03}%
  [2019/09/03 v 1.0 Replace K-th element of non-delimited-argument-list. (Ulrich Diez)]%
\RequirePackage{UD_Paraphernalia_2019_09_03}[2019/09/03]%
\RequirePackage{UD_ExtractFirstUndelimitedArg_2019_09_03}[2019/09/03]%
%%//////////////////////////////////////////////////////////////////////////////
%% EXPANDABLE REPLACEMENT OF K-TH ELEMENT OF LIST OF NON-DELIMITED
%% MACRO ARGUMENTS
%% 
%%==============================================================================
%% Replace K-th element of list of non-delimited macro arguments:
%%
%%   \UD@ReplaceKthArg{<integer K>}%
%%                    {<replacement>}%
%%                    {<list of non-delimited macro arguments>} 
%% 
%% In case a K-th argument cannot be determined in the
%% <list of non-delimited macro arguments> : 
%%   Does deliver: {<list of non-delimited macro arguments>}
%%
%% In case a K-th argument can be determined in the
%% <list of non-delimited macro arguments> : 
%%   Does deliver: {<list of non-delimited macro arguments>} 
%%                 with the list's K-th element replaced by {<replacement>}
%%
%% Each element of the <list of non-delimited macro arguments> will be nested 
%% in braces afterwards.
%%
%% The <list of non-delimited macro arguments> may be empty.
%% In this case an empty list will be returned.
%%
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having \UD@ReplaceKthArg "hit" via
%%   two \expandafter(-chains).  )
%%..............................................................................
%% Examples:
%%
%%   \UD@ReplaceKthArg{0}{c}{{A}{B}{C}{D}{E}} yields: {{A}{B}{C}{D}{E}}
%%
%%   \UD@ReplaceKthArg{3}{c}{{A}{B}{C}{D}{E}} yields: {{A}{B}{c}{D}{E}}
%%
%%   \UD@ReplaceKthArg{1}{aa}{{A}{B}{C}{D}{E}} yields: {{aa}{B}{C}{D}{E}}
%%
%%   \UD@ReplaceKthArg{4}{four}{{A}{B}{C}{D}{E}} yields: {{A}{B}{C}{four}{E}}
%%
%%   \UD@ReplaceKthArg{6}{six}{{A}{B}{C}{D}{E}} yields: {{A}{B}{C}{D}{E}}
%% 
%%   \UD@ReplaceKthArg{0}{c}{ABCDE} yields: {{A}{B}{C}{D}{E}}
%%
%%   \UD@ReplaceKthArg{3}{c}{ABCDE} yields: {{A}{B}{c}{D}{E}}
%%
%%   \UD@ReplaceKthArg{1}{aa}{ABCDE} yields: {{aa}{B}{C}{D}{E}}
%%
%%   \UD@ReplaceKthArg{4}{four}{ABCDE} yields: {{A}{B}{C}{four}{E}}
%%
%%   \UD@ReplaceKthArg{6}{six}{ABCDE} yields: {{A}{B}{C}{D}{E}}
%% 
%%   \UD@ReplaceKthArg{6}{six}{} yields: {}
%%
%%==============================================================================
\newcommand\UD@ReplaceKthArg[1]{%
  % #1: <integer K>
  \romannumeral0%
  \expandafter\UD@ReplaceKthArgCheck
  \expandafter{\romannumeral\number\number#1 000}%
}%
\newcommand\UD@ReplaceKthArgCheck[3]{%
  % #1: <amount of K letters m>
  % #2: <replacement>
  % #3: <list of non-delimited macro arguments>
  \UD@CheckWhetherNull{#1}{%
    \UD@ReplaceKthArgLoop{}{}{#3}{}{m}%
  }{%
    \expandafter\UD@ReplaceKthArgLoop
    \expandafter{\expandafter}\expandafter{\UD@FirstOfTwo{}#1}{#3}{#2}{}%
  }%
}%
\newcommand\UD@ReplaceKthArgLoop[5]{%
  % #1: <new list of non-delimited macro arguments>
  % #2: <amount of K letters m>
  % #3: <list of non-delimited macro arguments>
  % #4: <Replacement>
  % #5: <indicator whether replacement already took place. 
  %     "m" in this case. Empty otherwise.>
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@FirstOfTwo#3{}.}{ {#1}}{%
    \UD@CheckWhetherNull{#5#2}{%
      \expandafter\UD@Exchange\expandafter{\expandafter{\UD@FirstOfTwo{}#3}}{%
        \UD@ReplaceKthArgLoop{#1{#4}}{}%
      }{}{m}%
    }{%
      \expandafter\UD@Exchange\expandafter{\expandafter{\UD@FirstOfTwo{}#3}}{%
        \expandafter\UD@Exchange\expandafter{%
        \expandafter{\UD@FirstOfTwo{}#5#2}}{%
          \expandafter\UD@ReplaceKthArgLoop\expandafter{%
            \romannumeral0%
            \UD@FirstOfTwo{\expandafter\expandafter\expandafter}{} %
            \expandafter\UD@Exchange\expandafter{\expandafter{%
              \romannumeral0\UD@ExtractFirstArgLoop{#3\UD@SelDOm}%
            }}{#1}%
          }%
        }%
      }{#4}{#5}%
    }%
  }%
}%
\endinput
%%//////////////////////////////////////////////////////////////////////////////

Package UD_TrimSpaces_2019_09_03.sty:

%%//////////////////////////////////////////////////////////////////////////////
%% AUTHOR
%%
%% Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%
%% LICENCE AND COPYRIGHT
%%
%% Copyright (C) 2019 by Ulrich W. Diez (ud.usenetcorrespondence@web.de)
%%..............................................................................
%% This work may be distributed and/or modified under the conditions of the
%% LaTeX Project Public Licence (LPPL), either version 1.3 of this license or 
%% (at your option) any later version. 
%% (The latest version of this license is in:
%%    http://www.latex-project.org/lppl.txt
%%  and version 1.3 or later is part of all distributions of 
%%  LaTeX version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included documentation nor for 
%% any other part/component of this work.
%% If something breaks, you usually may keep the pieces.
%%
%%//////////////////////////////////////////////////////////////////////////////
\NeedsTeXFormat{LaTeX2e}[1994/06/01]%
\ProvidesPackage{UD_TrimSpaces_2019_09_03}%
  [2019/09/03 v 1.0 Trim spaces that surround token sequences. (Ulrich Diez)]%
\RequirePackage{UD_Paraphernalia_2019_09_03}[2019/09/03]%
%%//////////////////////////////////////////////////////////////////////////////
%% EXPANDABLE REMOVAL OF LEADING AND TRAILING SPACES
%%
%%   The obscure case of removing several leading/trailing spaces was taken 
%%   into consideration.
%%
%%   Removal of spaces was implemented in a way where no brace-stripping from
%%   the arguments takes place. 
%%   Explicit-catcode-1/2-character-token-pairs remain untouched.
%%
%%   Spaces interspersing the argument or hidden within braces will be left in
%%   place.
%%
%%   The arguments themselves do not get expanded.
%%
%%   (For some obscure reason I don't remember any more I needed this in the
%%    past.)
%%
%%==============================================================================
%% Check whether brace-balanced argument starts with a space-token
%%..............................................................................
%% \UD@CheckWhetherLeadingSpace{<argument which is to be checked>}%
%%                             {<tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is a
%%                               space-token>}%
%%                             {<tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is not
%%                               a space-token>}%
%%
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having \UD@CheckWhetherLeadingSpace "hit" via
%%   two \expandafter(-chains).  )
%%==============================================================================
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\UD@Exchange{ }\expandafter\UD@SecondOfTwo}%
  {\expandafter\UD@SecondOfTwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\@ifdefinable\UD@CheckWhetherLeadingSpaceB{%
  \long\def\UD@CheckWhetherLeadingSpaceB#1 {%
    \expandafter\UD@CheckWhetherNull\expandafter{\UD@SecondOfTwo#1{}}%
    {\UD@Exchange{\UD@FirstOfTwo}}{\UD@Exchange{\UD@SecondOfTwo}}%
    {\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
     \expandafter\expandafter\expandafter}\expandafter\expandafter
     \expandafter}\expandafter\UD@SecondOfTwo\expandafter{\string}%
  }%
}%
%%==============================================================================
%% \UD@TrimAllLeadSpace{<argument>} 
%%..............................................................................
%%   Expandably removes all leading spaces from  <argument> in case at least
%%   one leading space is present. 
%%   Then
%%     <argument without leading spaces>
%%   is delivered.
%%
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having \UD@TrimAllLeadSpace "hit" via
%%   two \expandafter(-chains).  )
%%==============================================================================
\newcommand\UD@TrimAllLeadSpace[1]{%
  \romannumeral0\UD@TrimAllLeadSpaceLoop{#1}%
}%
\newcommand\UD@TrimAllLeadSpaceLoop[1]{%
  \UD@CheckWhetherLeadingSpace{#1}%
                            {%
                              \expandafter\UD@TrimAllLeadSpaceLoop
                              \expandafter{\UD@RemoveSpace#1}%
                            }%
                            { #1}%
}%
\@ifdefinable\UD@RemoveSpace{\UD@FirstOfTwo{\def\UD@RemoveSpace}{} {}}%
%%==============================================================================
%% \UD@TrimAllTrailSpace{<argument>} 
%%..............................................................................
%%   Expandably removes all trailing spaces from  <argument> in case at least
%%   one trailing space is present. 
%%   Then
%%     <argument without trailing spaces>
%%   is delivered.
%%
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having \UD@TrimAllTrailSpace "hit" via
%%   two \expandafter(-chains).  )
%%==============================================================================
\newcommand\UD@TrimAllTrailSpace[1]{%
   \romannumeral0\UD@TrimTrailSpaceLoop{#1}%
}%
%%------------------------------------------------------------------------------
%% \UD@TrimTrailSpaceLoop{<list of space-delimited arguments>}
%%..............................................................................
%%   Both extracts the first space-delimited argument from the <list of space-
%%   delimited arguments> as {<current argument with one trailing space 
%%   removed>} and removes it from the <list of space-delimited arguments> for
%%   obtaining the <remaining list of space delimited arguments> and passes 
%%   these two things and an empty list of <arguments preceding the current
%%   argument gathered so far>  at the end of the iteration to 
%%   \UD@CheckWhetherLastSpaceDelimitedItem.
%%
%%   \UD@CheckWhetherLastSpaceDelimitedItem in turn does choose the next
%%   action.
%%------------------------------------------------------------------------------
\newcommand\UD@TrimTrailSpaceLoop[1]{%
  %#1 argument
  \UD@ObtainFirstSpaceDelimitedTokenSetLoop{.#1 \UD@SeLDom}{%
    \expandafter\UD@CheckWhetherLastSpaceDelimitedItem
    \expandafter{\UD@RemoveTokensTillNextSpace.#1 }%
  }{}%
}%
%%------------------------------------------------------------------------------
%% Macros for \UD@ObtainFirstSpaceDelimitedTokenSetLoop.
%%------------------------------------------------------------------------------
\@ifdefinable\UD@RemoveTokensTillNextSpace{%
  \long\def\UD@RemoveTokensTillNextSpace#1 {}%
}%
\@ifdefinable\UD@BraceStripRemoveNextSpace{%
  \long\def\UD@BraceStripRemoveNextSpace#1 {#1}%
}%
\@ifdefinable\UD@GetFirstSpaceDelimitedTokenSet{%
  \long\def\UD@GetFirstSpaceDelimitedTokenSet#1 #2\UD@SeLDom{#1 }%
}%
\@ifdefinable\UD@GobbleDot{%
  \def\UD@GobbleDot.{}%
}%
%%------------------------------------------------------------------------------
%% \UD@ObtainFirstSpaceDelimitedTokenSetLoop%
%%     {<list of space delimited arguments>}%
%%     {<action>}%
%%
%% -> <action>{<first element of list of space delimited arguments>}%
%%...............................................................................
%% \UD@ObtainFirstSpaceDelimitedTokenSetLoop does--without unwanted brace-re-
%% moval--append the first space delimited argument from a
%% <list of space delimited arguments> as brace-delimited argument behind
%% a set of tokens given as <action>.
%%------------------------------------------------------------------------------
\newcommand\UD@ObtainFirstSpaceDelimitedTokenSetLoop[1]{%
  \expandafter\UD@CheckWhetherNull
  \expandafter{\UD@RemoveTokensTillNextSpace#1}{%
    \expandafter\expandafter\expandafter\UD@Exchange
    \expandafter\expandafter\expandafter{%
    \expandafter\expandafter\expandafter{%
    \expandafter\UD@GobbleDot\UD@BraceStripRemoveNextSpace#1}}%
  }{%
    \expandafter\UD@ObtainFirstSpaceDelimitedTokenSetLoop
    \expandafter{\UD@GetFirstSpaceDelimitedTokenSet#1}%
  }%
}%
%%------------------------------------------------------------------------------
%% \UD@CheckWhetherLastSpaceDelimitedItem
%%    {<remaining list of space delimited arguments>}%
%%    {<current argument with one trailing space removed>}%
%%    {<arguments preceding the current argument gathered
%%      so far>}%
%%..............................................................................
%% Case 1: <remaining list of space delimited arguments> is
%%         empty.
%%         We are done: Thus:
%%         <space> for terminating \romannumeral-expansion, and
%%         <arguments preceding the current argument gathered so
%%         far><current argument with one trailing space removed>
%% Case 2: <remaining list of space delimited arguments> consists of a single 
%%         space.
%%         A trailing space was removed. There may be more. Thus:
%%         \UD@TrimTrailSpaceLoop{%
%%           <arguments preceding the current argument gathered so
%%           far><current argument with one trailing space removed>%
%%         }%
%% Neither case 1 nor case 2: 
%%         The <current argument with one trailing space  removed> is not the
%%         last argument of the list, thus:
%%         For the next iteration 
%%         - attach it and a trailing space to the <arguments preceding the
%%           current argument gathered so far>,
%%         - get the first space delimited argument of the <remaining list of 
%%           space delimited arguments> as  <current argument with one trailing
%%           space removed>
%%         - remove that first space delimited argument from the <remaining list 
%%           of space delimited arguments>
%%------------------------------------------------------------------------------
\newcommand\UD@CheckWhetherLastSpaceDelimitedItem[3]{%
  \UD@CheckWhetherNull{#1}{ #3#2}{%
    \UD@CheckWhetherLeadingSpace{#1}{%
      \expandafter\UD@CheckWhetherNull
      \expandafter{\UD@RemoveSpace#1}{\UD@FirstOfTwo}{\UD@SecondOfTwo}%
    }{\UD@SecondOfTwo}%
    {\UD@TrimTrailSpaceLoop{#3#2}}%
    {%
      \UD@ObtainFirstSpaceDelimitedTokenSetLoop{.#1\UD@SeLDom}{%
        \expandafter\UD@CheckWhetherLastSpaceDelimitedItem
        \expandafter{\UD@RemoveTokensTillNextSpace.#1}%
      }{#3#2 }%
    }%
  }%
}%
%%==============================================================================
%% \UD@TrimAllSurroundSpace{<argument>} 
%%..............................................................................
%%   expandably removes all leading and trailing spaces from  <argument> in
%%   case at least one leading space is present. 
%%   Then
%%     <argument without leading and trailing spaces>
%%   is delivered.
%%
%% ( Due to \romannumeral0-expansion, the result will be delivered after
%%   two expansion-steps/after having \UD@TrimAllSurroundSpace "hit" via
%%   two \expandafter(-chains).  )
%%==============================================================================
\newcommand\UD@TrimAllSurroundSpace[1]{%
  \romannumeral0\expandafter\UD@TrimTrailSpaceLoop
                \expandafter{\romannumeral0\UD@TrimAllLeadSpaceLoop{#1}}%
}%
\endinput
%%//////////////////////////////////////////////////////////////////////////////
Ulrich Diez
  • 28,770