27

I am writing a document which will contain many small (approximately a page) snippets of text, potentially organized in sections or subsections. To each such section, I would like to attach several "tags" from a predefined list. At a different place in the document, I would like to make a list of all tags and the sections which are associated with each.

I have an idea how to make a very primitive tagging system if the tags list occurs in the end of the document (e.g. I can successively redefine a command adding text to it; later I can place this command to list the contents). Does anyone know a better solution? Ideally I would like to organize my tags in the beginning of the document, after the table of contents. I feel there might be a package which does this but I haven't been able to find one.

4
  • Hi, Nasko! Welcome to TeX.SX.
    – Ryan Reich
    Dec 12, 2011 at 0:34
  • Isn't this same as an index?
    – Aditya
    Dec 13, 2011 at 13:15
  • @Aditya: I sort of agree, but I went with a ToC because I forget how to make indices :) Also, it seemed to me that the main job of indexing tools is to sort and collate the many index entries, and here, the entries (that is, tagged sections) should appear in document order but with repetitions that are not collated. Maybe another perspective: ToC's are "many to one" maps, combining topics under broad headings; indices are "one to many" maps, giving all the topics appearing under a heading (really, on a page). He wants a "many to many" map. I thought it closer to a ToC than an index.
    – Ryan Reich
    Dec 14, 2011 at 3:42
  • Related tex.stackexchange.com/questions/250024/…
    – user30471
    Aug 22, 2018 at 6:26

4 Answers 4

17

You can do this by placing your tags into a "table of contents". I have adopted egreg's brilliant suggestion to put every tag in one ToC file and simply reread it repeatedly, deactivating the "wrong" tags each time. This mandates that I \input the ToC file manually, which circumvents the usual mechanism \listofX (here, X = tags) that actually creates this file (or overwrites it, which is why I can't use it more than once), so I have included the fairly superfluous feature that a plain list of all tags is then printed at the bottom (below the lists of their references) using this macro, so that everything is properly set up.

I recently read an answer (I believe by Ahmed Musa) that used the {filecontents} environment to build an inline package, and since this is obviously the right way of presenting such an example, I am using it here too.

\documentclass{article}
\usepackage{filecontents}

% Here's the package file
\begin{filecontents*}{tagstoc.sty}
\usepackage{tocloft,etoolbox}

% Declare the master TOC.  This will contain:
% * A list of tags
% * A sequence of entries where tags are referenced
% and it will be used multiple times to generate lists of tag usage
\newlistof{tags}{tags}{All tags}
\renewcommand*\cfttagstitlefont{\Large\bfseries}% For example

