expl3 add tag
ckm
I'm running into an issue where `\clist_if_in:NnF` doesn't work as expected when used with `prop` keys.

Here's the setup. I have a set of key-value "rules" in a `prop` where the key is supposed to be some identifying keyword and the value is the rule text. Then I have a command `\PrintRules` to print all the defined rules.

Sometimes I want to exclude some rules from the printout. So the `\PrintRules` command takes an optional `clist` of rule keywords to exclude. Here's what I have:

```
\documentclass{article}

\ExplSyntaxOn
% Declare the rules prop and a command to add a rule
\prop_new:N \l_rules_prop
\newcommand{\addrule}[2]{
  \prop_put:Nnn \l_rules_prop {#1} {#2}
}

% Declare the optional exclude clist
\keys_define:nn { rules } {
  exclude .clist_set:N = \l_exclude_clist,
  exclude .value_required:n = true
}

% Command to print the rules
\NewDocumentCommand{\PrintRules}{O{}}{
  \keys_set:nn { rules } {#1}

  % Debug: list excluded rules
  \clist_use:Nn \l_exclude_clist {,~}

  \begin{itemize}
    % Iterate over all defined rules
    \prop_map_inline:Nn \l_rules_prop {
      % If the rule was not excluded, print it
      \clist_if_in:NnF \l_exclude_clist {##1} {
        \item ##2
      }
    }
  \end{itemize}
}
\ExplSyntaxOff

% Add some rules
\addrule{ruleone}{Rule one text.}
\addrule{ruletwo}{Rule two text.}
\addrule{rulethree}{Rule three text.}
\addrule{rulefour}{Rule four text.}

\begin{document}

% Print the rules but exclude rules 2 and 4
\PrintRules[exclude={ruletwo,rulefour}]

\end{document}
```

With the above, I get the following output:
![image.png](/image?hash=d6286d0b011b5c1dc860be7caedfbe1b07af2ffc4772b954df19a8869933008a)
Even though I can see the excluded rules are in the `clist`, for some reason the conditional doesn't see them.

This does work if I limit the keywords to simple numbers:
```
\addrule{1}{Rule one text.}
\addrule{2}{Rule two text.}
\addrule{3}{Rule three text.}
\addrule{4}{Rule four text.}

\PrintRules[exclude={2,4}]
```

![image.png](/image?hash=2d2c12531d0287808eda366c1444eb5778bcb916675627023e3833bdcd6c6573)

But I would rather have descriptive keywords instead.

