解析日期字符串时超出 TeX 容量

解析日期字符串时超出 TeX 容量

我正在尝试解析 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#1Feb

\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)。在 的情况下#2May会查看属性列表以查找与属性 相对应的值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}

相关内容