It appears to be the case that instance names for an object in xtemplate need to be unique, even if these instances are created from different templates. Is this is intended behavior or a bug? I suspect the former, but I couldn't find this issue documented anywhere, and it seems counter-intuitive, as I would have expected the namespace for each template to be distinct.
In the following example, I create two templates for an object representing a section in a standardized test. Each template represents a different test format (e.g., SAT, ACT, GRE, LSAT, etc.). Within that format, there can be one or more domains (e.g., reading, math, etc.) which have their own custom settings in addition to the generic ones for the format. Those domains correspond to the instances.
\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} As long as all the instance names are unique, I get this result:
However, in the MWE, creating a second instance named domain-one simply overwrites the previous definition, even though the templates they're associated with are different. This is the result:
Because I have documents where I have multiple formats loaded simultaneously, and because I want this package to be easily extensible to new formats without worrying about previously defined instance names, I need protection against instance-name collisions. My current workaround is to construct instance names so that they consist of the template name + the base instance name.
% 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} } Is this a reasonable approach, or am I conceiving of the problem in the wrong way?
Also, to save myself some work, I create a function to make an editable copy of a template, which is a slightly modified version of \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} } } This allows me to work using child templates, giving a hierarchy that behaves something like this:
Creating child templates this way has no direct effect on the namespace issue above, but I'm worried that I may be doing something dangerous here (other than simply using internal xtemplate commands). The only public copy-template command xtemplate provides is \DeclareRestrictedTemplate. Was that done to save me from some disaster that I'm about to create if I go down this road?
Revised (working) example using the above commands:
\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} 


\IfInstanceExistTF {object type} {instance}.