6

I don't get what I'm doing wrong here. Neither of the commented lines work as the uncommented version does.

\begin{filecontents}{silly.tex}
    This is just a test.
\end{filecontents}
\documentclass{article}
\usepackage{xparse}
\NewDocumentCommand{\setversion}{ m }
    {
            Hello I'm version \texttt{#1}.\par        
    }
\ExplSyntaxOn
\cs_generate_variant:Nn \seq_set_split:Nnn {NVV}
\DeclareExpandableDocumentCommand{\makeversions}{ somm }
%\NewDocumentCommand{\makeversions}{ somm }
    {
        \bool_set:Nn \l_ae_makeversions_star_bool       { #1 }
        \tl_set:Nn   \l_ae_makeversions_delimiter_tl    { #2 }
        \tl_set:Nn   \l_ae_makeversions_versionlist_tl  { #3 }
        \tl_set:Nn   \l_ae_makeversions_inputfile_tl    { #4 }
        \exp_args:NV \IfNoValueTF \l_ae_makeversions_delimiter_tl 
                    {
                        TRUE:I have no value:\par
                        \tl_set:Nn \l_ae_makeversions_delimiter_tl { ; }
                    }
                    {
                        FALSE:\l_ae_makeversions_delimiter_tl \par
                    }
                    Delimiter ~ is ~ \l_ae_makeversions_delimiter_tl \par
%% In the following, I want the first commented out line of code to work.
%% Only one of the next FOUR lines should be left uncommented.
%        \seq_set_split:NVV \l_ae_version_seq  \l_ae_makeversions_delimiter_tl  \l_ae_makeversions_versionlist_tl 
%        \seq_set_split:NnV \l_ae_version_seq { ; } \l_ae_makeversions_versionlist_tl 
%        \seq_set_split:Nxn \l_ae_version_seq { \tl_use:N \l_ae_makeversions_delimiter_tl  }{ #3 }
        \seq_set_split:Nnn \l_ae_version_seq { ;  }{ #3 }
        \seq_map_inline:Nn \l_ae_version_seq { 
                                                 \setversion{##1}
                                                 \input{  \l_ae_makeversions_inputfile_tl }
                                                 \IfBooleanT { \l_ae_makeversions_star_bool } 
                                                             { \par\clearpage }
                                              }
    }

\ExplSyntaxOff
\pagestyle{empty}
\begin{document}

\makeversions*{a;b;c;d}{silly}

\end{document}

I've made various attempts at generating variants for \seq_set_split:N** but to no avail.

Addendum

I'm very aware that I could have defined \makeversions as follows:

\NewDocumentCommand{\makeversions}{ s O{;} mm }
    {
        \tl_set:Nn   \l_ae_makeversions_versionlist  { #3 }
        \tl_set:Nn   \l_ae_makeversions_input_file   { #4 }
        \seq_set_split:Nnn \l_ae_version_seq { ;  }{ #3 }
        \seq_map_inline:Nn \l_ae_version_seq { 
                                                 \setversion{##1}
                                                 \input{#4}
                                                 \IfBooleanT { #1 } 
                                                             { \par\clearpage }
                                              }
    }

But I would like to remind readers that this is a MWE. My original code is much more complicated than the above example suggests. The first MWE merely illustrates how I managed to boil my original problem down to something short and concise for this website.

In my original \makeversions calls another function which is context sensitive. This new function, let's call is \ae_makeversion_by_context makes various decisions on how various parameters should be handled and then farms out the jobs to several other macros which then make further decisions.

For example, there is no universal default value for the delimiter. As I change context, what constitutes the default also changes (this might sound wacky on the surface, but it makes sense within the type of documents my group needs to create). But, I also do want to allow the user to override this context dependent default value.

Based upon the responses I've been getting, I'm beginning to think that, with regards to the delimiter, I should probably just create a boolean variable. That is, I could write something like

\bool_new:N \g_ae_makeversions_delimiter_override_bool
\NewDocumentCommand{\makeversions}{ somm }
    {
        \IfNoValueTF {#2}
                    {
                        \bool_gset_true:N \g_ae_makeversions_delimiter_override_bool
                    }
                    {
                        \bool_gset_false:N \g_ae_makeversions_delimiter_override_bool
                    }
        ... < other code> ....    
    }

And that would probably be a better design decision.

So my original question boiled down to the following two questions:

  • How do I get \IfNoValue to return the correct value if passed a token and not the raw input variable #2?
  • How do I get seq_set_split to properly interpret tokens in its 2nd and 3rd arguments?

I kept these questions together because they seemed, at first blush, to revolve around the same issue.

@ScottH very nicely answered both of these questions. I suspected there was still something deeper going on with \IfNoValue. Hence I posted a new question to address that matter.

I hope this better explains some of my apparent poor design decisions.

  • Your IfNoValue code isn't executing. Probably (maybe), that code is written to taken an n type argument and can't deal with being passed a macro. – Scott H. May 18 '13 at 3:08
  • There are still problems if I replace the argument of \IfNoValueT with #2. – A.Ellett May 18 '13 at 3:10
  • In that case add \cs_generate_variant:Nn \seq_set_split:Nnn {NVV} and use that in your set split with just the macro names, no braces (which works for me). – Scott H. May 18 '13 at 3:13
  • 2
    Please use the name convention of expl. Variables always end with the type. In your case \l_ae_makeversions_input_file should be \l_ae_makeversions_input_file_tl – Marco Daniel May 18 '13 at 8:29
  • @A.Ellett Good addendum. I think you meant 2nd and 3rd arguments for \seq_set_split:Nnn. – Bruno Le Floch May 18 '13 at 15:09
5

There are two occurrences of the same issue: using an n argument type when a V argument type is required. When an n, or "no manipulation", argument type is specified the macro expects a brace group whose contents are passed untouched to the macro. When a V argument type is specified, the macro expects a variable name as an argument, and the V alue, or replacement text, of that variable is passed to the macro. Compare,

\tl_set:Nn \l_tmpa_tl {contents}

\tl_set:Nn \l_tmpb_tl {\l_tmpa_tl}
\tl_show:N \l_tmpb_tl

\tl_set:NV \l_tmpb_tl \l_tmpa_tl
\tl_show:N \l_tmpb_tl

Now where this occurs first is in

\IfNoValueT { \l_ae_makeversions_delimiter }

The macro \IfNoValueT is defined as

\cs_new_eq:NN \IfNoValueT \__xparse_if_no_value:nT

where we can see that it is an alias for an internal macro that expects an n type argument. Thus, what is passed to \__xparse_if_no_value:nT is the string

"\l_ae_makeversions_delimiter"

rather than its contents,

-NoValue-

You can work around this by first replacing \l_ae_makeversions_delimiter with its contents using

\exp_args:NV \IfNoValueT \l_ae_makeversions_delimiter

where as the V indicates, the value of \l_ae_makeversions_delimiter is recovered.


The second spot where this happens is in

\seq_set_split:Nnn \l_ae_version_seq { \l_ae_makeversions_delimiter }{ \l_ae_makeversions_version_list }

where the contents of the second brace group are used as the delimiter, so the delimiter is set to be the string "\l_ae_makeversions_delimiter" which of course is not what was intended. Rather, we want the contents of \l_ae_makeversions_delimiter to be used as the delimiter, and as such we need a V type argument. A variant of \seq_set_split with the required argument types can be generated via:

\cs_generate_variant:Nn \seq_set_split:Nnn {NVV}

and then used as in

\seq_set_split:NVV \l_ae_version_seq \l_ae_makeversions_delimiter \l_ae_makeversions_version_list

when all is as intended.


\begin{filecontents}{silly.tex}
    This is just a test.
\end{filecontents}
\documentclass{article}
\usepackage{xparse}
\NewDocumentCommand{\setversion}{ m }
    {
            Hello I'm version \texttt{#1}.\par        
    }
\ExplSyntaxOn
\cs_generate_variant:Nn \seq_set_split:Nnn {NVV}
\NewDocumentCommand{\makeversions}{ somm }
    {
        \bool_set:Nn \l_ae_makeversions_star         { #1 }
        \tl_set:Nn   \l_ae_makeversions_delimiter    { #2 }
        \tl_set:Nn   \l_ae_makeversions_version_list { #3 }
        \tl_set:Nn   \l_ae_makeversions_input_file   { #4 }
        \exp_args:NV \IfNoValueT \l_ae_makeversions_delimiter
                    {
                        \tl_set:Nn \l_ae_makeversions_delimiter { ; }
                    }
%% In the following, I want the first commented out line of code to work.
%% Only one of the next THREE lines should be left uncommented.
        \seq_set_split:NVV \l_ae_version_seq \l_ae_makeversions_delimiter \l_ae_makeversions_version_list
        \seq_map_inline:Nn \l_ae_version_seq { 
                                                 \setversion{##1}
                                                 \input{  \l_ae_makeversions_input_file }
                                                 \IfBooleanT { \l_ae_makeversions_star } 
                                                             { \par\clearpage }
                                              }
    }


\ExplSyntaxOff
\pagestyle{empty}
\begin{document}

\makeversions{a;b;c;d}{silly}

\end{document}
  • I don't fully agree with your assessment of what's happening with \IfNoValueT. Unfortunately, I need to write something like \IfNoValueT { \controlsequence } which should still be read as an n type argument. – A.Ellett May 18 '13 at 3:25
  • Fair enough, I could be wrong. How do you interpret \cs_new_eq:NN \IfNoValueT \__xparse_if_no_value:nT? – Scott H. May 18 '13 at 3:26
  • Writing \IfNoValueT { \controlsequence } seems to me to be passing an n type argument. I'm not writing \IfNoValueT \controlsequence. If what you say is true, then this seems to quite a short-coming with xparse. – A.Ellett May 18 '13 at 3:31
  • n type arguments (for the most part) are passed as is, there's no expansion or replacement. So you're asking whether the sting \controlsequence is equal to -NoValue-. Can you use an optional with default value ;? – Scott H. May 18 '13 at 3:35
  • I think I see what's happening. In the documentation for xparse, it says that simply writing \IfNoValue{-NoValue-} will not result in T. Somehow, my token list is be expanded into the string -NoValue-. What a pain. – A.Ellett May 18 '13 at 3:36
5

You seem to be doing a lot of assignments for no reason at all. Cutting your example down and 'tidying up', I get to

\begin{filecontents}{silly.tex}
    This is just a test.
\end{filecontents}
\documentclass{article}
\usepackage{xparse}
\NewDocumentCommand{\setversion}{m}
    {Hello I'm version \texttt{#1}.\par}
\ExplSyntaxOn
\seq_new:N  \l_ae_version_seq
\NewDocumentCommand { \makeversions } { s O { ; } m m }
  {
    \seq_set_split:Nnn \l_ae_version_seq {#2} {#3}
    \seq_map_inline:Nn \l_ae_version_seq
      { 
        \setversion {##1}
        \input {#4}
        \IfBooleanT #1 { \par\clearpage }
      }
    }
\ExplSyntaxOff
\pagestyle{empty}
\begin{document}

\makeversions{a;b;c;d}{silly}

\end{document}

(Ideally, \makeversions and \setversion would point to code-level functions, but I think that might obscure the point here.)

What are the key points I've tried to pick up on?

  • Variables should be declared
  • Document-level commands where optional arguments have a default are handled 'correctly' using O { <default> } (part of the interface) rather than o plus a \IfNoValue test (part of the code)
  • The s type returns a boolean which can be tested directly
  • \IfNoValue(TF) and \IfBoolean(TF) are meant to be used directly on the input to make branching decisions, not on stored values
  • I suspect there may be more going on in the 'real' situation which might explain some of the odd decisions in the MWE in the question. I'll edit if that's the case! – Joseph Wright May 18 '13 at 8:16
  • The asker seems convinced that my answer is incorrect, if that's the case (aside from using the word string when I maybe shouldn't have etc.) I wouldn't mind knowing what I've misunderstood! – Scott H. May 18 '13 at 8:50
  • @ScottH. Your answer looks broadly correct, although I'd strongly discourage (1) using internal functions (2) using \exp_args: on a document- or design-level function. – Joseph Wright May 18 '13 at 9:06
  • @ScottH. It's not that I think you're incorrect. I really mean no disrespect of you. Your answers definitely work. But I felt I still wasn't understanding something and started playing around with defining new macros as in this new question and felt that your solution still wasn't fully explaining my results. I apologize if you feel slighted; that was not my intent. :( – A.Ellett May 18 '13 at 14:03
  • There is definitely much more going on in the "real" situation. Otherwise, I would have written the code just as you have. Perhaps I'm writing something a bit too complicated, but I don't think so. I'll edit the above question to try to explain a bit. – A.Ellett May 18 '13 at 14:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.