Print range of many group rows in datatools

I use this code from this answer:

\documentclass{article}

\usepackage{filecontents}
\begin{filecontents*}{product.tex}
%Type =1,2...10
No,Type,Name,Description
1,1,A1,D1
2,1,A1,D2
3,1,A1,D3
30,1,A1,D30
31,2,A2,D31
131,2,A2,D131
132,3,A3,D132
133,3,A3,D133
134,3,A3,D134
249,4,A4,D249
1000,10,A10,D1000
\end{filecontents*}

\usepackage{datatool}

\newcommand{\printtype}[1]{%
\par
\section*{Type #1}
\DTLforeach*
[\DTLiseq{\Type}{#1}]% Condition
{products}% Database
{\No=No,\Type=Type,\Name=Name,\Description=Description}{%
}%
}

\begin{document}

%Here is some text.

\printtype{1}

Some breaking text here.

\printtype{3}

And then some final text.

\end{document}


Can I use \DTLforeach with a type=(1-3) and then create the pdf for the type range 1, 2, 3? Or any other solutions.

I don't want to have separate calls of \printtype{1}, \printtype{2} and printtype{3}.

Thanks

You can use the following expl3-based solution. It supports plain integers as well as ranges with the first and last elements possibly omitted:

\printtype{1}
\printtype{3}
\printtype{2-4}% all types between 2 and 4 (both inclusive)
\printtype{3-} % all types greater than or equal to 3
\printtype{-2} % all types lower than or equal to 2
\printtype{-}  % all types


Negative numbers are also supported. In order not to conflict with the - used for the range syntax, they must be input within braces (this is also allowed for non-negative numbers, though not required):

\printtype{{-1}}         % only type -1
\printtype{-{-1}}        % all types t for which  t ≤ -1
\printtype{{-1}-2}       % all types t for which -1 ≤ t ≤ 2
\printtype{{-1}-{2}}     % ditto
\printtype{{-+--1}-{2}}  % ditto (two minus signs cancel each other,
% because these are TeX 〈number〉s, actually)
\printtype{{-10}-{-1}}   % all types t for which -10 ≤ t ≤ -1


Full code:

\RequirePackage{filecontents}
\begin{filecontents*}{product.tex}
No,Type,Name,Description
0,-1,A-1,D-1
1,1,A1,D1
2,1,A1,D2
3,1,A1,D3
30,1,A1,D30
31,2,A2,D31
131,2,A2,D131
132,3,A3,D132
133,3,A3,D133
134,3,A3,D134
249,4,A4,D249
1000,10,A10,D1000
\end{filecontents*}

\documentclass[twocolumn]{article}
\usepackage{datatool}
\usepackage{xparse}

\ExplSyntaxOn

\msg_new:nnn { lforti } { invalid-test-expression-syntax }
{ Invalid~syntax~for~test~expression~'\exp_not:n~{#1}'. }

\regex_const:Nn \l__lforti_intexpr_regex
{ \A (?| (\d+) | \{ ([\+\-]* \d+) \} ) \Z }
\regex_const:Nn \l__lforti_rangeexpr_regex
{ \A (?| (\d*) | \{ ([\+\-]* \d*) \} ) \-
(?| (\d*) | \{ ([\+\-]* \d*) \} ) \Z }

% #1: value to test
% #2: test expression (integer or range)
\cs_new_protected:Npn \__lforti_check_match:nn #1#2
{
\regex_match:NnTF \l__lforti_rangeexpr_regex {#2}
{ \__lforti_check_range_match:nn {#1} {#2} }
{
\regex_match:NnTF \l__lforti_intexpr_regex {#2}
{ \__lforti_check_int_match:nn {#1} {#2} }
{ \msg_error:nnn { lforti } { invalid-test-expression-syntax } {#2} }
}
}

% #1: value to test
% #2: test expression (conforming to \l__lforti_intexpr_regex)
\cs_new_protected:Npn \__lforti_check_int_match:nn #1#2
{
% Extract the integer from the test expresion
\tl_set:Nn \l_tmpa_tl {#2}
\regex_replace_once:NnN \l__lforti_intexpr_regex { \1 } \l_tmpa_tl
% Compare it to #1
\int_compare:nNnTF {#1} = { \tl_use:N \l_tmpa_tl }
{ \use:c { @dtl@conditiontrue } }
{ \use:c { @dtl@conditionfalse } }
}

\tl_new:N \l__lforti_crm_first_tl % “start part“ of a range expression
\tl_new:N \l__lforti_crm_last_tl  % “end part“ of a range expression
\bool_new:N \l__lforti_crm_ismatch_bool % result of the test

% #1: value to test
% #2: test expression (conforming to \l__lforti_rangeexpr_regex)
\cs_new_protected:Npn \__lforti_check_range_match:nn #1#2
{
\tl_set:Nn \l__lforti_crm_first_tl {#2}
\regex_replace_once:NnN \l__lforti_rangeexpr_regex { \1 }
\l__lforti_crm_first_tl
\tl_set:Nn \l__lforti_crm_last_tl {#2}
\regex_replace_once:NnN \l__lforti_rangeexpr_regex { \2 }
\l__lforti_crm_last_tl

\bool_set_true:N \l__lforti_crm_ismatch_bool

\tl_if_empty:NF \l__lforti_crm_first_tl
{ % the "first" part of the range is non-empty, perform the test
\int_compare:nNnT {#1} < { \tl_use:N \l__lforti_crm_first_tl }
{ \bool_set_false:N \l__lforti_crm_ismatch_bool }
}

\tl_if_empty:NF \l__lforti_crm_last_tl
{ % the "last" part of the range is non-empty, perform the test
\int_compare:nNnT {#1} > { \tl_use:N \l__lforti_crm_last_tl }
{ \bool_set_false:N \l__lforti_crm_ismatch_bool }
}

\bool_if:NTF \l__lforti_crm_ismatch_bool
{ \use:c { @dtl@conditiontrue } }
{ \use:c { @dtl@conditionfalse } }
}

\makeatletter

% Test function modeled after \DTLiseq
%
% #1: \Type or so (should expand to the integer subject to the test)
% #2: test expression (integer or range expression)
\NewExpandableDocumentCommand \lfortiIsMatch { m m }
{
\TE@throw
% #1 and #2 will be fully expanded when \DTLforeach* checks the condition
\exp_not:N \__lforti_check_match:nn {#1} {#2}
\exp_not:N \if@dtl@condition
}

% From this point on, up to \lforti@print@rbrace included: this is not needed
% for ”basic use” of my code. This allows one to print the test expression
% correctly in a title or so, without having to input it twice.
\cs_generate_variant:Nn \tl_replace_all:Nnn { Nx }

% Store a version of #2 into #1 in a form that is suitable for printing.
%
% #1: token list variable; will be defined if necessary, and assigned locally
% #2: test expression, with the standard catcodes
\cs_new_protected:Npn \__lforti_prepare_testexpr_for_print:Nn #1#2
{
\tl_clear_new:N #1
\tl_set:Nx #1 { \tl_to_str:n {#2} } % \detokenize (needed for the braces)

\tl_replace_all:Nxn #1
{ \char_generate:nn { - } { 12 } }
{ \lforti@print@nolig@dash }
\tl_replace_all:Nxn #1
{ \char_generate:nn { \{ } { 12 } }
{ \lforti@print@lbrace }
\tl_replace_all:Nxn #1
{ \char_generate:nn { \} } { 12 } }
{ \lforti@print@rbrace }
}

\NewDocumentCommand \lforti@prepare@testexpr@for@print { m m }
{ \__lforti_prepare_testexpr_for_print:Nn #1 {#2} }

% Print a dash that can't participate in ligatures (definition inspired from
% LaTeX2e's \do@noligs)
\NewDocumentCommand \lforti@print@nolig@dash { }
{ \leavevmode \kern \z@ \char - }

\NewDocumentCommand \lforti@print@lbrace { }
{ \leavevmode \char_generate:nn { \{ } { 12 } }

\NewDocumentCommand \lforti@print@rbrace { }
{ \leavevmode \char_generate:nn { \} } { 12 } }

\ExplSyntaxOff

% #1: test expression
\newcommand*{\printtype}[1]{%
% Save the test expression #1 in a form that is suitable for printing in the
% text---even in section titles written to the TOC. This little complication
% is only useful to allow test expressions such as {-+--1}-{2} to be printed
% correctly in a title and in the TOC without having to input them twice.
\lforti@prepare@testexpr@for@print{\lforti@testexpr@for@print}{#1}%
\section*{With test expression \texttt{%
\lforti@testexpr@for@print}}

\noindent
\begin{tabular}{@{}ll@{}}
\DTLforeach*
[\lfortiIsMatch{\Type}{#1}]% Condition
{products}% Database
{\No=No,\Type=Type,\Name=Name,\Description=Description}{%
\Name & \Description\\
}%
\end{tabular}
\par
}

\makeatother

\begin{document}
\raggedbottom % just for presentation of the example, because page breakpoints
% are scarce here...

\printtype{{-1}}                % only type -1
\printtype{1}
\printtype{2}
\printtype{3}
\printtype{4}
\printtype{10}

\newpage
\printtype{1-3}                 % types between 1 and 3, both inclusive
\printtype{2-3}                 % types between 2 and 3, both inclusive
\printtype{3-}                  % all types t for which 3 ≤ t
\printtype{-2}                  % all types t for which t ≤ 2

\newpage
\printtype{-}                   % all types
\printtype{10000-}              % types greated than or equal to 10000
\printtype{-{-1}}               % all types t for which  t ≤ -1
\printtype{{-1}-2}              % all types t for which -1 ≤ t ≤ 2
\printtype{{-1}-{2}}            % ditto
\printtype{{-+--1}-{2}}         % ditto (two minus signs cancel each other,
% because these are TeX 〈number〉s, actually)
\printtype{{-10}-{-1}}          % all types t for which -10 ≤ t ≤ -1

\end{document}


On page 1:

On page 2:

If, for your application, you remove the special printing of the test expression done here inside \section* (it was added in order to provide nice output for the example without requiring any redundancy in the input), you may get rid of a few things: \__lforti_prepare_testexpr_for_print:Nn, \lforti@prepare@testexpr@for@print, \lforti@print@nolig@dash, \lforti@print@lbrace and \lforti@print@rbrace (the replacement text of \printtype can start with the \noindent, if you want it to be as short and as simple as possible).

• You may want to check the latest edit, as I've added support for negative integers. – frougon Jul 19 '19 at 19:58
• I've updated the code one more time. I think it's perfect now :-) (the problem could only happen if you used \tableofcontents and had replaced \section* with \section, in order to print the test expressions of the example to the table of contents—the core functionality hasn't been changed in any way, since it has no known bug). – frougon Jul 19 '19 at 22:38