我正在尝试解析 DD-MMM-YYYY 格式的日期(例如,到期日),例如 06-May-2012,并创建宏 \dueday、\duemonth 和 \dueyear。我使用xstring
's 命令提取子字符串。然后我使用xstring'
s\IfStrEqCase
生成数字\duemonth
:
\documentclass{article}
\usepackage{xstring}
\begin{document}
\newcommand{\duedate}{06-May-2012}
\newcommand{\dueday}{\StrBefore{\duedate}{-}}
\newcommand{\duemonthname}{\StrBetween[1,2]{\duedate}{-}{-}}
\newcommand{\dueyear}{\StrBehind[2]{\duedate}{-}}
Reconstruction: \dueday-\duemonthname-\dueyear.
%\renewcommand{\duemonthname}{May} % This appears to solve the problem.
\IfStrEqCase{\duemonthname}{%
{Jan}{\newcommand{\duemonth}{1}}%
{Feb}{\newcommand{\duemonth}{2}}%
{Mar}{\newcommand{\duemonth}{3}}%
{Apr}{\newcommand{\duemonth}{4}}%
{May}{\newcommand{\duemonth}{5}}%
{Jun}{\newcommand{\duemonth}{6}}%
{Jul}{\newcommand{\duemonth}{7}}%
{Aug}{\newcommand{\duemonth}{8}}%
{Sep}{\newcommand{\duemonth}{9}}%
{Oct}{\newcommand{\duemonth}{10}}%
{Nov}{\newcommand{\duemonth}{11}}%
{Dec}{\newcommand{\duemonth}{12}}%
}
\duemonth % This should output 5.
\end{document}
失败并显示错误消息TeX capacity exceeded
。 似乎\IfStrEqCase
不是问题,因为如果我\renewcommand{\duemonthname}{May}
在它之前发出,一切都很好。这表明错误是由于拆分命令造成的,但它们的输出是我期望的(至少它们的“可见”输出是这样的)。那么是什么导致了错误?
答案1
不,你不需要xstring
这么做。TeX 的语法就足够了。
\def
在 TeX 中,使用参数模板来提取任意格式的参数。语法如下(参见缺乏耐心的 TeX):
\def
\foo
参数文本{
替换文本}
因此你可以直接使用
\def\parsedate #1-#2-#3\stopmark{Use #1 and #2 and #3 as you wish}
获取日期中的不同参数。这比xstring
包更简单(也更有效)。
要从月份名称中获取数字,您可以定义一系列宏。例如,定义
\def\theFeb{2}
然后您可以使用\theFeb
来获取数字。此外,还允许您在时\csname ...\endcsname
使用 来\csname the#1\endcsname
获取。\theFeb
#1
Feb
\parsedate
上面需要一个\stopmark
(可以是任何东西)来表示参数的结束,因此定义一个新的宏来使用它很方便:
\def\parsedateHelper #1-#2-#3\stopmark{Use #1 and #2 and #3 as you wish}
\def\parsedate#1{\parsedateHelper #1\stopmark}
% use as \parsedate{06-May-2012}
当你使用宏作为 的参数时\parsedate
,你还可能会遇到一些扩展问题,因此最好将参数存储到另一个宏中,并借助 使用它\expandafter
。这有点棘手:
\def\parsedate#1{%
\edef\savedargument{#1}% #1 is expanded by \edef
\expandafter\parsedateHelper\savedargument\stopmark}
把所有东西放在一起:
\documentclass{article}
\def\parsedate#1{\edef\temp{#1}%
\expandafter\parsedateX\temp\relax}
\def\parsedateX #1-#2-#3\relax{%
\def\dueday{#1}%
\edef\duemonth{\csname the#2\endcsname}%
\def\dueyear{#3}}
\def\theJan{1}
\def\theFeb{2}
\def\theMar{3}
\def\theApr{4}
\def\theMay{5}
\def\theJun{6}
\def\theJul{7}
\def\theAug{8}
\def\theSep{9}
\def\theOct{10}
\def\theNov{11}
\def\theDec{12}
\begin{document}
\parsedate{06-May-2012}
\dueyear/\duemonth/\dueday
\def\duedate{01-Aug-2003}
\parsedate{\duedate}
\dueyear/\duemonth/\dueday
\end{document}
答案2
强制性的 LaTeX3 解决方案:
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\parsedate}{ >{ \SplitArgument { 2 } { - } } m }
{
\parsedate_main:nnn #1
}
\cs_new_protected:Npn \parsedate_main:nnn #1 #2 #3
{
\cs_gset:Npn \dueday { #1 }
\cs_gset:Npx \duemonth { \prop_get:Nn \g_parsedate_months_prop { #2 } }
\cs_gset:Npn \dueyear { #3 }
}
\prop_new:N \g_parsedate_months_prop
\prop_gput:Nnn \g_parsedate_months_prop { Jan } { 01 }
\prop_gput:Nnn \g_parsedate_months_prop { Feb } { 02 }
\prop_gput:Nnn \g_parsedate_months_prop { Mar } { 03 }
\prop_gput:Nnn \g_parsedate_months_prop { Apr } { 04 }
\prop_gput:Nnn \g_parsedate_months_prop { May } { 05 }
\prop_gput:Nnn \g_parsedate_months_prop { Jun } { 06 }
\prop_gput:Nnn \g_parsedate_months_prop { Jul } { 07 }
\prop_gput:Nnn \g_parsedate_months_prop { Aug } { 08 }
\prop_gput:Nnn \g_parsedate_months_prop { Sep } { 09 }
\prop_gput:Nnn \g_parsedate_months_prop { Oct } { 10 }
\prop_gput:Nnn \g_parsedate_months_prop { Nov } { 11 }
\prop_gput:Nnn \g_parsedate_months_prop { Dec } { 12 }
\ExplSyntaxOff
\begin{document}
\parsedate{06-May-2012}
\dueday/\duemonth/\dueyear
\end{document}
这将打印
2012 年 6 月 5 日
参数为06-May-2012
,但工具已将\SplitArgument
其呈现为。因此很容易提取日期和年份。对于月份,我们使用\parsedate_main:nnn
{06}{May}{2012}
房产清单:我们将每个月份与其编号关联起来,并用 检索这个编号\prop_get:Nn
。
一些解释
传递给用 定义的命令的参数可以在提交给执行实际工作的宏之前进行“处理”。在这种情况下,我们要求在用 处\NewDocumentCommand
将参数分成三个部分-
\SplitArgument{2}{-}
(这意味着-
需要两个)。因此,定义主体中用 表示的内容#1
应被视为{chunk1}{chunk2}{chunk3}
。这被传递给\parsedate_main:nnn
,实际上有三个参数。
\dueday
它的职责是为、\duemonth
和赋予含义\dueyear
。第一和第三很简单:只需(全局)将它们定义为无参数宏即可。
因为\duemonth
我们必须将月份缩写翻译成数字。最简单的方法是\prg_case_str:nnn
:
\cs_gset:Npx \duemonth
{
\prg_case_str:nnn { #2 }
{
{ Jan } { 01 }
{ Feb } { 02 }
...
{ Dec } { 12 }
}{}
}
但是使用属性列表的方法有一个优点:人们可以为各种语言定义一整套属性列表,并在运行时决定将哪一个重命名为\g_parsedate_month_prop
可以使用的属性列表\parsedate_main:nnn
,从而可以轻松地将相同的宏适应多语言环境。
因此,在“扩展定义”中,\duemonth
定义为\prop_get:Nn
:整个
\prop_get:Nn \g_parsedate_months_prop { #2 }
被扩展了,因为我们使用了\cs_gset:Npx
(最后一个x
表示“扩展”,它是经典的\xdef
)。在 的情况下#2
,May
会查看属性列表以查找与属性 相对应的值May
,即05
,因此这将成为 的替换文本\duemonth
。
答案3
您犯了一个常见的错误,即将\StrBefore
等宏放在宏定义中。这些操作不可扩展,不能用作其他字符串宏的输入。相反,请使用尾随的可选参数将提取的字符串存储到宏中。这样,您每次使用宏时都不必重新提取字符串,并且可以在其他字符串操作宏中使用它们。
\documentclass{article}
\usepackage{xstring}
\begin{document}
\newcommand{\duedate}{06-May-2012}
\StrBefore{\duedate}{-}[\dueday]
\StrBetween[1,2]{\duedate}{-}{-}[\duemonthname]
\StrBehind[2]{\duedate}{-}[\dueyear]
Reconstruction: \dueday-\duemonthname-\dueyear.
%\renewcommand{\duemonthname}{May} % This appears to solve the problem.
\IfStrEqCase{\duemonthname}{%
{Jan}{\newcommand{\duemonth}{1}}%
{Feb}{\newcommand{\duemonth}{2}}%
{Mar}{\newcommand{\duemonth}{3}}%
{Apr}{\newcommand{\duemonth}{4}}%
{May}{\newcommand{\duemonth}{5}}%
{Jun}{\newcommand{\duemonth}{6}}%
{Jul}{\newcommand{\duemonth}{7}}%
{Aug}{\newcommand{\duemonth}{8}}%
{Sep}{\newcommand{\duemonth}{9}}%
{Oct}{\newcommand{\duemonth}{10}}%
{Nov}{\newcommand{\duemonth}{11}}%
{Dec}{\newcommand{\duemonth}{12}}%
}
\duemonth % This should output 5.
\end{document}