是否可以创建一个命令来删除句子中的最后一个标点符号(这里为了简化,只需删除最后一个句号)?
感谢这个软件包,我可以删除一个简单句子的最后一个标点符号,但如果该句子包含在等中,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}