3

I would like to bold-face occurrences of strings conditionally on them occurring within longer strings, using the \IfSubStr and \StrSubstitute macros from the xstring package. The issue that I'm running into is related to expansion of the \makebold macro, which depends on the work of the \surnamefirst and \surnamelast macros. How can I get the \makebold macro to work correctly?

\documentclass{article}

\usepackage{xstring}

\newcommand{\name}{John Doe}

\makeatletter

% Macro to extract and store "last name" from full-name string
\newcommand{\lastname}{%
    \IfSubStr{\name}{.}%
        {\StrBehind{\name}{. }}%
        {\StrBehind{\name}{ }}%
        [\@lastname]
}

% Extract and store first name from full-name string
\newcommand{\firstname}{%
    \StrBefore{\name}{ }[\@firstname]
}

% Extract first initial from full-name string
\newcommand{\firstinitial}{%
    \StrLeft{\name}{1}[\@firstinitial]
}

% Extract middle initial (if any) from full-name string
\newcommand{\middleinitial}{%
    \StrBetween{\name}{ }{.}[\@middleinitial]
}

\newcommand{\testxstring}{%
    \middleinitial % Find middle initial
    \IfStrEq{\@middleinitial}{}%
        {No middle initial!}%
        {Middle initial: \@middleinitial}
}

% Surname first
\newcommand{\surnamefirst}{%
    \firstinitial
    \middleinitial % Find middle initial
    \lastname
    \IfStrEq{\@middleinitial}{}%
        {\@lastname, \@firstinitial.}%
        {\@lastname, \@firstinitial.~\@middleinitial.}
}

% Surname last
\newcommand{\surnamelast}{%
    \firstinitial
    \middleinitial % Find middle initial
    \lastname
    \IfStrEq{\@middleinitial}{}%
        {\@firstinitial.~\@lastname}%
        {\@firstinitial.~\@middleinitial.~\@lastname}
}

