2

Given some text, I would like to tag various passages as belonging to one or more categories, and produce a reverse list of such passages, itemized by their tags (see below, and see comments for 2nd version with addition of named tagging environment, allowing for multiple indices).


Ideally, a command such as \tag[tag1,tag2,...]{this is the text} would be used to associate a portion of the text with one or more tags.

As an example, let the first paragraph of Cervantes' Don Quixote (1605) be tagged as follows (contents are irrelevant).

\resettags[mytags]

En un lugar de \tag[place]{la Mancha}, de cuyo nombre no quiero acordarme, no ha mucho tiempo que vivía un hidalgo de los de \tag[weapon]{lanza} en astillero, \tag[weapon]{adarga} antigua, \tag[animal]{rocín} flaco y \tag[animal]{galgo corredor}. Una \tag[food]{olla} de algo más \tag[food,animal]{vaca} que \tag[food,animal]{carnero}, \tag[food]{salpicón} las más noches, \tag[food]{duelos y quebrantos} los \tag[day]{sábados}, \tag[food]{lantejas} los \tag[day]{viernes}, algún \tag[food,animal]{palomino} de añadidura los \tag[day]{domingos}, consumían las tres partes de su hacienda.

\maketags[mytags]


The \maketags[<tag index>] command would compute and insert the reverse tag index. Given the above I would get:

Tag Index

animal: carnero; galgo; palomino; rocín; vaca.

day: domingos; sábados; viernes.

food: duelos y quebrantos; lantejas; olla; palomino; salpicon; vaca; carnero.

place: la Mancha.

weapon: adarga; lanza.


Note: tags and entries under each tag are ordered alphabetically, and the separator is ";". These could be default options, to change one could do \maketags[name=mytags,sort=false,sep=|]


Update: the design has slightly evolved/converged to the usage shown below:

\usepackage[tagsmult] % loads .sty

\begin{document}

... 

\begin{tagging}[txt1]
... \tag{tag1}{text snippet} ...
... \tag{tag1,tag2}{text snippet} ...
... \tag{tag3}{text snippet} ...
\end{tagging}

... 

\begin{tagging}[txt2]
... tagged text 2
\end{tagging}

...

\begin{tagging}[txt1] % note reuse of 'txt1'
... tagged text 3
\end{tagging}

...

\section*{Txt1 Index}

\maketags[txt1] % combines two 'txt1' blocks

\section*{Txt2 Index}

\maketags[txt2]

\end{document}

2 Answers 2

3

Here's an answer using LaTeX3 and expl3. I've changed some of your syntax to be more standard for LaTeX, though it could be changed back pretty easily. Also note that this conflicts with amsmath tag, so you might want to change the name if you want to use both.

\ExplSyntaxOn

% Create a new command with no arguments that creates and clears the tag index
\NewDocumentCommand{\resettags}{}{%
  \prop_gclear_new:N \tag_index_prop
}

