\RequirePackage{expl3}[2023-10-10] \ProvidesExplPackage{genealogy-profiles}{2024/12/05}{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 } \seq_map_inline:Nn \g__gpr_name_part_order_seq { \tl_if_exist:cF { l__gpr_#1_tl } { \gpr_error:nn { name-part-missing } { #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 { given~name, surname } } {2} { \seq_gset_from_clist:Nn \g__gpr_name_part_order_seq { given~name, 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 , begin~content .tl_set:N = \l__gpr_begin_content_tl , end~content .tl_set:N = \l__gpr_end_content_tl , layout~preset .choices:nn = { clear , tcolorbox } { \int_case:nn { \l_keys_choice_int } { {1} { \tl_clear:N \l__gpr_begin_profile_tl \tl_clear:N \l__gpr_end_profile_tl \tl_clear:N \l__gpr_begin_header_tl \tl_clear:N \l__gpr_end_header_tl \tl_clear:N \l__gpr_begin_life_events_tl \tl_clear:N \l__gpr_end_life_events_tl \tl_clear:N \l__gpr_begin_content_tl \tl_clear:N \l__gpr_end_content_tl } {2} { \bool_set_false:N \l__gpr_auto_header_bool \tl_set:Nn \l__gpr_begin_profile_tl { \begin{tcolorbox}[title = \gprHeader] } \tl_set:Nn \l__gpr_end_life_events_tl { \tcblower } \tl_set:Nn \l__gpr_end_profile_tl { \end{tcolorbox} } } } } , 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 , header~format .tl_set:N = \l__gpr_header_format_tl , auto~header .bool_set:N = \l__gpr_auto_header_bool , % indexes full~name~index .tl_set:N = \l__gpr_full_name_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 } , use~styles~in~index .bool_set:N = \l__gpr_use_styles_in_index_bool , debug~log .bool_set:N = \l__gpr_debug_log_bool } \keys_define:nn { genealogy-profiles / profile } { full~name .tl_set:N = \l__gpr_full_name_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 } } \cs_new_protected:Nn \gpr_create_style_keys:nn { \keys_define:nn { genealogy-profiles } { #1~style .tl_set:c = { l__gpr_#1_style_tl } , #1~index .tl_set:c = { l__gpr_#1_index_tl } , } \keys_define:nn { genealogy-profiles / profile } { #1 .tl_set:c = { l__gpr_#1_tl } , } \tl_set:cn { l__gpr_#1_cmd_tl } { #2 } \exp_args:Nc \NewDocumentCommand { #2 Style } { m } { \gpr_name_part_style:nn { #1 } { ##1 } } } \cs_generate_variant:Nn \gpr_create_style_keys:nn { nV } \NewDocumentCommand { \gprCreateNamePart } { m o } { \IfValueTF { #2 } { \tl_set:Nn \l_tmpa_tl { #2 } } { \tl_set:Nn \l_tmpa_tl { gpr } \seq_set_split:Nnn \l_tmpa_seq { ~ } { #1 } \seq_map_inline:Nn \l_tmpa_seq { \tl_put_right:Nn \l_tmpa_tl { \text_titlecase_first:n { ##1 } } } } \gpr_create_style_keys:nV { #1 } \l_tmpa_tl \seq_new:c { l__gpr_#1_index_before_seq } \seq_new:c { l__gpr_#1_index_after_seq } } \gprCreateNamePart { given~name } \gprCreateNamePart { patronymic } \gprCreateNamePart { surname } \gprCreateNamePart { byname } \gpr_create_style_keys:nn { id } { gprID } % 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 = { \gprFullName \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 } \msg_new:nnn { genealogy-profiles } { profile-count } { There ~ are ~ #1 ~ profiles ~ in ~ this ~ document. } \msg_new:nnnn { genealogy-profiles } { name-part-missing } { Use ~ of ~ unknown ~ name ~ part ~ "#1" ~ on ~ input ~ line ~ #2 } { Create ~ it ~ with ~ gprCreateNamePart ~ first. } \msg_new:nnn { genealogy-profiles } { outside-profile } { The ~ #1 ~ macro ~ should ~ only ~ be ~ used ~ inside ~ profiles, ~ on ~ input ~ line ~ #2 } \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 \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_full_name_raw_tl \property_new:nnnn { raw-name } { now } { \c_novalue_tl } { \tl_use:N \l__gpr_full_name_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 % \cs_new_protected:Nn \gpr_name_handler:n { \tl_set:Nn \l__gpr_full_name_tl {#1} \regex_replace_all:nnN { \s*_\s* } { \ } \l__gpr_full_name_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} } \regex_replace_all:nnN { \s*_\s* } { \ } \l_tmpa_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_full_name_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_full_name_raw_tl } \prop_gput:NVn #1 \l__gpr_full_name_raw_tl {_AMBIGUOUS_} } } { % otherwise save raw name -> id \prop_gput:NVV #1 \l__gpr_full_name_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} } \msg_note:nne { genealogy-profiles } { profile-count } { \seq_count:N \g__gpr_used_profile_ids_seq } } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % 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, vVN } \cs_new_protected:Nn \gpr_get_name_parts:NNN { \seq_clear:N #1 \seq_map_inline:Nn #2 { \tl_if_empty:cF { l__gpr_##1_tl } { \bool_if:NTF #3 { \seq_put_right:Ne #1 { \use:c { \tl_use:c { l__gpr_##1_cmd_tl } Style } { \tl_use:c { l__gpr_##1_tl } } } } { \seq_put_right:Nv #1 { l__gpr_##1_tl } } } } } \cs_generate_variant:Nn \gpr_get_name_parts:NNN { NcN } \cs_new:Nn \gpr_build_index_entry:nnN { \tl_if_empty:cF { l__gpr_#1_tl } { % build before/after seqs if not defined \seq_if_empty:cT { l__gpr_#1_index_before_seq } { \tl_set:Nn \l_tmpa_tl { l__gpr_#1_index_after_seq } \seq_map_inline:Nn \g__gpr_name_part_order_seq { \tl_if_eq:nnT { #1 } { ##1 } { \tl_set:Nn \l_tmpa_tl { l__gpr_#1_index_before_seq } } \seq_put_right:cn { \tl_use:N \l_tmpa_tl } { ##1 } } } \gpr_get_name_parts:NcN \l_tmpa_seq { l__gpr_#1_index_before_seq } \c_false_bool \tl_set:Ne \l_tmpa_tl { \seq_use:Nn \l_tmpa_seq {~} } \bool_if:NTF \l__gpr_nest_index_entries_bool { % nested entries \bool_if:NT \l__gpr_use_styles_in_index_bool { \gpr_get_name_parts:NcN \l_tmpb_seq { l__gpr_#1_index_before_seq } \c_true_bool \tl_put_right:Ne \l_tmpa_tl { @ \seq_use:Nn \l_tmpb_seq {~} } } \gpr_get_name_parts:NcN \l_tmpa_seq { l__gpr_#1_index_after_seq } \c_false_bool \tl_put_right:Ne \l_tmpa_tl { ! \seq_use:Nn \l_tmpa_seq {~} \bool_if:NT \l__gpr_id_in_index_entries_bool { ~ (#2) } } \bool_if:NT \l__gpr_use_styles_in_index_bool { \gpr_get_name_parts:NcN \l_tmpb_seq { l__gpr_#1_index_after_seq } \c_true_bool \tl_put_right:Ne \l_tmpa_tl { @ \seq_use:Nn \l_tmpb_seq {~} \bool_if:NT \l__gpr_id_in_index_entries_bool { ~ (\gprIDStyle{#2}) } } } } { % un-nested entries % we only use a comma if there is an after seq \int_compare:nNnTF { \seq_count:c { l__gpr_#1_index_after_seq } } = { 0 } { \tl_set:Nn \l_tmpb_tl { } } { \tl_set:Nn \l_tmpb_tl { ,~ } } \gpr_get_name_parts:NcN \l_tmpb_seq { l__gpr_#1_index_after_seq } \c_false_bool \tl_put_right:Ne \l_tmpa_tl { \tl_use:N \l_tmpb_tl \seq_use:Nn \l_tmpb_seq {~} \bool_if:NT \l__gpr_id_in_index_entries_bool { ~ (#2) } } \bool_if:NT \l__gpr_use_styles_in_index_bool { \gpr_get_name_parts:NcN \l_tmpa_seq { l__gpr_#1_index_before_seq } \c_true_bool \gpr_get_name_parts:NcN \l_tmpb_seq { l__gpr_#1_index_after_seq } \c_true_bool \tl_put_right:Ne \l_tmpa_tl { @ \seq_use:Nn \l_tmpa_seq {~} \tl_use:N \l_tmpb_tl \seq_use:Nn \l_tmpb_seq {~} \bool_if:NT \l__gpr_id_in_index_entries_bool { ~ (\gprIDStyle{#2}) } } } } \gpr_add_to_named_index:vVN { l__gpr_#1_index_tl } \l_tmpa_tl #3 } } \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_full_name_tl \bool_if:NT \l__gpr_use_styles_in_index_bool { @ \gprIDStyle{#1} ~ \tl_use:N \l__gpr_styled_name_tl } } #2 } } \tl_if_blank:VF \l__gpr_full_name_index_tl { \gpr_add_to_named_index:VeN \l__gpr_full_name_index_tl { \bool_if:NTF \l__gpr_use_styles_in_index_bool { \tl_use:N \l__gpr_styled_name_tl } { \tl_use:N \l__gpr_full_name_tl } \bool_if:NT \l__gpr_id_in_index_entries_bool { ~ (\gprIDStyle{\tl_use:N #1}) } } #2 } \seq_map_inline:Nn \g__gpr_name_part_order_seq { \tl_if_empty:cF { l__gpr_##1_index_tl } { \gpr_build_index_entry:nnN { ##1 } { #1 } #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_empty:cF { l__gpr_##1_tl } { \seq_put_right:Ne \l__gpr_styled_name_seq { \use:c { \tl_use:c { l__gpr_##1_cmd_tl } Style } { \tl_use:c { l__gpr_##1_tl } } } } } \tl_set:Nn #1 { \seq_use:Nn \l__gpr_styled_name_seq {~} } } \cs_new_protected:Nn \gpr_name_part_style:nn { \group_begin: \tl_use:c { l__gpr_#1_style_tl } #2 \group_end: } \cs_generate_variant:Nn \gpr_name_part_style:nn { nv, nV } \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 { % render styled #3 only if present, otherwise use #2 \tl_if_blank:nTF { #3 } { #2 } { \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 } } } } \bool_if:NF #4 { \gpr_add_to_indexes:nN { #1 } \c_false_bool } } { \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 { unknown~id: ~ #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_gtr_keys:n { \gtrset{ #1 } } \cs_new_protected:Nn \gpr_set_gtr_database_keys:n { \gpr_set_gtr_keys:n{ database/.cd, #1 } } \cs_generate_variant:Nn \gpr_set_gtr_database_keys:n { V } \cs_new_protected:Nn \gpr_print_gtr_database: { \gtrPrintDatabase } \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% } \gpr_set_gtr_keys:n { database~format = gtr_db_format } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Environments % \cs_new_protected:Nn \gpr_create_profile_macro:nnn { \exp_args:Nc \DeclareDocumentCommand { #1 } { s } { \IfBooleanTF { ##1 } { #2 } { #3 } } } \cs_generate_variant:Nn \gpr_create_profile_macro:nnn { vvn } \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_full_name_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_empty:cF { l__gpr_##1_tl } { \seq_put_right:Nv \l_tmpa_seq { l__gpr_##1_tl } } } \tl_set:Ne \l__gpr_full_name_tl { \seq_use:Nn \l_tmpa_seq {~} } \tl_set_eq:NN \l__gpr_full_name_raw_tl \l__gpr_full_name_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_full_name_raw_tl \l__gpr_full_name_tl \gpr_name_handler:V \l__gpr_full_name_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_full_name_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_full_name_raw_tl \l__gpr_id_tl \gpr_map_name_to_id:NVV \g__gpr_name_to_id_active_prop \l__gpr_full_name_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:Npn \gprHeader { \tl_use:N \l__gpr_header_format_tl } \gpr_create_profile_macro:nnn { gprID } { \tl_use:N \l__gpr_id_tl } { \gpr_name_part_style:nV { id } \l__gpr_id_tl } \gpr_create_profile_macro:nnn { gprFullName } { \tl_use:N \l__gpr_full_name_tl } { \tl_use:N \l__gpr_styled_name_tl } % name parts \seq_map_inline:Nn \g__gpr_name_part_order_seq { \gpr_create_profile_macro:vvn { l__gpr_##1_cmd_tl } { l__gpr_##1_tl } { \gpr_name_part_style:nv { ##1 } { l__gpr_##1_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_gtr_database_keys:V \l__gpr_life_events_tl \gpr_print_gtr_database: \tl_use:N \l__gpr_end_life_events_tl } % begin content \tl_use:N \l__gpr_begin_content_tl }{ % end content \tl_use:N \l__gpr_end_content_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*}{full~name={#2}, #1}[#3] }{ \end{gprProfile*} } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Reference and name macros % \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_full_name_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~name } \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~name } \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: } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Floruit macros % \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 } { \tl_if_empty:NT \l__gpr_id_tl { \gpr_error:nn { outside-profile } { gprYear } } \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 } { \tl_if_empty:NT \l__gpr_id_tl ( \gpr_error:nn { outside-profile } { gprYears } ) \gpr_cmd_years:e {#2} \bool_if:NF { #1 } { #2 } } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Deprecations % \cs_new:Npn \gprStyledName { \gprFullName } \keys_define:nn { genealogy-profiles } { givenname~style .tl_set:c = { l__gpr_given~name_style_tl } , givenname~index .tl_set:c = { l__gpr_given~name_index_tl } , fullname~index .tl_set:N = \l__gpr_full_name_index_tl , } \keys_define:nn { genealogy-profiles / profile } { givenname .tl_set:c = { l__gpr_given~name_tl } , fullname .tl_set:N = \l__gpr_full_name_tl , } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Experimental! % \NewDocumentCommand { \gprSpouse } { s o m o } { \gtrsymMarried{} ~ \IfValueT{#4} { #4 ~ } \IfBooleanTF {#1} { \gprRef*[#2]{#3} } { \gprRef[#2]{#3} } }