假设我有一个文件 foo.sty,其中包含以下内容:
\newcommand{\a}{x}
\newcommand{\b}[2]{x}
\newcommand{\c}[2][y]{x}
\newenvironment{d}[3]{}{}
我希望能够通过某些软件运行它并生成一个数据文件,说明已定义哪些命令和环境,以及它们有多少个可选和强制参数 - 如下所示:
command,a,0,0
command,b,0,2
command,c,1,1
environment,d,0,3
我可以用 perl 编写一些在大多数情况下都能正常工作的代码,但我想知道是否有任何方法可以做到这一点,从而使其高度可靠。我得到的印象是完全正确地解析 LaTeX 很难,基本上只有 LaTeX 才能做到这一点。有没有办法让 LaTeX 做到这一点,也许使用适当构造的 .tex 文件来提取 foo.sty、进行自省,然后执行 write18 的操作……?
我正在寻找一个开源的解决方案,并且可以在 Linux 上以自动化方式运行(而不是 GUI)。
答案1
将以下代码放在序言或.sty
文件中
\usepackage{xparse}
\ExplSyntaxOn
% Define the command that will start the working; we'll redefine \newcommand
% so that \newcommand{\xac}[2][cccc]{abc} will execute
%
% \command_check:w { \command_check_newcommand:w }{ command } {\xab}
%
% #1 = \newcommand|\renewcommand|\newenvironment|\renewenvironment (in new form)
% #2 = command|environment
% #3 = the possible *
% #4 = the argument to \newcommand
% #5 = the number of arguments
% #6 = the possible optional argument
%
% We check if the last argument is missing and take the appropriate action
\NewDocumentCommand{ \command_check:w }{m m s m O{0} o}
{
\IfBooleanTF{#3}
{ \tl_gset:Nn \g_command_check_star_tl { * } \bool_gset_true:N \g_command_check_star_bool }
{ \tl_gset:Nn \g_command_check_star_tl { } \bool_gset_false:N \g_command_check_star_bool }
\IfNoValueTF{#6}
{ \command_check_noopt:nnnn {#1} {#2} {#4} {#5} }
{ \command_check_opt:nnnnn {#1} {#2} {#4} {#5} {#6} }
}
% \StartSaveCommands sets up the checks, first by creating aliases for the kernel commands
% and then redefining them as explained above
\NewDocumentCommand{\StartSaveCommands}{}
{
\cs_set_eq:NN \command_check_newcommand:w \newcommand
\cs_set_eq:NN \command_check_renewcommand:w \renewcommand
\cs_set_eq:NN \command_check_newenvironment:w \newenvironment
\cs_set_eq:NN \command_check_renewenvironment:w \renewenvironment
\cs_set:Npn \newcommand { \command_check:w {\command_check_newcommand:w}{command} }
\cs_set:Npn \renewcommand { \command_check:w {\command_check_renewcommand:w}{command} }
\cs_set:Npn \newenvironment { \command_check:w {\command_check_newenvironment:w}{environment} }
\cs_set:Npn \renewenvironment { \command_check:w {\command_check_renewenvironment:w}{environment} }
}
% \StopSaveCommands restores the kernel commands
\NewDocumentCommand{\StopSaveCommands}{}
{
\cs_set_eq:NN \newcommand \command_check_newcommand:w
\cs_set_eq:NN \renewcommand \command_check_renewcommand:w
\cs_set_eq:NN \newenvironment \command_check_newenvironment:w
\cs_set_eq:NN \renewenvironment \command_check_renewenvironment:w
}
% \WriteSaveCommands will take care of writing out the list of commands
% with their number of arguments
\NewDocumentCommand{\WriteSaveCommands}{}
{
\iow_open:Nn \g_command_check_write { \jobname.cmd }
\seq_map_inline:Nn \g_command_check_seq {\iow_now:Nx \g_command_check_write { ##1 } }
}
% Allocate a write stream
\iow_new:N \g_command_check_write
% If there's no optional argument, say \newcommand{\xab}[1]{aaa},
% we want to write out "command,\xab ,1,0", so we store that
% string into an item appended to the sequence \g_command_check_seq
\cs_new:Npn \command_check_noopt:nnnn #1 #2 #3 #4
{
\seq_gput_right:Nx \g_command_check_seq
{
#2 \bool_if:NT \g_command_check_star_bool { (*) },
\tl_to_str:n {#3} , #4, 0
}
\exp_after:wN #1 \g_command_check_star_tl {#3} [#4]
}
% If there's an optional argument, say \newcommand{\xac}[2][cccc]{abc},
% we want to write out "command,\xac,1,1[cccc]; everything as before,
% but we decrease by 1 the number of stated arguments
\cs_new:Npn \command_check_opt:nnnnn #1 #2 #3 #4 #5
{
\seq_gput_right:Nx \g_command_check_seq
{
#2 \bool_if:NT \g_command_check_star_bool { (*) }, \tl_to_str:n {#3} ,
\int_to_arabic:n {#4-1} , 1[\tl_to_str:n{#5}]
}
\exp_after:wN #1 \g_command_check_star_tl {#3}[#4][#5]
}
% Allocate the sequence, the token list variable and the boolean
\seq_new:N \g_command_check_seq
\tl_new:N \g_command_check_star_tl
\bool_new:N \g_command_check_star_bool
\ExplSyntaxOff
现在,您可以在\StartSaveCommands
和之间输入您的个人命令\StopSaveCommands
,例如
\documentclass{article}
<the above code>
\StartSaveCommands
\newcommand{\xaa}{abc}
\newcommand*{\xab}[1]{abc}
\newcommand{\xac}[2][cccc]{abc}
\renewcommand{\phi}{\varphi}
\newenvironment{xad}{}{}
\newenvironment{xae}[1]{}{}
\newenvironment{xaf}[2][sss]{}{}
\StopSaveCommands
\WriteSaveCommands
\begin{document}
<text>
\end{document}
这将定义命令和环境并编写一个扩展名为的文件.cmd
;在我们的示例中,它将包含
command,\xaa ,0,0
command(*),\xab ,1,0
command,\xac ,1,1[cccc]
command,\phi ,0,0
environment,xad,0,0
environment,xae,1,0
environment,xaf,1,1[sss]
\newcommand
所有用或定义以及在和\renewcommand
之间创建的环境的命令都将在文件中创建一个条目。\StartSaveCommands
\StopSaveCommands
.cmd
(注:编辑时也考虑到了\newcommand*
和\newenvironment*
)
答案2
在日志文件中你会发现:
command, ,\A,0,0
command,*,\AB,0,0
command, ,\B,2,0
command,*,\BA,2,0
command, ,\C,2,y
command,*,\CBA,2,y
environment, ,D,3,0
environment,*,DD,3,0
environment, ,foobar,3,0
environment,*,FooBar,3,y
它还处理星号版本\newcommand
和\newenvironment
也可以将其保存在自己的文件中。代码还可以扩展到\renewcommand
等等...
\documentclass{minimal}
\makeatletter
\newif\if@star
\newif\if@command
\let\NewCommand\newcommand
\let\NewEnvironment\newenvironment
\def\newcommand{\global\@commandtrue%
\@ifnextchar*{\global\@startrue\test@CommEnv}{\global\@starfalse\test@CommEnv*}}
\def\newenvironment{\global\@commandfalse%
\@ifnextchar*{\global\@startrue\test@CommEnv}{\global\@starfalse\test@CommEnv*}}
\def\test@CommEnv*#1{\@ifnextchar[{\test@CommEnv@i{#1}}{%
\if@command
\typeout{command,\if@star*\else\space\fi,\string#1,0,0}%
\def\next{\NewCommand{#1}}%
\else
\typeout{environment,\if@star*\else\space\fi,\string#1,0,0}%
\def\next{\NewEnvironment{#1}}
\fi\next}}
\def\test@CommEnv@i#1[#2]{%
\@ifnextchar[{\test@CommEnv@ii{#1}[#2]}{%
\if@command
\typeout{command,\if@star*\else\space\fi,\string#1,#2,0}%
\def\next{\NewCommand{#1}[#2]}%
\else
\typeout{environment,\if@star*\else\space\fi,#1,#2,0}%
\def\next{\NewEnvironment{#1}[#2]}
\fi\next}}
\def\test@CommEnv@ii#1[#2][#3]{%
\if@command
\typeout{command,\if@star*\else\space\fi,\string#1,#2,#3}%
\def\next{\NewCommand{#1}[#2][#3]}%
\else
\typeout{environment,\if@star*\else\space\fi,#1,#2,#3}%
\def\next{\NewEnvironment{#1}[#2][#3]}
\fi\next}
\newcommand{\A}{x}
\newcommand*{\AB}{x}
\newcommand{\B}[2]{x}
\newcommand*{\BA}[2]{x}
\newcommand{\C}[2][y]{x}
\newcommand*{\CBA}[2][y]{x}
\newenvironment{D}[3]{}{}
\newenvironment*{DD}[3]{}{}
\newenvironment{foobar}[3]{}{}
\newenvironment*{FooBar}[3][y]{}{}
\begin{document}
\A \B{1}{2} \C[1]{2} \begin{D}{1}{2}{3} D \end{D}
\end{document}