我正在尝试编写一个宏,它可以识别字符串中的某些字符序列,然后将它们映射到不同的集合上。在我考虑的特定情况下,由于有多种表示相同标记的方式,我希望规范化识别的序列。为了简化问题,以下是我能想到的演示问题的最小代码
\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}
在这个特定实例中,“规范化”是微不足道的——只是大写而已——但它说明了普遍的问题。本质上,它\regex_replace_all
并不像我预期的那样工作,也就是说我认为第二次调用会看到第一次调用的结果。然而,事实并非如此!有没有什么方法可以按顺序\regex_replace_all
实现所需的结果?
我在尝试编译该文档时遇到的错误如下:
! Undefined control sequence.
<argument> \LaTeX3 error:
Erroneous variable \tl_Map@a used!
l.33 \nstring{ABC abc XYZ}
答案1
该代码将适用于您,因为:
- 它使用 l3 语法(未混合)
- 在应用一些奇怪的东西之前,它会进行适当的扩展
- 它定义了一个可扩展的辅助命令
注意:如果设置(覆盖)变量,则无需清除。除此之外,您可能还需要考虑使用与这些常量值不同的类型来存储地图(例如属性列表)。
代码:
\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}
替代方案(保存正则表达式,感谢 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}
答案2
没有正则表达式。您可以映射字符串并使用\str_case:nnF
(使用执行 f 扩展的变体)进行替换。唯一的问题是处理映射标记列表时忽略的空格。
\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}