3

I have a list of keywords and descriptions for those keywords. I would like to have tables for the different contexts in which these are used, containing only the relevant ones for the case. Some of these keywords repeat on the different contexts, so it would be ideal to write the description only once and then "reference" that description in each of my tables for ease of maintenance.

Example:

\section{Runtype 1 description}
\begin{table}
\begin{center}
\begin{tabular}{ l l }
runtype & Keyword to determine runtype. Can be 1 or 2. \\
        & I don't want to hardcode this description twice...\\
param_1 & Keyword to determine the parameter of runtype = 1 \\
\end{tabular}
\end{center}
\end{table}

\section{Runtype 2 description}
\begin{table}
\begin{center}
\begin{tabular}{ l l }
runtype & Keyword to determine runtype. Can be 1 or 2. \\
        & I don't want to hardcode this description twice...\\
param_2 & Keyword to determine the parameter of runtype = 2 \\
\end{tabular}
\end{center}
\end{table}
Nordico
  • 133

3 Answers3

3

Below, I'm defining the following commands:

  • \setkeyword{<keyword>}{<description>} can be used to declare a keyword.
  • \getkeyword{<keyword>} retrieves the description you provided.
  • \keywordtable{<key1>,<key2>,…} displays a table containing the descriptions corresponding to a comma-separated list of keywords.

(Note that keywords containing underscores are allowed.)

\documentclass{article}

\usepackage[T1]{fontenc} %% <- necessary for _ to be displayed correctly
\usepackage{etoolbox}    %% <- for \csuse, \csdef, \ifcsdef and \forcsvlist

