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



I would like to extract the chapters and urls.

Example B

\chapter{A chap}
\marginnote{A note}

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. Aug 16, 2019 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, 2019 at 14:53
  • I intend to parse the csv file with r/python to ensure style guidelines are met. E.g. title case Aug 16, 2019 at 16:00

2 Answers 2


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



\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} }

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

\AtEndDocument { \iow_close:N \l_csgillespie_iow }


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




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


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;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, 2019 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, 2019 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, 2019 at 10:41
  • Let us continue this discussion in chat. Aug 17, 2019 at 11:47

The extract package would allow you to do that:


The documentation in the readme there (extract.pdf) is quite good.

  • Thanks. I'll check that out. Mar 17, 2020 at 11:32

You must log in to answer this question.

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