看起来,对象的实例名称xtemplate
必须是唯一的,即使这些实例是从不同的模板创建的。这是有意为之还是一个错误?我怀疑是前者,但我找不到任何地方记录此问题,而且这似乎违反直觉,因为我本来希望每个模板的命名空间都是不同的。
在以下示例中,我为代表标准化测试中某个部分的对象创建了两个模板。每个模板代表一种不同的测试格式(例如 SAT、ACT、GRE、LSAT 等)。在该格式中,除了通用的设置外,还可以有一个或多个域(例如阅读、数学等),这些域具有自己的自定义设置。这些域与实例相对应。
\documentclass{scrreprt}
\usepackage{xparse}
\usepackage{xtemplate}
\usepackage{multicol}
\usepackage{lipsum}
\ExplSyntaxOn
\int_new:N \g_stdt_section_count_int
\tl_new:N \l_stdt_active_format_tl
\tl_new:N \l_stdt_active_domain_tl
\int_new:N \l_stdt_section_ncols_int
\cs_new:Nn \stdt_preamble: {}
\tl_new:N \l_stdt_message_tl
\cs_new:Nn \stdt_generic_preamble:
{
Generic~ preamble~ to~ section~ \int_use:N \g_stdt_section_count_int .
}
\cs_new:Nn \stdt_custom_preamble:
{
Preamble~ for~ format~ \str_use:N \l_stdt_active_format_tl~,~ domain~
\str_use:N \l_stdt_active_domain_tl~ (section~ \int_use:N \g_stdt_section_count_int).
}
\DeclareObjectType {test-section} {1}
\DeclareTemplateInterface {test-section} {format-one} {1}
{
num-columns : integer = 2,
column-sep : length = 18pt,
preamble : function 0 = \stdt_generic_preamble:,
test-message : tokenlist = default~message
}
\DeclareTemplateCode {test-section} {format-one} {1}
{
num-columns = \l_stdt_section_ncols_int,
column-sep = \columnsep,
preamble = \stdt_preamble:,
test-message = \l_stdt_message_tl
}
{
\AssignTemplateKeys
\stdt_test_section_start:n {#1}
}
\DeclareInstance{test-section}{domain-one}{format-one}
{
test-message = Domain~One
}
\DeclareInstance{test-section}{domain-two}{format-one}
{
column-sep = 36pt,
test-message = Domain~Two
}
\DeclareTemplateInterface {test-section} {format-two} {1}
{
num-columns : integer = 1,
column-sep : length = 18pt,
preamble : function 0 = \stdt_custom_preamble:,
test-message : tokenlist = default~message
}
\DeclareTemplateCode {test-section} {format-two} {1}
{
num-columns = \l_stdt_section_ncols_int,
column-sep = \columnsep,
preamble = \stdt_preamble:,
test-message = \l_stdt_message_tl
}
{
\AssignTemplateKeys
\stdt_test_section_start:n {#1}
}
% Change instance name to be unique and everything behaves as intended
\DeclareInstance{test-section}{domain-one}{format-two}
{
test-message = Domain~with~possibly~duplicate~name
}
\cs_new:Nn \stdt_test_section_start:n
{
\int_gincr:N \g_stdt_section_count_int
% allow user overrides for a subset of keys
\keys_set:nn {stdt/section}{#1}
% now do stuff
\textbf{\stdt_preamble:}\par
\emph{\l_stdt_message_tl}\par
\int_compare:nNnT {\l_stdt_section_ncols_int} > {1}
{ \begin{multicols}{\l_stdt_section_ncols_int} }
}
\cs_new:Nn \stdt_test_section_stop:
{
\int_compare:nNnT {\l_stdt_section_ncols_int} > {1}
{ \end{multicols} }
}
% Parameters of the template that the user can set directly
\keys_define:nn {stdt/section}
{
test-message .tl_set:N = \l_stdt_message_tl
}
\NewDocumentCommand{\SetFormat}{m o}{
\tl_set:Nn \l_stdt_active_format_tl {#1}
\IfValueT{#2}{
\SetDomain{#2}
}
}
\NewDocumentCommand{\SetDomain}{m}{
\tl_set:Nn \l_stdt_active_domain_tl {#1}
}
\NewDocumentEnvironment{TestSection}{O{}}
{
\UseInstance{test-section}{\l_stdt_active_domain_tl}{#1}
}
{
\stdt_test_section_stop:
}
\ExplSyntaxOff
\begin{document}
\SetFormat{format-one}[domain-one]
\begin{TestSection}
This should be two columns with an 18pt column gap.
\lipsum[1]
\end{TestSection}
\SetDomain{domain-two}
\begin{TestSection}
This should be two columns with a 36pt column gap.
\lipsum[2]
\end{TestSection}
% Change instance name to be unique and everything behaves as intended
\SetFormat{format-two}[domain-one]
\begin{TestSection}
This should be one column.
\lipsum[3]
\end{TestSection}
\end{document}
只要所有实例名称都是唯一的,我就会得到这个结果:
然而,在 MWE 中,创建第二个名为的实例domain-one
只会覆盖先前的定义,即使它们关联的模板不同。结果如下:
因为我的文档同时加载了多种格式,而且我希望此包能够轻松扩展到新格式,而不必担心先前定义的实例名称,所以我需要防止实例名称冲突。我目前的解决方法是构造实例名称,使其由模板名称 + 基本实例名称组成。
% Stitch together instance name so it will be unique between sibling templates
\NewDocumentCommand{\DeclareChildInstance}{mmmm}{
\DeclareInstance{#1}{#3-#2}{#3}{#4}
}
\NewDocumentCommand{\UseChildInstance}{mmm}{
\UseInstance{#1}{#2-#3}
}
这是一个合理的方法吗,或者我对这个问题的认识是错误的?
另外,为了节省自己的工作量,我创建了一个函数来制作模板的可编辑副本,该模板是经过稍微修改的版本\DeclareRestrictedTemplate
:
% Create an editable child template
% #1 - object type
% #2 - parent template
% #3 - child template
% #4 - keys
\NewDocumentCommand{\DeclareChildTemplate} {mmmm}
{
\stdt_declare_child_template:nnnn {#1} {#2} {#3} {#4}
}
\cs_new_protected:Nn \stdt_declare_child_template:nnnn
{
\__xtemplate_if_keys_exist:nnT {#1} {#2}
{
\__xtemplate_set_template_eq:nn { #1 / #3 } { #1 / #2 }
\bool_set_false:N \l__xtemplate_restrict_bool
\__xtemplate_edit_defaults_aux:nnn {#1} {#3} {#4}
}
}
这使我能够使用子模板进行工作,并给出行为如下的层次结构:
以这种方式创建子模板对上述命名空间问题没有直接影响,但我担心我在这里可能会做一些危险的事情(除了简单地使用内部xtemplate
命令)。唯一公开的 copy-template 命令xtemplate
提供的是\DeclareRestrictedTemplate
。这样做是为了避免我走这条路时即将造成的灾难吗?
使用上述命令修改的(工作)示例:
\documentclass{scrreprt}
\usepackage{xparse}
\usepackage{xtemplate}
\usepackage{multicol}
\usepackage{lipsum}
\ExplSyntaxOn
% Create an editable child template
% #1 - object type
% #2 - parent template
% #3 - child template
% #4 - keys
\NewDocumentCommand{\DeclareChildTemplate} {mmmm}
{
\stdt_declare_child_template:nnnn {#1} {#2} {#3} {#4}
}
\cs_new_protected:Nn \stdt_declare_child_template:nnnn
{
\__xtemplate_if_keys_exist:nnT {#1} {#2}
{
\__xtemplate_set_template_eq:nn { #1 / #3 } { #1 / #2 }
\bool_set_false:N \l__xtemplate_restrict_bool
\__xtemplate_edit_defaults_aux:nnn {#1} {#3} {#4}
}
}
% Stitch together instance name so it will be unique between sibling templates
% #1 - object name
% #2 - instance base name
% #3 - template name
% #3 - keys
\NewDocumentCommand{\DeclareChildInstance}{mmmm}{
\DeclareInstance{#1}{#3-#2}{#3}{#4}
}
\NewDocumentCommand{\UseChildInstance}{mmm}{
\UseInstance{#1}{#2-#3}
}
\int_new:N \g_stdt_section_count_int
\tl_new:N \l_stdt_active_format_tl
\tl_new:N \l_stdt_active_domain_tl
\int_new:N \l_stdt_section_ncols_int
\cs_new:Nn \stdt_preamble: {}
\tl_new:N \l_stdt_message_tl
\cs_new:Nn \stdt_generic_preamble:
{
Generic~ preamble~ to~ section~ \int_use:N \g_stdt_section_count_int .
}
\cs_new:Nn \stdt_custom_preamble:
{
Preamble~ for~ format~ \str_use:N \l_stdt_active_format_tl,~ domain~
\str_use:N \l_stdt_active_domain_tl ~(section~ \int_use:N \g_stdt_section_count_int).
}
\DeclareObjectType {test-section} {1}
\DeclareTemplateInterface {test-section} {generic} {1}
{
num-columns : integer = 2,
column-sep : length = 18pt,
preamble : function 0 = \stdt_generic_preamble:,
test-message : tokenlist = default~message
}
\DeclareTemplateCode {test-section} {generic} {1}
{
num-columns = \l_stdt_section_ncols_int,
column-sep = \columnsep,
preamble = \stdt_preamble:,
test-message = \l_stdt_message_tl
}
{
\AssignTemplateKeys
\stdt_test_section_start:n {#1}
}
% keep all default settings
\DeclareChildTemplate {test-section} {generic} {format-one} {}
\DeclareChildInstance {test-section} {domain-one} {format-one}
{
test-message = Domain~One
}
\DeclareChildInstance {test-section} {domain-two} {format-one}
{
column-sep = 36pt,
test-message = Domain~Two
}
\DeclareChildTemplate {test-section} {generic} {format-two}
{
num-columns = 1,
preamble = \stdt_custom_preamble:
}
\DeclareChildInstance {test-section} {domain-one} {format-two}
{
test-message = Domain~with~duplicate~name
}
\cs_new:Nn \stdt_test_section_start:n
{
\int_gincr:N \g_stdt_section_count_int
% allow user overrides for a subset of keys
\keys_set:nn {stdt/section}{#1}
% now do stuff
\textbf{\stdt_preamble:}\par
\emph{\l_stdt_message_tl}\par
\int_compare:nNnT {\l_stdt_section_ncols_int} > {1}
{ \begin{multicols}{\l_stdt_section_ncols_int} }
}
\cs_new:Nn \stdt_test_section_stop:
{
\int_compare:nNnT {\l_stdt_section_ncols_int} > {1}
{ \end{multicols} }
}
% Parameters of the template that the user can set directly
\keys_define:nn {stdt/section}
{
test-message .tl_set:N = \l_stdt_message_tl
}
\NewDocumentCommand{\SetFormat}{m o}{
\tl_set:Nn \l_stdt_active_format_tl {#1}
\IfValueT{#2}{
\SetDomain{#2}
}
}
\NewDocumentCommand{\SetDomain}{m}{
\tl_set:Nn \l_stdt_active_domain_tl {#1}
}
\NewDocumentEnvironment{TestSection}{O{}}
{
\UseChildInstance{test-section}{\l_stdt_active_format_tl}{\l_stdt_active_domain_tl}{#1}
}
{
\stdt_test_section_stop:
}
\ExplSyntaxOff
\begin{document}
\SetFormat{format-one}[domain-one]
\begin{TestSection}
This should be two columns with an 18pt column gap.
\lipsum[1]
\end{TestSection}
\SetDomain{domain-two}
\begin{TestSection}
This should be two columns with a 36pt column gap.
\lipsum[2]
\end{TestSection}
\SetFormat{format-two}[domain-one]
\begin{TestSection}[test-message=Does this work?]
This should be one column.
\lipsum[3]
\end{TestSection}
\end{document}