Christian Hupfer's comment above helped me clarify for myself that I really wanted to just make xkeyval work the way I wanted, accepting both a \par and a comma, without any extra intervention required on the part of the end user or a package author. After spending a week trying to understand the code in xkvutils.tex in idle minutes, I came up with the following code to redefine a few macros of xkeyval:
\documentclass{minimal}
\usepackage{xkeyval}
\makeatletter
\long\def\@s@lective@sanitize[#1]#2#3{%
\begingroup
\count@#1\relax\advance\count@\@ne
\XKV@toks\expandafter{#3}%
\def#3{#2}\@onelevel@sanitize#3%
\edef#3{{#3}{\the\XKV@toks}}%
\expandafter\@s@l@ctive@sanitize\expandafter#3#3%
\expandafter\XKV@tempa@toks\expandafter{#3}%
\expandafter\endgroup\expandafter\toks@\expandafter{\the\XKV@tempa@toks}%
\edef#3{\the\toks@}%
}
\long\def\@s@l@ctive@sanitize#1#2#3{%
\def\@i{\futurelet\@@tok\@ii}%
\def\@ii{%
\ifx\@@tok\@s@l@ctive@sanitize
\let\@@cmd\@gobble
\else
\expandafter\@iii\meaning\@@tok\relax
\ifx\@@tok\@sptoken
\XKV@toks\expandafter{#1}\edef#1{\the\XKV@toks\space}%
\def\@@cmd{\afterassignment\@i\let\@@tok= }%
\else
\let\@@cmd\@iv
\fi
\fi
\@@cmd
}%
\def\@iii##1##2\relax{\if##1\@backslashchar\let\@@tok\relax\fi}%
\long\def\@iv##1{%
\toks@\expandafter{#1}\XKV@toks{##1}%
\ifx\@@tok\bgroup
\advance\count@\m@ne
\ifnum\count@>\z@
\begingroup
\def#1{\expandafter\@s@l@ctive@sanitize
\csname\string#1\endcsname{#2}}%
\expandafter#1\expandafter{\the\XKV@toks}%
\XKV@toks\expandafter\expandafter\expandafter
{\csname\string#1\endcsname}%
\edef#1{\noexpand\XKV@toks{\the\XKV@toks}}%
\expandafter\endgroup#1%
\fi
\edef#1{\the\toks@{\the\XKV@toks}}%
\advance\count@\@ne
\let\@@cmd\@i
\else
\edef#1{\expandafter\string\the\XKV@toks}%
\expandafter\in@\expandafter{#1}{#2}%
\edef#1{\the\toks@\ifin@#1\else
\ifx\@@tok\@sptoken\space\else\the\XKV@toks\fi\fi}%
\edef\@@cmd{\noexpand\@i\ifx\@@tok\@sptoken\the\XKV@toks\fi}%
\fi
\@@cmd
}%
\let#1\@empty\@i#3\@s@l@ctive@sanitize
}
\define@key{ABC}{mykey}{\long\def\mymacro{#1}}
\begin{document}
%\setkeys{ABC}{mykey={DE,F}} % This line works.
%\setkeys{ABC}{mykey={D\par EF}} % This line works.
\setkeys{ABC}{mykey={D\par E,F}} % This line now works.
\mymacro
%It can still handle conditionals.
\setkeys{ABC}{mykey={\ifnum1<2\relax True \else False\fi}}
{\tt\meaning\mymacro}
\end{document}
The only change to \@s@lective@sanitize is making it a \long macro. The interior macro \@s@l@ctive@sanitize required a little more work. In case anyone's interested in the details, here is the original code for comparison:
\def\@s@l@ctive@sanitize#1#2#3{%
\def\@i{\futurelet\@@tok\@ii}%
\def\@ii{%
\expandafter\@iii\meaning\@@tok\relax
\ifx\@@tok\@s@l@ctive@sanitize
\let\@@cmd\@gobble
\else
\ifx\@@tok\@sptoken
\XKV@toks\expandafter{#1}\edef#1{\the\XKV@toks\space}%
\def\@@cmd{\afterassignment\@i\let\@@tok= }%
\else
\let\@@cmd\@iv
\fi
\fi
\@@cmd
}%
\def\@iii##1##2\relax{\if##1\@backslashchar\let\@@tok\relax\fi}%
\def\@iv##1{%
\toks@\expandafter{#1}\XKV@toks{##1}%
\ifx\@@tok\bgroup
\tracingmacros=1\relax
\advance\count@\m@ne
\ifnum\count@>\z@
\begingroup
\def#1{\expandafter\@s@l@ctive@sanitize
\csname\string#1\endcsname{#2}}%
\expandafter#1\expandafter{\the\XKV@toks}%
\XKV@toks\expandafter\expandafter\expandafter
{\csname\string#1\endcsname}%
\edef#1{\noexpand\XKV@toks{\the\XKV@toks}}%
\expandafter\endgroup#1%
\fi
\edef#1{\the\toks@{\the\XKV@toks}}%
\advance\count@\@ne
\let\@@cmd\@i
\else
\edef#1{\expandafter\string\the\XKV@toks}%
\expandafter\in@\expandafter{#1}{#2}%
\edef#1{\the\toks@\ifin@#1\else
\ifx\@@tok\@sptoken\space\else\the\XKV@toks\fi\fi}%
\edef\@@cmd{\noexpand\@i\ifx\@@tok\@sptoken\the\XKV@toks\fi}%
\fi
\@@cmd
}%
\let#1\@empty\@i#3\@s@l@ctive@sanitize
}
Of course \@s@l@ctive@sanitize needed to be made \long itself, but it defines another macro \@iv which also reads tokens to be sanitized, so \@iv also needed to be made \long.
These changes made sense, but now there was a new problem: the recursion never stopped. The macro uses itself, \@s@l@ctive@sanitize, as the delimiter to let it know when to stop. First \@i uses \futurelet to save the next token in \@@tok, before \@ii takes over. Then \@ii has \@iii check whether that next token is a TeX primitive, using the mechanism of asking whether its \meaning begins with a backslash; if so, it sets \@@tok equal to \relax. (The goal here seems to be to keep conditionals like \if, \else, and \fi from causing trouble by being expanded inside \@iv.) When \@iii is done, \@ii takes over again, checking whether (a) \@@tok is the \@s@l@ctive@sanitize at the end of the tokens to be sanitized, in which case we stop the recursion by simply \@gobble-ing that \@s@l@ctive@sanitize token; (b) \@@tok is a space token, which requires special handling; or (c) \@@tok is anything else, in which case it gets added to the sanitized list by \@iv.
So the new problem is that when we made \@s@l@ctive@sanitize long, its \meaning has changed from
macro: #1#2#3->\def \@i {\futurelet ...
to
\long macro: #1#2#3->\def \@i {\futurelet ...
Now when we reach the end of the sequence of tokens to be sanitized, when \@@tok has been \let equal to \@s@l@ctive@sanitize, \@iii tests the meaning of \@@tok and sees the backslash of \long, believes it has found a TeX primitive to be guarded against, and sets \@@tok equal to \relax. Thus when \@ii runs \ifx\@@tok\@s@l@ctive@sanitize, the conditional evaluates as false, and the recursion never ends.
I fixed this in my patch above by having \@ii test for the end-of-recursion marker first, before having \@iii check for the backslash.
I was little worried about whether this would break anything \@iii was supposed to guard against, but so far my new code seems to work as long as the conditionals in the key are balanced, which was needed for xkeyval before the patch anyway. I'll test it a little more, but if anyone sees a problem with my patch or a better solution, please let me know!
\long\def\usermacro#1#2#3{\setkeys{ABC}{mykey={#1},myotherkey={#2}}\dosomethingelse{#3}}. If I have to add the "wrapper"\myargas you suggest, I might as well simply write\long\def\usermacro#1#2#3{\long\def\mymacro{#1}\setkeys{ABC}{myotherkey={#2}}\dosomethingelse{#3}}. It works just fine, but since I'm usingxkeyvalfor other arguments, I was hoping to keep a parallel structure. – Anders Hendrickson Sep 01 '15 at 19:09xkeyvalthat would let it accept both the\parand the comma without any intermediate steps. I think I've got one worked up, and I'll try to put it below. – Anders Hendrickson Sep 11 '15 at 20:52