6

In LaTeX, is there a way to define a macro (or something) to replace every instance of

\cite{a_literal_string}

with

\ref{appendix:a_different_literal_string}

without touching instances of \cite with arguments that aren't a_literal_string? I would like to do this for several different possible replacements.

The idea here is that I'm including some modular documents in a larger project, and I want to use some of them as appendices. I don't want to just find/replace, because the sub-documents need to be able to refer to each other when they aren't being used in the larger project.

  • 1
    Will Find and replace work? – darthbith Sep 9 '14 at 15:46
  • Nah. The idea is to do this for files which are being included via \subimport, and to only make the change in one particular document which is including them. I will add this to the question. – DanielSank Sep 9 '14 at 15:47
  • \renewcommand\cite[1]{\ref{appendix:#1}}? – Henri Menke Sep 9 '14 at 15:52
  • @HenriMenke: This redefines all uses of \cite, which is not what is asked for in the question. – DanielSank Sep 9 '14 at 17:11
9
+50

Here's an implementation with expl3 and xparse:

\begin{filecontents*}{\jobname.bib}
@article{cite:a,
 author={A. Uthor},
 title={Title a},
 journal={J. A},
 year={2014},
}
@article{cite:b,
 author={B. Athor},
 title={Title b},
 journal={J. B},
 year={2014},
}
@article{cite:c,
 author={C. Ethor},
 title={Title c},
 journal={J. C},
 year={2014},
}
\end{filecontents*}

\documentclass{article}
\usepackage{xparse,letltxmacro}
\LetLtxMacro{\latexcite}{\cite}

\ExplSyntaxOn
\RenewDocumentCommand{\cite}{om}
 {
  \IfNoValueTF{#1}
   { \daniel_cite_or_ref:n { #2 } }
   { \latexcite[#1]{#2} }
 }
\NewDocumentCommand{\addsubstitution}{mm}
 {
  \tl_gput_right:Nn \g_daniel_changes_tl { {#1}{\ref{#2}} }
 }

\tl_new:N \g_daniel_changes_tl

\cs_new_protected:Npn \daniel_cite_or_ref:n #1
 {
  \str_case:nVTF { #1 } \g_daniel_changes_tl
  {
   % \nocite{#1} %%%% <---- uncomment this line if you want \nocite
  }
  {
   \latexcite{#1}
  }
 }

\cs_generate_variant:Nn \str_case:nnTF { nV }
\ExplSyntaxOff

\addsubstitution{cite:a}{ref:a}
\addsubstitution{cite:b}{ref:b}

\begin{document}

\section{Something}\label{sec:some}

Here we do the citations: \cite{cite:a}, \cite{cite:b}, 
\cite{cite:c} and \cite[p.~42]{cite:c}

\appendix

\section{First appendix}\label{ref:a}

Text

\section{Second appendix}\label{ref:b}

Text

\bibliographystyle{plain}
\bibliography{\jobname}

\end{document}

You're supposed to use just one item in a \cite command; taking into account multiple citations with substitutions is possible, but it requires a decision about the final format.

With the \addsubstitution command you add the substitutions you want to do: the first argument is the citation key, the second argument is the label.

In the references only the actually cited items are inserted; if you want to add also the items that you substitute, it's sufficient to uncomment \nocite in the macros above.

enter image description here

Note on the implementation

The expl3 function \str_case:nnTF takes four arguments:

  1. the string to check;
  2. a set of pairs {<string>}{<action>};
  3. what to do if a match is found;
  4. what to do if no match is found.

With the variant \str_case:nVTF, the second argument is a token list variable that contains the set of pairs.

The new \cite command will use the original \cite if the optional argument is given; otherwise it passes control to \daniel_cite_or_ref:n that checks the argument against the set of pairs stored in \g_daniel_changes_tl; this variable is populated by any number of \addsubstitution commands.

It would be easy, albeit not straightforward, to add other features, for instance accepting multiple keys in the argument of \cite and deciding what to do when a key needs the substitution.

7

If you're willing to use LuaLaTeX, your objective can be achieved using Lua's powerful string.gsub function. In the example below, the Lua-side code is stored in a file called string_replacements.lua. (For anything but the most trivial cases, it's usually easiest to store the Lua code in a separate file and to call it with a \directlua{ require("string_replacements.lua")} directive.) The function replace_string can perform multiple string replacements. Is is assigned to the process_input_buffer callback, which does its job before any processing by LaTeX takes place; the contents of the input file are not modified.

enter image description here

% !TEX TS-program = lualatex
\documentclass{article}
\usepackage{luatexbase}
\directlua{require("string_replacements.lua")}

\begin{document}
\section{Hello}
Here's a cross-reference to Appendix \cite{a_literal_string}. 
And here's a cross-reference to abcdefuvwxyz.

\appendix
\section{Good-bye} \label{appendix:a_different_literal_string}
\begin{equation} \label{eq:pyth}
a^2+b^2=c^2
\end{equation}
\end{document}

The contents of string_replacements.lua are:

-- string_replacements.lua
   local function replace_string ( line )
       line = string.gsub ( line, 
           "\\cite{a_literal_string}" , 
           "\\ref{appendix:a_different_literal_string}" )
       line = string.gsub ( line,
           "abcdefuvwxyz",
           "equation~(\\ref{eq:pyth})" )
       return line
   end
   luatexbase.add_to_callback( "process_input_buffer", 
       replace_string, "replace_string" )
  • Interesting, but just one substitution is supported. – egreg Sep 9 '14 at 20:35
  • Indeed, I am looking for multiple substitutions. I will edit OP. – DanielSank Sep 10 '14 at 2:52
  • @DanielSank - I've updated the example to show how perform multiple string replacements. – Mico Sep 10 '14 at 5:11
  • wow...seems pretty dangerous though...to what does the substitution apply exactly? Does it also substitute comment strings? What about things in verbatim? I know having \verb|\cite{a_literal_string}| in the same doc is unrealistic but I'm interested in the general case... – Bordaigorl Sep 15 '14 at 16:17
  • @Bordaigorl - "seems pretty dangerous though" -- Why? As already noted, function in the process_input_buffer callback operate on all material in the input stream, before any processing by TeX takes place. If the OP wants the string a_literal_string replaced with a_different_literal_string everywhere in the document, I can only assume that this objective applies to verbatim material as well. Note that any substitutions inside comments remain commented out -- nothing dangerous there either, right? – Mico Sep 15 '14 at 16:28
3

Maybe you need something like this:

\def\speclabel#1#2{\expandafter\def\csname slab:#1\endcsname{#2}}
\let\citeOri=\cite
\def\cite#1{\expandafter\ifx\csname slab:#1\endcsname\relax\citeOri{#1}%
   \else \expandafter\expandafter\expandafter\ref
         \expandafter\expandafter\expandafter{\csname slab:#1\endcsname}\fi
}
\speclabel {a_literal_string}       {a_different_literal_string}
\speclabel {another_literal_strig}  {another_different_litral_string}

Now \cite{normal} behaves normal but \cite{a_literal_string} expands
to \ref{a_different_literal_string}.

You have to declare conversions by \speclabel macros and you can use normal \cite.

  • 1
    A pure LaTeX version would be \protected\def\cite#1{\@ifundefined{slab:#1}{\citeOri{#1}}{\ref{\@nameuse{slab:#1}}}}. I understand you want to be compatible with formats that also use \cite and \ref, but this is not good practice in my opinion. Of course, your code loses the optional argument to \cite. – egreg Sep 9 '14 at 20:09
  • 1
    @egreg The question doesn't include the word LaTeX. I don't know what format OP needs to use (and I don't understand why other posters know it). My answer is independent of used format (and I tested it only by plain TeX). – wipet Sep 9 '14 at 20:18
  • 4
    I thought Plain TeX hasn't \cite and \ref. Should I check the TeXbook? – egreg Sep 9 '14 at 20:21
  • 3
    TeX disposes of \def. So document can use arbitrary control sequences, \cite and \ref for example. You can check the chapter from TeXbook where the \def is described, if you want. – wipet Sep 10 '14 at 6:30
  • 2
    As you are using e-TeX, perhaps \expandafter\ifx\csname slab:#1\endcsname\relax could be replaced by an \ifcsname slab:#1\endcsname test? – Joseph Wright Sep 10 '14 at 18:13

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.