% File: jsonparse.sty % Copyright 2024 Jasper Habicht (mail(at)jasperhabicht.de). % % This work may be distributed and/or modified under the % conditions of the LaTeX Project Public License version 1.3c, % available at http://www.latex-project.org/lppl/. % % This file is part of the `jsonparse' package (The Work in LPPL) % and all files in that bundle must be distributed together. % % This work has the LPPL maintenance status `maintained'. % \ProvidesExplPackage {jsonparse} {2024-11-05} {0.9.7} {A handy way to parse, store and access JSON data from files or strings in LaTeX documents} \bool_new:N \l__jsonparse_debug_mode_bool \keys_define:nn { jsonparse / global } { debug .bool_set:N = \l__jsonparse_debug_mode_bool , debug .default:n = { true } , debug .initial:n = { false } } \ProcessKeyOptions [ jsonparse / global ] \msg_new:nnn { jsonparse } { debug-info } { #1 } \msg_new:nnn { jsonparse } { parsing-error } { \msg_error_text:n { jsonparse } \iow_newline: Could ~ not ~ parse ~ JSON. \iow_newline: Parsing ~ error ~ at ~ key ~ `#1` ~ with ~ value ~ `#2`. } \msg_new:nnn { jsonparse } { nested-non-expandable } { \msg_error_text:n { jsonparse } \iow_newline: Nested ~ use ~ of ~ \token_to_str:N \JSONParseValue \c_space_tl not ~ allowed. \iow_newline: Use ~ \token_to_str:N \JSONParseExpandableValue \c_space_tl instead. } \msg_new:nnn { jsonparse } { file-not-found } { \msg_error_text:n { jsonparse } \iow_newline: Could ~ not ~ find ~ file ~ #1. } \msg_new:nnn { jsonparse } { file-exists } { \msg_error_text:n { jsonparse } \iow_newline: File ~ #1 ~ already ~ existing. } \msg_new:nnn { jsonparse } { escape-in-key } { \msg_error_text:n { jsonparse } \iow_newline: Invalid ~ escape ~ sequence ~ #1 ~ in ~ key. } \msg_new:nnn { jsonparse } { escape-char-not-found } { \msg_error_text:n { jsonparse } \iow_newline: Escape ~ character ~ #1 ~ not ~ found. } \msg_new:nnn { jsonparse } { unknown-key } { \msg_warning_text:n { jsonparse } \iow_newline: Ignoring ~ unknown ~ key: ~ #1. } \msg_new:nnn { jsonparse } { saving-external } { \msg_info_text:n { jsonparse } \iow_newline: Saving ~ to ~ external ~ file: ~ #1. } \msg_new:nnn { jsonparse } { loading-external } { \msg_info_text:n { jsonparse } \iow_newline: Loading ~ from ~ external ~ file: ~ #1. } \msg_new:nnn { jsonparse } { not-array-item } { \msg_info_text:n { jsonparse } \iow_newline: Key ~ does ~ not ~ represent ~ an ~ array ~ item: ~ #1 } % === \str_new:N \l_jsonparse_externalize_prefix_str \str_new:N \l_jsonparse_current_prop_str \tl_new:N \l__jsonparse_externalize_file_name_tl \tl_new:N \l__jsonparse_externalize_file_data_tl \str_new:N \l__jsonparse_child_sep_str \str_new:N \l__jsonparse_array_sep_left_str \str_new:N \l__jsonparse_array_sep_right_str \str_new:N \l__jsonparse_true_str \str_new:N \l__jsonparse_false_str \str_new:N \l__jsonparse_null_str \bool_new:N \l__jsonparse_zero_based_bool \bool_new:N \l__jsonparse_check_num_bool \bool_new:N \l__jsonparse_rescan_bool \bool_new:N \l__jsonparse_externalize_bool \str_new:N \l__jsonparse_backspace_str \str_new:N \l__jsonparse_formfeed_str \str_new:N \l__jsonparse_linefeed_str \str_new:N \l__jsonparse_carriage_return_str \str_new:N \l__jsonparse_horizontal_tab_str \clist_new:N \l__jsonparse_unused_keys_clist \clist_const:Nn \c__jsonparse_escape_tex_chars_clist { number_sign , dollar_sign , percent_sign , ampersand , circumflex_accent , low_line , tilde } \str_new:N \l__jsonparse_escape_temp_str \clist_map_inline:Nn \c__jsonparse_escape_tex_chars_clist { \bool_new:c { l__jsonparse_escape_ #1 _bool } } \keys_define:nn { jsonparse / parse } { externalize .bool_set:N = \l__jsonparse_externalize_bool , externalize .default:n = { true } , externalize .initial:n = { false } , externalize ~ prefix .str_set:N = \l_jsonparse_externalize_prefix_str , externalize ~ prefix .initial:n = { } , externalize ~ file ~ name .tl_set:N = \l__jsonparse_externalize_file_name_tl , externalize ~ file ~ name .initial:n = { \l_jsonparse_externalize_prefix_str \c_sys_jobname_str \c_underscore_str \l_jsonparse_current_prop_str } , separator .code:n = { \keys_set:nn { jsonparse / parse / separator } {#1} } , separator / child .str_set:N = \l__jsonparse_child_sep_str , separator / child .initial:n = { . } , separator / array ~ left .str_set:N = \l__jsonparse_array_sep_left_str , separator / array ~ left .initial:n = { [ } , separator / array ~ right .str_set:N = \l__jsonparse_array_sep_right_str , separator / array ~ right .initial:n = { ] } , zero-based .bool_set:N = \l__jsonparse_zero_based_bool , zero-based .default:n = { true } , zero-based .initial:n = { true } , check ~ num .bool_set:N = \l__jsonparse_check_num_bool , check ~ num .default:n = { true } , check ~ num .initial:n = { true } , replace .code:n = { \keys_set:nn { jsonparse / parse / replace } {#1} } , replace / true .str_set:N = \l__jsonparse_true_str , replace / true .initial:n = { true } , replace / false .str_set:N = \l__jsonparse_false_str , replace / false .initial:n = { false } , replace / null .str_set:N = \l__jsonparse_null_str , replace / null .initial:n = { null } } \keys_define:nn { jsonparse / typeset } { replace .code:n = { \keys_set:nn { jsonparse / typeset / replace } {#1} } , replace / backspace .str_set:N = \l__jsonparse_backspace_str , replace / backspace .initial:n = { ~ } , replace / formfeed .str_set:N = \l__jsonparse_formfeed_str , replace / formfeed .initial:n = { ~ } , replace / linefeed .str_set:N = \l__jsonparse_linefeed_str , replace / linefeed .initial:n = { ~ } , replace / carriage ~ return .str_set:N = \l__jsonparse_carriage_return_str , replace / carriage ~ return .initial:n = { ~ } , replace / horizontal ~ tab .str_set:N = \l__jsonparse_horizontal_tab_str , replace / horizontal ~ tab .initial:n = { ~ } , escape .code:n = { \str_case:nnF {#1} { { all } { \clist_map_inline:Nn \c__jsonparse_escape_tex_chars_clist { \bool_set_true:c { l__jsonparse_escape_ ##1 _bool } } } { none } { \clist_map_inline:Nn \c__jsonparse_escape_tex_chars_clist { \bool_set_false:c { l__jsonparse_escape_ ##1 _bool } } } } { \clist_map_inline:nn {#1} { \str_set:Nn \l__jsonparse_escape_temp_str {##1} \str_replace_all:Nnn \l__jsonparse_escape_temp_str { ~ } { _ } \bool_if_exist:cTF { l__jsonparse_escape_ \l__jsonparse_escape_temp_str _bool } { \bool_set_true:c { l__jsonparse_escape_ \l__jsonparse_escape_temp_str _bool } } { \str_case:nnF {##1} { { hash } { \bool_set_true:c { l__jsonparse_escape_number_sign_bool } } { dollar } { \bool_set_true:c { l__jsonparse_escape_dollar_sign_bool } } { percent } { \bool_set_true:c { l__jsonparse_escape_percent_sign_bool } } { circumflex } { \bool_set_true:c { l__jsonparse_escape_circumflex_accent_bool } } { underscore } { \bool_set_true:c { l__jsonparse_escape_low_line_bool } } } { \msg_error:nno { jsonparse } { escape-char-not-found } {##1} } } } } } , escape .groups:n = { output } , rescan .bool_set:N = \l__jsonparse_rescan_bool , rescan .default:n = { true } , rescan .initial:n = { true } , rescan .groups:n = { output } } \cs_new:Npn \__jsonparse_warning_unused_keys: { \clist_map_inline:Nn \l__jsonparse_unused_keys_clist { \msg_warning:nne { jsonparse } { unknown-key } { ##1 } } } \NewDocumentCommand { \JSONParseSet } { m } { \keys_set_known:nnN { jsonparse / global } {#1} \l__jsonparse_unused_keys_clist \keys_set_known:noN { jsonparse / parse } { \l__jsonparse_unused_keys_clist } \l__jsonparse_unused_keys_clist \keys_set_known:noN { jsonparse / typeset } { \l__jsonparse_unused_keys_clist } \l__jsonparse_unused_keys_clist \__jsonparse_warning_unused_keys: } % === \cs_if_exist:NF \str_casefold:n { \cs_new:Npn \str_casefold:n { \str_foldcase:n } } \cs_generate_variant:Nn \file_input:n { e } \cs_generate_variant:Nn \tl_gset:Nn { Ne , ce } \cs_generate_variant:Nn \tl_gset_rescan:Nnn { Nne } \cs_generate_variant:Nn \tl_range:nnn { nne , nen } \cs_generate_variant:Nn \tl_range:Nnn { Nne , Nen } \cs_generate_variant:Nn \tl_remove_once:Nn { NV } \cs_generate_variant:Nn \tl_replace_all:Nnn { Non , Noe } \cs_generate_variant:Nn \tl_replace_once:Nnn { Non } \cs_generate_variant:Nn \tl_rescan:nn { no , ne } \cs_generate_variant:Nn \tl_set:Nn { Ne } \cs_generate_variant:Nn \tl_trim_spaces:n { e } \cs_generate_variant:Nn \str_case_e:nn { en } \cs_generate_variant:Nn \str_casefold:n { o } \cs_generate_variant:Nn \str_head_ignore_spaces:n { o } \cs_generate_variant:Nn \str_set:Nn { Ne } \cs_generate_variant:Nn \clist_const:Nn { Ne } \cs_generate_variant:Nn \seq_put_right:Nn { Ne } \cs_generate_variant:Nn \prop_gput:Nnn { Nee } \cs_generate_variant:Nn \prop_item:Nn { Ne , ce } \cs_generate_variant:Nn \prop_put:Nnn { Nen , Nee } \cs_generate_variant:Nn \keys_set_known:nnN { noN } \cs_generate_variant:Nn \iow_now:Nn { Ne } \cs_generate_variant:Nn \iow_open:Nn { Ne } \cs_generate_variant:Nn \msg_error:nnn { nno } \cs_generate_variant:Nn \msg_error:nnnn { nnoo } \cs_generate_variant:Nn \msg_info:nnn { nne } \cs_generate_variant:Nn \msg_log:nnn { nne } \cs_generate_variant:Nn \msg_warning:nnn { nne } \prg_generate_conditional_variant:Nnn \file_if_exist:n { e } { T , TF } \prg_generate_conditional_variant:Nnn \tl_if_head_eq_charcode:nN { oN } { T , TF } \prg_generate_conditional_variant:Nnn \tl_if_in:nn { nV } { F } \prg_generate_conditional_variant:Nnn \seq_if_in:Nn { Ne } { F } \prg_generate_conditional_variant:Nnn \str_if_eq:nn { en , eV } { T , F , TF } \prg_generate_conditional_variant:Nnn \str_if_eq:nn { Vn } { p } \prop_new:N \g_jsonparse_entries_prop \prop_new:N \l__jsonparse_temp_prop \tl_new:N \g__jsonparse_json_tl \tl_new:N \l__jsonparse_input_tl \tl_new:N \l__jsonparse_temp_tl \tl_new:N \l__jsonparse_prefix_tl \tl_new:N \l__jsonparse_key_tl \tl_new:N \l__jsonparse_val_tl \tl_new:N \l__jsonparse_object_array_key_tl \tl_new:N \l__jsonparse_object_array_val_tl \tl_new:N \l__jsonparse_remainder_tl \int_new:N \l__jsonparse_array_index_int \int_new:N \l__jsonparse_array_count_int \int_new:N \l__jsonparse_array_count_aux_int \seq_new:N \l__jsonparse_array_count_last_seq \str_new:N \l__jsonparse_filter_key_str \int_new:N \l__jsonparse_array_keys_index_int \tl_new:N \l__jsonparse_array_keys_index_roman_tl \str_new:N \l__jsonparse_array_values_key_str \tl_new:N \l__jsonparse_array_values_insert_tl \clist_new:N \l__jsonparse_array_map_keys_clist \str_new:N \l__jsonparse_array_map_function_str \bool_new:N \l__jsonparse_prop_map_first_bool \bool_new:N \l__jsonparse_externalize_load_bool \iow_new:N \g__jsonparse_externalize_iow % === \cctab_const:Nn \c__jsonparse_json_escape_cctab { \cctab_select:N \c_str_cctab \char_set_catcode_space:n { 9 } \char_set_catcode_escape:n { 92 } \bool_lazy_or:nnF { \sys_if_engine_xetex_p: } { \sys_if_engine_luatex_p: } { \int_step_function:nnN { 128 } { 255 } \char_set_catcode_active:n } } % === \tl_new:N \l__jsonparse_num_input_tl \tl_new:N \l__jsonparse_num_remainder_tl \tl_new:N \l__jsonparse_num_input_last_tl \bool_new:N \l__jsonparse_num_bool \bool_new:N \l__jsonparse_num_zero_seen_bool \bool_new:N \l__jsonparse_num_plus_minus_seen_bool \bool_new:N \l__jsonparse_num_fraction_seen_bool \bool_new:N \l__jsonparse_num_exponent_seen_bool \clist_const:Nn \c__jsonparse_num_digits_clist { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } \clist_const:Ne \c__jsonparse_num_others_clist { \tl_to_str:n { + } , \tl_to_str:n { - } , \tl_to_str:n { . } , \tl_to_str:n { e } , \tl_to_str:n { E } } \prg_new_conditional:Npnn \jsonparse_if_num:n #1 { p , T , F , TF } { \bool_set_true:N \l__jsonparse_num_bool \bool_set_false:N \l__jsonparse_num_zero_seen_bool \bool_set_false:N \l__jsonparse_num_plus_minus_seen_bool \bool_set_false:N \l__jsonparse_num_fraction_seen_bool \bool_set_false:N \l__jsonparse_num_exponent_seen_bool \tl_set:Nn \l__jsonparse_num_remainder_tl { #1 } \tl_if_empty:NTF \l__jsonparse_num_remainder_tl { \bool_set_false:N \l__jsonparse_num_bool } { \__jsonparse_parse_num:n {#1} } \clist_if_in:NVT \c__jsonparse_num_others_clist \l__jsonparse_num_input_last_tl { \bool_set_false:N \l__jsonparse_num_bool } \bool_if:NTF \l__jsonparse_num_bool { \prg_return_true: } { \prg_return_false: } } \cs_new:Npn \__jsonparse_parse_num:n #1 { \tl_set:Ne \l__jsonparse_num_input_tl { \tl_trim_spaces:e {#1} } \tl_if_empty:NF \l__jsonparse_num_input_tl { \cs_if_exist_use:cTF { __jsonparse_parse_num_ \str_head_ignore_spaces:o { \l__jsonparse_num_input_tl } :w } { \l__jsonparse_num_input_tl \q_stop } { \exp_last_unbraced:No \__jsonparse_parse_num_other:w \l__jsonparse_num_input_tl \q_stop } } } \cs_new:cpn { __jsonparse_parse_num_ + :w } #1 \q_stop { \exp_last_unbraced:No \__jsonparse_parse_num_plus_minus:w #1 \q_stop } \cs_new:cpn { __jsonparse_parse_num_ - :w } #1 \q_stop { \exp_last_unbraced:No \__jsonparse_parse_num_plus_minus:w #1 \q_stop } \cs_new:cpn { __jsonparse_parse_num_ . :w } #1 \q_stop { \exp_last_unbraced:No \__jsonparse_parse_num_fraction:w #1 \q_stop } \cs_new:cpn { __jsonparse_parse_num_ e :w } #1 \q_stop { \exp_last_unbraced:No \__jsonparse_parse_num_exponent:w #1 \q_stop } \cs_new:cpn { __jsonparse_parse_num_ E :w } #1 \q_stop { \exp_last_unbraced:No \__jsonparse_parse_num_exponent:w #1 \q_stop } \clist_map_inline:Nn \c__jsonparse_num_digits_clist { \cs_new:cpn { __jsonparse_parse_num_ #1 :w } ##1 \q_stop { \exp_last_unbraced:No \__jsonparse_parse_num_digit:w ##1 \q_stop } } \cs_new:Npn \__jsonparse_parse_num_plus_minus:w #1 \q_stop { \bool_if:NTF \l__jsonparse_num_plus_minus_seen_bool { \tl_set:Nn \l__jsonparse_num_remainder_tl { } \bool_set_false:N \l__jsonparse_num_bool } { \tl_if_empty:NTF \l__jsonparse_num_input_last_tl { \str_if_eq:enT { \tl_head:n {#1} } { + } { \tl_set:Nn \l__jsonparse_num_remainder_tl { } \bool_set_false:N \l__jsonparse_num_bool } } { \clist_if_in:NVT \c__jsonparse_num_others_clist \l__jsonparse_num_input_last_tl { \tl_set:Nn \l__jsonparse_num_remainder_tl { } \bool_set_false:N \l__jsonparse_num_bool } } } \bool_set_true:N \l__jsonparse_num_plus_minus_seen_bool \tl_set:Ne \l__jsonparse_num_input_last_tl { \tl_head:n {#1} } \tl_set:Ne \l__jsonparse_num_remainder_tl { \tl_tail:n {#1} } \__jsonparse_parse_num:n { \l__jsonparse_num_remainder_tl } } \cs_new:Npn \__jsonparse_parse_num_fraction:w #1 \q_stop { \bool_if:NTF \l__jsonparse_num_fraction_seen_bool { \tl_set:Nn \l__jsonparse_num_remainder_tl { } \bool_set_false:N \l__jsonparse_num_bool } { \tl_if_empty:NTF \l__jsonparse_num_input_last_tl { \tl_set:Nn \l__jsonparse_num_remainder_tl { } \bool_set_false:N \l__jsonparse_num_bool } { \clist_if_in:NVT \c__jsonparse_num_others_clist \l__jsonparse_num_input_last_tl { \tl_set:Nn \l__jsonparse_num_remainder_tl { } \bool_set_false:N \l__jsonparse_num_bool } } } \bool_set_false:N \l__jsonparse_num_zero_seen_bool \bool_set_true:N \l__jsonparse_num_plus_minus_seen_bool \bool_set_true:N \l__jsonparse_num_fraction_seen_bool \tl_set:Ne \l__jsonparse_num_input_last_tl { \tl_head:n {#1} } \tl_set:Ne \l__jsonparse_num_remainder_tl { \tl_tail:n {#1} } \__jsonparse_parse_num:n { \l__jsonparse_num_remainder_tl } } \cs_new:Npn \__jsonparse_parse_num_exponent:w #1 \q_stop { \bool_if:NTF \l__jsonparse_num_exponent_seen_bool { \tl_set:Nn \l__jsonparse_num_remainder_tl { } \bool_set_false:N \l__jsonparse_num_bool } { \tl_if_empty:NTF \l__jsonparse_num_input_last_tl { \tl_set:Nn \l__jsonparse_num_remainder_tl { } \bool_set_false:N \l__jsonparse_num_bool } { \clist_if_in:NVT \c__jsonparse_num_others_clist \l__jsonparse_num_input_last_tl { \tl_set:Nn \l__jsonparse_num_remainder_tl { } \bool_set_false:N \l__jsonparse_num_bool } } } \bool_set_false:N \l__jsonparse_num_zero_seen_bool \bool_set_false:N \l__jsonparse_num_plus_minus_seen_bool \bool_set_true:N \l__jsonparse_num_fraction_seen_bool \bool_set_true:N \l__jsonparse_num_exponent_seen_bool \tl_set:Ne \l__jsonparse_num_input_last_tl { \tl_head:n {#1} } \tl_set:Ne \l__jsonparse_num_remainder_tl { \tl_tail:n {#1} } \__jsonparse_parse_num:n { \l__jsonparse_num_remainder_tl } } \cs_new:Npn \__jsonparse_parse_num_digit:w #1 \q_stop { \bool_lazy_any:nTF { { \tl_if_empty_p:N \l__jsonparse_num_input_last_tl } { \bool_lazy_and_p:nn { \str_if_eq_p:Vn \l__jsonparse_num_input_last_tl { - } } { \bool_not_p:n { \l__jsonparse_num_exponent_seen_bool } } } } { \str_if_eq:enT { \tl_head:n {#1} } { 0 } { \bool_set_true:N \l__jsonparse_num_zero_seen_bool } } { \bool_if:NT \l__jsonparse_num_zero_seen_bool { \tl_set:Nn \l__jsonparse_num_remainder_tl { } \bool_set_false:N \l__jsonparse_num_bool } } \tl_set:Ne \l__jsonparse_num_input_last_tl { \tl_head:n {#1} } \tl_set:Ne \l__jsonparse_num_remainder_tl { \tl_tail:n {#1} } \__jsonparse_parse_num:n { \l__jsonparse_num_remainder_tl } } \cs_new:Npn \__jsonparse_parse_num_other:w #1 \q_stop { \tl_set:Nn \l__jsonparse_num_remainder_tl { } \bool_set_false:N \l__jsonparse_num_bool } % === \cs_new_protected:Npn \jsonparse_parse_to_prop:Nn #1#2 { \bool_if:NT \l__jsonparse_debug_mode_bool { \msg_log:nne { jsonparse } { debug-info } { \iow_newline: Parsing ~ JSON ~ ... } } \prop_gclear:N \g_jsonparse_entries_prop \jsonparse_parse:n {#2} \prop_gset_eq:NN #1 \g_jsonparse_entries_prop \bool_if:NT \l__jsonparse_debug_mode_bool { \msg_log:nne { jsonparse } { debug-info } { JSON ~ parsing ~ done. \iow_newline: } } } \cs_new_protected:Npn \jsonparse_parse:n #1 { \tl_set:Ne \l__jsonparse_input_tl { \tl_trim_spaces:e {#1} } \cs_if_exist_use:cTF { __jsonparse_parse_ \str_head_ignore_spaces:o { \l__jsonparse_input_tl } :w } { \l__jsonparse_input_tl \q_stop } { % other \exp_last_unbraced:No \__jsonparse_parse_other:w \l__jsonparse_input_tl \q_stop } } % === \cs_new:cpn { __jsonparse_parse_ \c_left_brace_str :w } #1 \q_stop { \exp_last_unbraced:No \__jsonparse_parse_object_begin:w #1 \q_stop } \cs_new:cpn { __jsonparse_parse_ \c_right_brace_str :w } #1 \q_stop { \exp_last_unbraced:No \__jsonparse_parse_object_end:w #1 \q_stop } \cs_new:cpn { __jsonparse_parse_ [ :w } #1 \q_stop { \exp_last_unbraced:No \__jsonparse_parse_array_begin:w #1 \q_stop } \cs_new:cpn { __jsonparse_parse_ ] :w } #1 \q_stop { \exp_last_unbraced:No \__jsonparse_parse_array_end:w #1 \q_stop } \cs_new:cpn { __jsonparse_parse_ " :w } #1 \q_stop { \exp_last_unbraced:No \__jsonparse_parse_string_key:w #1 \q_stop } \cs_new:Npn \__jsonparse_array_key_set: { \str_if_eq:eVT { \tl_range:Nen \l__jsonparse_prefix_tl { \int_eval:n { -1 * \tl_count:N \l__jsonparse_array_sep_left_str } } { -1 } } \l__jsonparse_array_sep_left_str { \int_incr:N \l__jsonparse_array_index_int \tl_set:Ne \l__jsonparse_key_tl { \l__jsonparse_prefix_tl \int_use:N \l__jsonparse_array_index_int \l__jsonparse_array_sep_right_str } } } \exp_last_unbraced:NNo \cs_new:Npn \__jsonparse_parse_object_begin:w \c_left_brace_str #1 \q_stop { \__jsonparse_array_key_set: \group_begin: \tl_set:Nn \l__jsonparse_remainder_tl {#1} % object begin \bool_if:NT \l__jsonparse_debug_mode_bool { \msg_log:nnn { jsonparse } { debug-info } { (obj ~ begin) } } \tl_if_empty:NTF \l__jsonparse_key_tl { \tl_set_eq:NN \l__jsonparse_object_array_key_tl \l__jsonparse_child_sep_str } { \tl_set_eq:NN \l__jsonparse_object_array_key_tl \l__jsonparse_key_tl \tl_set:Ne \l__jsonparse_prefix_tl { \l__jsonparse_key_tl \l__jsonparse_child_sep_str } } \tl_set:Nn \l__jsonparse_object_array_val_tl { \c_left_brace_str #1 } \__jsonparse_parse_remainder: } \exp_last_unbraced:NNo \cs_new:Npn \__jsonparse_parse_object_end:w \c_right_brace_str #1 \q_stop { \tl_set:Ne \l__jsonparse_object_array_val_tl { \tl_range:Nne \l__jsonparse_object_array_val_tl { 1 } { \int_eval:n { -1 * \tl_count:n {#1} - 1 } } } \prop_gput:Nee \g_jsonparse_entries_prop { \l__jsonparse_object_array_key_tl } { \l__jsonparse_object_array_val_tl } \bool_if:NT \l__jsonparse_debug_mode_bool { \msg_log:nne { jsonparse } { debug-info } { (key) ~ \str_use:N \l__jsonparse_object_array_key_tl : \iow_newline: \iow_char:N \ \iow_char:N \ (obj) ~ \str_use:N \l__jsonparse_object_array_val_tl } } \group_end: % object end \bool_if:NT \l__jsonparse_debug_mode_bool { \msg_log:nnn { jsonparse } { debug-info } { (obj ~ end) } } \tl_set:Nn \l__jsonparse_remainder_tl {#1} \__jsonparse_parse_remainder: } \cs_new:Npn \__jsonparse_parse_array_begin:w [ #1 \q_stop { \__jsonparse_array_key_set: \group_begin: \tl_set:Nn \l__jsonparse_remainder_tl {#1} % array begin \bool_if:NT \l__jsonparse_debug_mode_bool { \msg_log:nnn { jsonparse } { debug-info } { (arr ~ begin) } } \int_zero:N \l__jsonparse_array_index_int \bool_if:NT \l__jsonparse_zero_based_bool { \int_decr:N \l__jsonparse_array_index_int } \tl_set_eq:NN \l__jsonparse_object_array_key_tl \l__jsonparse_key_tl \tl_set:Nn \l__jsonparse_object_array_val_tl { [ #1 } \tl_set:Ne \l__jsonparse_prefix_tl { \l__jsonparse_key_tl \l__jsonparse_array_sep_left_str } \__jsonparse_parse_remainder: } \cs_new:Npn \__jsonparse_parse_array_end:w ] #1 \q_stop { \tl_set:Ne \l__jsonparse_object_array_val_tl { \tl_range:Nne \l__jsonparse_object_array_val_tl { 1 } { \int_eval:n { -1 * \tl_count:n {#1} - 1 } } } \prop_gput:Nee \g_jsonparse_entries_prop { \l__jsonparse_object_array_key_tl } { \l__jsonparse_object_array_val_tl } \bool_if:NT \l__jsonparse_debug_mode_bool { \msg_log:nne { jsonparse } { debug-info } { (key) ~ \str_use:N \l__jsonparse_object_array_key_tl : \iow_newline: \iow_char:N \ \iow_char:N \ (arr) ~ \str_use:N \l__jsonparse_object_array_val_tl } } \group_end: % array end \bool_if:NT \l__jsonparse_debug_mode_bool { \msg_log:nnn { jsonparse } { debug-info } { (arr ~ end) } } \tl_set:Nn \l__jsonparse_remainder_tl {#1} \__jsonparse_parse_remainder: } \cs_new:Npn \__jsonparse_parse_string_key:w " #1 " #2 \q_stop { \__jsonparse_array_key_set: \tl_set:Ne \l__jsonparse_remainder_tl { \tl_trim_spaces:n {#2} } % key or string? \tl_if_head_eq_charcode:oNTF { \l__jsonparse_remainder_tl } : { \tl_remove_once:NV \l__jsonparse_remainder_tl \c_colon_str \tl_set:Ne \l__jsonparse_key_tl { \l__jsonparse_prefix_tl #1 } } { \tl_set:Nn \l__jsonparse_val_tl {#1} \prop_gput:Nee \g_jsonparse_entries_prop { \l__jsonparse_key_tl } { \l__jsonparse_val_tl } % string \bool_if:NT \l__jsonparse_debug_mode_bool { \msg_log:nne { jsonparse } { debug-info } { (key) ~ \str_use:N \l__jsonparse_key_tl : \iow_newline: \iow_char:N \ \iow_char:N \ (str) ~ \str_use:N \l__jsonparse_val_tl } } } \__jsonparse_parse_remainder: } \cs_new:Npn \__jsonparse_parse_other:w #1 \q_stop { \__jsonparse_array_key_set: \tl_set:Nn \l__jsonparse_remainder_tl {#1} \tl_set:Nn \l__jsonparse_temp_tl { #1 , } \tl_replace_once:Nnn \l__jsonparse_temp_tl { ] } { , } \tl_replace_once:Non \l__jsonparse_temp_tl { \c_right_brace_str } { , } \exp_last_unbraced:No \__jsonparse_parse_other_aux:w \l__jsonparse_temp_tl \q_stop } \cs_new:Npn \__jsonparse_parse_other_aux:w #1 , #2 \q_stop { \tl_set:Ne \l__jsonparse_temp_tl { \tl_trim_spaces:n {#1} } \cs_if_exist_use:cF { __jsonparse_parse_ \str_casefold:o { \l__jsonparse_temp_tl } : } { \bool_if:NTF \l__jsonparse_check_num_bool { \jsonparse_if_num:nTF {#1} { \tl_set:Nn \l__jsonparse_val_tl {#1} \prop_gput:Nee \g_jsonparse_entries_prop { \l__jsonparse_key_tl } { \l__jsonparse_val_tl } % number \bool_if:NT \l__jsonparse_debug_mode_bool { \msg_log:nne { jsonparse } { debug-info } { (key) ~ \str_use:N \l__jsonparse_key_tl : \iow_newline: \iow_char:N \ \iow_char:N \ (num) ~ \str_use:N \l__jsonparse_val_tl } } } { % not a valid JSON fp \msg_error:nnoo { jsonparse } { parsing-error } { \l__jsonparse_key_tl } {#1} } } { \tl_set:Nn \l__jsonparse_val_tl {#1} \prop_gput:Nee \g_jsonparse_entries_prop { \l__jsonparse_key_tl } { \l__jsonparse_val_tl } % number \bool_if:NT \l__jsonparse_debug_mode_bool { \msg_log:nne { jsonparse } { debug-info } { (key) ~ \str_use:N \l__jsonparse_key_tl : \iow_newline: \iow_char:N \ \iow_char:N \ (num) ~ \str_use:N \l__jsonparse_val_tl } } } } \tl_set:Ne \l__jsonparse_remainder_tl { \tl_trim_spaces:e { \l__jsonparse_remainder_tl } } \tl_set:Ne \l__jsonparse_remainder_tl { \tl_range:Nen \l__jsonparse_remainder_tl { \int_eval:n { \tl_count:n {#1} + 1 } } { -1 } } \__jsonparse_parse_remainder: } \cs_new:Npn \__jsonparse_parse_true: { \tl_set_eq:NN \l__jsonparse_val_tl \l__jsonparse_true_str \prop_gput:Nee \g_jsonparse_entries_prop { \l__jsonparse_key_tl } { \l__jsonparse_val_tl } % true \bool_if:NT \l__jsonparse_debug_mode_bool { \msg_log:nne { jsonparse } { debug-info } { (key) ~ \str_use:N \l__jsonparse_key_tl : \iow_newline: \iow_char:N \ \iow_char:N \ (tru) ~ \str_use:N \l__jsonparse_val_tl } } } \cs_new:Npn \__jsonparse_parse_false: { \tl_set_eq:NN \l__jsonparse_val_tl \l__jsonparse_false_str \prop_gput:Nee \g_jsonparse_entries_prop { \l__jsonparse_key_tl } { \l__jsonparse_val_tl } % false \bool_if:NT \l__jsonparse_debug_mode_bool { \msg_log:nne { jsonparse } { debug-info } { (key) ~ \str_use:N \l__jsonparse_key_tl : \iow_newline: \iow_char:N \ \iow_char:N \ (fal) ~ \str_use:N \l__jsonparse_val_tl } } } \cs_new:Npn \__jsonparse_parse_null: { \tl_set_eq:NN \l__jsonparse_val_tl \l__jsonparse_null_str \prop_gput:Nee \g_jsonparse_entries_prop { \l__jsonparse_key_tl } { \l__jsonparse_val_tl } % null \bool_if:NT \l__jsonparse_debug_mode_bool { \msg_log:nne { jsonparse } { debug-info } { (key) ~ \str_use:N \l__jsonparse_key_tl : \iow_newline: \iow_char:N \ \iow_char:N \ (nul) ~ \str_use:N \l__jsonparse_val_tl } } } \cs_new:Npn \__jsonparse_parse_remainder: { \tl_set:Ne \l__jsonparse_remainder_tl { \tl_trim_spaces:e { \l__jsonparse_remainder_tl } } \tl_if_head_eq_charcode:oNT { \l__jsonparse_remainder_tl } , { \tl_remove_once:Nn \l__jsonparse_remainder_tl { , } } \tl_if_empty:NF \l__jsonparse_remainder_tl { \exp_args:No \jsonparse_parse:n { \l__jsonparse_remainder_tl } } } \cs_new:Npn \__jsonparse_filter:nn #1#2 { \str_case_e:en { \tl_range:nne {#1} { 1 } { \int_eval:n { \tl_count:o { \l__jsonparse_filter_key_str } + 1 } } } { { \l__jsonparse_filter_key_str \l__jsonparse_child_sep_str } { \prop_put:Nen \l__jsonparse_temp_prop { \tl_range:nen {#1} { \int_eval:n { \tl_count:o { \l__jsonparse_filter_key_str } + 2 } } { -1 } } {#2} } { \l__jsonparse_filter_key_str \l__jsonparse_array_sep_left_str } { \prop_put:Nen \l__jsonparse_temp_prop { \tl_range:nen {#1} { \int_eval:n { \tl_count:o { \l__jsonparse_filter_key_str } + 1 } } { -1 } } {#2} } } } \cs_new_protected:Npn \jsonparse_filter:Nn #1#2 { \prop_clear:N \l__jsonparse_temp_prop \str_set:Nn \l__jsonparse_filter_key_str {#2} \prop_map_function:NN #1 \__jsonparse_filter:nn \prop_set_eq:NN #1 \l__jsonparse_temp_prop } % === \NewDocumentCommand { \JSONParsePut } { m m +v } { \prop_if_exist:NF #1 { \prop_new:N #1 } \prop_gput:Nnn #1 {#2} {#3} } \cs_new:Npn \__jsonparse_externalize:Nn #1#2 { \file_if_exist:eTF {#2} { \msg_error:nne { jsonparse } { file-exists } {#2} } { \iow_open:Ne \g__jsonparse_externalize_iow {#2} \prop_map_inline:Nn #1 { \iow_now:Nn \g__jsonparse_externalize_iow { \JSONParsePut {#1} {##1} {##2} } } \iow_close:N \g__jsonparse_externalize_iow \msg_info:nne { jsonparse } { saving-external } {#2} } } % === \cs_new:Npn \__json_nested_construct_cs:Nnn #1#2#3 { \cs_set:Npn #1 #2 ##1 #3 #2 ##2 #3 { \prop_item:ce {##1} {##2} } } \cs_generate_variant:Nn \__json_nested_construct_cs:Nnn { Noo } \NewDocumentCommand { \JSONParse } { O{} m +v } { \group_begin: \str_set:Ne \l_jsonparse_current_prop_str { \cs_to_str:N #2 } \keys_set_known:nnN { jsonparse / global } {#1} \l__jsonparse_unused_keys_clist \keys_set_known:noN { jsonparse / parse } { \l__jsonparse_unused_keys_clist } \l__jsonparse_unused_keys_clist \__jsonparse_warning_unused_keys: \bool_set_false:N \l__jsonparse_externalize_load_bool \bool_if:NT \l__jsonparse_externalize_bool { \file_if_exist:eT { \l__jsonparse_externalize_file_name_tl .jsonparse } { \bool_set_true:N \l__jsonparse_externalize_load_bool } } \bool_if:NTF \l__jsonparse_externalize_load_bool { \msg_info:nne { jsonparse } { loading-external } { \l__jsonparse_externalize_file_name_tl .jsonparse } \file_input:e { \l__jsonparse_externalize_file_name_tl .jsonparse } } { \prop_new:N #2 \tl_gclear:N \g__jsonparse_json_tl \group_begin: \cs_set:Npn \" { \exp_not:N \" } \cs_set:Npn \/ { \exp_not:N \/ } \cs_set:Npn \\ { \exp_not:N \\ } \cs_set:Npn \b { \exp_not:N \b } \cs_set:Npn \f { \exp_not:N \f } \cs_set:Npn \n { \exp_not:N \n } \cs_set:Npn \r { \exp_not:N \r } \cs_set:Npn \t { \exp_not:N \t } \cs_set:Npn \u { \exp_not:N \u } \__json_nested_construct_cs:Noo \x \c_left_brace_str \c_right_brace_str \tl_set:Nn \obeyedline { ~ } \tl_gset_rescan:Nne \g__jsonparse_json_tl { \cctab_select:N \c__jsonparse_json_escape_cctab } {#3} \exp_args:NNe \jsonparse_parse_to_prop:Nn #2 { \g__jsonparse_json_tl } \group_end: \bool_if:NT \l__jsonparse_externalize_bool { \__jsonparse_externalize:Nn #2 { \l__jsonparse_externalize_file_name_tl .jsonparse } } } \group_end: } \NewDocumentCommand { \JSONParseFromFile } { O{} m m } { \file_if_exist:nF {#3} { \msg_error:nnn { jsonparse } { file-not-found } {#3} } \group_begin: \str_set:Ne \l_jsonparse_current_prop_str { \cs_to_str:N #2 } \keys_set_known:nnN { jsonparse / global } {#1} \l__jsonparse_unused_keys_clist \keys_set_known:noN { jsonparse / parse } { \l__jsonparse_unused_keys_clist } \l__jsonparse_unused_keys_clist \__jsonparse_warning_unused_keys: \bool_set_false:N \l__jsonparse_externalize_load_bool \bool_if:NT \l__jsonparse_externalize_bool { \file_if_exist:eT { \l__jsonparse_externalize_file_name_tl .jsonparse } { \bool_set_true:N \l__jsonparse_externalize_load_bool } } \bool_if:NTF \l__jsonparse_externalize_load_bool { \msg_info:nne { jsonparse } { loading-external } { \l__jsonparse_externalize_file_name_tl .jsonparse } \file_input:e { \l__jsonparse_externalize_file_name_tl .jsonparse } } { \prop_new:N #2 \tl_gclear:N \g__jsonparse_json_tl \group_begin: \cs_set:Npn \" { \exp_not:N \" } \cs_set:Npn \/ { \exp_not:N \/ } \cs_set:Npn \\ { \exp_not:N \\ } \cs_set:Npn \b { \exp_not:N \b } \cs_set:Npn \f { \exp_not:N \f } \cs_set:Npn \n { \exp_not:N \n } \cs_set:Npn \r { \exp_not:N \r } \cs_set:Npn \t { \exp_not:N \t } \cs_set:Npn \u { \exp_not:N \u } \file_get:nnN {#3} { \cctab_select:N \c__jsonparse_json_escape_cctab } \l__jsonparse_externalize_file_data_tl \tl_gset_eq:NN \g__jsonparse_json_tl \l__jsonparse_externalize_file_data_tl \exp_args:NNe \jsonparse_parse_to_prop:Nn #2 { \g__jsonparse_json_tl } \group_end: \bool_if:NT \l__jsonparse_externalize_bool { \__jsonparse_externalize:Nn #2 { \l__jsonparse_externalize_file_name_tl .jsonparse } } } \group_end: } \NewExpandableDocumentCommand { \JSONParseExpandableValue } { m m } { \prop_item:Ne #1 {#2} } \cs_set_eq:NN \__jsonparse_tex_quote: \" \cs_set_eq:NN \__jsonparse_tex_backslash: \\ \cs_new:Npn \__jsonparse_unicode_char:NNNN #1#2#3#4 { \symbol{ " \str_uppercase:n { #1 #2 #3 #4 } } { } } \cs_new:Npn \__jsonparse_rescan:n #1 { \group_begin: \cs_set:Npn \" { " } \cs_set:Npn \/ { / } \cs_set:Npn \\ { \c_backslash_str } \cs_set:Npn \b { \l__jsonparse_backspace_str } \cs_set:Npn \f { \l__jsonparse_formfeed_str } \cs_set:Npn \n { \l__jsonparse_linefeed_str } \cs_set:Npn \r { \l__jsonparse_carriage_return_str } \cs_set:Npn \t { \l__jsonparse_horizontal_tab_str } \cs_set_eq:NN \u \__jsonparse_unicode_char:NNNN \tl_set:Ne \l__jsonparse_temp_tl {#1} \cs_set_eq:NN \" \__jsonparse_tex_quote: \cs_set_eq:NN \\ \__jsonparse_tex_backslash: \bool_if:NT \l__jsonparse_escape_number_sign_bool { \tl_replace_all:Noe \l__jsonparse_temp_tl { \c_hash_str } { \c_backslash_str \c_hash_str } } \bool_if:NT \l__jsonparse_escape_dollar_sign_bool { \tl_replace_all:Noe \l__jsonparse_temp_tl { \c_dollar_str } { \c_backslash_str \c_dollar_str } } \bool_if:NT \l__jsonparse_escape_percent_sign_bool { \tl_replace_all:Noe \l__jsonparse_temp_tl { \c_percent_str } { \c_backslash_str \c_percent_str } } \bool_if:NT \l__jsonparse_escape_ampersand_bool { \tl_replace_all:Noe \l__jsonparse_temp_tl { \c_ampersand_str } { \c_backslash_str \c_ampersand_str } } \bool_if:NT \l__jsonparse_escape_circumflex_accent_bool { \tl_replace_all:Non \l__jsonparse_temp_tl { \c_circumflex_str } { \textasciicircum } } \bool_if:NT \l__jsonparse_escape_low_line_bool { \tl_replace_all:Noe \l__jsonparse_temp_tl { \c_underscore_str } { \c_backslash_str \c_underscore_str } } \bool_if:NT \l__jsonparse_escape_tilde_bool { \tl_replace_all:Non \l__jsonparse_temp_tl { \c_tilde_str } { \textasciitilde } } \tl_rescan:no { } { \l__jsonparse_temp_tl } \group_end: } \NewDocumentCommand { \JSONParseValue } { O{} m m } { \group_begin: \keys_set_known:nn { jsonparse / typeset } {#1} \l__jsonparse_unused_keys_clist \__jsonparse_warning_unused_keys: \bool_if:NTF \l__jsonparse_rescan_bool { \exp_args:Ne \__jsonparse_rescan:n { \prop_item:Ne #2 {#3} } } { \prop_item:Ne #2 {#3} } \group_end: } \cs_new:Npn \__jsonparse_keys:n #1 { \tl_if_in:nVF {#1} \l__jsonparse_child_sep_str { \bool_if:NTF \l__jsonparse_prop_map_first_bool { \bool_set_false:N \l__jsonparse_prop_map_first_bool } { \tl_put_right:Nn \l__jsonparse_temp_tl { , } } \tl_put_right:Nn \l__jsonparse_temp_tl { " #1 " } } } \NewDocumentCommand { \JSONParseKeys } { m m } { \tl_if_exist:NF #2 { \tl_new:N #2 } \bool_set_true:N \l__jsonparse_prop_map_first_bool \tl_set:Nn \l__jsonparse_temp_tl { [ } \prop_map_function:NN #1 \__jsonparse_keys:n \tl_put_right:Nn \l__jsonparse_temp_tl { ] } \tl_set_eq:NN #2 \l__jsonparse_temp_tl } \cs_new:Npn \__jsonparse_get_array_index:w [ #1 ] #2 \q_stop { #1 } \cs_new:Npn \__jsonparse_array_count:nn #1#2 { \str_if_eq:eVF { \tl_head:n {#1} } \l__jsonparse_array_sep_left_str { \msg_error:nnn { jsonparse } { not-array-item } {#1} } \seq_if_in:NeF \l__jsonparse_array_count_last_seq { \__jsonparse_get_array_index:w #1 \q_stop } { \int_incr:N \l__jsonparse_array_count_aux_int \seq_put_right:Ne \l__jsonparse_array_count_last_seq { \__jsonparse_get_array_index:w #1 \q_stop } } } \cs_new_protected:Npn \jsonparse_array_count:NN #1#2 { \int_zero:N \l__jsonparse_array_count_aux_int \seq_clear:N \l__jsonparse_array_count_last_seq \prop_map_function:NN #1 \__jsonparse_array_count:nn \int_set_eq:NN #2 \l__jsonparse_array_count_aux_int } \NewDocumentCommand { \JSONParseArrayCount } { m m } { \group_begin: \tl_set_eq:NN \l__jsonparse_temp_tl #1 \jsonparse_filter:Nn \l__jsonparse_temp_tl {#2} \jsonparse_array_count:NN \l__jsonparse_temp_tl \l__jsonparse_array_count_int \int_use:N \l__jsonparse_array_count_int \group_end: } \cs_new:Npn \__jsonparse_array_values:n #1 { \bool_if:NTF \l__jsonparse_prop_map_first_bool { \bool_set_false:N \l__jsonparse_prop_map_first_bool } { \l__jsonparse_array_values_insert_tl } \bool_if:NTF \l__jsonparse_rescan_bool { \exp_args:Ne \__jsonparse_rescan:n { \prop_item:Ne \l__jsonparse_temp_tl { \l__jsonparse_array_sep_left_str \bool_if:NTF \l__jsonparse_zero_based_bool { \int_eval:n { #1 - 1 } } { #1 } \l__jsonparse_array_sep_right_str \str_if_empty:NF \l__jsonparse_array_values_key_str { \l__jsonparse_child_sep_str \l__jsonparse_array_values_key_str } } } } { \prop_item:Ne \l__jsonparse_temp_tl { \l__jsonparse_array_sep_left_str \bool_if:NTF \l__jsonparse_zero_based_bool { \int_eval:n { #1 - 1 } } { #1 } \l__jsonparse_array_sep_right_str \str_if_empty:NF \l__jsonparse_array_values_key_str { \l__jsonparse_child_sep_str \l__jsonparse_array_values_key_str } } } } \NewDocumentCommand { \JSONParseArrayValues } { O{} m m O{} m } { \group_begin: \keys_set_known:nn { jsonparse / typeset } {#1} \l__jsonparse_unused_keys_clist \__jsonparse_warning_unused_keys: \tl_set_eq:NN \l__jsonparse_temp_tl #2 \jsonparse_filter:Nn \l__jsonparse_temp_tl {#3} \jsonparse_array_count:NN \l__jsonparse_temp_tl \l__jsonparse_array_count_int \str_set:Nn \l__jsonparse_array_values_key_str {#4} \tl_set:Nn \l__jsonparse_array_values_insert_tl {#5} \bool_set_true:N \l__jsonparse_prop_map_first_bool \int_step_function:nN { \l__jsonparse_array_count_int } \__jsonparse_array_values:n \group_end: } \tl_new:N \JSONParseArrayIndex \tl_new:N \JSONParseArrayKey \tl_new:N \JSONParseArrayValue \cs_new:Npn \__jsonparse_array_values_map_keys:n #1 { \int_incr:N \l__jsonparse_array_keys_index_int \tl_set:Ne \l__jsonparse_array_keys_index_roman_tl { \int_to_Roman:n { \l__jsonparse_array_keys_index_int } } \tl_gset:ce { JSONParseArrayKey \l__jsonparse_array_keys_index_roman_tl } { \l__jsonparse_array_sep_left_str \JSONParseArrayIndex \l__jsonparse_array_sep_right_str \l__jsonparse_child_sep_str #1 } \bool_if:NTF \l__jsonparse_rescan_bool { \tl_gset:cn { JSONParseArrayValue \l__jsonparse_array_keys_index_roman_tl } { \exp_args:Ne \__jsonparse_rescan:n { \prop_item:Ne \l__jsonparse_temp_tl { \l__jsonparse_array_sep_left_str \JSONParseArrayIndex \l__jsonparse_array_sep_right_str \l__jsonparse_child_sep_str #1 } } } } { \tl_gset:ce { JSONParseArrayValue \l__jsonparse_array_keys_index_roman_tl } { \prop_item:Ne \l__jsonparse_temp_tl { \l__jsonparse_array_sep_left_str \JSONParseArrayIndex \l__jsonparse_array_sep_right_str \l__jsonparse_child_sep_str #1 } } } } \cs_new:Npn \__jsonparse_array_values_map:n #1 { \bool_if:NTF \l__jsonparse_zero_based_bool { \tl_gset:Nn \JSONParseArrayIndex { \int_eval:n { #1 - 1 } } } { \tl_gset:Nn \JSONParseArrayIndex {#1} } \clist_if_empty:NTF \l__jsonparse_array_map_keys_clist { \tl_gset:Ne \JSONParseArrayKey { \l__jsonparse_array_sep_left_str \JSONParseArrayIndex \l__jsonparse_array_sep_right_str } \bool_if:NTF \l__jsonparse_rescan_bool { \tl_gset:Nn \JSONParseArrayValue { \exp_args:Ne \__jsonparse_rescan:n { \prop_item:Ne \l__jsonparse_temp_tl { \l__jsonparse_array_sep_left_str \JSONParseArrayIndex \l__jsonparse_array_sep_right_str } } } } { \tl_gset:Ne \JSONParseArrayValue { \prop_item:Ne \l__jsonparse_temp_tl { \l__jsonparse_array_sep_left_str \JSONParseArrayIndex \l__jsonparse_array_sep_right_str } } } } { \int_zero:N \l__jsonparse_array_keys_index_int \clist_map_function:NN \l__jsonparse_array_map_keys_clist \__jsonparse_array_values_map_keys:n \tl_gset_eq:NN \JSONParseArrayKey \JSONParseArrayKeyI \tl_gset_eq:NN \JSONParseArrayValue \JSONParseArrayValueI } \use:c { \l__jsonparse_array_map_function_str } } \cs_set:Npn \__jsonparse_array_map_create_cs:n #1 { \int_incr:N \l__jsonparse_array_keys_index_int \tl_set:Ne \l__jsonparse_array_keys_index_roman_tl { \int_to_Roman:n { \l__jsonparse_array_keys_index_int } } \tl_if_exist:cF { JSONParseArrayKey \l__jsonparse_array_keys_index_roman_tl } { \tl_new:c { JSONParseArrayKey \l__jsonparse_array_keys_index_roman_tl } } \tl_if_exist:cF { JSONParseArrayValue \l__jsonparse_array_keys_index_roman_tl } { \tl_new:c { JSONParseArrayValue \l__jsonparse_array_keys_index_roman_tl } } } \NewDocumentCommand { \JSONParseArrayValuesMap } { O{} m m O{} m O{} O{} } { \group_begin: \keys_set_known:nn { jsonparse / typeset } {#1} \l__jsonparse_unused_keys_clist \__jsonparse_warning_unused_keys: \tl_set_eq:NN \l__jsonparse_temp_tl #2 \jsonparse_filter:Nn \l__jsonparse_temp_tl {#3} \jsonparse_array_count:NN \l__jsonparse_temp_tl \l__jsonparse_array_count_int \clist_set:Nn \l__jsonparse_array_map_keys_clist {#4} \str_set:Nn \l__jsonparse_array_map_function_str {#5} \clist_if_empty:NF \l__jsonparse_array_map_keys_clist { \int_zero:N \l__jsonparse_array_keys_index_int \clist_map_function:NN \l__jsonparse_array_map_keys_clist \__jsonparse_array_map_create_cs:n } #6 \int_step_function:nN { \l__jsonparse_array_count_int } \__jsonparse_array_values_map:n #7 \group_end: } % EOF