Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I'd like a command that works like this:

\dothings{Takes several words as argument}

and have this expand to:

\@dothings{Takes} \@dothings{several} \@dothings{words}...

I know how to define a simple macro that takes everything up to the next space, but I don't know how to use that to apply it to each word in a sentence. Is there a simple way to do this?

I'm also curious about taking the next letter as an argument, but I guess that's maybe a separate question...

share|improve this question
    
I just found this related question: tex.stackexchange.com/questions/11595/… –  Seamus Aug 29 '11 at 10:40
add comment

4 Answers

up vote 25 down vote accepted
\documentclass{article}
\usepackage{xcolor}
\makeatletter
\def\dothings#1{\color{blue}\expandafter\dothings@i#1 \@nil}
\def\dothings@i#1 #2\@nil{%
  \color{.!80}#1
  \ifx\relax#2\relax\else\dothings@i#2\@nil\fi}
\makeatother

\begin{document}
\dothings{Takes several words as argument}

\end{document}

enter image description here

share|improve this answer
    
I think this needs an extra set of {} to keep the color change local to \donothing. So the first \def should be \def\dothings#1{{\color{blue}\expandafter\dothings@i#1 \@nil}} –  Peter Grill Aug 13 '11 at 17:54
    
The \expandafter is useless? –  Leo Liu Aug 13 '11 at 18:19
    
@Leo Liu: I guess that Herbert wants to also cover the case where the sentence is given within a macro. –  Bruno Le Floch Aug 14 '11 at 11:23
add comment

LaTeX3 now provides

\seq_set_split:Nnn<seq var>{<delimiter>} {<argument>}

which splits the <argument> at each occurrence of <delimiter> (and strips spaces from both ends of each argument), and puts the result in the <seq var> (the list data structure in LaTeX3). Once you have that sequence, you can map a given function on all its items.

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\seq_new:N \l_foo_seq
\NewDocumentCommand{\dothings}{m}
  {
    \seq_set_split:Nnn \l_foo_seq {~} {#1}
    \seq_map_inline:Nn \l_foo_seq { \fbox{##1} }
  }
\ExplSyntaxOff
\begin{document}
\dothings{Takes several words as argument}
\end{document}

EDIT: Actually you can do it faster with xparse's argument processors (here \SplitList).

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand {\dothings}
  { > { \SplitList { ~ } } m }
  { \tl_map_inline:nn {#1} { \fbox{##1} } }
\ExplSyntaxOff
\begin{document}
\dothings{Takes several words as argument}
\end{document}
share|improve this answer
    
That's neat! I like that! –  Seamus Aug 15 '11 at 9:48
    
When this goes onto CTAN, could you update this answer to reflect that? –  Seamus Oct 1 '11 at 9:58
    
@Seamus Thanks for the reminder, it is now on CTAN. –  Bruno Le Floch Oct 1 '11 at 18:03
add comment

Herbert has shown how to seperate words by spaces. Here is a very similar solution which prevent deep \ifx ... \fi nesting (Deep nesting may produce errors when many words is used):

\documentclass{minimal}
\begin{document}

\def\wordsloop#1{\wordsloopiter#1 \nil}
\def\wordsloopiter#1 #2\nil{%
  \do{#1} %
  \ifx&#2&% #2 is empty, then & equeals &
    \let\next\relax
  \else
    \def\next{\wordsloopiter#2\nil}% iterate
  \fi
  \next}

\def\do#1{(#1)}
\wordsloop{This is a sentence.}
% we have: (This) (is) (a) (sentence.)

\end{document}

However, it is still very slow if you use many words in the argument.


Loop on tokens can be obtained by similar approch, if you remove the spaces after #1 in the definitions above. But it can be easier:

\documentclass{minimal}
\begin{document}

\def\tokensloop#1{\tokensloopiter#1\nil}
\def\tokensloopiter#1{%
  \ifx\nil#1\else\do{#1}\expandafter\tokensloopiter\fi}

\def\do#1{(#1)}
\tokensloop{aa  bb}
% we have (a)(a)(b)(b)

\end{document}

You can also use \toksloop command from etextools package directly.

Note that spaces are ignored.


For spaces, catcode or other tricks may be used to read the characters verbatimly. That is more complex.

For example, we can use \obeyspaces to change the catcode of spaces:

\documentclass{minimal}
\begin{document}

\def\charsloop{\begingroup\obeyspaces\charsloophelper}
\def\charsloophelper#1{\charsloopiter#1\nil\endgroup}
\def\charsloopiter#1{%
  \ifx\nil#1\else\do{#1}\expandafter\charsloopiter\fi}

\def\do#1{(#1)}
\charsloop{aa  bb}
% we have (a)(a)( )( )(b)(b)

\end{document}

(Newlines are still ignored with \obeyspaces only.)

share|improve this answer
    
Is it simple to modify this to separate based on &. I am still searching for a more general solution to macro to allow for alignment points within its argument –  Peter Grill Aug 13 '11 at 18:29
add comment

ConTeXt provides a macro \processisolatedwords that does exactly that. For example, to draw a frame around each word, you can use

\processisolatedwords {\input ward \relax} \inframed

In MkIV, this macro is implemented in lua using the following funcction:

local function applytowords(list,what,nested)
    local doaction = context[what or "ruledhbox"]
    local noaction = context
    local current  = checkedlist(list)
    local start
    while current do
        local id = current.id
        if id == glue_code then
            if start then
                doaction(copynodelist(start,current))
                start = nil
            end
            noaction(copynode(current))
        elseif nested and (id == hlist_code or id == vlist_code) then
            context.hbox()
            context.bgroup()
            applytowords(current.list,what,nested)
            context.egroup()
        elseif not start then
            start = current
        end
        current = current.next
    end
    if start then
        doaction(copynodelist(start))
    end
end

See also:

  • \applytowords
  • \processwords and \applytosplitstringword
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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