如题,我想设计一个通过对 \tl_count:N
结果判断而加载不同模块的设计. 而要判断的 token list,则是通过某个命令对keys_set得到的.
首先,我在 skyrmion.cls
文件中写道:
\def\skyrmion@date{2024/10/05}
\def\skyrmion@version{0.1.1}
\ExplSyntaxOn
\cs_new_protected_nopar:Npn \skyrmion_provide_module:n #1
{
\ProvidesExplFile{skyrmion-#1-module.code.tex}{\skyrmion@date}{\skyrmion@version}
{skyrmion~ \text_titlecase:n { #1 } ~Module}
}
\ExplSyntaxOff
\ProvidesExplClass{skyrmion} {\skyrmion@date} {\skyrmion@version}
{Skyrmion in Hong Kong}
\cs_new_protected:Npn \skyrmion_msg_new:nn #1#2
{ \msg_new:nnn { skyrmion } { #1 } { #2 } }
\cs_new_protected:Npn \skyrmion_msg_error:nn #1#2
{ \msg_error:nnn { skyrmion } { #1 } { #2 } }
\cs_generate_variant:Nn \skyrmion_msg_error:nn { nx }
\skyrmion_msg_new:nn { not found module }
{
The~skyrmion~module~`#1'~not~found.
}
\cs_new_protected_nopar:Npn \skyrmion_load_module:n #1
{
\clist_map_inline:nn { #1 }
{
\file_if_exist_input:nF { skyrmion-##1-module.code.tex }
{
\skyrmion_msg_error:nn { not found module } { ##1 }
}
}
}
\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}}
\ProcessOptions\relax
\LoadClass[a4paper]{article}
无非就是平常编写cls时用的 \LoadClass
等文档类信息,和加载 skyrmion-#1-module.code.tex
模块文件的模块. 可以在 skyrmion.cls
中直接使用命令\skyrmion_load_module:n {#1}
来加载模块文件,模块文件命名当然是要 skyrmion-#1-module.code
这样. (这里参见@u19850 的ElegantBook-l3重构项目设计).
接下来,我定义了个keys组:
\keys_define:nn {skyrmion/foo}
{
tel.tl_set:N = \l__skyrmion_tel_tl
}
然后重头戏来了:定义个命令,用强制参数(m-argument)输入键值
\NewDocumentCommand{\foo}{m}
{
\keys_set:nn {skyrmion/foo} {#1}
}
这时,在用户创建的tex文件中,在preamble中执行命令 \foo{tel = 12345678}
,此时 \l__skyrmion_tel_tl
已经被赋为 12345678
,\tl_count:N \l__skyrmion_tel_tl
理所当然会输出 8
. 我就想利用根据用户输入长度是否为8还是10为判断,来加载不同的模组.
在模组文件 skyrmion-part1-module.code
中我写到:
\skyrmion_provide_module:n {part1}
\newcommand \myfoo [1] {This~is~module~1:~#1}
\endinput
在模组文件 skyrmion-part2-module.code
中我写到:
\skyrmion_provide_module:n {part2}
\newcommand \myfoo [1] {This~is~module~2:~#1}
\endinput
那么,我判断自然不能直接放 skyrmion.cls
里,因为判断必须得在命令 \foo
执行后,或者至少键 \l__skyrmion_tel_tl
被赋值后才能进行,否则 \tl_count:N
输出肯定为0. 那我就索性把判断塞 \foo
里面,并且在 \keys_set:nn
后面,进而达到在执行命令 \foo
时先给键 \l__skyrmion_tel_tl
赋值,再判断,也就是
\NewDocumentCommand{\foo}{m}
{
\keys_set:nn {skyrmion/foo} {#1}
\int_compare:nNnT {\l__skyrmion_tel_tl} = {8}
{
\skyrmion_load_module:n {part1}
}
\int_compare:nNnT {\l__skyrmion_tel_tl} = {10}
{
\skyrmion_load_module:n {part2}
}
}
然后我在 mwe.tex
中进行测试
\documentclass{skyrmion}
\foo{tel=12345678}
\begin{document}
\myfoo{1}
\ExplSyntaxOn
There~are~\tl_count:N \l__skyrmion_tel_tl{}~numbers~in~\l__skyrmion_tel_tl.
\ExplSyntaxOff
\end{document}
迎接我的是报错 Undefined control sequence.
. 说明两个模组并未被加载(说句废话:如果我用int_compare:nNnTF,False设置为加载另一个模组,那当然能顺利编译,但是结果肯定是错误的,因为一直被指向False分支).
那我就想:先把判断去掉试试?于是改为
\NewDocumentCommand{\foo}{m}
{
\keys_set:nn {skyrmion/foo} {#1}
\skyrmion_load_module:n {part1}
}
报错更热闹了:
Undefined control sequence.
Missing $ inserted.
<inserted text>
Missing \begin{document}.
Missing $ inserted.
<inserted text>
我意识到可能是expl3语法环境被淹没了?于是有重新加了组\ExplSyntaxOn/Off
\NewDocumentCommand{\foo}{m}
{
\keys_set:nn {skyrmion/foo} {#1}
\ExplSyntaxOn
\skyrmion_load_module:n {part1}
\ExplSyntaxOff
}
编译正常,讽刺的是同为expl3语法的 \keys_set:nn
在外面却安然无恙.
话又说回来,我试着加上判断外面再套一层 \ExplSyntaxOn/Off
\NewDocumentCommand{\foo}{m}
{
\ExplSyntaxOn
\keys_set:nn {skyrmion/foo} {#1}
\int_compare:nNnT {\tl_count:N \l__skyrmion_tel_tl} = {8}
{
\skyrmion_load_module:n {part1}
}
\int_compare:nNnT {\tl_count:N \l__skyrmion_tel_tl} = {10}
{
\skyrmion_load_module:n {part1}
}
\ExplSyntaxOff
}
结果还是 Undefined control sequence.
呼,问题实在是太难描述,写了个小作文😆. 最后请问expl3大师们,这个问题出在了哪里?问题中的.tex, .code.tex, .cls
文件见压缩包.Archive.zip
报错信息要看完整。
(./skyrmion-part1-module.code.tex
! Undefined control sequence.
l.1 \skyrmion
_provide_module:n {part1}
?
说明是在 skyrmion-part1-module.code.tex
这个文件的第一行报错,明显是由于没有处于 LaTeX3 环境而出错。因为是在导言区导入的模块,并不是 LaTeX3 环境。把名字替换成 \SkyrmionProvideModule
就好了。
另外,只是简单的用 \file_if_exist_input:n..
,如果重复加载一个模块,就会出现重复定义的问题,还可能会像加载 tikz 库一样,遇到类别码的问题,建议使用 \@onefilewithoptions
加载文件,不仅可以解决这两个问题,还可以像加载宏包时有宏包选项一样,有自己的模块选项。
%% skyrmion.cls, 另外两个模块文件的第一行也要修改
\def\skyrmion@date{2024/10/05}
\def\skyrmion@version{0.1.1}
\ExplSyntaxOn
\cs_new_protected_nopar:Npn \SkyrmionProvideModule #1
{
\ProvidesExplFile{skyrmion-#1-module.code.tex}{\skyrmion@date}{\skyrmion@version}
{skyrmion~ \text_titlecase:n { #1 } ~Module}
}
\ExplSyntaxOff
\ProvidesExplClass{skyrmion} {\skyrmion@date} {\skyrmion@version}
{Skyrmion in Hong Kong}
\cs_new_protected:Npn \skyrmion_msg_new:nn #1#2
{ \msg_new:nnn { skyrmion } { #1 } { #2 } }
\cs_new_protected:Npn \skyrmion_msg_error:nn #1#2
{ \msg_error:nnn { skyrmion } { #1 } { #2 } }
\cs_generate_variant:Nn \skyrmion_msg_error:nn { nx }
\skyrmion_msg_new:nn { not found module }
{
The~skyrmion~module~`#1'~not~found.
}
\tl_const:Nn \c__skyrmion_module_ext_tl { tex }
\cs_new_protected_nopar:Npn \skyrmion_load_module:n #1
{
\clist_map_inline:nn { #1 }
{
\file_if_exist:nTF { skyrmion-##1-module.code. \c__skyrmion_module_ext_tl }
{ \@onefilewithoptions { skyrmion-##1-module.code } [{}] [0000-00-00] { \c__skyrmion_module_ext_tl } } % 第一个方括号是模块选项,第二个是日期
{
\skyrmion_msg_error:nn { not found module } { ##1 }
}
}
}
\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}}
\ProcessOptions\relax
\LoadClass[a4paper]{article}
\keys_define:nn {skyrmion/foo}
{
tel.tl_set:N = \l__skyrmion_tel_tl
}
\NewDocumentCommand{\foo}{m}
{
\keys_set:nn {skyrmion/foo} {#1}
\int_compare:nNnT { \tl_count:N \l__skyrmion_tel_tl } = {8}
{
\skyrmion_load_module:n {part1}
}
\int_compare:nNnT { \tl_count:N \l__skyrmion_tel_tl } = {10}
{
\skyrmion_load_module:n {part2}
}
}
% \skyrmion_load_module:n {part1} % ?
\endinput