# Macro for integrals, a problem with limits

For a while now I have tried to come up with a macro, that can properly format inegrals (the way I like). Aswell as giving it a few options that can be chosen e.g. \int and \oint. So far there is a little problem arising in the code. It occurs when you choose the \oint by adding a * to the argument. It does come with rigth integral, but the limits are a little wrong, since it contain too the * in the limits.

My Integral macro and an example is shown below

\documentclass{article}
\usepackage{amsmath}
\usepackage{xparse}
\usepackage{xstring}

\NewDocumentCommand{\Int}{ >{\SplitList{;}}o}{%
\IfValueT{#1}{\ProcessList{#1}{\IntLimitONE}}\,%
}
\NewDocumentCommand{\IntLimitONE}{m}{%
\IfSubStr{#1}{*}{\oint\IntSplitLimits{#1}\!}{\int\IntSplitLimits{#1}\!}
}
\NewDocumentCommand{\IntSplitLimits}{ >{\SplitArgument{1}{,}}m}{%
\IfValueT{#1}{\IntLimits#1}%
}
\NewDocumentCommand{\IntLimits}{mm}{%
_{#1}^{\IfNoValueTF{#2}{}{#2\!\!}}%
}

\begin{document}
$$\Int[a,b;*c,d;e,f] f(x,y,z) dxdydz \quad , \quad \Int[*a,b] f(x) dx$$
\end{document}


I have tried a few ways to counter this, none satisfy completely my needs. One I tried was using the \xstring command \StrSubstitute

\NewDocumentCommand{\IntLimitONE}{m}{%
\StrSubstitute{#1}{*}{}[\temp]%
\IfSubStr{#1}{*}{\oint\IntSplitLimits{\temp}}{\int\IntSplitLimits{#1}}
}


It does indeed remove * from the limit, but the upper limit have been moved to the lower.

I cannot seem to understand what goes wrong here? Any help is appreciated :)

• Does "fx." mean "for example"? The usual abbreviation in English is "e.g." – TRiG May 29 '17 at 23:16
• @TRiG yes it does. I thougt the abbreviation was fx :) – Simon May 30 '17 at 7:11
• It's an oddity: Germans use an English abbreviation (f.e. or f.x.), while English-speakers use a Latin one. Germans naturally assume that the abbreviation they used is also used in English, but it really isn't. – TRiG May 30 '17 at 9:03
• @TRiG I don't know one German who uses 'f.e.' or 'f.x.'! I (and every other German I know) use 'bspw.'. – Skillmon May 30 '17 at 11:15

The problem is, that your \temp in the tried fix is read as one token and not parsed correctly. Inserting a few \expandafters fixes this:

\documentclass{article}
\usepackage{amsmath}
\usepackage{xparse}
\usepackage{xstring}

\NewDocumentCommand{\Int}{ >{\SplitList{;}}o}{%
\IfValueT{#1}{\ProcessList{#1}{\IntLimitONE}}\,%
}
\NewDocumentCommand{\IntLimitONE}{m}{%
\IfSubStr{#1}{*}{\StrSubstitute{#1}{*}{}[\temp]\oint\expandafter\IntSplitLimits\expandafter{\temp}}{\int\IntSplitLimits{#1}}
}
\NewDocumentCommand{\IntSplitLimits}{ >{\SplitArgument{1}{,}}m}{%
\IfValueT{#1}{\IntLimits#1}%
}
\NewDocumentCommand{\IntLimits}{mm}{%
_{#1}^{\IfNoValueTF{#2}{}{#2\!\!}}%
}

\begin{document}
$$\Int[a,b;*c,d;e,f] f(x,y,z) dxdydz \quad , \quad \Int[*a,b] f(x) dx$$
\end{document}


• This does work as intended, even if you misplace * [a*,b] :) – Simon May 29 '17 at 21:31
• @SimonJensen well it is your fix not mine. I just inserted the \expandafters. – Skillmon May 30 '17 at 6:51

The outer part is good.

For deciding whether * appears, I use \SplitArgument for at most one occurrence of *; this will produce two arguments, the second of which is -NoValue- in case no asterisk appears.

The rest is just splitting at the comma.

\documentclass{article}
\usepackage{amsmath}
\usepackage{xparse}

\NewDocumentCommand{\Int}{>{\SplitList{;}}O{}}{%
\ProcessList{#1}{\IntA}%
}
\NewDocumentCommand{\IntA}{>{\SplitArgument{1}{*}}{m}}{%
\IntB#1%
}
\NewDocumentCommand{\IntB}{mm}{%
\IfNoValueTF{#2}
{\IntC{\int}{#1}}% no *
{\IntC{\oint}{#2}}% *
}
\NewDocumentCommand{\IntC}{m >{\SplitArgument{1}{,}}m}{%
\IntD{#1}#2%
}
\NewDocumentCommand{\IntD}{mmm}{%
#1_{#2}^{#3}%
}

\begin{document}

$$\Int[a,b;*c,d;e,f] f(x,y,z) \,dx\,dy\,dz \quad , \quad \Int[*a,b] f(x)\,dx$$

\end{document}


With expl3 functions, you can split the argument at ; and map on the sequence so obtained. If the item starts with *, discard it and choose \oint, otherwise choose \int.

\documentclass{article}
\usepackage{amsmath}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\Int}{O{}}
{
\simon_int:n { #1 }
}

\seq_new:N \l_simon_int_args_seq

\cs_new_protected:Nn \simon_int:n
{
\seq_set_split:Nnn \l_simon_int_args_seq { ; } { #1 }
\seq_map_inline:Nn \l_simon_int_args_seq
{
\str_if_eq_x:nnTF { \tl_head:n { ##1 } } { * }
{% * case
\simon_int_inner:Nf \oint { \tl_tail:n { ##1 } }
}
{% no * case
\simon_int_inner:Nn \int { ##1 }
}
}
}
\cs_new_protected:Nn \simon_int_inner:Nn
{
#1 \sb{ \clist_item:nn { #2 } { 1 } }
\sp{ \clist_item:nn { #2 } { 2 } }
}
\cs_generate_variant:Nn \simon_int_inner:Nn { Nf }
\ExplSyntaxOff

\begin{document}

$$\Int[a,b;*c,d;e,f] f(x,y,z) \,dx\,dy\,dz \quad , \quad \Int[*a,b] f(x)\,dx$$

\end{document}


A variant that checks for the asterisk even if not leading the item:

\documentclass{article}
\usepackage{amsmath}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\Int}{O{}}
{
\simon_int:n { #1 }
}

\seq_new:N \l__simon_int_args_seq
\tl_new:N \l__simon_int_ast_tl

\cs_new_protected:Nn \simon_int:n
{
\seq_set_split:Nnn \l__simon_int_args_seq { ; } { #1 }
\seq_map_inline:Nn \l__simon_int_args_seq
{
\tl_if_in:nnTF { ##1 } { * }
{% * case
\tl_set:Nn \l__simon_int_ast_tl { ##1 }
\tl_remove_once:Nn \l__simon_int_ast_tl { * }
\simon_int_inner:NV \oint \l__simon_int_ast_tl
}
{% no * case
\simon_int_inner:Nn \int { ##1 }
}
}
}
\cs_new_protected:Nn \simon_int_inner:Nn
{
#1 \sb{ \clist_item:nn { #2 } { 1 } }
\sp{ \clist_item:nn { #2 } { 2 } }
}
\cs_generate_variant:Nn \simon_int_inner:Nn { NV }
\ExplSyntaxOff

\begin{document}

$$\Int[a,b;*c,d;e*,f] f(x,y,z) \,dx\,dy\,dz \quad , \quad \Int[*a,b] f(x)\,dx$$

\end{document}


• +1 I often upvote your excellent answer. I like the retina screenshot. :-) – Sebastiano May 29 '17 at 20:38
• It's no fun if you come around the corner and outsmart my answers by far :) – Skillmon May 29 '17 at 20:54
• @Skillmon Nice attempt, but never mix xstring with xparse. ;-) And the second version just defines two functions (plus a user level one) instead of four. – egreg May 29 '17 at 21:12
• This works quite well, except if you misplace *, that I can live with :) – Simon May 29 '17 at 21:28
• @SimonJensen The second code can be quite easily adjusted to check for a * anywhere. – egreg May 29 '17 at 21:41