5

To avoid the XY problem, I'll describe what I am looking for (the X) and the issue that I found while attempting X (the Y). To be clear: I'm not trying to troubleshoot Y, I'd be grateful if only X is solved.

What I'm looking for

I want to define a macro that accepts a string as an argument and it displays a given number of characters (by calling an arbitrary macro) per page until the string is entirely traversed.

Here's an example: Let's consider the string abcdefghijk, 3 as the number of characters we want to display per page, and \foo the macro that we want to call for each character. The resulting document should look like.

\documentclass{article}

\newcommand\foo[1]{Hello #1}

\begin{document} \foo{a} \foo{b} \foo{c} \newpage \foo{d} \foo{e} \foo{f} \newpage \foo{g} \foo{h} \foo{i} \newpage \foo{j} \foo{k} \newpage \end{document}

I came up with the following pseudocode:

1. While the original string is not empty:
  1.1. Create a substring from the first character to the third character.
  1.2. Remove the first three characters from the original string.
  1.3. For every character in the substring:
    1.3.1. Call \foo{character}
  1.4. Call \newpage

My attempt

For step 1.2, the original string needs to be set to a substring of itself, so I tried setting \mystring to a substring to itself using the \substring from the stringstrings package, but I got the error Use of \\substring doesn't match its definition..

\documentclass{article}

\usepackage{stringstrings} \usepackage{xstring}

\begin{document} % An arbitrary string \def\mystring{abcdef} % We obtain the length so that we can create a substring from the 4th % character to the end of the string. \StrLen{\mystring}[\mystringlen] % When I try to display the string, I get the following error: % ! Use of \substring doesn't match its definition. % \kernel@ifnextchar ...rved@d =#1\def \reserved@a { % #2}\def \reserved@b {#3}\f... % l.14 \mystring % % ? \def\mystring{\substring{\mystring}{4}{\mystringlen}} \mystring \end{document}

I feel there's a simpler way to implement the pseudocode that I explained above.

rdrg109
  • 565
  • While what you say can be implemented, it would have a bad time complexity because of limitations in TeX's memory models. A better algorithm would be to iterate character by character, maintain a counter for the current index, and if the counter reaches 3, reset the counter to 0 and execute \clearpage. – user202729 Mar 25 '24 at 03:15

6 Answers6

4

I'd split the text into a sequence, grapheme by grapheme, then map the sequence using the index, so after the specified number of items a \newpage command is issued. Each item is wrapped according to the template specified as the third argument.

\documentclass{article}
\usepackage[paperwidth=3cm,paperheight=4cm]{geometry} % just for smaller pictures

\ExplSyntaxOn

\NewDocumentCommand{\partitionstring}{mm+m} {% #1 = number of characters in the substrings % #2 = string % #3 = what to do to each item \rdrg_partition:nnn { #1 } { #2 } { #3 } }

\seq_new:N \l_rdrg_partition_items_seq

\cs_new_protected:Nn \rdrg_partition:nnn { \seq_clear:N \l_rdrg_partition_items_seq \text_map_inline:nn { #2 } { \seq_put_right:Nn \l_rdrg_partition_items_seq { ##1 } } \cs_set:Nn __rdrg_partition_do:n { #3 } \seq_map_indexed_inline:Nn \l_rdrg_partition_items_seq { __rdrg_partition_do:n { ##2 } \bool_lazy_or:nnT { \int_compare_p:n { \int_mod:nn { ##1 } { #1 } == 0 } } % specified interval { \int_compare_p:n { ##1 == \seq_count:N \l_rdrg_partition_items_seq } } % end { \clearpage } } }

\ExplSyntaxOff

\begin{document}

\partitionstring{3}{abćdefghìjk}{\fbox{#1}\par}

\raggedright Text on a new page

\end{document}

enter image description here

egreg
  • 1,121,712
3

Assuming you want graphemes not bytes/characters (so e.g. retaining accents, handling 8-bit input properly):

\documentclass{article}
\ExplSyntaxOn
\NewDocumentCommand \blockstring { O{3} m m }
  { \rdrg_block_string:nnN {#1} {#2} #3 }
\int_new:N \l__rdrg_block_int
\tl_new:N \l__rdrg_block_tl
\cs_new_protected:Npn \rdrg_block_string:nnN #1#2#3
  {
    \group_begin:
      \int_zero:N \l__rdrg_block_int
      \tl_clear:N \l__rdrg_block_tl
      \text_map_inline:nn {#2}
        {
          \int_incr:N \l__rdrg_block_int
          \tl_put_right:Nn \l__rdrg_block_tl {##1}
          \int_compare:nNnT \l__rdrg_block_int = {#1}
            {
              \int_zero:N \l__rdrg_block_int
              \exp_args:NV #3 \l__rdrg_block_tl
              \tl_clear:N \l__rdrg_block_tl
            }
        }
      \tl_if_empty:NF \l__rdrg_block_tl
        { \exp_args:NV #3 \l__rdrg_block_tl }
    \group_end:
  }
\begin{document}

\blockstring{abcdefghijklmnopqrstuvwxyz}\fbox

\end{document}

Joseph Wright
  • 259,911
  • 34
  • 706
  • 1,036
  • Behaviour if the input doesn't divide neatly into blocks was not specified: I assumed you'd just want to tidy up, but one could error or pad if required. – Joseph Wright Mar 25 '24 at 09:00
2

Here is my solution attempt using TeX primitives:

\newcount\subnum 
\newcount\subnumA

\def\processdata#1{\subnum=#1 \subnumA=0 \expandafter\processdataA\data\end}

\def\processdataA#1{\ifx\end#1\newpage\else \foo{#1}\space \advance\subnumA by1 \ifnum\subnumA<\subnum \else \newpage \subnumA=0 \fi \expandafter\processdataA \fi }

\def\foo#1{Hello #1} \def\data{abcdefghijk}

\processdata{3} % executes \foo{a} \foo{b} \foo{c} \newpage \foo{d} ... etc.

wipet
  • 74,238
1

Here is my solution attempt using ExplSyntax:

\documentclass{article}
\usepackage{xcolor}

\ExplSyntaxOn

\seq_new:N \l__rdrg_string_seq \tl_new:N \l__rdrg_string_left_tl \str_new:N \l__rdrg_substring_str

\NewDocumentCommand \foo { m } { __rdrg_foo:n {#1} } \cs_new_protected:Npn __rdrg_foo:n #1 { \seq_set_split:Nne \l__rdrg_string_seq { } { \tl_to_str:e {#1} } \int_do_until:nNnn { \seq_count:N \l__rdrg_string_seq } < { 3 } { \str_clear:N \l__rdrg_substring_str \prg_replicate:nn { 3 } { \seq_pop_left:NN \l__rdrg_string_seq \l__rdrg_string_left_tl \str_put_right:Ne \l__rdrg_substring_str { \l__rdrg_string_left_tl } } \str_map_function:NN \l__rdrg_substring_str __rdrg_foo_fn:n \newpage } } \cs_new_protected:Npn __rdrg_foo_fn:n #1 { % example function { \color { red } This~ is~ the~ letter~ \textsf {#1} !!! } \par }

\ExplSyntaxOff

\begin{document}

\foo{abcdefghijklmnopqrstuvwxyz}

\end{document}

User23456234
  • 1,808
1

Here is a token-cycle approach. While this MWE assumes the input \data are all characters (no spaces, macros, or groups), the approach is easily modified to take those into account.

Here, I have defined \triggeraction as \par\hrulefill\par rather than \newpage, for easy viewing.

\documentclass{article}
\usepackage{tokcycle}
%
\newcommand\foo[1]{\fbox{#1}}
\newcommand\triggerindex{3}
\newcommand\triggeraction{\par\hrulefill\par}
%
\newcounter{myindex}
\Characterdirective{\addcytoks{%
  \foo{#1}\stepcounter{myindex}\ifnum\value{myindex}=\triggerindex\relax
    \triggeraction\setcounter{myindex}{0}\fi
}}
\newcommand\processfoo[1]{%
  \setcounter{myindex}{0}%
  \expandafter\tokencyclexpress#1\endtokencyclexpress
  \clearpage
}
\begin{document}
\def\data{abcdefghijk}
\processfoo{\data}
After text
\end{document}

enter image description here

0

Using xstring:

\documentclass{article}
\usepackage[paperheight=3cm]{geometry}
\pagestyle{empty}
\usepackage{xstring}
\newcommand{\foo}[1]{Hello #1\ }

\newcommand{\charbychar}[1]{% \StrSplit{#1}{1}{\firstchar}{\rest}% \foo{\firstchar}% \IfStrEq{\rest}{}{}{% \charbychar{\rest}% } } \newcommand{\splitcall}[2]{% \StrSplit{#1}{#2}{\firstn}{\restn}% \charbychar{\firstn}% \IfStrEq{\restn}{}{}{% \newpage% \splitcall{\restn}{#2}% } } \begin{document} \splitcall{abcdefgh}{3} \end{document}

enter image description here

The idea is to use \StrSplit to split the original string into the first n characters and the rest, then process the first n, and if the rest is not empty call the command again (recursively) on the rest.

Processing the n characters works in the same way: split into the first character and the rest, call a macro \foo on the first character, if the rest isn't empty call the command recursively on the rest.

\StrSplit automatically takes care of uneven lengths, if the first part is not long enough (for example you ask to split on 3 characters but there are only 2) then the first part will be the complete string and the second part will be empty.

Marijn
  • 37,699