summaryrefslogtreecommitdiff
path: root/macros/generic/expkv/expkv.dtx
diff options
context:
space:
mode:
Diffstat (limited to 'macros/generic/expkv/expkv.dtx')
-rw-r--r--macros/generic/expkv/expkv.dtx169
1 files changed, 128 insertions, 41 deletions
diff --git a/macros/generic/expkv/expkv.dtx b/macros/generic/expkv/expkv.dtx
index 283cd0353d..da6fcf2542 100644
--- a/macros/generic/expkv/expkv.dtx
+++ b/macros/generic/expkv/expkv.dtx
@@ -421,7 +421,7 @@ and the derived files expkv.pdf
% \end{function}
% \example Define |text| in |foo| to store the value inside |\foo@text|:
% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt]
-% \protected\long\ekvdef{foo}{text}{\def\foo@width{#1}}
+% \protected\long\ekvdef{foo}{text}{\def\foo@text{#1}}
% \end{lstlisting}
%
% \begin{function}{\ekvdefNoVal}
@@ -440,7 +440,8 @@ and the derived files expkv.pdf
% \cs{ekvlet}\marg{set}\marg{key}\meta{cs}
% \end{syntax}
% Let the value taking \key\ in \set\ to \meta{cs}, there are no checks on
-% \meta{cs} enforced.
+% \meta{cs} enforced, but the code should expect the value as a single braced
+% argument directly following it.
% \end{function}
% \example Let |cmd| in |foo| do the same as |\foo@cmd|:
% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt]
@@ -811,7 +812,8 @@ and the derived files expkv.pdf
% argument will be wrapped in braces, the mandatory argument will be
% untouched). If there was no optional argument the result will be
% \meta{next}\marg{default}\marg{mandatory} (so the default will be used and
-% the mandatory argument will be wrapped in braces).
+% the mandatory argument will be wrapped in braces after being read once -- if
+% it was already wrapped it is effectively unchanged).
% \end{function}
% |\ekvoptarg| expands in exactly two steps, grabs all the arguments only at the
% second expansion step, and is alignment safe. It has its limitations however.
@@ -845,6 +847,43 @@ and the derived files expkv.pdf
% \newcommand\foo@b[1]{Mandatory: #1\par No optional.}
% \end{lstlisting}
%
+% \begin{function}{\ekvcsvloop}
+% \begin{syntax}
+% \cs{ekvcsvloop}\marg{code}\marg{csv-list}
+% \end{syntax}
+% This loops over the comma separated items in \meta{csv-list} and, after
+% stripping spaces from either end of \meta{item} and removing at most one set
+% of outer braces, leaves
+% \texttt
+% {^^A
+% \cs[no-index]{unexpanded}\hskip0pt
+% \{\hskip0pt
+% \meta{code}\hskip0pt
+% \marg{item}\hskip0pt
+% \}^^A
+% }
+% for each list item in the input stream. Blank elements are ignored (if you
+% need a blank element it should be given as |{}|). It supports both active
+% commas and commas of category other. You could consider it as a watered down
+% version of |\ekvparse|. However it is not alignment safe, which you could
+% achieve by nesting it in |\expanded| (since the braces around the argument
+% of |\expanded| will hide |&|s from \TeX's alignment parsing).
+% \end{function}
+% \example The following splits a comma separated list and prints it in a
+% typewriter font with parentheses around each element.\\
+% \begin{minipage}{.75\linewidth}
+% \begin{lstlisting}
+% \newcommand*\myprocessor[1]{\texttt{(#1)}}
+% \ekvcsvloop\myprocessor{abc,def,ghi}\par
+% \ekvcsvloop\myprocessor{1,,2,,3,,4}\par
+% \end{lstlisting}
+% \end{minipage}^^A
+% \begin{exresult}[nobeforeafter,box align=center]{.25\linewidth}
+% \newcommand*\myprocessor[1]{\texttt{(#1)}}
+% \ekvcsvloop\myprocessor{abc,def,ghi}\par
+% \ekvcsvloop\myprocessor{1,,2,,3,,4}\par
+% \end{exresult}
+%
% \begin{function}{\ekverr}
% \begin{syntax}
% \cs{ekverr}\marg{package}\marg{message}
@@ -1554,8 +1593,8 @@ and the derived files expkv.pdf
% \begin{macro}{\ekvVersion,\ekvDate}
% We're on our first input, so lets store the version and date in a macro.
% \begin{macrocode}
-\def\ekvVersion{1.8a}
-\def\ekvDate{2021-06-03}
+\def\ekvVersion{1.9}
+\def\ekvDate{2021-07-15}
% \end{macrocode}
% \end{macro}
%
@@ -1762,35 +1801,73 @@ and the derived files expkv.pdf
% \end{macrocode}
% \end{macro}
%
-% \begin{macro}[internal]{\ekv@csv@loop,\ekv@csv@loop@do,\ekv@csv@loop@end}
-% This is just a very simple loop over a list of comma separated values,
-% leaving each element as the argument to a specified function inside of
-% |\unravel|. It should be used as
-% \texttt
-% {^^A
-% \cs[no-index]{ekv@csv@loop}\hskip0pt^^A
-% \marg{function}\hskip0pt^^A
-% \cs[no-index]{ekv@mark}\hskip0pt^^A
-% \meta{csv-list}\hskip0pt^^A
-% ,\cs[no-index]{ekv@stop},^^A
-% }.
-% We use some |\expandafter| chain to preexpand |\ekv@strip| here.
-% \begin{macrocode}
-\ekv@exparg{\long\def\ekv@csv@loop#1#2,}%
+% \begin{macro}{\ekvcsvloop}
+% \begin{macro}[internal]{\ekv@csv@loop@active,\ekv@csv@loop@active@end}
+% An |\ekvcsvloop| will just loop over a csv list in a simple manner. First we
+% split at active commas (gives better performance this way), next we have to
+% check whether we're at the end of the list (checking for |\ekv@stop|). If
+% not we go on splitting at commas of category other.
+% \begin{macrocode}
+\begingroup
+\def\ekvcsvloop#1{%
+\endgroup
+\long\def\ekvcsvloop##1##2%
+ {\ekv@csv@loop@active{##1}\ekv@mark##2#1\ekv@stop#1}
+% \end{macrocode}
+% This does the same as |\ekv@csv@loop| but for active commas.
+% \begin{macrocode}
+\long\def\ekv@csv@loop@active##1##2#1%
{%
- \expandafter
- \ekv@gobble@from@mark@to@stop
- \expandafter#\expandafter2\expandafter\ekv@csv@loop@end\expandafter
+ \ekv@gobble@from@mark@to@stop##2\ekv@csv@loop@active@end\ekv@stop
+ \ekv@csv@loop{##1}##2,\ekv@stop,%
+ }%
+\long\def\ekv@csv@loop@active@end
\ekv@stop
- \ekv@strip{#2}{\ekv@csv@loop@do{#1}}%
- \ekv@csv@loop{#1}\ekv@mark
+ \ekv@csv@loop##1\ekv@mark\ekv@stop,\ekv@stop,%
+ {}%
+}
+% \end{macrocode}
+% Do the definitions with the weird catcode.
+% \begin{macrocode}
+\catcode`\,=13
+\ekvcsvloop,
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[internal]{\ekv@csv@loop,\ekv@csv@loop@do,\ekv@csv@loop@end}
+% We use temporary macros and an |\expandafter| chain to preexpand
+% |\ekv@strip| here. After splitting at other commas we check again for end
+% the end of the sublist, check for blank elements which should be ignored,
+% and else strip spaces and execute the user code (protecting it from further
+% expanding with |\unexpanded|).
+% \begin{macrocode}
+\def\ekv@csv@loop#1%
+ {%
+ \long\def\ekv@csv@loop##1##2,%
+ {%
+ \ekv@gobble@from@mark@to@stop##2\ekv@csv@loop@end\ekv@stop
+ \ekv@ifblank@##2\ekv@nil\ekv@ifempty@B\ekv@csv@loop@blank
+ \ekv@ifempty@A\ekv@ifempty@B
+ #1\ekv@csv@loop@do{##1}%
+ }%
}
-\long\def\ekv@csv@loop@do#1#2{\unexpanded{#1{#2}}}
-\long\expandafter\def\expandafter\ekv@csv@loop@end
- \expandafter\ekv@stop
- \ekv@strip{#1}#2%
- \ekv@csv@loop#3\ekv@mark
- {}
+\expandafter\ekv@csv@loop\expandafter{\ekv@strip{#2}}
+\long\def\ekv@csv@loop@do#1#2{\unexpanded{#2{#1}}\ekv@csv@loop{#2}\ekv@mark}
+\def\ekv@csv@loop@end#1%
+ {%
+ \long\def\ekv@csv@loop@end
+ \ekv@stop
+ \ekv@ifblank@\ekv@mark\ekv@stop\ekv@nil\ekv@ifempty@B\ekv@csv@loop@blank
+ \ekv@ifempty@A\ekv@ifempty@B
+ #1\ekv@csv@loop@do##1%
+ {\ekv@csv@loop@active{##1}\ekv@mark}%
+ }
+\expandafter\ekv@csv@loop@end\expandafter{\ekv@strip{\ekv@mark\ekv@stop}}
+\long\expandafter\def\expandafter\ekv@csv@loop@blank
+ \expandafter\ekv@ifempty@A\expandafter\ekv@ifempty@B
+ \ekv@strip{\ekv@mark#1}\ekv@csv@loop@do#2%
+ {\ekv@csv@loop{#2}\ekv@mark}
% \end{macrocode}
% \end{macro}
%
@@ -1969,9 +2046,9 @@ and the derived files expkv.pdf
\begingroup
\edef\ekv@tmp
{%
- \ekv@csv@loop#1\ekv@mark#6,\ekv@stop,%
+ \ekvcsvloop#1{#6}%
\unexpanded{#2}%
- {\ekv@csv@loop{}\ekv@mark#5,#6,\ekv@stop,}%
+ {\ekvcsvloop{}{#5,#6}}%
}%
\ekv@expargtwice
{\endgroup#3{#5}}%
@@ -2690,7 +2767,10 @@ and the derived files expkv.pdf
% possibilities (they don't care for nested bracket levels).
%
% We start with a temporary definition to pre-expand |\ekv@alignsafe| (will be
-% |#1|) and |\ekv@endalignsafe| (will be |#2|).
+% |#1|) and |\ekv@endalignsafe| (will be |#2|). As |\ekv@alignsafe| starts with
+% a |\romannumeral| we use that to also control the number of steps needed
+% instead of adding another |\romannumeral|. For this we have to remove the
+% space token from the end of |\ekv@alignsafe|'s definition.
% \begin{macrocode}
\begingroup
\def\ekvoptarg#1#2{%
@@ -2702,24 +2782,24 @@ and the derived files expkv.pdf
% bracket, if it is found grab the optional argument, else leave |#1{#2}| in the
% input stream after ending the expansion context.
% \begin{macrocode}
-\def\ekvoptarg{\romannumeral#1\ekv@optarg@a}
+\def\ekvoptarg{#1\ekv@optarg@a}
\long\def\ekv@optarg@a##1##2##3%
{%
\ekv@optarg@if\ekv@mark##3\ekv@mark\ekv@optarg@b\ekv@mark[\ekv@mark
#2%
- \@firstofone{\ekv@zero##1}{##2}{##3}%
+ \@firstofone{ ##1}{##2}{##3}%
}%
% \end{macrocode}
% The other variant of this will do roughly the same. Here, |#1| will be the
% next step if an optional argument is found, |#2| the next step else, and |#3|
% might be the opening bracket or mandatory argument.
% \begin{macrocode}
-\def\ekvoptargTF{\romannumeral#1\ekv@optargTF@a}
+\def\ekvoptargTF{#1\ekv@optargTF@a}
\long\def\ekv@optargTF@a##1##2##3%
{%
\ekv@optarg@if\ekv@mark##3\ekv@mark\ekv@optargTF@b{##1}\ekv@mark[\ekv@mark
#2%
- \@firstofone{\ekv@zero##2}{##3}%
+ \@firstofone{ ##2}{##3}%
}
% \end{macrocode}
% The two macros to grab the optional argument have to remove the remainder of
@@ -2729,12 +2809,19 @@ and the derived files expkv.pdf
{#2##2{##5}}
\long\def\ekv@optargTF@b
##1\ekv@mark[\ekv@mark\ifnum`##2\fi\@firstofone##3##4##5]%
- {#2\ekv@zero##1{##5}}
+ {#2 ##1{##5}}
}
% \end{macrocode}
-% Do the definitions and add the test macro.
+% Do the definitions and add the test macro. We use |\ekv@strip| to remove the
+% trailing space from the definition of |\ekv@alignsafe|.
% \begin{macrocode}
-\ekv@exparg{\expandafter\ekvoptarg\expandafter{\ekv@alignsafe}}\ekv@endalignsafe
+\ekv@exparg
+ {%
+ \expandafter\ekv@strip\expandafter
+ {\expandafter\ekv@mark\ekv@alignsafe}%
+ \ekvoptarg
+ }%
+ \ekv@endalignsafe
\long\def\ekv@optarg@if#1\ekv@mark[\ekv@mark{}
% \end{macrocode}
% \end{macro}