% 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-04-28} {0.8.3} {JSON Parse} \bool_new:N \l__jsonparse_debug_mode_bool \keys_define:nn { jsonparse } { debug .bool_set:N = \l__jsonparse_debug_mode_bool , debug .default:n = { true } , } \ProcessKeyOptions [ jsonparse ] \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 } { saving-external } { \msg_info_text:n { jsonparse } \iow_newline: Saving ~ external ~ file: ~ #1. } \msg_new:nnn { jsonparse } { loading-external } { \msg_info_text:n { jsonparse } \iow_newline: Loading ~ from ~ external ~ file: ~ #1. } % === \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_array_index_zero_based_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_escape_tex_chars_clist \clist_set:Nn \l__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 \l__jsonparse_escape_tex_chars_clist { \bool_new:c { l__jsonparse_escape_ #1 _bool } } \keys_define:nn { jsonparse } { array ~ index ~ zero-based .bool_set:N = \l__jsonparse_array_index_zero_based_bool , array ~ index ~ zero-based .default:n = { true } , array ~ index ~ zero-based .initial:n = { true } , separator .code:n = { \keys_set:nn { jsonparse / 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 = { ] } , replacement .code:n = { \keys_set:nn { jsonparse / replacement } {#1} }, replacement / true .str_set:N = \l__jsonparse_true_str , replacement / true .initial:n = { true } , replacement / false .str_set:N = \l__jsonparse_false_str , replacement / false .initial:n = { false } , replacement / null .str_set:N = \l__jsonparse_null_str , replacement / null .initial:n = { null } , replacement / backspace .str_set:N = \l__jsonparse_backspace_str , replacement / backspace .initial:n = { ~ } , replacement / formfeed .str_set:N = \l__jsonparse_formfeed_str , replacement / formfeed .initial:n = { ~ } , replacement / linefeed .str_set:N = \l__jsonparse_linefeed_str , replacement / linefeed .initial:n = { ~ } , replacement / carriage ~ return .str_set:N = \l__jsonparse_carriage_return_str , replacement / carriage ~ return .initial:n = { ~ } , replacement / horizontal ~ tab .str_set:N = \l__jsonparse_horizontal_tab_str , replacement / horizontal ~ tab .initial:n = { ~ } , escape .code:n = { \str_case:nnF {#1} { { all } { \clist_map_inline:Nn \l__jsonparse_escape_tex_chars_clist { \bool_set_true:c { l__jsonparse_escape_ ##1 _bool } } } { none } { \clist_map_inline:Nn \l__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} } } } } } } \NewDocumentCommand { \JSONParseSet } { m } { \keys_set:nn { jsonparse } {#1} } % === \cs_if_exist:NF \str_casefold:n { \cs_new:Npn \str_casefold:n { \str_foldcase:n } } \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 \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 \iow_now:Nn { Ne } \cs_generate_variant:Nn \msg_error:nnn { nno } \cs_generate_variant:Nn \msg_error:nnnn { nnoo } \cs_generate_variant:Nn \msg_log:nnn { nne } \prg_generate_conditional_variant:Nnn \tl_if_eq:nn { en } { T } \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 \str_if_eq:nn { en , eV } { T , TF } \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_last_int \bool_new:N \l__jsonparse_prop_map_first_bool \bool_new:N \l__jsonparse_load_external_bool \ior_new:N \l__jsonparse_json_ior \iow_new:N \g__jsonparse_externalize_iow % === \cctab_const:Nn \c__jsonparse_json_escape_cctab { \cctab_select:N \c_str_cctab \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 } } % === \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_array_index_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 } : } { \fp_if_nan:nTF {#1} { % nan \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_protected:Npn \jsonparse_filter:Nn #1#2 { \prop_clear:N \l__jsonparse_temp_prop \prop_map_inline:Nn #1 { \str_case_e:en { \tl_range:nne {##1} { 1 } { \int_eval:n { \tl_count:n {#2} + 1 } } } { { #2 \l__jsonparse_child_sep_str } { \prop_put:Nen \l__jsonparse_temp_prop { \tl_range:nen {##1} { \int_eval:n { \tl_count:n {#2} + 2 } } { -1 } } {##2} } { #2 \l__jsonparse_array_sep_left_str } { \prop_put:Nen \l__jsonparse_temp_prop { \tl_range:nen {##1} { \int_eval:n { \tl_count:n {#2} + 1 } } { -1 } } {##2} } } } \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:nTF {#2} { \msg_error:nnn { jsonparse } { file-exists } {#2} } { \iow_open:Nn \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:nnn { jsonparse } { saving-external } {#2} } } % === \NewDocumentCommand { \JSONParse } { o m +v } { \bool_set_false:N \l__jsonparse_load_external_bool \tl_if_novalue:nF {#1} { \file_if_exist:nT { #1 .jsonparse } { \bool_set_true:N \l__jsonparse_load_external_bool } } \bool_if:NTF \l__jsonparse_load_external_bool { \msg_info:nnn { jsonparse } { loading-external } { #1 .jsonparse } \file_input:n { #1 .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 } \cs_set:Npn \x [ ##1 ] [ ##2 ] { \prop_item:ce {##1} {##2} } \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: \tl_if_novalue:nF {#1} { \__jsonparse_externalize:Nn #2 { #1 .jsonparse } } } } \NewDocumentCommand { \JSONParseFromFile } { o m m } { \file_if_exist:nF {#3} { \msg_error:nnn { jsonparse } { file-not-found } {#3} } \bool_set_false:N \l__jsonparse_load_external_bool \tl_if_novalue:nF {#1} { \file_if_exist:nT { #1 .jsonparse } { \bool_set_true:N \l__jsonparse_load_external_bool } } \bool_if:NTF \l__jsonparse_load_external_bool { \msg_info:nnn { jsonparse } { loading-external } { #1 .jsonparse } \file_input:n { #1 .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 } \cs_set:Npn \x [ ##1 ] [ ##2 ] { \prop_item:ce {##1} {##2} } \file_get:nnN {#3} { \cctab_select:N \c__jsonparse_json_escape_cctab } \g__jsonparse_json_tl \exp_args:NNe \jsonparse_parse_to_prop:Nn #2 { \g__jsonparse_json_tl } \group_end: \tl_if_novalue:nF {#1} { \__jsonparse_externalize:Nn #2 { #1 .jsonparse } } } } \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_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:Npn \u { \char" } \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 } { s m m } { \bool_if:NTF #1 { \prop_item:Ne #2 {#3} } { \exp_args:Ne \__jsonparse_rescan:n { \prop_item:Ne #2 {#3} } } } \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_inline:Nn #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 " } } } \tl_put_right:Nn \l__jsonparse_temp_tl { ] } \tl_set_eq:NN #2 \l__jsonparse_temp_tl } \NewDocumentCommand { \JSONParseArrayValues } { s m m O{} m } { \group_begin: \tl_set:Nn \l__jsonparse_temp_tl {#2} \jsonparse_filter:Nn \l__jsonparse_temp_tl {#3} \bool_set_true:N \l__jsonparse_prop_map_first_bool \prop_map_inline:Nn \l__jsonparse_temp_tl { \str_if_eq:enT { \tl_range:nen {##1} { \int_eval:n { -1 * \tl_count:n {#4} } } { -1 } } {#4} { \bool_if:NTF \l__jsonparse_prop_map_first_bool { \bool_set_false:N \l__jsonparse_prop_map_first_bool } { #5 } \bool_if:NTF #1 { ##2 } { \__jsonparse_rescan:n {##2} } } } \group_end: } \cs_new:Npn \__jsonparse_get_array_index:w [ #1 ] #2 \q_stop { #1 } \NewDocumentCommand { \JSONParseArrayCount } { m m } { \group_begin: \jsonparse_filter:Nn #1 {#2} \int_zero:N \l__jsonparse_array_count_int \int_set:Nn \l__jsonparse_array_count_last_int { -1 } \prop_map_inline:Nn #1 { \int_compare:nNnF { \__jsonparse_get_array_index:w ##1 \q_stop } = { \l__jsonparse_array_count_last_int } { \int_incr:N \l__jsonparse_array_count_int } \int_set:Nn \l__jsonparse_array_count_last_int { \__jsonparse_get_array_index:w ##1 \q_stop } } \int_use:N \l__jsonparse_array_count_int \group_end: } \tl_new:N \JSONParseArrayIndex \tl_new:N \JSONParseArrayKey \tl_new:N \JSONParseArrayValue \NewDocumentCommand { \JSONParseArrayValuesMap } { s m m O{} m } { \group_begin: \jsonparse_filter:Nn #2 {#3} \prop_map_inline:Nn #2 { \str_if_eq:enT { \tl_range:nen {##1} { \int_eval:n { -1 * \tl_count:n {#4} } } { -1 } } {#4} { \int_incr:N \l__jsonparse_array_index_int \tl_set:Ne \JSONParseArrayIndex { \__jsonparse_get_array_index:w ##1 \q_stop } \tl_set:Nn \JSONParseArrayKey {##1} \bool_if:NTF #1 { \tl_set:Nn \JSONParseArrayValue { \prop_item:Nn #2 {##1} } } { \tl_set:Nn \JSONParseArrayValue { \exp_args:Ne \__jsonparse_rescan:n { \prop_item:Nn #2 {##1} } } } \use:c {#5} } } \group_end: } % EOF