61

What is the recommended way to define a variable inside a document and retrieve its value later?

I've googled several different solutions, usually about ~10 lines long. I was wondering, that there must be a shorter way of doing something like this (Pseudocode):

\setvalue(VARIABLE1){foo foo bar}

and later in the document

$\getvalue(VARIABLE1)$.

I've also seen solutions that include defining a new command, but what if that command is already used inside another package? That doesn't really seem like an independent solution.

3
  • As noted in my answer, newcommands can't conflict with an existing command without telling you. LaTeX doesn't let you overwrite with it.
    – qubyte
    Dec 4, 2011 at 19:00
  • 1
    Define a macro with \newcommand after loading all packages. Redefine it with \renewcommand in the middle of document. That's what you need I think. Confliction will produce an error when you use \newcommand, don't worry. And the most answers below by now are overkill, especially for a newcomer.
    – Leo Liu
    Dec 5, 2011 at 2:30
  • If the stored value is a box, maybe \saveboxand friends can help? I've noticed also xsavebox package, but i haven't tried it.
    – Alexey
    Mar 9, 2020 at 14:23

8 Answers 8

34

Well, I know it's not what you want, but the standard route is either with a def or a newcommand. The problem that you've touched on is the global namespace. If you use newcommand it will tell you if the command is already set (as an error in compilation which will name the offending newcommand attempt), protecting you from these issues.

% Set your new variable. In this case it will be 
% called "\MyNewVariable" and the value will be "hello".
\newcommand{\MyNewVariable}{hello}

% Use to get the variable.
\MyNewVariable

If you could prepend them all with your name or some other mnemonic, then you're unlikely to get clashes. This mnemonic then behaves as your own (sort of) namespace.

Assuming that the first instance of your variable is set with a newcommand without raising an error, then a renewcommand can be used to alter that variable later.

1
  • As I said, probably not what you're looking for, but it is minimal.
    – qubyte
    Dec 4, 2011 at 18:18
29

Sounds like you're looking for a key-value system. May I suggest pgfkeys? Including Yiannis' idea of using properties for each variable, I'd do it like this:

\documentclass{article}
\usepackage{pgfkeys}

