4

Is there any easy way to adapt soulutf8 to work with LaTeX math mode delimiters?

As of now, compiling

\documentclass{article}
\usepackage{xcolor,soulutf8}
\begin{document}
Well, \hl{\(e^{i\pi}=-1\)}.
\end{document}

with pdflatex results in

! Extra }, or forgotten $.
\SOUL@doword ...hskip \z@ \relax \the \SOUL@word }
                                                  \let \SOUL@errmsg \SOUL@er...
l.4 Well, \hl{\(e^{i\pi}=-1\)}
                              .
? X

Let us not go into the discussion on which one is better: $...$ or \(...\); take it for granted that the typesetter has his/her reasons to prefer the second pair of delimiters.

P.S. As Heiko pointed out, the same problem exists in soul (which unfortunately seems to lack an active maintainer as of now), whose documentation specifically mentions this limitation.

  • @VasiliPupkin you can patch soul but only under the conditions of the LPPL (which clearly allows you to modify the package locally -- redistribution only under certain conditions) – Skillmon Apr 11 '18 at 19:49
  • @HeikoOberdiek Oh, I see now. There are two copyrights: one for the dtx file and another one for the sty file. Since there is no need to ship the sty file, its copyright can be effectively ignored. – user159824 Apr 12 '18 at 17:36
4

The use of \( and \) is not supported by package soul, from the documentation:

§ 3 Mathematics:

Example: \so{foo$x^3$bar}

Mathematic formulas are allowed, as long as they are surrounded by $.

Note that the LaTeX equivalent \(...\) does not work.

Thus, the workaround with $ remains:

\hl{$e^{i\pi}=-1$}
4

One could parse the argument of \hl to replace all \(...\) by $...$ in that argument and then use the original definition of \hl. I'm not entirely sure whether the following is completely stable, though it worked for everything I've thrown at it so far.

Edit: minimise code by removing case which is never matched (##3 is dot and ##2 is not) and not include the \(.\) tokens in every recursion of the replacement but only once (which suffices as those are in ##3 in the case of a recursion).

Note that there are cases of valid but strange syntax (if used outside of the argument of \hl) which fail here: If one mixes $ and one of \( or \) as the delimiters the provided code fails; though $a^b\) or \(a^b$ would be valid syntax \hl{$a^b\)} and \hl{\(a^b$} would fail as they don't match the required argument pattern of \hl@grab.

\documentclass{article}
\usepackage{xcolor,soulutf8}
\usepackage[]{amsmath}
\usepackage{letltxmacro}
\LetLtxMacro\hlBAK\hl
\begingroup
\makeatletter
\catcode`.=4% changing catcode so the dot is never matched by ordinary text
\def\zz%
  {\endgroup
    \renewcommand\hl[1]
      {%
        \hl@grab##1\(.\)\endhl@grab
      }%
    \long\def\hl@grab##1\(##2\)##3\endhl@grab%
      {%
        \hl@grab@ifdot{##2}
          {\hlBAK{##1}}
          {\hl@grab##1$##2$##3\endhl@grab}%
      }%
    \long\def\hl@grab@ifdot##1%
      {%
        \ifx.##1%
          \expandafter\@firstoftwo
        \else
          \expandafter\@secondoftwo
        \fi
      }%
  }%
\zz
\begin{document}
Well, \hl{\(.e^{i\pi}=-1\) text \(2=2\).}
\end{document}

enter image description here

EDIT: The following provides a replacement using two macros, one to replace \( and one to replace \). As a result the strange (but technically correct) syntax of \(a^b$ and $a^b\) should also work as an argument to \hl (though this way \hl{\(a^b\( text $a^b$} would work, too, which is obviously not correct syntax).

\documentclass{article}
\usepackage{xcolor,soulutf8}
\usepackage[]{amsmath}
\usepackage{letltxmacro}
\LetLtxMacro\hlBAK\hl
\begingroup
\makeatletter
\catcode`.=4% changing catcode so the dot is never matched by ordinary text
\def\zz%
  {\endgroup
    \renewcommand\hl[1]
      {%
        \hl@grab@A##1\(.\endhl@grab
      }%
    \long\def\hl@grab##1\(##2\)##3\endhl@grab%
      {%
        \hl@grab@ifdot{##2}
          {\hlBAK{##1}}
          {\hl@grab##1$##2$##3\endhl@grab}%
      }%
    \long\def\hl@grab@A##1\(##2\endhl@grab%\)
      {%
        \hl@grab@ifdot{##2}
          {\hl@grab@B##1\).\endhl@grab}
          {\hl@grab@A##1$##2\endhl@grab}%
      }%
    \long\def\hl@grab@B##1\)##2\endhl@grab%
      {%
        \hl@grab@ifdot{##2}
          {\hlBAK{##1}}
          {\hl@grab@B##1$##2\endhl@grab}%
      }%
    \long\def\hl@grab@ifdot##1%
      {%
        \ifx.##1%
          \expandafter\@firstoftwo
        \else
          \expandafter\@secondoftwo
        \fi
      }%
  }%
\zz
\begin{document}
Well, \hl{\(.e^{i\pi}=-1\) text \(2=2\).}

\hl{$a^b\)}\hl{\(a^b$}

\hl{\(a \(b$ $}
\end{document}
  • 1
    @user49915 the changes made by \makeatletter and \catcode`.=4 are local to a group which is started by \bgroup and ended by \zz as the definition of \zz starts with an \egroup. This is a neat trick to not alter any catcodes while still defining stuff with non standard catcodes. Per convention those should be \begingroup and \endgroup, though, so you're right, the code is not perfect, I'll edit it. – Skillmon Jul 26 '18 at 12:58
  • 1
    @user49915 \bgroup is essentially the same as { (except that it doesn't have to be balanced while you define stuff), as a result \bgroup and \egroup can be balanced with { and }, while \begingroup must be balanced by \endgroup. It is convention (at least in l3) that you don't use \bgroup and \egroup for programming, so by that convention I should've used \begingroup and \endgroup. To see that \makeatletter has no effect anymore, you could place \@gobble{this} after \zz, it'll result in You can't use `\spacefactor' in vertical mode. – Skillmon Jul 26 '18 at 13:46
3

With the l3regex module it's quite easy and works for all soul commands. The idea is to redefine \SOUL@start, which is the macro that does all the business according to settings already made by the called macros.

\documentclass{article}
\usepackage{amsmath}
\usepackage{xcolor,soulutf8}
\usepackage{expl3}

\ExplSyntaxOn
\tl_new:N \__l_SOUL_argument_tl
\cs_set_eq:Nc \SOUL_start:n { SOUL@start }
\cs_generate_variant:Nn \SOUL_start:n { V }
\cs_set_protected:cpn {SOUL@start} #1
 {
  \tl_set:Nn \__l_SOUL_argument_tl { #1 }
  \regex_replace_all:nnN
   { \c{\(} (.*?) \c{\)} } % look for \(...\) (lazily)
   { \cM\$ \1 \cM\$ }      % replace with $...$
   \__l_SOUL_argument_tl
  \SOUL_start:V \__l_SOUL_argument_tl % do the usual
 }
\ExplSyntaxOff

\begin{document}

Well, \hl{\(e^{i\pi}=-1\) text \(2=2\).}

Well, \ul{\(e^{i\pi}=-1\) text \(2=2\).}

Well, \so{\(e^{i\pi}=-1\) text \(2=2\).}

Well, \caps{\(e^{i\pi}=-1\) text \(2=2\).}

Well, \st{\(e^{i\pi}=-1\) text \(2=2\).}

\end{document}

enter image description here

  • Why did you use \cs_generate_variant:Nn instead of \exp_args:NV? – Skillmon Jul 26 '18 at 18:51
  • @Skillmon Better compliance to the guidelines. – egreg Jul 26 '18 at 19:25
  • Simply amazing… – Clément Dec 23 '18 at 19:02

Your Answer

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