xtemplate 对象中的重复实例名称

xtemplate 对象中的重复实例名称

看起来,对象的实例名称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} 

相关内容