3

I am new to latex.

I need to create a text that has a bunch of entries that have the same structure, so I thought I would create a macro for it. However there are 7 moving parts, so it needs 7 arguments. It's becoming hard to read, hard to know which argument number I'm on. So I want to name the arguments (not so much in the definition of the macro, but at least when it's used).

I tried searching on the net, but found no easy solution, and it gets very quickly highly unreadable (using xparse and things like that) for non-latex experts. I found out, using luatex, I can include lua code, which seems way more readable. So I switched to luatex.

The usage that I am proposing is as follows. I'm open to other usages, but it should be at least easily readable, prone to extra spaces (and newlines), and close to the latex syntax. values can themselves have newlines, and latex code (such as \textbf). curly braces (the character ending the value) should be able to be escaped in the values.

\myfunction {
  arg1 = {value1},
  arg2 = {value2},
  arg3 = {value3},
  arg4 = {value4},
  arg5 = {value5},
  arg6 = {value6},
  arg7 = {value7}
}

The way I tried to parse it is as follows.

\documentclass{article}
\usepackage{luacode}

\begin{luacode}
function parse_args(input)
    local result = {}
    local pattern = '(%w+)%s*=%s*{%s*([^}]+)%s*}'
    for key, value in input:gmatch(pattern) do
        result[key] = value
    end
    return result
end

function my_function(input)
    local args = parse_args(input)
    for key, value in args do
        tex.print(key .. " : " .. value .. "\\")
end

\end{luacode}

\newcommand{\myfunction}[1]{%
    \directlua{
        my_function([===[#1]===])
    }%
}

\begin{document}

\myfunction {
  arg1 = {value1},
  arg2 = {value2},
  arg3 = {value3},
  arg4 = {value4},
  arg5 = {value5},
  arg6 = {value6},
  arg7 = {value7}
}

\end{document}

This doesn't work, I'm getting weird issues, it seems to closing bracket in the pattern string is interpreted as latex code and I get a closing bracket error within the string.

I'm open to either making my code work with minimal changes, or to provide a whole other solution, if it's elegant and easy to read.

New contributor
user1724887 is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
5
  • Your input shows one argument using keyvals, not multiple named arguments (for TeX, each argument is a balanced text). Commented 12 hours ago
  • latek has standard (tex) fubctions for parsing key=value syntax. If you want to use Lua the usual way would be to pass in a lua table and use the native table syntax rather than passing in a string and then splitiing it up with regex Commented 12 hours ago
  • I'm open to any solution basically, please start by providing how the usage would look Commented 12 hours ago
  • 1
    Mandatory links: tug.org/TUGboat/tb30-1/tb94wright-keyval.pdf and tex.stackexchange.com/questions/543755/simple-key-value-example Commented 11 hours ago
  • I compile only with lualatex, and use lua code for situations that are similar to what you asked. In my case, it involves elaborate parsing of command options, much too complex for the usual ke=value methods. You are on the right track, using lua. If possible, put as much as you can into a separate *.lua file. That will help, if you use a text editor that is separately syntax-aware for *.tex and *.lua. Commented 7 hours ago

3 Answers 3

6

\begin{advertisement}[package=expkv-cs, author=myself]

General

expkv-cs provides a very straight forward way to define such macros. Notice however that the arguments it defines are optional keys and if you want them to be mandatory you have to implement the required checks yourself.

This is fully expandable and works in all TeX engines with e-TeX and the \expanded primitive (so anything running contemporary LaTeX).

expkv-cs is part of the expkv-bundle. This answer won't be exhaustive, if you want to know all the features (and also the other packages of the bundle) pay the documentation a visit.

Brief explanation

The basic syntax to define a macro in expkv-cs is

\ekvcSplit⟨cs⟩{⟨key=value declarations⟩}{⟨definition⟩} therein

  • cs is the name of an undefined control sequence that you want to define
  • key=value declarations is a list of key names and their default values (which get used if you omit those keys on use time). Each key can be prefixed by either long or short specifying whether it should be able to get an explicit \par (long) or not (short -- which is the default behaviour, but without this you couldn't define a short key named long). That prefix won't be part of the key name.
  • definition is the replacement text of the defined macro (so to what it expands). Inside it you can refer to the individual value of each key using the numeric arguments #1 to #9 (the number being the position in the key=value declarations -- this limits the number of available keys to 9).

There is an alternative front end as well:

\ekvcHash⟨cs⟩{⟨key=value declarations⟩}{⟨definition⟩}
It works identical but instead of using #1 to #9 you use \ekvcValue{⟨key⟩}{#1} to get the values of the individual keys (this has some technical implications, but those are better to be ignored for beginners -- see the documentation if you're curious, might require advanced TeX-programming-knowledge).

So in order to define a simple macro with two keys, both having default values you'd use

\ekvcSplit\mycmd
  {
     arg1 = default1
    ,arg2={default2}
  }
  {1: #1, 2: #2.}

And if you want to define the very same macro, but this time both arguments might contain \par tokens:

\ekvcSplit\mycmd
  {
     long arg1 = default1
    ,long arg2={default2}
  }
  {1: #1, 2: #2.}

Both these definitions could then be used in the document as any of the following (results as comments)

\mycmd{arg1 = {value1}, arg2=value2} % -> 1: value1, 2: value2.
\mycmd{}                             % -> 1: default1, 2: default2.
\mycmd{arg2={{2}}, }                 % -> 1: default1, 2: {2}.
\mycmd{arg2= V , arg1=}              % -> 1: , 2: V.

Turning the keys mandatory

As you can see in the example above, the arguments are now optional, omitting one leads to a default value being used. If you define a command with \newcommand\mycmd[7]{...} it will have 7 ordered mandatory arguments. If we want to mimic this behaviour, just with named unordered arguments, we'll have to set specific default values.

LaTeX has two handy builtins for this. The first one is the test \IfNoValueT, it'll test whether its argument is a special -NoValue- marker. The second is the command storing that marker, \c_novalue_tl. Don't be afraid of the strange name with underscores, it's part of the LaTeX3 programming layer. To access that marker we can use another feature of expkv and its key=value-parser, which is expansion control: Using a prefix v: in front of everything in a key=value list means "build a control sequence from the value's contents and retrieve that control sequences value".

So v: long arg1 = c_novalue_tl in our key=value declarations will define the key arg1 to have the -NoValue- marker as its default value. We can then check for that marker and throw an error if we find it. \ekverr allows us to throw an error while still being fully expandable. Our example macro becomes

\ekvcSplit\mycmd
  {
     v: long arg1 = c_novalue_tl
    ,v: long arg2=c_novalue_tl
  }
  {%
    \IfNoValueT{#1}{\ekverr{user1724887}{missing key arg1}}%
    \IfNoValueT{#2}{\ekverr{user1724887}{missing key arg2}}%
    1: #1, 2: #2.%
  }

With that new definition, if we use the same input as above we'll get different behaviour (again using comments)

\mycmd{arg1 = {value1}, arg2=value2} % -> 1: value1, 2: value2.
\mycmd{}                             % 2 ERRORS -> 1: -NoValue-, 2: -NoValue-.
\mycmd{arg2={{2}}, }                 % 1 ERROR  -> 1: -NoValue-, 2: {2}.
\mycmd{arg2= V , arg1=}              % -> 1: , 2: V.

MWE defining macros with 7 keys

This MWE uses the above approaches to define two macros, both taking 7 named arguments. One of which having all 7 as mandatory arguments (with a small helper macro).

\documentclass[]{article}

\usepackage{expkv-cs}

\ekvcSplit\myfunction
  {
     long arg1 = {} % #1
    ,long arg2 = {} % #2
    ,long arg3 = {} % #3
    ,long arg4 = {} % #4
    ,long arg5 = {} % #5
    ,long arg6 = {} % #6
    ,long arg7 = {} % #7
  }
  {%
    \par
    \noindent
    arg1: #1 \\
    arg2: #2 \\
    arg3: #3 \\
    arg4: #4 \\
    arg5: #5 \\
    arg6: #6 \\
    arg7: #7%
  }

\ekvcSplit\myfunctionMandatory
  {
     v: long arg1 = c_novalue_tl % #1
    ,v: long arg2 = c_novalue_tl % #2
    ,v: long arg3 = c_novalue_tl % #3
    ,v: long arg4 = c_novalue_tl % #4
    ,v: long arg5 = c_novalue_tl % #5
    ,v: long arg6 = c_novalue_tl % #6
    ,v: long arg7 = c_novalue_tl % #7
  }
  {%
    \assertMandatoryArg{arg1}{#1}%
    \assertMandatoryArg{arg2}{#2}%
    \assertMandatoryArg{arg3}{#3}%
    \assertMandatoryArg{arg4}{#4}%
    \assertMandatoryArg{arg5}{#5}%
    \assertMandatoryArg{arg6}{#6}%
    \assertMandatoryArg{arg7}{#7}%
    \par
    \noindent
    arg1: #1 \\
    arg2: #2 \\
    arg3: #3 \\
    arg4: #4 \\
    arg5: #5 \\
    arg6: #6 \\
    arg7: #7%
  }
\newcommand\assertMandatoryArg[2]
  {\IfNoValueT{#2}{\ekverr{user1724887}{missing #1}}}

\begin{document}
\myfunction {
  arg1 = {value1},
  arg2 = {value2},
  arg3 = {value3},
  arg4 = {value4},
  arg5 = {value5},
  arg6 = {value6},
  arg7 = {value7}
}

\myfunctionMandatory {
  arg1 = {value1},
  arg2 = {value2},
  arg3 = {value3},
  arg4 = {value4},
  arg5 = {value5},
  arg6 = {value6},
  arg7 = {value7}
}
\end{document}

enter image description here

Example macros using \ekvcHash

Keep in mind that if you need more than 9 arguments you can't use \ekvcSplit and have to use \ekvcHash instead. In that case you can't use \IfNoValueT{\ekvcValue{arg1}{#1}} because the test won't understand the \ekvcValue-nesting. The following defines two small example macros using the hashing variant and makes some adjustments for the auxiliary \assertMandatoryArg.

\documentclass[]{article}

\usepackage{expkv-cs}

\ekvcHash\myfunction
  {
     long arg1 = {}
    ,long arg2 = {}
  }
  {1: \ekvcValue{arg1}{#1}, 2: \ekvcValue{arg2}{#1}.}

\ekvcHash\myfunctionMandatory
  {
     v: long arg1 = c_novalue_tl
    ,v: long arg2 = c_novalue_tl
  }
  {%
    \assertMandatoryArg{arg1}{#1}%
    \assertMandatoryArg{arg2}{#1}%
    1: \ekvcValue{arg1}{#1}, 2: \ekvcValue{arg2}{#1}.%
  }
\newcommand\assertMandatoryArg[2]
  {\ekvcValueSplit{#1}{#2}\IfNoValueT{\ekverr{user1724887}{missing #1}}}

\begin{document}
\myfunction {
  arg1 = {value1},
  arg2 = {value2},
}

\myfunctionMandatory {
  arg1 = {value1},
  arg2 = {value2},
}
\end{document}

enter image description here

\end{advertisement}

24
  • I'm sorry to say it doesn't feel easy to read (for latex newbies), though I have some very basic programming knowledge. Can you break down block by block the main components and their meaning ? Commented 11 hours ago
  • Basic syntax (for both \ekvcSplit and \ekvcHash) is: \ekvcSplit <macro> { <key=value list declaration> } { <definition> }. <macro> is just the macro you want to define. <key=value list declaration> defines the named arguments the macro will know. For \ekvcSplit those will be turned into numbered arguments inside your <definition> in the order you declared them. In \ekvcHash instead you only get a single argument #1 and can refer to individual values using \ekvcValue{<key>}{#1}. Commented 11 hours ago
  • @user1724887 inside the list of declarations you provide the values the individual keys should have if they are omitted at use time (so in \ekvcSplit\cmd{key={}}{#1} the value of key will by default be empty). A single prefix long (or short for the inverse meaning) will not be part of the key name but instead tells the system that the key will be able to read explicit \par tokens (TeX programming thingy). Commented 11 hours ago
  • @user1724887 the prefix v: means build a macro from the given value and expand that macro to the value stored therein. \c_novalue_tl is a macro from the LaTeX3 programming layer, the value of which will turn the \IfNoValueT test true, hence we can use it to check whether the key was given (its default value will be the marker indicating no value). Commented 11 hours ago
  • @user1724887 if you have a local TeX Live or MikTeX installation including the documentation you can use texdoc expkv-bundle on the CLI to see the documentation. Do you need further explanations? Commented 11 hours ago
4

An answer (that also works with pdflatex) that uses the standard l3keys systrem. here rather than declare each key I have just defined a default key handler so foo=bar gets saved as \example-foo with value bar so \UseName{example-foo} would access the value.

enter image description here

\documentclass{article}

\DeclareUnknownKeyHandler [example]{
#1: #2\par % debug print
\ExpandArgs{c}\def{example-#1}{#2}% Use \UseName{example-arg2} to access a value.
}

\DeclareKeys[example]{
 }
 
\newcommand{\myfunction}[1]{%
 \SetKeys[example]{#1}%
}

\begin{document}

\myfunction {
  arg1 = value1 \textbf{this},
  arg2 = value2,
  arg3 = value3,
  arg4 = value4,
  arg5 = value5,
  arg6 = value6,
  arg7 = value7
}

\end{document}
4

If you are anyway restricting to LuaTeX, and only using the key/value in Lua functions, you can use native Lua table syntax rather than splitting things up with regex/ (Otherwise you could use normal latex keyval declarations and not use Lua at all).

enter image description here

\documentclass{article}
\usepackage{luacode}

\begin{luacode}
function my_function(input)
for key, value in pairs(input)  do
     tex.print(key .. " : " .. value .. "\\par")
   end
 return input
end
\end{luacode}

\newcommand{\myfunction}[1]{%
    \directlua{
      my_function({#1})
    }%
}

\begin{document}

\myfunction {
  arg1 = "value1 \string\\textbf{this}",
  arg2 = "value2",
  arg3 = "value3",
  arg4 = "value4",
  arg5 = "value5",
  arg6 = "value6",
  arg7 = "value7"
}

\end{document}
17
  • Can you show what it would look like without lua at all ? Also, in your lua example, can values have multiple lines ? And how to escape " ? Commented 11 hours ago
  • @user1724887 see jps's answer for one way but you could do similar with the standard keyval or l3keys just as in latex you can do \includegraphics[height=2sm,width=5cm]{...} as for this answer each value is a Lua string so you can use any Lua string sytax so \string\n to add a \n newline, or use [[...]] string syntax etc Commented 11 hours ago
  • i'll be waiting for someone else to provide a keyval or l3keys solution then, to see if it's any easier to read. jps's answer doesn't seem that readable to me, at least from my beginner point of view. Commented 11 hours ago
  • @user1724887 believe me, the syntax of expkv-cs is close to the most readable way to define key=value macros there is. keyval and l3keys are harder to read. Commented 11 hours ago
  • well as someone who is not a latex expert, i find this lua solution more readable. Perhaps it's a question of taste, or familiarity with latex. So if expkv-cs is the next best thing, i think i'll stick with this lua solution Commented 11 hours ago

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.