xstring - 如何定义一个删除最后一个标点符号的宏?

xstring - 如何定义一个删除最后一个标点符号的宏?

是否可以创建一个命令来删除句子中的最后一个标点符号(这里为了简化,只需删除最后一个句号)?

感谢这个软件包,我可以删除一个简单句子的最后一个标点符号,但如果该句子包含在等中,xstring它就不起作用。\textit\textbf

是否可以告诉xstring忽略这些命令而只看它们的参数?

阅读文档后,我没有看到执行此操作的命令。

在此处输入图片描述

\documentclass{article}
\usepackage{xstring}

\newcommand{\removePunct}[1]{{\exploregroups%
    \StrRemoveBraces{#1}[\resultA]
    \IfEndWith{\resultA}{.}{%
        \StrGobbleRight{#1}{1}[\resultB]%
        \resultB%
    }{#1}
}}

\begin{document}
\removePunct{This is a test.}

\removePunct{{This is a test.}}

\removePunct{\textit{This is a test.}}
\end{document}

答案1

为了多样化,我们还考虑基于 LuaLaTeX 的解决方案。它定义了一个名为 的 LaTeX 实用程序宏\RemovePunct,该宏又调用一个名为 的 Lua 函数removepunct来完成大部分工作。removepunct采用 Lua 的多功能string.gsub函数,利用 Lua 强大的模式匹配和捕获功能。

请注意,此解决方案仅当标点符号出现在参数的末尾时才会删除它们\RemovePunct(除了任何结束花括号和空格)。它将删除此类标点符号的单个和多个实例。参数中\RemovePunct最后一个标点符号之后的任何空格也将被删除。

在此处输入图片描述

% !TEX TS-program = lualatex 
\documentclass{article}
\usepackage{luacode} % for 'luacode*' environment and '\luastringN' macro
\begin{luacode*}
   function removepunct ( s )
      tex.sprint ( ( s:gsub ( "%p*([}%s]-)$" , function ( v )
                                 return ( v:gsub ( "%s" , "" ) )
                              end ) ) )
   end
\end{luacode*}

%% LaTeX-side code:
\newcommand{\RemovePunct}[1]{\directlua{removepunct(\luastringN{#1})}}

\begin{document}
\RemovePunct{This is a test. This is also a test. }

\RemovePunct{{This is a test. Is this also a test? }  }

\RemovePunct{\textit{This is a test. This is indeed a test! } }

\RemovePunct{\textit{\textbf{This is a test. This is also a test!!! } }}

\end{document}


附录回答OP的后续问题,关于上面显示的答案中的Lua代码块的含义。

  • 中的“sprint”tex.sprint是“string print”的缩写,不是“短时间内跑得非常快”。

  • 中的“gsub”string.gsub是“general subsitution”的缩写。代码块实际上包含 2string.gsub条指令。

  • “内部” -- v:gsub ( "%s" , "" )-- 用于%s从搜索字符串中清除空格字符(用“魔法”字符表示)。

  • 最外层以 开头s:gsub ( "%p*([}%s]-)$"。这意味着它对s(宏传递给 Lua 函数的字符串\RemovePunct)进行操作,并使用搜索字符串

    %p*([}%s]-)$
    

    此搜索字符串有三个主要组成部分:%p*([}%s]-)$。让我们从后往前看:在 Lua 搜索模式中,$表示“字符串结尾”,即,它充当“锚点”:搜索时仅考虑字符串的最后部分。[}%s]设置一个 Lua“字符类”,由两个字符组成:(}“结束花括号”)和%s-- 表示任何空格字符的“魔法字符”。[}%s]-表示“字符类中 0 个或多个字符实例[}%s]。 周围的圆括号[}%s]-表示 Lua捕获,即模式匹配将被视为可在字符串替换阶段整体处理的捕获。最后,%p是 Lua 的“魔法字符”,表示“任何标点符号”。Lua 标点符号不仅仅是句子结尾的变体字符之一,.?!还有,,,;甚至还有+-*/,以及其他一些。%p*表示“0 个或更多标点符号类型字符(贪婪)”。

    将搜索字符串中的所有内容放在一起%p*([}%s]-)$,我们看到它的意思是“0 个或更多标点符号类型字符(贪婪),后跟 0 个或更多%s(空格)或}(非贪婪)实例,后跟行尾符号$。如果发生模式匹配,则该[}%s]-部分被“捕获”。请注意,搜索模式并不关心字符串中前面发生了什么s;这是设计使然。

  • 这把我们带到了字符串替代品“外部”指令的操作string.gsub。通常,这将是一个静态字符串。然而,对于手头的解决方案,它是“动态的”,因为它是由 Lua 给出的功能

    function ( v ) 
       return ( v:gsub ( "%s" , "" ) ) 
    end
    

    回想一下,搜索部分查找与模式匹配的字符串%p*([}%s]-)$,如果匹配,则将[}%s]-捕获为实体本身。由于该函数只接受一个参数,因此v该参数必须是捕获的内容。(如果搜索字符串中有 3 个捕获,则它们将被处理为,例如function (u,v,w)。)另外:由于%p*不是放置在捕获中,此时它会被丢弃;这正是我们一直想要的,不是吗?如前所述,该函数v通过丢弃空格来运行。

让我们通过检查 LaTeX 遇到以下情况来了解这一切是如何结合在一起的:

