5

I have a command which uses a key-value syntax. For example, it includes an image with various options, if desired.

I am using expl3 key-value processing.

I would like to save the key-value settings to a global token list, so that I can reuse them as defaults the next time the command is executed. I couldn't find an obvious way to do this, as I don't want to make the macros the key-value options set global as making them global would complicate things in various ways. All I want is to save the relevant key-values to a global token list so I can reuse them. (I don't care if it is not a token list - that just seemed the obvious choice.)

I can save the booleans and comma-separated lists fine, it seems, but I'm having trouble with token lists which are themselves lists of key-value options. For example, graphics={<key-value options>} is used to pass settings to \includegraphics. However, I cannot get this to work.

Here is a (comparatively) minimal example, which makes it completely unobvious why on Venus I'm trying to do this, but that's miniaturisation for you.

\documentclass{article}
\usepackage{xparse,graphicx}
\begin{document}
\ExplSyntaxOn
\tl_new:N \l_pre_graphics_tl
\tl_new:N \g_pre_img_options_tl
\tl_new:N \g_pre_img_tl
\cs_new_protected_nopar:Nn \pre_key_tl_save:Nn
{
  \tl_if_empty:cF {l_pre_#2_tl}
    {
      \tl_gput_right:No #1 { #2 = }
      \tl_gput_right:NV #1 \c_left_brace_str
      \tl_gput_right:Nv #1 { l_pre_#2_tl }
      \tl_gput_right:NV #1 \c_right_brace_str
      \tl_gput_right:Nn #1 { , }
    }
}
\cs_generate_variant:Nn \tl_gput_right:Nn {Nv}
% includegraphics bit courtesy of egreg (chat 2015-01-08)
\cs_new:Npn \pre_includegraphics:nn #1 #2
 {
  \includegraphics[#1]{#2}
 }
\cs_generate_variant:Nn \pre_includegraphics:nn { VV }
\keys_define:nn { pre / img }
{
  graphics .tl_set:N = \l_pre_graphics_tl,
}
\NewDocumentCommand \incimg { o m }
{
  \group_begin:
    \tl_if_empty:NF \g_pre_img_options_tl
    {
      \keys_set:nV { pre / img } \g_pre_img_options_tl
    }
    \IfValueT{#1}{ \keys_set:nn { pre / img } { #1 } }
    \pre_key_tl_save:Nn \g_pre_img_options_tl { graphics }
    \tl_set:Nn \g_pre_img_tl { #2 }
    \pre_includegraphics:VV \l_pre_graphics_tl \g_pre_img_tl
  \group_end:
}
\ExplSyntaxOff

\incimg[
  graphics={width=\textwidth},
]{example-image-a}
\incimg[
  graphics={width=\textwidth},
]{example-image-a}
\end{document}

The error message has suggested I ask a human for help, so here I am ....

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! LaTeX error: "kernel/misplaced-equals-sign"
! 
! Misplaced equals sign in key-value input 194
! 
! See the LaTeX3 documentation for further information.
! 
! For immediate help type H <return>.
!...............................................  

l.194 ]{example-image-a}

? h
|'''''''''''''''''''''''''''''''''''''''''''''''                                                                                              
| LaTeX is attempting to parse some key-value input but found two equals signs                                                                
| not separated by a comma.                                                                                                                   
|...............................................                                                                                              
? h                                                                                                                                           
Sorry, I already gave what help I could...                                                                                                    
Maybe you should try asking a human?                                                                                                          
An error might have occurred before I noticed any problems.                                                                                   
``If all else fails, read the instructions.''                                                                                                 

?   

If I show the token list before trying to use it, I get

> \g_pre_img_options_tl=graphics={width=\textwidth },.

which suggests to my simple-mind that I need to somehow include curly brackets around the list. However, my attempts to do this have so far been unsuccessful. Either LaTeX looks for something which isn't meant to be a key (e.g. \g_pre_img_options) or the brackets just evaporate.

  • 1
    I think that the token list should be converted to a string first. I've done similar things with \prop lists, where foo=bar was possible and feeded as new input to \keys_set:nn – user31729 Jan 26 '17 at 23:50
  • \tl_gput_right:NV #1 \c_left_brace_str doesn't put a { with catcode 1. – Manuel Jan 26 '17 at 23:50
  • I don't entirely understand what you are trying to do. You just want to use \incimg[<options>]{foo} \incimg{baz} and ensure that the second one uses the same options as the first one? Or there's something more. If that's the case, it's easy, but I imagine there's something more. – Manuel Jan 26 '17 at 23:58
  • @ChristianHupfer Thank you very much! That works, I think :-). – cfr Jan 27 '17 at 0:25
3

I don't entirely understand what you are trying to achieve, and whatever I'm guessing, I don't think I would do it the same way, but, in any case, here's a change that makes that document compile.

\cs_new_protected_nopar:Nn \pre_key_tl_save:Nn
{
  \tl_if_empty:cF {l_cfr_#2_tl}
    {
      \tl_gput_right:No #1 { #2 = }
      \tl_gput_right:NV #1 \c_left_brace_str
      \tl_gput_right:Nv #1 { l_pre_#2_tl }
      \tl_gput_right:NV #1 \c_right_brace_str
      \tl_gput_right:Nn #1 { , }
    }
}

This seems wrong, even if I don't understand that clearly, that's, for one, overcomplicating the issue with many different parts, and also, the fact that you cannot actually do that (adding \c_left_brace_str is not adding an opening group catcode 1 { but an catcode “other”, similar to \string{). It's simpler with a plain x argument, with the correct \exp_not:*.

\cs_new_protected_nopar:Nn \pre_key_tl_save:Nn
 {
  \tl_if_empty:cF {l_cfr_#2_tl}
   {
    \tl_gput_right:Nx #1 { #2 = { \exp_not:v { l_pre_#2_tl } } , }
   }
 }

With this change it at least compiles, but I don't know if this solves all.


You want to add a string, and the value of a macro to another macro. The easiest way to do that is to do all at once with an x argument. It's literally doing what you wanted to do, but correct. \tl_put_right:Nx expands the second argument in an \edef, that is, it expands everything, so you only need to stop at the precise moment that you want. After substituting arguments you end with

\tl_gput_right:Nx \g_pre_img_options_tl { graphics = { \exp_not:v { l_pre_grahpics_tl } } , }

If you saw \edef\foo{graphics={foo}} you understand what the content of \foo would be: nothing expands, nor the letters, nor the “others” (“=”), nor the braces { and }. So the magic is inside \exp_not:v { l_pre_grahpics_tl }. Thanks to expl3 arguments that is exactly the same as \exp_not:V \l_pre_grahpics_tl which in turn is the same as \exp_not:n { width=\textwidth } which just stops the expansion there (\exp_not:n is \unexpanded).

That way, the line above, after the x expansion, ends up being exactly

\tl_gput_right:Nn \g_pre_img_options_tl { graphics = { width = \textwidth } , }

if it wasn't for the \unexpanded{width=\textwidth} TeX might have tried to expand \textwidth as with any other macro, which (not sure right now) might have been expanded to \dimen <number> and, in general, we don't want that expansion to happen.

Sorry, but anyone is free to edit this answer, which is anything but perfect.

  • Exactly what I was going to suggest :) The \something:x { ... \exp_not:v { ... } } pattern (whether :v: or whatever) works very nicely for most expansion problems along these lines. – Will Robertson Jan 27 '17 at 0:15
  • @WillRobertson I just changed that, I don't understand other parts of the code, nor have I really thought about wether \tl_if_empty:cF or \tl_gput_right:Nx are the tools for that job. But yes, the \something:x { .. \exp_not:v { .. } } is the tool for that expansion problem :) – Manuel Jan 27 '17 at 0:18
  • Thanks. I don't understand it, though .... I don't understand how it keeps the group together, I guess. The ` #2 = { ... } ` groups them? – cfr Jan 27 '17 at 1:04
  • I'd like to understand it because it seems much better than what I had, but I can't figure out how to adapt it for a clist. – cfr Jan 27 '17 at 1:09
  • 1
    @cfr An x argument is basically passed through an \edef, so it looks kind of \edef\foo{#2={\unexpanded{..}}} which leaves in your case graphics={..} which are all letters or “others” that do not expand, then there are braces with catcode 1 and 2 which do not expand, and the only thing that expands is what's inside the braces. We have there \exp_not:v { l_pre_graphics_tl } which (by definition) takes the content of \l_pre_graphics_tl and leaves it inside \unexpanded{..} so it won't expand further. That way you basically end with graphics={\unexpanded{width=\textwidth}}. – Manuel Jan 27 '17 at 1:10

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.