1

Here are some macros that I am trying to define (based on this question):

  • \odef{foo}{bar} will define an "object" with name foo and value bar.
  • \objRef{foo} will insert the value of the foo "object."

These macros should allow me to reference variables defined anywhere in my document. For example, doing this:

Lorem ipsum dolor \objRef{sentence-Ends/lipsum}.

% INSERT OTHER PARTS OF MY DOCUMENT HERE (CHAPTERS, SECTIONS, FIGURES, TABLES, ETC.)

Defining variables now... \odef{sentence-Ends/lipsum}{sit amet} Done!

should produce

Lorem ipsum dolor ???.

Definining variables now... Done!

after the first run of LaTeX and

Lorem ipsum dolor sit amet.

Definining variables now... Done!

after the second run.

I wrote this code in objectref.sty, but it is not working as expected—I always get ??? on the page and the undefined warning in my log file.

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{objectref}[Create and reference custom variables in LaTeX documents]

\def\odef#1#2{\immediate\write\@auxout{\expandafter\def \csname obj:#1\endcsname {#2}}}
\def\objRef#1{\ifcsname obj:#1\endcsname \csname obj:#1\endcsname \else ??? \PackageWarning{objectref}{Object #1 undefined on input line \the\inputlineno.}\fi}

Among other things, the .aux file generated when I compile my document contains:

\def \obj:sentence-Ends/lipsum {sit amet}

which should mean that everything will work properly (alas, it does not).

What is the problem with my code, and how do I fix it?

1
  • 1
    \def\obj:sentence-Ends/lipsum {sit amet} creates a macro \obj with a delimiter sentence-Ends/lipsum. You'll either need to either write the macro name wrapped in \csname/\endcsname to the .aux file or only use letters + @ in the macro name. Oct 27, 2022 at 3:29

2 Answers 2

3

The main problem is that you should use \gdef and not \def, because the .aux file is read inside a group.

Second problem: with your code you get

\def \obj:sentence-Ends/lipsum {...}

in the .aux file, but this defines \obj.

Third problem: if you do

\odef{test}{\'amet}

the .aux file will contain garbage (try it).

Fourth problem: you get spurious spaces in the output, if you plan to say \odef in the middle of a paragraph.

Proposed solution.

Don't write \def nor \gdef in the .aux file and make the replacement text not to expand. Also use \@bsphack and \@esphack.

\documentclass{article}

\makeatletter

\newcommand{\odef}[2]{%
  \@bsphack
  \immediate\write\@auxout{\string\objectref@def{#1}{\unexpanded{#2}}}%
  \@esphack
}
\newcommand{\objectref@def}[2]{\global\@namedef{obj:#1}{#2}}

\newcommand{\objRef}[1]{%
  \ifcsname obj:#1\endcsname
    \csname obj:#1\endcsname
  \else
    ???%
    \PackageWarning{objectref}{Object #1 undefined on input line \the\inputlineno.}%
  \fi
}

\makeatother

\begin{document}

Lorem ipsum dolor \objRef{sentence-Ends/lipsum}

Defining variables now... \odef{sentence-Ends/lipsum}{sit \'amet} Done!

Defining variables now... Done!% added line to check spaces

\end{document}

The .aux file will be

\relax 
\objectref@def{sentence-Ends/lipsum}{sit \'amet}
\gdef \@abspage@last{1}

The output is

enter image description here

If I comment out \@bsphack and \@esphack, I get

enter image description here

and the wrong spacing is clear.

A different version where the second argument to \odef is safely expanded in the .aux file; safely means that accents and other dangerous command need not be protected.

This is achieved with the more powerful functions of expl3, in particular \text_expand:n that precisely does the job we need.

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\odef}{mm}
 {
  \use:c { @bsphack }
  \iow_now:cx { @auxout } { \token_to_str:N \objectrefdef { #1 } { \text_expand:n { #2 } } }
  \use:c { @esphack }
 }

\NewDocumentCommand{\objectrefdef}{mm}
 {
  \prop_gput:Nnn \g_benz_objectref_prop { #1 } { #2 }
 }

\prop_new:N \g_benz_objectref_prop

\NewExpandableDocumentCommand{\objRef}{m}
 {
  \prop_if_in:NnTF \g_benz_objectref_prop { #1 }
   {
    \prop_item:Nn \g_benz_objectref_prop { #1 }
   }
   {
    ???
    \PackageWarning{objectref}{Object~#1~undefined}
   }
 }

\ExplSyntaxOff

\newcounter{Counter}

\begin{document}

Lorem ipsum dolor \objRef{sentence-Ends/lipsum}

Defining variables now... \odef{sentence-Ends/lipsum}{sit \'amet} Done!

Defining variables now... Done!% added line to check spaces

\stepcounter{Counter}

\odef{Name with space}{The counter value hêr\'e is \theCounter}

\stepcounter{Counter} % now 2

\objRef{Name with space}

\end{document}

Note that spaces in the first argument of \odef are honored and that the value of Counter current at \odef time is used.

enter image description here

6
  • Works very well! I like that you configured the macro to use the unexpanded version of the text in the .aux file, but one of my use cases is storing the value of a counter at the point of \odef. \odef{myObj}{\theCounter} just prints out the value of the counter at the point of use (i.e., where I write \objectRef)—how can I print the value of the counter at the point of definition? Something like \odef{myObj}{\expand\theCounter} (this doesn't actually work)? Oct 27, 2022 at 11:52
  • Also, is there a way to allow spaces within the object name? For example, \odef{My Object}{Hello, world!}. Oct 27, 2022 at 11:54
  • @BenZ. Spaces should work out of the box. For the other problem, it mostly depend on what you expect to have in the second argument to \odef. Is it just either text or a single macro such as \theCounter? However, if you only plan to use counter representations there, the \label-\ref mechanism can be used without needing any other infrastructure.
    – egreg
    Oct 27, 2022 at 12:40
  • Is there a command that I can put in the second argument to \odef that will allow me to expand a counter then and there, if I use one in the argument? So I would like to be able to do something like \odef{myObj}{The counter value here is \theCounter}, but using the value of the Counter counter at the point of writing \odef. Oct 27, 2022 at 17:55
  • 1
    @BenZ. I added a version that does the more complex job you need.
    – egreg
    Oct 27, 2022 at 20:10
2

You didn't give minimal example, so my answer is only theoretical and not checked. Define \sdef macro with usage:

\sdef{text}{body}

which define \csname text\endcsname as body. And use it in .aux file. So:

\def\sdef#1{\expandafter\gdef\csname#1\endcsname}
\def\odef#1#2{\immediate\write\@auxout{\string\sdef{obj:#1}{#2}}}
\def\objRef#1{\ifcsname obj:#1\endcsname \csname obj:#1\endcsname \else ??? \PackageWarning{objectref}{Object #1 undefined on input line \the\inputlineno.}\fi}
0

You must log in to answer this question.

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