# l3prop property stores and etoolbox loops: weird results

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...

-

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.

-
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

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

-
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