2

I would like to extract the contents of various latex commands, e.g.

  • section
  • chapter
  • caption
  • sidenote, etc

from a .tex file. These elements would eventually end up in a CSV file

chapter, chapter 1 title text
chapter, chapter 2 title text
figure, figure text

Does this require a regular expression or is this reinventing the wheel?


The .tex file is generated from a markdown document, via pandoc.


Example A

\documentclass{report}
\usepackage{hyperref}
\newcommand*\Quux{Pouet}

\begin{document}
\chapter[foo]{Foo}
\url{http://www.tex.org}
\end{document}

I would like to extract the chapters and urls.

Example B

\documentclass{tufte-book}
\begin{document}
\chapter{A chap}
\marginnote{A note}
\end{document}

I would like to extract the marginnote

  • 1
    One way would be to modify those commands so that they also generate the desired CSV file that you require. However, it would be useful to know what the ultimate goal with the CSV file is as there may already be hooks to achieve the final goal. – Peter Grill Aug 16 at 14:33
  • if you don't have nested braces/brackets then a regex should be fairly straight forward. if you do have nested braces/brackets, then some care will be needed. for example \chapter{my chapter \emph{text} goes here} – cmhughes Aug 16 at 14:53
  • I intend to parse the csv file with r/python to ensure style guidelines are met. E.g. title case – csgillespie Aug 16 at 16:00
2

You can use a file like this to instrument all sectioning commands (let's call it instrumenter.tex):

\RequirePackage{xparse}

\ExplSyntaxOn

\iow_new:N \l_csgillespie_iow

% #1: sectioning command name as a single token
% #2: character tokens representing the command name, without the backslash
\cs_new_protected:Npn \csgillespie_instrument_sec_cmd:Nn #1#2
  {
    % Save the original sectioning command
    \cs_gset_eq:cN { g_csgillespie_#2_orig: } #1

    \RenewDocumentCommand #1 { s O{##3} m }
      {
        \iow_now:Nn \l_csgillespie_iow { #2 ; ##2 ; ##3 }

        \IfBooleanTF {##1}
          { \use:c { g_csgillespie_#2_orig: } * {##3} }
          { \use:c { g_csgillespie_#2_orig: } [##2] {##3} }
      }
  }

\cs_generate_variant:Nn \csgillespie_instrument_sec_cmd:Nn { c }

% #1: character tokens representing the command name, without the backslash
\cs_new_protected:Npn \csgillespie_instrument_sec_cmd_ifexists:n #1
  {
    \cs_if_exist:cT {#1}
      { \csgillespie_instrument_sec_cmd:cn {#1} {#1} }
  }

\AtBeginDocument
  {
    \iow_open:Nn \l_csgillespie_iow { sectioning.csv }
    \clist_map_inline:nn
      {
        part, chapter, section, subsection, subsubsection, paragraph,
        subparagraph
      }
      { \csgillespie_instrument_sec_cmd_ifexists:n {#1} }
  }

\AtEndDocument { \iow_close:N \l_csgillespie_iow }

\ExplSyntaxOff
\endinput

Then, suppose that you want to check a file called tested.tex:

\documentclass{report}

\newcommand*\Quux{Pouet}

\begin{document}

  \chapter[foo]{Foo}
  \chapter{Bar \emph{Baz!}}
  \chapter*{\Quux !}
  \section{A \emph{section}!}
  \section[Short title]{Another section}

\end{document}

All you have to do is to put instrumenter.tex in a location present in TEXINPUTS (possibly the same directory as tested.tex), then run:

latex '\input instrumenter \input tested'

(single-quotes are for the shell here, adapt to your shell). You'll obtain a file called sectioning.csv in the same directory as tested.tex that looks like this for this example:

chapter;foo;Foo
chapter;Bar \emph {Baz!};Bar \emph {Baz!}
chapter;\Quux !;\Quux !
section;A \emph {section}!;A \emph {section}!
section;Short title;Another section

You may want to add double quotes to the output (or whatever you need for the CSV format) in case some titles contain the semi-colon separator (;). Just add them in the line that reads:

\iow_now:Nn \l_csgillespie_iow { #2 ; ##2 ; ##3 }

Paragraphs in arguments

In case you use this code to wrap other commands and these commands can legally have \par tokens (e.g., blank lines) in their arguments, then add a + in front of the corresponding arguments, like this:

\RenewDocumentCommand #1 { s +O{##3} +m }

The O{##3} corresponds to the optional argument of the wrapped command (defaulting to the value of the mandatory argument), the m to its mandatory argument. The + shouldn't be useful for standard sectioning commands, but might be if you use this system to log arguments of other commands.

  • The example from your previous comment works fine for me, I get: chapter;A chap;A chap. Regarding your first comment, please submit a minimal working example. My code relies on the standard LaTeX syntax for the sectioning commands. Of course, if you happen to use a class that redefines these commands to have a non-standard syntax, the code will not work without adaptations to the actually-used syntax. I don't know if this is the case here (no experience with tufte, and a quick look at texdoc tufte-latex didn't tell me anything interesting regarding this)—just thought it worth mentioning. – frougon Aug 17 at 9:22
  • (My file is called instrumenter.tex.) I'm afraid both of your examples work fine here. The sectioning.csv file obtained from example A contains one line (chapter;foo;Foo) and the one obtained from example B also contains one line, which is chapter;A chap;A chap. It's all as expected. What kind of errors do you obtain? It is very, very strange that \url could cause an error, because my code doesn't modify it in any way. Did you test the examples as is, in an otherwise empty directory? – frougon Aug 17 at 10:35
  • Note that you can use instrumenter.tex from a parent dir, as in pdflatex '\input ../instrumenter \input example-A' or latex '\input ../instrumenter \input example-B' (I run the first command from a folder containing only example-A.tex and the second one from a folder containing only example-B.tex). The slash separators work on Linux (TL 2019), at least. – frougon Aug 17 at 10:41
  • Let us continue this discussion in chat. – csgillespie Aug 17 at 11:47

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.