5

I got this Tex command, as a solution to a different problem and it works. But I have trouble understanding the details of how it works since I'm not that proficient in Latex coding and I want to learn.

%\DTLfetchifnull{<db name>}%
%               {<column1 name>}%
%               {<column1 value>}%
%               {<column2 name>}%
%               {<tokens in case <column2-value> is not a null-value>}%
%               {<tokens in case <column2-value> is a null-value>}%
%               {<tokens in case <column1-value> does not exist>}%
%
% Both with <tokens in case <column2-value> is not a null-value> and with
% <tokens in case <column2-value> is a null-value> the macro
% \dtlcurrentvalue is defined to yield the value of the column whose name
% is <column2 name>.
%
\makeatletter
\@ifundefined{NewDocumentCommand}%
{\newcommand\DTLfetchifnull[7]}%
{\NewDocumentCommand\DTLfetchifnull{mmmmmmm}}%
{%
  \begingroup%
    \protected@edef\@dtl@dogetrowforvalue{%
        \noexpand%
  \endgroup
  \noexpand\dtlgetrowindex{\noexpand\dtl@rowidx}{#1}{\dtlcolumnindex{#1}{#2}}{#3}%
  }\@dtl@dogetrowforvalue%
  \ifx \dtl@rowidx\dtlnovalue%
    \expandafter\@secondoftwo%
  \else%
    \expandafter\@firstoftwo%
  \fi%
  {%
    \edtlgetrowforvalue{#1}{\dtlcolumnindex{#1}{#2}}{#3}%
    \dtlgetentryfromcurrentrow{\dtlcurrentvalue}{\dtlcolumnindex{#1}{#4}}%
    \DTLifnull{\dtlcurrentvalue}{#6}{#5}% 
  }{%
    #7%
  }%
}%
\makeatother
  • The \makeatletter ... \makeatother as I understand is necessary, because we change something fundamental about NewDocumentCommand and newcommand?
  • @ifundefined{NewDocumentCommand}% as I understand makes it possible to use NewDocumentCommand instead of newcommand without \usepackage{xparse}?

Why do we need this? Can we just use \usepackage{xparse} and skip the whole:

\makeatletter
\@ifundefined{NewDocumentCommand}%
{\newcommand\DTLfetchifnull[7]}%
...
\makeatother

Or is there an advantage in doing this way?

  • \begingroup ... \endgroup as I understand is something like '{ }' but with different behavior

  • but it enclosed "\protected@edef@dtl@dogetrowforvalue{" so the "}" is outside, how does that work?

    \begingroup% \protected@edef@dtl@dogetrowforvalue{% \noexpand% \endgroup \noexpand\dtlgetrowindex{\noexpand\dtl@rowidx}{#1}{\dtlcolumnindex{#1}{#2}}{#3}% }@dtl@dogetrowforvalue%

I find it very hard to read how these functions are encapsulated and to decide what is an argument of which function or just following after a function.

This says check if(\dtl@rowidx\dtlnovalue) do \expandafter@secondoftwo else \expandafter@firstoftwo.

  \ifx \dtl@rowidx\dtlnovalue
    \expandafter\@secondoftwo
  \else
    \expandafter\@firstoftwo
  \fi

Are @firstoftwo and @secondoftwo the two options in curly braces that are coming after this?

2
  • you surely can simply load xparse (or not if you know your latex is current enough) and drop the test. Aug 9, 2021 at 10:40
  • @UlrikeFischer One of my computers still runs Win95 and MiKTeX 2.4. I installed datatool and some other packages manually. xparse is not installed there and I am too lazy to do that. So I prefer the test. :-) Aug 9, 2021 at 14:17

1 Answer 1

6

\makeatletter and \makeatother are needed in order to allow using command names with @ in them. There is nothing really special about it.

The first idea is to use \NewDocumentCommand{\DTLfetchifnull}{mmmmmmm} if it is available, otherwise the standard \newcommand{\DTLfetchifnull}[7] is used (for compatibility with older documents).

With newer releases we essentially get

\NewDocumentCommand\DTLfetchifnull{mmmmmmm}
{%
  \begingroup%
    \protected@edef\@dtl@dogetrowforvalue{%
        \noexpand%
  \endgroup
  \noexpand\dtlgetrowindex{\noexpand\dtl@rowidx}{#1}{\dtlcolumnindex{#1}{#2}}{#3}%
  }\@dtl@dogetrowforvalue%
  \ifx \dtl@rowidx\dtlnovalue%
    \expandafter\@secondoftwo%
  \else%
    \expandafter\@firstoftwo%
  \fi%
  {%
    \edtlgetrowforvalue{#1}{\dtlcolumnindex{#1}{#2}}{#3}%
    \dtlgetentryfromcurrentrow{\dtlcurrentvalue}{\dtlcolumnindex{#1}{#4}}%
    \DTLifnull{\dtlcurrentvalue}{#6}{#5}% 
  }{%
    #7%
  }%
}%

that shows not really good programming style. For instance, there are several useless %, but they're not harmful, just annoying. Also the first \noexpand is useless, because `\endgroup is not expandable. I'd write it as

\NewDocumentCommand\DTLfetchifnull{mmmmmmm}
{%
  \begingroup
    \protected@edef\@dtl@dogetrowforvalue{%
  \endgroup
  \noexpand\dtlgetrowindex{\noexpand\dtl@rowidx}{#1}{\dtlcolumnindex{#1}{#2}}{#3}%
  }\@dtl@dogetrowforvalue
  \ifx \dtl@rowidx\dtlnovalue
    \expandafter\@secondoftwo
  \else
    \expandafter\@firstoftwo
  \fi
  {%
    \edtlgetrowforvalue{#1}{\dtlcolumnindex{#1}{#2}}{#3}%
    \dtlgetentryfromcurrentrow{\dtlcurrentvalue}{\dtlcolumnindex{#1}{#4}}%
    \DTLifnull{\dtlcurrentvalue}{#6}{#5}% 
  }{%
    #7%
  }%
}

If an older LaTeX release, the first line would be \newcommand{\DTLfetchifnull}[7].

The idea is to define \@dtl@dogetrowforvalue to yield

\endgroup
\dtlgetrowindex{\dtl@rowidx}
  {expansion of #1}
  {expansion of \dtlcolumnindex{#1}{#2}}
  {expansion of #3}

and then the scratch macro \@dtl@dogetrowforvalue will be executed, which will assign a meaning to \dtl@rowidx, which can be \dtlnovalue (depending on the arguments #1, #2 and #3. The initial \endgroup will balance the already digested \begingroup and the definition of \@dtl@dogetrowforvalue will be forgotten as soon as \endgroup is executed (but the expansion of \@dtl@dogetrowforvalue has already been placed on the input stream, so there is no problem with undefining it).

Next the token is compared with \dtlnovalue. The construction

\if<something>
  \expandafter\@secondoftwo
\else
  \expandafter\@firstoftwo
\fi
{tokens A}
{tokens B}

is very common in LaTeX programming. If the test returns true, tokens B will be used, otherwise tokens A.

So, if \dtl@rowidx turns out to be \dtlnovalue, the argument #7 will be used. Otherwise it will be some legal row index and

\edtlgetrowforvalue{#1}{\dtlcolumnindex{#1}{#2}}{#3}%
\dtlgetentryfromcurrentrow{\dtlcurrentvalue}{\dtlcolumnindex{#1}{#4}}%
\DTLifnull{\dtlcurrentvalue}{#6}{#5}% 

will be used. Finding out what this does needs studying the manual of datatool.

9
  • 1
    To suppress extra space ("glue") ending up in the output. e.g., newline character. Try an experiment to see.
    – Cicada
    Aug 9, 2021 at 12:09
  • 1
    @TheMindWithin The annoying % do not come from me/my code. The unneeded \noexpand before \endgroup, which is not expandable, indeed is bad programming style and does come from my code, but I spotted it and therefore edited my code and removed it before you posted this question. I should have notified you about that edit, e.g., via a comment. Please accept my apologies for not doing so. Aug 9, 2021 at 13:29
  • 1
    @egreg +1 I should have been the one to explain the code, because I am the perpetrator who committed writing it. :-) Thank you very much for doing it for me. :-) Aug 9, 2021 at 13:37
  • 1
    @Cicada TeX reads input linew-ise and tokenizes character-wise. After reading a line all space-characters at the right end of the line are removed and a character is appended whose character-code equals the value of the parameter \endlinechar. Usually \endlinechar is 13, denoting the carriage-return-character, which in turn usually has category code 5 (end of line) and therefore yields a) no token at all if the reading-apparatus is in state "skipping blanks" b) an explicit character-token of charcode 32 and catcode 10(space) if in state "middle of line" c) the token \par if in state Aug 9, 2021 at 13:49
  • 1
    @Cicada c) the token \par if in state "new line". \newlinechar is a parameter denoting the character-code of character-tokees which trigger continuing writing at the begin of a new line when TeX is writing tokens to external file, e.g., .log-file, or to open \write-register or terminal. Besides carriage-return-character, there is a linefeed-character both in ASCII and in Unicode. But "newline character" seems like misleading terminology to me. Aug 9, 2021 at 13:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.