# Best approach to detect whether hierarchy of sectioning commands is respected?

Is there a package that checks whether each subsection lies in a parent section, each subsubsection lies in a parent subsection, etc. and issues a warning or error otherwise? Not to my knowledge, but that would be a very handy feature for documents in which consistency of the document structure is paramount.

How would you detect that hierarchy of sectioning commands is not respected, e.g. that a subsection has no parent section?

My current approach would be to redefine all sectioning commands (but the top one) to check that the value of the counter associated with the sectioning command one level up is nonzero, as illustrated by the following MWE:

\documentclass{article}

\makeatletter
\long\def\subsection{%
\ifnum\value{section}=0\@latex@warning{subsection outside section}\else\fi
\@startsection {subsection}{2}{\z@ }{-3.25ex\@plus -1ex \@minus -.2ex}{1.5ex
\@plus .2ex}{\normalfont \large \bfseries }
}
\makeatother

\begin{document}
%\section{foo}
\subsection{bar}
\end{document}

However, this approach would not be effective if numbering of the sectioning command one level up (section, in the case of my MWE) was modified to start at 0. Can you think of a more robust approach?

Edit: hyperref does issue a warning if hierarchy is not respected (see Gonzalo's comment). However, users may choose to ignore them. How would issue errors rather than warnings?

• hyperref does something like this. This code \documentclass{article} \usepackage{hyperref} \begin{document} \section{Test} \subsubsection{Test} \end{document} produces Package hyperref Warning: Difference (2) between bookmark levels is greater (hyperref) than one, level fixed on input line 7. – Gonzalo Medina Jun 1 '13 at 0:25
• If you don't use starred versions, you should get a 0 if a higher hierarchy is missing. E.g.: 1.0.2, 0.1.1, etc. – Mario S. E. Jun 1 '13 at 0:39
• @MarioS.E. Yes, but that requires inspection by a human agent. I want it to be automated. – jub0bs Jun 1 '13 at 0:49
• @GonzaloMedina Warnings are good, but users of my style file may choose to ignore them. How would you return an error instead of a warning? – jub0bs Jun 1 '13 at 0:51
• How would you "[modify a section] to start at 0", rather than 1, say? – Werner Jun 1 '13 at 1:24

If the difference between the sectioning level corresponding to a command differs by more than one from the current level, we issue a warning. Just patch \@startsection to keep track of the current level and to do the check.

\documentclass{article}
\usepackage{etoolbox}

\makeatletter
\pretocmd{\@startsection}{\check@level{#2}}{}{}
\def\check@level#1{%
\ifnum\numexpr#1-\current@level\relax>\@ne
\@latex@warning{\@nameuse{level@#1} outside
\@nameuse{level@\number\numexpr#1-1\relax}}%
\fi
\gdef\current@level{#1}}
\@namedef{level@1}{section}
\@namedef{level@2}{subsection}
\@namedef{level@3}{subsubsection}
\@namedef{level@4}{paragraph}
\@namedef{level@5}{subparagraph}
\def\current@level{0}
\makeatother

\begin{document}
\section{A}
\subsection{B}
\subsubsection{C}
\section{D}
\subsubsection{E}
\end{document}

Here's one idea, using conditionals. Un-commenting any of the lines that are commented out will trigger an error:

\documentclass{article}

\newif\ifchecksection
\newif\ifchecksubsection
\newif\ifchecksubsubsection
\checksectionfalse
\checksubsectionfalse
\checksubsubsectionfalse

\makeatletter
\renewcommand\section{\@startsection {section}{1}{\z@}%
{-3.5ex \@plus -1ex \@minus -.2ex}%
{2.3ex \@plus.2ex}%
{\global\checksectiontrue\normalfont\Large\bfseries}}
\renewcommand\subsection{\@startsection{subsection}{2}{\z@}%
{-3.25ex\@plus -1ex \@minus -.2ex}%
{1.5ex \@plus .2ex}%
{\global\checksubsectiontrue%
\ifchecksection\else\@latex@error{Subsection without section}{}\fi%
\global\checksectionfalse\normalfont\large\bfseries}}
\renewcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}%
{-3.25ex\@plus -1ex \@minus -.2ex}%
{1.5ex \@plus .2ex}%
{\global\checksubsubsectiontrue%
\ifchecksubsection\else\@latex@error{Subsubsection without subsection}{}\fi%
\global\checksubsectionfalse\normalfont\normalsize\bfseries}}
\makeatother

\begin{document}

%\subsection{Test Subsection}
\section{Test Section One}
\subsection{Test Subsection One One}
\subsubsection{Test Subsubsection One One}
\section{Test Section Two}
\section{Test Section Three}
%\subsubsection{Test Subsubsection Two -- One}

\end{document}
• Your solution looks great. I'll test it more thoroughly after a good night's sleep, but I think you'll get my checkmark :) – jub0bs Jun 1 '13 at 1:53
• @Jubobs Thanks, but let's wait. I'm pretty sure some other solutions will be provided :-) – Gonzalo Medina Jun 1 '13 at 2:17
• As egreg's answer doesn't require any toggles, I'm going to accept it, but thanks again for your answer. – jub0bs Jun 1 '13 at 18:08

The following is an approach similar to that of Gonzalo's. The main idea is to tap into one of the subsidiary functions \@stpelt called whenever \stepcounter is called.

\documentclass{article}

\makeatletter
%\newif\if@newsection\@newsectiontrue % Not really needed
\newif\if@newsubsection\@newsubsectionfalse
\newif\if@newsubsubsection\@newsubsubsectionfalse
\newif\if@newparagraph\@newparagraphfalse
\let\old@stpelt\@stpelt
\renewcommand{\@stpelt}[1]{\old@stpelt{#1}\global\@nameuse{@new#1true}}%
\let\old@subsection\subsection
\renewcommand{\subsection}{% Check if allowed to use \subsection
\if@newsubsection\relax\@newsubsectionfalse\else\@latex@error{Subsection without section}{}\fi%
\old@subsection}% Continue with regular \subsection
\let\old@subsubsection\subsubsection
\renewcommand{\subsubsection}{% Check if allowed to use \subsubsection
\if@newsubsubsection\relax\@newsubsubsectionfalse\else\@latex@error{Subsubsection without subsection}{}\fi%
\old@subsubsection}% Continue with regular \subsubsection
\makeatletter

\begin{document}

\section{A section}
\subsection{A subsection}
\subsubsection{A subsubsection}

\end{document}

For each sectioning command <section> a condition \if@new<section> is created that is initially set to false (\@new<section>false). Then, whenever a counter <parent> is stepped, the counter <child> is reset as well as \@new<child>true. Then, a boolean check within the sectioning command makes sure it can be used, otherwise an error is thrown.

Caveats:

• The error-checking would hold, even if starred-variants of the sectioning commands are used. It's possible to remove this, but for now I don't know whether this is required.
• More sectioning commands require patching as in the above example, but depends on the \documentclass used and how deep the level of usage is. Again, this can be adapted if required.
• +1 Thanks. It works great. Since egreg's answer doesn't require \newif's I'm going to accept it, though. – jub0bs Jun 1 '13 at 18:12