summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/genealogy-profiles/latex/genealogy-profiles.sty
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/contrib/genealogy-profiles/latex/genealogy-profiles.sty')
-rw-r--r--macros/latex/contrib/genealogy-profiles/latex/genealogy-profiles.sty848
1 files changed, 848 insertions, 0 deletions
diff --git a/macros/latex/contrib/genealogy-profiles/latex/genealogy-profiles.sty b/macros/latex/contrib/genealogy-profiles/latex/genealogy-profiles.sty
new file mode 100644
index 0000000000..69f1cf00d7
--- /dev/null
+++ b/macros/latex/contrib/genealogy-profiles/latex/genealogy-profiles.sty
@@ -0,0 +1,848 @@
+\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 <mikkel.eriksen@gmail.com>
+
+\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}
+ }
+} \ No newline at end of file