4

Just like the title described, we know in tikz, the name of the key color could be omitted, that is

\tikz \draw [color = red] (0,0) -- (1,0); 

equals to

\tikz \draw [red] (0,0) -- (1,0); 

I'd like to achieve this effect via expl3, but I failed. One can get the error message Package xcolor: Undefined color ``'. by uncomment \skyrmion[red, text = {Hello, \LaTeX}] in the following MWE:

\documentclass{article} \usepackage{xcolor} \ExplSyntaxOn \keys_define:nn { skyrmion / mwe } { color .tl_set:N = \l__skyrmion_color_tl, color .initial:n = black, text .tl_set:N = \l__skyrmion_text_tl, unknown .code:n = { \tl_set:Nn \l__skyrmion_color_tl {#1} } } \NewDocumentCommand \skyrmion { O{} } { \keys_set:nn { skyrmion / mwe } {#1} \textcolor {\l__skyrmion_color_tl} {\l__skyrmion_text_tl} } \ExplSyntaxOff \begin{document} \skyrmion[color = red, text = {Hello, \LaTeX}] % \skyrmion[red, text = {Hello, \LaTeX}] \end{document} 

1 Answer 1

7

Your problem is that #1 is the value of a key, not the key name, even in the unknown handler.

The following works better:

\documentclass{article} \usepackage{xcolor} \ExplSyntaxOn \keys_define:nn { skyrmion / mwe } { color .tl_set:N = \l__skyrmion_color_tl, color .initial:n = black, text .tl_set:N = \l__skyrmion_text_tl, unknown .code:n = \tl_set_eq:NN \l__skyrmion_color_tl \l_keys_key_tl } \NewDocumentCommand \skyrmion { O{} } { \keys_set:nn { skyrmion / mwe } {#1} \textcolor {\l__skyrmion_color_tl} {\l__skyrmion_text_tl} } \ExplSyntaxOff \begin{document} \skyrmion[color = red, text = {Hello, \LaTeX}] \skyrmion[red, text = {Hello, \LaTeX}] \end{document} 

However, this is still not foolproof, because you only want to treat keys that didn't get any value this way (now a \skyrmion[foo=bar] would try to use the colour foo, which is obviously wrong). So we need to also check that no value was given:

\documentclass{article} \usepackage{xcolor} \ExplSyntaxOn \msg_new:nnn { skyrmion / mwe } { unknown-key } { The~ key~ \l_keys_key_str\c_space_tl is~ unknown~ and~ is~ being~ ignored. } \keys_define:nn { skyrmion / mwe } { color .tl_set:N = \l__skyrmion_color_tl, color .initial:n = black, text .tl_set:N = \l__skyrmion_text_tl, unknown .code:n = \tl_if_novalue:nTF {#1} { \tl_set_eq:NN \l__skyrmion_color_tl \l_keys_key_tl } { \msg_error:nn { skyrmion / mwe } { unknown-key } } ,unknown .default:V = \c_novalue_tl } \NewDocumentCommand \skyrmion { O{} } { \keys_set:nn { skyrmion / mwe } {#1} \textcolor {\l__skyrmion_color_tl} {\l__skyrmion_text_tl} } \ExplSyntaxOff \begin{document} \skyrmion[color = red, text = {Hello, \LaTeX}] \skyrmion[red, text = {Hello, \LaTeX}] \end{document} 

This however doesn't throw any error on unknown keys. TikZ has a mechanism that determines whether something might be a colour which we can implement ourselves as well. We assume that everything that's a named colour is a colour (duh!) and that everything that contains an ! is a colour expression.

\documentclass{article} \usepackage{xcolor} \ExplSyntaxOn \msg_new:nnn { skyrmion / mwe } { unknown-key } { The~ key~ \l_keys_key_str\c_space_tl is~ unknown~ and~ is~ being~ ignored. } \keys_define:nn { skyrmion / mwe } { color .tl_set:N = \l__skyrmion_color_tl, color .initial:n = black, text .tl_set:N = \l__skyrmion_text_tl, unknown .code:n = \tl_if_novalue:nTF {#1} { \__skyrmion_if_colour:VTF \l_keys_key_str { \tl_set_eq:NN \l__skyrmion_color_tl \l_keys_key_tl } { \msg_error:nn { skyrmion / mwe } { unknown-key } } } { \msg_error:nn { skyrmion / mwe } { unknown-key } } ,unknown .default:V = \c_novalue_tl } \prg_new_protected_conditional:Npnn \__skyrmion_if_colour:n #1 { TF } { \cs_if_exist:cTF { \c_backslash_str color @ #1 } { \prg_return_true: } { \tl_if_in:nnTF {#1} { ! } \prg_return_true: \prg_return_false: } } \prg_generate_conditional_variant:Nnn \__skyrmion_if_colour:n { V } { TF } \NewDocumentCommand \skyrmion { O{} } { \keys_set:nn { skyrmion / mwe } {#1} \textcolor {\l__skyrmion_color_tl} {\l__skyrmion_text_tl} } \ExplSyntaxOff \begin{document} \skyrmion[color = red, text = {Hello, \LaTeX}] \skyrmion[red, text = {Hello, \LaTeX}] \end{document} 

Shameless advertising: Doing the same in expkv (or expkv-def -- disclaimer: I'm the author of these) is a bit easier because in expkv you don't have to test for an unknown key not having a value, it differentiates between the two (in expkv there is a concept separating keys in NoVal-keys and Val-keys). So there this MWE would look like:

\documentclass{article} \usepackage{xcolor,expkv-def} \makeatletter \ekvdefinekeys{skyrmion/mwe} { store text = \skyrmion@text ,store color = \skyrmion@color ,initial color = black % #2 is the undetokenized key name of an unknown NoVal-key ,unknown noval = \edef\skyrmion@color{\unexpanded{#2}} } \NewDocumentCommand \skyrmion { O{} } {% \ekvset{skyrmion/mwe}{#1}% \textcolor{\skyrmion@color}{\skyrmion@text}% } \makeatother \begin{document} \skyrmion[color = red, text = {Hello, \LaTeX}] \skyrmion[red, text = {Hello, \LaTeX}] \end{document} 

I used \makeatletter...\makeatother instead of L3-syntax here because expkv is not part of L3, and while store is 100% compatible with .tl_set:N this is sort of an implementation detail and nothing I can document, so to avoid any potential (however unlikely) conflict in the future, this doesn't rely on that fact.

Another variant using expkv-cs instead (because this MWE is really simple and defining the macro using it is kind of straight forward):

\documentclass{article} \usepackage{xcolor,expkv-cs} \makeatletter \ekvcSplit\skyrmion@kv { color = black ,text = } {\textcolor{#1}{#2}} \ekvdefunknownNoVal{\string\skyrmion@kv}{\ekvcPass\skyrmion@kv{color}{#2}} % only necessary for the optional argument grabbing \NewDocumentCommand \skyrmion { O{} } {\skyrmion@kv{#1}} \makeatother \begin{document} \skyrmion[color = red, text = {Hello, \LaTeX}] \skyrmion[red, text = {Hello, \LaTeX}] \end{document} 

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.