0

After a good time searching the forum I gave up (I thought I could do it by myself), I have a problem passing a key from l3keys (separated by commas) to tikz. I've created a command that accepts comma-separated arguments and key=val that encapsulates tikz(mark) to generate school examples like these:

output

The command is as follows

\tikzmkpd[config-A={yshift-A={#1}, end-yshift={#1,#2,#3},
                    color={#1,#2,#3}, yshift-A={#1}, distance={#1,#2,#3}
                    angle-star={#1,#2,#3}, angle-end={#1,#2,#3},
                    }]{A,B}{C,D,E}

Keys that accept arguments separated by comma are all passed through the same function and everything works OK, with the exception of color key, when activated I get the error:

! Package PGF Math Error: Unknown function `gray' (in 'gray').

See the PGF Math package documentation for explanation.
Type H <return> for immediate help.
 ...                                              

l.126 ...,yshift-A=15pt,color={gray,red}}]{A}{B,D}

The function does not delimit it correctly and absorbs more than the account, passing the rest of the arguments to tikz, the strange thing is that if I define the same code within \keys_define:nn it works correctly:

\keys_define:nn { tikzmkpd / config-A }
 {
  color .code:n = {
                   \clist_set:Nn \l_tmpa_clist { #1 }
                   \bool_if:nT { \int_compare_p:n { \clist_count:n { #1 } = 1 } }
                    {
                     \tl_set:Nx \l_colour_AC_tl { \clist_item:Nn \l_tmpa_clist {1} }
                    }
                   \bool_if:nT { \int_compare_p:n { \clist_count:n { #1 } = 2 } }
                    {
                     \tl_set:Nx \l_colour_AC_tl { \clist_item:Nn \l_tmpa_clist {1} }
                     \tl_set:Nx \l_colour_AD_tl { \clist_item:Nn \l_tmpa_clist {2} }
                    }
                   \bool_if:nT { \int_compare_p:n { \clist_count:n { #1 } = 3 } }
                    {
                     \tl_set:Nx \l_colour_AC_tl { \clist_item:Nn \l_tmpa_clist {1} }
                     \tl_set:Nx \l_colour_AD_tl { \clist_item:Nn \l_tmpa_clist {2} }
                     \tl_set:Nx \l_colour_AE_tl { \clist_item:Nn \l_tmpa_clist {3} }
                    }
                  },
 }

This is the code I own, I have minimized it as much as possible so that it is understood (in my original document there is also tikzmkpd / config-B)

\documentclass{article}
\usepackage{xparse,tikz,etoolbox}
\usetikzlibrary{arrows.meta,bending,calc,tikzmark}
\setlength{\parindent}{0pt} % just for the example
\pagestyle{empty}
\newcommand*{\TkM}[2]{\tikzmarknode{#1}{#2}} % short :)
\makeatletter
\def\@colon{:} % active : for expl3
\ExplSyntaxOn
\cs_new_protected:Npn \__define_numeric_keys:nnnnnnnnn #1#2#3#4#5#6#7#8#9
 {
  \keys_define:nn { tkmdraw/config-#1 }
   {
    yshift-#1C .tl_set:c = {l_yshift_#1C_tl}, yshift-#1C .initial:n = #2,
    yshift-#1D .tl_set:c = {l_yshift_#1D_tl}, yshift-#1D .initial:n = #3,
    yshift-#1E .tl_set:c = {l_yshift_#1E_tl}, yshift-#1E .initial:n = #4,
    distan-#1C .tl_set:c = {l_distan_#1C_tl}, distan-#1C .initial:n = #5,
    distan-#1D .tl_set:c = {l_distan_#1D_tl}, distan-#1D .initial:n = #6,
    distan-#1E .tl_set:c = {l_distan_#1E_tl}, distan-#1E .initial:n = #7,
    angles-#1C .tl_set:c = {l_angles_#1C_tl}, angles-#1C .initial:n = #8,
    angles-#1D .tl_set:c = {l_angles_#1D_tl}, angles-#1D .initial:n = #8,
    angles-#1E .tl_set:c = {l_angles_#1E_tl}, angles-#1E .initial:n = #8,
    anglee-#1C .tl_set:c = {l_anglee_#1C_tl}, anglee-#1C .initial:n = #9,
    anglee-#1D .tl_set:c = {l_anglee_#1D_tl}, anglee-#1D .initial:n = #9,
    anglee-#1E .tl_set:c = {l_anglee_#1E_tl}, anglee-#1E .initial:n = #9,
   }
}
\__define_numeric_keys:nnnnnnnnn{A}{1.7ex}{1.7ex}{1.7ex}{1ex}{2ex}{3ex}{60}{120}

\cs_new_protected:Npn \__define_colour_keys:nnnn #1#2#3#4
 {
  \keys_define:nn { tkmdraw/config-#1 }
   {
    colour-#1C .tl_set:c = {l_colour_#1C_tl}, colour-#1C .initial:n = #2,
    colour-#1D .tl_set:c = {l_colour_#1D_tl}, colour-#1D .initial:n = #3,
    colour-#1E .tl_set:c = {l_colour_#1E_tl}, colour-#1E .initial:n = #4,
   }
 }
\__define_colour_keys:nnnn{A}{red}{green}{blue}

\keys_define:nn { tikzmkpd / config-A }
 {
  yshift-A   .tl_set:c  = {l_yshift_A_tl},%
  yshift-A   .initial:n = 1.7ex,
  end-yshift .code:n    = \__testkeys:nnn{A}{yshift}{#1},
  angle-star .code:n    = \__testkeys:nnn{A}{angles}{#1},
  angle-end  .code:n    = \__testkeys:nnn{A}{anglee}{#1},
  distance   .code:n    = \__testkeys:nnn{A}{distan}{#1},
  color      .code:n    = \__testkeys:nnn{A}{colour}{#1},
 }

\tl_new:N \l_tmpc_tl
\cs_new_protected:Npn \__testkeys:nnn #1#2#3
 {
  \clist_set:Nn \l_tmpa_clist { #3 }
  \bool_if:nT { \int_compare_p:n { \clist_count:n { #3 } = 1 } }
   {
    \tl_set:Nx \l_tmpa_tl { \clist_item:Nn \l_tmpa_clist {1} }
    \keys_set:nn { tkmdraw/config-#1 }
     { #2-#1C= \l_tmpa_tl, }
   }
  \bool_if:nT { \int_compare_p:n { \clist_count:n { #3 } = 2 } }
   {
    \tl_set:Nx \l_tmpa_tl { \clist_item:Nn \l_tmpa_clist {1} }
    \tl_set:Nx \l_tmpb_tl { \clist_item:Nn \l_tmpa_clist {2} }
    \keys_set:nn { tkmdraw/config-#1 }
     { #2-#1C= \l_tmpa_tl, #2-#1D= \l_tmpb_tl, }
   }
  \bool_if:nT { \int_compare_p:n { \clist_count:n { #3 } = 3 } }
   {
    \tl_set:Nx \l_tmpa_tl { \clist_item:Nn \l_tmpa_clist {1} }
    \tl_set:Nx \l_tmpb_tl { \clist_item:Nn \l_tmpa_clist {2} }
    \tl_set:Nx \l_tmpc_tl { \clist_item:Nn \l_tmpa_clist {3} }
    \keys_set:nn { tkmdraw/config-#1 }
    { #2-#1C= \l_tmpa_tl, #2-#1D= \l_tmpb_tl, #2-#1E= \l_tmpc_tl,}
   }
 }

% case 1: a(c+d)  A1(B1+B2)
\cs_new_protected:Npn \_case_one:n
 {
  \draw[->,\use:c{l_colour_AC_tl}]
  ([yshift=\use:c{l_yshift_A_tl}]$(pic ~ cs\@colon\csuse{A1})$)
   to[out=\use:c{l_angles_AC_tl},in=\use:c{l_anglee_AC_tl},distance=\use:c{l_distan_AC_tl}]
   ([yshift=\use:c{l_yshift_AC_tl}]$(pic ~ cs\@colon\csuse{B1})$);
  \draw[->,\use:c{l_colour_AD_tl}] %,
  ([yshift=\use:c{l_yshift_A_tl}]$(pic ~ cs\@colon\csuse{A1})$)
   to[out=\use:c{l_angles_AD_tl},in=\use:c{l_anglee_AD_tl},distance=\use:c{l_distan_AD_tl}]
   ([yshift=\use:c{l_yshift_AD_tl}]$(pic ~ cs\@colon\csuse{B2})$);
 }
% case 2: a(c+d+e)  A1(B1+B2+B3)
\cs_new_protected:Npn \_case_two:n
 {
  \_case_one:n
  \draw[->,\use:c{l_colour_AE_tl}]
  ([yshift=\use:c{l_yshift_A_tl}]$(pic ~ cs\@colon\csuse{A1})$)
   to[out=\use:c{l_angles_AE_tl},in=\use:c{l_anglee_AE_tl},distance=\use:c{l_distan_AE_tl}]
   ([yshift=\use:c{l_yshift_AE_tl}]$(pic ~ cs\@colon\csuse{B3})$);
 }

\keys_define:nn { tikzmkpd }
 {
  config-A .code:n = { \keys_set:nn { tikzmkpd / config-A } { #1 } },
 }

\NewDocumentCommand{\tikzmkpd}{O{} m m}
 {
  \group_begin:
  \IfNoValueF { #1 } { \keys_set:nn { tikzmkpd }{ #1 } }
   \foreach \x [count=\n] in {#2} { \csxdef{A\n}{\x} }%  save in A<n>
   \foreach \y [count=\m] in {#3} { \csxdef{B\m}{\y} }%  save in B<m>
   \tikzset{>={Straight ~ Barb[length=1.5pt,round,bend]}}
    \begin{tikzpicture}[overlay,remember ~ picture]%
     \bool_if:nT { \int_compare_p:n { \clist_count:n { #2 } = 1 }   &&
                   \int_compare_p:n { \clist_count:n { #3 } = 2 } } { \_case_one:n }
     \bool_if:nT { \int_compare_p:n { \clist_count:n { #2 } = 1 }   &&
                   \int_compare_p:n { \clist_count:n { #3 } = 3 } } { \_case_two:n }
    \end{tikzpicture}
  \group_end:
 }
\ExplSyntaxOff
\makeatother
\begin{document}
\section{case 1}
$\TkM{A}{2a^2}(\TkM{B}{b}+\TkM{D}{3q})$%
\tikzmkpd[config-A={end-yshift={15pt,15pt},yshift-A=15pt,color={gray,red}}]{A}{B,D}
\section{case 2}
$\TkM{a1}{2p}\left(\TkM{b1}{3q}+\TkM{c1}{4r}+\TkM{d1}{1}\right)$
\tikzmkpd[config-A={color={gray,gray,gray},end-yshift={5pt,5pt,5pt}}]{a1}{b1,c1,d1}
\end{document}

Any idea how to solve it?....(my idea is to try to repeat similar code as little as possible)

Greetings.

2

This is not a minimal example, there are ten different questions here! The code given below works. Following the request in your comment, I have made more changes than strictly necessary in order to better adhere to LaTeX3 coding conventions (to the best of my knowledge).

Note that the _private_ naming scheme in \keys_define:nn { tikzmkpd/_private_/config-#1 } is an invention of mine; I don't know if there is a standard naming scheme for such situations. I did this way because I believe the keys defined under this hierarchy are an implementation detail of your macros and shouldn't be used directly by “users”.

LaTeX3 coding conventions are presented in expl3.pdf, l3styleguide.pdf and at the beginning of interface3.pdf. The indentation style suggested there is a bit different from yours (in particular, it uses a basic offset of two spaces). But the code below is already fairly readable, so I let you decide for yourself whether you want to strictly follow the indentation guidelines too.

For functions, I used:

  • a \__tikzmkpd_ prefix for private ones;

  • a \tikzmkpd_ prefix for public ones (only \tikzmkpd:nnn, which I added as the programming-layer backend function corresponding to the user-layer \tikzmkpd command you had already defined).

This way, it is very easy to turn all this code into a package that will play well with the rest of the LaTeX ecosystem. Your \_case_one:n and \_case_two:n functions had an incorrect name for two reasons:

  • the prefix;
  • the :n suffix which indicated they were supposed to take an argument, whereas they actually don't.

I renamed these two functions to \__tikzmkpd_case_one: and \__tikzmkpd_case_two:, respectively.

\documentclass{article}
\usepackage{xparse}
\usepackage{tikz}
\usetikzlibrary{arrows.meta, bending, calc, tikzmark}

\ExplSyntaxOn

\cs_new_protected:Npn \__tikzmkpd_define_numeric_keys:nnnnnnnnn
                      #1#2#3#4#5#6#7#8#9
 {
  \keys_define:nn { tikzmkpd/_private_/config-#1 }
   {
    yshift-#1C .tl_set:c = { l_yshift_#1C_tl }, yshift-#1C .initial:n = {#2},
    yshift-#1D .tl_set:c = { l_yshift_#1D_tl }, yshift-#1D .initial:n = {#3},
    yshift-#1E .tl_set:c = { l_yshift_#1E_tl }, yshift-#1E .initial:n = {#4},
    distan-#1C .tl_set:c = { l_distan_#1C_tl }, distan-#1C .initial:n = {#5},
    distan-#1D .tl_set:c = { l_distan_#1D_tl }, distan-#1D .initial:n = {#6},
    distan-#1E .tl_set:c = { l_distan_#1E_tl }, distan-#1E .initial:n = {#7},
    angles-#1C .tl_set:c = { l_angles_#1C_tl }, angles-#1C .initial:n = {#8},
    angles-#1D .tl_set:c = { l_angles_#1D_tl }, angles-#1D .initial:n = {#8},
    angles-#1E .tl_set:c = { l_angles_#1E_tl }, angles-#1E .initial:n = {#8},
    anglee-#1C .tl_set:c = { l_anglee_#1C_tl }, anglee-#1C .initial:n = {#9},
    anglee-#1D .tl_set:c = { l_anglee_#1D_tl }, anglee-#1D .initial:n = {#9},
    anglee-#1E .tl_set:c = { l_anglee_#1E_tl }, anglee-#1E .initial:n = {#9},
   }
}

\__tikzmkpd_define_numeric_keys:nnnnnnnnn
 { A } { 1.7ex } { 1.7ex } { 1.7ex } { 1ex } { 2ex } { 3ex } { 60 } { 120 }
\__tikzmkpd_define_numeric_keys:nnnnnnnnn
 { B }{ -0.5ex }{ -0.5ex }{ -0.5ex } { 1ex } { 2ex } { 3ex } {-60 } {-120 }

\cs_new_protected:Npn \__tikzmkpd_define_colour_keys:nnnn #1#2#3#4
 {
  \keys_define:nn { tikzmkpd/_private_/config-#1 }
   {
    colour-#1C .tl_set:c = { l_colour_#1C_tl }, colour-#1C .initial:n = {#2},
    colour-#1D .tl_set:c = { l_colour_#1D_tl }, colour-#1D .initial:n = {#3},
    colour-#1E .tl_set:c = { l_colour_#1E_tl }, colour-#1E .initial:n = {#4},
   }
 }

\__tikzmkpd_define_colour_keys:nnnn { A } { red } { green } { blue }
\__tikzmkpd_define_colour_keys:nnnn { B } { red } { green } { blue }

\keys_define:nn { tikzmkpd / config-A }
 {
  yshift-A   .tl_set:c  = { l_yshift_A_tl },
  yshift-A   .initial:n = { 1.7ex },
  end-yshift .code:n    = { \__tikzmkpd_set_subkeys:nnn { A } { yshift } {#1} },
  angle-star .code:n    = { \__tikzmkpd_set_subkeys:nnn { A } { angles } {#1} },
  angle-end  .code:n    = { \__tikzmkpd_set_subkeys:nnn { A } { anglee } {#1} },
  distance   .code:n    = { \__tikzmkpd_set_subkeys:nnn { A } { distan } {#1} },
  color      .code:n    = { \__tikzmkpd_set_subkeys:nnn { A } { colour } {#1} },
  raise      .meta:n    = { yshift-A = {#1}, end-yshift = {#1,#1,#1} },
 }

\keys_define:nn { tikzmkpd / config-B }
 {
  yshift-B   .tl_set:c  = { l_yshift_B_tl },
  yshift-B   .initial:n = { -0.5ex },
  end-yshift .code:n    = { \__tikzmkpd_set_subkeys:nnn { B } { yshift } {#1} },
  angle-star .code:n    = { \__tikzmkpd_set_subkeys:nnn { B } { angles } {#1} },
  angle-end  .code:n    = { \__tikzmkpd_set_subkeys:nnn { B } { anglee } {#1} },
  distance   .code:n    = { \__tikzmkpd_set_subkeys:nnn { B } { distan } {#1} },
  color      .code:n    = { \__tikzmkpd_set_subkeys:nnn { B } { colour } {#1} },
  raise      .meta:n    = { yshift-B = {#1}, end-yshift = {#1,#1,#1} },
 }

\cs_new:Npn \__tikzmkpd_subs_i:nnn #1#2#3
 { \exp_not:n { #1-#2C = {#3} } }

\cs_new:Npn \__tikzmkpd_subs_ii:nnnn #1#2#3#4
 { \exp_not:n { #1-#2C= {#3}, #1-#2D= {#4} } }

\cs_new:Npn \__tikzmkpd_subs_iii:nnnnn #1#2#3#4#5
 { \exp_not:n { #1-#2C= {#3}, #1-#2D= {#4}, #1-#2E= {#5} } }

\cs_generate_variant:Nn \__tikzmkpd_subs_i:nnn { nnV }
\cs_generate_variant:Nn \__tikzmkpd_subs_ii:nnnn { nnVV }
\cs_generate_variant:Nn \__tikzmkpd_subs_iii:nnnnn { nnVVV }
\cs_generate_variant:Nn \keys_set:nn { nx }

\tl_new:N \l__tikzmkpd_tmp_tl

\cs_new_protected:Npn \__tikzmkpd_set_subkeys:nnn #1#2#3
 {
  \clist_set:Nn \l_tmpa_clist {#3}
  \bool_if:nT { \int_compare_p:n { \clist_count:n {#3} = 1 } }
   {
    \tl_set:Nx \l_tmpa_tl { \clist_item:Nn \l_tmpa_clist {1} }
    \keys_set:nx
     { tikzmkpd/_private_/config-#1 }
     { \__tikzmkpd_subs_i:nnV {#2} {#1} \l_tmpa_tl }
   }
  \bool_if:nT { \int_compare_p:n { \clist_count:n {#3} = 2 } }
   {
    \tl_set:Nx \l_tmpa_tl { \clist_item:Nn \l_tmpa_clist {1} }
    \tl_set:Nx \l_tmpb_tl { \clist_item:Nn \l_tmpa_clist {2} }
    \keys_set:nx
     { tikzmkpd/_private_/config-#1 }
     { \__tikzmkpd_subs_ii:nnVV {#2} {#1} \l_tmpa_tl \l_tmpb_tl }
   }
  \bool_if:nT { \int_compare_p:n { \clist_count:n {#3} = 3 } }
   {
    \tl_set:Nx \l_tmpa_tl { \clist_item:Nn \l_tmpa_clist {1} }
    \tl_set:Nx \l_tmpb_tl { \clist_item:Nn \l_tmpa_clist {2} }
    \tl_set:Nx \l__tikzmkpd_tmp_tl { \clist_item:Nn \l_tmpa_clist {3} }
    \keys_set:nx
     { tikzmkpd/_private_/config-#1 }
     { \__tikzmkpd_subs_iii:nnVVV {#2} {#1} \l_tmpa_tl \l_tmpb_tl
                                  \l__tikzmkpd_tmp_tl }
   }
 }

\seq_new:N \l__tikzmkpd_mycmd_seq

\cs_new_protected:Npn \__tikzmkpd_store_node_names:nn #1#2
  {
    \seq_set_from_clist:Nn \l__tikzmkpd_mycmd_seq {#2}
    \seq_indexed_map_inline:Nn \l__tikzmkpd_mycmd_seq
      {
        \tl_clear_new:c { l__tikzmkpd_nodename_#1##1_tl }
        \tl_set:cn { l__tikzmkpd_nodename_#1##1_tl } {##2}
      }
  }

% case 1: a(c+d)  A1(B1+B2)
\cs_new_protected:Npn \__tikzmkpd_case_one:
 {
  \draw[->, \use:c{l_colour_AC_tl}]
   ($(\tl_use:c { l__tikzmkpd_nodename_A1_tl }) + (0, \use:c{l_yshift_A_tl})$)
   to[out=\use:c{l_angles_AC_tl}, in=\use:c{l_anglee_AC_tl},
      distance=\use:c{l_distan_AC_tl}]
   ([yshift=\use:c{l_yshift_AC_tl}] \tl_use:c { l__tikzmkpd_nodename_B1_tl });
  \draw[->,\use:c{l_colour_AD_tl}]
   ($(\tl_use:c { l__tikzmkpd_nodename_A1_tl }) + (0, \use:c{l_yshift_A_tl})$)
   to[out=\use:c{l_angles_AD_tl}, in=\use:c{l_anglee_AD_tl},
      distance=\use:c{l_distan_AD_tl}]
   ([yshift=\use:c{l_yshift_AD_tl}] \tl_use:c { l__tikzmkpd_nodename_B2_tl });
 }

% case 2: a(c+d+e)  A1(B1+B2+B3)
\cs_new_protected:Npn \__tikzmkpd_case_two:
 {
  \__tikzmkpd_case_one:
  \draw[->, \use:c{l_colour_AE_tl}]
   ($(\tl_use:c { l__tikzmkpd_nodename_A1_tl }) + (0, \use:c{l_yshift_A_tl})$)
   to[out=\use:c{l_angles_AE_tl}, in=\use:c{l_anglee_AE_tl},
      distance=\use:c{l_distan_AE_tl}]
   ([yshift=\use:c{l_yshift_AE_tl}] \tl_use:c { l__tikzmkpd_nodename_B3_tl });
 }

\keys_define:nn { tikzmkpd }
 {
  config-A .code:n = { \keys_set:nn { tikzmkpd / config-A } {#1} },
  config-B .code:n = { \keys_set:nn { tikzmkpd / config-B } {#1} },
 }

\cs_new_protected:Npn \tikzmkpd:nnn #1#2#3
 {
  \group_begin:
  \keys_set:nn { tikzmkpd } {#1}
  \__tikzmkpd_store_node_names:nn {A} {#2}
  \__tikzmkpd_store_node_names:nn {B} {#3}
  \tikzset{>={Straight ~ Barb[length=1.5pt,round,bend]}}

  \begin{tikzpicture}[overlay, remember ~ picture]
      \bool_if:nT
        {
          \int_compare_p:n { \clist_count:n {#2} = 1 } &&
          \int_compare_p:n { \clist_count:n {#3} = 2 }
        }
        { \__tikzmkpd_case_one: }
      \bool_if:nT
        {
          \int_compare_p:n { \clist_count:n {#2} = 1 } &&
          \int_compare_p:n { \clist_count:n {#3} = 3 }
        }
        { \__tikzmkpd_case_two: }
  \end{tikzpicture}
  \group_end:
 }

\NewDocumentCommand \tikzmkpd { O{} m m }
 {
  \tikzmkpd:nnn {#1} {#2} {#3}
 }

\ExplSyntaxOff

\newcommand*{\TkM}[2]{\tikzmarknode{#1}{#2}} % short :)
\setlength{\parindent}{0pt} % just for the example
\pagestyle{empty}

\begin{document}

\section{case 1}

$\TkM{A}{2a^2}(\TkM{B}{b}+\TkM{D}{3q})$%
\tikzmkpd[config-A={end-yshift={8pt,9pt}, yshift-A=8pt, color={gray,red}}]%
         {A}{B, D}

\section{case 2}

$\TkM{a1}{2p}\left(\TkM{b1}{3q}+\TkM{c1}{4r}+\TkM{d1}{1}\right)$%
\tikzmkpd[config-A={color={gray,gray,gray}, end-yshift={5pt,5pt,5pt}}]%
         {a1}{b1, c1, d1}

\end{document}

I tested the preceding code, it gives the screenshot below. At the end of the post, you'll find a variant with improvements from Pablo that deals with more cases of the distributive property of multiplication (cases “one” to “four” instead of just “one” and “two”). However, I can't completely validate that version myself, as it apparently relies on recent bug fixes or features of PGF/TikZ—too recent for the version I have here, which is 2019/02/02 v3.1.1.

Screenshot

I believe you can make progress regarding your debugging methodology: had you tested the output of every single function of yours, you'd have done:

\tl_show:N \l_yshift_AC_tl
\tl_show:N \l_yshift_AD_tl
\tl_show:N \l_colour_AC_tl
\tl_show:N \l_colour_AD_tl

right after the \keys_set:nn { tikzmkpd } {#1} call, which would have shown that your \__testkeys:nnn (now called \__tikzmkpd_set_subkeys:nnn) was not working properly. The main problem there was that you were passing single tokens such as \l_tmpa_tl in arguments where you actually wanted their value (something equivalent to the result of expanding \l_tmpa_tl or \tl_use:N \l_tmpa_tl; see how I did). Also, more bracing was desirable in some places in case some parameter values were to contain commas. For instance, this:

\cs_new:Npn \__tikzmkpd_subs_i:nnn #1#2#3
 { #1-#2C = {#3}, }

is preferable to this:

\cs_new:Npn \__tikzmkpd_subs_i:nnn #1#2#3
 { #1-#2C= #3, }

in case #3 were to contain commas. In fact, unless you plan to use macros in #1, #2 or #3, the following is maybe preferable in order to avoid unwanted expansions (since we later use it inside an x argument), which is what I did above:

\cs_new:Npn \__tikzmkpd_subs_i:nnn #1#2#3
 { \exp_not:n { #1-#2C = {#3} } }

The other problem that prevented the code from working is that for nodes created with \tikzmarknode, the pic cs syntax you used doesn't work—at least in my setup. The good news is, this can be simplified a lot (see my changes to \_case_one:n and \_case_two:n [now renamed to \__tikzmkpd_case_one: and \__tikzmkpd_case_two:]).

Your question is so complex with all the parameters, the TikZ code mixed up with the l3keys code, that I fear no one is going to reuse my answer. :-( I don't criticize the use of l3keys for TikZ work (for one, I'm much more comfortable myself with l3keys than with pgfkeys), but in the interest of other users of this site, I think you should have isolated the parsing problem from the TikZ issue—as people already told you.

Your LaTeX coding skills seem good; my main advice would be:

  • Read a bit more documentation, in particular on the LaTeX3 naming conventions, expansion and variants (see the first chapters of interface3.pdf).

  • Whenever you face again a problem like that, use \tracingmacros=1 \tracingonline=1\relax, and if this isn't enough to understand what is happening, print the value of every “thing” that your functions prepare1 (for each of your functions). For example, before even trying to typeset the tikzpicture, print all options and all parameters that you are going to use therein. This method would have helped you find the errors in your \__testkeys:nnn (now renamed to \__tikzmkpd_set_subkeys:nnn).

As promised, here is the expanded code with additions from Pablo:

\documentclass{article}
\usepackage{xparse}
\usepackage{tikz}
\usetikzlibrary{arrows.meta, bending, calc, tikzmark}

\ExplSyntaxOn

\cs_new_protected:Npn \__tikzmkpd_define_numeric_keys:nnnnnnnnn
                      #1#2#3#4#5#6#7#8#9
 {
  \keys_define:nn { tikzmkpd/_private_/config-#1 }
   {
    yshift-#1C .tl_set:c = { l_yshift_#1C_tl }, yshift-#1C .initial:n = {#2},
    yshift-#1D .tl_set:c = { l_yshift_#1D_tl }, yshift-#1D .initial:n = {#3},
    yshift-#1E .tl_set:c = { l_yshift_#1E_tl }, yshift-#1E .initial:n = {#4},
    distan-#1C .tl_set:c = { l_distan_#1C_tl }, distan-#1C .initial:n = {#5},
    distan-#1D .tl_set:c = { l_distan_#1D_tl }, distan-#1D .initial:n = {#6},
    distan-#1E .tl_set:c = { l_distan_#1E_tl }, distan-#1E .initial:n = {#7},
    angles-#1C .tl_set:c = { l_angles_#1C_tl }, angles-#1C .initial:n = {#8},
    angles-#1D .tl_set:c = { l_angles_#1D_tl }, angles-#1D .initial:n = {#8},
    angles-#1E .tl_set:c = { l_angles_#1E_tl }, angles-#1E .initial:n = {#8},
    anglee-#1C .tl_set:c = { l_anglee_#1C_tl }, anglee-#1C .initial:n = {#9},
    anglee-#1D .tl_set:c = { l_anglee_#1D_tl }, anglee-#1D .initial:n = {#9},
    anglee-#1E .tl_set:c = { l_anglee_#1E_tl }, anglee-#1E .initial:n = {#9},
   }
}

\__tikzmkpd_define_numeric_keys:nnnnnnnnn
 { A } { 1.7ex } { 1.7ex } { 1.7ex } { 1ex } { 2ex } { 3ex } { 60 } { 120 }
\__tikzmkpd_define_numeric_keys:nnnnnnnnn
 { B }{ -0.5ex }{ -0.5ex }{ -0.5ex } { 1ex } { 2ex } { 3ex } {-60 } {-120 }

\cs_new_protected:Npn \__tikzmkpd_define_colour_keys:nnnn #1#2#3#4
 {
  \keys_define:nn { tikzmkpd/_private_/config-#1 }
   {
    colour-#1C .tl_set:c = { l_colour_#1C_tl }, colour-#1C .initial:n = {#2},
    colour-#1D .tl_set:c = { l_colour_#1D_tl }, colour-#1D .initial:n = {#3},
    colour-#1E .tl_set:c = { l_colour_#1E_tl }, colour-#1E .initial:n = {#4},
   }
 }

\__tikzmkpd_define_colour_keys:nnnn { A } { red } { green } { blue }
\__tikzmkpd_define_colour_keys:nnnn { B } { red } { green } { blue }

\keys_define:nn { tikzmkpd / config-A }
 {
  yshift-A   .tl_set:c  = { l_yshift_A_tl },
  yshift-A   .initial:n = { 1.7ex },
  end-yshift .code:n    = { \__tikzmkpd_set_subkeys:nnn { A } { yshift } {#1} },
  angle-star .code:n    = { \__tikzmkpd_set_subkeys:nnn { A } { angles } {#1} },
  angle-end  .code:n    = { \__tikzmkpd_set_subkeys:nnn { A } { anglee } {#1} },
  distance   .code:n    = { \__tikzmkpd_set_subkeys:nnn { A } { distan } {#1} },
  color      .code:n    = { \__tikzmkpd_set_subkeys:nnn { A } { colour } {#1} },
  raise      .meta:n    = { yshift-A = {#1}, end-yshift = {#1,#1,#1} },
 }

\keys_define:nn { tikzmkpd / config-B }
 {
  yshift-B   .tl_set:c  = { l_yshift_B_tl },
  yshift-B   .initial:n = { -0.5ex },
  end-yshift .code:n    = { \__tikzmkpd_set_subkeys:nnn { B } { yshift } {#1} },
  angle-star .code:n    = { \__tikzmkpd_set_subkeys:nnn { B } { angles } {#1} },
  angle-end  .code:n    = { \__tikzmkpd_set_subkeys:nnn { B } { anglee } {#1} },
  distance   .code:n    = { \__tikzmkpd_set_subkeys:nnn { B } { distan } {#1} },
  color      .code:n    = { \__tikzmkpd_set_subkeys:nnn { B } { colour } {#1} },
  raise      .meta:n    = { yshift-B = {#1}, end-yshift = {#1,#1,#1} },
 }

\cs_new:Npn \__tikzmkpd_subs_i:nnn #1#2#3
 { \exp_not:n { #1-#2C = {#3} } }

\cs_new:Npn \__tikzmkpd_subs_ii:nnnn #1#2#3#4
 { \exp_not:n { #1-#2C= {#3}, #1-#2D= {#4} } }

\cs_new:Npn \__tikzmkpd_subs_iii:nnnnn #1#2#3#4#5
 { \exp_not:n { #1-#2C= {#3}, #1-#2D= {#4}, #1-#2E= {#5} } }

\cs_generate_variant:Nn \__tikzmkpd_subs_i:nnn { nnV }
\cs_generate_variant:Nn \__tikzmkpd_subs_ii:nnnn { nnVV }
\cs_generate_variant:Nn \__tikzmkpd_subs_iii:nnnnn { nnVVV }
\cs_generate_variant:Nn \keys_set:nn { nx }

\tl_new:N \l__tikzmkpd_tmp_tl

\cs_new_protected:Npn \__tikzmkpd_set_subkeys:nnn #1#2#3
 {
  \clist_set:Nn \l_tmpa_clist {#3}
  \bool_if:nT { \int_compare_p:n { \clist_count:n {#3} = 1 } }
   {
    \tl_set:Nx \l_tmpa_tl { \clist_item:Nn \l_tmpa_clist {1} }
    \keys_set:nx
     { tikzmkpd/_private_/config-#1 }
     { \__tikzmkpd_subs_i:nnV {#2} {#1} \l_tmpa_tl }
   }
  \bool_if:nT { \int_compare_p:n { \clist_count:n {#3} = 2 } }
   {
    \tl_set:Nx \l_tmpa_tl { \clist_item:Nn \l_tmpa_clist {1} }
    \tl_set:Nx \l_tmpb_tl { \clist_item:Nn \l_tmpa_clist {2} }
    \keys_set:nx
     { tikzmkpd/_private_/config-#1 }
     { \__tikzmkpd_subs_ii:nnVV {#2} {#1} \l_tmpa_tl \l_tmpb_tl }
   }
  \bool_if:nT { \int_compare_p:n { \clist_count:n {#3} = 3 } }
   {
    \tl_set:Nx \l_tmpa_tl { \clist_item:Nn \l_tmpa_clist {1} }
    \tl_set:Nx \l_tmpb_tl { \clist_item:Nn \l_tmpa_clist {2} }
    \tl_set:Nx \l__tikzmkpd_tmp_tl { \clist_item:Nn \l_tmpa_clist {3} }
    \keys_set:nx
     { tikzmkpd/_private_/config-#1 }
     { \__tikzmkpd_subs_iii:nnVVV {#2} {#1} \l_tmpa_tl \l_tmpb_tl
                                  \l__tikzmkpd_tmp_tl }
   }
 }

\seq_new:N \l__tikzmkpd_mycmd_seq

\cs_new_protected:Npn \__tikzmkpd_store_node_names:nn #1#2
  {
    \seq_set_from_clist:Nn \l__tikzmkpd_mycmd_seq {#2}
    \seq_indexed_map_inline:Nn \l__tikzmkpd_mycmd_seq
      {
        \tl_clear_new:c { l__tikzmkpd_nodename_#1##1_tl }
        \tl_set:cn { l__tikzmkpd_nodename_#1##1_tl } {##2}
      }
  }

% case 1: a(c+d)  A1(B1+B2)
\cs_new_protected:Npn \__tikzmkpd_case_one:
 {
  \draw[->, \tl_use:c{l_colour_AC_tl}]
   ([yshift=\tl_use:c{l_yshift_A_tl}]$(pic ~ cs  \tl_to_str:n { : } \tl_use:c { l__tikzmkpd_nodename_A1_tl})$)
   to[out=\tl_use:c{l_angles_AC_tl}, in=\tl_use:c{l_anglee_AC_tl},
      distance=\tl_use:c{l_distan_AC_tl}]
   ([yshift=\tl_use:c{l_yshift_AC_tl}]$(pic ~ cs \tl_to_str:n { : } \tl_use:c { l__tikzmkpd_nodename_B1_tl})$);
  \draw[->,\tl_use:c{l_colour_AD_tl}]
   ([yshift=\tl_use:c{l_yshift_A_tl}]$(pic ~ cs  \tl_to_str:n { : } \tl_use:c { l__tikzmkpd_nodename_A1_tl})$)
   to[out=\tl_use:c{l_angles_AD_tl}, in=\tl_use:c{l_anglee_AD_tl},
      distance=\tl_use:c{l_distan_AD_tl}]
   ([yshift=\tl_use:c{l_yshift_AD_tl}]$(pic ~ cs \tl_to_str:n { : } \tl_use:c { l__tikzmkpd_nodename_B2_tl})$);
 }

% case 2: a(c+d+e)  A1(B1+B2+B3)
\cs_new_protected:Npn \__tikzmkpd_case_two:
 {
  \__tikzmkpd_case_one:
  \draw[->, \tl_use:c{l_colour_AE_tl}]
   ([yshift=\tl_use:c{l_yshift_A_tl}]$(pic ~ cs \tl_to_str:n { : } \tl_use:c { l__tikzmkpd_nodename_A1_tl})$)
   to[out=\tl_use:c{l_angles_AE_tl}, in=\tl_use:c{l_anglee_AE_tl},
      distance=\tl_use:c{l_distan_AE_tl}]
   ([yshift=\tl_use:c{l_yshift_AE_tl}]$(pic ~ cs \tl_to_str:n { : } \tl_use:c { l__tikzmkpd_nodename_B3_tl})$);
 }

% case 3: (a+b)(c+d) (A1+A2)(B1+B2)
\cs_new_protected:Npn \__tikzmkpd_case_three:
 {
  \__tikzmkpd_case_one:
  \draw[->,\tl_use:c{l_colour_BC_tl}]
   ([yshift=\tl_use:c{l_yshift_B_tl}]$(pic ~ cs \tl_to_str:n { : } \tl_use:c { l__tikzmkpd_nodename_A2_tl})$)
   to[out=\tl_use:c{l_angles_BC_tl},in=\tl_use:c{l_anglee_BC_tl},
      distance=\tl_use:c{l_distan_BC_tl}]
   ([yshift=\tl_use:c{l_yshift_BC_tl}]$(pic ~ cs \tl_to_str:n { : } \tl_use:c { l__tikzmkpd_nodename_B1_tl})$);
  \draw[->,\tl_use:c{l_colour_BD_tl}]
   ([yshift=\tl_use:c{l_yshift_B_tl}]$(pic ~ cs \tl_to_str:n { : }  \tl_use:c { l__tikzmkpd_nodename_A2_tl})$)
   to[out=\tl_use:c{l_angles_BD_tl},in=\tl_use:c{l_anglee_BD_tl},
      distance=\tl_use:c{l_distan_BD_tl}]
   ([yshift=\tl_use:c{l_yshift_BD_tl}]$(pic ~ cs \tl_to_str:n { : } \tl_use:c { l__tikzmkpd_nodename_B2_tl})$);
 }

% case 4: (a+b)(c+d+e) (A1+A2)(B1+B2+B3)
\cs_new_protected:Npn \__tikzmkpd_case_four:
 {
  \__tikzmkpd_case_three:
  \draw[->,\tl_use:c{l_colour_AE_tl}]
   ([yshift=\tl_use:c{l_yshift_A_tl}]$(pic ~ cs \tl_to_str:n { : }  \tl_use:c { l__tikzmkpd_nodename_A1_tl})$)
   to[out=\tl_use:c{l_angles_AE_tl},in=\tl_use:c{l_anglee_AE_tl},
      distance=\tl_use:c{l_distan_AE_tl}]
   ([yshift=\tl_use:c{l_yshift_AE_tl}]$(pic ~ cs \tl_to_str:n { : } \tl_use:c { l__tikzmkpd_nodename_B3_tl})$);
  \draw[->,\tl_use:c{l_colour_BE_tl}]
   ([yshift=\tl_use:c{l_yshift_B_tl}]$(pic ~ cs \tl_to_str:n { : }  \tl_use:c { l__tikzmkpd_nodename_A2_tl})$)
   to[out=\tl_use:c{l_angles_BE_tl},in=\tl_use:c{l_anglee_BE_tl},
      distance=\tl_use:c{l_distan_BE_tl}]
   ([yshift=\tl_use:c{l_yshift_BE_tl}]$(pic ~ cs \tl_to_str:n { : } \tl_use:c { l__tikzmkpd_nodename_B3_tl})$);
 }

\keys_define:nn { tikzmkpd }
 {
  config-A .code:n = { \keys_set:nn { tikzmkpd / config-A } {#1} },
  config-B .code:n = { \keys_set:nn { tikzmkpd / config-B } {#1} },
 }

\cs_new_protected:Npn \tikzmkpd:nnn #1#2#3
 {
  \group_begin:
  \keys_set:nn { tikzmkpd } {#1}
  \__tikzmkpd_store_node_names:nn {A} {#2}
  \__tikzmkpd_store_node_names:nn {B} {#3}
  \tikzset{>={Straight ~ Barb[length=1.5pt,round,bend]}}

  \begin{tikzpicture}[overlay, remember ~ picture]
      \bool_if:nT
        {
          \int_compare_p:n { \clist_count:n {#2} = 1 } &&
          \int_compare_p:n { \clist_count:n {#3} = 2 }
        }
        { \__tikzmkpd_case_one: }
      \bool_if:nT
        {
          \int_compare_p:n { \clist_count:n {#2} = 1 } &&
          \int_compare_p:n { \clist_count:n {#3} = 3 }
        }
        { \__tikzmkpd_case_two: }
      \bool_if:nT
        {
          \int_compare_p:n { \clist_count:n {#2} = 2 } &&
          \int_compare_p:n { \clist_count:n {#3} = 2 }
        }
        { \__tikzmkpd_case_three: }
      \bool_if:nT
        {
          \int_compare_p:n { \clist_count:n {#2} = 2 }   &&
          \int_compare_p:n { \clist_count:n {#3} = 3 }
        }
        { \__tikzmkpd_case_four: }
  \end{tikzpicture}
  \group_end:
 }

\NewDocumentCommand \tikzmkpd { O{} m m }
 {
  \tikzmkpd:nnn {#1} {#2} {#3}
 }

\ExplSyntaxOff

\newcommand*{\TkM}[2]{\tikzmarknode{#1}{#2}} % short :)
\setlength{\parindent}{0pt} % just for the example
\pagestyle{empty}

\begin{document}

\section{case 1}

$\TkM{A}{2a^2}(\TkM{B}{b}+\TkM{D}{3q})$%
\tikzmkpd[config-A={raise=12pt}]%
         {A}{B, D}

\section{case 2}

$\TkM{a1}{2p}\left(\TkM{b1}{3q}+\TkM{c1}{4r}+\TkM{d1}{1}\right)$%
\tikzmkpd[config-A={color={gray,gray,gray}, end-yshift={5pt,5pt,5pt}}]%
         {a1}{b1, c1, d1}

\section{case 3}

$(\TkM{X1}{2a^2}+\TkM{X2}{n})(\TkM{Y1}{b}+\TkM{Y2}{3q})$%
\tikzmkpd{X1,X2}{Y1,Y2}

\section{case 4}

$(\TkM{a}{x}-\TkM{b}{2})(\TkM{c}{x^2}+\TkM{d}{2x}+\TkM{e}{4})$
\tikzmkpd[config-B={end-yshift={-15pt,-15pt},color={gray,red}}]{a,b}{c, d, e}

\tikzmkpd[config-A={raise=4pt,color={gray,gray,gray}}]%
         {A}{B, D}
\end{document}

Screenshot


Footnote

  1. For such temporary code, you can use TeX or LaTeX2e commands such as \show or \typeout, as well as LaTeX3 ones such as \tl_show:N or \box_show:N.
  • Great, can you modify your response to fit LaTeX3 naming conventions, I'll adapt the rest of my code (including config-B). Related to (pic cs), in my case it works well, the problem is in tikzmark (github.com/loopspace/tikzmark/issues/9) in my case I have installed the version from git. And it's true, mixing pgfkeys with l3key doesn't seem like a good idea, but, I just want to modify those parameters and not the rest of the tikz options, so from my point of view it's fine. I must study more about the use of variants and expansion in expl3. Gracias Totales :) – Pablo González L Jun 10 '19 at 12:51
  • Please see my last edits. As said there, I don't criticize the use of l3keys for TikZ work; what I'm saying is that you could have completely separated the parsing issue from the TikZ one as part of reducing your example to something minimal. And probably you would have found the problem yourself while doing so, because you would likely have seen then what was happening when preparing the options destined to TikZ. – frougon Jun 10 '19 at 16:20
  • You are absolutely right, I tried to separate and reduce a little the code, but, I do not remain as a MWE (thousand apologies). Debugging the keys and code using \debug_on:n { check-declarations , deprecation } and \tl_show:N along with a more accurate reading of the expl3 documentation should have been my starting point. The yshift-A key doesn't work, I'll add the use of (pic cs) and this solves my problem. Thanks again. – Pablo González L Jun 10 '19 at 16:52
  • 1
    I've fixed yshift-A too. It was a TikZ parsing (syntax) problem—don't ask me why the other quite similar cases work. As can be seen with \tl_show:N, \l_yshift_A_tl was quite fine in \__tikzmkpd_case_one: and \__tikzmkpd_case_two: even before this edit (i.e., all the LaTeX3 option parsing code was working as designed). – frougon Jun 10 '19 at 18:59
  • Now everything is going great....In my first attempt I had left yshift-A inside \__tikzmkpd_define_numeric and also failed, TikZ is a bit dark and difficult to understand in certain points. Until next time and thank you for the programming lesson and debug in LaTeX|expl3. – Pablo González L Jun 10 '19 at 19:43

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.