5

A little warning in advance: This will be quite lenghty. I spent quite some time over the last few days trying to solve the following problem: I'm typesetting mathematical lecture notes and have used the ntheorem package to define various theorem-like environments and frequently encounter situations of the form

\begin{mythm}\begin{enumerate}
  \item\label{a}
  \item\label{b}
\end{enumerate}\end{mythm}

and ideally typing (in my case, as I use the cleveref package)

\cref{b}

should produce something of the form "Theorem 3 (b)" (i.e. including the name and number of the theorem environment the label is placed in).

This turned out to be quite difficult and also not very well documented. The main problem is that there are two counters (enumi and the theorem-counter, lets call it thmcnt), as well as the theorem name involved.

My first try was to, with the etoolbox package, hack myself into the mythm-environment, create a new macro every time it's started, which saves the theorem name and number and can be formated (and referenced) with the cleveref commands, i.e. something like

\BeforeBeginEnvironment{mythm}{
   \crefname{\labelcode}{Theorem~\thethmcnt}
   \edef\Label#1{\noexpand\label[\labelcode]{#1}}
}

Additionally, \labelcode should be something unique to this call of the environment, such as

\edef\labelcode{ctr@thm@\roman{chapter}@\roman{section}@\arabic{thmcnt}

and, as we're before the execution of the environment, we need to remember to raise the counter by one (and set it back down once we're done), otherwise the theorem counter as saved in \crefname will be off by one.

So ideally, in our above example, we could write

\begin{mythm}\begin{enumerate}
  \item\Label{a}
  \item\Label{b}
\end{enumerate}\end{mythm}

and \cref{b} should do just what we want it to. Unfortunately, as far as I could tell, cleveref ignores all \crefname commands after \begin{document}, so it's not quite that easy.

What I tried then was to write all the formating information into a file and let LaTeX read it the next time round so that after upon compiling the document twice, all link information is accessible. Something like

\newread\foo
\immediate\openin\foo=\filename
\ifeof\foo\relax\else\input\filename\relax\fi
\immediate\closein\foo
\newwrite\blah
\immediate\openout\blah=\filename
\AtEndDocument{\immediate\write\blah{\string\endinput}\relax
   \immediate\closeout\blah}

and then replace the corresponding line above by

\immediate\write\blah{\string\crefname{\labelcode}{Theorem~\thethmcnt}{}}

and that seems to work, although it seemed to me rather complicated for a (what I believed to be) relatively common problem. If anyone (is still reading ;)) and knows a more simple solution, I'd be glad and interested to know!

There's still one small fault, however. When cleveref is used together with hyperref it also turns all references into clickable links. However, in the current solution, only the "(b)" is turned to a hyper-reference. The obvious approach is to use cleverefs \crefformat command, which has an, for this purpose, impractical syntax, namely, we would have to write something of the sort

