2

I'm following this example on how to get strikethrough to work across multiple paragraphs (which contain citations, a sticking point for many strikethrough solutions):

More robust strike-through / cross-out

I have created a minimally reproducible example that does work, shown below.

\documentclass{article}
\usepackage{xcolor}
\usepackage[normalem]{ulem}

\newcommand\deleted[1]{\color{red}\let\helpcmd\sout\parhelp#1\par\relax\relax} \newcommand\added[1]{\color{blue}\let\helpcmd\parhelp#1\par\relax\relax} \long\def\parhelp#1\par#2\relax{% \helpcmd{#1}\ifx\relax#2\else\par\parhelp#2\relax\fi% }

\begin{document}

\deleted{ Example deleted section - \cite{abc} Lorem ipsum

dolor sit amet, consectetur adipiscing elit, sed

do eiusmod tempor incididunt ut labore et dolore magna aliqua. }

\added{ Example added section - - \cite{abc} Lorem ipsum

dolor sit amet, consectetur adipiscing elit, sed

do eiusmod tempor incididunt ut labore et dolore magna aliqua. }

\end{document}

In my main document where this is being used I've got the \usepackage statements and I'm copying/pasting the \newcommand lines and \long\def... line (I honestly don't understand this line, I've copied it from the tex/SE article referenced above).

When I use \added{...paragraph text...} I get the following Undefined control sequence error:

\added #1->\color {blue}\let \helpcmd 
                                      \parhelp #1\par \relax \relax 
l.184     }

The control sequence at the end of the top line of your error message was never \def'ed. If you have misspelled it (e.g., \hobx'), typeI' and the correct spelling (e.g., `I\hbox'). Otherwise just continue, and I'll forget about whatever was undefined.

I can't see any difference between my document (which mostly just has a bunch more \usepackage statements) and the minimally reproducible example here.

  • It's going to be much easier to help if you provide an example that actually produces the error. I know it's a pain, but it helps immensely. You can either make a copy of your larger project, and then keep subtracting things until the problem goes away (in which case you'll know what to add back), or keep adding to this example until the problem appears. – frabjous Jul 28 '22 at 23:11
  • This is interesting. I added everything from my document header to my MRE and it worked fine. Then I tested using my \added command in main.tex, and it worked fine. Then I tested using \added in one of the subsections of the document which are separate .tex sections imported to main.tex using \input{subsection-file-name}. When I try to use \added in the subsection page overleaf doesn't auto-complete for me anymore. I'm testing if I can reproduce this in the MRE currently. – davidparks21 Jul 28 '22 at 23:31
  • No luck with the above, it still works fine in a subpage in my minimally reproducible example. I'm still trying to reproduce the problem. – davidparks21 Jul 28 '22 at 23:34
  • 1
    Looks like because added is fragile macros - What is the difference between Fragile and Robust commands? When and why do we need \protect? - TeX - LaTeX Stack Exchange. Add \MakeRobust\added after the \newcommand\added ... line. (just a guess, because no MWE.) – user202729 Jul 29 '22 at 01:48
  • The error-message says: "The control sequence at the end of the top line of your error message". At the end you find: \let \helpcmd, so it seems there is a situation where the \let-assignment is not carried out and an attempt at expanding \helpcmd takes place while that is undefined. Such things can happen in \edef- or \writecontexts, e.g., when sectioning-commands write their arguments to .toc-file. Try to suppress expansion by doing as suggested by user 202729 or by defining \deleted and \added by means of \DeclareRobustCommand instead of \newcommand. – Ulrich Diez Jul 29 '22 at 18:27
  • The definition of \added looks weird: Seems the \let-assignment is not complete, thus \helpcmd erroneously is let equal to \parhelp instead of having \added carry out \parhelp. However, this should not lead to error-messages in normal contexts. But not carrying out \let in \edef- or \write-contexts and thus attempting to expand undefined \helpcmd triggers error-messages. – Ulrich Diez Jul 29 '22 at 19:13

1 Answers1

4

At first glimpse one might fix the \let-assignment within the definition of \added, add some scoping for preventing color-changes from being permanent and use \DeclareRobustCommand for having some robutness:

\documentclass{article}
\usepackage{xcolor}
\usepackage[normalem]{ulem}

\makeatletter \DeclareRobustCommand\deleted[1]{{\color{red}\let\helpcmd\sout\parhelp#1\par\relax\relax}} \DeclareRobustCommand\added[1]{{\color{blue}\let\helpcmd@firstofone\parhelp#1\par\relax\relax}} \long\def\parhelp#1\par#2\relax{% \helpcmd{#1}\ifx\relax#2\else\par\parhelp#2\relax\fi } \makeatother

\begin{document}

\noindent texttexttext

\noindent\deleted{ %<- this space is not removed and not striked Example deleted section - \cite{abc} Lorem ipsum \par dolor sit amet, consectetur adipiscing elit, sed

do eiusmod tempor incididunt ut labore et dolore magna aliqua. %<- this space is not removed and striked }texttexttext

\noindent texttexttext

\noindent\added{ %<- this space is not removed Example added section - - \cite{abc} Lorem ipsum \par dolor sit amet, consectetur adipiscing elit, sed

do eiusmod tempor incididunt ut labore et dolore magna aliqua. %<- this space is not removed }texttexttext

\end{document}

enter image description here


Second glimpse:

The following code is an attempt at ensuring

  • robustness,
  • leading and trailing space tokens of \added/\deleted's argument being preserved in horizontal mode but not being striked through,
  • within the argument of \added/\deleted space-tokens still forming discardable glue instead of "whitespace that is striked through" at the end of a paragraph.
\makeatletter
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo, \UD@Exchange, \UD@PassFirstToSecond,
%%    \UD@stopromannumeral, \UD@CheckWhetherNull, \UD@ExtractFirstParArg,
%%    \UD@TrimLeadingTokens, \UD@TrimTrailingTokens
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@gobbletwo[2]{}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
  \expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner \par-undelimited argument:
%%
%%   \UD@ExtractFirstParArg{A\par B\par C\par D\par E} yields  {A}
%%
%%   \UD@ExtractFirstParArg{{AB}\par C\par D\par E} yields  {{AB}}
%%
%%   \UD@ExtractFirstParArg{AB\par C\par D\par E} yields  {AB}
%%
%%   \UD@ExtractFirstParArg{{AB}} yields  {{AB}}
%%
%%   \UD@ExtractFirstParArg{} yields  {}
%%
%% Due to \romannumeral-expansion the result is delivered after two 
%% expansion-steps/after "hitting" \UD@ExtractFirstParArg with \expandafter
%% twice.
%%
%% Use frozen-\relax as delimiter for speeding things up.
%% I chose frozen-\relax because David Carlisle pointed out in
%% <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \UD@ExtractFirstParArg's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
%%
%%.............................................................................
\@ifdefinable\UD@RemoveFromParTillFrozenrelax{%
  \expandafter\expandafter\expandafter\UD@Exchange
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\ifnum0=0\fi}%
  {\long\def\UD@RemoveFromParTillFrozenrelax#1\par#2}{{#1}\par}%
}%
\expandafter\UD@PassFirstToSecond\expandafter{%
  \romannumeral\expandafter
  \UD@PassFirstToSecond\expandafter{\romannumeral
    \expandafter\expandafter\expandafter\UD@Exchange
    \expandafter\expandafter\expandafter{%
    \expandafter\expandafter\ifnum0=0\fi}{\UD@stopromannumeral{{}}#1\par}%
  }{%
    \UD@stopromannumeral\romannumeral\UD@ExtractFirstParArgLoop
  }%
}{%
  \newcommand\UD@ExtractFirstParArg[1]%
}%
\newcommand\UD@ExtractFirstParArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletwo#1}%
  {%
    \expandafter\expandafter
    \expandafter\UD@stopromannumeral
    \expandafter\UD@PassFirstToSecond
    \expandafter{%
      \romannumeral
      \expandafter\UD@firstoftwo\expandafter\UD@stopromannumeral
      \UD@firstoftwo#1%
    }{}%
  }%
  {\expandafter\UD@ExtractFirstParArgLoop\expandafter{\UD@RemoveFromParTillFrozenrelax#1}}%
}%
%%-----------------------------------------------------------------------------
%%  \UD@iterateParList{<tokens>}{<\par-separated list>}
%%
%%  Each item of the <\par-separated list> is nested in curly braces before
%%  prepending <tokens> to it. Items are separated by \par.
%%
%%  I tried my best at preventing removal of curly braces.
%%  
%%-----------------------------------------------------------------------------
\@ifdefinable\UD@gobbletopar{\long\def\UD@gobbletopar#1\par{}}%
\newcommand\UD@iterateParList[2]{%
  \romannumeral\UD@iterateparlistloop{#2}{#1}{}{}%
}%
\newcommand\UD@iterateparlistloop[4]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletopar#1\par}{%  
    \UD@stopromannumeral#4#3#2{#1}%
  }{%
    \expandafter\UD@PassFirstToSecond\expandafter{%
       \romannumeral
       \expandafter\expandafter\expandafter\UD@Exchange
       \expandafter\expandafter\expandafter{%
         \UD@ExtractFirstParArg{#1}%
       }{\UD@stopromannumeral#4#3#2}%
    }{%
      \expandafter\UD@iterateparlistloop\expandafter{\UD@gobbletopar#1}{#2}{\par}%
    }%
  }%
}%
%%-----------------------------------------------------------------------------
%%  "\UD@shifttrailspaces{<tokens>}{STUFF  }"  yields  "<tokens>{STUFF}  "
%%-----------------------------------------------------------------------------
\newcommand\UD@shifttrailspaces[2]{%
  \romannumeral
  \expandafter\UD@Exchange\expandafter{%
    \romannumeral\UD@shifttrailspacesloop{{}}#2\UD@ForBidden/ \UD@ForBidden/\UD@ForBidden/ {{}#2}{#1}{}%
  }\UD@stopromannumeral
}%
\@ifdefinable\UD@shifttrailspacesloop{%
  \long\def\UD@shifttrailspacesloop#1 \UD@ForBidden/#2\UD@ForBidden/ #3#4#5{%
    \UD@CheckWhetherNull{#2}{% no trailing space
      \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#3}{\UD@stopromannumeral#4}#5%
    }{% trailing space
      \UD@shifttrailspacesloop#1\UD@ForBidden/ \UD@ForBidden/\UD@ForBidden/ {#1}{#4}{#5 }%
    }%
  }%
}%
%%-----------------------------------------------------------------------------
%%  "\UD@shiftleadspaces{<tokens>}{  STUFF}"  yields  "  <tokens>{STUFF}"
%%-----------------------------------------------------------------------------
\@ifdefinable\UD@gobblespace{\UD@firstoftwo{\def\UD@gobblespace}{} {}}%
\newcommand\UD@shiftleadspaces[2]{%
  \romannumeral
  \expandafter\UD@Exchange\expandafter{%
    \romannumeral
    \UD@shiftleadspacesloop\UD@ForBidden/#2\UD@ForBidden/ \UD@ForBidden/\UD@ForBidden/{#2}{#1}{}%
  }\UD@stopromannumeral
}%
\@ifdefinable\UD@shiftleadspacesloop{%
  \long\def\UD@shiftleadspacesloop#1\UD@ForBidden/ #2\UD@ForBidden/\UD@ForBidden/#3#4#5{%
    \UD@CheckWhetherNull{#1}{% Leading space
      \expandafter\UD@Exchange\expandafter{\expandafter{\UD@gobblespace#3}}%
      {\UD@shiftleadspacesloop\UD@ForBidden/#2\UD@ForBidden/\UD@ForBidden/}{#4}{#5 }%
    }{% no leading space
      \UD@stopromannumeral#5#4{#3}%
    }%
  }%
}%
%%-----------------------------------------------------------------------------
\@ifdefinable\deleted{%
  \DeclareRobustCommand\deleted[1]{{\color{red}\ifhmode\null\fi\UD@iterateParList{\UD@shifttrailspaces{\UD@shiftleadspaces{\sout}}}{#1}}}%
}%
\@ifdefinable\added{%
  %\DeclareRobustCommand\added[1]{{\color{blue}\ifhmode\null\fi\UD@iterateParList{\UD@shifttrailspaces{\UD@shiftleadspaces{\@firstofone}}}{#1}}}%
  \DeclareRobustCommand\added[1]{{\color{blue}\ifhmode\null\fi#1}}%
}%
\makeatother

\documentclass{article} \usepackage{xcolor} \usepackage[normalem]{ulem}

\begin{document}

\noindent texttexttext

\noindent\deleted{ %<- this space is not removed and not striked Example deleted section - \cite{abc} Lorem ipsum \par dolor sit amet, consectetur adipiscing elit, sed

do eiusmod tempor incididunt ut labore et dolore magna aliqua. %<- this space is not removed and not striked }texttexttext

\noindent texttexttext

\noindent\added{ %<- this space is not removed Example added section - - \cite{abc} Lorem ipsum \par dolor sit amet, consectetur adipiscing elit, sed

do eiusmod tempor incididunt ut labore et dolore magna aliqua. %<- this space is not removed }texttexttext

\end{document}

enter image description here

Ulrich Diez
  • 28,770