2

I'm trying to write a macro which will recognize certain sequences of characters in a string, and then map those onto a different set. In the particular case I have in mind, since there are multiple ways of representing the same token, I want to normalize the recognized sequence. In attempting to distill down the problem, here is the minimal code I could come up with which demonstrates the problem

\documentclass{article}

\usepackage{xstring}
\usepackage{xparse}
\usepackage{expl3}
\usepackage{l3regex}

\makeatletter
\ExplSyntaxOn

\DeclareDocumentCommand\@Normalize { m } {
  \tl_clear_new:N \tl_@str
  \tl_set:Nx \tl_@str {#1}
  \tl_use:N \tl_upper_case:n \tl_@str
}

\tl_const:Nn \tl_Map@A {1}
\tl_const:Nn \tl_Map@B {2}
\tl_const:Nn \tl_Map@C {3}

\NewDocumentCommand\nstring { m } {
  \tl_clear_new:N \tl_@norm 
  \tl_set:Nn \tl_@norm {#1}
  \regex_replace_all:nnN {[A-Ca-c]} {\c{@Normalize}\cB\{\0\cE\}} \tl_@norm
  \regex_replace_all:nnN {[A-Ca-c]} {\u{tl_Map@\0}} \tl_@norm
  \tl_use:N \tl_@norm
}

\ExplSyntaxOff
\makeatother

\begin{document}
\nstring{ABC abc XYZ}
\end{document}

In this particular instance, the "normalization" is trivial—it's merely capitalization—but it's illustrative of the general problem. Essentially, \regex_replace_all doesn't work like how I expected, which is that I figured the second invocation would see the result of the first invocation. However, this is not the case! Is there any way to sequence \regex_replace_all to achieve the desired result?

The error I get when trying to compile this document is as follows:

! Undefined control sequence.
<argument> \LaTeX3 error: 
                Erroneous variable \tl_Map@a used!
l.33 \nstring{ABC abc XYZ}
1
  • 2
    Please do never mix l3 syntax with l2e syntax. Refer to l3 naming conventions.
    – TeXnician
    Commented Jan 29, 2018 at 20:21

2 Answers 2

5

This code will work for you, because:

  • it uses l3 syntax (unmixed)
  • it does proper expansion, before applying some odd things
  • it defines an exandable auxiliary command

Note: You do not need to clear if you set (overwrite) a variable. Apart from that you might want to consider a different type for storing your map than these constant values (e.g. a property list).

Code:

\documentclass{article}

\usepackage{xstring}
\usepackage{xparse}
\usepackage{expl3}

\ExplSyntaxOn

\cs_set:Npn \kevin_normalize:n #1
    {
        \text_uppercase:n { #1 }
    }

\tl_const:Nn \c_kevin_mapA_tl { 1 }
\tl_const:Nn \c_kevin_mapB_tl { 2 }
\tl_const:Nn \c_kevin_mapC_tl { 3 }

\NewDocumentCommand\nstring { m } {
%  \tl_clear:N \l_tmpa_tl
    \tl_set:Nn \l_tmpa_tl { #1 }
  \regex_replace_all:nnN {[A-Ca-c]} { \c{kevin_normalize:n}\cB\{\0\cE\}} \l_tmpa_tl
  \tl_set:Nx \l_tmpa_tl { \tl_use:N \l_tmpa_tl }
  \regex_replace_all:nnN {[A-Ca-c]} { \u{c_kevin_map\0_tl} } \l_tmpa_tl
  \tl_use:N \l_tmpa_tl
}

\ExplSyntaxOff

\begin{document}
\nstring{ABC abc XYZ}
\end{document}

Alternative (to save a regex, thanks to egreg):

\documentclass{article}

\usepackage{xstring}
\usepackage{xparse}
\usepackage{expl3}

\ExplSyntaxOn

\cs_set:Npn \kevin_normalize:n #1
    {
        \tl_if_exist:cTF {c_kevin_map\str_uppercase:n{#1}_tl}
            { \tl_use:c {c_kevin_map\str_uppercase:n{#1}_tl }}
            { #1 }
    }

\tl_const:Nn \c_kevin_mapA_tl { 1 }
\tl_const:Nn \c_kevin_mapB_tl { 2 }
\tl_const:Nn \c_kevin_mapC_tl { 3 }

\NewDocumentCommand\nstring { m } {
  \tl_set:Nn \l_tmpa_tl { #1 }
  \regex_replace_all:nnN {[A-Ca-c]} { \c{kevin_normalize:n}\cB\{\0\cE\} } \l_tmpa_tl
  \tl_set:Nx \l_tmpa_tl { \tl_use:N \l_tmpa_tl }
  \tl_use:N \l_tmpa_tl
}

\ExplSyntaxOff

\begin{document}
\nstring{ABC abc XYZ xyz}
\end{document}
1
  • 1
    You can define \kevin_normalize:n to do \tl_use:c { c_kevin_\str_upper_case:n{#1}_tl} which saves a regex replacement.
    – egreg
    Commented Jan 29, 2018 at 21:02
4

No regex. You can map the string and do the substitution with \str_case:nnF (with a variant for doing f-expansion). The only problem is coping with spaces that are ignored when mapping a token list.

\documentclass{article}

\usepackage{xparse}
\usepackage{expl3}

\ExplSyntaxOn

\NewDocumentCommand{\nstring}{m}
 {
  \tl_set:Nn \l_tmpa_tl { #1 }
  \tl_replace_all:Nnn \l_tmpa_tl { ~ } { \c_space_tl }
  \tl_map_inline:Nn \l_tmpa_tl
   {
    \str_case:fnF { \str_uppercase:n { ##1 } }
     {
      {A}{1}
      {B}{2}
      {C}{3}
     }
     {##1}
   }
 }
\prg_generate_conditional_variant:Nnn \str_case:nn { f } { T, F, TF }
\ExplSyntaxOff

\begin{document}
\nstring{ABC abc XYZ}
\end{document}

enter image description here

2
  • That's a neat trick to replace ~ by \space. Commented Jan 29, 2018 at 23:54
  • @WillRobertson Probably \c_space_tl should be better.
    – egreg
    Commented Jan 29, 2018 at 23:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .