3

This is a demo

\documentclass{article}
\usepackage{soul}
\makeatletter
\newcommand{\mydef}[2]{%
    \expandafter\def\csname my#1\endcsname {#2}
}
\newcommand{\mygetdef}[1]{%
    \ifcsname my#1\endcsname \csname my#1\endcsname \else #1 not defined \fi%
}
\makeatother

\def\test{some text to complete}
\mydef{xxx}{some text to complete}

\begin{document}
\ul\test                 % it works
%%%% if uncomment, the below one does not work, an error is reported 
% \ul\mygetdef{xxx}
\end{document}

As the comment says, if \ul\mygetdef{xxx} takes place, there comes a error:

! Argument of \SOUL@@ has an extra }.

UPDATE:

I switch to package ulem where \uline can underline \fbox. Everything works (see MWE below). Then I try to make colorful box. However, I find that once I include package xcolor or tcolorbox (as the comment in MWE), some weird error comes up immediately even before I actually use them in the document.

\documentclass{article}
%%% uncomment either of the packages below, it reports errors.
%\usepackage{xcolor}
%\usepackage{tcolorbox}

\usepackage[normalem]{ulem}
\makeatletter
\newcommand{\mydef}[2]{%
    \expandafter\def\csname my#1\endcsname {#2}
}
\newcommand{\mygetdef}[1]{%
    \ifcsname my#1\endcsname \csname my#1\endcsname \else \protect\fbox{#1} \fi%
}

\def\test{some text to complete}
\mydef{xxx}{some text to complete}

\begin{document}
\uline{\test}

\edef\mytmp{\mygetdef{xxx}}
\uline{\mytmp}

\edef\mytmp{\mygetdef{yyy}}
\uline{\mytmp}

\end{document}

After all my efforts, I can not figure out why and solve it. So I updated my original problem.

2

Try

\edef\tmp{\mygetdef{xxx}}
\ul\tmp

The \mygetdef needs to be expanded before being passed to \ul.

\documentclass{article}
\usepackage{soul}
\makeatletter
\newcommand{\mydef}[2]{%
    \expandafter\def\csname my#1\endcsname {#2}
}
\newcommand{\mygetdef}[1]{%
    \ifcsname my#1\endcsname \csname my#1\endcsname \else #1 not defined \fi%
}
\makeatother

\def\test{some text to complete}
\mydef{xxx}{some text to complete}

\begin{document}
\ul\test                 % it works
%%%% if uncomment, the below one does not work, an error is reported 
% \ul\mygetdef{xxx}

\edef\tmp{\mygetdef{xxx}}
\ul\tmp
\end{document}

enter image description here


SUPPLEMENTAL NOTE TO OP

Not really to answer OP question, but in reply to some of his comments to this answer:

\documentclass{article}
\usepackage{soul}
\makeatletter
\newcommand{\mydef}[2]{%
    \expandafter\def\csname my#1\endcsname {#2}
}
\newcommand{\mygetdef}[1]{%
    \ifcsname my#1\endcsname \csname my#1\endcsname \else
#1 not defined \fi%
}
\newcommand{\myulgetdef}[1]{%
    \ifcsname my#1\endcsname \expandafter\ul{\csname my#1\endcsname}%
  \else\fbox{#1 not defined} \fi%
}
\newcommand{\myxgetdef}[1]{%
    \ifcsname my#1\endcsname \expandafter\csname my#1\endcsname%
  \else\protect\protect\protect\fbox{#1 not defined} \fi%
}
\newcommand\exul[1]{\edef\tmp{#1}\ul\tmp}
\makeatother

\def\test{some text to complete}
\mydef{xxx}{some text to complete}

\begin{document}
\ul\test                 % it works

\exul{\mygetdef{xxx}}

1. \mygetdef{yyy}

2. \myulgetdef{yyy}

3. \myxgetdef{yyy}

4. \exul{\mygetdef{yyy}}

5. \exul{\myxgetdef{yyy}}% HOWEVER, \fbox IS NOT UNDERLINED

%6. \exul{\myulgetdef{yyy}}% FAILS; CANNOT \ul{\fbox{...}}

\end{document}

enter image description here

  • Is there any way putting \edef part in \mygetdef to make it work? – jiewuza Mar 23 at 3:01
  • @jiewuza It would be easier to define \newcommand\exul[1]{\edef\tmp{#1}\ul\tmp} and then invoke it as \exul{\mygetdef{xxx}}. – Steven B. Segletes Mar 23 at 3:07
  • Well, then I have to define more \newcommand for others besides \ul. But it works anyway. Thanks. – jiewuza Mar 23 at 4:00
  • I just got another problem. I added \fbox in \mygetdef, that is, \ifcsname my#1\endcsname \csname my#1\endcsname \else \fbox{#1 not defined} \fi. And when I try \mygetdef{yyy} (which is not defined), it brokes with some Undefined control sequence error. – jiewuza Mar 23 at 4:26
  • @jiewuza The command \newcommand{\mygetdef}[1]{% \ifcsname my#1\endcsname \csname my#1\endcsname \else \fbox{ #1 not defined} \fi% } works just fine if invoked directly as \mygetdef{yyy}. However, \exul{\mygetdef{yyy}} does not work in that case because you can't \ul an \fbox. – Steven B. Segletes Mar 23 at 4:58
3

The command \ul is defined as \SOUL@ulsetup\SOUL@. The first command sets up some commands that will be later delivered after a group has started and is of no concern here.

The macro \SOUL@ is defined as

% soul.sty, line 99:
\def\SOUL@{%
    \futurelet\SOUL@@\SOUL@expand
}

which means that the following token is looked up and stored in \SOUL@@; then \SOUL@expand is executed

% soul.sty, line 102:
\def\SOUL@expand{%
    \ifcat\bgroup\noexpand\SOUL@@
        \let\SOUL@n\SOUL@start
    \else
        \bgroup
            \def\\##1##2{\def##2{\noexpand##2}}%
            \the\SOUL@cmds
            \SOUL@buffer={%
                \\\TeX\\\LaTeX\\\soulomit\\\mbox\\\hbox\\\textregistered
                \\\slash\\\textcircled\\\copyright\\\S\\\,\\\<\\\>\\~%
                \\\\%
            }%
            \def\\##1{\def##1{\noexpand##1}}%
            \the\SOUL@buffer
            \let\protect\noexpand
            \xdef\SOUL@n##1{\noexpand\SOUL@start{\SOUL@@}}%
        \egroup
    \fi
    \SOUL@n
}

Quite a mouthful! The behavior depends on whether the token stored in \SOUL@@ is a left brace or not. In your case it isn't.

The part of the code above which is of primary interest is

            \let\protect\noexpand
            \xdef\SOUL@n##1{\noexpand\SOUL@start{\SOUL@@}}%

Note that \SOUL@@ is the token found at the first step, in your case \mygetdef. At the end, \SOUL@n is executed, which discards the next token (your \mygetdef) and does

\SOUL@start{<full expansion of \mygetdef`>}

The place where the error happens is at the \xdef, because \mygetdef does not find its argument, but just the closing brace. What you want is that

\ul\mygetdef{xxx}

becomes

\ul\myxxx

You need to do some clever expansion for this, but it becomes very awkward, mainly because of the conditionals. If you do

\expandafter\ul\myxxx

you get

\ul\ifcsname myxxx\endcsname\csname myxxx\endcsname\else xxx not defined \fi

With three \expandafters

\ul\csname myxxx\endcsname\else xxx not defined \fi

With seven \expandafters

\ul\myxxx\else xxx not defined \fi

That's it! Let's see what happens with \mygetdef{noway} (assuming it's undefined). You'd get

\ul noway not defined \fi

which is not really what you want, but can be easily fixed.

\documentclass{article}
\usepackage{soul}

\newcommand{\mydef}[2]{%
  \expandafter\def\csname my#1\endcsname{#2}
}
\newcommand{\mygetdef}[1]{%
  \ifcsname my#1\endcsname
    \csname my#1\endcsname
  \else
   {}#1 not defined%
  \fi
}

\def\test{some text to complete}
\mydef{xxx}{some text to complete}

\begin{document}

\ul\test

\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter
\expandafter\ul\mygetdef{xxx}

\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter
\expandafter\ul\mygetdef{noway}

\end{document}

enter image description here

My proposal is to define differently \mygetdef, if you plan to use it after soul macros, but not only.

\documentclass{article}
\usepackage{soul}

\makeatletter
\newcommand{\mydef}[2]{%
  \@namedef{my#1}{#2}
}
\newcommand{\mygetdef}[2][\relax]{%
  \@ifundefined{my#2}%
    {{}#2 not defined}%
    {\expandafter#1\csname my#2\endcsname}%
}
\makeatother

\mydef{xxx}{some text to complete}

\begin{document}

\mygetdef{xxx}

\mygetdef[\ul]{xxx}

\mygetdef[\textit]{xxx}

\mygetdef[\ul]{noway}

\end{document}

enter image description here

  • Although it is now beyond my understanding, your explanation is really helpful. I am learning the expanding rules. And your redefination of \mygetdef is quite interesting. I will dig more. – jiewuza Mar 23 at 14:27

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.