Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I'm writing a class for handling multi-language jargon formulas. My idea is to use l3prop to store, for each formula, its language variations -- i.e., a sort of hash table. Building on one of the suggestions in What is the recommended way to assign a value to a variable and retrieve it for later use?, I'm trying the following:

\documentclass{article}
\usepackage{expl3}
\usepackage{etoolbox}
\usepackage{xstring}

\ExplSyntaxOn
\newcommand*{\setproperty}[3][standard]{%
  \cs_if_exist:cF { g_citr_#1_prop } { \prop_new:c { g_citr_#1_prop } }
  \prop_gput:cnn { g_citr_#1_prop } { #2 } { #3 }
}
\newcommand*{\getproperty}[2][standard]{%
  \cs_if_exist:cTF { g_citr_#1_prop } {%
    \prop_get:cnN { g_citr_#1_prop } { #2 } \l_citr_value_tl
    \quark_if_no_value:NTF \l_citr_value_tl {%
      Inexistent~property~`#2'
    }{%
      \tl_use:N \l_citr_value_tl
    }
  }{%
    Inexistent~family~`#1'
  }
}
\prop_new:N \g_citr_standard_prop
\tl_new:N \l_citr_value_tl
\ExplSyntaxOff

\newcommand*{\setformulas}{
  \renewcommand*{\do}[1]{%
    % split the tuple <f/x/y>
    \StrBefore      {##1}{/}   [\f]
    \StrBetween[1,2]{##1}{/}{/}[\x]
    \StrBehind [2]  {##1}{/}   [\y]
    % set it
    \setproperty[\f]{X}{\x}
    \setproperty[\f]{Y}{\y}
    % this shows properties are correctly set, but...
    `\f::X' = \getproperty[\f]{X}\par
    `\f::Y' = \getproperty[\f]{Y}\par
  }
  \docsvlist{foo/xfoo/yfoo,bar/xbar/ybar}
}
\newcommand*{\dumpformulas}{
  \renewcommand*{\do}[1]{%
    `##1::X' = \getproperty[##1]{X}\par
    `##1::Y' = \getproperty[##1]{Y}\par
  }
  \docsvlist{foo,bar}
}

\begin{document}

Setting formulas:\par
\setformulas
Getting formulas:\par
\dumpformulas

\end{document}

My problem is that properties that seem to be correctly stored via command \setformulas (there, the calls to \getproperty are for debugging purpose), appear to be mangled when accessed later via (debug) command \dumpformulas. This is what I get:

Setting formulas:
‘foo::X’ = xfoo
‘foo::Y’ = yfoo
‘bar::X’ = xbar
‘bar::Y’ = ybar
Getting formulas:
‘foo::X’ = xbar
‘foo::Y’ = ybar
‘bar::X’ = xbar
‘bar::Y’ = ybar

As you can see, it looks like the 'foo' prop gets overwritten by 'bar' prop, even though the log says that two different props are actually used (or at least that's my interpretation):

\g_citr_foo_prop=\toks35
\g_citr_bar_prop=\toks36

Conversely, using \set/getproperty outside \docsvlist loops works perfectly.

What am I doing wrong?

P.S. I originally tried with a pgffor \foreach loop which would give more concise code, especially avoiding the xstring trickery in \setformulas, but stumbled upon expansion troubles...

share|improve this question
add comment

2 Answers

up vote 5 down vote accepted

I notice a couple of things here, one which is the key to the problem and a second about the version of expl3 in use.

The big problem here is that you have

\prop_gput:cnn { g_citr_ #1 _prop } {#2} {#3}

inside \setproperty, and are using that as

\setproperty[\f]{X}{\x}

LaTeX3 is very careful about expansion, and so you are storing \x and not the value of \x in the property list. You can see this if you use for example \prop_show:N \g_citr_bar_prop:

The property list \g_citr_bar_prop contains the pairs (without outer braces):
>  {X}  =>  {\x }
>  {Y}  =>  {\y }.

Of course, when you typeset the material you get back via \getproperty what you see is the current full expansion of the data, as you've just used the token list rather than something like \tl_to_str:N.

What you (probably) want to do is to pass the content of the variable. Now, you've rather mixed code-level and document level syntax, so I'm not quite sure whether the third argument to \setproperty is supposed to be a token list variable or the material to store itself. A demonstration of the difference between the two possibilities:

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{xparse,etoolbox,xstring}

\ExplSyntaxOn

\NewDocumentCommand { \getproperty } { O { standard } m }
  { \citr_prop_get:nn {#1} {#2} }
\NewDocumentCommand { \setproperty } { O { standard } m m }
  { \citr_prop_set:nnn {#1} {#2} {#3} }

\tl_new:N \l_citr_value_tl

\cs_new_protected:Npn \citr_prop_set:nnn #1#2#3
  {
    \prop_if_exist:cF { g_citr_ #1 _prop }
      { \prop_new:c { g_citr_ #1 _prop } }
    \prop_gput:cnn { g_citr_#1_prop } {#2} {#3}
  }
\cs_generate_variant:Nn \citr_prop_set:nnn { nnV }
\cs_new_protected:Npn \citr_prop_get:nn #1#2
  {
    \prop_if_exist:cTF { g_citr_ #1 _prop }
      {
        \prop_get:cnNTF { g_citr_ #1 _prop } {#2} \l_citr_value_tl
% Convert the 'true' case to a string so we see what was stored
          { \tl_to_str:N \l_citr_value_tl }
          { Inexistent~property~`#2' }
      }
      { Inexistent~family~`#1' }
  }

\ExplSyntaxOff

\newcommand*{\setformulas}{%
  \renewcommand*{\do}[1]{%
    % split the tuple <f/x/y>
    \StrBefore      {##1}{/}   [\f]
    \StrBetween[1,2]{##1}{/}{/}[\x]
    \StrBehind [2]  {##1}{/}   [\y]
    % set it
    \setproperty[\f]{X}{\x}
    \setproperty[\f]{Y}{\y}
    % this shows properties are correctly set, but...
    `\f::X' = \getproperty[\f]{X}\par
    `\f::Y' = \getproperty[\f]{Y}\par
  }%
  \docsvlist{foo/xfoo/yfoo,bar/xbar/ybar}%
}
\newcommand*{\dumpformulas}{%
  \renewcommand*{\do}[1]{%
    `##1::X' = \getproperty[##1]{X}\par
    `##1::Y' = \getproperty[##1]{Y}\par
  }%
  \docsvlist{foo,bar}%
}

\begin{document}

Setting formulas:\par
\setformulas
Getting formulas:\par
\dumpformulas

\ExplSyntaxOn
\RenewDocumentCommand { \setproperty } { O { standard } m m }
  { \citr_prop_set:nnV {#1} {#2} #3 }
\ExplSyntaxOff

Setting formulas:\par
\setformulas
Getting formulas:\par
\dumpformulas

\end{document}

(I've made only the changes needed here to show the differences in behaviour. I would probably code everything in expl3 if writing this myself.)

On your version of expl3, property lists have not been built on toks for at least a couple of years. So you are in serious trouble using any recent LaTeX3 examples: there have been a lot of other changes since we made that change.


One way of implementing the entire thing using just expl3 for the data storage:

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{xparse,etoolbox}

\ExplSyntaxOn

\NewDocumentCommand { \getproperty } { O { standard } m }
  { \citr_prop_get:nn {#1} {#2} }
\NewDocumentCommand { \setproperty } { O { standard } m m }
  { \citr_prop_set:nnn {#1} {#2} {#3} }
\NewDocumentCommand { \setformulas } { } { \citr_formulas_set: }

\tl_new:N \l_citr_value_tl

\cs_new_protected:Npn \citr_prop_set:nnn #1#2#3
  {
    \prop_if_exist:cF { g_citr_ #1 _prop }
      { \prop_new:c { g_citr_ #1 _prop } }
    \prop_gput:cnn { g_citr_#1_prop } {#2} {#3}
  }
\cs_generate_variant:Nn \citr_prop_set:nnn { nnV }
\cs_new_protected:Npn \citr_prop_get:nn #1#2
  {
    \prop_if_exist:cTF { g_citr_ #1 _prop }
      {
        \prop_get:cnNTF { g_citr_ #1 _prop } {#2} \l_citr_value_tl
% Convert the 'true' case to a string so we see what was stored
          { \tl_to_str:N \l_citr_value_tl }
          { Inexistent~property~`#2' }
      }
      { Inexistent~family~`#1' }
  }

\cs_new_protected:Npn \citr_formulas_set:
  {
    \clist_map_inline:nn {  foo/xfoo/yfoo , bar/xbar/ybar }
      { \citr_split:nN {##1} \citr_formulas_set_aux:nnn }
  }
\cs_new_protected:Npn \citr_formulas_set_aux:nnn #1#2#3
  { 
    \citr_prop_set:nnn {#1} { X } {#2}
    \citr_prop_set:nnn {#1} { Y } {#3} 
  }

\cs_new:Npn \citr_split:nN #1#2 { \citr_split:wN  #1 \q_stop #2 }
\cs_new:Npn \citr_split:wN #1 / #2 / #3 \q_stop #4 { #4 {#1} {#2} {#3} }

\ExplSyntaxOff

\newcommand*{\dumpformulas}{%
  \renewcommand*{\do}[1]{%
    `##1::X' = \getproperty[##1]{X}\par
    `##1::Y' = \getproperty[##1]{Y}\par
  }%
  \docsvlist{foo,bar}%
}

\begin{document}

Setting formulas:\par
\setformulas
Getting formulas:\par
\dumpformulas

\end{document}

I've assumed here that the input foo/xfoo/yfoo will always contain two / tokens, which is not clear from the question. If not, something like \seq_set_split:Nnn would be appropriate if you don't want to do everything the traditional TeX way. Another alternative would be l3regex.

share|improve this answer
    
Thanks for the tip. So, if I understand correctly, in \setformulas, the debug print works because \getproperty gives back an \x which is then expanded to its original content, right? BTW, I'd appreciate if you could post your complete expl3 solution, since the modified version looks a bit confusing (indeed, everything will go into a class, without the extra \getproperty calls...) –  sphakka Jun 10 '12 at 15:37
    
@sphakka That's exactly right about \x: tracking when things expand is important. (This is a classic problem when following the flow: things like writing to the log or typesetting fully expand everything.) –  Joseph Wright Jun 10 '12 at 16:41
    
I finally managed to upgrade my expl3 installation, and the full expl3 works fine! –  sphakka Nov 28 '12 at 15:46
add comment

I wouldn't mix expl3 and etoolbox loops and map functions:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\setproperty}{O{standard}mm}
 {
  \prop_if_exist:cF { g_citr_#1_prop } { \prop_new:c { g_citr_#1_prop } }
  \prop_gput:cnn { g_citr_#1_prop } { #2 } { #3 }
 }
\NewDocumentCommand{\getproperty}{O{standard}m}
 {
  \cs_if_exist:cTF { g_citr_#1_prop }
   {
    \prop_get:cnNTF { g_citr_#1_prop } { #2 } \l_citr_value_tl
     { \tl_use:N \l_citr_value_tl }
     { Inexistent~property~`#2' }
   }
   {
    Inexistent~family~`#1'
   }
 }

\prop_new:N \g_citr_standard_prop
\tl_new:N \l_citr_value_tl

%%% Testing the macros above
\seq_new:N \l_citr_temp_seq
\NewDocumentCommand{\setformulas}{ }
 {
  \clist_map_inline:nn {foo/xfoo/yfoo,bar/xbar/ybar}
   {
    \seq_set_split:Nnn \l_citr_temp_seq { / } { ##1 }
    \setproperty[\seq_item:Nn \l_citr_temp_seq { 0 }]{X}{ \seq_item:Nn \l_citr_temp_seq { 1 } }
    \setproperty[\seq_item:Nn \l_citr_temp_seq { 0 }]{Y}{ \seq_item:Nn \l_citr_temp_seq { 2 } }
    `\seq_item:Nn \l_citr_temp_seq { 0 }::X' ~ = ~
       \getproperty[\seq_item:Nn \l_citr_temp_seq { 0 }]{X}\par
    `\seq_item:Nn \l_citr_temp_seq { 0 }::Y' ~ = ~
       \getproperty[\seq_item:Nn \l_citr_temp_seq { 0 }]{Y}\par
   }
}
\NewDocumentCommand{\dumpformulas}{ }
 {
  \clist_map_inline:nn {foo,bar}
   {
    `##1::X' ~ = ~ \getproperty[##1]{X}\par
    `##1::Y' ~ = ~ \getproperty[##1]{Y}\par
   }
}
\ExplSyntaxOff

\begin{document}

Setting formulas:\par
\setformulas
Getting formulas:\par
\dumpformulas

\end{document}

The output is

enter image description here

share|improve this answer
    
so my sin was mixing loop and map functions ;-) your solution is indeed more homogeneous, but it doesn't compile with pdflatex:! Undefined control sequence. \clist_map_inline_1:n #1->\seq_set_split:Nnn \l_citr_temp_seq {/}{#1}\setpro... l.54 \setformulas –  sphakka Jun 10 '12 at 13:05
    
@sphakka Mixing loops and map functions of different packages. Sorry, but I get correct compilation; check if your version of expl3 is up-to-date. –  egreg Jun 10 '12 at 13:10
    
@sphakka I get the expected output too and to confirm I've added the output image while I'm at it. –  percusse Jun 10 '12 at 13:29
    
@percusse, I'm updating right now; will let you know when done. –  sphakka Jun 10 '12 at 15:08
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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