Package for special treatment of sequences of capitals

For linguistic writing, I frequently deal with input data where sequences of capitals should be typeset not as capitals, but should be treated specially, e.g. be typeset in lower case, small caps. Ideally, I would like a package that provides a macro "morphemize" that scans through a string and applies a user-defined macro "morpheme" to any string of more than a single capital in there. This is the idea:

\morphemize{ pro.3PERS.FEM be.PAST-HABIT here}

should evaluate to:

pro.3\morpheme{PERS}.\morpheme{FEM} be.\morpheme{PAST}-\morpheme{HABIT} here

As far as I know there's nothing exactly like this out there, and I'll need to write a macro on my own. So, pointers to something similar would be appreciated.

Disclaimer: I'm not a linguist. From what I understand only multiple uppercase characters (A-Z) should be handled differently. Everything else (single upper case characters, a-z, punctuation, etc.) should be left untouched.

The user interface below consists of the \morphemize and \morpheme commands, as requested. Additionally, I have added the \xformupper command, which is applied to each uppercase character in a string of uppercase characters (this allows \lowercase to be used while maintaining expandability).

The basic process is to first split the argument of \morphemize at the spaces using \spacesplit@morphemize and pass the chunks to \@morphemize for uppercase sequence identification. This is required to preserve the spaces.

In \@morphemize, the command \is@upper is used to test for uppercase. If two are found in a row, then \@stringofuppers is used to get the uppercase string which is passed to \morpheme for processing. The \@stringoflowers strips the uppercase string and processes the remaining content with \@morphemize.

The solution (' added to demonstrate that pre and post spaces are preserved):

\makeatletter
%document-level command to apply \morpheme to strings of uppercase characters (wrapper for \@morphemize)
\newcommand{\morphemize}[1]{\spacesplit@morphemize#1 \nil\unskip}
%document-level command applied to strings of uppercase characters
\newcommand{\morpheme}[1]{\textsc{#1}}
%document-level command applied to individual uppercase characters in strings up uppercase characters
\newcommand{\xformupper}[1]{\lowercase{#1}}

%Recursive command to split at spaces (otherwise they are lost)
\def\spacesplit@morphemize#1 #2\nil{%
\if\relax\detokenize{#1}\relax%preceeding space => eval the #2
\space%
\if\relax\detokenize{#2}\relax%#2 is empty=> do nothing
\else
\spacesplit@morphemize#2\nil%
\fi
\else%
\@morphemize#1\nil%
\if\relax\detokenize{#2}\relax%#2 is empty...do nothing
\else
\space\spacesplit@morphemize#2\nil%
\fi
\fi}

%Recursive command to parse a string (without spaces) based on sequences of uppercase characters
\def\@morphemize#1#2\nil{%
\ifnum\is@upper#1\nil=1\relax%uppercase => treat differently
\if\relax\detokenize{#2}\relax%#2 is empty => treat as a single captial (pass through)
#1%
\else%#2 is not empty => eval if next is capital
\ifnum\is@upper#2\nil=1\relax%next char is uppercase => string of uppercases
\morpheme{\@stringofuppers#1#2\nil}%
\@stringoflowers#1#2\nil%
\else%next char is not uppercase => pass through unchanged
#1\@morphemize#2\nil%
\fi
\fi
\else%lowercase => pass through
#1%
\if\relax\detokenize{#2}\relax%#2 is empty => nothing left to parse
\else%#2 is not empty => more to parse
\@morphemize#2\nil%
\fi
\fi}

%Command to return 1 if the first character is uppercase, 0 if other
\def\is@upper#1#2\nil{%
\ifx#1A 1\else
\ifx#1B 1\else
\ifx#1C 1\else
\ifx#1D 1\else
\ifx#1E 1\else
\ifx#1F 1\else
\ifx#1G 1\else
\ifx#1H 1\else
\ifx#1I 1\else
\ifx#1J 1\else
\ifx#1K 1\else
\ifx#1L 1\else
\ifx#1M 1\else
\ifx#1N 1\else
\ifx#1O 1\else
\ifx#1P 1\else
\ifx#1Q 1\else
\ifx#1R 1\else
\ifx#1S 1\else
\ifx#1T 1\else
\ifx#1U 1\else
\ifx#1V 1\else
\ifx#1W 1\else
\ifx#1X 1\else
\ifx#1Y 1\else
\ifx#1Z 1\else
0%
\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi
\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi
\fi\fi\fi\fi\fi\fi}

%Recursive command expanding to the string of uppercase characters at the beginning of #1#2
\def\@stringofuppers#1#2\nil{%
\ifnum\is@upper#1\nil=1\relax%uppercase => carry on
\if\relax\detokenize{#2}\relax%#2 is empty => add a nill
\xformupper{#1}%
\else%#2 not empty => evaluate next char
\xformupper{#1}\@stringofuppers#2\nil%
\fi
\else\fi}%lowercase => do nothing

%Recursive command expanding to the string of lowercase characters after a string of upper case characters at the beginning of #1#2
%process the remaining contents with \@morphemize
\def\@stringoflowers#1#2\nil{%
\ifnum\is@upper#1\nil=1\relax%uppercase=>carry on
\if\relax\detokenize{#2}\relax%#2 is empty => the whole content was all caps...return nothing
\else%#2 not empty => evaluate next char
\@stringoflowers#2\nil%
\fi
\else%lowercase =>process with \@morphemize
\@morphemize#1#2\nil%
\fi}
\makeatother
\begin{document}
\noindent\verb|\morphemize{ pro.3PERS.FEM be.PAST-HABIT here}|

\morphemize{ pro.3PERS.FEM be.PAST-HABIT here}'

\noindent\verb|\morphemize{ singleCapiTaL.LETERS here}|

\morphemize{ singleCapiTaL.LETERS here}'

\noindent\verb|\morphemize{with.A PO.ST-space }|

\morphemize{with.A PO.ST-space }'
\end{document}

I'm sure it could be tidied up a bit (expl3 would have been a better choice in hindsight). Also, it should be expandable, pending the user-defined \morpheme and \xformupper.

• Wow, thanks a lot. That's exactly what I wanted -- only sequences of two or more capitals. – Uli S. Mar 15 '16 at 11:09