% Render full name in bold
\newcommand{\makeboldname}[1]{%
    \expandarg
    \IfSubStr{#1}{\getvalue{name}}%
        {\StrSubstitute{#1}{\name}{\textbf{\name}}\par}%
        {#1}
}

% Render only last name in bold
\newcommand{\makeboldlastname}[1]{%
    \lastname
    \expandarg
    \IfSubStr{#1}{\@lastname}%
        {\StrSubstitute{#1}{\@lastname}{\textbf{\@lastname}}\par}%
        {#1}
}

% Render surname-first and surname-last in bold
\newcommand{\makebold}[1]{%
    \expandarg
    \IfSubStr{#1}{\surnamefirst}%
        {\StrSubstitute{#1}{\surnamefirst}{\textbf{\surnamefirst}}\par}%
        {\IfSubStr{#1}{\surnamelast}%
            {\StrSubstitute{#1}{\surnamelast}{\textbf{\surnamelast}}\par}%
            {#1}%
        }
}

\makeatother

\begin{document}

    \name

    \surnamefirst

    \surnamelast

    \makeboldname{His name was John Doe.}

    \makeboldlastname{Again, Doe was his last name.}

    \makebold{I heard that Doe, J. was from Kentucky.}

    \makebold{There was also a J. Doe from New York.}

\end{document}

Expected output:

John Doe

Doe, J.

J. Doe

His name was John Doe.

Again, Doe was his last name.

I heard that Doe, J. was from Kentucky.

There was also a J. Doe from New York.

6
  • 1
    Please provide a sense of which formats of first, middle, and last names could be encountered.
    – Mico
    Jan 17 '19 at 3:41
  • @Mico Thank you for your comment. Currently, it is only designed for names such as "Bob Smith" or "Bob T. Smith." It would be great if it also worked for full middle names. However, that is not the issue. The issue is that \makebold does not work due to expansion issues with its use of \IfSubStr for calling \surnamefirst or \surnamelast. How can I store the result of \IfSubStr for use in \makebold? Jan 17 '19 at 17:31
  • @Mico In short, I would like a way to store intermediate values for \surnamelast and \surnamefirst. Jan 17 '19 at 17:45
  • I've attempted to streamline some of your write-up. If you think I misunderstood the setup, feel free to revert or to apply further edits.
    – Mico
    Jan 17 '19 at 22:53
  • 1
    Could you please make some real world examples accompanied by the expected output? It's not really clear what you expect from \makeboldname{His name was John Doe.}
    – egreg
    Jan 17 '19 at 23:29
3

This seems to be a job for regular expressions:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\definename}{m}
 {
  \adam_name_define:n { #1 }
 }

\NewDocumentCommand{\makebold}{m}
 {
  \adam_name_makebold:n { #1 }
 }

\NewDocumentCommand{\makeboldlastname}{m}
 {
  \adam_name_makebold_lastname:n { #1 }
 }


\tl_new:N \l_adam_name_full_tl
\tl_new:N \l_adam_name_first_tl
\tl_new:N \l_adam_name_last_tl
\tl_new:N \l_adam_name_initials_tl
\tl_new:N \l__adam_name_input_tl

\cs_new_protected:Nn \adam_name_define:n
 {
  % store the full name
  \tl_set:Nn \l_adam_name_full_tl { #1 }
  % duplicate it in two token lists to be modified
  \tl_set_eq:NN \l_adam_name_first_tl \l_adam_name_full_tl
  \tl_set_eq:NN \l_adam_name_last_tl \l_adam_name_full_tl
  % remove everything from the last space to the end (first name)
  \regex_replace_once:nnN { (.*)\s[^\s]*\Z } { \1 } \l_adam_name_first_tl
  % remove everything up to the last space (surname)
  \regex_replace_once:nnN { .*\s([^\s]*)\Z } { \1 } \l_adam_name_last_tl
  % duplicate the token list with the first name
  \tl_set_eq:NN \l_adam_name_initials_tl \l_adam_name_first_tl
  % remove letters after the first in the first name part, replacing them with a period (initials)
  \regex_replace_all:nnN { ([[:alpha:]])[[:alpha:]]+ } { \1. } \l_adam_name_initials_tl
 }

\cs_new_protected:Nn \adam_name_makebold:n
 {
  \tl_set:Nn \l__adam_name_input_tl { #1 }
  % \u{<token list name>} stands for the contents of the token list
  % we'll search for “full name” or “initials surname” or
  % “surname, first name” or “surname, initials”
  % and replace the match with \textbf{\match}
  \regex_replace_once:nnN
   {
    (
     \u{l_adam_name_full_tl}                                   % name surname
     |
     \u{l_adam_name_initials_tl} \s \u{l_adam_name_last_tl}    % initials surname
     |
     \u{l_adam_name_last_tl} , \s \u{l_adam_name_first_tl}     % surname, name
     |
     \u{l_adam_name_last_tl} , \s \u{l_adam_name_initials_tl}  % surname, initials
    )
   }
   { \c{textbf} \cB\{ \1 \cE\} }
   \l__adam_name_input_tl
  % print
  \tl_use:N \l__adam_name_input_tl
 }

\cs_new_protected:Nn \adam_name_makebold_lastname:n
 {
  \tl_set:Nn \l__adam_name_input_tl { #1 }
  % search for the surname and replace it with \textbf{<match>}
  \regex_replace_once:nnN
   { (\u{l_adam_name_last_tl}) }
   { \c{textbf} \cB\{ \1 \cE\} }
   \l__adam_name_input_tl
  \tl_use:N \l__adam_name_input_tl
 }

\ExplSyntaxOff

\begin{document}

\definename{John Doe}

\makebold{His name was John Doe.}

\makebold{His name was J. Doe.}

\makebold{His name was Doe, John.}

\makebold{His name was Doe, J. again.}

\makeboldlastname{Again, Doe was his last name.}

\definename{John T. Doe}

\makebold{His name was John T. Doe.}

\makebold{His name was J. T. Doe.}

\makebold{His name was Doe, John T. again.}

\makebold{His name was Doe, J. T. again.}

\makeboldlastname{Again, Doe was his last name.}

\end{document}

enter image description here

12
  • Thank you! This does not use xstring but is nonetheless a correct solution using regex, albeit using experimental LaTeX3 syntax. What document would you recommend reading to understand the new syntax? Similar to others, I love the power LaTeX3 provides, but I find the syntax rather atrocious and unlike TeX. Perhaps if I get to know it well, it may grow on me. No other syntax gives me this much trouble. Even brainf*ck is easier to read. Jan 18 '19 at 1:55
  • How can I implement this when name is defined in a xparse key-value store and retrieved with this function: \cs_new:Npn \getvalue#1{% \prop_item:Nn \list_prop {#1} } or \getvalue{name}? Jan 18 '19 at 20:08
  • Also, this solution fails to match J. Doe in Jackson, Bob and J. Doe. Jan 18 '19 at 20:48
  • 1
    @AdamErickson I get a bold J. Doe also from \makebold{Jackson, Bob and J. Doe.}` Please, don't pile up questions in comments. This is an altogether different question and you should be more specific when asking.
    – egreg
    Jan 18 '19 at 21:06
  • 1
    @AdamErickson No, roll back and ask a new question. Please, don't attempt to show an implementation; just state how you wish the syntax to be and what's the expected output.
    – egreg
    Jan 18 '19 at 22:05
0

As promised, below is an answer using xstring by its author, Christian Tellechea.

\documentclass{article}

\usepackage[T1]{fontenc}

\usepackage{xstring}

\def\makebold#1#2{%
    \begingroup\expandarg
    \StrCount{#1}{ }[\nbspaces]%
    \StrCut[\nbspaces]{#1}{ }\firstname\lastname
    \StrSplit\firstname1\firstnameinitial\fisrtnameremain
    \IfSubStr\fisrtnameremain{ }
        {\StrBehind\fisrtnameremain{ }[\fisrtnameremain]}
        {\let\fisrtnameremain\empty}%
    \edef\firstnameinitial{\firstnameinitial.\ifx\fisrtnameremain\empty\else\space\fi\fisrtnameremain}%
    \edef\stringA{#1}%
    \edef\stringB{\firstnameinitial\space\lastname}%
    \edef\stringC{\lastname, \firstname}%
    \edef\stringD{\lastname, \firstnameinitial}%
    \StrSubstitute{#2}\stringA{\expandafter\textbf\expandafter{\stringA}}[\finaltext]%
    \StrSubstitute\finaltext\stringB{\expandafter\textbf\expandafter{\stringB}}[\finaltext]%
    \StrSubstitute\finaltext\stringC{\expandafter\textbf\expandafter{\stringC}}[\finaltext]%
    \StrSubstitute\finaltext\stringD{\expandafter\textbf\expandafter{\stringD}}[\finaltext]%
    \StrSubstitute\finaltext\lastname{\expandafter\textbf\expandafter{\lastname}}[\finaltext]%
    \finaltext
    \endgroup
}

\begin{document}

    \makebold{John Doe}{His name was John Doe.}

    \makebold{John Doe}{His name was J. Doe.}

    \makebold{John Doe}{His name was Doe, John.}

    \makebold{John Doe}{Again, Doe was his last name.}
    \bigskip

    \makebold{John T. Doe}{His name was John T. Doe.}

    \makebold{John T. Doe}{His name was J. T. Doe.}

    \makebold{John T. Doe}{His name was Doe, John T. again.}

    \makebold{John T. Doe}{His name was Doe, J. T. again.}

    \makebold{John T. Doe}{Again, Doe was his last name.}

\end{document}

The output is as expected. It's definitely less verbose, though regex is more universal.

1
  • You should try this with Rémy Levêque.
    – egreg
    Jan 18 '19 at 21:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.