\newcommand{\setvalue}[1]{\pgfkeys{/variables/#1}}
\newcommand{\getvalue}[1]{\pgfkeysvalueof{/variables/#1}}
\newcommand{\declare}[1]{%
 \pgfkeys{
  /variables/#1.is family,
  /variables/#1.unknown/.style = {\pgfkeyscurrentpath/\pgfkeyscurrentname/.initial = ##1}
 }%
}

\declare{}

\begin{document}
 \setvalue{VARIABLE1 = foo foo bar}
 \getvalue{VARIABLE1}

 \declare{test/}
 \setvalue{test/property = 12}
 \getvalue{test/property}
\end{document}

Less than ten lines, even counting the ones that have only braces. The operation is very simple: pgfkeys stores variables as "files" in "directories"; I've decided that yours should be in the /variables directory, so not in the global namespace. (By the way, pgfkeys keys never conflict with normal macro names, so its "global namespace" is different from the macros namespace.)

The \setvalue macro just changes directory appropriately and then calls your assignment. The \getvalue macro retrieves the variable from the correct directory.

The only trick is that in pgfkeys, a key needs to be "known" before it is assigned, or else you have to call it as key/.initial = value. Since I don't want to force you to write that, I created a "handler" for unknown variables that just adds this piece of code behind the scenes.

You declare a variable with properties using \declare{variable/}, and then you can use variable/property as a variable name in \setvalue (you can also use variable/ as a default directory, so write

\setvalue{variable, property 1 = value 1, property 2 = value 2}

which is convenient). The \declare macro just sets up the unknown handler for the "directory" /variables/variable/ (which means that the cryptic line \declare{} at the beginning sets up the /variables/ directory itself).

2
  • 2
    It seems that you have a small typo in your definition of setvalue/getvalue. Your code as is worked for me with \declare{}, but returned error while \setvalue with \declare{abc}. It workes well though when I corrected setvalue to: \newcommand{\setvalue}[1]{\pgfkeys{/variables/#1}}. Jul 24, 2014 at 18:19
  • 2
    @Artem Strange that this went unfixed for so long. Thanks!
    – Ryan Reich
    Jul 24, 2014 at 18:22
18

I would rather define them using a sort of a Lisp way, or object orientated way.

In the minimal below, we use:

\setproperty{test}{aproperty}{12}
\getproperty{test}{aproperty}

Think of them as representing test.aproperty (we actually define them a test@paproperty), this way it is highly unlikely to conflict with any existing commands, other than possibly your own:

The minimal:

\documentclass{article}
\makeatletter
%  Properties a la Lisp.
\def\ece#1#2{\expandafter#1\csname#2\endcsname}%
% \setproperty{ATOM}{PROPNAME}{VALUE} defines the property PROPNAME on the
% ``atom'' ATOM to have VALUE.
% 
\def\setproperty#1#2#3{\ece\protected@edef{#1@p#2}{#3}}%
\def\setpropertyglobal#1#2#3{\ece\protected@xdef{#1@p#2}{#3}}%
%
% 
% \getproperty{ATOM}{PROPNAME} expands to the value of the property
% PROPNAME on ATOM, or to nothing (i.e., \empty), if the property isn't
% present.
% 
\def\getproperty#1#2{%
  \expandafter\ifx\csname#1@p#2\endcsname\relax
  % then \empty
  \else \csname#1@p#2\endcsname
  \fi
}%
%
\makeatother

\begin{document}
\setproperty{test}{aproperty}{12}
\getproperty{test}{aproperty}
\end{document}
1
  • 1
    Instead of \edef and \xdef, maybe \protected@edef and \protected@xdef should be used, so as to allow very general values.
    – egreg
    Dec 4, 2011 at 23:17
12

How about a token list:

\newtoks\myvar
\myvar={foo bar baz}

and later on in the document when you want to use it:

\the\myvar
10

Your code is almost valid ConTeXt: you define a variable using

\setvalue{variable1}{value}

and can get its value using

\getvalue{variable1}

(These are similar to \@namedef and \@nameuse in LaTeX). If you want key-value driven variables, you can use:

\definenamespace
    [VAR]
    [
      name=VAR,
      setup=list,
      command=list,
      parent=VAR,
    ]

\setupVAR
    [a={default A},
     b={default B}]

\defineVAR
    [set1]
    [a={set1 A},
     c={set1 C}]

\defineVAR
    [set2]
    [b={set2 B},
     c={set2 C}]

\starttext
\startlines
  \namedVARparameter{set1}{a} % gives set1 A
  \namedVARparameter{set1}{b} % gives default B
  \namedVARparameter{set1}{c} % gives set1 C
\stoplines
\stoptext
5

A solution with the prop module from LaTeX3:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\setproperty}{ O{standard} m m }
  {
   \cs_if_exist:cF { g_citr_#1_prop } { \prop_new:c { g_citr_#1_prop } }
   \prop_gput:cnn { g_citr_#1_prop } { #2 } { #3 }
  }
\NewDocumentCommand{\getproperty}{ O{standard} m }
  {
   \cs_if_exist:cTF { g_citr_#1_prop } 
     {
      \prop_get:cnN { g_citr_#1_prop } { #2 } \l_citr_value_tl
      \quark_if_no_value:NTF \l_citr_value_tl 
        {
         Inexistent~property~#2
        }
        {
         \tl_use:N \l_citr_value_tl
        }
     }
     { 
      Inexistent~family~#1
     }
  }
\prop_new:N \g_citr_standard_prop
\tl_new:N \l_citr_value_tl
\ExplSyntaxOff

\begin{document}
\setproperty[test]{aproperty}{12}
\getproperty[test]{aproperty}

\getproperty[test]{bproperty}

\setproperty{cproperty}{42}

\getproperty{cproperty}


\end{document}

There is a "standard" property list set; others can be defined and accessed with the optional argument to \setproperty or \getproperty.

3

There are already enough solutions for you. I give the following because your question provides me an opportunity to test a new feature of pathkeys package. Please don't give \NewVariables the name \newvariables. The latter is an internal command in a package. That will be changed soon.

\documentclass{article}
\usepackage{pathkeys}
\makeatletter
\newcommand\setvalues[1]{\pathkeys{citronas/var/set:#1}}
\newcommand\getvalue[1]{\pathkeysval{citronas/var/#1}}
\def\variablelist{}
\newcommand\NewVariables[1]{%
  \cptfor#1\dofor{%
    \def\reserved@a####1=####2=####3\@nil{%
      \cptgaddtolist[;]\variablelist{cmd/####1/####2}%
    }%
    \reserved@a##1==\@nil
  }%
  \cptexpanded{\pathkeys{citronas/var/define*:\expandcsonce\variablelist}}%
}
\makeatother
\NewVariables{%
  var1 = default value1,
  var2 = defaultvalue2
}
\setvalues{var1=new value1, var2=new value2}
\edef\x{\getvalue{var1}}

\begin{document}
\getvalue{var1}
\end{document}

Version 0.0.3 of ltxkeys package (to be uploaded on 2011/12/09) will allow you to do things like the following:

\declarevariables[mynamespace]{%
  var1 = {default value1} = \def\mycolor{#1}\def\cmd##1{##1},
  var2 = default value2, % No callback
  var3 % no default value and no callback
}

\setvarvalues[mynamespace]{var1=new value1, var2=new value2}

\edef\x{\getvarvalue[mynamespace]{var1}}

\begin{document}
\getvarvalue{fam/subfam}{var1}
\end{document}

[mynamespace] will be optional. That means that, to keep \getvarvalue expandable, we need an expandable \@testopt. One can be designed but Joseph Wright, if I got him right the other time, doesn't seem to be comfortable with any expandable \@testopt. My tests have so far revealed nothing worrisome about them. With some luck Joseph will confirm or refute his objection here.

2

How about:

\documentclass{article}

\newcommand{\setvalue}[2]{
    \ifdefined #1
        \renewcommand{#1}{#2}
    \else
        \newcommand{#1}{#2}
    \fi
}

\setvalue{\somevar}{value1}  % set a value
\begin{document}
    \somevar                     % get/print the value of somevar (value1)
    \setvalue{\somevar}{value2}  % set different value  
    \somevar                     % get/print the value of somevar (value2)
\end{document}

Note that you should choose your variable name carefully, as it will overwrite its previous definition.

1
  • Apparenltly, the following is not possible: \setvalue{\somevar}{\somevar+2} Jul 21, 2023 at 12:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .