\RequirePackage{expl3}[2023-10-10] \ProvidesExplPackage{genealogy-profiles}{2024/01/24}{0.1}{Macros for creating genealogical profiles} % License: CC-BY-SA 4.0 % Author: Mikkel Eide Eriksen \RequirePackage{genealogytree} \RequirePackage{hyperref} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Key setup % \keys_define:nn { genealogy-profiles } { name~part~order .code:n = { \seq_gset_from_clist:Nn \g__gpr_name_part_order_seq {#1} } , name~type .choices:nn = { given~and~surname , nordic~historical , } { \int_case:nn { \l_keys_choice_int } { {1} { \seq_gset_from_clist:Nn \g__gpr_name_part_order_seq { givenname, surname } } {2} { \seq_gset_from_clist:Nn \g__gpr_name_part_order_seq { givenname, patronymic, byname } } } } , auto~id .bool_set:N = \l__gpr_autoid_bool , auto~id .default:n = { true } , auto~id~prefix .tl_set:N = \l__gpr_id_prefix_tl , % formatting profiles begin~profile .tl_set:N = \l__gpr_begin_profile_tl , end~profile .tl_set:N = \l__gpr_end_profile_tl , begin~header .tl_set:N = \l__gpr_begin_header_tl , end~header .tl_set:N = \l__gpr_end_header_tl , begin~life~events .tl_set:N = \l__gpr_begin_life_events_tl , end~life~events .tl_set:N = \l__gpr_end_life_events_tl , reference~style .cs_set:Np = \gpr__reference_style_handler:nnn #1#2#3 , unknown~reference~style .cs_set:Np = \gpr__unknown_reference_handler:nn #1#2 , page~number~style .cs_set:Np = \gpr__page_number_handler:n #1 , % name part styling givenname~style .tl_set:N = \l__gpr_givenname_style_tl , patronymic~style .tl_set:N = \l__gpr_patronymic_style_tl , surname~style .tl_set:N = \l__gpr_surname_style_tl , byname~style .tl_set:N = \l__gpr_byname_style_tl , header~format .tl_set:N = \l__gpr_header_format_tl , auto~header .bool_set:N = \l__gpr_auto_header_bool , % indexes id~index .tl_set:N = \l__gpr_id_index_tl , fullname~index .tl_set:N = \l__gpr_fullname_index_tl , patronymic~index .tl_set:N = \l__gpr_patronymic_index_tl , surname~index .tl_set:N = \l__gpr_surname_index_tl , byname~index .tl_set:N = \l__gpr_byname_index_tl , nest~index~entries .bool_set:N = \l__gpr_nest_index_entries_bool , id~in~index~entries .bool_set:N = \l__gpr_id_in_index_entries_bool , main~index~entry~style .tl_set:N = \l__gpr_main_index_entry_style_tl , include~unknown~in~index .bool_set:N = \l__gpr_include_unknown_in_index_bool , include~unknown~in~index .default:n = { true } , include~ambiguous~in~index .bool_set:N = \l__gpr_include_ambiguous_in_index_bool , include~ambiguous~in~index .default:n = { true } , debug~log .bool_set:N = \l__gpr_debug_log_bool } \keys_define:nn { genealogy-profiles / profile } { id .tl_set:N = \l__gpr_id_tl , fullname .tl_set:N = \l__gpr_fullname_tl , givenname .tl_set:N = \l__gpr_givenname_tl , patronymic .tl_set:N = \l__gpr_patronymic_tl , surname .tl_set:N = \l__gpr_surname_tl , byname .tl_set:N = \l__gpr_byname_tl , life~events .tl_set:N = \l__gpr_life_events_tl , no~index .bool_set:N = \l__gpr_no_index_bool , } \NewDocumentCommand { \gprKeys } { +m } { \keys_set:nn { genealogy-profiles } { #1 } } % set some defaults \keys_set:nn { genealogy-profiles } { auto~id , include~unknown~in~index , include~ambiguous~in~index , name~type = { given~and~surname } , header~format = { \gprStyledName \hfill \gprID } , reference~style = { #1 \gpr_super_subscript:nn {#2} {#3} } , page~number~style = { p.\nobreakspace#1 } , unknown~reference~style = { \color_select:n {red} #1 \tl_if_novalue:nF {#2} { ~ (#2) } } , } \cs_generate_variant:Nn \gpr__unknown_reference_handler:nn { Vn, nV } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Errors and warnings % \msg_new:nnn { genealogy-profiles } { reference-recorded } { Reference ~ recorded ~ from ~ #1 ~ to ~ #2 ~ on ~ input ~ line ~ #3 } \msg_new:nnn { genealogy-profiles } { ambiguous-reference } { Ambiguous ~ reference: ~ #1 ~ on ~ input ~ line ~ #2, ~ use ~ IDs ~ to ~ fix ~ references } \msg_new:nnn { genealogy-profiles } { unknown-reference-id } { Reference ~ to ~ unknown ~ id: ~ #1 ~ on ~ input ~ line ~ #2 } \msg_new:nnn { genealogy-profiles } { unknown-reference-name } { Reference ~ to ~ unknown ~ name: ~ #1 ~ on ~ input ~ line ~ #2 } \msg_new:nnn { genealogy-profiles } { missing-backref } { Missing ~ backreference: ~ #1 ~ refers ~ to ~ #2 ~ but ~ not ~ vice ~ versa ~ on ~ input ~ line ~ #3 } \msg_new:nnnn { genealogy-profiles } { missing-id } { You ~ have ~ not ~ assigned ~ an ~ ID ~ to ~ profile: ~ #1 ~ on ~ input ~ line ~ #2 } { You ~ must ~ either ~ give ~ each ~ profile ~ a ~ unique ~ ID, ~ or ~ let ~ them ~ be ~ autogenerated. } \msg_new:nnnn { genealogy-profiles } { reused-id } { You ~ have ~ assigned ~ an ~ ID ~ that ~ is ~ already ~ in ~ use: ~ #1 ~ on ~ input ~ line ~ #2 } { You ~ must ~ either ~ give ~ every ~ profile ~ a ~ unique ~ ID, ~ or ~ let ~ them ~ be ~ autogenerated. } \msg_new:nnn { genealogy-profiles } { ambiguous-rawname } { Multiple ~ profiles ~ with ~ the ~ name ~ '#1', ~ you ~ will ~ need ~ to ~ use ~ IDs ~ to ~ reference ~ them } \cs_new_protected:Nn \gpr_warning:nn { \msg_warning:nnee { genealogy-profiles } { #1 } { #2 } { \the\inputlineno } } \cs_new_protected:Nn \gpr_warning:nnn { \msg_warning:nneee { genealogy-profiles } { #1 } { #2 } { #3 } { \the\inputlineno } } \cs_new_protected:Nn \gpr_error:nn { \msg_error:nnee { genealogy-profiles } { #1 } { #2 } { \the\inputlineno } } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Extensions to expl3 library % \prg_generate_conditional_variant:Nnn \tl_if_blank:n { v, V } { F } \prg_generate_conditional_variant:Nnn \property_if_recorded:nn { en } { T, TF } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Properties % \seq_clear_new:N \l__gpr_floruit_years_seq \property_new:nnnn { floruit-years } { now } { \c_novalue_tl } { \seq_use:Nn \l__gpr_floruit_years_seq {,} } \tl_clear_new:N \l__gpr_fullname_raw_tl \property_new:nnnn { raw-name } { now } { \c_novalue_tl } { \tl_use:N \l__gpr_fullname_raw_tl } \prop_clear_new:N \g__gpr_name_to_id_prop % saved at end, loaded into _active at start \prop_clear_new:N \g__gpr_name_to_id_active_prop % used for lookups during run \property_new:nnnn { names-ids } { now } { \c_novalue_tl } { \prop_to_keyval:N \g__gpr_name_to_id_prop } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Names % \tl_const:Ne \c_gpr_underscore_tl { \char_generate:nn { `_ } { 8 } } \cs_new_protected:Nn \gpr_name_handler:n { \tl_set:Nn \l__gpr_fullname_tl {#1} \tl_replace_all:NVn \l__gpr_fullname_tl \c_gpr_underscore_tl { ~ } \seq_set_split:Nnn \l_tmpa_seq {~} {#1} \seq_map_indexed_inline:Nn \g__gpr_name_part_order_seq { \tl_set:Ne \l_tmpa_tl { \seq_item:Nn \l_tmpa_seq {##1} } \tl_replace_all:NVn \l_tmpa_tl \c_gpr_underscore_tl { ~ } \tl_if_blank:VTF \l_tmpa_tl { \tl_clear:c { l__gpr_##2_tl } } { \tl_set:cV { l__gpr_##2_tl } \l_tmpa_tl } } \tl_clear_new:N \l__gpr_styled_name_tl \gpr_style_name:N \l__gpr_styled_name_tl } \cs_generate_variant:Nn \gpr_name_handler:n { V, e } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % IDs % \cs_new_protected:Nn \gpr_map_name_to_id:Nnn { % check if name already has an associated id \prop_get:NVNTF #1 \l__gpr_fullname_raw_tl \l_tmpb_tl { % mark name ambiguous if already reserved \tl_if_eq:NNF \l__gpr_id_tl \l_tmpb_tl { \msg_note:nne { genealogy-profiles } { ambiguous-rawname } { \tl_use:N \l__gpr_fullname_raw_tl } \prop_gput:NVn #1 \l__gpr_fullname_raw_tl {_AMBIGUOUS_} } } { % otherwise save raw name -> id \prop_gput:NVV #1 \l__gpr_fullname_raw_tl \l__gpr_id_tl } } \cs_generate_variant:Nn \gpr_map_name_to_id:Nnn { NVV } \seq_clear_new:N \g__gpr_used_profile_ids_seq \cs_new_protected:Nn \gpr_generate_unique_id: { \tl_clear_new:N \l_tmpa_tl \tl_put_right:NV \l_tmpa_tl \l__gpr_id_prefix_tl % build base id from initials \seq_map_indexed_inline:Nn \g__gpr_name_part_order_seq { \tl_if_empty:cTF { l__gpr_##2_tl } { \tl_put_right:Ne \l_tmpa_tl { - } } { \tl_put_right:Ne \l_tmpa_tl { \tl_item:cn { l__gpr_##2_tl } {1} } } } % find unused variant \int_zero_new:N \l__gpr_id_index_int \bool_set_false:N \l__gpr_id_found_bool \bool_do_until:nn { \l__gpr_id_found_bool } { \int_incr:N \l__gpr_id_index_int \tl_set:Ne \l__gpr_id_tl { \tl_use:N \l_tmpa_tl \int_use:N \l__gpr_id_index_int } \seq_if_in:NVTF \g__gpr_used_profile_ids_seq \l__gpr_id_tl { \bool_set_false:N \l__gpr_id_found_bool } { % mark id as used \seq_gput_right:NV \g__gpr_used_profile_ids_seq \l__gpr_id_tl \bool_set_true:N \l__gpr_id_found_bool } } } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % References % \prop_new:N \g__gpr_references_prop \cs_new_protected:Nn \gpr_save_reference:nn { \prop_if_in:NnTF \g__gpr_references_prop { #1 } { \prop_get:NnN \g__gpr_references_prop { #1 } \l_tmpa_tl } { \tl_clear:N \l_tmpa_tl } % wrap ID as _ID_ so we can match them out later \tl_put_right:Nn \l_tmpa_tl { _#2_ } \prop_gput:NnV \g__gpr_references_prop { #1 } \l_tmpa_tl % \msg_note:nneee { genealogy-profiles } { reference-recorded } % { #1 } { #2 } {\the\inputlineno} } \cs_generate_variant:Nn \gpr_save_reference:nn { Vn } \cs_new_protected:Nn \gpr_check_backrefs:nn { \regex_extract_all:nnN { _([^_]+)_ } {#2} \l_tmpa_seq \seq_map_inline:Nn \l_tmpa_seq { % dont test entire matches (_ID_), only submatches (ID) \tl_if_eq:neF { _ } { \tl_item:nn { ##1 } {1} } { \seq_if_in:NnT \g__gpr_used_profile_ids_seq { ##1 } { \prop_get:NnN \g__gpr_references_prop {##1} \l_tmpa_tl \regex_match:nVF { #1 } { \l_tmpa_tl } { \gpr_warning:nnn { missing-backref } { #1 } { ##1 } } } } } } \cs_new:Nn \gpr_init_names_to_keys:n { \tl_if_novalue:nF {#1} { \prop_set_from_keyval:Nn \g__gpr_name_to_id_active_prop {#1} } } \cs_generate_variant:Nn \gpr_init_names_to_keys:n { e } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Hooks % \AddToHook{begindocument/end} { \gpr_init_names_to_keys:e { \property_ref:nn { gpr } { names-ids } } } \AddToHook{shipout/lastpage} { \property_record:ee { gpr } { names-ids } \prop_map_inline:Nn \g__gpr_references_prop { \gpr_check_backrefs:nn {##1} {##2} } } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Indexing % \cs_new:Nn \gpr_add_to_named_index:nnN { \index[#1]{ #2 \bool_if:nT { #3 && !\tl_if_blank_p:V \l__gpr_main_index_entry_style_tl } { | \tl_use:N \l__gpr_main_index_entry_style_tl } } } \cs_generate_variant:Nn \gpr_add_to_named_index:nnN { VVN, VeN } \cs_new:Nn \gpr_build_index_entry:Nnnn { \seq_clear:N \l_tmpa_seq \clist_map_inline:nn {#2} { \tl_if_blank:vF { l__gpr_##1_tl } { \seq_put_right:Nv \l_tmpa_seq { l__gpr_##1_tl } } } \seq_clear:N \l_tmpb_seq \clist_map_inline:nn {#3} { \tl_if_blank:vF { l__gpr_##1_tl } { \seq_put_right:Nv \l_tmpb_seq { l__gpr_##1_tl } } } \tl_set:Ne #1 { \seq_use:Nn \l_tmpa_seq {~} \bool_if:NTF \l__gpr_nest_index_entries_bool { ! } { ,~ } \seq_use:Nn \l_tmpb_seq {~} \bool_if:NT \l__gpr_id_in_index_entries_bool { ~ (#4) } } } \cs_new:Nn \gpr_add_to_indexes:nN { \tl_if_blank:VF \l__gpr_id_index_tl { \tl_if_novalue:nF {#1} { \gpr_add_to_named_index:VeN \l__gpr_id_index_tl { #1 ~ \tl_use:N \l__gpr_fullname_tl } #2 } } \tl_if_blank:VF \l__gpr_fullname_index_tl { \gpr_add_to_named_index:VVN \l__gpr_fullname_index_tl { \tl_use:N \l__gpr_fullname_tl \bool_if:NT \l__gpr_id_in_index_entries_bool { ~ (\tl_use:N #1) } } #2 } \tl_if_blank:VF \l__gpr_patronymic_index_tl { \tl_if_blank:VF \l__gpr_patronymic_tl { \gpr_build_index_entry:Nnnn \l_tmpa_tl { patronymic, surname, byname } { givenname } { #1 } \gpr_add_to_named_index:VVN \l__gpr_patronymic_index_tl \l_tmpa_tl #2 } } \tl_if_blank:VF \l__gpr_surname_index_tl { \tl_if_blank:VF \l__gpr_surname_tl { \gpr_build_index_entry:Nnnn \l_tmpa_tl { surname, byname } { givenname, patronymic } { #1 } \gpr_add_to_named_index:VVN \l__gpr_surname_index_tl \l_tmpa_tl #2 } } \tl_if_blank:VF \l__gpr_byname_index_tl { \tl_if_blank:VF \l__gpr_byname_tl { \gpr_build_index_entry:Nnnn \l_tmpa_tl { byname } { givenname, patronymic, surname } { #1 } \gpr_add_to_named_index:VVN \l__gpr_byname_index_tl \l_tmpa_tl #2 } } } \cs_generate_variant:Nn \gpr_add_to_indexes:nN { VN } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Styling % \cs_new_protected:Nn \gpr_style_name:N { \seq_clear_new:N \l__gpr_styled_name_seq \seq_map_inline:Nn \g__gpr_name_part_order_seq { \tl_if_blank:vF { l__gpr_##1_tl } { \seq_put_right:Nn \l__gpr_styled_name_seq { \group_begin: \tl_use:c { l__gpr_##1_style_tl } \tl_use:c { l__gpr_##1_tl } \group_end: } } } \tl_set:Nn #1 { \seq_use:Nn \l__gpr_styled_name_seq {~} } } \cs_new_protected:Nn \gpr_format_floruit:N { \int_compare:nNnTF { \seq_count:N \l__gpr_floruit_years_seq } = {1} { \tl_set:Ne #1 { floruit- = { \seq_item:Nn \l__gpr_floruit_years_seq {1} } } } { \int_zero_new:N \l__gpr_floruit_from_int \int_set:Nn \l__gpr_floruit_from_int { 9999 } \int_zero_new:N \l__gpr_floruit_to_int \int_set:Nn \l__gpr_floruit_to_int { 0000 } \seq_map_inline:Nn \l__gpr_floruit_years_seq { \int_set:Nn \l__gpr_floruit_from_int { \int_min:nn {##1} { \l__gpr_floruit_from_int } } \int_set:Nn \l__gpr_floruit_to_int { \int_max:nn {##1} { \l__gpr_floruit_to_int } } } \tl_set:Ne #1 { floruit- = { \int_use:N \l__gpr_floruit_from_int / \int_use:N \l__gpr_floruit_to_int } } } } \cs_new_protected:Nn \gpr_super_subscript:nn { \group_begin: \hbox_set:Nn \l_tmpa_box { \textsuperscript {#1} } \hbox_set:Nn \l_tmpb_box { \textsubscript {#2} } \hbox_to_wd:nn { \dim_max:nn { \box_wd:N \l_tmpa_box } { \box_wd:N \l_tmpb_box } } { \hbox_overlap_right:n { \box_use:N \l_tmpa_box } \box_use:N \l_tmpb_box } \group_end: } \cs_new_protected:Nn \gpr_typeset_reference:nnnN { \property_if_recorded:nTF { gpr/#1/target } { \hyperlink{ \property_ref:nn { gpr/#1/target } { target } } { \gpr__reference_style_handler:nnn { \tl_if_blank:nTF { #3 } { #2 } { % render styled #3 if present \group_begin: \gpr_name_handler:n {#3} \tl_use:N \l__gpr_styled_name_tl \group_end: } } { #1 } { \gpr__page_number_handler:n { \property_ref:nnn { gpr/#1/target } { page } { \gpr__unknown_reference_handler:nV { \bfseries ?? } \c_novalue_tl } } } } } { \gpr_warning:nn { unknown-reference-id } { #1 } \gpr__unknown_reference_handler:nn {#2} { unknown~id: ~ #1 } } \bool_if:NF #4 { \gpr_add_to_indexes:nN { #1 } \c_false_bool } \gpr_save_reference:Vn \l__gpr_id_tl { #1 } } \cs_generate_variant:Nn \gpr_typeset_reference:nnnN { nVnN, VVnN } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % genealogytree package integration % \cs_new_protected:Nn \gpr_set_genealogytree_keys:n { \gtrset{ database/.cd, #1 } } \cs_generate_variant:Nn \gpr_set_genealogytree_keys:n { V } \prg_new_protected_conditional:Nnn \gpr_if_lifespan_defined: {T,F,TF} { \bool_if:nTF { ( \cs_if_exist_p:c { gtrDBbirthyear } || \cs_if_exist_p:c { gtrDBbaptismyear } ) && ( \cs_if_exist_p:c { gtrDBdeathyear } || \cs_if_exist_p:c { gtrDBburialyear } ) } { \prg_return_true: } { \prg_return_false: } } \gtrDeclareDatabaseFormat{gtr_db_format}{}{% \begin{gtrprintlist}{\par}{\unskip,\ }{\unskip.}{\unskip}% \gtr@list@event{birth}% \gtr@list@event{baptism}% \gpr_if_lifespan_defined:F { \gtr@list@event{floruit} } \gtr@list@event{death}% \gtr@list@event{burial}% \end{gtrprintlist}% \gtr@print@infolist% } \gtrset{database~format=gtr_db_format} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % User commands % \NewDocumentEnvironment { gprProfile* } { m O{} } { \bool_if:NT \l__gpr_debug_log_bool { \tl_log:e {\the\inputlineno} \tl_log:n {BEGIN ~ #1} } \group_begin: \keys_set:nn { genealogy-profiles / profile } { #1, life~events={#2} } \tl_if_empty:NTF \l__gpr_fullname_tl { % if no full name supplied, build one from given parts \seq_clear:N \l_tmpa_seq \seq_map_inline:Nn \g__gpr_name_part_order_seq { \tl_if_blank:vF { l__gpr_##1_tl } { \seq_put_right:Nv \l_tmpa_seq { l__gpr_##1_tl } } } \tl_set:Ne \l__gpr_fullname_tl { \seq_use:Nn \l_tmpa_seq {~} } \tl_set_eq:NN \l__gpr_fullname_raw_tl \l__gpr_fullname_tl \tl_clear_new:N \l__gpr_styled_name_tl \gpr_style_name:N \l__gpr_styled_name_tl } { % otherwise use full name to split into parts \tl_set_eq:NN \l__gpr_fullname_raw_tl \l__gpr_fullname_tl \gpr_name_handler:V \l__gpr_fullname_tl } % ensure unique id \tl_if_empty:NTF \l__gpr_id_tl { \bool_if:NTF \l__gpr_autoid_bool { \gpr_generate_unique_id: } { \gpr_error:nn { missing-id } { \l__gpr_fullname_tl } } } { \seq_if_in:NVT \g__gpr_used_profile_ids_seq \l__gpr_id_tl { \gpr_error:nn { reused-id } { \l__gpr_id_tl } } \seq_gput_right:NV \g__gpr_used_profile_ids_seq \l__gpr_id_tl } \gpr_map_name_to_id:NVV \g__gpr_name_to_id_prop \l__gpr_fullname_raw_tl \l__gpr_id_tl \gpr_map_name_to_id:NVV \g__gpr_name_to_id_active_prop \l__gpr_fullname_raw_tl \l__gpr_id_tl % store raw name in aux \property_record:ee { gpr/\tl_use:N \l__gpr_id_tl/name } { raw-name } % init floruit years \property_if_recorded:enT { gpr/\tl_use:N \l__gpr_id_tl/floruit } { floruit-years } { \tl_set:Ne \l_tmpa_tl { \property_ref:ee { gpr/\tl_use:N \l__gpr_id_tl/floruit } { floruit-years } } \seq_set_split:NnV \l__gpr_floruit_years_seq {,} \l_tmpa_tl \tl_clear_new:N \l__formatted_gpr_floruit_tl \gpr_format_floruit:N \l__formatted_gpr_floruit_tl \tl_put_right:Ne \l__gpr_life_events_tl { , \tl_use:N \l__formatted_gpr_floruit_tl } } \seq_clear_new:N \l__gpr_floruit_years_seq % placeholders for id and names \cs_set:Npe \gprID { \tl_use:N \l__gpr_id_tl } \cs_set:Npe \gprStyledName { \tl_use:N \l__gpr_styled_name_tl } \cs_set:Npe \gprFullName { \tl_use:N \l__gpr_fullname_tl } \cs_set:Npe \gprGivenName { \tl_use:N \l__gpr_givenname_tl } \cs_set:Npe \gprPatronymic { \tl_use:N \l__gpr_patronymic_tl } \cs_set:Npe \gprSurname { \tl_use:N \l__gpr_surname_tl } \cs_set:Npe \gprByname { \tl_use:N \l__gpr_byname_tl } \cs_set:Npe \gprHeader { \tl_use:N \l__gpr_header_format_tl } % set hyperref target just before typesetting \MakeLinkTarget*{ gpr/\tl_use:N \l__gpr_id_tl/target } % begin typesetting \tl_use:N \l__gpr_begin_profile_tl % record properties for id \property_record:ee { gpr/\tl_use:N \l__gpr_id_tl/target } { page, target } % add to indexes unless key set \bool_if:NF \l__gpr_no_index_bool { \gpr_add_to_indexes:VN \l__gpr_id_tl \c_true_bool } \bool_if:NT \l__gpr_auto_header_bool { \tl_use:N \l__gpr_begin_header_tl \tl_use:N \l__gpr_header_format_tl \tl_use:N \l__gpr_end_header_tl } % typeset life events \tl_if_empty:NF \l__gpr_life_events_tl { \tl_use:N \l__gpr_begin_life_events_tl \gpr_set_genealogytree_keys:V \l__gpr_life_events_tl \gtrPrintDatabase \tl_use:N \l__gpr_end_life_events_tl } }{ % end typesetting \tl_use:N \l__gpr_end_profile_tl % save floruit years including updates from \gprYear \seq_if_empty:NF \l__gpr_floruit_years_seq { \property_record:ee { gpr/\tl_use:N \l__gpr_id_tl/floruit } { floruit-years } } \group_end: \bool_if:NT \l__gpr_debug_log_bool { \tl_log:e {\the\inputlineno} \tl_log:n {END ~ #1} } } \NewDocumentEnvironment { gprProfile } { O{} m O{} } { \begin{gprProfile*}{fullname={#2}, #1}[#3] }{ \end{gprProfile*} } \cs_new_protected:Nn \gpr_cmd_ref:Nnn { \group_begin: \tl_if_empty:nTF {#2} { % no id provided, so we use the name to try looking it up \gpr_name_handler:n { #3 } \prop_get:NnNTF \g__gpr_name_to_id_active_prop {#3} \l_tmpa_tl { \str_if_eq:VnTF \l_tmpa_tl {_AMBIGUOUS_} { \gpr_warning:nn { ambiguous-reference } { \l__gpr_fullname_tl } \gpr__unknown_reference_handler:Vn \l__gpr_styled_name_tl { ambiguous } \bool_if:NT \l__gpr_include_ambiguous_in_index_bool { \gpr_add_to_indexes:nN { ambiguous } \c_false_bool } } { \gpr_typeset_reference:VVnN \l_tmpa_tl \l__gpr_styled_name_tl {} #1 } } { \gpr_warning:nn { unknown-reference-name } { #3 } \gpr__unknown_reference_handler:Vn \l__gpr_styled_name_tl { unknown~name } \bool_if:NT \l__gpr_include_unknown_in_index_bool { \gpr_add_to_indexes:nN { unknown } \c_false_bool } } } { % id provided, find the name to use for indexes \property_if_recorded:nTF { gpr/#2/name } { \gpr_name_handler:e { \property_ref:nn { gpr/#2/name } { raw-name } } } { \gpr_name_handler:n {#3} } \gpr_typeset_reference:nVnN {#2} \l__gpr_styled_name_tl {#3} #1 } \group_end: } \cs_generate_variant:Nn \gpr_cmd_ref:Nnn { Nee } \NewDocumentCommand { \gprRef } { s O{} m } { \gpr_cmd_ref:Nee #1 {#2} {#3} } \NewDocumentCommand { \gprName } { s m } { \group_begin: \gpr_name_handler:n { #2 } \tl_use:N \l__gpr_styled_name_tl \bool_if:nT { !#1 && \l__gpr_include_unknown_in_index_bool } { \bool_set_false:N \l__gpr_id_in_index_entries_bool \gpr_add_to_indexes:nN { unknown } \c_false_bool } \group_end: } \cs_new_protected:Nn \gpr_cmd_year:n { % record floruit year \seq_if_in:NnF \l__gpr_floruit_years_seq {#1} { \seq_gput_right:Nn \l__gpr_floruit_years_seq {#1} } } \cs_generate_variant:Nn \gpr_cmd_year:n { e } \NewDocumentCommand { \gprYear } { s m } { \gpr_cmd_year:e {#2} \bool_if:NF { #1 } { #2 } } \cs_new_protected:Nn \gpr_cmd_years:n { % split year range \seq_set_split:Nnn \l_tmpa_seq { - } {#1} % first year \seq_pop_left:NN \l_tmpa_seq \l_tmpa_tl \seq_if_in:NVF \l__gpr_floruit_years_seq \l_tmpa_tl { \seq_gput_right:NV \l__gpr_floruit_years_seq \l_tmpa_tl } % last year \seq_pop_right:NN \l_tmpa_seq \l_tmpb_tl \int_set:Nn \l_tmpa_int { \tl_count:N \l_tmpa_tl - \tl_count:N \l_tmpb_tl } \tl_put_left:Ne \l_tmpb_tl { \tl_range:Nnn \l_tmpa_tl { 1 } { \l_tmpa_int } } \seq_if_in:NVF \l__gpr_floruit_years_seq \l_tmpb_tl { \seq_gput_right:NV \l__gpr_floruit_years_seq \l_tmpb_tl } } \cs_generate_variant:Nn \gpr_cmd_years:n { e } \NewDocumentCommand { \gprYears } { s m } { \gpr_cmd_years:e {#2} \bool_if:NF { #1 } { #2 } } \NewDocumentCommand { \gprSpouse } { s o m o } { \gtrsymMarried{} ~ \IfValueT{#4} { #4 ~ } \IfBooleanTF {#1} { \gprRef*[#2]{#3} } { \gprRef[#2]{#3} } }