我制作了一个宏,用于xparse
格式化月份和可选的日期和年份。但附加的可选参数在描述列表的描述标签内不起作用---除非我将其括在花括号中(例如{\Month{4}[8]}
)。此错误可通过花括号轻松修复,但我想知道它发生的原因,也许还想知道如何避免使用花括号修复它。
\documentclass{article}
\usepackage{stix2}
\usepackage{etoolbox,xparse,xspace}
%\numtomonth converts a number into its corresponding month.
%The starred and unstarred versions display the long and short forms of the month.
\makeatletter
\NewDocumentCommand{\numtomonth}{ s m }{%
\IfBooleanTF{#1}%
{%ifstar
\ifnumequal{#2}{1}{January}{%
\ifnumequal{#2}{2}{February}{%
\ifnumequal{#2}{3}{March}{%
\ifnumequal{#2}{4}{April}{%
\ifnumequal{#2}{5}{May}{%
\ifnumequal{#2}{6}{June}{%
\ifnumequal{#2}{7}{July}{%
\ifnumequal{#2}{8}{August}{%
\ifnumequal{#2}{9}{September}{%
\ifnumequal{#2}{10}{October}{%
\ifnumequal{#2}{11}{November}{%
\ifnumequal{#2}{12}{December}{%
\errmessage{The input must be an integer between 1 and 12}%
}}}}}%
}}}}}%
}}%
}%
{%ifnostar
%https://tex.stackexchange.com/questions/15009/macros-for-common-abbreviations
\ifnumequal{#2}{1}{Jan\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{2}{Feb\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{3}{March}{%
\ifnumequal{#2}{4}{April}{%
\ifnumequal{#2}{5}{May}{%
\ifnumequal{#2}{6}{June}{%
\ifnumequal{#2}{7}{July}{%
\ifnumequal{#2}{8}{Aug\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{9}{Sep\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{10}{Oct\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{11}{Nov\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{12}{Dec\@ifnextchar{.}{}{.\@\xspace}}{%
\errmessage{The input must be an integer between 1 and 12}%
}}}}}%
}}}}}%
}}%
}%
}
\makeatother
%
%\Month has three arguments.
%The first and third are optional.
%The first is the year and the third is the day of the month.
%The mandatory argument is the number corresponding to the month.
%\Month displays the short form of the month and, if used, the day and the year.
\NewDocumentCommand{\Month}{ O{} m O{} }{%\month already defined
\numtomonth{#2}%
\ifstrempty{#3}{}{~#3}%
\ifstrempty{#1}{}{%
\ifstrempty{#3}{ #1}{, #1}%
}%
}
\usepackage{enumitem}
\parindent=0pt
\begin{document}
I am trying to make a description list on \Month[2023]{4}[8].%random sentence
\begin{description}
\item[\Month{4}]%works; no appended optional argument
Alpha
\item%[\Month{4}[8]]%doesn't work with appended optional argument
Beta
\item[{\Month{4}[8]}]%works; same as above, but enclosed in curly braces
Charlie
\item%[\Month[2023]{4}[8]]%doesn't work with appended optional argument
Delta
\item[{\Month[2023]{4}[8]}]%works; same as above, but enclosed in curly braces
Epsilon
\end{description}
\end{document}
答案1
目前 LaTeX\item
是一个经典的LaTeX 命令未使用 定义\NewDocumentCommand
。因此不支持嵌套带有可选参数的命令。但您可以定义自己的命令\Item
并使用它来代替\item
来修复此问题:
\documentclass{article}
\usepackage{stix2}
\usepackage{etoolbox,xparse,xspace}
%\numtomonth converts a number into its corresponding month.
%The starred and unstarred versions display the long and short forms of the month.
\makeatletter
\NewDocumentCommand{\numtomonth}{ s m }{%
\IfBooleanTF{#1}%
{%ifstar
\ifnumequal{#2}{1}{January}{%
\ifnumequal{#2}{2}{February}{%
\ifnumequal{#2}{3}{March}{%
\ifnumequal{#2}{4}{April}{%
\ifnumequal{#2}{5}{May}{%
\ifnumequal{#2}{6}{June}{%
\ifnumequal{#2}{7}{July}{%
\ifnumequal{#2}{8}{August}{%
\ifnumequal{#2}{9}{September}{%
\ifnumequal{#2}{10}{October}{%
\ifnumequal{#2}{11}{November}{%
\ifnumequal{#2}{12}{December}{%
\errmessage{The input must be an integer between 1 and 12}%
}}}}}%
}}}}}%
}}%
}%
{%ifnostar
%https://tex.stackexchange.com/questions/15009/macros-for-common-abbreviations
\ifnumequal{#2}{1}{Jan\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{2}{Feb\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{3}{March}{%
\ifnumequal{#2}{4}{April}{%
\ifnumequal{#2}{5}{May}{%
\ifnumequal{#2}{6}{June}{%
\ifnumequal{#2}{7}{July}{%
\ifnumequal{#2}{8}{Aug\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{9}{Sep\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{10}{Oct\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{11}{Nov\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{12}{Dec\@ifnextchar{.}{}{.\@\xspace}}{%
\errmessage{The input must be an integer between 1 and 12}%
}}}}}%
}}}}}%
}}%
}%
}
\makeatother
%
%\Month has three arguments.
%The first and third are optional.
%The first is the year and the third is the day of the month.
%The mandatory argument is the number corresponding to the month.
%\Month displays the short form of the month and, if used, the day and the year.
\NewDocumentCommand{\Month}{ O{} m O{} }{%\month already defined
\numtomonth{#2}%
\ifstrempty{#3}{}{~#3}%
\ifstrempty{#1}{}{%
\ifstrempty{#3}{ #1}{, #1}%
}%
}
\NewDocumentCommand{\Item}{o}{%
\IfValueTF{#1}{\item[{#1}]}{\item}%
}
\usepackage{enumitem}
\parindent=0pt
\begin{document}
I am trying to make a description list on \Month[2023]{4}[8].%random sentence
\begin{description}
\Item[\Month{4}]%works; no appended optional argument
Alpha
\Item[\Month{4}[8]]%works with appended optional argument
Beta
\Item[{\Month{4}[8]}]%works; same as above, but with enclosed in curly braces
Charlie
\Item[\Month[2023]{4}[8]]%works with appended optional argument
Delta
\Item[{\Month[2023]{4}[8]}]%works; same as above, but enclosed in curly braces
Epsilon
\end{description}
\end{document}
我不建议重新定义\item
自身,因为如果包\item
根据环境重新定义,这可能会失败。但如果你想忽略这一点,你也可以这样做:
\documentclass{article}
\usepackage{stix2}
\usepackage{etoolbox,xparse,xspace}
%\numtomonth converts a number into its corresponding month.
%The starred and unstarred versions display the long and short forms of the month.
\makeatletter
\NewDocumentCommand{\numtomonth}{ s m }{%
\IfBooleanTF{#1}%
{%ifstar
\ifnumequal{#2}{1}{January}{%
\ifnumequal{#2}{2}{February}{%
\ifnumequal{#2}{3}{March}{%
\ifnumequal{#2}{4}{April}{%
\ifnumequal{#2}{5}{May}{%
\ifnumequal{#2}{6}{June}{%
\ifnumequal{#2}{7}{July}{%
\ifnumequal{#2}{8}{August}{%
\ifnumequal{#2}{9}{September}{%
\ifnumequal{#2}{10}{October}{%
\ifnumequal{#2}{11}{November}{%
\ifnumequal{#2}{12}{December}{%
\errmessage{The input must be an integer between 1 and 12}%
}}}}}%
}}}}}%
}}%
}%
{%ifnostar
%https://tex.stackexchange.com/questions/15009/macros-for-common-abbreviations
\ifnumequal{#2}{1}{Jan\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{2}{Feb\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{3}{March}{%
\ifnumequal{#2}{4}{April}{%
\ifnumequal{#2}{5}{May}{%
\ifnumequal{#2}{6}{June}{%
\ifnumequal{#2}{7}{July}{%
\ifnumequal{#2}{8}{Aug\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{9}{Sep\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{10}{Oct\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{11}{Nov\@ifnextchar{.}{}{.\@\xspace}}{%
\ifnumequal{#2}{12}{Dec\@ifnextchar{.}{}{.\@\xspace}}{%
\errmessage{The input must be an integer between 1 and 12}%
}}}}}%
}}}}}%
}}%
}%
}
\makeatother
%
%\Month has three arguments.
%The first and third are optional.
%The first is the year and the third is the day of the month.
%The mandatory argument is the number corresponding to the month.
%\Month displays the short form of the month and, if used, the day and the year.
\NewDocumentCommand{\Month}{ O{} m O{} }{%\month already defined
\numtomonth{#2}%
\ifstrempty{#3}{}{~#3}%
\ifstrempty{#1}{}{%
\ifstrempty{#3}{ #1}{, #1}%
}%
}
\NewCommandCopy{\iitem}{\item}
\RenewDocumentCommand{\item}{o}{%
\IfValueTF{#1}{\iitem[{#1}]}{\iitem}%
}
\usepackage{enumitem}
\parindent=0pt
\begin{document}
I am trying to make a description list on \Month[2023]{4}[8].%random sentence
\begin{description}
\item[\Month{4}]%works; no appended optional argument
Alpha
\item[\Month{4}[8]]%works with appended optional argument
Beta
\item[{\Month{4}[8]}]%works; same as above, but enclosed in curly braces
Charlie
\item[\Month[2023]{4}[8]]%works with appended optional argument
Delta
\item[{\Month[2023]{4}[8]}]%works; same as above, but enclosed in curly braces
Epsilon
\end{description}
\end{document}
答案2
我建议采用一种不同的方法,不需要那么多可选参数。
\documentclass{article}
\usepackage{stix2}
\usepackage{enumitem}
%\numtomonth converts a number into its corresponding month.
%The starred and unstarred versions display the long and short forms of the month.
\ExplSyntaxOn
\NewDocumentCommand{\printdate}{sm}
{
\IfBooleanTF { #1 }
{
\egreg_date_print:Nn \egreg_date_longmonth:e { #2 }
}
{
\egreg_date_print:Nn \egreg_date_shortmonth:e { #2 }
}
}
\seq_new:N \l__egreg_date_items_seq
\cs_new_protected:Nn \egreg_date_print:Nn
{% #1 is either long or short month
\seq_set_split:Nnn \l__egreg_date_items_seq { - } { #2 }
\int_case:nnF { \seq_count:N \l__egreg_date_items_seq }
{
{1}{ #1 { #2 } }% just the month
{2}{
\__egreg_date_year_or_day:Nee
#1
{ \seq_item:Nn \l__egreg_date_items_seq { 1 } }
{ \seq_item:Nn \l__egreg_date_items_seq { 2 } }
}
{3}{
#1 { \seq_item:Nn \l__egreg_date_items_seq { 2 } } % month
\nobreakspace
\seq_item:Nn \l__egreg_date_items_seq { 3 } % day
,~
\seq_item:Nn \l__egreg_date_items_seq { 1 } % year
}
}
{\errmessage{Invalid~date}}
}
\cs_new_protected:Nn \__egreg_date_year_or_day:Nnn
{
\int_compare:nTF { #2>1000 }
{% first item is year, second item is month
#1 { #3 } % month
\nobreakspace
#2 % year
}
{% first item is month, second item is day
#1 { #2 } % month
\nobreakspace
#3 % day
}
}
\cs_generate_variant:Nn \__egreg_date_year_or_day:Nnn { Nee }
\cs_new_protected:Nn \egreg_addperiod:
{
\peek_charcode:NF { . } { . }
}
\cs_new:Nn \egreg_date_longmonth:n
{
\int_case:nnF { #1 }
{
{1}{January}
{2}{February}
{3}{March}
{4}{April}
{5}{May}
{6}{June}
{7}{July}
{8}{August}
{9}{September}
{10}{October}
{11}{November}
{12}{December}
}
{\errmessage{The~input~must~be~an~integer~between~1~and~12}}
}
\cs_generate_variant:Nn \egreg_date_longmonth:n { e }
\cs_new:Nn \egreg_date_shortmonth:n
{
\int_case:nnF { #1 }
{
{1}{Jan\egreg_addperiod:}
{2}{Feb\egreg_addperiod:}
{3}{March}
{4}{April}
{5}{May}
{6}{June}
{7}{July}
{8}{Aug\egreg_addperiod:}
{9}{Sep\egreg_addperiod:}
{10}{Oct\egreg_addperiod:}
{11}{Nov\egreg_addperiod:}
{12}{Dec\egreg_addperiod:}
}
{\errmessage{The~input~must~be~an~integer~between~1~and~12}}
}
\cs_generate_variant:Nn \egreg_date_shortmonth:n { e }
\ExplSyntaxOff
\begin{document}
I am trying to make a description list on \printdate{2023-4-8}
\begin{description}
\item[\printdate{4}] Alpha
\item[\printdate{4-8}] Beta
\item[\printdate{2023-4}] Charlie
\item[\printdate{2023-4-8}] Delta
\item[\printdate{2023-1-8}] Delta
\item[\printdate*{2023-1-8}] Delta
\end{description}
\end{document}
如果仅使用一个项目指定日期,则将其视为月份。如果有两个项目,则检查第一个项目是否大于 1000:在这种情况下,将其视为年份,第二个项目为月份,否则第一个项目为月份,第二个项目为日期。如果有三个项目,则有年份、月份和日期。
答案3
这个错误可以通过花括号轻松修复,但我想知道为什么会发生这种情况以及也许还如何避免使用花括号来修复它。
\item
是一个经典的 LaTeX 命令。
使用传统 LaTeX 命令时,可选参数被实现为]
-delimited 宏参数。如果使用此类命令嵌套可选参数,则第一个]
将被视为最外层可选参数的匹配分隔符(尽管它应该被视为最内层可选参数的分隔符),随后的]
将被视为最外层可选参数的匹配分隔符。使用分隔宏参数的分隔符匹配与使用非分隔宏参数的花括号匹配不同。
(与传统命令不同,使用 xparse-facilities 定义的非传统命令需要\NewDocumentCommand
付出一些额外的努力来跟踪嵌套[
并进行正确的]
匹配。)
正如您在问题中提到的,如果使用传统的 LaTeX 命令嵌套可选参数,则应将整个(!)外部可选参数的内容嵌套在花括号中,以确保正确的分隔符匹配。花括号“隐藏”了]
内部可选参数的分隔符,使其无法被用作外部可选参数的匹配分隔符。由于花括号包围了整个参数,因此在收集属于该参数的标记的过程中,花括号将被移除/剥离,因此没有任何危害。
\item[{\Month{4}[8]}]
:此处的花括号{...}
确保]
of\Month{4}[8]
不会被用作匹配的可选参数]
的 -delimiter \item
。由于花括号将全部的\item
的可选/分隔参数的内容]
,它们在收集形成 的可选参数的标记的过程中被丢弃,\item
以便只有标记\Month{4}[8]
,而不是周围的花括号,被收集作为\item
的可选参数。
\item[{\Month[2023]{4}[8]}]
:此处的花括号{...}
确保匹配的可选参数的 -delimiter ]
of\Month[2023]
和]
of均不[8]
被采用。由于花括号将]
\item
全部的\item
的可选/分隔参数的内容]
,它们在收集形成 的可选参数的标记的过程中被丢弃,\item
以便只有标记\Month[2023]{4}[8]
,而不是周围的括号,被收集作为\item
的可选参数。
在我的回答中TeX 如何查找分隔参数?我试图解释 TeX 收集分隔参数和 TeX 收集非分隔参数之间的区别。