% Use this in the preamble to make a new tag.
\newcommand*\declaretag[1]{%
 \newlistentry[tags]{tag#1}{tags}{0}%
 \listadd\Tags{#1}%
 \addtocontents{tags}{(#1) }%
}
% This is only true when printing the list of tags
\newif\ifprintingtags
% This makes sure that the list of tags is not printed most of the time...
\addtocontents{tags}{\protect\ifprintingtags}
% ...because it wraps the entire TOC from the preamble
\AtBeginDocument{\addtocontents{tags}{\protect\fi}}

% This is a rather inefficient way to selectively print particular tags.
% Presumably, I should just pop each tag from the list as I go,
% but etoolbox doesn't seem to handle stacks.  Probably I'm missing something.
\newcommand*\deactivatetag[1]{%
 \expandafter\let\csname l@tag#1\endcsname=\@gobbletwo
}
\newcommand*\activatetag[1]{%
 \forlistloop\deactivatetag\Tags
 \expandafter\let\csname l@tag#1\endcsname=\l@section
}

% These are hooks for the user
\providecommand*\currenttag{}
\providecommand*\listoftagstitle{}
% This prints all the references to a particular tag.
% Effectively, it's a partial ToC for that tag.
\newcommand*\dotag[1]{%
 \renewcommand*\currenttag{#1}%
 \begingroup
  \activatetag{#1}%
  \section*{\listoftagstitle}%
  \@input{\jobname.tags}%
 \endgroup
}%
% This prints the snippets, placed (with repeats) beneath their tags.
% It also prints a list of defined tags, more or less as an excuse
% to have \listoftags handle the .tags auxiliary ToC file.
\newcommand*\snippetsbytag{%
 \forlistloop\dotag\Tags
 \activatetag{}
 \printingtagstrue
 \listoftags
}

% This is how to proclaim a snippet, which is what gets tagged.
\newcounter{snippet}
\providecommand*\snippetname{}
\newcommand*\snippettitle{\thesnippet. \snippetname}
\newcommand*\snippet[1]{%
 \refstepcounter{snippet}%
 \renewcommand*\snippetname{#1}%
 \noindent{\Large\bfseries\snippettitle.}%
}

% In case of no hyperref
\providecommand\phantomsection{}
% This is how you place a tag beneath a snippet.
\newcommand*\placetag[1]{%
 \phantomsection% In case of hyperref
 \addcontentsline{tags}{tag#1}{\snippettitle}%
 (tag~#1)%
}
\end{filecontents*}

\usepackage{tagstoc}

\renewcommand*\listoftagstitle{Snippets tagged with \currenttag}
% Preferred way to declare tags.  They will be printed in this order.
\forcsvlist\declaretag{A,B}

\begin{document}
 \snippetsbytag
 \newpage

 \snippet{First snippet}
 \placetag{A}

 \newpage

 \snippet{Second snippet}
 \placetag{A} \placetag{B}
\end{document}
4
  • in case you don't get time to avoid using too many writes, you can use my morewrites package, whose aim is to silently do all the non-synchronous work inthe background. --- just realized my version number is completely wrong... I haven't edited the documentation 2000 times yet :). Dec 12, 2011 at 0:08
  • You could use only one "list-of" which is then read in multiple times, by redefining all the \l@tag... commands to no-ops except for the relevant one. Or even read in once, accumulating information and then spitting it out.
    – egreg
    Dec 12, 2011 at 0:11
  • @Ryan Reich -- nice solution, a bit different that what I would do (if I get time later on I will post an alternative). filecontents is very useful, see usage tex.stackexchange.com/questions/33894/…. You can also use an input immediately after it to avoid getting an error on the first run.
    – yannisl
    Dec 12, 2011 at 9:48
  • Would it be possible to somehow use this approach to tag sections instead of snippets? I cannot make it work for normal LaTeX sections.. Feb 11, 2018 at 13:17
9

Just to present an alternative way to Ryan's fine solution.

This is the sort of problem, where TeX beats Python hands down. TeX provides a method to create control sequences automatically by using \csname...\endcsname. We leverage this to create a macro for each individual tag that will hold the list of sections that each tag contains.

I have assumed that you referring to tags in a similar fashion to those used in blogs on the web. We hold the tags in a list:

  \tags{Mathematics,Biology,...,}

Each tag will have its own list, which will hold the section numbers, tagged with this particular tag,

   \Art{1,5,6,7,8}

We define macros to tag a section in a similar fashion to \index by calling it as \tag{tag name}.

The MWE is shown below:

\documentclass{article}
\usepackage{lstdoc}
\makeatletter
\def\tags{Physics,Computer Science,Chemistry,}
%% sorts the tags for later on
\def\tags@sorted{\lst@BubbleSort\tags}
\tags@sorted
%% we automatically create macros for each tag
 \def\macrofy@#1\@nil{%
      \expandafter\def\csname#1\endcsname{}
  }
\def\addtags#1{\g@addto@macro\tags{#1,}
   \tags@sorted
   \macrofy@@
}
%% Make macros to hold the lists for each tag
%% 
\def\macrofy@@{\@for\next:=\tags\do{%
    \expandafter\macrofy@\next\@nil
}}
\macrofy@@

\def\addtotag#1\@nil#2{%
   \expandafter\ifx\csname#2\endcsname\@empty
        \expandafter\g@addto@macro\csname#2\endcsname{#1}
    \else
        \expandafter\g@addto@macro\csname#2\endcsname{,#1}
    \fi
}
\def\tag#1{%
   \expandafter\addtotag\the\c@section\@nil{#1}
}
\def\thetags{%
%% we now print the tags or save them to a file
  \section*{The Tags}  
  \@for\next:=\tags\do{%
     \ifx\next\@empty\else\next: See Section(s) \@nameuse{\next}\par\fi
  }
}
\parindent0pt
\begin{document}
% add some tags
   \addtags{Mathematics,Art}
% test to see everything ok and list is sorted   
   \tags
% example text    
   \section{Einstein}
      \tag{Physics}\tag{Mathematics}
   \section{D E Knuth}
      \tag{Computer Science}\tag{Art}
    \section{Riemann}
       \tag{Mathematics}
   \section{Lamport}
      \tag{Computer Science}
   \section{Botticelli}
      \tag{Art}
   \section{Avogadro}
      \tag{Chemistry}
%% prints the tags with the sections
\thetags
\end{document}

We define a command to print the tags (which we keep sorted) as \thetags. The lists are kept in memory and printed at the end. If we want to print them at the beginning of the document then we need to store them in a file, which we import. For brevity I omitted this as is trivial, and wanted to keep the code short and simple to read. It produces output as:

enter image description here

1
  • Nice solution. How can I expand this to also show the subsection number.
    – fsociety
    Sep 4, 2015 at 17:46
9

I don't know what egreg will make of the 16 hash characters in one of the \do's in this solution, but the aim was to use and as the last conjunction of section numbers.

\documentclass{article}
\usepackage{catoptions}[2011/12/07]
\makeatletter
\new@def*\tag@hook{}
\robust@def*\tags#1{%
  \iflacus#1\dolacus\else
    \ifnum\c@section=\z@pt\else
      \docommalist{#1}{%
        \oifinsetTF{##1}\tag@hook{%
          \def\reserved@a####1##1####2####3\@nil{%
            \xifinsetTF{\detokenize\expandafter{\expandafter{\the\c@section}}}%
              {\detokenize{####2}}{}{%
              \xdef\tag@hook{####1;##1{####2{\the\c@section}};####3}%
            }%
          }%
          \expandafter\reserved@a\tag@hook\@nil
        }{%
          \xdef\tag@hook{\cptliststack;\tag@hook##1{{\the\c@section}}}%
        }%
      }%
    \fi
  \fi
}
\def\ignoretags#1{%
  \cptfiltermergecsv\tag@ignore{#1}\nofilter
}
\robust@def*\printtags{%
  \ifx\protect\@typeset@protect
    \def\csv@do##1{%
      \defpass\reserveda####1####{%
        \defpass\reserveda########1\@nil{%
          \oifinset@sp@TF,{####1}\tag@ignore{}{%
            \def\reservedb{########1}%
            \cptnumbersort\reservedb<%
            \cptexpandbracenext\tag@formatsecnos\reservedb
            \tagnameformat{####1}: %
            See section\ifnum\reservedc=\@ne\else s\fi~%
            \def\do################1{%
              \ifnum\indrisnr>\@ne
                \iflastindris\@space and\@space\else,\@space\fi
              \fi
              ################1%
            }%
            \indrisloop*\reservedb\do
            \tagseparator
          }%
        }%
      }%
      ##1\@nil
    }%
    \csv@@parse*[;]\tag@hook
  \else
    \@latex@error{Wrong location of '\string\printtags'}\@ehc
  \fi
}
\robust@def*\tag@formatsecnos#1{%
  \begingroup
  \@tempcnta\z@pt
  \def\reservedb{}%
  \def\do##1##2\@nil{%
    \ifblankTF{##1}{%
      \do##2\@nil
    }{%
      \advance\@tempcnta\@ne
      \edef\reservedb{\cptliststack,\reservedb##1}%
      \ifnot@nil{##2}{\do##2\@nil}%
    }%
  }%
  \do#1{\@nil}\@nil
  \cptexpanded{\endgroup
    \def\noexpand\reservedb{\reservedb}%
    \def\noexpand\reservedc{\number\@tempcnta}%
  }%
}
\newletcsset{\tag=\tags,\noprinttags=\ignoretags,\tagseparator=\newline,
  \tagnameformat=\textit}

\parindent0pt
\begin{document}
\tag{Physics}
\section{Einstein}
\tags{Physics, Mathematics, Physics}

\section{D E Knuth}
\tags{Computer Science, Art, Physics}

\section{Riemann}
\tags{Mathematics, Art}

\section{Lamport}
\tag{Computer Science}

\section{Botticelli}
\tags{Art, Physics}

\section{Avogadro}
\tags{Chemistry, Literature, Art}

\printtags
\end{document}

enter image description here

3
  • 1
    +1 for the eight # :)
    – egreg
    Dec 12, 2011 at 23:26
  • @egreg: Thanks. That was just to avoid another auxiliary command. It looks neater that way, at least for people like you.
    – Ahmed Musa
    Dec 13, 2011 at 19:27
  • How can I expand this to also show the subsection number.
    – fsociety
    Sep 4, 2015 at 19:38
1

Is hyperref's bookmark or the bookmark package of any help?

You must log in to answer this question.

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