\RemovePunct{\textit{This is a test. This is indeed a test! } }

该命令的参数是

|\textit{This is a test. This is indeed a test! } |

其中|表示参数的开始和结束。请注意,参数的最后几个字符是test! },并且 之前和之后都有空格}\luastringN宏将此参数放置在无扩展转换为字符串,然后传递给我们最喜欢的 Lua 函数进行进一步处理;我们的 Lua 函数将此输入参数称为s。发生模式匹配,因此s转换为

|\textit{This is a test. This is indeed a test}|

注意最后一个标点符号! 尾随的空白字符消失了。Lua 函数将修改后的字符串传递给tex.sprint函数进行最终处理。(如果你眼尖,你可能会注意到的参数tex.sprint被包裹在两对,而不是一对圆括号。这是故意的。原因是string.gsub实际上返回 2 个输出:(a) 替换字符串和 (b) 执行替换的次数。将 的输出括gsub在一对额外的括号中是一种快速简便的丢弃第二个参数的方法。)

我希望这有帮助。



第二附录,以解决 OP 关于如何抑制\dots\ldots以及\textellipsis它们是否发生在结尾的论点\RemovePunct(除了可能的关闭花括号和空格)。

这个附加目标可能可以用许多不同的方式来处理。一种简单(并且希望易于理解)的方法是添加说明

      s = s:gsub ( "\\textellipsis([}%s]-)$" , "%1" )
      s = s:gsub ( "\\l?dots([}%s]-)$" , "%1" 

在 Lua 函数启动后立即removepunct执行。这种方法不会带来很大的开销 —— 除非您开始清除 参数末尾的大量 TeX 宏\RemovePunct

修改/增强的removepunct功能将如下所示:

   function removepunct ( s )
      s = s:gsub ( "\\textellipsis([}%s]-)$" , "%1" )
      s = s:gsub ( "\\l?dots([}%s]-)$" , "%1" )
      s = s:gsub ( "%p*([}%s]-)$" , function ( v )
                       return ( v:gsub ( "%s" , "" ) )
                    end ) 
      tex.sprint ( s )
   end

答案2

简单易用expl3

\documentclass{article}

%\usepackage{xparse} % uncomment for LaTeX prior to 2020-10-01

\ExplSyntaxOn
\NewDocumentCommand{\removePunct}{m}
 {
  \tl_set:Nn \l_tmpa_tl { #1 }
  \regex_replace_all:nnN { (\,|\;|\:|\.|\!|\?) } { } \l_tmpa_tl
  \tl_use:N \l_tmpa_tl
 }
\ExplSyntaxOff

\begin{document}

\removePunct{This has, as you see, no punctuation; really. Really?!}

\removePunct{\textit{This has, \textbf{as you see}, \textsc{no} punctuation; really. Really?!}}

\end{document}

在此处输入图片描述

如果您只想删除结尾的句点:

\documentclass{article}

%\usepackage{xparse} % uncomment for LaTeX prior to 2020-10-01

\ExplSyntaxOn
\NewDocumentCommand{\removePunct}{m}
 {
  \tl_set:Nn \l_tmpa_tl { #1 }
  \regex_replace_all:nnN { (\,|\;|\:|\.|\!|\?) } { } \l_tmpa_tl
  \tl_use:N \l_tmpa_tl
 }
\NewDocumentCommand{\removeFinalPeriod}{m}
 {
  \tl_set:Nn \l_tmpa_tl { #1 }
  \regex_replace_once:nnN { \.([^[:alpha:]]*) \Z } { \1 } \l_tmpa_tl
  \tl_use:N \l_tmpa_tl
 }
  
\ExplSyntaxOff


\begin{document}

\removePunct{This has, as you see, no punctuation; really. Really?!}

\removePunct{\textit{This has, \textbf{as you see}, \textsc{no} punctuation; really. Really?!}}

\removeFinalPeriod{This has no period.}

\removeFinalPeriod{This has no period}

\removeFinalPeriod{\textit{This has no period.}}

\end{document}

在此处输入图片描述

答案3

使用令牌循环。

\documentclass{article}
\usepackage{tokcycle}
\newcommand\removePunct[1]{%
  \def\bufchar{}%
  \stripgroupingtrue
  \tokcycle
    {\purgebuffer\gdef\bufchar{##1}}
    {\purgebuffer\groupedcytoks{\processtoks{##1}%
      \expandafter\testbuffer\bufchar\empty}}
    {\purgebuffer\addcytoks{##1}}
    {\purgebuffer\addcytoks{##1}}
    {#1}%
  \expandafter\testbuffer\bufchar\empty
  \the\cytoks
}
\newcommand\purgebuffer{\addcytoks[1]{\bufchar}\gdef\bufchar{}}
\newcommand\testbuffer[1]{%
  \ifx.#1\else
  \ifx,#1\else
  \ifx!#1\else
  \ifx?#1\else
    \purgebuffer
  \fi\fi\fi\fi
}
\begin{document}
\removePunct{This is a test.}

\removePunct{{This is a test.}}

\removePunct{\textit{This is a test?}}

\removePunct{\rule{1ex}{1ex}, \textbf{\textit{This is a test!}}}

\removePunct{\textit{This is a test. \today}}
\end{document}

在此处输入图片描述

相关内容