Does anyone know what I'm doing wrong?
Top Answer
frougon
As written in the first paragraph of the documentation of `prop` variables (chapter *The `l3prop` module Property lists* from [interface3.pdf](https://mirrors.ctan.org/macros/latex/required/l3kernel/interface3.pdf)):

> the ⟨key⟩ is processed using `\tl_to_str:n`

therefore your `\addrule` calls store keys that are entirely comprised of character tokens with category code 12 (this is because they contain no space; space tokens would have category code 10). However, when you add elements to the `\l_exclude_clist` clist variable using `\PrintRules[exclude={ruletwo,rulefour}]`, `ruletwo` and `rulefour` are tokenized using the standard LaTeX2e category codes, which is 11 for letters. As a result, the test `\clist_if_in:NnF` is always false because character tokens of category 11 and 12 are always different, even if they have the same character code.

Your test does work with `\PrintRules[exclude={2,4}]` because `2` and `4` are always tokenized with category code 12 (“other”) under the standard category code régime, which happens to coincide with how the keys are stored in your `prop` variable. It would also work with space tokens, for the same reason with category code 10 instead of 12.

My proposal is to make `\PrintRules`'s `exclude` option store the keys in string-ified form (i.e. after processing by `\tl_to_str:n`, which is an alias for the `\detokenize` TeX primitive) inside a `seq` variable, namely `\l_my_exclude_seq` (don't forget the `my` namespace component, which you should modify to be unique to your package, class or document). This way, category codes of the character tokens stored in the seq variable correspond to how `\prop_put:Nnn` stores the keys, so that the `\seq_if_in:NnF` subsequent comparison will work as intended.

Please note the namespace used for the `\keys_define:nn` and `\keys_set:nn` operations (specific to your `\PrintRules` command) and the **added grouping** inside `\PrintRules`, which avoids the `\keys_set:nn` call to “leak” excluded keys that would otherwise be excluded by further calls to `\PrintRules`.

```
\documentclass{article}

\ExplSyntaxOn

% Declare the rules prop and a command to add a rule
\prop_new:N \l_my_rules_prop

\cs_new_protected:Npn \my_add_rule:nn #1#2
  {
    \prop_put:Nnn \l_my_rules_prop {#1} {#2}
  }

\cs_new_eq:NN \addrule \my_add_rule:nn

\seq_new:N \l_my_exclude_seq

% Option for adding rule keys to the optional exclusion list
\keys_define:nn { my / PrintRules }
  {
    exclude .code:n =
      {
        \clist_map_inline:nn {#1}
          {
            \seq_put_right:Ne \l_my_exclude_seq { \tl_to_str:n {##1} }
          }
      },
    exclude .value_required:n = true,
  }

% Command to print the rules
\NewDocumentCommand { \PrintRules } { O{} }
  {
    \group_begin:
      \keys_set:nn { my / PrintRules } {#1}

      % Debug: list excluded rules
      Excluded~rules:~\seq_use:Nn \l_my_exclude_seq {,~}

      \begin{itemize}
        % Iterate over all defined rules
        \prop_map_inline:Nn \l_my_rules_prop
          {
            % If the rule was not excluded, print it
            \seq_if_in:NnF \l_my_exclude_seq {##1} { \item ##2 }
          }
      \end{itemize}
    \group_end:
  }

\ExplSyntaxOff

\begin{document}

% Add some rules
\addrule{ruleone}{Rule one text.}
\addrule{ruletwo}{Rule two text.}
\addrule{rulethree}{Rule three text.}
\addrule{rulefour}{Rule four text.}

% Print the rules but exclude rules 2 and 4
\PrintRules[exclude={ruletwo,rulefour}]

% Now only exclude rule 3
\PrintRules[exclude={rulethree}]

\end{document}
```

![image.png](/image?hash=e75527cebd13de8ebded9d938f0d336711429f617b360d025bcaaea429b78f88)

One possible improvement, if you think that your “users” are likely to use `\PrintRules[exclude={...}]` with duplicate keys, would be to modify the code for `exclude` so that it only does the `\seq_put_right:Ne` if the key being processed is not already in the exclusion list. However, if the only user is you, I believe the code is fine and easier to read as is.
Answer #2
ckm
As pointed out in the comments, the problem is that the keys in the `prop` are strings, but the `clist` has regular tokens. The comparison function looks at catcodes, which won't match between the two even if the characters do.

I tried processing the clist on intake by adding `\tl_to_str:n` within a map functon, but that still didn't work. I assume the issue is related to `\clist_if_in:NnF` not properly comparing strings.

Eventually I got it to work by converting the `clist` into another prop to hold the excluded keys:

```
\keys_define:nn { rules } {
  exclude .code:n = {
    \prop_clear:N \l_exclude_prop
    % Set a temporary clist (for whitespace stripping)
    \clist_set:Nn \l_tmpa_clist {#1}
    % Add each excluded key
    \clist_map_inline:Nn \l_tmpa_clist {
      \prop_put:Nnn \l_exclude_prop {##1} {}
    }
  },
  exclude .value_required:n = true
}

\NewDocumentCommand{\PrintRules}{O{}}{
  \keys_set:nn { rules } {#1}
  
  \begin{itemize}
    % Iterate over all defined rules
    \prop_map_inline:Nn \l_rules_prop {
      % If the rule was not excluded, print it
      \prop_if_in:NnF \l_exclude_prop {##1} {
        \item ##2
      }
    }
  \end{itemize}
}
```

Enter question or answer id or url (and optionally further answer ids/urls from the same question) from

Separate each id/url with a space. No need to list your own answers; they will be imported automatically.