% ---------------------------------------------------------------------------- % the XSIM package - properties module % % eXercise Sheets IMproved % % ---------------------------------------------------------------------------- % Clemens Niederberger % Web: https://github.com/cgnieder/xsim % E-Mail: contact@mychemistry.eu % ---------------------------------------------------------------------------- % Copyright 2017--2020 Clemens Niederberger % % This work may be distributed and/or modified under the % conditions of the LaTeX Project Public License, either version 1.3 % of this license or (at your option) any later version. % The latest version of this license is in % http://www.latex-project.org/lppl.txt % and version 1.3 or later is part of all distributions of LaTeX % version 2005/12/01 or later. % % This work has the LPPL maintenance status `maintained'. % % The Current Maintainer of this work is Clemens Niederberger. % ---------------------------------------------------------------------------- % If you have any ideas, questions, suggestions or bugs to report, please % feel free to contact me. % ---------------------------------------------------------------------------- \XSIMmodule{properties}{properties of exercises} \xsim_load_modules:n {base,auxfile} \msg_new:nnn {xsim} {unknown-property} { You~ tried~ to~ set~ the~ property~ `#1'~ \msg_line_context: . \\ This~ property~ does~ not~ exist.~ Check~ for~ a~ typo~ or~ \\ define~ the~ property~ yourself. } \msg_new:nnn {xsim} {property-unique} { You~ tried~ to~ set~ the~ property~ `#1'~ \msg_line_context: . \\ This~ property~ has~ to~ have~ a~ unique~ value~ and~ thus~ \\ cannot~ be~ set~ to~ `#2'~ since~ this~ value~ is~ already~ talen. } \msg_new:nnn {xsim} {no-boolean-property} { You~ tried~ to~ check~ the~ boolean~ value~ of~ property~ `#1'~ \msg_line_context: . \\ However,~ property~ `#1'~ is~ not~ a~ boolean~ property! } \msg_new:nnn {xsim} {unbalanced-property-list} { There~ is~ a~ mis-match~ in~ the~ property~ list: \\ `#1' \\ \msg_line_context: } \seq_new:N \l__xsim_properties_seq \seq_new:N \l__xsim_boolean_properties_seq \seq_new:N \l__xsim_unique_properties_seq \seq_new:N \l__xsim_noupdate_properties_seq \seq_new:N \l__xsim_given_properties_seq % #1: type % #2: id % #3: property \cs_new_protected:Npn \__xsim_define_property:nnn #1#2#3 { \xsim_attribute_new:nn {#1!#2} {#3} } % check if property is set: % #1: type % #2: id % #3: property \prg_new_conditional:Npnn \xsim_if_property_set:nnn #1#2#3 {p,T,F,TF} { \xsim_attribute_if_set:nnTF {#1!#2} {#3} { \prg_return_true: } { \prg_return_false: } } \prg_generate_conditional_variant:Nnn \xsim_if_property_set:nnn {nx,nnx,xx,nV} {T,F,TF} % new property: % #1: boolean if unique % #2: boolean if boolean % #3: boolean if noupdate % #4: property name \cs_new_protected:Npn \xsim_declare_property:nnnn #1#2#3#4 { \xsim_if_property_exist:nF {#4} { \seq_put_right:Nn \l__xsim_properties_seq {#4} \bool_if:nTF {#3} { \seq_if_in:NnF \l__xsim_noupdate_properties_seq {#4} { \seq_put_right:Nn \l__xsim_noupdate_properties_seq {#4} } } { \xsim_new_list:n {#4} } \bool_if:nTF {#2} { \xsim_verbose:n { Declaring~ new~ boolean~ exercise~ property~ `#4'. } \seq_if_in:NnF \l__xsim_boolean_properties_seq {#4} { \seq_put_right:Nn \l__xsim_boolean_properties_seq {#4} } } { \bool_if:nTF {#1} { \xsim_verbose:n { Declaring~ new~ unique~ exercise~ property~ `#4'. } \seq_if_in:NnF \l__xsim_unique_properties_seq {#4} { \seq_put_right:Nn \l__xsim_unique_properties_seq {#4} } \seq_new:c {l__xsim_unique_property_#4_values_seq} } { \xsim_verbose:n { Declaring~ new~ exercise~ property~ `#4'. } } } \bool_if:nF {#3} { \xsim_at_end_document:n { \xsim_update_list:n {#4} } \xsim_at_begin_document:n { \__xsim_set_property_from_list:n {#4} } } } } % check if property exists: \prg_new_conditional:Npnn \xsim_if_property_exist:n #1 {p,T,F,TF} { \seq_if_in:NnTF \l__xsim_properties_seq {#1} { \prg_return_true: } { \prg_return_false: } } \cs_generate_variant:Nn \xsim_if_property_exist:nTF {x} \prg_new_conditional:Npnn \xsim_if_property_noupdate:n #1 {p,T,F,TF} { \seq_if_in:NnTF \l__xsim_noupdate_properties_seq {#1} { \prg_return_true: } { \prg_return_false: } } \cs_generate_variant:Nn \xsim_if_property_noupdate:nF {V} \prg_new_conditional:Npnn \xsim_if_property_boolean:n #1 {p,T,F,TF} { \seq_if_in:NnTF \l__xsim_boolean_properties_seq {#1} { \prg_return_true: } { \prg_return_false: } } \prg_new_conditional:Npnn \xsim_if_property_unique:n #1 {p,T,F,TF} { \seq_if_in:NnTF \l__xsim_unique_properties_seq {#1} { \prg_return_true: } { \prg_return_false: } } % true if property #1 has been set in the /last/ call of % \xsim_set_properties:nnn \prg_new_conditional:Npnn \xsim_if_property_given:n #1 {p,T,F,TF} { \seq_if_in:NnTF \l__xsim_given_properties_seq {#1} { \prg_return_true: } { \prg_return_false: } } % #1: property % #2: value \prg_new_conditional:Npnn \xsim_if_property_unique_value:nn #1#2 {p,T,F,TF} { \xsim_if_property_unique:nTF {#1} { \seq_if_in:cnTF {l__xsim_unique_property_#1_values_seq} {#2} { \prg_return_true: } { \prg_return_false: } } { \prg_return_false: } } % #1: type % #2: id % #3: property % #4: value \cs_new_protected:Npn \xsim_set_property:nnnn #1#2#3#4 { \xsim_if_property_exist:nTF {#3} % _p { \xsim_if_property_set:nnnTF {#1} {#2} {#3} % _p { \xsim_if_property_unique_value:nnTF {#3} {#4} %_p { \msg_warning:nnnn {xsim} {property-unique} {#3} {#4} } { \xsim_if_property_boolean:nTF {#3} % _p { \__xsim_set_boolean_property:nnnn {#1} {#2} {#3} {#4} } { \xsim_if_property_unique:nTF {#3} % _p { \__xsim_set_unique_property:nnnn {#1} {#2} {#3} {#4} } { \__xsim_set_property:nnnn {#1} {#2} {#3} {#4} } } } } { \xsim_if_property_boolean:nTF {#3} % _p { \__xsim_set_boolean_property:nnnn {#1} {#2} {#3} {#4} } { \xsim_if_property_unique:nTF {#3} % _p { \__xsim_set_unique_property:nnnn {#1} {#2} {#3} {#4} } { \__xsim_set_property:nnnn {#1} {#2} {#3} {#4} } } } } { \msg_error:nnn {xsim} {unknown-property} {#3} } } \cs_generate_variant:Nn \xsim_set_property:nnnn {nnxx,nnnV,nV,nVnx,nnnx,xx,xxnx,nxnx} % #1: type % #2: id % #3: property % #4: value \cs_new_protected:Npn \__xsim_set_boolean_property:nnnn #1#2#3#4 { \str_case_e:nnTF { \str_lowercase:n {#4} } { {} {} {true} {} } { \__xsim_set_property:nnnn {#1} {#2} {#3} {true} } { \__xsim_set_property:nnnn {#1} {#2} {#3} {false} } } % #1: type % #2: id % #3: property % #4: value \cs_new_protected:Npn \__xsim_set_unique_property:nnnn #1#2#3#4 { \__xsim_set_property:nnnn {#1} {#2} {#3} {#4} \__xsim_set_property_reverse:nnnn {#1} {#2} {#3} {#4} } % #1: type % #2: id % #3: property \cs_new_protected:Npn \__xsim_unset_property:nnn #1#2#3 { \xsim_verbose:n { Un-setting~ property~ `#3'~ of~ exercise~ type~ `#1'~ id~ `#2'. } \__xsim_remove_property_from_list:nnn {#1} {#2} {#3} \xsim_attribute_unset:nn {#1!#2} {#3} } % #1: type % #2: id % #3: property \cs_new_protected:Npn \xsim_unset_property:nnn #1#2#3 { \xsim_if_property_exist:nTF {#3} { \__xsim_unset_property:nnn {#1} {#2} {#3} } { \msg_error:nnn {xsim} {unknown-property} {#3} } } \cs_generate_variant:Nn \xsim_unset_property:nnn {nx,nnx,nV} % #1: type % #2: id % #3: property % #4: value \cs_new_protected:Npn \__xsim_set_property:nnnn #1#2#3#4 { \xsim_verbose:n { Setting~ property~ `#3'~ of~ exercise~ type~ `#1'~ id~ `#2'~ to~ value~ `#4'. } \__xsim_remove_property_from_list:nnn {#1} {#2} {#3} % now we can safely add the property to the list: \xsim_add_to_list:nn {#3} {#1-#2=={#4}} \xsim_attribute_set:nnn {#1!#2} {#3} {#4} } \cs_generate_variant:Nn \__xsim_set_property:nnnn {VVnV} % #1: type % #2: id % #3: property % #4: value \cs_new_protected:Npn \__xsim_set_property_reverse:nnnn #1#2#3#4 { \xsim_attribute_set:nnn {type:#3} {#4} {#1} \xsim_attribute_set:nnn {id:#3} {#4} {#2} } \tl_set:Nx \l__xsim_tmpa_tl { \cs_to_str:N \{ } \tl_set:Nx \l__xsim_tmpb_tl { \cs_to_str:N \} } \use:x { \cs_new_protected:Npn \exp_not:N \__xsim_extract_property_list_entry:NNNwww ##1##2##3##4-##5== \l__xsim_tmpa_tl ##6 \l__xsim_tmpb_tl \exp_not:N \q_stop } { \str_set:Nn #1 {#4} \str_set:Nn #2 {#5} \str_set:Nn #3 {#6} } % #1: type % #2: id % #3: property \cs_new_protected:Npn \__xsim_remove_property_from_list:nnn #1#2#3 { % make sure we change the entry if it has been recorded previously: \xsim_foreach_list_entry:nn {#3} { \__xsim_extract_property_list_entry:NNNwww \l__xsim_tmpa_str \l__xsim_tmpb_str \l__xsim_tmpc_str ##1 \q_stop \str_if_eq:xnT { \l__xsim_tmpa_str - \l__xsim_tmpb_str } {#1-#2} { \xsim_remove_from_list:nx {#3} { #1-#2 == {\l__xsim_tmpc_str} } } } % make sure we change the entry if it has been set during the current run: \xsim_foreach_new_list_entry:nn {#3} { \__xsim_extract_property_list_entry:NNNwww \l__xsim_tmpa_str \l__xsim_tmpb_str \l__xsim_tmpc_str ##1 \q_stop \str_if_eq:xnT { \l__xsim_tmpa_str - \l__xsim_tmpb_str } {#1-#2} { \xsim_remove_from_list:nx {#3} { #1-#2 == {\l__xsim_tmpc_str} } } } } \cs_new_protected:Npn \__xsim_set_property_from_list:n #1 { \xsim_foreach_list_entry:nn {#1} { \__xsim_extract_property_list_entry:NNNwww \l__xsim_tmpa_str \l__xsim_tmpb_str \l__xsim_tmpc_str ##1 \q_stop \tl_set_rescan:Nnx \l__xsim_tmpc_tl {} { \l__xsim_tmpc_str } \__xsim_set_property:VVnV \l__xsim_tmpa_str \l__xsim_tmpb_str {#1} \l__xsim_tmpc_tl } } % set properties: % #1: type % #2: id % #3: csv list of properties \cs_new_protected:Npn \xsim_set_properties:nnn #1#2#3 { \seq_clear:N \l__xsim_given_properties_seq \clist_map_inline:nn {#3} { \seq_set_split:Nnx \l__xsim_tmpa_seq {=} { \tl_trim_spaces:n {##1} } \int_compare:nNnT { \seq_count:N \l__xsim_tmpa_seq } > {2} { \msg_error:nnn {xsim} {unbalanced-property-list} {##1} } \xsim_set_property:nnxx {#1} {#2} { \seq_item:Nn \l__xsim_tmpa_seq {1} } { \seq_item:Nn \l__xsim_tmpa_seq {2} } \seq_put_right:Nx \l__xsim_given_properties_seq { \seq_item:Nn \l__xsim_tmpa_seq {1} } } \xsim_property_set_aliases:nn {#1} {#2} } \cs_generate_variant:Nn \xsim_set_properties:nnn {nnx,nxx,nV,nVx} % ---------------------------------------------------------------------------- \seq_new:N \g__xsim_property_aliases_seq % #1: property \prg_new_conditional:Npnn \xsim_if_property_alias:n #1 {p,T,F,TF} { \seq_if_in:NnTF \g__xsim_property_aliases_seq {#1} { \prg_return_true: } { \prg_return_false: } } % #1: property \cs_new:Npn \xsim_property_alias:n #1 { \xsim_attribute_get:nn {#1} {alias} } % #1: property % #2: alias of \cs_new_protected:Npn \xsim_make_property_alias:nn #1#2 { \xsim_if_property_alias:nF {#1} { \xsim_verbose:n { Making~ property~ `#1'~ an~ alias~ of~ property~ `#2'. } \xsim_attribute_set:nnn {#1} {alias} {#2} \seq_gput_right:Nn \g__xsim_property_aliases_seq {#1} } } % #1: type % #2: id % #3: property \cs_new_protected:Npn \xsim_property_set_alias:nnn #1#2#3 { \xsim_if_property_set:nnnF {#1} {#2} {#3} { \xsim_if_property_set:nnxT {#1} {#2} { \xsim_property_alias:n {#3} } { \xsim_set_property:nnnx {#1} {#2} {#3} { \xsim_get_property:nnf {#1} {#2} { \xsim_property_alias:n {#3} } } } } \xsim_if_property_set:nnxT {#1} {#2} { \xsim_property_alias:n {#3} } { \xsim_attribute_set:nnx {alias:#1!#2} {#3} { \xsim_get_property:nnf {#1} {#2} { \xsim_property_alias:n {#3} } } } } \cs_generate_variant:Nn \xsim_property_set_alias:nnn {nV} % #1: type % #2: id \cs_new_protected:Npn \xsim_property_set_aliases:nn #1#2 { \seq_map_inline:Nn \g__xsim_property_aliases_seq { \xsim_property_set_alias:nnn {#1} {#2} {##1} } } % ---------------------------------------------------------------------------- % retrieve properties: % #1: type % #2: id % #3: property \cs_new:Npn \xsim_get_property:nnn #1#2#3 { \xsim_attribute_get:nn {#1!#2} {#3} } \cs_generate_variant:Nn \xsim_get_property:nnn {nx,ne,xx,oo,no,nnf,nf,oof,nV} % #1: property % #2: value \cs_new:Npn \xsim_get_id_for_property:nn #1#2 { \xsim_attribute_get:nn {id:#1} {#2} } % #1: property % #2: value \cs_new:Npn \xsim_get_type_for_property:nn #1#2 { \xsim_attribute_get:nn {type:#1} {#2} } \cs_generate_variant:Nn \xsim_get_type_for_property:nn {nV} % #1: id or ID \cs_new:Npn \xsim_normalize_id:n #1 { \tl_if_blank:eTF { \xsim_get_id_for_property:nn {ID} {#1} } {#1} { \xsim_get_id_for_property:nn {ID} {#1} } } \cs_generate_variant:Nn \xsim_normalize_id:n {e} \cs_new:Npn \__xsim_get_property_tmp:n #1 {} \cs_generate_variant:Nn \__xsim_get_property_tmp:n {V} % #1: type % #2: id % #3: property \cs_new_protected:Npn \xsim_get_property_if_set:nnnTF #1#2#3#4#5 { \group_begin: \xsim_if_property_exist:nTF {#3} { \xsim_if_property_set:nnnTF {#1} {#2} {#3} { \tl_set:Nx \PropertyValue { \xsim_get_property:nnn {#1} {#2} {#3} } \cs_set:Npn \__xsim_get_property_tmp:n ##1 {#4} \__xsim_get_property_tmp:V \PropertyValue } {#5} } { \msg_error:nnn {xsim} {unknown-property} {#3} } \group_end: } \cs_new_protected:Npn \xsim_get_property_if_set:nnnT #1#2#3#4 { \xsim_get_property_if_set:nnnTF {#1} {#2} {#3} {#4} {} } \cs_new_protected:Npn \xsim_get_property_if_set:nnnF #1#2#3#4 { \xsim_get_property_if_set:nnnTF {#1} {#2} {#3} {} {#4} } \cs_generate_variant:Nn \xsim_get_property_if_set:nnnTF {oo} % #1: type % #2: id % #3: property \prg_new_conditional:Npnn \xsim_if_boolean_property:nnn #1#2#3 {T,F,TF} { \xsim_if_property_exist:nTF {#3} { \xsim_if_property_boolean:nTF {#3} { \xsim_if_property_set:nnnTF {#1} {#2} {#3} { \bool_if:cTF {c_ \xsim_get_property:nnn {#1} {#2} {#3} _bool} { \prg_return_true: } { \prg_return_false: } } { \prg_return_false: } } { \msg_error:nnn {xsim} {no-boolean-property} {#3} } } { \msg_error:nnn {xsim} {unknown-property} {#3} } } \cs_generate_variant:Nn \xsim_if_boolean_property:nnnTF {oo} \cs_generate_variant:Nn \xsim_if_boolean_property:nnnF {nV} \cs_new_protected:Npn \xsim_save_property:nnnN #1#2#3#4 { \tl_set:No #4 { \cs:w __xsim_property_#2!#1!#3: \cs_end: } } \cs_generate_variant:Nn \xsim_save_property:nnnN {nx,xx} \cs_new_protected:Npn \xsim_gsave_property:nnnN #1#2#3#4 { \xsim_save_property:nnnN {#1} {#2} {#3} #4 \tl_gset:NV #4 #4 } \cs_generate_variant:Nn \xsim_gsave_property:nnnN {nx,xx,nV} % ---------------------------------------------------------------------------- \file_input_stop: 2020/03/09 - add meaningful error if a comma is missing in the property list 2020/03/14 - performance optimizations