Here's a LuaLaTeX-based solution. It doesn't require loading the soul
package.
The user macro is called \ulow
-- short for "underline only words". With \ulow
, all non-letter glyphs, and not just whitespace, are exempt from being underlined.
The input is assumed to be UTF8-encoded. (ASCII is a proper subset of UTF8; thus, if your input file is pure ASCII, it's UTF8 automatically.)
If the argument of \ulow
contains letters with descenders (g, j, p, q, or y), the lines below all words will be set with a generous amount of vertical whitespace. Conversely, if no letters with descenders are present, the spacing will be set more tightly. If you want the more generous spacing in all cases, simply replace the five-line if
construct in the lua function with s = unicode.utf8.gsub (s, "(%a+)" , "\\underline{%1\\vphantom{p}}" )
. (Even if you don't know Lua syntax, you may be able to guess that the term %a+
"captures" entire words, where a "word" can be any contiguous groups of uppercase and lowercase letters.)

% !TEX TS-program = lualatex
\documentclass{article}
\usepackage{fontspec}
\setmainfont{Latin Modern Roman} % set main document font
%% Lua-side code
\usepackage{luacode}
\begin{luacode}
function ulow ( s )
if unicode.utf8.find ( s, "[gjpqy]") then -- any letters with descenders?
s = unicode.utf8.gsub (s, "(%a+)" , "\\underline{%1\\vphantom{p}}" )
else
s = unicode.utf8.gsub (s, "(%a+)" , "\\underline{%1}" )
end
return ( tex.sprint ( s ) )
end
\end{luacode}
%% TeX-side code
\newcommand\ulow[1]{\directlua{ ulow ( \luastring {#1} ) }}
\begin{document}
\frenchspacing
\ulow{The quick brown fox jumps over the lazy dog.}
\bigskip
\ulow{El rápido zorro marrón saltó sobre el perro perezoso.}
\bigskip
\ulow{And, but; let: show? that. where (now), èéà äöü ÄÖÜß!}
\end{document}
,
and.
) within and at the end of a sentence be underlined, or should they get the same treatment as whitespace, i.e., not be underlined?