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} |
upLaTeX で使用したときに,<body>
内に 和文文字 + 半角数字1~9
の並びが存在すると,
Illegal parameter number in definition of \bxkvc_tmpcs:w
というエラーが出るようです。(LuaLaTeXだとこの現象は起こりません。)
再現ソース
%#!uplatex
\documentclass[uplatex,dvipdfmx]{jsarticle}
\usepackage{bxkvcmd}
\newkeyvalcommand{\xTemplate}{}{あ1}
\begin{document}
\xTemplate{}
\end{document}
調査
原因を調べてみたところ,upLaTeX で l3regex を使用したときの \cO
と和文文字のマッチングの問題に起因するようです。
とりあえず
\regex_const:Nn \c_bxkvc_param_regex
{ \cP. (\cO[1-9]) }
の部分を
\regex_const:Nn \c_bxkvc_param_regex
{ \cP. (\cO\relax[1-9]) }
にしてみたらとりあえずコンパイルは通り,テンプレートの動作もしているように見えますが,あまり理解できていないので,副作用の検証など十分にできておりませんが,とりあえずご報告まで。
副作用の検証など十分にできておりませんが
当然の副作用として,「#1
~#9
による通常の引数利用」が機能しなくなりますね……。
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
前提環境
(最近のLaTeXが動くやつならOK)
パッケージオプション
なし。
使用法
\newkeyvalcommand{\命令名}[通常引数個数]{キー定義}{定義本体}
:新しいkey-value命令\命令名
を定義する。\renewkeyvalcommand{\命令名}[通常引数個数]{キー定義}{定義本体}
:既存のkey-value命令\命令名
を再定義する。※(仕様としては)
\命令名
はkey-value命令である必要がある。引数の説明。
通常引数個数
は0~9の範囲の整数で、通常の命令と同様の「key-valueでない引数」の個数を表す。既定値は0(通常引数無し)。キー定義
は以下の形式。([]
は省略可能を示し、[]
自体は書かない。)キー名1[={既定値1}],キー名2[={既定値2}],…
既定値
の既定値は空。キー名
、{既定値}
の周りの空白は無視される。,
や両端の空白を含まないなら{}
は省略可。,
=
|
と半角空白とLaTeXの特殊文字。それ以外の文字は全て使える。定義本体
は通常の命令定義と同じ。|キー名|
の形の文字列は当該のキーの使用を表す。||
の間の文字列がキー定義で指定した有効なキー名でない場合はその文字列自体を表す。#1
~#9
は通常の引数の使用を表す。key-value命令は以下の形式で呼び出す。
キー設定
は以下の形式。(細則は「キー定義」と同じ。)キー名1={値1},キー名2={値2},…
値
を指定しなかった場合は当該キーの既定値が用いられる。N
は通常引数個数
の値。N
が0の場合は通常引数
の部分は無しになる。