summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/genealogy-profiles
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2024-01-25 03:00:57 +0000
committerNorbert Preining <norbert@preining.info>2024-01-25 03:00:57 +0000
commit768d5db2700eb846ed6616dc4839993f193b22cd (patch)
treec06578df8e40e137d12018e0a4ba053466c86ca1 /macros/latex/contrib/genealogy-profiles
parent30e494c68ef358eeb09d53920dd0658dc9238f51 (diff)
CTAN sync 202401250300
Diffstat (limited to 'macros/latex/contrib/genealogy-profiles')
-rw-r--r--macros/latex/contrib/genealogy-profiles/README.md13
-rw-r--r--macros/latex/contrib/genealogy-profiles/docs/genealogy-profiles.pdfbin0 -> 132492 bytes
-rw-r--r--macros/latex/contrib/genealogy-profiles/docs/genealogy-profiles.preamble.sty87
-rw-r--r--macros/latex/contrib/genealogy-profiles/docs/genealogy-profiles.tex601
-rw-r--r--macros/latex/contrib/genealogy-profiles/latex/genealogy-profiles.sty848
5 files changed, 1549 insertions, 0 deletions
diff --git a/macros/latex/contrib/genealogy-profiles/README.md b/macros/latex/contrib/genealogy-profiles/README.md
new file mode 100644
index 0000000000..eb4dd087ef
--- /dev/null
+++ b/macros/latex/contrib/genealogy-profiles/README.md
@@ -0,0 +1,13 @@
+Genealogical profile macros for LaTeX
+=====================================
+
+Macros and environments for presenting genealogical profiles.
+
+See [genealogy-profiles.pdf](docs/genealogy-profiles.pdf) for the documentation.
+
+Contents last updated 2024-01-24.
+
+<!-- Package available via CTAN at https://www.ctan.org/pkg/genealogy-profiles -->
+
+License: CC-BY-SA 4.0
+Author: Mikkel Eide Eriksen <mikkel.eriksen@gmail.com>
diff --git a/macros/latex/contrib/genealogy-profiles/docs/genealogy-profiles.pdf b/macros/latex/contrib/genealogy-profiles/docs/genealogy-profiles.pdf
new file mode 100644
index 0000000000..bf030bf364
--- /dev/null
+++ b/macros/latex/contrib/genealogy-profiles/docs/genealogy-profiles.pdf
Binary files differ
diff --git a/macros/latex/contrib/genealogy-profiles/docs/genealogy-profiles.preamble.sty b/macros/latex/contrib/genealogy-profiles/docs/genealogy-profiles.preamble.sty
new file mode 100644
index 0000000000..a2dda4a26d
--- /dev/null
+++ b/macros/latex/contrib/genealogy-profiles/docs/genealogy-profiles.preamble.sty
@@ -0,0 +1,87 @@
+\usepackage{imakeidx}
+\indexsetup{noclearpage}
+\makeindex
+
+\usepackage{needspace}
+
+\usepackage[english]{babel}
+%\usepackage[
+% backend=biber,
+% urldate=long,
+% date=long,
+%]{biblatex}
+%\addbibresource{genealogy-profiles.bib}
+
+\usepackage{enumitem}
+\usepackage[
+% hidelinks,
+]{hyperref}
+
+\usepackage{tcolorbox}
+\tcbuselibrary{documentation}
+\tcbuselibrary{minted}
+\tcbuselibrary{breakable}
+
+\definecolor{DarkDefinition}{rgb}{0.392,0.392,0.942}
+
+\tcbset{
+ listing engine=minted,
+ color definition=DarkDefinition,
+ floatplacement=h,
+ index colorize=true,
+ doc head={
+ interior style={
+ fill,
+ color=blue!10,
+ },
+ boxsep=2pt,
+ after skip=0pt,
+ nobeforeafter,
+ },
+ doc raster={
+ raster after skip=0pt,
+ },
+ before doc body={
+ \begin{tcolorbox}[
+ colback=blue!5,
+ colframe=blue!5,
+ arc=0pt,
+ outer arc=0pt,
+ before skip=0pt,
+ nobeforeafter,
+ ]
+ },
+ after doc body={\end{tcolorbox}},
+ index format=off, % to get the profile index title to show up
+}
+
+\AtBeginEnvironment{tcb@manual@entry}{%
+ \needspace{4\baselineskip}%
+}
+
+\setlist[description]{
+ leftmargin=5em,
+ rightmargin=\leftmargin,
+ itemindent=\labelwidth,
+ align=right,
+ font=\docAuxKey,
+ noitemsep,
+}
+
+\usepackage{csquotes}
+
+\usepackage{fnpct}
+
+\usepackage{cleveref}
+
+\makeindex[name=gpr-surnames, title={Profile Index}]
+
+\usepackage{genealogy-profiles}
+
+\gprKeys{
+ patronymic index = gpr-surnames,
+ surname index = gpr-surnames,
+ byname index = gpr-surnames,
+}
+
+
diff --git a/macros/latex/contrib/genealogy-profiles/docs/genealogy-profiles.tex b/macros/latex/contrib/genealogy-profiles/docs/genealogy-profiles.tex
new file mode 100644
index 0000000000..e65b90aa9a
--- /dev/null
+++ b/macros/latex/contrib/genealogy-profiles/docs/genealogy-profiles.tex
@@ -0,0 +1,601 @@
+% !TEX TS-program = LuaLaTeX-shell-escape
+\documentclass[
+ a4paper,
+]{article}
+
+\usepackage{genealogy-profiles.preamble}
+
+\title{Genealogical Profiles for \LaTeX}
+\author{Mikkel Eide Eriksen\\%
+ \href{mailto:mikkel.eriksen@gmail.com}{\texttt{mikkel.eriksen@gmail.com}}}
+
+\newcommand\brackval[1]{\brackets{\docValue{#1}}}
+
+\begin{document}
+
+\maketitle
+
+\section{Preface} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+This package enables the presenstation of individual \emph{profiles}, which may be useful for genealogical or local history treatises.
+
+Each profile is typeset using key/value-configurable environments, and a number of macros are provided to enable references and name formatting.
+
+Issues can be reported at\\\null\hfill\url{https://github.com/mikkelee/latex-genprofile/issues}
+
+\begin{figure}
+\begingroup
+\gprKeys{
+ name type = given and surname,
+ surname style = \scshape,
+ begin profile = {
+ \begin{tcolorbox}[title = \gprHeader]
+ },
+ begin life events = \small,
+ end life events = \tcblower,
+ end profile = \end{tcolorbox},
+}
+\begin{gprProfile}{Wolfgang_Amadeus Mozart}[
+ birth = {1756-01-27}{Getreidegasse 9, Salzburg},
+ death = {1791-12-05}{Vienna}
+]
+ Had a complex relationship with his rival \gprRef{Antonio Salieri}.
+\end{gprProfile}
+\begin{gprProfile}{Antonio Salieri}[
+ birth = {1750-08-18}{Legnago, Republic of Venice},
+ death = {1825-05-07}{Vienna}
+]
+ Was falsely accused of poisoning \gprRef[WM1]{_ Mozart}.
+\end{gprProfile}
+\endgroup
+\end{figure}
+
+\clearpage
+\section{Configuration} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+The package is configured in the following manner:
+
+\begin{docCommand}
+ {usepackage}
+ {\brackets{genealogy-profiles}}
+
+ Loads the package and sets some sensible defaults as further described below.
+
+\end{docCommand}
+
+\begin{docCommand}
+ {gprKeys}
+ {\marg{general options}}
+
+ Can be used to set options globally (in the preamble) or locally (in a group). See \cref{sec:genconf} for possible keys/values.
+
+\end{docCommand}
+
+\section{Usage} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\begin{docEnvironment}
+ {gprProfile}
+ {\oarg{profile options}\marg{name}\oarg{life events}}
+
+ Typesets its contents according to the configured layout (see \cref{sec:structure}). For possible profile options, see \cref{sec:profopts}.
+
+ The name will be parsed according to the current \refKey{name part order} (see \cref{sec:nameparsing} for discussion) and styled according to \refKey{givenname style} etc.
+
+ Each profile must have an ID, either supplied by the author via the \refKey{id} profile option key or automatically generated via the general \refKey{auto id} key.
+
+ The \refKey{life events} will be parsed as a database by the \href{https://ctan.org/pkg/genealogytree}{genealogytree} package.
+
+\end{docEnvironment}
+
+\begin{docEnvironment}
+ {gprProfile*}
+ {\marg{profile options}\oarg{life events}}
+
+ The \refEnv*{gprProfile*} environment provides more control at the expense of convenience, by requiring the name part(s) be set explicitly as profile options. See \refKey{givenname} etc.
+
+\end{docEnvironment}
+
+
+\begin{docCommands}[
+ doc name = {gprName}
+ ]
+ {
+ { doc parameter = {\marg{name}}},
+ { doc parameter = {\sarg\marg{name}}},
+ }
+
+ Typesets a name, styled according to \refKey{givenname style} etc.
+
+ The regular version adds the name to any indices, the starred veresion does not.
+
+\end{docCommands}
+
+
+\begin{docCommands}[
+ doc name = {gprRef}
+ ]
+ {
+ { doc parameter = {\oarg{id}\marg{name}}},
+ { doc parameter = {\sarg\oarg{id}\marg{name}}},
+ }
+
+ Typesets a reference to a profile.
+
+ The ID is optional, in case they are not known or available at the time of writing. However, if the name is unique to the document, the reference should automatically be recognized.
+
+ If it is not possible for the package to identify the intended reference, either via ID or unique name, warnings will be emitted in the log as well as in the document (the latter can be configured via the \refKey{unknown reference style}).
+
+ When using an ID, the name parameter can be left empty, or alternately be used to override the displayed name, eg. to change case to genitive, etc.
+
+ The regular version adds the reference to configured indices, the starred version does not.
+
+ These commands require two runs to account for forward references.
+\end{docCommands}
+
+
+\begin{docCommands}[
+ doc name = {gprYear}
+ ]
+ {
+ { doc parameter = {\marg{year}}},
+ { doc parameter = {\sarg\marg{year}}},
+ }
+
+ All tagged years in a profile will be gathered and inserted as a range into a \docValue*{floruit} life event, which will by default only be displayed if there is no defined lifespan (ie. birth or baptism \emph{and} death or burial).
+
+ The starred version does not typeset anything, and can thus be used to add \enquote{hidden} years to the floruit event.
+
+ These commands require two runs.
+\end{docCommands}
+
+
+\begin{docCommands}[
+ doc name = {gprYears}
+ ]
+ {
+ { doc parameter = {\marg{year range}}},
+ { doc parameter = {\sarg\marg{year range}}},
+ }
+
+ Adds two years to the \docValue*{floruit} event, by splitting at one or more hyphens.
+
+ Values such as \docValue*{1750--1755} or \docValue*{1750--55} will both be parsed as the two years \docValue*{1750} and \docValue*{1755} and typesets as the expected 1750--1755 or 1750--55, respectively.
+
+ Like \refCom{gprYear}, the starred version produces no output, and two runs are required.
+\end{docCommands}
+
+
+\clearpage
+\section{Profile Layout} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\label{sec:structure}
+The typeset profiles are laid out according to the following structure.
+
+\begin{enumerate}
+
+\item The contents of \refKey{begin profile}
+
+\item If \refKey{auto header} is \docValue*{true}:
+ \begin{enumerate}
+ \item The contents of \refKey{begin header}
+ \item The contents of \refKey{header format}
+ \item The contents of \refKey{end header}
+ \end{enumerate}
+
+\item If \refKey{life events} and/or \refCom{gprYear}(s) were used:
+ \begin{enumerate}
+ \item The contents of \refKey{begin life events}.
+ \item The events formatted according to the database format configured via the \href{https://ctan.org/pkg/genealogytree}{genealogytree} package. The provided default simply lists life events separated by commas.
+ \item The contents of \refKey{end life events}
+ \end{enumerate}
+
+\item The content provided to the environment by the author.
+
+\item The contents of \refKey{end profile}
+
+\end{enumerate}
+
+
+\section{Name Parsing} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\label{sec:nameparsing}
+Names are parsed according to the configured \refKey{name part order} (some presets are provided with \refKey{name type}), in a left-to-right evaluation.
+
+In order for single name parts to include multiple separate tokens (eg. multiple given names), underscores can be used to combine them. For example, \docValue*{Wolfgang\_Amadeus Mozart} will, with the default \docValue{given and surname} name type, be parsed as the given name(s) \docValue*{Wolfgang Amadeus} and the surname \docValue*{Mozart}.
+
+If the \refKey{name part order} has more parts than the supplied value, the right-most parts will be empty. If this is not desired, one may mark empty name parts with a single underscore; for example, using the \docValue{nordic historical} name type, \docValue*{Jens _ Smed} will parse as the given name \docValue*{Jens}, no patronymic, and the byname \docValue*{Smed} (ie. blacksmith).
+
+Inside a profile environment, a number of shortcuts are provided to access the available name parts, as well as the ID and a full name styled according to the name style keys.
+
+
+\clearpage
+\section{Profile Macros} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+A number of extra macros are available inside profiles to allow accessing some key values.
+
+\begin{docCommand}
+ {gprHeader}
+ {\brackets{}}
+
+ A header styled according to \refKey{header format}.
+
+ If \refKey{auto header} is \docValue*{false}, one may wish to use \refCom{gprHeader} to manually insert the header at the desired location (see \cref{sec:examples} for an example).
+
+\end{docCommand}
+
+
+\begin{docCommands}[
+ doc parameter = {\brackets{}}
+ ]
+ {
+ { doc name = gprID},
+ { doc name = gprStyledName},
+ { doc name = gprFullName},
+ { doc name = gprGivenName},
+ { doc name = gprPatronymic},
+ { doc name = gprSurname},
+ { doc name = gprByname},
+ }
+
+ Typesets the ID and name parts accordig to configured styles.
+
+ The styled name is formatted according to the style keys, see \refKey{givenname style} etc.
+
+\end{docCommands}
+
+
+\subsection{Option Keys} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\subsubsection{General Options} %---------------------------------------------------------
+
+\label{sec:genconf}
+These are used with the \refCom{gprKeys} command, either globally in the preamble or locally in a group.
+
+\begin{docKey}
+ {auto header}
+ {=\meta{true/false}}
+ {initially \docValue*{false}}
+
+ Automatically inserts a header using \refKey{header format} at the beginning of profiles. See \cref{sec:structure}.
+\end{docKey}
+
+\begin{docKey}
+ {auto id}
+ {=\meta{true/false}}
+ {initially \docValue*{true}}
+
+ Generates an ID for each profile if no \refKey{id} is supplied. The format is the name part initials combined with a number to ensure uniqueness.
+
+ If no ID is set either automatically or manually, an error is emitted.
+\end{docKey}
+
+\begin{docKey}
+ {auto id prefix}
+ {=\meta{...}}
+ {initially not set}
+
+ Prefixes auto-generated IDs with a string, which may be useful for works containing multiple sections.
+\end{docKey}
+
+\begin{docKeys}
+ [
+ doc parameter = {=\meta{...}},
+ doc description = {initially not set},
+ ]
+ {
+ {
+ doc name = begin profile,
+ },
+ {
+ doc name = begin header,
+ },
+ {
+ doc name = end header,
+ },
+ {
+ doc name = begin life events,
+ },
+ {
+ doc name = end life events,
+ },
+ {
+ doc name = end profile,
+ },
+ }
+
+ These keys allow configuring arbitrary \LaTeX{} code to be inserted before/during/after the typeset \refEnv{gprProfile} and \refEnv{gprProfile*} environments (see \cref{sec:structure}).
+
+\end{docKeys}
+
+\begin{docKey}
+ {header format}
+ {=\meta{...}}
+ {initially \brackets{\docValue*{\cs{gprStyledName}\brackets{}\cs{hfill}\cs{gprID}\brackets{}}}}
+
+ Formats a profile header. See \refCom{gprID} etc. for available macros.
+\end{docKey}
+
+\begin{docKeys}
+ [
+ doc parameter = {=\meta{...}},
+ ]
+ {
+ {
+ doc name = name type,
+ doc description = {initially \docValue{given and surname}}
+ },
+ {
+ doc name = name part order,
+ doc description = {initially \brackets{\docValue*{givenname, surname}}}
+ },
+ }
+
+ The \refKey*{name part order} is used for splitting a \refKey{fullname} to its constituent parts for formatting, index entries, and (if configured) IDs.
+
+ Using the \refKey*{name type} key provides access to a number of preconfigured \refKey*{name part order}s:
+
+ \begin{itemize}
+ \item \docValue{given and surname} will set the \refKey*{name part order} key to \brackets{\docValue*{givenname, surname}} (the default).
+ \item \docValue{nordic historical} will set the \refKey*{name part order} key to \brackets{\docValue*{givenname, patronymic, byname}}, commonly used in Scandinavia and the rest of the nordic countries.
+ \end{itemize}
+
+ If no preset \refKey*{name type} exists for the intended use case, the \refKey*{name part order} can be set directly (suggestions are welcome).
+\end{docKeys}
+
+\begin{docKeys}
+ [
+ doc parameter = {=\meta{...}},
+ doc description = {initially not set},
+ ]
+ {
+ {
+ doc name = givenname style,
+ },
+ {
+ doc name = patronymic style,
+ },
+ {
+ doc name = surname style,
+ },
+ {
+ doc name = byname style,
+ },
+ }
+
+ These keys set the styling for each name part, which will be available as the \refCom{gprStyledName}.
+
+\end{docKeys}
+
+\begin{docKeys}
+ [
+ doc parameter = {=\meta{...}},
+ doc description = {initially not set},
+ ]
+ {
+ {
+ doc name = id index,
+ },
+ {
+ doc name = fullname index,
+ },
+ {
+ doc name = givenname index,
+ },
+ {
+ doc name = patronymic index,
+ },
+ {
+ doc name = surname index,
+ },
+ {
+ doc name = byname index,
+ },
+ }
+
+ Setting these keys will cause \refEnv{gprProfile} and \refCom{gprRef} to emit index data to the named index. The index must be created with eg. \texttt{imakeidx} before using.
+
+ They can point to the same index (eg. one may wish to use one index for patronymics, surnames, and/or bynames). In fact, that has been set for this document (see final page).
+
+ At the end of the document, \docAuxCommand*{printindex} can then be used for each index as normal.
+
+\end{docKeys}
+
+\begin{docKey}
+ {id in index entries}
+ {=\meta{true/false}}
+ {initially not set}
+
+ Causes index entries to include the IDs in parentheses.
+\end{docKey}
+
+\begin{docKeys}
+ [
+ doc parameter = {=\meta{true/false}},
+ doc description = {initially not set},
+ ]
+ {
+ {
+ doc name = include unknown in index,
+ },
+ {
+ doc name = include ambiguous in index,
+ },
+ }
+
+ Causes the indexes to include references to persons with unknown IDs or ambiguous names, which can be used for correcting drafts.
+
+\end{docKeys}
+
+\begin{docKey}
+ {main index entry style}
+ {=\meta{...}}
+ {initially not set}
+
+ Adds formatting to the main index entry page numbers (ie. the ones pointing to the profile), leaving the ones reference with \refCom{gprRef} untouched; for example \docValue*{textbf} will bold the main entry.
+\end{docKey}
+
+\begin{docKey}
+ {nest index entries}
+ {=\meta{true/false}}
+ {initially not set}
+
+ Causes index entries to be nested under the various patronymics/surnames/bynames.
+\end{docKey}
+
+\begin{docKey}
+ {reference style}
+ {=\meta{...}}
+ {see below}
+
+ Formats references. The value is expanded with the arguments \docValue*{\#1} being the name, \docValue*{\#2} being the ID, and \docValue*{\#2} being the page reference. The default is to present these as the name followed by combined super- and subscripts (see also \cref{sec:examples} for another style).
+\end{docKey}
+
+\begin{docKey}
+ {unknown reference style}
+ {=\meta{...}}
+ {see below}
+
+ Formats unknown references. The value is expanded with the arguments \docValue*{\#1} being the name supplied by \refCom{gprRef} and \docValue*{\#2} being a short description of the reason. The default is to present the name as red text with the reason (unknown/ambiguous) following in parentheses.
+\end{docKey}
+
+
+\subsubsection{Profile Options} %---------------------------------------------------------
+
+\label{sec:profopts}
+These are used with the \refEnv{gprProfile} and \refEnv{gprProfile*} environments.
+
+\begin{docKey}
+ {id}
+ {=\meta{...}}
+ {initially not set}
+
+ Sets an ID for the profile. If none is specified and \refKey{auto id} is \docValue*{true}, one will be generated from name initials combined with a number to ensure uniqueness.
+
+ If an already used ID is specified, an error will be emitted.
+
+ Likewise, not specifying an ID while \refKey{auto id} is \docValue*{false} will cause an error.
+\end{docKey}
+
+\begin{docKey}
+ {fullname}
+ {=\marg{...}}
+ {initially not set}
+
+ Sets the full name of the person. If it is specified, the configured \refKey{name part order} will be used to set the individual name parts.
+
+ If it is not specified, one will be generated by combining the given name parts according to the configured \refKey{name part order}, using the below keys. See \cref{sec:nameparsing} for further details.
+\end{docKey}
+
+\begin{docKeys}
+ [
+ doc parameter = {=\meta{...}},
+ doc description = {initially not set},
+ ]
+ {
+ {
+ doc name = givenname,
+ },
+ {
+ doc name = patronymic,
+ },
+ {
+ doc name = surname,
+ },
+ {
+ doc name = byname,
+ },
+ }
+
+ Sets individual name parts.
+
+\end{docKeys}
+
+\begin{docKey}
+ {life events}
+ {=\meta{...}}
+ {initially not set}
+
+ Populates a \href{https://ctan.org/pkg/genealogytree}{genealogytree} database, which will be typeset according to the settings of that package; refer to its documenation for configuration. Simple display defaults have been provided.
+
+ For convenience, this key can be set with the final optional argument of the \refEnv{gprProfile} and \refEnv{gprProfile*} environments.
+\end{docKey}
+
+\begin{docKey}
+ {no index}
+ {=\meta{true/false}}
+ {initially not set}
+
+ Skips adding index entries for this profile.
+\end{docKey}
+
+\clearpage
+\section{Examples} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\label{sec:examples}
+
+\subsection{Using \texttt{tcolorbox} \& \docValue*{nordic historical}}
+
+A simple example to show name parsing and use of \refCom{gprRef} and \refCom{gprYear}.
+\begin{dispExample}
+\gprKeys{
+ name type = nordic historical,
+ patronymic style = \itshape,
+ byname style = \scshape,
+ begin profile = {\begin{tcolorbox}[title = \gprHeader]},
+ end life events = \tcblower,
+ end profile = \end{tcolorbox},
+}
+
+\begin{gprProfile}{Jens Hansen}[ birth = {1790}{Denmark} ]
+ Wife: \gprRef{Anne_Marie Olsdatter}.
+
+ Let's also tag some years:
+ \gprYear{1830}, \gprYear{1835}, and \gprYear{1840}.
+\end{gprProfile}
+
+\begin{gprProfile}{Anne_Marie Olsdatter}[ birth = {1795}{Denmark} ]
+ Husband: \gprRef{Jens Hansen}.
+\end{gprProfile}
+
+\begin{gprProfile}{Jens Hansen Smed}
+ An unrelated person with a byname.
+\end{gprProfile}
+
+\end{dispExample}
+
+\clearpage
+\subsection{Using \texttt{hrule} \& \docValue*{given and surname}}
+
+A simple example to show different layout and reference styles.
+
+\begin{dispExample}
+\newcommand\spacedrule{\vspace*{5pt}\hrule\vspace*{5pt}}
+\gprKeys{
+ name type = given and surname,
+ surname style = \scshape,
+ auto header,
+ begin profile = \spacedrule,
+ end life events = \spacedrule,
+ end profile = \spacedrule,
+ reference style = {#1\footnote{#2, #3}}
+}
+
+\begin{gprProfile}{George Washington}[
+ birth = {1732-02-22}{Popes Creek, Virginia Colony},
+ death = {1799-12-14}{Mount Vernon, Virginia, U.S.}
+]
+ Attended the first \gprRef[WM1]{_ Mozart} performance
+ in America in \gprYear{1784}.
+\end{gprProfile}
+
+\end{dispExample}
+
+\clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\small % because index format=off
+\printindex
+
+\indexprologue{This index was created by setting the keys \refKey*{patronymic index}, \refKey*{surname index}, and \refKey*{byname index} all to the same index.}
+\printindex[gpr-surnames]
+
+\end{document} \ No newline at end of file
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