10

How can I create a macro

\split{ABC}

that outputs

enter image description here

where the formatting of the characters are made with another macro, i.e.

\formatchar{A}

where

\def\formatchar#1{{\color{red}#1}}

for any string length?

I tried to modify the code in Split a character string n by n

\documentclass{article}
\usepackage{xstring}

\def\split#1#2{% \StrSplit{#2}{1}\tempa\tempb x% \tempa\let\tempa\empty \unless\ifx\tempb\empty\def\tempa{|\split{1}\tempb}\fi y% \tempa }

\begin{document} \split{1}{ABCD} \end{document}

And I get x and y before and after (since I failed limiting with a command and }) but I cannot get it to format properly. Also I would like to avoid the {1} in call, and use

\split{ABCD}

only. It is always 1-charater splits.

Any help is much appreciated!

mf67
  • 666

5 Answers5

8

If your input is not guaranteed to be “ASCII-only” an approach with \seq_set_split:Nnn may not work.

I changed the name to \splitchars because \split is taken up by amsmath.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{xcolor}

\NewDocumentCommand{\formatchar}{m}{% \textcolor{red!90!yellow}{#1}% <--- or whatever you like }

\ExplSyntaxOn

\NewDocumentCommand{\splitchars}{O{|}m} {% #1 = optional separator, default | % #2 = text to split \mflxvii_split:nn { #1 } { #2 } }

\seq_new:N \l__mflxvii_split_items_seq

\cs_new_protected:Nn \mflxvii_split:nn { \seq_clear:N \l__mflxvii_split_items_seq \text_map_inline:nn { #2 } { \seq_put_right:Nn \l__mflxvii_split_items_seq { \formatchar{##1} } } \seq_use:Nn \l__mflxvii_split_items_seq { #1 } }

\ExplSyntaxOff

\begin{document}

\splitchars{ABC}

\splitchars[--]{ABC}

\splitchars{ábç}

\splitchars{ÅÄÖ}

\end{document}

enter image description here

In order to get a list-like output, here's an extension that allows a different separator between the last two items.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{xcolor}

\NewDocumentCommand{\formatchar}{m}{% \textcolor{red!90!yellow}{#1}% <--- or whatever you like }

\ExplSyntaxOn

\NewDocumentCommand{\splitchars}{O{|}O{#1}m} {% #1 = optional separator, default | % #2 = optional separator between last two % #3 = text to split \mflxvii_split:nnn { #1 } { #2 } { #3 } }

\seq_new:N \l__mflxvii_split_items_seq

\cs_new_protected:Nn \mflxvii_split:nnn { \seq_clear:N \l__mflxvii_split_items_seq \text_map_inline:nn { #3 } { \seq_put_right:Nn \l__mflxvii_split_items_seq { \formatchar{##1} } } \seq_use:Nnnn \l__mflxvii_split_items_seq { #2 } { #1 } { #2 } }

\ExplSyntaxOff

\begin{document}

\splitchars{ABC}

\splitchars[--]{ABC}

\splitchars{ábç}

\splitchars{ÅÄÖ}

\splitchars[, ][ and ]{ÅÄÖ}

\splitchars[, ][ and ]{ÅÄ}

\end{document}

enter image description here

egreg
  • 1,121,712
  • Thank you. Is there some way to get in contact with you for a commission? I have not found any 'dm'/'pm' function on this site. – mf67 Dec 21 '23 at 11:27
  • It did not work, and gives error LaTeX cmd Error: Command '\split' already defined. I'm running pdfLaTeX, TeXLive 2022 – mf67 Dec 21 '23 at 21:45
  • In addition it cannot handle ÅÄÖ. Can this be corrected? – mf67 Dec 21 '23 at 21:52
  • 1
    @mf67 I changed the name, because \split is preempted by amsmath. But you see that it deals correctly with ÅÄÖ. Are you using UTF-8? I tried with TeX Live 2022 and it works the same. – egreg Dec 21 '23 at 22:04
  • I don't understand. I take your sample, paste into a new TexLive 2022 PdfLatex document at Overleaf and get Undefined control sequence.: \mflxvii_split:nn ...tems_seq \text_map_inline:nn {#2}{\seq_put_right:Nn \l_... l.33 \splitchars{ABC} (it does not help that his comment box is too small to display it well.) – mf67 Dec 21 '23 at 22:58
  • After some investigation, Overleaf must run in TexLive 2023 mode to accept the code. – mf67 Dec 22 '23 at 04:04
  • Would it be possible to expand the optional arguments so that a “last separator” is specified too? E.g. \splitchars[, ][and]{ABCDE}gives “A, B, C, D and E”? – mf67 Dec 30 '23 at 04:14
  • 1
    @mf67 Sure. Added in the last edit. – egreg Dec 30 '23 at 10:05
6

Here's a LuaLaTeX-based solution, which makes use of Lua's gsub and sub built-in functions. Actually, the code uses versions of the gsub and sub functions that can handle utf8-encoded characters directly.

The main user-level macro is called \splitstring rather than \split, because the amsmath package defines an environment called split along with macros called \split and \endsplit.

Observe that (a) the argument of \splitstring can contain whitespace and (b) formatchar is implemented as a Lua function rather than as a LaTeX macro.

enter image description here

% !TEX TS-program = lualatex
\documentclass{article}
\usepackage{xcolor}  % for '\textcolor' macro

% Lua-side code \directlua{

  function formatchar ( c )
     return ( '\\textcolor{red}{\\textbf{' .. c .. '}}|' )
  end
  function split ( s )
     s = unicode.utf8.gsub ( s , '.' , formatchar )
     return ( unicode.utf8.sub ( s , 1 , -2 ) ) % delete final '|'
  end
}
% LaTeX-side code
\newcommand\splitstring[1]{\directlua{tex.sprint(split('#1'))}}

\begin{document}
\obeylines % just for this example
\splitstring{ABC}
\splitstring{123abc}
\splitstring{Hello World}
\end{document}
Mico
  • 506,678
5

Here is my solution attempt using ExplSyntax:

\documentclass{article}
\usepackage{xcolor}

\ExplSyntaxOn \seq_new:N \l__my_split_seq \NewDocumentCommand { \split } { m } { __my_split:n { #1 } } \cs_new_protected:Npn __my_split:n #1 { \seq_set_split:Nnn \l__my_split_seq {} { #1 } \seq_set_map:NNn \l__my_split_seq \l__my_split_seq { __my_split_fn:n { ##1 } } % \seq_log:N \l__my_split_seq \seq_use:Nn \l__my_split_seq { | } } \cs_new_protected:Npn __my_split_fn:n #1 { { \color { red } #1 } } \ExplSyntaxOff

\begin{document}

\split{ABCD}

\split{1234}

\end{document}

enter image description here

User23456234
  • 1,808
  • Thank you, very kind of you. I don't know Expl, but it seems good. Why is one line commented? Is it important? – mf67 Dec 21 '23 at 09:38
4

Nobody actually tried to use the OPs code with xstring, which is arguably the easiest :) Here a recursive definition of \split, using \StrSplit and \IfStrEq. It works also for accented letters.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{xstring}
\usepackage{xcolor}

\def\formatchar#1{{\color{red}#1}}

\def\split#1{% \StrSplit{#1}{1}{\tempa}{\tempb}% split into first character \tempa and the rest \tempb \formatchar{\tempa}% % print first character in red \IfStrEq{\tempb}{}{}{% % if rest is empty (first {}) do nothing (second {}), which will stop the recursive loop. |% % else: print a bar, and \split{\tempb}% % call the function again for the rest, }% % which will split the first character of the rest etc. }

\begin{document} \split{ABCDÅÄÖ} \end{document}

enter image description here

Marijn
  • 37,699
  • Nice idea: +1 . // The stop condition is critical to avoid infinite loops. It's in your code, off course, if you know what to look for. Do you have any hints for future readers on this subject? Or can you augment your code with some comments? Thank you – MS-SPO Jan 18 '24 at 07:35
  • 1
    @MS-SPO I added comments, I hope it is more clear now. – Marijn Jan 18 '24 at 07:47
  • Thank you: great – MS-SPO Jan 18 '24 at 07:48
3

Since there are no LaTeX2e solutions offered, I give one here. I use a token cycle to grab characters one-by-one from the argument. This solution assumes the argument has no macros and no groups. I omitted those, because no guidance was given on how they should be handled, though they can be handled, as needed.

If unicode characters need to be handled, compilation must be performed under xelatex or lualatex.

\documentclass{article}
\usepackage{tokcycle,xcolor}
\newcommand\formatchar[1]{\textcolor{red}{#1}}
\def\split#1{%
  \tokencycle{\formatchar{##1}\splitchar}
  {}{}{##1\splitchar}#1\endtokencycle}
\newcommand\splitchar{\tcpeek\z\ifx\empty\z\else$\mid$\fi}
\begin{document}
\split{ABCD}

\split{Å ÄÖ} \end{document}

enter image description here