% Create a new command with two (m)andatory arguments, the tag(s) and the text
\NewDocumentCommand{\tag}{mm}{%
  % Preserve the text
  #2
  % Split the tags up into a temporary sequence
  \seq_set_split:Nnn \l_tmpa_seq {,} {#1}
  % Loop through the tags
  \seq_map_inline:Nn \l_tmpa_seq {%
    % Check if the tag has been created yet
    \prop_get:NnNTF \tag_index_prop {##1} \l_tmpa_tl {% 
      % If already created
      % Add the text to the tag index as a comma separated value
      \prop_put:Nnx \tag_index_prop {##1} { \exp_not:V \l_tmpa_tl,#2 }
    } {%
      % If not yet created
      % Set the tag's value in the index to the text
      \prop_put:Nnn \tag_index_prop {##1} { #2 }
    }
  }
}

% Create a new command with one (o)ptional argument, the options for the command
\NewDocumentCommand{\maketags}{O{}}{%
  % Define options for make tags
  \keys_define:nn {make_tags_module}
  {
    sort .bool_set:N = \tag_sort_keys_bool,
    sort .initial:n = true,
    sep .tl_set:N = \tag_word_sep_tl,
    sep .initial:n = ;\ ,
  }
  % Read in options
  \keys_set:nn {make_tags_module} { #1 }

  % Turn the keys of the tag index into a seq for sorting
  \seq_clear:N \l_tmpa_seq
  \prop_map_inline:Nn \tag_index_prop {%
    \seq_put_right:Nn \l_tmpa_seq {##1}
  }

  % Check if should sort
  \bool_if:NTF \tag_sort_keys_bool {%
    % Sort the tags in alphabetical order
    \seq_sort:Nn \l_tmpa_seq {%
      % You can use \str_compare:nNnTF here, but it wasn't in my distribution
      \int_compare:nTF {\pdfstrcmp{##1}{##2} < 0} 
      { \sort_return_same: } 
      { \sort_return_swapped: }
    }
  } {}

  % Print each tag
  \seq_map_inline:Nn \l_tmpa_seq {%
    % Get the words
    \prop_get:NnN \tag_index_prop {##1} \l_tmpa_tl
    % Split the words by comma
    \seq_set_split:NnV \l_tmpb_seq {,} \l_tmpa_tl
    % Check if should sort
    \bool_if:NTF \tag_sort_keys_bool {%
      % Sort the words in alphabetical order
      \seq_sort:Nn \l_tmpb_seq {%
        \int_compare:nTF {\pdfstrcmp{####1}{####2} < 0} 
        { \sort_return_same: } 
        { \sort_return_swapped: }
      }
    } {}
    % Print
    \noindent\textbf{##1}:\ \seq_use:Nn \l_tmpb_seq {\tag_word_sep_tl} \par
  }
}

\ExplSyntaxOff

It would be used like so:

\begin{document}

\resettags

En un lugar de \tag{place}{la Mancha}, de cuyo nombre no quiero acordarme, no ha mucho tiempo que vivía un hidalgo de los de \tag{weapon}{lanza} en astillero, \tag{weapon}{adarga} antigua, \tag{animal}{rocín} flaco y \tag{animal}{galgo} corredor. Una \tag{food}{olla} de algo más \tag{food,animal}{vaca} que \tag{food,animal}{carnero}, \tag{food}{salpicón} las más noches, \tag{food}{duelos y quebrantos} los \tag{day}{sábados}, \tag{food}{lantejas} los \tag{day}{viernes}, algún \tag{food,animal}{palomino} de añadidura los \tag{day}{domingos}, consumían las tres partes de su hacienda.

\maketags
% or
\maketags[sep={,}\ ]
% or
\maketags[sort=false, sep=-]

\end{document}

Which renders more or less exactly like you showed in your question. Hope this helps!


Edit: Added support for named indices, and made it into an environment, as discussed in the comments. Edit 2: Fixed naming and made tags merge.

\ExplSyntaxOn

% Create a new command with no arguments that creates and clears the tag index
\NewDocumentEnvironment{tagged}{o}{%
  \prop_gclear_new:N \g__tag_index_prop
}{%
  \IfNoValueTF{#1}{}{%
    \prop_if_exist:cTF {g__tag_index_#1_prop} {
      % If it already exists, merge
      \prop_map_inline:Nn \g__tag_index_prop {
        \prop_get:cnNTF {g__tag_index_#1_prop} { ##1 } \l_tmpa_tl {
          % Merge
          \prop_gput:cno {g__tag_index_#1_prop} { \l_tmpa_tl, ##2 }
        } {
          % Add
          \prop_gput:cnn {g__tag_index_#1_prop} { ##1 } { ##2 }
        }
      }
    } {
      % If it doesn't exist, just copy everything over
      \prop_new:c {g__tag_index_#1_prop}
      \prop_gset_eq:cN {g__tag_index_#1_prop} \g__tag_index_prop
    }
  }
}

% Create a new command with two (m)andatory arguments, the tag(s) and the text
\NewDocumentCommand{\tag}{mm}{%
  % Preserve the text
  #2
  % Split the tags up into a temporary sequence
  \seq_set_split:Nnn \l_tmpa_seq {,} {#1}
  % Loop through the tags
  \seq_map_inline:Nn \l_tmpa_seq {%
    % Check if the tag has been created yet
    \prop_get:NnNTF \g__tag_index_prop {##1} \l_tmpa_tl {% 
      % If already created
      % Add the text to the tag index as a comma separated value
      \prop_gput:Nno \g__tag_index_prop {##1} { \l_tmpa_tl, #2 }
    } {%
      % If not yet created
      % Set the tag's value in the index to the text
      \prop_gput:Nnn \g__tag_index_prop {##1} { #2 }
    }
  }
}

% Create a new command with one (o)ptional argument, the options for the command
\NewDocumentCommand{\maketags}{O{}}{%
  % Define options for make tags
  \keys_define:nn {make_tags_module}
  {
    name .tl_set:N = \tag_name_tl,
    name .initial:n =,
    sort .bool_set:N = \tag_sort_bool,
    sort .initial:n = true,
    sep .tl_set:N = \tag_word_sep_tl,
    sep .initial:n = ;\ ,
  }
  % Read in options
  \keys_set:nn {make_tags_module} { #1 }

  \tl_if_empty:NTF \tag_name_tl {
    \prop_set_eq:NN \l_tmpa_prop \g__tag_index_prop  
  } {
    \prop_set_eq:Nc \l_tmpa_prop {g__tag_index_ \tag_name_tl _prop}
  }

  % Turn the keys of the tag index into a seq for sorting
  \seq_clear:N \l_tmpa_seq
  \prop_map_inline:Nn \l_tmpa_prop {%
    \seq_put_right:Nn \l_tmpa_seq {##1}
  }

  % Check if should sort
  \bool_if:NTF \tag_sort_bool {%
    % Sort the tags in alphabetical order
    \seq_sort:Nn \l_tmpa_seq {%
      % You can use \str_compare:nNnTF here, but it wasn't in my distribution
      \int_compare:nTF {\pdfstrcmp{##1}{##2} < 0} 
      { \sort_return_same: } 
      { \sort_return_swapped: }
    }
  } {}

  % Print each tag
  \seq_map_inline:Nn \l_tmpa_seq {%
    % Get the words
    \prop_get:NnN \l_tmpa_prop {##1} \l_tmpa_tl
    % Split the words by comma
    \seq_set_split:NnV \l_tmpb_seq {,} \l_tmpa_tl
    % Check if should sort
    \bool_if:NTF \tag_sort_bool {%
      % Sort the words in alphabetical order
      \seq_sort:Nn \l_tmpb_seq {%
        \int_compare:nTF {\pdfstrcmp{####1}{####2} < 0} 
        { \sort_return_same: } 
        { \sort_return_swapped: }
      }
    } {}
    % Print
    \noindent\textbf{##1}:\ \seq_use:Nn \l_tmpb_seq {\tag_word_sep_tl} \par
  }
}

\ExplSyntaxOff

\begin{document}

\begin{tagged}[sec1]
  En un lugar de \tag{place}{la Mancha}, de cuyo nombre no quiero acordarme, no ha mucho tiempo que vivía un hidalgo de los de \tag{weapon}{lanza} en astillero, \tag{weapon}{adarga} antigua, \tag{animal}{rocín} flaco y \tag{animal}{galgo} corredor. Una \tag{food}{olla} de algo más \tag{food,animal}{vaca} que \tag{food,animal}{carnero}, \tag{food}{salpicón} las más noches, \tag{food}{duelos y quebrantos} los \tag{day}{sábados}, \tag{food}{lantejas} los \tag{day}{viernes}, algún \tag{food,animal}{palomino} de añadidura los \tag{day}{domingos}, consumían las tres partes de su hacienda.
\end{tagged}

\begin{tagged}[sec2]
  \tag{test}{Lorem} ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis \tag{test2}{aute} irure dolor in reprehenderit in voluptate velit esse cillum dolore eu \tag{test}{fugiat} nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
\end{tagged}

\begin{tagged}[sec1]
  \tag{test}{Lorem} ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis \tag{test2}{aute} irure dolor in reprehenderit in voluptate velit esse cillum dolore eu \tag{test}{fugiat} nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
\end{tagged}

% by default prints the previous if no name is given
\maketags
%or
\maketags[name=sec1]
% or
\maketags[name=sec2]

\end{document}
12
  • Kevin, can't wait to try it out. You are a godsend!
    – Dan Reznik
    May 12 at 11:18
  • I have one crucial request: can you make the \resettags[name] and \maketags[name] accept an optional index name, allowing several tag indices in the same document. E.g.: \resettags[hello1] ... first tagged text ... \resettags[hello2] ... tagged text 2 ... % two reverse indices here \maketags[hello1] \maketags[hello2] Another option (perhaps more efficient) is to surround the tagged region with \begin{tagging}[name] ... \end{tagging}
    – Dan Reznik
    May 12 at 11:31
  • @DanReznik I'll see what I can do, but if you want to try to add that yourself, it would basically just be turning the \resettags macro into the tagging environment, and having it save the tag index into a specifically named variable at the end (maybe \tag_index_#1_prop), which could then be selected in maketags. May 12 at 16:33
  • Thanks. I am very grateful for your help. I have zero training in the latex programming language.
    – Dan Reznik
    May 12 at 16:57
  • @DanReznik I added support for named tag indices in the edit above, hope it helps! Btw if this answered your question, I'd appreciate it if you could mark my answer as the solution. Have a nice day! May 12 at 18:41
1

In OpTeX, we can solve this task by following macros:

\def\tags{}
\def\tag[#1]#2{\foreach#1,\do##1,{\unskip
   \ifcsname tag:##1\endcsname \ea\addto\csname tag:##1\endcsname{{#2}}%
   \else \sdef{tag:##1}{{#2}}\ea\addto\ea\tags\ea{\csname tag:##1\endcsname}%
   \fi
}}
\def\sort #1\endsort {\def\slist{}\tmpnum=0
   \foreach #1\do
      {\ifx.##1\else \incr\tmpnum 
       \ea\addto\ea\slist\ea{\csname+##1\endcsname}%
       \sdef{+##1}{##1}\fi
      }
   \_dosorting\slist
   \ea\foreach \slist \do{\decr\tmpnum ##1\ifnum\tmpnum=0.\else; \fi}%
}
\def\maketags{%
   \_dosorting\tags
   \ea \xargs \ea \newitem \tags ;
}
\def\newitem #1{\par \noindent \ea\tagname\string#1; 
   \ea\sort #1.\endsort
}
\def\tagname#1:#2;{{\bf#2}:}

%% test:

En un lugar de \tag[place]{la Mancha}, de cuyo nombre no quiero acordarme, no ha mucho tiempo que vivía un hidalgo de los de \tag[weapon]{lanza} en astillero, \tag[weapon]{adarga} antigua, \tag[animal]{rocín} flaco y \tag[animal]{galgo corredor}. Una \tag[food]{olla} de algo más \tag[food,animal]{vaca} que \tag[food,animal]{carnero}, \tag[food]{salpicón} las más noches, \tag[food]{duelos y quebrantos} los \tag[day]{sábados},
\tag[food]{lantejas} los \tag[day]{viernes}, algún
\tag[food,animal]{palomino} de añadidura los \tag[day]{domingos}, consumían las tres partes de su hacienda.

\bigskip

\maketags

\bye
3
  • very interesting too, let me try it.
    – Dan Reznik
    May 14 at 11:46
  • Kevin is it possible to add an extra "lookup table" argument to \maketags[] so that the tags would be replaced by the table when spat out? e.g., assume I have tags "place", and "food", as above, but I issued: \maketags[name=blabla1,lookup={place=loc,food=eat}] this would cause the header to the 'place:' list to be printed as 'loc:' (still in boldface) and that of the 'food:" tag to be printed as 'eat:', with all others preserved. is this feasible?
    – Dan Reznik
    2 days ago
  • @DanReznik Please, be more specific. The alphabetic sorting must be done by what? By the original words or by the substitutions?
    – wipet
    2 days ago

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.