Created
January 4, 2021 10:45
-
-
Save zr-tex8r/71c91cdbc565fbd669dd996c00fcf234 to your computer and use it in GitHub Desktop.
LaTeX:key-value型の引数指定をもつユーザ命令を(LaTeXレベルで)定義する
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% | |
%% This is file 'bxkvcmd.sty'. | |
%% | |
%% Copyright (c) 2021 Takayuki YATO (aka. "ZR") | |
%% GitHub: https://github.com/zr-tex8r | |
%% Twitter: @zr_tex8r | |
%% | |
%% This package is distributed under the MIT License. | |
%% | |
%% package declarations | |
\RequirePackage{expl3,xparse} | |
\ProvidesExplPackage {bxkvcmd} {2021/01/02} {0.2.0} | |
{User commands with key-value interface} | |
%--------------------------------------- general | |
%% internal parameters | |
\int_const:Nn \c_bxkvc_absize_int { 9 } | |
% A kv command is represented by a nullary function of the following form: | |
% ---- | |
% { | |
% \bxkvc_invoke:nnnn | |
% { <number_of_keys> } { <number_of_arguments> } | |
% { | |
% \do { <key_1> } { <default_value_1> } | |
% \do { <key_2> } { <default_value_2> } | |
% ... | |
% } | |
% { <command_body> } | |
% ---- | |
% In the command body, keys and arguments are represented by "uninterpreted | |
% parameter notation", which is a sequel of '#' token followed by a digit | |
% token. (NOte that the body contains no "real" parameter tokens, since | |
% the function is nullary.) | |
% - 1st--9th keys are represented by #1--#9 (1st level) | |
% - 10th--18th keys are represented by ##1--##9 (2nd level) | |
% - 19th--27th keys are represented by ####1--####9 (3rd level) | |
% - And so on. When all keys end up to use N levels, then (ordinary) | |
% arguments employ the (N+1)th level. For example, when a command | |
% has 25 keys and two arguments, then keys use #1 through ####7 | |
% and arguments use ########1 and ########2 (4th level). | |
%% variables | |
\bool_new:N \l_bxkvc_ok_bool | |
\clist_new:N \l_bxkvc_tmpa_clist | |
\clist_new:N \l_bxkvc_tmpb_clist | |
\int_new:N \l_bxkvc_tmpa_int | |
\int_new:N \l_bxkvc_nkeys_int | |
\int_new:N \l_bxkvc_nargs_int | |
\prop_new:N \l_bxkvc_values_prop | |
\tl_new:N \l_bxkvc_tmpa_tl | |
\tl_new:N \l_bxkvc_tmpb_tl | |
\tl_new:N \l_bxkvc_tmpc_tl | |
\tl_new:N \l_bxkvc_definer_tl | |
\tl_new:N \l_bxkvc_keyinfo_tl | |
\int_new:N \l_bxkvc_cbs_int | |
\cs_new:Npn \bxkvc_tmpcs:w {} | |
%--------------------------------------- user interface | |
%%<*> \newkeyvalcommand \CS [<#arguments>] {<key-info>} {<body>} | |
\NewDocumentCommand \newkeyvalcommand { m O{0} m +m } | |
{ | |
\bxkvc_check_new_cmd:nT {#1} | |
{ | |
\tl_set:Nn \l_bxkvc_definer_tl { \NewDocumentCommand #1 {} } | |
\int_set:Nn \l_bxkvc_nargs_int {#2} | |
\tl_set:Nn \l_bxkvc_keyinfo_tl {#3} | |
\tl_set:Nn \l_bxkvc_body_tl {#4} | |
\bxkvc_declare: | |
} | |
} | |
%%<*> \renewkeyvalcommand \CS [<#arguments>] {<key-info>} {<body>} | |
\NewDocumentCommand \renewkeyvalcommand { m O{0} m +m } | |
{ | |
\bxkvc_check_renew_cmd:nT {#1} | |
{ | |
\tl_set:Nn \l_bxkvc_definer_tl { \RenewDocumentCommand #1 {} } | |
\int_set:Nn \l_bxkvc_nargs_int {#2} | |
\tl_set:Nn \l_bxkvc_keyinfo tl {#3} | |
\tl_set:Nn \l_bxkvc_body_tl {#4} | |
\bxkvc_declare: | |
} | |
} | |
%--------------------------------------- messages | |
\msg_new:nnnn { bxkvc } { bad-command } | |
{ Command~not~valid. } | |
{ | |
You~have~used~#1~with~something~not~counted~as~a~valid~command. | |
} | |
\msg_new:nnnn { bxkvc } { already-defined } | |
{ Command~'#1'~already~defined. } | |
{ | |
You~have~used~#2~with~a~command~that~already~has~a~definition. | |
} | |
\msg_new:nnnn { bxkvc } { not-kv-command } | |
{ Command~'#1'~is~not~an~existing~key-value~command. } | |
{ | |
You~have~used~#2~with~a~command~that~is~not~defined~as~ | |
a~key-value~command. | |
} | |
\msg_new:nnnn { bxkvc } { bad-key } | |
{ Bad~key~name~'#1'. } | |
{ | |
A~key~name~must~not~contain~a~space~or~a~special~character. | |
} | |
%--------------------------------------- command declaration | |
%% variables | |
\regex_new:N \l_bxkvc_keys_regex | |
%% constants | |
\bool_if:nTF { \sys_if_engine_ptex_p: || \sys_if_engine_uptex_p: } | |
{ | |
\regex_const:Nn \c_bxkvc_key_only_regex | |
{ ^ (\c[OL][^=,\|] | [^\x00-\xff])+ \z } | |
\regex_const:Nn \c_bxkvc_key_value_regex | |
{ ^ (\c[OL][^=,\|] | [^\x00-\xff])+ \ * = } | |
} | |
{ | |
\regex_const:Nn \c_bxkvc_key_only_regex | |
{ ^ \c[OL][^=,\|]+ \z } | |
\regex_const:Nn \c_bxkvc_key_value_regex | |
{ ^ \c[OL][^=,\|]+ \ * = } | |
} | |
\regex_const:Nn \c_bxkvc_rxmeta_regex | |
{ [\!\"\'\(\)\*\+\-\.\/\:\;\<\=\>\?\@\[\]\`\|] } | |
\regex_const:Nn \c_bxkvc_param_regex | |
{ \cP. (\cO[1-9]) } | |
%% \bxkvc_check_[re]new_cd:nT {<command>} {T} | |
\prg_new_conditional:Nnn \bxkvc_check_new_cmd:n { T } | |
{ \bxkvc_check_cmd_gen:NnN \newkeyvalcommand {#1} \bxkvc_check_new_cmd_aux:NN } | |
\prg_new_conditional:Nnn \bxkvc_check_renew_cmd:n { T } | |
{ \bxkvc_check_cmd_gen:NnN \renewkeyvalcommand {#1} \bxkvc_check_renew_cmd_aux:NN } | |
\cs_new:Nn \bxkvc_check_cmd_gen:NnN | |
{ | |
\bool_lazy_and:nnTF | |
{ \tl_if_single_token_p:n {#2} } | |
{ \token_if_cs_p:N #2 } | |
{#3#1#2} | |
{ | |
\msg_error:nnx { bxkvc } { bad-command } { \token_to_str:N #1 } | |
\prg_return_false: | |
} | |
} | |
\cs_new:Nn \bxkvc_check_new_cmd_aux:NN | |
{ | |
\cs_if_exist:NTF #2 | |
{ | |
\msg_error:nnxx { bxkvc } { already-defined } { \token_to_str:N #2 } | |
{ \token_to_str:N #1 } | |
\prg_return_false: | |
} | |
{ \prg_return_true: } | |
} | |
\cs_new:Nn \bxkvc_check_renew_cmd_aux:NN | |
{ | |
\cs_if_exist:NTF #2 | |
{ \prg_return_true: } % TODO | |
{ | |
\msg_error:nnxx { bxkvc } { not-kv-command } { \token_to_str:N #2 } | |
{ \token_to_str:N #1 } | |
\prg_return_false: | |
} | |
} | |
\cs_new:Nn \bxkvc_declare: | |
{ | |
\bool_set_true:N \l_bxkvc_ok_bool | |
\bxkvc_parse_keyinfo: | |
\bool_if:NT \l_bxkvc_ok_bool | |
{ | |
\int_set:Nn \l_bxkvc_nkeys_int { \prop_count:N \l_bxkvc_values_prop } | |
\bxkvc_render_keyinfo: | |
\bxkvc_cook_cmd_body: | |
\bxkvc_define_command: | |
} | |
} | |
\cs_new:Nn \bxkvc_parse_keyinfo: | |
{ | |
\clist_set:NV \l_bxkvc_tmpa_clist \l_bxkvc_keyinfo_tl | |
\clist_clear:N \l_bxkvc_tmpb_clist | |
\clist_map_function:NN \l_bxkvc_tmpa_clist \bxkvc_parse_keyinfo_aux:n | |
\exp_args:NNV \prop_set_from_keyval:Nn \l_bxkvc_values_prop \l_bxkvc_tmpb_clist | |
} | |
\cs_new:Nn \bxkvc_parse_keyinfo_aux:n | |
{ | |
\regex_match:NnTF \c_bxkvc_key_only_regex {#1} | |
{ \clist_put_right:Nn \l_bxkvc_tmpb_clist { #1 = {} } } | |
{ | |
\regex_match:NnTF \c_bxkvc_key_value_regex {#1} | |
{ \clist_put_right:Nn \l_bxkvc_tmpb_clist {#1} } | |
{ | |
\msg_error:nnn { bxkvc } { bad-key } {#1} | |
} | |
} | |
} | |
\cs_new:Nn \bxkvc_render_keyinfo: | |
{ | |
\tl_clear:N \l_bxkvc_tmpa_tl | |
\tl_set:Nn \l_bxkvc_tmpb_tl { #### } % double '#' | |
\int_zero:N \l_bxkvc_tmpa_int | |
\clist_clear:N \l_bxkvc_tmpa_clist | |
\prop_map_function:NN \l_bxkvc_values_prop \bxkvc_render_keyinfo_aux:nn | |
\tl_set_eq:NN \l_bxkvc_keyinfo_tl \l_bxkvc_tmpa_tl | |
% construct regex for key names | |
\exp_args:NNx \regex_set:Nn \l_bxkvc_keys_regex | |
{ | |
\exp_not:n { \|( } | |
\clist_use:Nn \l_bxkvc_tmpa_clist { | } | |
\exp_not:n { )\| } | |
} | |
} | |
\cs_new:Nn \bxkvc_render_keyinfo_aux:nn | |
{ | |
\tl_put_right:Nn \l_bxkvc_tmpa_tl { \do {#1} {#2} } | |
% create a parameter notation (\l_bxkvc_tmpc_tl) | |
\int_incr:N \l_bxkvc_tmpa_int | |
\int_compare:nNnT { \l_bxkvc_tmpa_int } > { \c_bxkvc_absize_int } | |
{ | |
\int_set:Nn \l_bxkvc_tmpa_int { 1 } | |
\tl_put_right:NV \l_bxkvc_tmpb_tl \l_bxkvc_tmpb_tl % doubling '#' | |
} | |
\tl_set_eq:NN \l_bxkvc_tmpc_tl \l_bxkvc_tmpb_tl | |
\tl_put_right:Nx \l_bxkvc_tmpc_tl { \int_to_arabic:n { \l_bxkvc_tmpa_int } } | |
% set replacer variables | |
\tl_clear_new:c { l_bxkvc_R_ #1 _tl } | |
\tl_set_eq:cN { l_bxkvc_R_ #1 _tl } \l_bxkvc_tmpc_tl | |
% construct regex for key names | |
\tl_set:Nn \l_bxkvc_tmpc_tl {#1} | |
\regex_replace_all:NnN \c_bxkvc_rxmeta_regex { \\ \0 } \l_bxkvc_tmpc_tl | |
\clist_put_right:NV \l_bxkvc_tmpa_clist \l_bxkvc_tmpc_tl | |
} | |
\cs_new:Nn \bxkvc_cook_cmd_body: | |
{ | |
\tl_set:Nn \l_bxkvc_tmpb_tl { #### } | |
\int_step_inline:nnnn { 1 } { \c_bxkvc_absize_int } { \l_bxkvc_nkeys_int } | |
{ \tl_put_right:NV \l_bxkvc_tmpb_tl \l_bxkvc_tmpb_tl } | |
\regex_replace_all:NnN \c_bxkvc_param_regex { \u{l_bxkvc_tmpb_tl} \1 } | |
\l_bxkvc_body_tl | |
\regex_replace_all:NnN \l_bxkvc_keys_regex { \u{l_bxkvc_R_ \1 _tl} } | |
\l_bxkvc_body_tl | |
} | |
\cs_new:Nn \bxkvc_define_command: | |
{ | |
\tl_set:Nx \l_bxkvc_tmpa_tl | |
{ | |
\exp_not:V \l_bxkvc_definer_tl | |
{ | |
\exp_not:N \bxkvc_invoke:nnnn | |
{ \int_to_arabic:n { \l_bxkvc_nkeys_int } } | |
{ \int_to_arabic:n { \l_bxkvc_nargs_int } } | |
{ \exp_not:V \l_bxkvc_keyinfo_tl } | |
{ \exp_not:V \l_bxkvc_body_tl } | |
} | |
} | |
\tl_use:N \l_bxkvc_tmpa_tl | |
} | |
%--------------------------------------- command invocation | |
%% variables | |
\tl_new:N \l_bxkvc_body_tl | |
\tl_new:N \l_bxkvc_keys_tl | |
\tl_new:N \l_bxkvc_org_do_tl | |
%% constants | |
\tl_const:Nn \c_bxkvc_para_seq_tl | |
{ | |
\if_case:w \l_bxkvc_cbs_int | |
\or: \exp_not:n {#1} | |
\or: \exp_not:n {#1#2} | |
\or: \exp_not:n {#1#2#3} | |
\or: \exp_not:n {#1#2#3#4} | |
\or: \exp_not:n {#1#2#3#4#5} | |
\or: \exp_not:n {#1#2#3#4#5#6} | |
\or: \exp_not:n {#1#2#3#4#5#6#7} | |
\or: \exp_not:n {#1#2#3#4#5#6#7#8} | |
\or: \exp_not:n {#1#2#3#4#5#6#7#8#9} | |
\fi: | |
} | |
%% \bxkvc_invoke:nnnn {<#keys>} {<#arguments>} {<key-info>} {<body>} | |
\cs_new:Nn \bxkvc_invoke:nnnn | |
{ | |
\int_set:Nn \l_bxkvc_nkeys_int {#1} | |
\int_set:Nn \l_bxkvc_nargs_int {#2} | |
\tl_set:Nn \l_bxkvc_keyinfo_tl {#3} | |
\tl_set:Nn \l_bxkvc_body_tl {#4} | |
\bxkvc_read_values:n | |
} | |
\cs_new:Nn \bxkvc_read_values:n | |
{ | |
\prop_set_from_keyval:Nn \l_bxkvc_values_prop {#1} | |
\tl_set_eq:NN \l_bxkvc_org_do_tl \do | |
\tl_set:Nn \do { \bxkvc_read_value_do:nn } | |
\tl_use:N \l_bxkvc_keyinfo_tl | |
\tl_set:Nn \do { \bxkvc_gather_key_do:nn } | |
\tl_set:Nx \l_bxkvc_keys_tl { \tl_use:N \l_bxkvc_keyinfo_tl } | |
\tl_set_eq:NN \do \l_bxkvc_org_do_tl | |
\bxkvc_invoke_main: | |
} | |
\cs_new:Nn \bxkvc_read_value_do:nn | |
{ | |
\tl_if_empty:nF {#2} | |
{ \prop_put_if_new:Nnn \l_bxkvc_values_prop {#1} {#2} } | |
} | |
\cs_new:Nn \bxkvc_gather_key_do:nn | |
{ {#1} } | |
\cs_new:Nn \bxkvc_invoke_main: | |
{ | |
\bxkvc_bind_keys: | |
\bxkvc_create_func: | |
\tl_clear:N \l_bxkvc_tmpa_tl | |
\tl_clear:N \l_bxkvc_tmpb_tl | |
\tl_clear:N \l_bxkvc_tmpc_tl | |
\prop_clear:N \l_bxkvc_values_prop | |
\bxkvc_tmpcs:w | |
} | |
\cs_new:Nn \bxkvc_bind_keys: | |
{ | |
\int_step_function:nnnN { 1 } { \c_bxkvc_absize_int } { \l_bxkvc_nkeys_int } | |
\bxkvc_bind_key_block:n | |
} | |
\cs_new:Nn \bxkvc_bind_key_block:n | |
{ | |
\int_set:Nn \l_bxkvc_cbs_int | |
{ \int_min:nn { \c_bxkvc_absize_int } { \l_bxkvc_nkeys_int - #1 + 1 } } | |
\tl_set:Nf \l_bxkvc_tmpa_tl | |
{ \tl_range:Nnn \l_bxkvc_keys_tl {#1} { #1 + \l_bxkvc_cbs_int - 1} } | |
\tl_clear:N \l_bxkvc_tmpb_tl | |
\tl_map_function:NN \l_bxkvc_tmpa_tl \bxkvc_bind_key_block_aux:n | |
\tl_set:Nx \l_bxkvc_tmpa_tl | |
{ | |
\exp_not:n { \cs_set:Npn \bxkvc_tmpcs:w } | |
\c_bxkvc_para_seq_tl | |
{ \exp_not:V \l_bxkvc_body_tl } | |
} | |
\tl_use:N \l_bxkvc_tmpa_tl | |
\tl_set:Nx \l_bxkvc_tmpa_tl | |
{ | |
\exp_not:n { \tl_set:No \l_bxkvc_body_tl } | |
{ \exp_not:N \bxkvc_tmpcs:w \exp_not:V \l_bxkvc_tmpb_tl } | |
} | |
\tl_use:N \l_bxkvc_tmpa_tl | |
} | |
\cs_new:Nn \bxkvc_bind_key_block_aux:n | |
{ | |
\prop_get:NnNTF \l_bxkvc_values_prop {#1} \l_bxkvc_tmpc_tl | |
{ \tl_put_right:Nx \l_bxkvc_tmpb_tl { { \exp_not:V \l_bxkvc_tmpc_tl } } } | |
{ \tl_put_right:Nn \l_bxkvc_tmpb_tl { {} } } | |
} | |
\cs_new:Nn \bxkvc_create_func: | |
{ | |
\int_set:Nn \l_bxkvc_cbs_int \l_bxkvc_nargs_int | |
\tl_set:Nx \l_bxkvc_tmpa_tl | |
{ | |
\exp_not:n { \cs_set:Npn \bxkvc_tmpcs:w } | |
\c_bxkvc_para_seq_tl | |
{ \exp_not:V \l_bxkvc_body_tl } | |
} | |
\tl_use:N \l_bxkvc_tmpa_tl | |
} | |
%--------------------------------------- all done | |
% EOF |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%#!uplatex | |
\documentclass[uplatex,dvipdfmx,a6paper]{jsarticle} | |
\usepackage{bxkvcmd}% key-valueしたい!! | |
\usepackage{scsnowman} | |
\newkeyvalcommand{\xTemplate}{ | |
姓, 名, 好きなマフラーの色=red % '='以降は既定値 | |
}{\newpage % '|...|'がそのキーに対する値に置き換わる | |
\noindent |姓| 様\par\medskip | |
先日、|姓|様の技術ブログを拝見し、大変興味深く感じました。\par | |
つきましては、弊社の提供するマフラー付きゆきだるまを | |
ご鑑賞いただければ幸いです。\par | |
\begin{center} | |
\scsnowman[muffler=|好きなマフラーの色|, | |
scale=12, hat, arms, snow, buttons] | |
\end{center} | |
} | |
\begin{document} | |
% key-valueのリストを引数にとる | |
\xTemplate{姓=黒☃, 名=大地, 好きなマフラーの色=blue} | |
\end{document} |
副作用の検証など十分にできておりませんが
当然の副作用として,「#1
~#9
による通常の引数利用」が機能しなくなりますね……。
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
upLaTeX で使用したときに,
<body>
内に和文文字 + 半角数字1~9
の並びが存在すると,Illegal parameter number in definition of \bxkvc_tmpcs:w
というエラーが出るようです。(LuaLaTeXだとこの現象は起こりません。)
再現ソース
調査
原因を調べてみたところ,upLaTeX で l3regex を使用したときの
\cO
と和文文字のマッチングの問題に起因するようです。とりあえず
の部分を
にしてみたらとりあえずコンパイルは通り,テンプレートの動作もしているように見えますが,あまり理解できていないので,副作用の検証など十分にできておりませんが,とりあえずご報告まで。