\crefformat{\labelcode}{#2Theorem~\thethmcnt~#1#3}

in our \BeforeBeginEnvironment block, but of course this has the same problem as before that \thethmcnt produces the number of the theorem we're at when referencing and not the one of the theorem we are making reference to.

So I tried, as above to simply write it to our file with

\immediate\write\blah{\noexpand\crefformat{\labelcode}{#2Theorem~\thethmcnt~#1#3}}

but unfortunately this doesn't work and this is the point where I'm stuck: Writing this to a file doesn't work, as the # get replaced with ## and that results in an error when LaTeX reads the file. It actually works if one manually replaces ## by # in the output file, before rerunning LaTeX, but that's not a very satisfying solution. I guess one could write an external script and call it together with LaTeX upon every compilation or with the \write18 command or something similar, but that isn't good for collaboration or cross-platform implementation, so does anyone know a LaTeX-internal solution?

I already tried changing the catcode of # or trying to access it via \char and similar commands, but none of that worked, it always resulted in something like

! Illegal parameter number in definition of \@groupformat.

Does anyone have any comments or ideas? Thank you for reading this far :)

EDIT: This functionality is now incorporated in the coolthms package, available on CTAN.

2
  • Note that \char is not good for receiving the character as ASCII character, e.g. for \edef or \write, but it is possible to do it with \catcode changes. You should might have done something wrong, e.g. changing the catcode to late. Feb 25, 2011 at 15:56
  • Note that it is unusual around here to sign your questions (as there is already a box with your username below it) or to have any greeting.
    – lockstep
    Feb 26, 2011 at 15:42

3 Answers 3

2

You can use the following methods to write the hash character # into external files. I replaced some macro with dummies to keep the code minimal.

\documentclass{article}

\begingroup
\catcode`\#=12
\gdef\hashchar{#}%
\endgroup

% Alternative
\edef\althashchar{\string#}%

% dummy macros
\def\labelcode{LABEL}
\def\thethmcnt{thethmcnt}
\def\crefformat#1#2{}
\chardef\blah=1\relax% write to .aux file

\begin{document}

% Works
\immediate\write\blah{\noexpand\crefformat{\labelcode}%
   {\hashchar2Theorem\string~\thethmcnt\string~\hashchar1\hashchar3}}

% Works as well
\immediate\write\blah{\noexpand\crefformat{\labelcode}%
   {\althashchar2Theorem\string~\thethmcnt\string~\althashchar1\althashchar3}}

% Works too
\immediate\write\blah{\noexpand\crefformat{\labelcode}%
   {\string#2Theorem\string~\thethmcnt\string~\string#1\string#3}}

% Now inside a macro:
\def\mywritemacro{%
% Works
\immediate\write\blah{\noexpand\crefformat{\labelcode}%
   {\hashchar2Theorem\string~\thethmcnt\string~\hashchar1\hashchar3}}

% Works as well
\immediate\write\blah{\noexpand\crefformat{\labelcode}%
   {\althashchar2Theorem\string~\thethmcnt\string~\althashchar1\althashchar3}}

% Works too
\immediate\write\blah{\noexpand\crefformat{\labelcode}%
   {\string##2Theorem\string~\thethmcnt\string~\string##1\string##3}}
}
\mywritemacro

\end{document}

Resulting file (here .aux):

\relax 
\crefformat {LABEL}{#2Theorem~thethmcnt~#1#3}
\crefformat {LABEL}{#2Theorem~thethmcnt~#1#3}
\crefformat {LABEL}{#2Theorem~thethmcnt~#1#3}
\crefformat {LABEL}{#2Theorem~thethmcnt~#1#3}
\crefformat {LABEL}{#2Theorem~thethmcnt~#1#3}
\crefformat {LABEL}{#2Theorem~thethmcnt~#1#3}
2
  • wow, thank you so much! I didn't think it was going to be that easy! My mistake was probably that I tried changing the catcode only inside my defined macro and that I put \string in the wrong places...
    – Jonathan
    Feb 25, 2011 at 16:34
  • @Jonathan: Yes, changing the catcode only inside my defined macro is a common mistake. Note that you should also ensure that ~ isn't expanded. I found out that here \protect doesn't do much good. A simple \string like in my answer or \noexpand should do the trick. Feb 25, 2011 at 16:52
2

By all means, step away from using the enumerate environment when you really want a subtheorem environment, with its own counter.

\documentclass{article}


\usepackage{ntheorem}
%\usepackage{amsthm}

\usepackage{thmtools}
\usepackage{enumitem}
\usepackage{hyperref}

\usepackage{cleveref}

\declaretheorem[name=My Theorem,
    ]{mythm}
% yes, this boilerplate could be automated: do it for every theorem.
% in theory, wasting counters could be avoided by use of counter aliasing,
% but that would require patching of enumitem.
\newlist{submythm}{enumerate}{1}
\crefname{submythmi}{My Theorem}{My Theorems}
\setlist[submythm]{label=(\alph*),%
  ref=\themythm (\alph*)}

% dynamically turn into the propery submythm, sublemma, whatnot.
\makeatletter
\newenvironment{subs}{%
  \edef\subname{sub\thmt@envname}%
  \expandafter\begin\expandafter{\subname}% enumitem requires explicit begin
}{%
  \expandafter\end\expandafter{\subname}%
}
\makeatother


\begin{document}
\begin{mythm}\begin{subs}
  \item\label{a} Hello!
  \item\label{b} Hello yourself!
\end{subs}\end{mythm}

\begin{enumerate}
  \item bim!\label{c}
\end{enumerate}

Those were \ref{a} and \ref{b} and \ref{c}

Those were \cref{a} and \cref{b} and \cref{c}
\end{document}

Edit: this variant allows \ref, but it will yield 1 (a) even before the next theorem, which might not be what you want. (Possibly fixable by careful use of \p@foo, but I submit that it would cause confusion if you have Theorem 1 a ... b ... Corollary 1 a ... b ... Proof of Theorem 1 ... Proof of Corollary 1 ...: it's not clear where to look for a naked reference to (a).)

3
  • Thank you! This is really a very elegant solution (especially because it avoids creating loads of counters). I thought about using the enumitem ref feature, but didn't know how to access the ntheorem data. Unfortunately there seems to be no easy combination of enumitem and cleveref and I do find it quite essential to be able to reference only the list counter (for example when refering to part of the theorem in the proof...)
    – Jonathan
    Feb 25, 2011 at 17:51
  • @Jonathan: good point. Still solvable by throwing enough resources at it. Will update example once I've had enough coffee. (Inhowfar would enumitem and cleveref not combine? cleveref hooks into a lower level than enumitem, you just have to tell it what the name for the counters is.) Feb 26, 2011 at 7:42
  • @ulrich-schwarz: you're right, I guess that works too. I thought an ideal combination of the two packages would have been to let the ref list option modify \cref via \crefname and \crefformat instead of \ref to keep that one only pointing to the counter (I think we can agree that there might be cases where it's handy to have only the counter to refer to; of course there are others, else we would not go through all this trouble...)
    – Jonathan
    Feb 26, 2011 at 11:31
1

For the record, whilst \crefname must be used in the preamble, the lower-level \crefformat et al. commands work fine even after \begin{document}. So your original solution could have worked if you'd used \crefformat instead of \crefname.

On the other hand, Ulrich's solution seems more elegant.

You must log in to answer this question.

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