3

I'd like to use \nameref with hyperref, but I want be able to render my document without hyperref, too.

So I'd like to define a wrapper command around nameref (not defining a "dummy" \nameref myself), depending on whether \nameref is defined.

The answer preferably works with any defined command, not just \nameref.

From similar questions I tried to derive some solution that did not work:

\newcommand{\checkAndDef}[1]{%
\ifcsname#1\endcsname%
\newcommand{\refn}[1]{\nameref{#1}}%
\else%
\newcommand{\refn}[1]{[#1]}% no \nameref available
\fi%
}
\checkAndDef{nameref}%

Note the \checkAndDef looks ugly, and I added it after I failed to do a literal check for \nameref. I guess I would need to know more details about TeX and LaTeX to fix that than I do.

First solution modeled after werner's proposals (does not work):

\newcommand{\namerefcheck}{%
\ifcsname{}nameref\endcsname%
\newcommand{\refn}[1]{\nameref{##1}}%
\else%
\newcommand{\refn}[1]{[\detokenize{##1}]}% no \nameref available
\fi%
}
\AtBeginDocument{\namerefcheck}

This "solution" defines \refn as if hyperref weren't loaded, regardless whether is is or not.

Note: If you replace {}nameref with \nameref, then it works (it seems to me).

  • The package hyperref loads the package nameref but the package nameref can also be loaded without hyperref. If you do that, the command \nameref is available without hyperlinks. – Ulrich Diez Nov 14 '19 at 2:11
1

A couple of things are going on here:

  1. When defining a command within another command, you need to double up on the use of your parameter notation. So, #1 should become ##1, #2 should become ##2, and so forth. In that sense, the following definition of \checkAndDef is probably what you're after:

    \newcommand{\checkAndDef}[1]{%
      \ifcsname#1\endcsname%
        \newcommand{\refn}[1]{\nameref{##1}}%
      \else%
        \newcommand{\refn}[1]{[\detokenize{##1}]}% no \nameref available
      \fi%
    }
    

    I've added a \detokenize to the "no \nameref available" option, since labels can contain some odd characters (like underscore _) by default.

  2. hyperref should usually be loaded last of all the packages, since it interfaces with many document elements, all of which could be changed by other included packages. As such, \checkAndDef may be required after \begin{document} in some cases. This is specifically required for \nameref.

\documentclass{article}

\usepackage{hyperref}% Comment out or not

\newcommand{\checkAndDef}[1]{%
  \ifcsname#1\endcsname
    \newcommand{\refn}[1]{\nameref{##1}}%
  \else
    \newcommand{\refn}[1]{[\detokenize{##1}]}% no \nameref available
  \fi
}

\AtBeginDocument{\checkAndDef{nameref}}

\begin{document}

\section{A section}\label{sec:section}

\refn{sec:section}

\end{document}

The above code results in

enter image description here

when hyperref is loaded, and

enter image description here

when hyperref is not loaded.

  • On your recommendation that hyperref should be loaded as the last package, please see tex.stackexchange.com/questions/516039/… ;-) – U. Windl Nov 12 '19 at 20:11
  • OK, I wasn't aware that \begin{document} does some define magic. As the body of \checkAndDef only makes sense if the argument is nameref, can you simplify that? If the code needs to be run after \begin{document} it should be inside a command still, as otherwise it would just look too ugly (IMHO). – U. Windl Nov 12 '19 at 20:16
  • @U.Windl: It's not \begin{document} that is doing some magic... it's that some macros are delayed in terms of their definition until \begin{document}. So, \AtBeginDocument{<stuff>} delays <stuff> until after \begin{document}. It really depends on what you're defining. You could include the \AtBeginDocument as part of the \checkAndDef code. – Werner Nov 12 '19 at 20:23
  • Would you care to represent an alternate \AtBeginDocument version that replaces the first parameter of \checkAndDef with literal nameref? It seems that would be what I really had in mind. – U. Windl Nov 12 '19 at 20:35
2

I propose

\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\conditionaldef}{mmO{0}omm}
 {% #1 = command to define
  % #2 = command to test
  % #3 = number of args,
  % #4 = default optional
  % #5 = replacement text if #2 exists
  % #6 = replacement text if #2 doesn't exist
  \AtBeginDocument
   {
    \cs_if_exist:NTF #2
     {
      \IfNoValueTF{#4}
       {
        \newcommand{#1}[#3]{#5}
       }
       {
        \newcommand{#1}[#3][#4]{#5}
       }
     }
     {
      \IfNoValueTF{#4}
       {
        \newcommand{#1}[#3]{#6}
       }
       {
        \newcommand{#1}[#3][#4]{#6}
       }
     }
   }
 }
\ExplSyntaxOff

and you call it like

\conditionaldef{\refn}{\nameref}[1]{\nameref{#1}}{[\detokenize{#1}]}

Full example.

\documentclass{article}
\usepackage{xparse}
\usepackage{hyperref}

\ExplSyntaxOn
\NewDocumentCommand{\conditionaldef}{mmO{0}omm}
 {% #1 = command to define
  % #2 = command to test
  % #3 = number of args,
  % #4 = default optional
  % #5 = replacement text if #2 exists
  % #6 = replacement text if #2 doesn't exist
  \AtBeginDocument
   {
    \cs_if_exist:NTF #2
     {
      \IfNoValueTF{#4}{\newcommand{#1}[#3]{#5}}}{\newcommand{#1}[#3][#4]{#5}}
     }
     {
      \IfNoValueTF{#4}{\newcommand{#1}[#3]{#6}}{\newcommand{#1}[#3][#4]{#6}}
     }
   }
 }
\ExplSyntaxOff

\conditionaldef{\refn}{\nameref}[1]{\nameref{#1}}{[\detokenize{#1}]}

\begin{document}

\section{Test}\label{test}

\refn{test}

\end{document}

Output if hyperref is loaded

enter image description here

Output if hyperref is not loaded

enter image description here

Note

You essentially use the same syntax as for \newcommand, so also commands with a single optional argument are allowed. No need of doubling # when calling \conditionaldef.

Without xparse

\documentclass{article}
%\usepackage{hyperref}

%%% syntax
%% \conditionalref{<marg>}[<oarg>][<oarg>]{<marg>}{<marg>}
\makeatletter
\newcommand{\conditionaldef}[2]{%
  \@ifnextchar[{\cond@def@opt{#1}{#2}}{\cond@def@noopt{#1}{#2}}%
}
\newcommand{\cond@def@noopt}[4]{%
  \AtBeginDocument{%
    \ifdefined#2%
      \newcommand{#1}{#3}%
    \else
      \newcommand{#1}{#4}%
    \fi
  }%
}
\def\cond@def@opt#1#2[#3]{%
  \@ifnextchar[{\cond@def@twoopt{#1}{#2}{#3}}{\cond@def@oneopt{#1}{#2}{#3}}%
}
\def\cond@def@oneopt#1#2#3#4#5{%
  \AtBeginDocument{%
    \ifdefined#2%
      \newcommand{#1}[#3]{#4}%
    \else
      \newcommand{#1}[#3]{#5}%
    \fi
  }%
}
\def\cond@def@twoopt#1#2#3[#4]#5#6{%
  \AtBeginDocument{%
    \ifdefined#2%
      \newcommand{#1}[#3][#4]{#5}%
    \else
      \newcommand{#1}[#3][#4]{#6}%
    \fi
  }%
}
\makeatother

\conditionaldef{\refn}{\nameref}[1]{\nameref{#1}}{[\detokenize{#1}]}

\begin{document}

\section{Test}\label{test}

\refn{test}

\end{document}

Simpler version

Of course, the above is a waste of time if you just want to define one command: a simple

\AtBeginDocument{%
  \ifdefined\nameref
    \newcommand{\refn}[1]{\nameref{#1}}%
  \else
    \newcommand{\refn}[1]{[\detokenize{#1}]}%
  \fi
}

will suffice. The infrastructure above allows to define many commands without code duplication.

  • This answer looks nice, but can it be done without xparse also? – U. Windl Nov 12 '19 at 21:39
  • @U.Windl Yes, certainly. Why? – egreg Nov 12 '19 at 21:45
  • Defining many commands creates a "dependency mess", so I prefer solutions with no extra dependencies. Didn't need xparse yet. – U. Windl Nov 12 '19 at 21:58
  • @U.Windl Compare the codes in the updated answer and you'll know why I'd prefer xparse. – egreg Nov 12 '19 at 22:00
  • OK, as my final solution was based on werner's proposals, I'll accept the latter as an answer. Probably simply, because he was faster. – U. Windl Nov 12 '19 at 22:35
1

Assuming that the token \BizArReUnDEFiNeD is always undefined, you can do:

\documentclass{article}

\makeatletter
% \checkAndDef{<other command>}%
%             {<definition-command><new command><parameter text>}%
%             {<new command's definition-text in case other command is undefined>}%
%             {<new command's definition-text in case other command is defined>}
%
% <parameter text> can be omitted.
%
\newcommand\checkAndDef[4]{%
  \AtBeginDocument{%
    \ifx\BizArReUnDEFiNeD#1%
      \expandafter\@firstoftwo
    \else
      \expandafter\@secondoftwo
    \fi
    {#2{#3}}{#2{#4}}%
  }%
}%
\makeatother

\usepackage{hyperref}
%\csname @ifpackageloaded\endcsname{hyperref}{}{\usepackage{nameref}}

\checkAndDef{\nameref}{\newcommand*\refn[1]}{\detokenize{[#1]}}{\nameref{#1}}%

\begin{document}
\section{Test-Section}\label{test}%
Some text. Reference: \refn{test}
\end{document}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.