12

How to define a command which replaces every instance of a certain character in its argument with another certain character?

For example, I'd like to define the command \myreplace{1-2-abc-345} which gives me the output 1.2.abc.345 by replacing every - with ..

  • 3
    If just normal text is involved in the argument (as opposed to macros), the stringstrings package might be of help. There are other such packages, too. I think regex is one, though I can't seem to locate it at the moment. – Steven B. Segletes Jun 19 '14 at 18:58
  • 1
    The xstring paskage has a \StringSubstitute command? But is it tobe used with chains of characters? – Bernard Jun 19 '14 at 19:01
14

Use expl3:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\myreplace}{m}
 {
  \tl_set:Nn \l__maxd_argument_tl { #1 }
  \tl_replace_all:Nnn \l__maxd_argument_tl { - } { . }
  \tl_use:N \l__maxd_argument_tl
 }
\tl_new:N \l__maxd_argument_tl
\ExplSyntaxOff

\begin{document}

\myreplace{1-2-abc-345}

\end{document}

enter image description here

What's the advantage? You can easily generalize your command using the same idea.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\myreplace}{ O{} m }
 {
  \maxd_replace:nn { #1 } { #2 }
 }

\tl_new:N \l__maxd_argument_tl
\cs_new_protected:Npn \maxd_replace:nn #1 #2
 {
  \group_begin:
  \tl_set:Nn \l__maxd_argument_tl { #2 }
  \keys_set:nn { maxd/replace } { #1 }
  \tl_replace_all:NVV \l__maxd_argument_tl \l_maxd_search_tl \l_maxd_replace_tl
  \tl_use:N \l__maxd_argument_tl
  \group_end:
 }
\cs_generate_variant:Nn \tl_replace_all:Nnn { NVV }

\keys_define:nn { maxd/replace }
 {
  search .tl_set:N = \l_maxd_search_tl,
  search .initial:n = { - },
  replace .tl_set:N = \l_maxd_replace_tl,
  replace .initial:n = { . }
 }

\ExplSyntaxOff

\begin{document}

\myreplace{1-2-abc-345}

\myreplace[replace=--]{1-2-abc-345}

\myreplace[search=2,replace=0]{1-2-abc-345}

\end{document}

enter image description here

11

One way would be to use \StrSubstitute from the xstring package:

enter image description here

Code:

\documentclass{article}
\usepackage{xstring}

\newcommand{\myreplace}[2]{%
    \StrSubstitute{#2}{-}{.}[#1]%
}%

\begin{document}
\myreplace{\NewValue}{1-2-abc-345}
input: 1-2-abc-345 

output: \NewValue

\end{document}
11

Another solution using only TeX primitives:

\def\addto#1#2{\expandafter\def\expandafter#1\expandafter{#1#2}}
\def\replacestrings#1#2{%
   \def\tmp##1#1##2\end{\ifx\end##2\end\addto\tmpb{##1}\else\addto\tmpb{##1#2}\tmp##2\end\fi}%
   \expandafter\def\expandafter\tmpb\expandafter{\expandafter}\expandafter\tmp\tmpb#1\end 
}
\def\myreplace#1{\def\tmpb{#1}\replacestrings{-}{.}\tmpb}

\myreplace{1-2-abc-345}

Edit: I'll try to explain how it works. IMHO, it is more valuable to know how TeX works inside than only usage the code or package without thinking.

First line defines \addto<macro>{<text>} which adds to the <macro> the <text>. For example after \def\a{xx} \addto\a{yy} we have \a defined as xxyy.

The \replacestrings{<string1>}{<string2>} replaces all occurrences of <string1> in the \tmpb macro to the <string2>. First, the \tmp macro is defined at line 3 as:

\def\tmp#1<string1>#2\end{...}

Second, the \tmp is executed with expanded \tmpb as a parameter followed by \end. The \tmpb is (1) expanded, (2) it is set to empty and (3) the \tmp is executed. This is the reason of 5x \expandafter in line 4.

Suppose, we have \def\tmpb{abXXcdXXef} and we run \replacestring{XX}{YY}. Then \tmp is declared as the following pseudocode:

\def\tmp#1XX#2\end{if #2 is empty: \addto\tmpb{#1}
                   else: \addto\tmpb{#1YY}
                         \tmp #2\end
                   fi}

Then \tmp aaXXcdXXefXX\end is executed, but the \tmpb is set to empty first. The \tmp expands with #1=aa, #2=cdXXefXX. Because #2 isn't empty, the

\addto\tmpb{aaYY} \tmp cdXXefXX\end

is executed. So, \tmpb includes aaYY. The second run of \tmp expands with #1=cd #2=efXX to

\addto\tmpb{cdYY} \tmp efXX\end

Now, \tmpb includes aaYYcdYY. The third run of \tmp expands with #1=ef and #2 empty to

\addto\tmpb{ef}

This gives the result in \tmpb aaYYcdYYef.

An attentive reader can give notice that there is a bug: if the replaced text ends with one X, for example \def\tmpb{aaXXbbX} and <string1> is XX then there is a problem. It is true, but the question in this thread demands to replace only one token to one token. The real \replacestrings (used in OPmac, for example) does one more step: adds the special token at the end of the replaced text, then does the replacement and then removes this special token.

7

Another solution using the stringstrings package as suggested by Steven B. Segletes:

\documentclass{article}
\usepackage{stringstrings}

\newcommand{\myreplace}[1]{\convertchar{#1}{-}{.}}

\begin{document}

\myreplace{1-2-abc-345} %output: 1.2.abc.345

\end{document}
3

I'd like to define the command \myreplace{1-2-abc-345} which gives me the output 1.2.abc.345 by replacing every - with .

Here's a LuaLaTeX-based solution, which uses Lua's powerful gsub string function.

enter image description here

% !TEX TS-program = lualatex
\documentclass{article}
\usepackage{luacode}
\begin{luacode}
function myreplace ( s ) 
   return ( unicode.utf8.gsub ( s , "-", "." ) )
end
\end{luacode}
\newcommand\myreplace[1]{\directlua{myreplace(\luastringN{#1})}}

\begin{document}
\myreplace{1-2-abc-345}
\end{document}

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.