%% Define/retrieve a new keyword:
\newcommand\setkeyword[2]{\csdef{keyw@#1}{#2}}
\newcommand*\getkeyword[1]{%
  \ifcsdef{keyw@#1}{%                %% <- if the key is defined...
    \csuse{keyw@#1}%                 %% <- return the description
  }{%                                %% <- otherwise...
    ??%                              %% <- question marks
    %\errmessage{Undefined key: #1}% %% <- or an ERROR, if you prefer
  }%
}

%% Display a table describing a list of keywords:
\newcommand*\keywordtable[1]{%
  \begin{tabular}{lp{8cm}}
    \forcsvlist{\tableentry}{#1} %% <- apply \tableentry to each value in #1
  \end{tabular}%
}
\newcommand*\tableentry[1]{%
    \formattableentry{\detokenize{#1}}{\getkeyword{#1}}%
}
\newcommand\formattableentry[2]{ #1 & #2 \\ }

% %% Declaration of keywords:
\setkeyword{runtype}{Keyword to determine runtype. Can be 1 or 2. \par
                     I don't want to hardcode this description twice\ldots}
\setkeyword{param_1}{Keyword to determine the parameter of runtype = 1}
\setkeyword{param_2}{Keyword to determine the parameter of runtype = 2}

\begin{document}

\section{Runtype 1 description}
\keywordtable{runtype,param_1}

\section{Runtype 2 description}
\keywordtable{runtype,param_2}

\end{document}

output


Some remarks

  • \setkeyword{<keyword>}{<description>} effectively defines \keyw@<keyword> for you, so that it expands to <definition>. You can't use this macro directly because its name contains an @.
  • \getkeyword{<keyword>} just calls \keyw@<keyword>.
  • I'm using the e-TeX primitive \detokenize to print the keyword. This command strips all tokens in its argument of their special meaning (by changing their catcodes), so you can for instance use \detokenize{param_1} safely.
  • Without \usepackage[T1]{fontenc}, underscores are displayed as " ̇", rather than as "_". You'll probably want to use this package anyway because of the reasons outlined here.
  • I'm using tabularx to create a table that has the same width as the current line width. You can replace \linewidth by some other value if you want a different width (or just use tabular, in which case you should replace the X column type by something else).


A more customisable version

By request, here is the most customisable version I can think of. This lets you create keywords using \setkeyword{<keyword>}{<key1>=<value1>,<key2>=<value2>,…} and retrieve them using \getkeyword{<keyword>}{<key>}. You can create a table with rows corresponding a set of keywords and columns corresponding to specific keys using

\keywordtable[<key1>,<key2>,…]{<keyword1>,<keyword2>,…}

I'm using pkgkeys, which is documented in section 82 of the pgf manual.

\documentclass{article}

\usepackage[T1]{fontenc} %% <- necessary for _ to be displayed correctly
\usepackage{pgfkeys}     %% <- for everything starting with \pgf
\usepackage{etoolbox}    %% <- for \forcsvlist

\newcommand*{\declarekeyword}[1]{%
  \pgfkeys{
    /keyw/#1/.is family,
    /keyw/#1/.unknown/.style = {\pgfkeyscurrentpath/\pgfkeyscurrentname/.initial={##1}},
  }%
}
\newcommand\setkeyword[2]{%
  \declarekeyword{#1}%
  \pgfkeys{/keyw/#1/.cd,name=\detokenize{#1},#2}%
}
\newcommand*\getkeyword[2]{\pgfkeysvalueof{/keyw/#1/#2}}

%% Display a table describing a list of keywords:
\newcommand*\keywordtable[2][name,description]{%
  \begin{center}
    \begin{tabular}{\forcsvlist{\getkeyword{@alignment}}{#1}}
      \forcsvlist{\tableentry[\bfseries]{#1}}{@headings}
      \forcsvlist{\tableentry{#1}}{#2}
    \end{tabular}
  \end{center}%
}
\newcommand*\tableentry[3][]{%
    \let\keywcolsep\empty
    \forcsvlist{\keywcolsep\def\keywcolsep{&}\formattableentry[#1]{#3}}{#2}\\
}
\newcommand*\formattableentry[3][]{#1{\getkeyword{#2}{#3}}}

%% "Fake" keywords (control column titles and alignment)
\setkeyword{@headings}{name=Parameter,description=Description,value=Value}
\setkeyword{@alignment}{name=l,description=l,value=r}

%% Declaration of keywords:
\setkeyword{runtype}{description=A parameter,value=1}
\setkeyword{param_1}{description=Another parameter,value=0}
\setkeyword{param_2}{name=\detokenize{PARAM_2},description=A third parameter,value=42}

\begin{document}

\section{Runtype 1 description}
\keywordtable{runtype,param_1}

\section{Runtype 2 description}
\keywordtable[name,value,description]{runtype,param_2}

\end{document}

output

Circumscribe
  • 10,856
  • Awesome! This is a very versatile method and thorough response, thank you very much! It is a bit complex and wordy (at least in setup) for something that feels conceptually simple, but apparently that's just how latex is. Not sure what's the difference between tabular and tabularx, but the later didn't compile correctly for me so went back to the former. – Nordico Nov 29 '18 at 02:27
  • 1
    @Nordico \begin{tabularx}{\linewidth}{lX} creates a table of total width \linewidth whose second column stretches to accommodate. It was very much inessential to the answer, so I probably shouldn't have used it. (I needed to use either the p{<some width>} or the X column type because one of the entries spans two lines.) – Circumscribe Nov 29 '18 at 09:04
  • Sorry to bother you again, but could this be easily modified so as to include 3 columns instead of two? (the part I'm having trouble with is the definition of \setkeyword) – Nordico Dec 04 '18 at 19:00
  • No problem. The simplest way would be to just insert an &, as in \setkeyword{<keyw>}{<2nd col>&<3d col>} (and to also add another column to the tabular environment, of course). – Circumscribe Dec 04 '18 at 19:08
  • I could also change \setkeyword so that it takes three arguments that you can then also extract separately using something like \getdescription and \getothercolumn. – Circumscribe Dec 04 '18 at 19:09
  • Yes, I would be interested in the second option. I can't figure out how \keyname and \csdef interact or how the two arguments of \setkeyword are used inside different brackets and how that would look like for a third parameter (both for setting and referencing). It seems very specific to a 1-1 relationship. – Nordico Dec 04 '18 at 20:48
  • 1
    It is 1-1, but you could for instance have setkeyword call \csdef twice to make it 1-2, like I've done here. I can think of a few other (fancier) ways to do this, but those wouldn't really perform any better than this. – Circumscribe Dec 04 '18 at 21:45
  • As for \keyname, it isn't too important. \csdef{<name>}{<definition>} expands <name> completely, so I could've just typed out keyw@#1 three times in my solution. I don't remember why I didn't just do that. – Circumscribe Dec 04 '18 at 21:46
  • 1
    @Nordico See the bottom part of my answer for a ridiculously customisable version. – Circumscribe Dec 05 '18 at 09:17
  • Hey, thanks a lot! It looks kind of complicated, I'll try to figure it out how that pgfkeys work reading the manual. But so I can start applying it: how could I use symbols like "," and "=" inside the keyword without the method thinking I'm changing keywords (like name=keyname, description=A descritpion, but know that keyname = 0 doesn't work) – Nordico Dec 05 '18 at 13:17
  • 1
    @Nordico You can use description={something containing = and ,.}. This actually didn't work because I had forgotten to wrap the ##1 in the /keyw/#1/.unknown/.style = … line by {}. (Fixed now) – Circumscribe Dec 05 '18 at 18:58
2
\documentclass[12pt]{article}
\newcommand\zzz{%
  runtype & Keyword to determine runtype. Can be 1 or 2. \\
          & I don't want to hardcode this description twice...}
\begin{document}
\section{Runtype 1 description}
\begin{table}[ht]
\begin{center}
\begin{tabular}{ l l }
\zzz\\
param\_1 & Keyword to determine the parameter of runtype = 1 \\
\end{tabular}
\end{center}
\end{table}

\section{Runtype 2 description}
\begin{table}[ht]
\begin{center}
\begin{tabular}{ l l }
\zzz\\
param\_2 & Keyword to determine the parameter of runtype = 2 \\
\end{tabular}
\end{center}
\end{table}
\end{document}

enter image description here

  • Ok, this works but feels like using a cannon to kill a fly. More precisely: I need to use this with around a hundred keywords, is defining a hundred newcommand to use as a "reference dictionary" advisable? How can I use descriptive names (tablekey_keyname) without using underscores? – Nordico Nov 27 '18 at 21:20
  • @Nordico As to how to use underscores in a command name, options are limited: 1) change catcode of _ (not recommended, as it breaks subscript math), or 2) \csdef and \csuse with the etoolbox package (recommended if you insist on underscore in name) – Steven B. Segletes Nov 27 '18 at 21:24
  • I mean, not necessarily an underscore, it can be other symbol ("tablekey.keyname") or a way of structuring the commands (like an equivalent to C++ namespaces, so I could do "tablekey::keyname"), anything that makes it more human readable/usable than just altogether "tablekeykeyname". – Nordico Nov 27 '18 at 21:29
  • 1
    @Nordico In my example, add \catcode\:=11 as the first line after\documentclass, This changes:to be a "letter". Then\newcommand\zz:z{...}can be defined and later used as\zz:z. What I don't know is if this breaks anything (e.g., spacing, hyphenation), if you use:` as a normal character. – Steven B. Segletes Nov 27 '18 at 21:36
  • 1
    I thought \zzz was reserved for @David Carlisle ;-) – Raoul Kessels Nov 28 '18 at 11:12
  • @RaoulKessels Imitation is the greatest form of flattery. Either that or I am a plagiarizzzt. – Steven B. Segletes Nov 28 '18 at 11:16
1

It depends on where you want to first key the description you want to repeat.

Probably the best place is in the document preamble or in an external file that you can \input.

\documentclass{article}
\usepackage{xparse}

% a few line of code for setting up the system
\ExplSyntaxOn

\NewDocumentCommand{\newdesc}{mm}
 {% #1 is a key, #2 is the description
  \prop_gput:Nnn \g_nordico_descriptions_plist { #1 } { #2 }
 }

\NewDocumentCommand{\getdesc}{m}
 {% #1 is a key
  \prop_if_in:NnTF \g_nordico_descriptions_plist { #1 }
   { \prop_item:Nn \g_nordico_descriptions_plist { #1 } }
   { ???~non~existent~description~??? }
 }

\prop_new:N \g_nordico_descriptions_plist

\ExplSyntaxOff

% the descriptions (they can go in an external file
% say desc.tex and here you'd do \input{desc}

\newdesc{A}{I don't want to hardcode this description twice...}

\begin{document}

\section{Runtype 1 description}
\begin{center}
\begin{tabular}{ l l }
runtype  & Keyword to determine runtype. Can be 1 or 2. \\
         & \getdesc{A} \\
param\_1 & Keyword to determine the parameter of runtype = 1 \\
\end{tabular}
\end{center}

\section{Runtype 2 description}
\begin{center}
\begin{tabular}{ l l }
runtype  & Keyword to determine runtype. Can be 1 or 2. \\
         & \getdesc{A} \\
param\_2 & Keyword to determine the parameter of runtype = 2 \\
\end{tabular}
\end{center}

\end{document}

Note that it is not at all necessary to place a tabular in a floating table environment (which might make the tabular go to another page).

enter image description here

egreg
  • 1,121,712