我想引用文档中的特定项目,我称之为“行动要点”。我为它们创建了一个计数器,我想创建一个“行动要点列表”,就像创建数字列表一样。但是,问题在于,我想要一个桌子代替列表,并且我希望这些行动点包含更多信息,作为标题(特别是分配的标签和另外三个参数)。
首先,计数器和参考
\newcounter{ap}
\newcommand{\apref}[1]{A\ref{ap:#1}}
\newcommand{\ap}[1]{\refstepcounter{ap}\label{ap:#1}\apref{#1}}
然后我深入研究 latex.ltx,查看了许多 SE 答案,并定义了这些函数。
\makeatletter
\long\def\newwritefile#1#2{%
\@ifundefined{tf@#1}\relax
{%
\add@percent@to@temptokena
\@empty#2\protected@file@percent
\add@percent@to@temptokena
\immediate\write\csname tf@#1\endcsname{\the\@temptokena}%
}%
}
\def\@newstarttoc#1{%
\begingroup
\makeatletter
\@input{\jobname.#1}%
\if@filesw
\expandafter\newwrite\csname tf@#1\endcsname
\immediate\openout \csname tf@#1\endcsname \jobname.#1\relax
\fi
\@nobreakfalse
\endgroup}
% passes argument to write file
\def\newaddcontentsline#1#2{%
\newaddtocontents{#1}{\protect\newcontentsline#2
\protected@file@percent}}
% basically, it writes to file
\long\def\newaddtocontents#1#2{%
\protected@write\@auxout
{\let\label\@gobble \let\index\@gobble \let\glossary\@gobble}%
{\string\newwritefile{#1}{#2}}}
% Rendering of the line read in the file.
\def\newcontentsline#1{\shortcut{#1} \par}
\newcommand{\shortcut}[1]{%
\@tempswafalse
\@for\next:=#1\do
{\if@tempswa{ + }\else\@tempswatrue\fi\next}%
}
% What happens when we define an action point.
\def\actionpoint#1#2#3#4{
\refstepcounter{ap}\label{ap:#1}\apref{#1}
\newaddcontentsline{lap}{{{\apref{#1}},{#2},{#3},{#4}}}
}
% The list of action points.
\newcommand\tableofaps{
\@newstarttoc{lap}
}
\makeatother
到目前为止,我的最小实验看起来是这样的
\documentclass[a4paper]{article}
[ALL THAT CODE ABOVE]
\begin{document}
\tableofaps
\actionpoint{a1}{b1}{c1}{d1}
\actionpoint{a2}{b2}{c2}{d2}
\end{document}
输出
这些是字段,按照“快捷方式”函数中的定义,用“+”分隔。现在,这不是表格,所以我想修改 \tableofaps 并将 \newstarttoc 封装在表格中,并将“+”替换为“&”,
\newcommand{\shortcut}[1]{%
\@tempswafalse
\@for\next:=#1\do
{\if@tempswa{ & }\else\@tempswatrue\fi\next}%
}
\newcommand\tableofaps{
\begin{tabular}{llll}
\@newstarttoc{lap}
\end{tabular}
}
这个解决方案给我带来了很多错误,其中第一个是第 1 行的“Missing } insert”,所以没有什么帮助。
我想知道如何在不借助晦涩难懂的软件包的情况下实现我想要的目标。
答案1
[2020 年 1 月 17 日:对于这个答案先前版本中的所有示例,我犯了一个大错误:
我让宏通过/\tableofaps
写入表格环境的开始和结束。 这样,如果 不是放在文档开头,而是放在调用 之后的某个地方(例如,在文档中间或结尾),则相应的-/条目在 .aux 文件中出现的顺序以及行在 .lap 文件中出现的顺序将是错误的。\newaddtocontents
\addtocontents
\newwritefile
\@writefile
\tableofaps
\actionpoint
通过此编辑,表格环境的开始和结束根本不会写入文件。而是\tableofaps
在本地进行修补\@input
,将对\@@input
(\@@input
是 TeX 基元的 LaTeX 2e 名称\input
) 的调用包装到\begin{tabular}...\end{tabular}
.]
使用\@for
或其他方式对表格单元格内容列表进行迭代仅在您需要灵活控制表格行的表格单元格数量时才有意义。
如果您始终有四个表格单元格,则可以轻松地不用迭代/递归/循环,而只需定义处理四个参数的宏:
\documentclass[a4paper]{article}
%\usepackage{hyperref}
\makeatletter
\newcounter{ap}
% The starred variant of \apref is always without hyperlink.
% The non-starred variant of \apref is without hyperlink only
% in case hyperref is not loaded.
\@ifdefinable\apref{%
\DeclareRobustCommand{\apref}{\@ifstar\apref@star\apref@nostar}%
}%
\@ifpackageloaded{hyperref}{%
\newcommand{\apref@nostar}[1]{\hyperref[{ap:#1}]{A\ref*{ap:#1}}}%
\newcommand{\apref@star}[1]{A\ref*{ap:#1}}%
}{%
\newcommand{\apref@nostar}[1]{A\ref{ap:#1}}%
\newcommand{\apref@star}[1]{A\ref{ap:#1}}%
}%
% What happens when we define an action point.
\newcommand\actionpoint[4]{%
\ifvmode\leavevmode\fi
\refstepcounter{ap}%
\apref*{#1}%
\label{ap:#1}%
\addtocontents{lap}{%
\string\MakeMyDayByMakingMyTableline{\string\apref{#1}}{#2}{#3}{#4}\string\protected@file@percent
}%
}
\newcommand\MakeMyDayByMakingMyTableline[4]{\hline#1\\}%
% The list of action points.
\newcommand\tableofaps{%
\begingroup
% Patch \@input to wrap the call to \@@input (which is the LaTeX-name
% of the TeX-primitive \input) into \begin{tabular}..\end{tabular}%
\patch@input
\@starttoc{lap}%
\endgroup
}
\newcommand\patch@input{%
\let\saved@input=\@input
\def\@input##1{%
\let\@input=\saved@input
\IfFileExists{##1}{%
\begin{tabular}{|c|c|c|c|}%
\hline
heading1&heading2&heading3&heading4\\
\@@input\@filef@und
\hline
\end{tabular}%
}{\typeout{No file ##1.}}%
}%
}%
% Just to make sure it is not already defined...
\newcommand\saved@input{}%
\makeatother
\begin{document}
\tableofaps
\actionpoint{a1}{b1}{c1}{d1}
\actionpoint{a2}{b2}{c2}{d2}
\end{document}
以下是生成的 .aux 文件:
\relax
\newlabel{ap:a1}{{1}{1}}
\@writefile{lap}{\MakeMyDayByMakingMyTableline{\apref{a1}}{b1}{c1}{d1}\protected@file@percent}
\newlabel{ap:a2}{{2}{1}}
\@writefile{lap}{\MakeMyDayByMakingMyTableline{\apref{a2}}{b2}{c2}{d2}\protected@file@percent}
以下是生成的 .lap 文件:
\MakeMyDayByMakingMyTableline {\apref {a1}}{b1}{c1}{d1}%
\MakeMyDayByMakingMyTableline {\apref {a2}}{b2}{c2}{d2}%
以下是生成的 .pdf 输出文件的图片:
顺序\if@tempswa{ & }\else\@tempswatrue\fi\next
有问题:
如果条件为真,括号&
将不是被剥离。//-
\if..
匹配独立于组匹配、括号匹配和表格环境底层对齐机制的-匹配。 括号周围的括号会干扰表格环境底层的对齐机制。被包裹在表格环境中会干扰\else
-匹配。\fi
&
&
&
\if..\else..\fi
\if..\else..\fi
此外,构成表格单元格内容的内容将在组/本地范围内处理。
因此\next
,之后&
不会在定义它的表格单元格的本地范围内结束\@for
,因此在 LaTeX 尝试执行时将处于未定义状态。
您可以隐藏&
在宏中并使用一些技巧让 LaTeX在关闭当前表格单元格的本地范围之前\expandafter
执行。\next
在\addtocontents
某些情况下,我更喜欢\string
这样做,\protect
以避免在形成未扩展的控制字标记名称的字符后面写一个尾随空格。
对代码进行尽可能少的更改,您可以通过省略这些括号、进行一些\expandafter
-trickery 以及使用\tableofaps
本地补丁\@input
来包装\@@input
到用于开始和结束某些表格环境的命令中来获得一个表格:
\documentclass[a4paper]{article}
%\usepackage{hyperref}
\makeatletter
\newcounter{ap}
% The starred variant of \apref is always without hyperlink.
% The non-starred variant of \apref is without hyperlink only
% in case hyperref is not loaded.
\@ifdefinable\apref{%
\DeclareRobustCommand{\apref}{\@ifstar\apref@star\apref@nostar}%
}%
\@ifpackageloaded{hyperref}{%
\newcommand{\apref@nostar}[1]{\hyperref[{ap:#1}]{A\ref*{ap:#1}}}%
\newcommand{\apref@star}[1]{A\ref*{ap:#1}}%
}{%
\newcommand{\apref@nostar}[1]{A\ref{ap:#1}}%
\newcommand{\apref@star}[1]{A\ref{ap:#1}}%
}%
% \ap is not used anywhere, therefore it is commented out:
%\newcommand{\ap}[1]{\refstepcounter{ap}\label{ap:#1}\apref{#1}}
\long\def\newwritefile#1#2{%
\@ifundefined{tf@#1}\relax
{%
\add@percent@to@temptokena
\@empty#2\protected@file@percent
\add@percent@to@temptokena
\immediate\write\csname tf@#1\endcsname{\the\@temptokena}%
}%
}
\def\@newstarttoc#1{%
\begingroup
\makeatletter
\@input{\jobname.#1}%
\if@filesw
\expandafter\newwrite\csname tf@#1\endcsname
\immediate\openout \csname tf@#1\endcsname \jobname.#1\relax
\fi
\@nobreakfalse
\endgroup}
% passes argument to write file
\def\newaddcontentsline#1#2{%
\newaddtocontents{#1}{\string\newcontentsline{#2}%%%%%%%%% braces now surround #2.
\string\protected@file@percent}}
% basically, it writes to file
\long\def\newaddtocontents#1#2{%
\protected@write\@auxout
{\let\label\@gobble \let\index\@gobble \let\glossary\@gobble}%
{\string\newwritefile{#1}{#2}}}%
% Rendering of the line read in the file.
\def\newcontentsline#1{\shortcut{#1}\par}
\def\AndHiddenFomAlignMechanisms{&}
\newcommand{\shortcut}[1]{%
\hline
\global\@tempswatrue
\@for\next:=#1\do{%
\if@tempswa
\global\@tempswafalse
\else
\expandafter\expandafter\expandafter\AndHiddenFomAlignMechanisms
\fi
\next
}%
\\% <- End the table-row.
}
% What happens when we define an action point.
\def\actionpoint#1#2#3#4{%
\ifvmode\leavevmode\fi
\refstepcounter{ap}%
\apref*{#1}%
\label{ap:#1}%
%%% One pair of braces removed because that will now be added by
%%% \newaddcontentsline. \newaddcontentsline is safer this way.
\newaddcontentsline{lap}{{\string\apref{#1}},{#2},{#3},{#4}}%
}
% The list of action points.
\newcommand\tableofaps{%
\begingroup
% Patch \@input to wrap the call to \@@input (which is the LaTeX-name
% of the TeX-primitive \input) into \begin{tabular}..\end{tabular}%
\patch@input
\@newstarttoc{lap}%
\endgroup
}
\newcommand\patch@input{%
\let\saved@input=\@input
\def\@input##1{%
\let\@input=\saved@input
\IfFileExists{##1}{%
\begin{tabular}{|c|c|c|c|}%
\hline
heading1&heading2&heading3&heading4\\
\@@input\@filef@und
\hline
\end{tabular}%
}{\typeout{No file ##1.}}%
}%
}%
% Just to make sure it is not already defined...
\newcommand\saved@input{}%
\makeatother
\begin{document}
\tableofaps
\actionpoint{a1}{b1}{c1}{d1}
\actionpoint{a2}{b2}{c2}{d2}
\end{document}
以下是生成的 .aux 文件:
\relax
\newlabel{ap:a1}{{1}{1}}
\newwritefile{lap}{\newcontentsline{{\apref{a1}},{b1},{c1},{d1}}\protected@file@percent}
\newlabel{ap:a2}{{2}{1}}
\newwritefile{lap}{\newcontentsline{{\apref{a2}},{b2},{c2},{d2}}\protected@file@percent}
以下是生成的 .lap 文件:
\newcontentsline {{\apref {a1}},{b1},{c1},{d1}}%
\newcontentsline {{\apref {a2}},{b2},{c2},{d2}}%
以下是生成的 .pdf 输出文件的图片:
因为我喜欢扩展技巧,所以我更喜欢一个完全可扩展的\romannumeral0
基于扩展的尾部递归循环,用于递归地迭代单元格内容列表,并在此积累形成表行的标记。
我认为\romannumeral0
-expansion需要一些解释。
通常用于获取罗马数字的表示形式。\romannumeral⟨number⟩
由于\romannumeral
工作方式的微妙,该序列\romannumeral0
可以很好地(滥用)用于触发扩展,而无需编写一堆\expandafter
:
该序列\ronmannumeral0
触发搜索属于形成数字序列的更多数字⟨number⟩
,或搜索终止形成数字序列的某些内容⟨number⟩
在搜索过程中,可扩展事物的扩展被触发。
如果找到空格标记,则会默默丢弃该空格并终止搜索更多数字的过程。
如果收集到的数字形成一个非正数,例如数字 0,\romannumeral
则将只会默默地吞下该数字而不提供任何令牌作为回报。
因此,\romannumeral
在寻找更多数字或数字序列的终止符时,对事物的扩展可以被滥用,以触发大量的扩展工作和宏参数的翻转,只要确保在所有扩展工作之后\romannumeral
在标记流中“找到”的第一个事物形成一个非正数,例如序列就是这种情况。0⟨space token⟩
我建议向 LaTeX 提供\addtocontents
一系列标记作为参数,这些标记包括
- 调用宏
\FormTableRowFromNonDelimitedArgList
。 - 非分隔/括号嵌套参数,该参数又由非分隔/括号嵌套参数列表组成,每个参数都包含表格行的一个表格单元格的内容。
此后,此参数称为“表格单元格内容列表”。
这些内容最终都会出现在 .lap 文件中。
当处理 .lap 文件时,\FormTableRowFromNonDelimitedArgList
将执行对 的调用以及附加的 tablecell 内容列表。
在此阶段,\FormTableRowFromNonDelimitedArgList
可以启动一个完整的可扩展扩展\romannumeral0
驱动尾递归循环,用于迭代表单元格内容列表,以累积表行的标记:
\FormTableRowFromNonDelimitedArgList
只需在 tablecell-contents 列表中添加一个由 组成的标记序列即可。\romannumeral0\FormTableRowFromNonDelimitedArgListLoop⟨some more arguments⟩
\FormTableRowFromNonDelimitedArgListLoop
依次处理一些参数:
- 表行中的标记已累积至目前为止。
- 表格单元格内容的剩余列表。
- 应位于每个表行之前的标记。类似于
\hline
。 - 标记应位于每个表行后面。类似于
\\
。 - 表格单元格之间的分隔符。类似于
&
。
\FormTableRowFromNonDelimitedArgListLoop
处理这些参数的方法如下:
如果 tablecell-contents 的剩余列表为空:
\romannumeral0
通过提供一个空格(产生\romannumeral
一个非正数......)和迄今为止积累的表行标记来终止-expansion,并且在此情况下 - 如果保存迄今为止积累的表行标记的参数不为空 - 将来自 的参数的标记添加到迄今为止积累的表行标记\hline
中,并将来自 的参数的标记附加到迄今为止积累的表行标记中\\
。
如果 tablecell-contents 的剩余列表不为空:
再次调用尾部递归循环宏,其参数修改如下:
如果保存迄今为止累积的表行标记的参数是不是为空,将参数中的标记&
附加到它。将剩余的 tablecell-contents 列表的第一个元素附加到它。从
保存剩余的 tablecell-contents 列表的参数中删除第一个元素。
\documentclass[a4paper]{article}
%\usepackage{hyperref}
\makeatletter
%%=============================================================================
%% Expandable handling of lists of non-delimited arguments:
%%-----------------------------------------------------------------------------
%% Pass first argument to second argument; Exchange two arguments:
%%.............................................................................
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
%%-----------------------------------------------------------------------------
%% %% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%
%% I explained this macro in my answer to the question
%% "Expandable test for an empty token list—methods, performance, and robustness"
%% at TeX-LaTeX-StackExchange: <https://tex.stackexchange.com/a/522506/118714>
%%
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral0\expandafter\@secondoftwo\string{\expandafter
\@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
\@secondoftwo\string}\@firstoftwo\expandafter{} \@secondoftwo}%
{\@firstoftwo\expandafter{} \@firstoftwo}%
}%
%% With e-TeX-extensions' \detokenize available you can do:
%\newcommand\UD@CheckWhetherNull[1]{%
% \romannumeral0\if\relax\detokenize{#1}\relax
% \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
% {\@firstoftwo\expandafter{} \@firstoftwo}%
% {\@firstoftwo\expandafter{} \@secondoftwo}%
%}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%% \romannumeral0\UD@ExtractFirstArgLoop{<list of non-delimited arguments>\UD@SelDOm}
%%
%% yields the first non-delimited argument of the <list of non-delimited arguments>
%%
%% \romannumeral0\UD@ExtractFirstArgLoop{ABCDE\UD@SelDOm} yields A
%% \romannumeral0\UD@ExtractFirstArgLoop{{AB}CDE\UD@SelDOm} yields AB
%%
%% <list of non-delimited arguments> may contain the token \UD@SelDOm.
%% <list of non-delimited arguments> must not be empty.
%% Emptiness/non-emptiness of <list of non-delimited arguments> can be
%% checked via \UD@CheckWhetherNull.
%%-----------------------------------------------------------------------------
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\@firstoftwo{}#1}%
{\@firstoftwo\expandafter{} \@secondoftwo{}#1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%=============================================================================
\newcounter{ap}
% The starred variant of \apref is always without hyperlink.
% The non-starred variant of \apref is without hyperlink only
% in case hyperref is not loaded.
\@ifdefinable\apref{%
\DeclareRobustCommand{\apref}{\@ifstar\apref@star\apref@nostar}%
}%
\@ifpackageloaded{hyperref}{%
\newcommand{\apref@nostar}[1]{\hyperref[{ap:#1}]{A\ref*{ap:#1}}}%
\newcommand{\apref@star}[1]{A\ref*{ap:#1}}%
}{%
\newcommand{\apref@nostar}[1]{A\ref{ap:#1}}%
\newcommand{\apref@star}[1]{A\ref{ap:#1}}%
}%
% \ap is not used anywhere, therefore it is commented out:
%\newcommand{\ap}[1]{\refstepcounter{ap}\label{ap:#1}\apref{#1}}
\newcommand\FormTableRowFromNonDelimitedArgList{%
\romannumeral0\FormTableRowFromNonDelimitedArgListLoop{\hline}{\\}{&}{}%
}%
\newcommand\FormTableRowFromNonDelimitedArgListLoop[5]{%
% #1 = tokens to prepend to each table-row (\hline)
% #2 = tokens to append to each table-row (\\)
% #3 = tokens that separate contents of single cells (&)
% #4 = tokens of the table-row accumulated so far
% #5 = remaining list of tablecell-contents
\UD@CheckWhetherNull{#5}{%
\UD@CheckWhetherNull{#4}{ }{ #1#4#2}% <- the trailing spaces in \UD@CheckWhetherNull's
% 2nd/3rd argument terminate \romannumeral0-expansion
% started by \FormTableRowFromNonDelimitedArgList
}{%
\expandafter\UD@PassFirstToSecond\expandafter{\@gobble#5}{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral0%
\expandafter\UD@Exchange\expandafter{%
\romannumeral0\UD@ExtractFirstArgLoop{#5\UD@SelDOm}%
}{%
\UD@CheckWhetherNull{#4}{ }{ #4#3}%
}%
}{%
\FormTableRowFromNonDelimitedArgListLoop{#1}{#2}{#3}%
}%
}%
}%
}%
% What happens when an action point gets defined:
\newcommand\actionpoint[4]{%
\ifvmode\leavevmode\fi
\refstepcounter{ap}%
\apref*{#1}%
\label{ap:#1}%
\addtocontents{lap}{%
\string\FormTableRowFromNonDelimitedArgList{%
{\string\apref{#1}}{#2}{#3}{#4}%
}\string\protected@file@percent
}%
}
% The list of action points.
\newcommand\tableofaps{%
\begingroup
% Patch \@input to wrap the call to \@@input (which is the LaTeX-name
% of the TeX-primitive \input) into \begin{tabular}..\end{tabular}%
\patch@input
\@starttoc{lap}%
\endgroup
}
\newcommand\patch@input{%
\let\saved@input=\@input
\def\@input##1{%
\let\@input=\saved@input
\IfFileExists{##1}{%
\begin{tabular}{|c|c|c|c|}%
\hline
heading1&heading2&heading3&heading4\\
\@@input\@filef@und
\hline
\end{tabular}%
}{\typeout{No file ##1.}}%
}%
}%
% Just to make sure it is not already defined...
\newcommand\saved@input{}%
\makeatother
\begin{document}
\tableofaps
\actionpoint{a1}{b1}{c1}{d1}
\actionpoint{a2}{b2}{c2}{d2}
\end{document}
以下是生成的 .aux 文件:
\relax
\newlabel{ap:a1}{{1}{1}}
\@writefile{lap}{\FormTableRowFromNonDelimitedArgList{{\apref{a1}}{b1}{c1}{d1}}\protected@file@percent}
\newlabel{ap:a2}{{2}{1}}
\@writefile{lap}{\FormTableRowFromNonDelimitedArgList{{\apref{a2}}{b2}{c2}{d2}}\protected@file@percent}
以下是生成的 .lap 文件:
\FormTableRowFromNonDelimitedArgList {{\apref {a1}}{b1}{c1}{d1}}%
\FormTableRowFromNonDelimitedArgList {{\apref {a2}}{b2}{c2}{d2}}%
以下是生成的 .pdf 输出文件的图片: