A command challenge
Here's a little challenge. The goal is to create a command that takes is two or three arguments without using square brackets for optional arguments. The first and second argument are separated by a comma and a space, while the second and the third (if present) are separated by a dash. This means that both of the following inputs should be acceptable
mycommand{arg_1, arg_2}
and
mycommand{arg_1, arg_2-arg_3}
The definition of what mycommand
does with the two/three arguments is irrelevant to the problem.
macros expl3 xparse
add a comment |
Here's a little challenge. The goal is to create a command that takes is two or three arguments without using square brackets for optional arguments. The first and second argument are separated by a comma and a space, while the second and the third (if present) are separated by a dash. This means that both of the following inputs should be acceptable
mycommand{arg_1, arg_2}
and
mycommand{arg_1, arg_2-arg_3}
The definition of what mycommand
does with the two/three arguments is irrelevant to the problem.
macros expl3 xparse
2
That's one argument from a TeX point of view ... I guess you want a parser for that one document-level argument.
– Joseph Wright♦
Feb 8 at 17:46
Trydefmycommand#1,space#2-#3{...}
.
– John Kormylo
Feb 8 at 21:41
Since I assume this is more or less a follow-up to tex.stackexchange.com/q/473949/35864, let me please re-iterate that I believe that the usualbiblatex
syntax is not really inferior to this syntax. In fact the syntax proposed here has the serious drawback that it removes the ability to cite several entries in one cite commandautocite{sugfridsson,worman,nussbaum}
as that uses the comma-separated syntax in one argument already.
– moewe
Feb 9 at 8:00
add a comment |
Here's a little challenge. The goal is to create a command that takes is two or three arguments without using square brackets for optional arguments. The first and second argument are separated by a comma and a space, while the second and the third (if present) are separated by a dash. This means that both of the following inputs should be acceptable
mycommand{arg_1, arg_2}
and
mycommand{arg_1, arg_2-arg_3}
The definition of what mycommand
does with the two/three arguments is irrelevant to the problem.
macros expl3 xparse
Here's a little challenge. The goal is to create a command that takes is two or three arguments without using square brackets for optional arguments. The first and second argument are separated by a comma and a space, while the second and the third (if present) are separated by a dash. This means that both of the following inputs should be acceptable
mycommand{arg_1, arg_2}
and
mycommand{arg_1, arg_2-arg_3}
The definition of what mycommand
does with the two/three arguments is irrelevant to the problem.
macros expl3 xparse
macros expl3 xparse
asked Feb 8 at 17:42
noibenoibe
32010
32010
2
That's one argument from a TeX point of view ... I guess you want a parser for that one document-level argument.
– Joseph Wright♦
Feb 8 at 17:46
Trydefmycommand#1,space#2-#3{...}
.
– John Kormylo
Feb 8 at 21:41
Since I assume this is more or less a follow-up to tex.stackexchange.com/q/473949/35864, let me please re-iterate that I believe that the usualbiblatex
syntax is not really inferior to this syntax. In fact the syntax proposed here has the serious drawback that it removes the ability to cite several entries in one cite commandautocite{sugfridsson,worman,nussbaum}
as that uses the comma-separated syntax in one argument already.
– moewe
Feb 9 at 8:00
add a comment |
2
That's one argument from a TeX point of view ... I guess you want a parser for that one document-level argument.
– Joseph Wright♦
Feb 8 at 17:46
Trydefmycommand#1,space#2-#3{...}
.
– John Kormylo
Feb 8 at 21:41
Since I assume this is more or less a follow-up to tex.stackexchange.com/q/473949/35864, let me please re-iterate that I believe that the usualbiblatex
syntax is not really inferior to this syntax. In fact the syntax proposed here has the serious drawback that it removes the ability to cite several entries in one cite commandautocite{sugfridsson,worman,nussbaum}
as that uses the comma-separated syntax in one argument already.
– moewe
Feb 9 at 8:00
2
2
That's one argument from a TeX point of view ... I guess you want a parser for that one document-level argument.
– Joseph Wright♦
Feb 8 at 17:46
That's one argument from a TeX point of view ... I guess you want a parser for that one document-level argument.
– Joseph Wright♦
Feb 8 at 17:46
Try
defmycommand#1,space#2-#3{...}
.– John Kormylo
Feb 8 at 21:41
Try
defmycommand#1,space#2-#3{...}
.– John Kormylo
Feb 8 at 21:41
Since I assume this is more or less a follow-up to tex.stackexchange.com/q/473949/35864, let me please re-iterate that I believe that the usual
biblatex
syntax is not really inferior to this syntax. In fact the syntax proposed here has the serious drawback that it removes the ability to cite several entries in one cite command autocite{sugfridsson,worman,nussbaum}
as that uses the comma-separated syntax in one argument already.– moewe
Feb 9 at 8:00
Since I assume this is more or less a follow-up to tex.stackexchange.com/q/473949/35864, let me please re-iterate that I believe that the usual
biblatex
syntax is not really inferior to this syntax. In fact the syntax proposed here has the serious drawback that it removes the ability to cite several entries in one cite command autocite{sugfridsson,worman,nussbaum}
as that uses the comma-separated syntax in one argument already.– moewe
Feb 9 at 8:00
add a comment |
5 Answers
5
active
oldest
votes
You can do it with xparse
:
documentclass{article}
usepackage{xparse}
% split at the comma
NewDocumentCommand{mycommand}{ >{SplitArgument{1}{,}}m }{%
mycommandA#1%
}
% do something with #1 and split the second part at the hyphen
NewDocumentCommand{mycommandA}{ m >{SplitArgument{1}{-}}m }{%
Main is #1%
IfNoValueTF{#2}{.}{; mycommandB#2}%
}
% do something with the second part
NewDocumentCommand{mycommandB}{mm}{%
Secondary is #1IfValueT{#2}{ plus #2}.%
}
begin{document}
mycommand{arg1}
mycommand{arg1, arg2}
mycommand{arg1, arg2-arg3}
end{document}
An advantage: it's irrelevant if you forget the space after the comma.
add a comment |
documentclass{article}
defmycommand#1{xmycommand#1--relax}
defxmycommand#1,#2-#3-#4relax{[#1][#2][#3]}
begin{document}
$mycommand{arg_1, arg_2}$
$mycommand{arg_1, arg_2-arg3}$
end{document}
6
Even without electricity. I'm impressed!
– marmot
Feb 8 at 17:53
How about adding some mortar to the bricks!?
– Werner
Feb 8 at 18:06
@Werner where I come from we prefer this style: derbyshireuk.net/derbyshire_drystonewalls.html
– David Carlisle
Feb 8 at 18:12
5
@noibe oh no a disaster! I don't mind not getting the tick (I have more than enough points) but seeing points go to egreg is so depressing:)
– David Carlisle
Feb 8 at 18:49
2
Nozzz
? :'-(
– Henri Menke
Feb 9 at 6:02
|
show 4 more comments
Some crocheting/knitting by hand (or whatever)... ;-)
I suppose this question came into being while you thought about your other question: Custom cite command. In that question you ask for a customized cite
-command with the same syntax.
Be that as it may.
The challenge is doing it in a way where desired space-removal but no undesired removal of argument-braces takes place.
E.g., with things like mycommand{{arg_1}a, arg_2}
the braces surrounding arg_1
should probably be preserved while with things like mycommand{{arg_1} , arg_2}
they should probably be removed.
Also probably only space-tokens surrounding the entire arguments/surrounding those commas and those dashes that are taken for argument-separators should get removed.
Below is an approach where comma takes precedence over the dash and where the leftmost unhidden comma delimits the first argument from the remaining arguments and where the first unhidden dash following that comma delimits the second from the third argument.
Exactly one pair of matching curly braces will be removed in case after removing surrounding spaces it surrounds an entire argument as in that case it is assumed that the braces serve for hiding commas/dashes/spaces/emptiness. In all other cases braces will be preserved.
E.g., with things like mycommand{ {arg_1} , {arg_2-2} - arg_3}
you should get:
First argument: arg_1
Second argument: arg_2-2
Third argument: arg_3
.
E.g., with things like mycommand{ {{arg 1,a} - arg 1b} , {arg 2-2} , 2 - arg 3a - arg 3b}
you should get:
First argument (left from the first unhidden comma, surrounding spaces and outermost brace-level that surrounds the entire argument removed): {arg 1,a} - arg 1b
Second argument (right from the first unhidden comma, left from the first unhidden dash following the comma): {arg 2-2} , 2
Third argument (the remainder, right from the first unhidden dash following the comma): arg 3a - arg 3b
.
E.g., with things like mycommand{{arg 1}, {arg 2-2} , 2 - arg 3a - arg 3b}
you should get:
First argument (left from the first unhidden comma): arg 1
Second argument (right from the first unhidden comma, left from the first unhidden dash following the comma): {arg 2-2} , 2
Third argument (the remainder, right from the first unhidden dash following the comma): arg 3a - arg 3b
.
You may not be familiar to methods for removing space-tokens that surround arguments, thus a quote from the manual of my deprecated labelcas-package:
A note about removing leading and trailing spaces
The matter of removing trailing spaces from an (almost) arbitrary
token-sequence is elaborated in detail by Michael Downes, 'Around the
Bend #15, answers', a summary of internet-discussion which took place
under his guidance primarily at the INFO-TEX list, but also at
comp.text.tex (usenet) and via private email; December 1993. Online
archived at http://www.tug.org/tex-archive/info/arobend/answer.015.
One basic approach suggested therein is using TeX's scanning of
delimited parameters in order to detect and discard the ending space
of an argument:
... scan for a pair of tokens: a space-token and some well-chosen
bizarre token that can't possibly occur in the scanned text. If you
put the bizarre token at the end of the text, and if the text has a
trailing space, then TeX's delimiter matching will match at that point
and not before, because the earlier occurrences of space don’t have
the requisite other member of the pair.
Next consider the possibility that the trailing space is absent: TeX
will keep on scanning ahead for the pair
⟨space⟩⟨bizarre⟩
until either it finds them or it decides to give up and signal a
'Runaway argument?' error. So you must add a stop pair to catch the
runaway argument possibility: a second instance of the bizarre
token, preceded by a space. If TeX doesn't find a match at the
first bizarre token, it will at the second one.
(Look up the macros
KV@@sp@def
,KV@@sp@b
,KV@@sp@c
andKV@@sp@d
in
David Carlisle’s keyval-package for an interesting variation on this
approach.)
When scanning for parameters
##1⟨space⟩⟨bizarre⟩##2⟨B1⟩
the sequence:⟨stuff where to remove trail-space⟩⟨bizarre⟩⟨space⟩⟨bizarre⟩⟨B1⟩
, you can fork two cases:
Trailing-space:
##1
=⟨stuff where to remove trail-space⟩
, but with removed space. (And possibly one
removed brace-level!)##2
=⟨space⟩⟨bizarre⟩
.
No trailing-space:
##1
=⟨stuff where to remove trail-space⟩⟨bizarre⟩
.##2
is empty.
So forking can be implemented depending on the emptiness of
##2
.
You can easily prevent the brace-removal in the first case, e.g., by adding (and
later removing) something (e.g., a space-token) in front of the
⟨stuff where to remove trail-space⟩
.
You can choose
⟨B1⟩=⟨bizarre⟩⟨space⟩
.
The link in the quote is out of date.
Nowadays you can find the entire "Around the bend" -collection at http://mirrors.ctan.org/info/challenges/AroBend/AroundTheBend.pdf.
As you see, the methods for space-removal exhibited in the quote above do rely on some sequence of tokens that must not occur within the argument.
In the example below it is relied on the token UD@seldom
not occurring within arguments.
In other words: You must not use the token UD@seldom
within your arguments.
If you don't like that restriction, then I can deliver space-removal-routines which do without such restrictions, but they are slower and they form another huge load of code.
documentclass{article}
makeatletter
%%-----------------------------------------------------------------------------
%% Paraphernalia ;-) :
%%.............................................................................
newcommandUD@firstoftwo[2]{#1}%
newcommandUD@secondoftwo[2]{#2}%
newcommandUD@exchange[2]{#2#1}%
newcommandUD@removespace{}UD@firstoftwo{defUD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% 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>
%%.............................................................................
newcommandUD@CheckWhetherNull[1]{%
romannumeral0expandafterUD@secondoftwostring{expandafter
UD@secondoftwoexpandafter{expandafter{string#1}expandafter
UD@secondoftwostring}expandafterUD@firstoftwoexpandafter{expandafter
UD@secondoftwostring}expandafterexpandafterUD@firstoftwo{ }{}%
UD@secondoftwo}{expandafterexpandafterUD@firstoftwo{ }{}UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>}%
newcommandUD@CheckWhetherLeadingSpace[1]{%
romannumeral0UD@CheckWhetherNull{#1}%
{expandafterexpandafterUD@firstoftwo{ }{}UD@secondoftwo}%
{expandafterUD@secondoftwostring{UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
newcommandUD@CheckWhetherLeadingSpaceB{}%
longdefUD@CheckWhetherLeadingSpaceB#1 {%
expandafterUD@CheckWhetherNullexpandafter{UD@secondoftwo#1{}}%
{UD@exchange{UD@firstoftwo}}{UD@exchange{UD@secondoftwo}}%
{UD@exchange{ }{expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter}expandafterexpandafter
expandafter}expandafterUD@secondoftwoexpandafter{string}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%% UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% UD@ExtractFirstArg{{AB}CDE} yields {AB}
%%
%% !!! The argument of UD@ExtractFirstArg must not be empty. !!!
%% You can check for emptiness via UD@CheckWhetherNull before applying
%% UD@ExtractFirstArg.
%%.............................................................................
newcommandUD@RemoveTillUD@SelDOm{}%
longdefUD@RemoveTillUD@SelDOm#1#2UD@SelDOm{{#1}}%
newcommandUD@ExtractFirstArg[1]{%
romannumeral0%
UD@ExtractFirstArgLoop{#1UD@SelDOm}%
}%
newcommandUD@ExtractFirstArgLoop[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@firstoftwo{}#1}%
{ #1}%
{expandafterUD@ExtractFirstArgLoopexpandafter{UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% UD@RemoveSpacesAndOneLevelOfBraces{<argument>} removes leading and
%% trailing spaces from <argument>.
%% If after that the <argument> is something that is entirely nested
%% between at least one pair of matching curly braces, the outermost
%% pair of these braces will be removed.
%%
%% !!!! <argument> must not contain the token UD@seldom !!!!
%%.............................................................................
begingroup
newcommandUD@RemoveSpacesAndOneLevelOfBraces[1]{%
endgroup
newcommandUD@RemoveSpacesAndOneLevelOfBraces[1]{%
romannumeral0%
UD@trimtrailspaceloop#1##1UD@seldom#1UD@seldomUD@seldom#1{##1}%
}%
newcommandUD@trimtrailspaceloop{}%
longdefUD@trimtrailspaceloop##1#1UD@seldom##2UD@seldom#1##3{%
UD@CheckWhetherNull{##2}{%
UD@trimleadspaceloop{##3}%
}{%
UD@trimtrailspaceloop##1UD@seldom#1UD@seldomUD@seldom#1{##1}%
}%
}%
}%
UD@RemoveSpacesAndOneLevelOfBraces{ }%
newcommandUD@trimleadspaceloop[1]{%
UD@CheckWhetherLeadingSpace{#1}{%
expandafterUD@trimleadspaceloopexpandafter{UD@removespace#1}%
}{%
UD@CheckWhetherNull{#1}{ }{%
expandafterUD@CheckWhetherNullexpandafter{UD@firstoftwo{}#1}{%
UD@exchange{ }{expandafter}UD@secondoftwo{}#1%
}{ #1}%
}%
}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no comma which is not nested
%% in braces:
%%.............................................................................
%% UD@CheckWhetherNoComma{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% contains no comma>}%
%% {<Tokens to be delivered in case that argument
%% contains comma>}%
%%
newcommandUD@GobbleToComma{}longdefUD@GobbleToComma#1,{}%
newcommandUD@CheckWhetherNoComma[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@GobbleToComma#1,}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no dash which is not nested
%% in braces:
%%.............................................................................
%% UD@CheckWhetherNoDash{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% contains no dash>}%
%% {<Tokens to be delivered in case that argument
%% contains dash>}%
%%
newcommandUD@GobbleToDash{}longdefUD@GobbleToDash#1-{}%
newcommandUD@CheckWhetherNoDash[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@GobbleToDash#1-}%
}%
%%-----------------------------------------------------------------------------
%% Take a comma-delimited/dash-delimited argument where a space was
%% prepended for preventing brace-removal and wrap it in curly braces.
%%.............................................................................
newcommandUD@SplitCommaArg{}%
longdefUD@SplitCommaArg#1,{%
romannumeral0UD@exchange{ }{expandafter}expandafter{UD@removespace#1}%
}%
newcommandUD@SplitDashArg{}%
longdefUD@SplitDashArg#1-{%
romannumeral0UD@exchange{ }{expandafter}expandafter{UD@removespace#1}%
}%
%%-----------------------------------------------------------------------------
%% Now we have the tools for creating the desired mycommand-macro:
%%.............................................................................
newcommandmycommand[1]{%
romannumeral0%
UD@CheckWhetherNoComma{#1}{%
expandaftermycommand@oneargumentexpandafter{%
romannumeral0UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}{%
expandafterUD@exchangeexpandafter{expandafter
{UD@GobbleToComma#1}%
}{%
expandafter@mycommand
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@ExtractFirstArgexpandafter{%
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@SplitCommaArg
UD@firstoftwo{ }{}#1}%
}%
}%
}%
newcommand@mycommand[2]{%
UD@CheckWhetherNoDash{#2}{%
expandafterUD@exchangeexpandafter{expandafter
{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#2}%
}%
}{%
expandaftermycommand@twoargumentsexpandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}%
}{%
expandafterUD@exchangeexpandafter{expandafter%
{UD@GobbleToDash#2}%
}{%
expandafter@@mycommand
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@ExtractFirstArgexpandafter{%
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@SplitDashArg
UD@firstoftwo{ }{}#2}%
}%
{#1}%
}%
}%
newcommand@@mycommand[3]{%
expandafterUD@exchangeexpandafter{%
expandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#2}%
}%
}{%
expandafterUD@exchangeexpandafter{%
expandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}{%
expandaftermycommand@threeargumentsexpandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#3}%
}%
}%
}%
}%
newcommandmycommand@oneargument[1]{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is no comma or only empty args after the comma, thus:\
argument 1 = printmeaning{#1}%
}%
newcommandmycommand@twoarguments[2]{%
UD@CheckWhetherNull{#2}{%
mycommand@oneargument{#1}%
}{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is a comma but either no dash or only one non-empty argument near
the dash, thus:\
argument 1 = printmeaning{#1}\
argument 2 = printmeaning{#2}%
}%
}%
newcommandmycommand@threearguments[3]{%
UD@CheckWhetherNull{#2}{%
UD@CheckWhetherNull{#3}{%
mycommand@oneargument{#1}%
}{%x
mycommand@twoarguments{#1}{#3}%
}%
}{%
UD@CheckWhetherNull{#3}{%
mycommand@twoarguments{#1}{#2}%
}{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is a comma and a dash, thus:\
argument 1 = printmeaning{#1}\
argument 2 = printmeaning{#2}\
argument 3 = printmeaning{#3}%
}%
}%
}%
newcommandprintmeaning[1]{%
defUD@tempa{#1}%
fbox{texttt{expandafterstrip@prefixmeaningUD@tempa}}%
}%
makeatother
parindent=0ex
parskip=smallskipamount
pagestyle{empty}%
begin{document}
vspace*{-3.5cm}%
enlargethispage{7cm}%
verb|mycommand{ arg 1 }|:\
mycommand{ arg 1 }\
nullhrulefillnull
verb|mycommand{ arg 1 , }|:\
mycommand{ arg 1 , }\
nullhrulefillnull
verb|mycommand{ arg 1 , - }|:\
mycommand{ arg 1 , - }\
nullhrulefillnull
verb|mycommand{ arg 1 , {{arg 2}} }|:\
mycommand{ arg 1 , {{arg 2}} }\
(Be aware that (only) the outermost braces get removed from
an argument if surrounding the entire argument after removal
of surrounding spaces.)\
nullhrulefillnull
verb|mycommand{ arg 1 , arg 2 - }|:\
mycommand{ arg 1 , arg 2 - }\
nullhrulefillnull
verb|mycommand{ arg 1 , arg 2 - arg 3}|:\
mycommand{ arg 1 , arg 2 - arg 3}\
nullhrulefillnull
verb|mycommand{ arg 1 , - arg 3}|:\
mycommand{ arg 1 , - arg 3}\
nullhrulefillnull
verb|mycommand{ {arg 1a} - arg 1b , {arg 2-2} , 2 - arg 3a - arg 3b}|:\
mycommand{ {arg 1a} - arg 1b , {arg 2-2} , 2 - arg 3a - arg 3b}\
nullhrulefillnull
verb|mycommand{ {arg 1} , {arg 2-2} , 2 - arg 3a - arg 3b}|:\
mycommand{ {arg 1} , {arg 2-2} , 2 - arg 3a - arg 3b}
end{document}
add a comment |
A listofitems
approach.
By setting a nested parsing with setsepchar{,/-}
prior to reading the list into myargs
, everything to the left of the 1st comma is read into myargs[1]
, everything to the right of the comma (and before the next comma) is read into myargs[2]
.
Within myargs[2]
, everything to the left of the first dash is assigned to myargs[2,1]
, and everything after the dash (and before the next dash) is assigned to myargs[2,2]
.
The number of sub-elements within myargs[2]
is available through listlenmyargs[2]
.
All these elements and list length variables are fully expandable.
documentclass{article}
usepackage{listofitems}
newcommandmycommand[1]{setsepchar{,/-}readlist*myargs{#1}%
noindent Arg 1 is myargs[1]\
Arg 2 is myargs[2,1]\
Arg 3 is ifnumlistlenmyargs[2]=1relax Absentelsemyargs[2,2]fi%
}
begin{document}
mycommand{arg1, arg2}
and
mycommand{arg1, arg2-arg3}
end{document}
add a comment |
One more:
documentclass{article}
usepackage{xstring}
defmycommand#1,#2 {IfSubStr{#2}{-}{[[#1],
[StrBefore{#2}{-}]-[StrBehind{#2}{-}]] }{[[#1],[#2]]}}
begin{document}
mycommand aaa,bbb-ccc
mycommand aaa,bbb
end{document}
add a comment |
Your Answer
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "85"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2ftex.stackexchange.com%2fquestions%2f473965%2fa-command-challenge%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
5 Answers
5
active
oldest
votes
5 Answers
5
active
oldest
votes
active
oldest
votes
active
oldest
votes
You can do it with xparse
:
documentclass{article}
usepackage{xparse}
% split at the comma
NewDocumentCommand{mycommand}{ >{SplitArgument{1}{,}}m }{%
mycommandA#1%
}
% do something with #1 and split the second part at the hyphen
NewDocumentCommand{mycommandA}{ m >{SplitArgument{1}{-}}m }{%
Main is #1%
IfNoValueTF{#2}{.}{; mycommandB#2}%
}
% do something with the second part
NewDocumentCommand{mycommandB}{mm}{%
Secondary is #1IfValueT{#2}{ plus #2}.%
}
begin{document}
mycommand{arg1}
mycommand{arg1, arg2}
mycommand{arg1, arg2-arg3}
end{document}
An advantage: it's irrelevant if you forget the space after the comma.
add a comment |
You can do it with xparse
:
documentclass{article}
usepackage{xparse}
% split at the comma
NewDocumentCommand{mycommand}{ >{SplitArgument{1}{,}}m }{%
mycommandA#1%
}
% do something with #1 and split the second part at the hyphen
NewDocumentCommand{mycommandA}{ m >{SplitArgument{1}{-}}m }{%
Main is #1%
IfNoValueTF{#2}{.}{; mycommandB#2}%
}
% do something with the second part
NewDocumentCommand{mycommandB}{mm}{%
Secondary is #1IfValueT{#2}{ plus #2}.%
}
begin{document}
mycommand{arg1}
mycommand{arg1, arg2}
mycommand{arg1, arg2-arg3}
end{document}
An advantage: it's irrelevant if you forget the space after the comma.
add a comment |
You can do it with xparse
:
documentclass{article}
usepackage{xparse}
% split at the comma
NewDocumentCommand{mycommand}{ >{SplitArgument{1}{,}}m }{%
mycommandA#1%
}
% do something with #1 and split the second part at the hyphen
NewDocumentCommand{mycommandA}{ m >{SplitArgument{1}{-}}m }{%
Main is #1%
IfNoValueTF{#2}{.}{; mycommandB#2}%
}
% do something with the second part
NewDocumentCommand{mycommandB}{mm}{%
Secondary is #1IfValueT{#2}{ plus #2}.%
}
begin{document}
mycommand{arg1}
mycommand{arg1, arg2}
mycommand{arg1, arg2-arg3}
end{document}
An advantage: it's irrelevant if you forget the space after the comma.
You can do it with xparse
:
documentclass{article}
usepackage{xparse}
% split at the comma
NewDocumentCommand{mycommand}{ >{SplitArgument{1}{,}}m }{%
mycommandA#1%
}
% do something with #1 and split the second part at the hyphen
NewDocumentCommand{mycommandA}{ m >{SplitArgument{1}{-}}m }{%
Main is #1%
IfNoValueTF{#2}{.}{; mycommandB#2}%
}
% do something with the second part
NewDocumentCommand{mycommandB}{mm}{%
Secondary is #1IfValueT{#2}{ plus #2}.%
}
begin{document}
mycommand{arg1}
mycommand{arg1, arg2}
mycommand{arg1, arg2-arg3}
end{document}
An advantage: it's irrelevant if you forget the space after the comma.
answered Feb 8 at 18:22
egregegreg
720k8719093208
720k8719093208
add a comment |
add a comment |
documentclass{article}
defmycommand#1{xmycommand#1--relax}
defxmycommand#1,#2-#3-#4relax{[#1][#2][#3]}
begin{document}
$mycommand{arg_1, arg_2}$
$mycommand{arg_1, arg_2-arg3}$
end{document}
6
Even without electricity. I'm impressed!
– marmot
Feb 8 at 17:53
How about adding some mortar to the bricks!?
– Werner
Feb 8 at 18:06
@Werner where I come from we prefer this style: derbyshireuk.net/derbyshire_drystonewalls.html
– David Carlisle
Feb 8 at 18:12
5
@noibe oh no a disaster! I don't mind not getting the tick (I have more than enough points) but seeing points go to egreg is so depressing:)
– David Carlisle
Feb 8 at 18:49
2
Nozzz
? :'-(
– Henri Menke
Feb 9 at 6:02
|
show 4 more comments
documentclass{article}
defmycommand#1{xmycommand#1--relax}
defxmycommand#1,#2-#3-#4relax{[#1][#2][#3]}
begin{document}
$mycommand{arg_1, arg_2}$
$mycommand{arg_1, arg_2-arg3}$
end{document}
6
Even without electricity. I'm impressed!
– marmot
Feb 8 at 17:53
How about adding some mortar to the bricks!?
– Werner
Feb 8 at 18:06
@Werner where I come from we prefer this style: derbyshireuk.net/derbyshire_drystonewalls.html
– David Carlisle
Feb 8 at 18:12
5
@noibe oh no a disaster! I don't mind not getting the tick (I have more than enough points) but seeing points go to egreg is so depressing:)
– David Carlisle
Feb 8 at 18:49
2
Nozzz
? :'-(
– Henri Menke
Feb 9 at 6:02
|
show 4 more comments
documentclass{article}
defmycommand#1{xmycommand#1--relax}
defxmycommand#1,#2-#3-#4relax{[#1][#2][#3]}
begin{document}
$mycommand{arg_1, arg_2}$
$mycommand{arg_1, arg_2-arg3}$
end{document}
documentclass{article}
defmycommand#1{xmycommand#1--relax}
defxmycommand#1,#2-#3-#4relax{[#1][#2][#3]}
begin{document}
$mycommand{arg_1, arg_2}$
$mycommand{arg_1, arg_2-arg3}$
end{document}
answered Feb 8 at 17:48
David CarlisleDavid Carlisle
490k4111341883
490k4111341883
6
Even without electricity. I'm impressed!
– marmot
Feb 8 at 17:53
How about adding some mortar to the bricks!?
– Werner
Feb 8 at 18:06
@Werner where I come from we prefer this style: derbyshireuk.net/derbyshire_drystonewalls.html
– David Carlisle
Feb 8 at 18:12
5
@noibe oh no a disaster! I don't mind not getting the tick (I have more than enough points) but seeing points go to egreg is so depressing:)
– David Carlisle
Feb 8 at 18:49
2
Nozzz
? :'-(
– Henri Menke
Feb 9 at 6:02
|
show 4 more comments
6
Even without electricity. I'm impressed!
– marmot
Feb 8 at 17:53
How about adding some mortar to the bricks!?
– Werner
Feb 8 at 18:06
@Werner where I come from we prefer this style: derbyshireuk.net/derbyshire_drystonewalls.html
– David Carlisle
Feb 8 at 18:12
5
@noibe oh no a disaster! I don't mind not getting the tick (I have more than enough points) but seeing points go to egreg is so depressing:)
– David Carlisle
Feb 8 at 18:49
2
Nozzz
? :'-(
– Henri Menke
Feb 9 at 6:02
6
6
Even without electricity. I'm impressed!
– marmot
Feb 8 at 17:53
Even without electricity. I'm impressed!
– marmot
Feb 8 at 17:53
How about adding some mortar to the bricks!?
– Werner
Feb 8 at 18:06
How about adding some mortar to the bricks!?
– Werner
Feb 8 at 18:06
@Werner where I come from we prefer this style: derbyshireuk.net/derbyshire_drystonewalls.html
– David Carlisle
Feb 8 at 18:12
@Werner where I come from we prefer this style: derbyshireuk.net/derbyshire_drystonewalls.html
– David Carlisle
Feb 8 at 18:12
5
5
@noibe oh no a disaster! I don't mind not getting the tick (I have more than enough points) but seeing points go to egreg is so depressing:)
– David Carlisle
Feb 8 at 18:49
@noibe oh no a disaster! I don't mind not getting the tick (I have more than enough points) but seeing points go to egreg is so depressing:)
– David Carlisle
Feb 8 at 18:49
2
2
No
zzz
? :'-(– Henri Menke
Feb 9 at 6:02
No
zzz
? :'-(– Henri Menke
Feb 9 at 6:02
|
show 4 more comments
Some crocheting/knitting by hand (or whatever)... ;-)
I suppose this question came into being while you thought about your other question: Custom cite command. In that question you ask for a customized cite
-command with the same syntax.
Be that as it may.
The challenge is doing it in a way where desired space-removal but no undesired removal of argument-braces takes place.
E.g., with things like mycommand{{arg_1}a, arg_2}
the braces surrounding arg_1
should probably be preserved while with things like mycommand{{arg_1} , arg_2}
they should probably be removed.
Also probably only space-tokens surrounding the entire arguments/surrounding those commas and those dashes that are taken for argument-separators should get removed.
Below is an approach where comma takes precedence over the dash and where the leftmost unhidden comma delimits the first argument from the remaining arguments and where the first unhidden dash following that comma delimits the second from the third argument.
Exactly one pair of matching curly braces will be removed in case after removing surrounding spaces it surrounds an entire argument as in that case it is assumed that the braces serve for hiding commas/dashes/spaces/emptiness. In all other cases braces will be preserved.
E.g., with things like mycommand{ {arg_1} , {arg_2-2} - arg_3}
you should get:
First argument: arg_1
Second argument: arg_2-2
Third argument: arg_3
.
E.g., with things like mycommand{ {{arg 1,a} - arg 1b} , {arg 2-2} , 2 - arg 3a - arg 3b}
you should get:
First argument (left from the first unhidden comma, surrounding spaces and outermost brace-level that surrounds the entire argument removed): {arg 1,a} - arg 1b
Second argument (right from the first unhidden comma, left from the first unhidden dash following the comma): {arg 2-2} , 2
Third argument (the remainder, right from the first unhidden dash following the comma): arg 3a - arg 3b
.
E.g., with things like mycommand{{arg 1}, {arg 2-2} , 2 - arg 3a - arg 3b}
you should get:
First argument (left from the first unhidden comma): arg 1
Second argument (right from the first unhidden comma, left from the first unhidden dash following the comma): {arg 2-2} , 2
Third argument (the remainder, right from the first unhidden dash following the comma): arg 3a - arg 3b
.
You may not be familiar to methods for removing space-tokens that surround arguments, thus a quote from the manual of my deprecated labelcas-package:
A note about removing leading and trailing spaces
The matter of removing trailing spaces from an (almost) arbitrary
token-sequence is elaborated in detail by Michael Downes, 'Around the
Bend #15, answers', a summary of internet-discussion which took place
under his guidance primarily at the INFO-TEX list, but also at
comp.text.tex (usenet) and via private email; December 1993. Online
archived at http://www.tug.org/tex-archive/info/arobend/answer.015.
One basic approach suggested therein is using TeX's scanning of
delimited parameters in order to detect and discard the ending space
of an argument:
... scan for a pair of tokens: a space-token and some well-chosen
bizarre token that can't possibly occur in the scanned text. If you
put the bizarre token at the end of the text, and if the text has a
trailing space, then TeX's delimiter matching will match at that point
and not before, because the earlier occurrences of space don’t have
the requisite other member of the pair.
Next consider the possibility that the trailing space is absent: TeX
will keep on scanning ahead for the pair
⟨space⟩⟨bizarre⟩
until either it finds them or it decides to give up and signal a
'Runaway argument?' error. So you must add a stop pair to catch the
runaway argument possibility: a second instance of the bizarre
token, preceded by a space. If TeX doesn't find a match at the
first bizarre token, it will at the second one.
(Look up the macros
KV@@sp@def
,KV@@sp@b
,KV@@sp@c
andKV@@sp@d
in
David Carlisle’s keyval-package for an interesting variation on this
approach.)
When scanning for parameters
##1⟨space⟩⟨bizarre⟩##2⟨B1⟩
the sequence:⟨stuff where to remove trail-space⟩⟨bizarre⟩⟨space⟩⟨bizarre⟩⟨B1⟩
, you can fork two cases:
Trailing-space:
##1
=⟨stuff where to remove trail-space⟩
, but with removed space. (And possibly one
removed brace-level!)##2
=⟨space⟩⟨bizarre⟩
.
No trailing-space:
##1
=⟨stuff where to remove trail-space⟩⟨bizarre⟩
.##2
is empty.
So forking can be implemented depending on the emptiness of
##2
.
You can easily prevent the brace-removal in the first case, e.g., by adding (and
later removing) something (e.g., a space-token) in front of the
⟨stuff where to remove trail-space⟩
.
You can choose
⟨B1⟩=⟨bizarre⟩⟨space⟩
.
The link in the quote is out of date.
Nowadays you can find the entire "Around the bend" -collection at http://mirrors.ctan.org/info/challenges/AroBend/AroundTheBend.pdf.
As you see, the methods for space-removal exhibited in the quote above do rely on some sequence of tokens that must not occur within the argument.
In the example below it is relied on the token UD@seldom
not occurring within arguments.
In other words: You must not use the token UD@seldom
within your arguments.
If you don't like that restriction, then I can deliver space-removal-routines which do without such restrictions, but they are slower and they form another huge load of code.
documentclass{article}
makeatletter
%%-----------------------------------------------------------------------------
%% Paraphernalia ;-) :
%%.............................................................................
newcommandUD@firstoftwo[2]{#1}%
newcommandUD@secondoftwo[2]{#2}%
newcommandUD@exchange[2]{#2#1}%
newcommandUD@removespace{}UD@firstoftwo{defUD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% 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>
%%.............................................................................
newcommandUD@CheckWhetherNull[1]{%
romannumeral0expandafterUD@secondoftwostring{expandafter
UD@secondoftwoexpandafter{expandafter{string#1}expandafter
UD@secondoftwostring}expandafterUD@firstoftwoexpandafter{expandafter
UD@secondoftwostring}expandafterexpandafterUD@firstoftwo{ }{}%
UD@secondoftwo}{expandafterexpandafterUD@firstoftwo{ }{}UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>}%
newcommandUD@CheckWhetherLeadingSpace[1]{%
romannumeral0UD@CheckWhetherNull{#1}%
{expandafterexpandafterUD@firstoftwo{ }{}UD@secondoftwo}%
{expandafterUD@secondoftwostring{UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
newcommandUD@CheckWhetherLeadingSpaceB{}%
longdefUD@CheckWhetherLeadingSpaceB#1 {%
expandafterUD@CheckWhetherNullexpandafter{UD@secondoftwo#1{}}%
{UD@exchange{UD@firstoftwo}}{UD@exchange{UD@secondoftwo}}%
{UD@exchange{ }{expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter}expandafterexpandafter
expandafter}expandafterUD@secondoftwoexpandafter{string}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%% UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% UD@ExtractFirstArg{{AB}CDE} yields {AB}
%%
%% !!! The argument of UD@ExtractFirstArg must not be empty. !!!
%% You can check for emptiness via UD@CheckWhetherNull before applying
%% UD@ExtractFirstArg.
%%.............................................................................
newcommandUD@RemoveTillUD@SelDOm{}%
longdefUD@RemoveTillUD@SelDOm#1#2UD@SelDOm{{#1}}%
newcommandUD@ExtractFirstArg[1]{%
romannumeral0%
UD@ExtractFirstArgLoop{#1UD@SelDOm}%
}%
newcommandUD@ExtractFirstArgLoop[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@firstoftwo{}#1}%
{ #1}%
{expandafterUD@ExtractFirstArgLoopexpandafter{UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% UD@RemoveSpacesAndOneLevelOfBraces{<argument>} removes leading and
%% trailing spaces from <argument>.
%% If after that the <argument> is something that is entirely nested
%% between at least one pair of matching curly braces, the outermost
%% pair of these braces will be removed.
%%
%% !!!! <argument> must not contain the token UD@seldom !!!!
%%.............................................................................
begingroup
newcommandUD@RemoveSpacesAndOneLevelOfBraces[1]{%
endgroup
newcommandUD@RemoveSpacesAndOneLevelOfBraces[1]{%
romannumeral0%
UD@trimtrailspaceloop#1##1UD@seldom#1UD@seldomUD@seldom#1{##1}%
}%
newcommandUD@trimtrailspaceloop{}%
longdefUD@trimtrailspaceloop##1#1UD@seldom##2UD@seldom#1##3{%
UD@CheckWhetherNull{##2}{%
UD@trimleadspaceloop{##3}%
}{%
UD@trimtrailspaceloop##1UD@seldom#1UD@seldomUD@seldom#1{##1}%
}%
}%
}%
UD@RemoveSpacesAndOneLevelOfBraces{ }%
newcommandUD@trimleadspaceloop[1]{%
UD@CheckWhetherLeadingSpace{#1}{%
expandafterUD@trimleadspaceloopexpandafter{UD@removespace#1}%
}{%
UD@CheckWhetherNull{#1}{ }{%
expandafterUD@CheckWhetherNullexpandafter{UD@firstoftwo{}#1}{%
UD@exchange{ }{expandafter}UD@secondoftwo{}#1%
}{ #1}%
}%
}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no comma which is not nested
%% in braces:
%%.............................................................................
%% UD@CheckWhetherNoComma{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% contains no comma>}%
%% {<Tokens to be delivered in case that argument
%% contains comma>}%
%%
newcommandUD@GobbleToComma{}longdefUD@GobbleToComma#1,{}%
newcommandUD@CheckWhetherNoComma[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@GobbleToComma#1,}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no dash which is not nested
%% in braces:
%%.............................................................................
%% UD@CheckWhetherNoDash{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% contains no dash>}%
%% {<Tokens to be delivered in case that argument
%% contains dash>}%
%%
newcommandUD@GobbleToDash{}longdefUD@GobbleToDash#1-{}%
newcommandUD@CheckWhetherNoDash[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@GobbleToDash#1-}%
}%
%%-----------------------------------------------------------------------------
%% Take a comma-delimited/dash-delimited argument where a space was
%% prepended for preventing brace-removal and wrap it in curly braces.
%%.............................................................................
newcommandUD@SplitCommaArg{}%
longdefUD@SplitCommaArg#1,{%
romannumeral0UD@exchange{ }{expandafter}expandafter{UD@removespace#1}%
}%
newcommandUD@SplitDashArg{}%
longdefUD@SplitDashArg#1-{%
romannumeral0UD@exchange{ }{expandafter}expandafter{UD@removespace#1}%
}%
%%-----------------------------------------------------------------------------
%% Now we have the tools for creating the desired mycommand-macro:
%%.............................................................................
newcommandmycommand[1]{%
romannumeral0%
UD@CheckWhetherNoComma{#1}{%
expandaftermycommand@oneargumentexpandafter{%
romannumeral0UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}{%
expandafterUD@exchangeexpandafter{expandafter
{UD@GobbleToComma#1}%
}{%
expandafter@mycommand
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@ExtractFirstArgexpandafter{%
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@SplitCommaArg
UD@firstoftwo{ }{}#1}%
}%
}%
}%
newcommand@mycommand[2]{%
UD@CheckWhetherNoDash{#2}{%
expandafterUD@exchangeexpandafter{expandafter
{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#2}%
}%
}{%
expandaftermycommand@twoargumentsexpandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}%
}{%
expandafterUD@exchangeexpandafter{expandafter%
{UD@GobbleToDash#2}%
}{%
expandafter@@mycommand
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@ExtractFirstArgexpandafter{%
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@SplitDashArg
UD@firstoftwo{ }{}#2}%
}%
{#1}%
}%
}%
newcommand@@mycommand[3]{%
expandafterUD@exchangeexpandafter{%
expandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#2}%
}%
}{%
expandafterUD@exchangeexpandafter{%
expandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}{%
expandaftermycommand@threeargumentsexpandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#3}%
}%
}%
}%
}%
newcommandmycommand@oneargument[1]{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is no comma or only empty args after the comma, thus:\
argument 1 = printmeaning{#1}%
}%
newcommandmycommand@twoarguments[2]{%
UD@CheckWhetherNull{#2}{%
mycommand@oneargument{#1}%
}{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is a comma but either no dash or only one non-empty argument near
the dash, thus:\
argument 1 = printmeaning{#1}\
argument 2 = printmeaning{#2}%
}%
}%
newcommandmycommand@threearguments[3]{%
UD@CheckWhetherNull{#2}{%
UD@CheckWhetherNull{#3}{%
mycommand@oneargument{#1}%
}{%x
mycommand@twoarguments{#1}{#3}%
}%
}{%
UD@CheckWhetherNull{#3}{%
mycommand@twoarguments{#1}{#2}%
}{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is a comma and a dash, thus:\
argument 1 = printmeaning{#1}\
argument 2 = printmeaning{#2}\
argument 3 = printmeaning{#3}%
}%
}%
}%
newcommandprintmeaning[1]{%
defUD@tempa{#1}%
fbox{texttt{expandafterstrip@prefixmeaningUD@tempa}}%
}%
makeatother
parindent=0ex
parskip=smallskipamount
pagestyle{empty}%
begin{document}
vspace*{-3.5cm}%
enlargethispage{7cm}%
verb|mycommand{ arg 1 }|:\
mycommand{ arg 1 }\
nullhrulefillnull
verb|mycommand{ arg 1 , }|:\
mycommand{ arg 1 , }\
nullhrulefillnull
verb|mycommand{ arg 1 , - }|:\
mycommand{ arg 1 , - }\
nullhrulefillnull
verb|mycommand{ arg 1 , {{arg 2}} }|:\
mycommand{ arg 1 , {{arg 2}} }\
(Be aware that (only) the outermost braces get removed from
an argument if surrounding the entire argument after removal
of surrounding spaces.)\
nullhrulefillnull
verb|mycommand{ arg 1 , arg 2 - }|:\
mycommand{ arg 1 , arg 2 - }\
nullhrulefillnull
verb|mycommand{ arg 1 , arg 2 - arg 3}|:\
mycommand{ arg 1 , arg 2 - arg 3}\
nullhrulefillnull
verb|mycommand{ arg 1 , - arg 3}|:\
mycommand{ arg 1 , - arg 3}\
nullhrulefillnull
verb|mycommand{ {arg 1a} - arg 1b , {arg 2-2} , 2 - arg 3a - arg 3b}|:\
mycommand{ {arg 1a} - arg 1b , {arg 2-2} , 2 - arg 3a - arg 3b}\
nullhrulefillnull
verb|mycommand{ {arg 1} , {arg 2-2} , 2 - arg 3a - arg 3b}|:\
mycommand{ {arg 1} , {arg 2-2} , 2 - arg 3a - arg 3b}
end{document}
add a comment |
Some crocheting/knitting by hand (or whatever)... ;-)
I suppose this question came into being while you thought about your other question: Custom cite command. In that question you ask for a customized cite
-command with the same syntax.
Be that as it may.
The challenge is doing it in a way where desired space-removal but no undesired removal of argument-braces takes place.
E.g., with things like mycommand{{arg_1}a, arg_2}
the braces surrounding arg_1
should probably be preserved while with things like mycommand{{arg_1} , arg_2}
they should probably be removed.
Also probably only space-tokens surrounding the entire arguments/surrounding those commas and those dashes that are taken for argument-separators should get removed.
Below is an approach where comma takes precedence over the dash and where the leftmost unhidden comma delimits the first argument from the remaining arguments and where the first unhidden dash following that comma delimits the second from the third argument.
Exactly one pair of matching curly braces will be removed in case after removing surrounding spaces it surrounds an entire argument as in that case it is assumed that the braces serve for hiding commas/dashes/spaces/emptiness. In all other cases braces will be preserved.
E.g., with things like mycommand{ {arg_1} , {arg_2-2} - arg_3}
you should get:
First argument: arg_1
Second argument: arg_2-2
Third argument: arg_3
.
E.g., with things like mycommand{ {{arg 1,a} - arg 1b} , {arg 2-2} , 2 - arg 3a - arg 3b}
you should get:
First argument (left from the first unhidden comma, surrounding spaces and outermost brace-level that surrounds the entire argument removed): {arg 1,a} - arg 1b
Second argument (right from the first unhidden comma, left from the first unhidden dash following the comma): {arg 2-2} , 2
Third argument (the remainder, right from the first unhidden dash following the comma): arg 3a - arg 3b
.
E.g., with things like mycommand{{arg 1}, {arg 2-2} , 2 - arg 3a - arg 3b}
you should get:
First argument (left from the first unhidden comma): arg 1
Second argument (right from the first unhidden comma, left from the first unhidden dash following the comma): {arg 2-2} , 2
Third argument (the remainder, right from the first unhidden dash following the comma): arg 3a - arg 3b
.
You may not be familiar to methods for removing space-tokens that surround arguments, thus a quote from the manual of my deprecated labelcas-package:
A note about removing leading and trailing spaces
The matter of removing trailing spaces from an (almost) arbitrary
token-sequence is elaborated in detail by Michael Downes, 'Around the
Bend #15, answers', a summary of internet-discussion which took place
under his guidance primarily at the INFO-TEX list, but also at
comp.text.tex (usenet) and via private email; December 1993. Online
archived at http://www.tug.org/tex-archive/info/arobend/answer.015.
One basic approach suggested therein is using TeX's scanning of
delimited parameters in order to detect and discard the ending space
of an argument:
... scan for a pair of tokens: a space-token and some well-chosen
bizarre token that can't possibly occur in the scanned text. If you
put the bizarre token at the end of the text, and if the text has a
trailing space, then TeX's delimiter matching will match at that point
and not before, because the earlier occurrences of space don’t have
the requisite other member of the pair.
Next consider the possibility that the trailing space is absent: TeX
will keep on scanning ahead for the pair
⟨space⟩⟨bizarre⟩
until either it finds them or it decides to give up and signal a
'Runaway argument?' error. So you must add a stop pair to catch the
runaway argument possibility: a second instance of the bizarre
token, preceded by a space. If TeX doesn't find a match at the
first bizarre token, it will at the second one.
(Look up the macros
KV@@sp@def
,KV@@sp@b
,KV@@sp@c
andKV@@sp@d
in
David Carlisle’s keyval-package for an interesting variation on this
approach.)
When scanning for parameters
##1⟨space⟩⟨bizarre⟩##2⟨B1⟩
the sequence:⟨stuff where to remove trail-space⟩⟨bizarre⟩⟨space⟩⟨bizarre⟩⟨B1⟩
, you can fork two cases:
Trailing-space:
##1
=⟨stuff where to remove trail-space⟩
, but with removed space. (And possibly one
removed brace-level!)##2
=⟨space⟩⟨bizarre⟩
.
No trailing-space:
##1
=⟨stuff where to remove trail-space⟩⟨bizarre⟩
.##2
is empty.
So forking can be implemented depending on the emptiness of
##2
.
You can easily prevent the brace-removal in the first case, e.g., by adding (and
later removing) something (e.g., a space-token) in front of the
⟨stuff where to remove trail-space⟩
.
You can choose
⟨B1⟩=⟨bizarre⟩⟨space⟩
.
The link in the quote is out of date.
Nowadays you can find the entire "Around the bend" -collection at http://mirrors.ctan.org/info/challenges/AroBend/AroundTheBend.pdf.
As you see, the methods for space-removal exhibited in the quote above do rely on some sequence of tokens that must not occur within the argument.
In the example below it is relied on the token UD@seldom
not occurring within arguments.
In other words: You must not use the token UD@seldom
within your arguments.
If you don't like that restriction, then I can deliver space-removal-routines which do without such restrictions, but they are slower and they form another huge load of code.
documentclass{article}
makeatletter
%%-----------------------------------------------------------------------------
%% Paraphernalia ;-) :
%%.............................................................................
newcommandUD@firstoftwo[2]{#1}%
newcommandUD@secondoftwo[2]{#2}%
newcommandUD@exchange[2]{#2#1}%
newcommandUD@removespace{}UD@firstoftwo{defUD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% 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>
%%.............................................................................
newcommandUD@CheckWhetherNull[1]{%
romannumeral0expandafterUD@secondoftwostring{expandafter
UD@secondoftwoexpandafter{expandafter{string#1}expandafter
UD@secondoftwostring}expandafterUD@firstoftwoexpandafter{expandafter
UD@secondoftwostring}expandafterexpandafterUD@firstoftwo{ }{}%
UD@secondoftwo}{expandafterexpandafterUD@firstoftwo{ }{}UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>}%
newcommandUD@CheckWhetherLeadingSpace[1]{%
romannumeral0UD@CheckWhetherNull{#1}%
{expandafterexpandafterUD@firstoftwo{ }{}UD@secondoftwo}%
{expandafterUD@secondoftwostring{UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
newcommandUD@CheckWhetherLeadingSpaceB{}%
longdefUD@CheckWhetherLeadingSpaceB#1 {%
expandafterUD@CheckWhetherNullexpandafter{UD@secondoftwo#1{}}%
{UD@exchange{UD@firstoftwo}}{UD@exchange{UD@secondoftwo}}%
{UD@exchange{ }{expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter}expandafterexpandafter
expandafter}expandafterUD@secondoftwoexpandafter{string}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%% UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% UD@ExtractFirstArg{{AB}CDE} yields {AB}
%%
%% !!! The argument of UD@ExtractFirstArg must not be empty. !!!
%% You can check for emptiness via UD@CheckWhetherNull before applying
%% UD@ExtractFirstArg.
%%.............................................................................
newcommandUD@RemoveTillUD@SelDOm{}%
longdefUD@RemoveTillUD@SelDOm#1#2UD@SelDOm{{#1}}%
newcommandUD@ExtractFirstArg[1]{%
romannumeral0%
UD@ExtractFirstArgLoop{#1UD@SelDOm}%
}%
newcommandUD@ExtractFirstArgLoop[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@firstoftwo{}#1}%
{ #1}%
{expandafterUD@ExtractFirstArgLoopexpandafter{UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% UD@RemoveSpacesAndOneLevelOfBraces{<argument>} removes leading and
%% trailing spaces from <argument>.
%% If after that the <argument> is something that is entirely nested
%% between at least one pair of matching curly braces, the outermost
%% pair of these braces will be removed.
%%
%% !!!! <argument> must not contain the token UD@seldom !!!!
%%.............................................................................
begingroup
newcommandUD@RemoveSpacesAndOneLevelOfBraces[1]{%
endgroup
newcommandUD@RemoveSpacesAndOneLevelOfBraces[1]{%
romannumeral0%
UD@trimtrailspaceloop#1##1UD@seldom#1UD@seldomUD@seldom#1{##1}%
}%
newcommandUD@trimtrailspaceloop{}%
longdefUD@trimtrailspaceloop##1#1UD@seldom##2UD@seldom#1##3{%
UD@CheckWhetherNull{##2}{%
UD@trimleadspaceloop{##3}%
}{%
UD@trimtrailspaceloop##1UD@seldom#1UD@seldomUD@seldom#1{##1}%
}%
}%
}%
UD@RemoveSpacesAndOneLevelOfBraces{ }%
newcommandUD@trimleadspaceloop[1]{%
UD@CheckWhetherLeadingSpace{#1}{%
expandafterUD@trimleadspaceloopexpandafter{UD@removespace#1}%
}{%
UD@CheckWhetherNull{#1}{ }{%
expandafterUD@CheckWhetherNullexpandafter{UD@firstoftwo{}#1}{%
UD@exchange{ }{expandafter}UD@secondoftwo{}#1%
}{ #1}%
}%
}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no comma which is not nested
%% in braces:
%%.............................................................................
%% UD@CheckWhetherNoComma{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% contains no comma>}%
%% {<Tokens to be delivered in case that argument
%% contains comma>}%
%%
newcommandUD@GobbleToComma{}longdefUD@GobbleToComma#1,{}%
newcommandUD@CheckWhetherNoComma[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@GobbleToComma#1,}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no dash which is not nested
%% in braces:
%%.............................................................................
%% UD@CheckWhetherNoDash{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% contains no dash>}%
%% {<Tokens to be delivered in case that argument
%% contains dash>}%
%%
newcommandUD@GobbleToDash{}longdefUD@GobbleToDash#1-{}%
newcommandUD@CheckWhetherNoDash[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@GobbleToDash#1-}%
}%
%%-----------------------------------------------------------------------------
%% Take a comma-delimited/dash-delimited argument where a space was
%% prepended for preventing brace-removal and wrap it in curly braces.
%%.............................................................................
newcommandUD@SplitCommaArg{}%
longdefUD@SplitCommaArg#1,{%
romannumeral0UD@exchange{ }{expandafter}expandafter{UD@removespace#1}%
}%
newcommandUD@SplitDashArg{}%
longdefUD@SplitDashArg#1-{%
romannumeral0UD@exchange{ }{expandafter}expandafter{UD@removespace#1}%
}%
%%-----------------------------------------------------------------------------
%% Now we have the tools for creating the desired mycommand-macro:
%%.............................................................................
newcommandmycommand[1]{%
romannumeral0%
UD@CheckWhetherNoComma{#1}{%
expandaftermycommand@oneargumentexpandafter{%
romannumeral0UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}{%
expandafterUD@exchangeexpandafter{expandafter
{UD@GobbleToComma#1}%
}{%
expandafter@mycommand
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@ExtractFirstArgexpandafter{%
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@SplitCommaArg
UD@firstoftwo{ }{}#1}%
}%
}%
}%
newcommand@mycommand[2]{%
UD@CheckWhetherNoDash{#2}{%
expandafterUD@exchangeexpandafter{expandafter
{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#2}%
}%
}{%
expandaftermycommand@twoargumentsexpandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}%
}{%
expandafterUD@exchangeexpandafter{expandafter%
{UD@GobbleToDash#2}%
}{%
expandafter@@mycommand
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@ExtractFirstArgexpandafter{%
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@SplitDashArg
UD@firstoftwo{ }{}#2}%
}%
{#1}%
}%
}%
newcommand@@mycommand[3]{%
expandafterUD@exchangeexpandafter{%
expandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#2}%
}%
}{%
expandafterUD@exchangeexpandafter{%
expandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}{%
expandaftermycommand@threeargumentsexpandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#3}%
}%
}%
}%
}%
newcommandmycommand@oneargument[1]{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is no comma or only empty args after the comma, thus:\
argument 1 = printmeaning{#1}%
}%
newcommandmycommand@twoarguments[2]{%
UD@CheckWhetherNull{#2}{%
mycommand@oneargument{#1}%
}{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is a comma but either no dash or only one non-empty argument near
the dash, thus:\
argument 1 = printmeaning{#1}\
argument 2 = printmeaning{#2}%
}%
}%
newcommandmycommand@threearguments[3]{%
UD@CheckWhetherNull{#2}{%
UD@CheckWhetherNull{#3}{%
mycommand@oneargument{#1}%
}{%x
mycommand@twoarguments{#1}{#3}%
}%
}{%
UD@CheckWhetherNull{#3}{%
mycommand@twoarguments{#1}{#2}%
}{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is a comma and a dash, thus:\
argument 1 = printmeaning{#1}\
argument 2 = printmeaning{#2}\
argument 3 = printmeaning{#3}%
}%
}%
}%
newcommandprintmeaning[1]{%
defUD@tempa{#1}%
fbox{texttt{expandafterstrip@prefixmeaningUD@tempa}}%
}%
makeatother
parindent=0ex
parskip=smallskipamount
pagestyle{empty}%
begin{document}
vspace*{-3.5cm}%
enlargethispage{7cm}%
verb|mycommand{ arg 1 }|:\
mycommand{ arg 1 }\
nullhrulefillnull
verb|mycommand{ arg 1 , }|:\
mycommand{ arg 1 , }\
nullhrulefillnull
verb|mycommand{ arg 1 , - }|:\
mycommand{ arg 1 , - }\
nullhrulefillnull
verb|mycommand{ arg 1 , {{arg 2}} }|:\
mycommand{ arg 1 , {{arg 2}} }\
(Be aware that (only) the outermost braces get removed from
an argument if surrounding the entire argument after removal
of surrounding spaces.)\
nullhrulefillnull
verb|mycommand{ arg 1 , arg 2 - }|:\
mycommand{ arg 1 , arg 2 - }\
nullhrulefillnull
verb|mycommand{ arg 1 , arg 2 - arg 3}|:\
mycommand{ arg 1 , arg 2 - arg 3}\
nullhrulefillnull
verb|mycommand{ arg 1 , - arg 3}|:\
mycommand{ arg 1 , - arg 3}\
nullhrulefillnull
verb|mycommand{ {arg 1a} - arg 1b , {arg 2-2} , 2 - arg 3a - arg 3b}|:\
mycommand{ {arg 1a} - arg 1b , {arg 2-2} , 2 - arg 3a - arg 3b}\
nullhrulefillnull
verb|mycommand{ {arg 1} , {arg 2-2} , 2 - arg 3a - arg 3b}|:\
mycommand{ {arg 1} , {arg 2-2} , 2 - arg 3a - arg 3b}
end{document}
add a comment |
Some crocheting/knitting by hand (or whatever)... ;-)
I suppose this question came into being while you thought about your other question: Custom cite command. In that question you ask for a customized cite
-command with the same syntax.
Be that as it may.
The challenge is doing it in a way where desired space-removal but no undesired removal of argument-braces takes place.
E.g., with things like mycommand{{arg_1}a, arg_2}
the braces surrounding arg_1
should probably be preserved while with things like mycommand{{arg_1} , arg_2}
they should probably be removed.
Also probably only space-tokens surrounding the entire arguments/surrounding those commas and those dashes that are taken for argument-separators should get removed.
Below is an approach where comma takes precedence over the dash and where the leftmost unhidden comma delimits the first argument from the remaining arguments and where the first unhidden dash following that comma delimits the second from the third argument.
Exactly one pair of matching curly braces will be removed in case after removing surrounding spaces it surrounds an entire argument as in that case it is assumed that the braces serve for hiding commas/dashes/spaces/emptiness. In all other cases braces will be preserved.
E.g., with things like mycommand{ {arg_1} , {arg_2-2} - arg_3}
you should get:
First argument: arg_1
Second argument: arg_2-2
Third argument: arg_3
.
E.g., with things like mycommand{ {{arg 1,a} - arg 1b} , {arg 2-2} , 2 - arg 3a - arg 3b}
you should get:
First argument (left from the first unhidden comma, surrounding spaces and outermost brace-level that surrounds the entire argument removed): {arg 1,a} - arg 1b
Second argument (right from the first unhidden comma, left from the first unhidden dash following the comma): {arg 2-2} , 2
Third argument (the remainder, right from the first unhidden dash following the comma): arg 3a - arg 3b
.
E.g., with things like mycommand{{arg 1}, {arg 2-2} , 2 - arg 3a - arg 3b}
you should get:
First argument (left from the first unhidden comma): arg 1
Second argument (right from the first unhidden comma, left from the first unhidden dash following the comma): {arg 2-2} , 2
Third argument (the remainder, right from the first unhidden dash following the comma): arg 3a - arg 3b
.
You may not be familiar to methods for removing space-tokens that surround arguments, thus a quote from the manual of my deprecated labelcas-package:
A note about removing leading and trailing spaces
The matter of removing trailing spaces from an (almost) arbitrary
token-sequence is elaborated in detail by Michael Downes, 'Around the
Bend #15, answers', a summary of internet-discussion which took place
under his guidance primarily at the INFO-TEX list, but also at
comp.text.tex (usenet) and via private email; December 1993. Online
archived at http://www.tug.org/tex-archive/info/arobend/answer.015.
One basic approach suggested therein is using TeX's scanning of
delimited parameters in order to detect and discard the ending space
of an argument:
... scan for a pair of tokens: a space-token and some well-chosen
bizarre token that can't possibly occur in the scanned text. If you
put the bizarre token at the end of the text, and if the text has a
trailing space, then TeX's delimiter matching will match at that point
and not before, because the earlier occurrences of space don’t have
the requisite other member of the pair.
Next consider the possibility that the trailing space is absent: TeX
will keep on scanning ahead for the pair
⟨space⟩⟨bizarre⟩
until either it finds them or it decides to give up and signal a
'Runaway argument?' error. So you must add a stop pair to catch the
runaway argument possibility: a second instance of the bizarre
token, preceded by a space. If TeX doesn't find a match at the
first bizarre token, it will at the second one.
(Look up the macros
KV@@sp@def
,KV@@sp@b
,KV@@sp@c
andKV@@sp@d
in
David Carlisle’s keyval-package for an interesting variation on this
approach.)
When scanning for parameters
##1⟨space⟩⟨bizarre⟩##2⟨B1⟩
the sequence:⟨stuff where to remove trail-space⟩⟨bizarre⟩⟨space⟩⟨bizarre⟩⟨B1⟩
, you can fork two cases:
Trailing-space:
##1
=⟨stuff where to remove trail-space⟩
, but with removed space. (And possibly one
removed brace-level!)##2
=⟨space⟩⟨bizarre⟩
.
No trailing-space:
##1
=⟨stuff where to remove trail-space⟩⟨bizarre⟩
.##2
is empty.
So forking can be implemented depending on the emptiness of
##2
.
You can easily prevent the brace-removal in the first case, e.g., by adding (and
later removing) something (e.g., a space-token) in front of the
⟨stuff where to remove trail-space⟩
.
You can choose
⟨B1⟩=⟨bizarre⟩⟨space⟩
.
The link in the quote is out of date.
Nowadays you can find the entire "Around the bend" -collection at http://mirrors.ctan.org/info/challenges/AroBend/AroundTheBend.pdf.
As you see, the methods for space-removal exhibited in the quote above do rely on some sequence of tokens that must not occur within the argument.
In the example below it is relied on the token UD@seldom
not occurring within arguments.
In other words: You must not use the token UD@seldom
within your arguments.
If you don't like that restriction, then I can deliver space-removal-routines which do without such restrictions, but they are slower and they form another huge load of code.
documentclass{article}
makeatletter
%%-----------------------------------------------------------------------------
%% Paraphernalia ;-) :
%%.............................................................................
newcommandUD@firstoftwo[2]{#1}%
newcommandUD@secondoftwo[2]{#2}%
newcommandUD@exchange[2]{#2#1}%
newcommandUD@removespace{}UD@firstoftwo{defUD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% 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>
%%.............................................................................
newcommandUD@CheckWhetherNull[1]{%
romannumeral0expandafterUD@secondoftwostring{expandafter
UD@secondoftwoexpandafter{expandafter{string#1}expandafter
UD@secondoftwostring}expandafterUD@firstoftwoexpandafter{expandafter
UD@secondoftwostring}expandafterexpandafterUD@firstoftwo{ }{}%
UD@secondoftwo}{expandafterexpandafterUD@firstoftwo{ }{}UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>}%
newcommandUD@CheckWhetherLeadingSpace[1]{%
romannumeral0UD@CheckWhetherNull{#1}%
{expandafterexpandafterUD@firstoftwo{ }{}UD@secondoftwo}%
{expandafterUD@secondoftwostring{UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
newcommandUD@CheckWhetherLeadingSpaceB{}%
longdefUD@CheckWhetherLeadingSpaceB#1 {%
expandafterUD@CheckWhetherNullexpandafter{UD@secondoftwo#1{}}%
{UD@exchange{UD@firstoftwo}}{UD@exchange{UD@secondoftwo}}%
{UD@exchange{ }{expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter}expandafterexpandafter
expandafter}expandafterUD@secondoftwoexpandafter{string}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%% UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% UD@ExtractFirstArg{{AB}CDE} yields {AB}
%%
%% !!! The argument of UD@ExtractFirstArg must not be empty. !!!
%% You can check for emptiness via UD@CheckWhetherNull before applying
%% UD@ExtractFirstArg.
%%.............................................................................
newcommandUD@RemoveTillUD@SelDOm{}%
longdefUD@RemoveTillUD@SelDOm#1#2UD@SelDOm{{#1}}%
newcommandUD@ExtractFirstArg[1]{%
romannumeral0%
UD@ExtractFirstArgLoop{#1UD@SelDOm}%
}%
newcommandUD@ExtractFirstArgLoop[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@firstoftwo{}#1}%
{ #1}%
{expandafterUD@ExtractFirstArgLoopexpandafter{UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% UD@RemoveSpacesAndOneLevelOfBraces{<argument>} removes leading and
%% trailing spaces from <argument>.
%% If after that the <argument> is something that is entirely nested
%% between at least one pair of matching curly braces, the outermost
%% pair of these braces will be removed.
%%
%% !!!! <argument> must not contain the token UD@seldom !!!!
%%.............................................................................
begingroup
newcommandUD@RemoveSpacesAndOneLevelOfBraces[1]{%
endgroup
newcommandUD@RemoveSpacesAndOneLevelOfBraces[1]{%
romannumeral0%
UD@trimtrailspaceloop#1##1UD@seldom#1UD@seldomUD@seldom#1{##1}%
}%
newcommandUD@trimtrailspaceloop{}%
longdefUD@trimtrailspaceloop##1#1UD@seldom##2UD@seldom#1##3{%
UD@CheckWhetherNull{##2}{%
UD@trimleadspaceloop{##3}%
}{%
UD@trimtrailspaceloop##1UD@seldom#1UD@seldomUD@seldom#1{##1}%
}%
}%
}%
UD@RemoveSpacesAndOneLevelOfBraces{ }%
newcommandUD@trimleadspaceloop[1]{%
UD@CheckWhetherLeadingSpace{#1}{%
expandafterUD@trimleadspaceloopexpandafter{UD@removespace#1}%
}{%
UD@CheckWhetherNull{#1}{ }{%
expandafterUD@CheckWhetherNullexpandafter{UD@firstoftwo{}#1}{%
UD@exchange{ }{expandafter}UD@secondoftwo{}#1%
}{ #1}%
}%
}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no comma which is not nested
%% in braces:
%%.............................................................................
%% UD@CheckWhetherNoComma{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% contains no comma>}%
%% {<Tokens to be delivered in case that argument
%% contains comma>}%
%%
newcommandUD@GobbleToComma{}longdefUD@GobbleToComma#1,{}%
newcommandUD@CheckWhetherNoComma[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@GobbleToComma#1,}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no dash which is not nested
%% in braces:
%%.............................................................................
%% UD@CheckWhetherNoDash{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% contains no dash>}%
%% {<Tokens to be delivered in case that argument
%% contains dash>}%
%%
newcommandUD@GobbleToDash{}longdefUD@GobbleToDash#1-{}%
newcommandUD@CheckWhetherNoDash[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@GobbleToDash#1-}%
}%
%%-----------------------------------------------------------------------------
%% Take a comma-delimited/dash-delimited argument where a space was
%% prepended for preventing brace-removal and wrap it in curly braces.
%%.............................................................................
newcommandUD@SplitCommaArg{}%
longdefUD@SplitCommaArg#1,{%
romannumeral0UD@exchange{ }{expandafter}expandafter{UD@removespace#1}%
}%
newcommandUD@SplitDashArg{}%
longdefUD@SplitDashArg#1-{%
romannumeral0UD@exchange{ }{expandafter}expandafter{UD@removespace#1}%
}%
%%-----------------------------------------------------------------------------
%% Now we have the tools for creating the desired mycommand-macro:
%%.............................................................................
newcommandmycommand[1]{%
romannumeral0%
UD@CheckWhetherNoComma{#1}{%
expandaftermycommand@oneargumentexpandafter{%
romannumeral0UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}{%
expandafterUD@exchangeexpandafter{expandafter
{UD@GobbleToComma#1}%
}{%
expandafter@mycommand
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@ExtractFirstArgexpandafter{%
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@SplitCommaArg
UD@firstoftwo{ }{}#1}%
}%
}%
}%
newcommand@mycommand[2]{%
UD@CheckWhetherNoDash{#2}{%
expandafterUD@exchangeexpandafter{expandafter
{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#2}%
}%
}{%
expandaftermycommand@twoargumentsexpandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}%
}{%
expandafterUD@exchangeexpandafter{expandafter%
{UD@GobbleToDash#2}%
}{%
expandafter@@mycommand
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@ExtractFirstArgexpandafter{%
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@SplitDashArg
UD@firstoftwo{ }{}#2}%
}%
{#1}%
}%
}%
newcommand@@mycommand[3]{%
expandafterUD@exchangeexpandafter{%
expandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#2}%
}%
}{%
expandafterUD@exchangeexpandafter{%
expandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}{%
expandaftermycommand@threeargumentsexpandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#3}%
}%
}%
}%
}%
newcommandmycommand@oneargument[1]{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is no comma or only empty args after the comma, thus:\
argument 1 = printmeaning{#1}%
}%
newcommandmycommand@twoarguments[2]{%
UD@CheckWhetherNull{#2}{%
mycommand@oneargument{#1}%
}{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is a comma but either no dash or only one non-empty argument near
the dash, thus:\
argument 1 = printmeaning{#1}\
argument 2 = printmeaning{#2}%
}%
}%
newcommandmycommand@threearguments[3]{%
UD@CheckWhetherNull{#2}{%
UD@CheckWhetherNull{#3}{%
mycommand@oneargument{#1}%
}{%x
mycommand@twoarguments{#1}{#3}%
}%
}{%
UD@CheckWhetherNull{#3}{%
mycommand@twoarguments{#1}{#2}%
}{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is a comma and a dash, thus:\
argument 1 = printmeaning{#1}\
argument 2 = printmeaning{#2}\
argument 3 = printmeaning{#3}%
}%
}%
}%
newcommandprintmeaning[1]{%
defUD@tempa{#1}%
fbox{texttt{expandafterstrip@prefixmeaningUD@tempa}}%
}%
makeatother
parindent=0ex
parskip=smallskipamount
pagestyle{empty}%
begin{document}
vspace*{-3.5cm}%
enlargethispage{7cm}%
verb|mycommand{ arg 1 }|:\
mycommand{ arg 1 }\
nullhrulefillnull
verb|mycommand{ arg 1 , }|:\
mycommand{ arg 1 , }\
nullhrulefillnull
verb|mycommand{ arg 1 , - }|:\
mycommand{ arg 1 , - }\
nullhrulefillnull
verb|mycommand{ arg 1 , {{arg 2}} }|:\
mycommand{ arg 1 , {{arg 2}} }\
(Be aware that (only) the outermost braces get removed from
an argument if surrounding the entire argument after removal
of surrounding spaces.)\
nullhrulefillnull
verb|mycommand{ arg 1 , arg 2 - }|:\
mycommand{ arg 1 , arg 2 - }\
nullhrulefillnull
verb|mycommand{ arg 1 , arg 2 - arg 3}|:\
mycommand{ arg 1 , arg 2 - arg 3}\
nullhrulefillnull
verb|mycommand{ arg 1 , - arg 3}|:\
mycommand{ arg 1 , - arg 3}\
nullhrulefillnull
verb|mycommand{ {arg 1a} - arg 1b , {arg 2-2} , 2 - arg 3a - arg 3b}|:\
mycommand{ {arg 1a} - arg 1b , {arg 2-2} , 2 - arg 3a - arg 3b}\
nullhrulefillnull
verb|mycommand{ {arg 1} , {arg 2-2} , 2 - arg 3a - arg 3b}|:\
mycommand{ {arg 1} , {arg 2-2} , 2 - arg 3a - arg 3b}
end{document}
Some crocheting/knitting by hand (or whatever)... ;-)
I suppose this question came into being while you thought about your other question: Custom cite command. In that question you ask for a customized cite
-command with the same syntax.
Be that as it may.
The challenge is doing it in a way where desired space-removal but no undesired removal of argument-braces takes place.
E.g., with things like mycommand{{arg_1}a, arg_2}
the braces surrounding arg_1
should probably be preserved while with things like mycommand{{arg_1} , arg_2}
they should probably be removed.
Also probably only space-tokens surrounding the entire arguments/surrounding those commas and those dashes that are taken for argument-separators should get removed.
Below is an approach where comma takes precedence over the dash and where the leftmost unhidden comma delimits the first argument from the remaining arguments and where the first unhidden dash following that comma delimits the second from the third argument.
Exactly one pair of matching curly braces will be removed in case after removing surrounding spaces it surrounds an entire argument as in that case it is assumed that the braces serve for hiding commas/dashes/spaces/emptiness. In all other cases braces will be preserved.
E.g., with things like mycommand{ {arg_1} , {arg_2-2} - arg_3}
you should get:
First argument: arg_1
Second argument: arg_2-2
Third argument: arg_3
.
E.g., with things like mycommand{ {{arg 1,a} - arg 1b} , {arg 2-2} , 2 - arg 3a - arg 3b}
you should get:
First argument (left from the first unhidden comma, surrounding spaces and outermost brace-level that surrounds the entire argument removed): {arg 1,a} - arg 1b
Second argument (right from the first unhidden comma, left from the first unhidden dash following the comma): {arg 2-2} , 2
Third argument (the remainder, right from the first unhidden dash following the comma): arg 3a - arg 3b
.
E.g., with things like mycommand{{arg 1}, {arg 2-2} , 2 - arg 3a - arg 3b}
you should get:
First argument (left from the first unhidden comma): arg 1
Second argument (right from the first unhidden comma, left from the first unhidden dash following the comma): {arg 2-2} , 2
Third argument (the remainder, right from the first unhidden dash following the comma): arg 3a - arg 3b
.
You may not be familiar to methods for removing space-tokens that surround arguments, thus a quote from the manual of my deprecated labelcas-package:
A note about removing leading and trailing spaces
The matter of removing trailing spaces from an (almost) arbitrary
token-sequence is elaborated in detail by Michael Downes, 'Around the
Bend #15, answers', a summary of internet-discussion which took place
under his guidance primarily at the INFO-TEX list, but also at
comp.text.tex (usenet) and via private email; December 1993. Online
archived at http://www.tug.org/tex-archive/info/arobend/answer.015.
One basic approach suggested therein is using TeX's scanning of
delimited parameters in order to detect and discard the ending space
of an argument:
... scan for a pair of tokens: a space-token and some well-chosen
bizarre token that can't possibly occur in the scanned text. If you
put the bizarre token at the end of the text, and if the text has a
trailing space, then TeX's delimiter matching will match at that point
and not before, because the earlier occurrences of space don’t have
the requisite other member of the pair.
Next consider the possibility that the trailing space is absent: TeX
will keep on scanning ahead for the pair
⟨space⟩⟨bizarre⟩
until either it finds them or it decides to give up and signal a
'Runaway argument?' error. So you must add a stop pair to catch the
runaway argument possibility: a second instance of the bizarre
token, preceded by a space. If TeX doesn't find a match at the
first bizarre token, it will at the second one.
(Look up the macros
KV@@sp@def
,KV@@sp@b
,KV@@sp@c
andKV@@sp@d
in
David Carlisle’s keyval-package for an interesting variation on this
approach.)
When scanning for parameters
##1⟨space⟩⟨bizarre⟩##2⟨B1⟩
the sequence:⟨stuff where to remove trail-space⟩⟨bizarre⟩⟨space⟩⟨bizarre⟩⟨B1⟩
, you can fork two cases:
Trailing-space:
##1
=⟨stuff where to remove trail-space⟩
, but with removed space. (And possibly one
removed brace-level!)##2
=⟨space⟩⟨bizarre⟩
.
No trailing-space:
##1
=⟨stuff where to remove trail-space⟩⟨bizarre⟩
.##2
is empty.
So forking can be implemented depending on the emptiness of
##2
.
You can easily prevent the brace-removal in the first case, e.g., by adding (and
later removing) something (e.g., a space-token) in front of the
⟨stuff where to remove trail-space⟩
.
You can choose
⟨B1⟩=⟨bizarre⟩⟨space⟩
.
The link in the quote is out of date.
Nowadays you can find the entire "Around the bend" -collection at http://mirrors.ctan.org/info/challenges/AroBend/AroundTheBend.pdf.
As you see, the methods for space-removal exhibited in the quote above do rely on some sequence of tokens that must not occur within the argument.
In the example below it is relied on the token UD@seldom
not occurring within arguments.
In other words: You must not use the token UD@seldom
within your arguments.
If you don't like that restriction, then I can deliver space-removal-routines which do without such restrictions, but they are slower and they form another huge load of code.
documentclass{article}
makeatletter
%%-----------------------------------------------------------------------------
%% Paraphernalia ;-) :
%%.............................................................................
newcommandUD@firstoftwo[2]{#1}%
newcommandUD@secondoftwo[2]{#2}%
newcommandUD@exchange[2]{#2#1}%
newcommandUD@removespace{}UD@firstoftwo{defUD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% 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>
%%.............................................................................
newcommandUD@CheckWhetherNull[1]{%
romannumeral0expandafterUD@secondoftwostring{expandafter
UD@secondoftwoexpandafter{expandafter{string#1}expandafter
UD@secondoftwostring}expandafterUD@firstoftwoexpandafter{expandafter
UD@secondoftwostring}expandafterexpandafterUD@firstoftwo{ }{}%
UD@secondoftwo}{expandafterexpandafterUD@firstoftwo{ }{}UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>}%
newcommandUD@CheckWhetherLeadingSpace[1]{%
romannumeral0UD@CheckWhetherNull{#1}%
{expandafterexpandafterUD@firstoftwo{ }{}UD@secondoftwo}%
{expandafterUD@secondoftwostring{UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
newcommandUD@CheckWhetherLeadingSpaceB{}%
longdefUD@CheckWhetherLeadingSpaceB#1 {%
expandafterUD@CheckWhetherNullexpandafter{UD@secondoftwo#1{}}%
{UD@exchange{UD@firstoftwo}}{UD@exchange{UD@secondoftwo}}%
{UD@exchange{ }{expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter}expandafterexpandafter
expandafter}expandafterUD@secondoftwoexpandafter{string}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%% UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% UD@ExtractFirstArg{{AB}CDE} yields {AB}
%%
%% !!! The argument of UD@ExtractFirstArg must not be empty. !!!
%% You can check for emptiness via UD@CheckWhetherNull before applying
%% UD@ExtractFirstArg.
%%.............................................................................
newcommandUD@RemoveTillUD@SelDOm{}%
longdefUD@RemoveTillUD@SelDOm#1#2UD@SelDOm{{#1}}%
newcommandUD@ExtractFirstArg[1]{%
romannumeral0%
UD@ExtractFirstArgLoop{#1UD@SelDOm}%
}%
newcommandUD@ExtractFirstArgLoop[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@firstoftwo{}#1}%
{ #1}%
{expandafterUD@ExtractFirstArgLoopexpandafter{UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% UD@RemoveSpacesAndOneLevelOfBraces{<argument>} removes leading and
%% trailing spaces from <argument>.
%% If after that the <argument> is something that is entirely nested
%% between at least one pair of matching curly braces, the outermost
%% pair of these braces will be removed.
%%
%% !!!! <argument> must not contain the token UD@seldom !!!!
%%.............................................................................
begingroup
newcommandUD@RemoveSpacesAndOneLevelOfBraces[1]{%
endgroup
newcommandUD@RemoveSpacesAndOneLevelOfBraces[1]{%
romannumeral0%
UD@trimtrailspaceloop#1##1UD@seldom#1UD@seldomUD@seldom#1{##1}%
}%
newcommandUD@trimtrailspaceloop{}%
longdefUD@trimtrailspaceloop##1#1UD@seldom##2UD@seldom#1##3{%
UD@CheckWhetherNull{##2}{%
UD@trimleadspaceloop{##3}%
}{%
UD@trimtrailspaceloop##1UD@seldom#1UD@seldomUD@seldom#1{##1}%
}%
}%
}%
UD@RemoveSpacesAndOneLevelOfBraces{ }%
newcommandUD@trimleadspaceloop[1]{%
UD@CheckWhetherLeadingSpace{#1}{%
expandafterUD@trimleadspaceloopexpandafter{UD@removespace#1}%
}{%
UD@CheckWhetherNull{#1}{ }{%
expandafterUD@CheckWhetherNullexpandafter{UD@firstoftwo{}#1}{%
UD@exchange{ }{expandafter}UD@secondoftwo{}#1%
}{ #1}%
}%
}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no comma which is not nested
%% in braces:
%%.............................................................................
%% UD@CheckWhetherNoComma{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% contains no comma>}%
%% {<Tokens to be delivered in case that argument
%% contains comma>}%
%%
newcommandUD@GobbleToComma{}longdefUD@GobbleToComma#1,{}%
newcommandUD@CheckWhetherNoComma[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@GobbleToComma#1,}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no dash which is not nested
%% in braces:
%%.............................................................................
%% UD@CheckWhetherNoDash{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% contains no dash>}%
%% {<Tokens to be delivered in case that argument
%% contains dash>}%
%%
newcommandUD@GobbleToDash{}longdefUD@GobbleToDash#1-{}%
newcommandUD@CheckWhetherNoDash[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@GobbleToDash#1-}%
}%
%%-----------------------------------------------------------------------------
%% Take a comma-delimited/dash-delimited argument where a space was
%% prepended for preventing brace-removal and wrap it in curly braces.
%%.............................................................................
newcommandUD@SplitCommaArg{}%
longdefUD@SplitCommaArg#1,{%
romannumeral0UD@exchange{ }{expandafter}expandafter{UD@removespace#1}%
}%
newcommandUD@SplitDashArg{}%
longdefUD@SplitDashArg#1-{%
romannumeral0UD@exchange{ }{expandafter}expandafter{UD@removespace#1}%
}%
%%-----------------------------------------------------------------------------
%% Now we have the tools for creating the desired mycommand-macro:
%%.............................................................................
newcommandmycommand[1]{%
romannumeral0%
UD@CheckWhetherNoComma{#1}{%
expandaftermycommand@oneargumentexpandafter{%
romannumeral0UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}{%
expandafterUD@exchangeexpandafter{expandafter
{UD@GobbleToComma#1}%
}{%
expandafter@mycommand
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@ExtractFirstArgexpandafter{%
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@SplitCommaArg
UD@firstoftwo{ }{}#1}%
}%
}%
}%
newcommand@mycommand[2]{%
UD@CheckWhetherNoDash{#2}{%
expandafterUD@exchangeexpandafter{expandafter
{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#2}%
}%
}{%
expandaftermycommand@twoargumentsexpandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}%
}{%
expandafterUD@exchangeexpandafter{expandafter%
{UD@GobbleToDash#2}%
}{%
expandafter@@mycommand
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@ExtractFirstArgexpandafter{%
romannumeral0%
UD@exchange{ }{%
expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter
}%
expandafterUD@SplitDashArg
UD@firstoftwo{ }{}#2}%
}%
{#1}%
}%
}%
newcommand@@mycommand[3]{%
expandafterUD@exchangeexpandafter{%
expandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#2}%
}%
}{%
expandafterUD@exchangeexpandafter{%
expandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}{%
expandaftermycommand@threeargumentsexpandafter{%
romannumeral0%
UD@exchange{ }{expandafterexpandafterexpandafter}%
UD@RemoveSpacesAndOneLevelOfBraces{#3}%
}%
}%
}%
}%
newcommandmycommand@oneargument[1]{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is no comma or only empty args after the comma, thus:\
argument 1 = printmeaning{#1}%
}%
newcommandmycommand@twoarguments[2]{%
UD@CheckWhetherNull{#2}{%
mycommand@oneargument{#1}%
}{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is a comma but either no dash or only one non-empty argument near
the dash, thus:\
argument 1 = printmeaning{#1}\
argument 2 = printmeaning{#2}%
}%
}%
newcommandmycommand@threearguments[3]{%
UD@CheckWhetherNull{#2}{%
UD@CheckWhetherNull{#3}{%
mycommand@oneargument{#1}%
}{%x
mycommand@twoarguments{#1}{#3}%
}%
}{%
UD@CheckWhetherNull{#3}{%
mycommand@twoarguments{#1}{#2}%
}{%
UD@secondoftwo{%
%We need a space to stop romannumeral-expansion.
}{ }%
There is a comma and a dash, thus:\
argument 1 = printmeaning{#1}\
argument 2 = printmeaning{#2}\
argument 3 = printmeaning{#3}%
}%
}%
}%
newcommandprintmeaning[1]{%
defUD@tempa{#1}%
fbox{texttt{expandafterstrip@prefixmeaningUD@tempa}}%
}%
makeatother
parindent=0ex
parskip=smallskipamount
pagestyle{empty}%
begin{document}
vspace*{-3.5cm}%
enlargethispage{7cm}%
verb|mycommand{ arg 1 }|:\
mycommand{ arg 1 }\
nullhrulefillnull
verb|mycommand{ arg 1 , }|:\
mycommand{ arg 1 , }\
nullhrulefillnull
verb|mycommand{ arg 1 , - }|:\
mycommand{ arg 1 , - }\
nullhrulefillnull
verb|mycommand{ arg 1 , {{arg 2}} }|:\
mycommand{ arg 1 , {{arg 2}} }\
(Be aware that (only) the outermost braces get removed from
an argument if surrounding the entire argument after removal
of surrounding spaces.)\
nullhrulefillnull
verb|mycommand{ arg 1 , arg 2 - }|:\
mycommand{ arg 1 , arg 2 - }\
nullhrulefillnull
verb|mycommand{ arg 1 , arg 2 - arg 3}|:\
mycommand{ arg 1 , arg 2 - arg 3}\
nullhrulefillnull
verb|mycommand{ arg 1 , - arg 3}|:\
mycommand{ arg 1 , - arg 3}\
nullhrulefillnull
verb|mycommand{ {arg 1a} - arg 1b , {arg 2-2} , 2 - arg 3a - arg 3b}|:\
mycommand{ {arg 1a} - arg 1b , {arg 2-2} , 2 - arg 3a - arg 3b}\
nullhrulefillnull
verb|mycommand{ {arg 1} , {arg 2-2} , 2 - arg 3a - arg 3b}|:\
mycommand{ {arg 1} , {arg 2-2} , 2 - arg 3a - arg 3b}
end{document}
edited Feb 9 at 13:34
answered Feb 8 at 22:53
Ulrich DiezUlrich Diez
4,830618
4,830618
add a comment |
add a comment |
A listofitems
approach.
By setting a nested parsing with setsepchar{,/-}
prior to reading the list into myargs
, everything to the left of the 1st comma is read into myargs[1]
, everything to the right of the comma (and before the next comma) is read into myargs[2]
.
Within myargs[2]
, everything to the left of the first dash is assigned to myargs[2,1]
, and everything after the dash (and before the next dash) is assigned to myargs[2,2]
.
The number of sub-elements within myargs[2]
is available through listlenmyargs[2]
.
All these elements and list length variables are fully expandable.
documentclass{article}
usepackage{listofitems}
newcommandmycommand[1]{setsepchar{,/-}readlist*myargs{#1}%
noindent Arg 1 is myargs[1]\
Arg 2 is myargs[2,1]\
Arg 3 is ifnumlistlenmyargs[2]=1relax Absentelsemyargs[2,2]fi%
}
begin{document}
mycommand{arg1, arg2}
and
mycommand{arg1, arg2-arg3}
end{document}
add a comment |
A listofitems
approach.
By setting a nested parsing with setsepchar{,/-}
prior to reading the list into myargs
, everything to the left of the 1st comma is read into myargs[1]
, everything to the right of the comma (and before the next comma) is read into myargs[2]
.
Within myargs[2]
, everything to the left of the first dash is assigned to myargs[2,1]
, and everything after the dash (and before the next dash) is assigned to myargs[2,2]
.
The number of sub-elements within myargs[2]
is available through listlenmyargs[2]
.
All these elements and list length variables are fully expandable.
documentclass{article}
usepackage{listofitems}
newcommandmycommand[1]{setsepchar{,/-}readlist*myargs{#1}%
noindent Arg 1 is myargs[1]\
Arg 2 is myargs[2,1]\
Arg 3 is ifnumlistlenmyargs[2]=1relax Absentelsemyargs[2,2]fi%
}
begin{document}
mycommand{arg1, arg2}
and
mycommand{arg1, arg2-arg3}
end{document}
add a comment |
A listofitems
approach.
By setting a nested parsing with setsepchar{,/-}
prior to reading the list into myargs
, everything to the left of the 1st comma is read into myargs[1]
, everything to the right of the comma (and before the next comma) is read into myargs[2]
.
Within myargs[2]
, everything to the left of the first dash is assigned to myargs[2,1]
, and everything after the dash (and before the next dash) is assigned to myargs[2,2]
.
The number of sub-elements within myargs[2]
is available through listlenmyargs[2]
.
All these elements and list length variables are fully expandable.
documentclass{article}
usepackage{listofitems}
newcommandmycommand[1]{setsepchar{,/-}readlist*myargs{#1}%
noindent Arg 1 is myargs[1]\
Arg 2 is myargs[2,1]\
Arg 3 is ifnumlistlenmyargs[2]=1relax Absentelsemyargs[2,2]fi%
}
begin{document}
mycommand{arg1, arg2}
and
mycommand{arg1, arg2-arg3}
end{document}
A listofitems
approach.
By setting a nested parsing with setsepchar{,/-}
prior to reading the list into myargs
, everything to the left of the 1st comma is read into myargs[1]
, everything to the right of the comma (and before the next comma) is read into myargs[2]
.
Within myargs[2]
, everything to the left of the first dash is assigned to myargs[2,1]
, and everything after the dash (and before the next dash) is assigned to myargs[2,2]
.
The number of sub-elements within myargs[2]
is available through listlenmyargs[2]
.
All these elements and list length variables are fully expandable.
documentclass{article}
usepackage{listofitems}
newcommandmycommand[1]{setsepchar{,/-}readlist*myargs{#1}%
noindent Arg 1 is myargs[1]\
Arg 2 is myargs[2,1]\
Arg 3 is ifnumlistlenmyargs[2]=1relax Absentelsemyargs[2,2]fi%
}
begin{document}
mycommand{arg1, arg2}
and
mycommand{arg1, arg2-arg3}
end{document}
edited Feb 9 at 14:15
answered Feb 8 at 18:54
Steven B. SegletesSteven B. Segletes
155k9199409
155k9199409
add a comment |
add a comment |
One more:
documentclass{article}
usepackage{xstring}
defmycommand#1,#2 {IfSubStr{#2}{-}{[[#1],
[StrBefore{#2}{-}]-[StrBehind{#2}{-}]] }{[[#1],[#2]]}}
begin{document}
mycommand aaa,bbb-ccc
mycommand aaa,bbb
end{document}
add a comment |
One more:
documentclass{article}
usepackage{xstring}
defmycommand#1,#2 {IfSubStr{#2}{-}{[[#1],
[StrBefore{#2}{-}]-[StrBehind{#2}{-}]] }{[[#1],[#2]]}}
begin{document}
mycommand aaa,bbb-ccc
mycommand aaa,bbb
end{document}
add a comment |
One more:
documentclass{article}
usepackage{xstring}
defmycommand#1,#2 {IfSubStr{#2}{-}{[[#1],
[StrBefore{#2}{-}]-[StrBehind{#2}{-}]] }{[[#1],[#2]]}}
begin{document}
mycommand aaa,bbb-ccc
mycommand aaa,bbb
end{document}
One more:
documentclass{article}
usepackage{xstring}
defmycommand#1,#2 {IfSubStr{#2}{-}{[[#1],
[StrBefore{#2}{-}]-[StrBehind{#2}{-}]] }{[[#1],[#2]]}}
begin{document}
mycommand aaa,bbb-ccc
mycommand aaa,bbb
end{document}
answered Feb 9 at 18:13
FranFran
52.5k6117178
52.5k6117178
add a comment |
add a comment |
Thanks for contributing an answer to TeX - LaTeX Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2ftex.stackexchange.com%2fquestions%2f473965%2fa-command-challenge%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
2
That's one argument from a TeX point of view ... I guess you want a parser for that one document-level argument.
– Joseph Wright♦
Feb 8 at 17:46
Try
defmycommand#1,space#2-#3{...}
.– John Kormylo
Feb 8 at 21:41
Since I assume this is more or less a follow-up to tex.stackexchange.com/q/473949/35864, let me please re-iterate that I believe that the usual
biblatex
syntax is not really inferior to this syntax. In fact the syntax proposed here has the serious drawback that it removes the ability to cite several entries in one cite commandautocite{sugfridsson,worman,nussbaum}
as that uses the comma-separated syntax in one argument already.– moewe
Feb 9 at 8:00