114

Suppose I have a document with multiple include or input statements

\input{fileA}
\input{fileB}

etc.

Is there an easy way to generate a single .tex file where \input{fileA} is replaced by the actual content of fileA etc. without copying it manually?

0

10 Answers 10

110

You can use the following tools to do this. All of them are on CTAN but not all of them are part of either TeXLive or MikTeX, so you may need to manually install them. They need either Perl or a C compiler installed. Both should not be a problem with Linux but might be one under Windows or Mac. However IIRC TeXLive installs its own Perl interpreter.


latexpand Perl script:

Latexpand is a Perl script that simply replaces \input and \include commands with the content of the file input/included. The script does not deal with \includeonly commands.

Installation:

Simply download it from http://mirrors.ctan.org/support/latexpand/latexpand and run it. You need Perl installed however.

Usage:

    perl latexpand mainfile.tex > newfile.tex

flatex

A C program to flatten a LaTeX file into a single file, by explicitly including the files included by \include and \input commands. Also, if BibTeX is being used, then includes the bbl file into the resulting file. The result is therefore a stand-alone LaTeX file that can be emailed to a collaborator.

Installation:

Get the single C file and compile it, e.g. with cc flatex.c -o flatex.

Usage:

    flatex mainfile.tex > newfile.tex

flatten

A program to flatten a LaTeX root file by copying \input and \include files into the root file.

Installation:

Get the ZIP file from CTAN, unpack it and run make followed by make install. You need a C compiler and maybe flex for this. However it seems quite old and you might run in trouble because of it. I failed compiling it!

Usage:

    flatten mainfile.tex newfile.tex
15
  • 8
    I wanted a Python version of this for my own paper building toolchain so I created one: github.com/johnjosephhorton/flatex Commented May 12, 2012 at 16:18
  • 3
    @JohnHorton: Good work! You should consider uploading it to CTAN as well. Just get into contact with the author of the original flatten. I don't think he mind having the Python version in the same directory. Commented May 12, 2012 at 16:36
  • 10
    Meanwhile, latexpand seems to be part of TexLive (confirmed with v2012) and also of MikTex
    – Hotschke
    Commented Apr 4, 2013 at 8:48
  • 1
    I wonder if any of the scripts take care of the difference between \input and \include? Otherwise the new file might give different results as the original source
    – cgnieder
    Commented Jul 30, 2014 at 17:08
  • 2
    latexpand does make a difference between \input and \include. It does also take $TEXINPUTS into account in recent enough versions. I tried my best to reproduce exactly the same output, and tested it on several tricky testcases (using pdftotext and diff to check the result). If it does not do so properly, then please submit a bug report (I'm the author). Commented Jan 21, 2015 at 10:15
9

You need an installed Perl. Save it as buildFile.pl and make it executable or run it with perl buildFile.pl

eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' && eval 'exec perl -S $0 $argv:q'
  if 0;
#use strict;
# 
# Usage:
# buildFile.pl < main-file.tex > main-file.tot
#     
sub p_inc {
  $DateiName = shift;
    if ( open (my $datei, "$DateiName.tex") ) {
      print "%%%---------- open: ", $DateiName, "\n";
      while (<$datei>) {
        if (/^\s*\\include{\s+(\S+)/i) {
          my $include = $1;
          chomp($include);chop($include);
          print "%%%%%%%%% Springe nach ", $include, "\n";
          p_inc($include);
        } else { print unless /^\s*(#|$)/; }
      }
      print "%%%---------- close: ", $DateiName, "\n";
      close $datei;
    } else { print "%%%<===== Datei existiert nicht\n"; }
}
#
@zeilen = (<>);
for $zeile (@zeilen) {
  next if $zeile =~ /^\s*(%)/;
  if ($zeile =~ /^\s*\\include{\s*(\S+)/i) {
    my $include = $1;
    chomp($include);
    chop($include);
    print "%%%%%%%%%%% Springe nach ", $include, "\n";
    p_inc($include);
  } else { print $zeile; }
4
  • Can this work for \import as well ? Thanks.
    – Anusha
    Commented Oct 15, 2014 at 9:59
  • replace include by import and it should work. However, \import is not a TeX command
    – user2478
    Commented Oct 15, 2014 at 11:03
  • It is from the import package which is useful for including files with relative paths.
    – Anusha
    Commented Oct 15, 2014 at 11:16
  • Running on a Mac, I updated Perl at perl.org, but I'm having an error when I run perl buildFile.pl: Missing right curly or square bracket at buildFile.pl line 34, at end of line syntax error at buildFile.pl line 34, at EOF Execution of buildFile.pl aborted due to compilation errors. I don't know Perl, so I am unable to debug. Any thoughts? Commented Feb 12, 2023 at 8:06
9

I've recently discovered TexSoup, a Python module to parse Latex files inspired by BeatifulSoup. It makes really easy to work with .tex files in a very straightforward way. For instance it allows to target specific Latex commands such as \input{} in just few lines of codes.

Suppose you have a main.tex file in your folder which reads as follows:

\documentclass[12pt]{article}
\begin{document}

% ..other commands here..

\section{First part}
\input{file1}

\section{Second part}
\input{file2}

% ..other commands here..

\end{document}

If the files file1.tex and file2.tex are in the same folder just save the script at the bottom in a file named flatten.py and execute the command:

python flatten.py main.tex expanded.tex

to get the expanded file.

#!/usr/bin/env python3
import sys
from TexSoup import TexSoup

def soupify(file):
    """
    Load file in TexSoup object.
    """
    with open(file, 'r') as file_to_read:
        content = file_to_read.read()
    soup = TexSoup(content) 
    return soup

def flatten(soup):
    """
    Expand \input{} commands in a tex file.
    """
    while soup.input:
        soup.input.replace(flatten(soupify(soup.input.args[0]+'.tex')))
    return soup

if __name__ == "__main__":    
    file = sys.argv[1]
    expanded_tex = flatten(soupify(file))

    if len(sys.argv) > 2:
        output_file = sys.argv[2]
    else:
        output_file = 'output.tex'

    with open(output_file, 'w') as output:
        output.write(repr(expanded_tex))

    pass

Notice: this work for Python 3 only and you have to install TexSoup before (pip install TexSoup). If you have both Python 2 and 3 installed on the same machine you may need to launch the command using python3 instead of python as first word.

9

I developed FLaP, a python script which replaces \input, \include and other inclusion directives by the file they refer to while supporting \includeonly. In addition it move graphic files around and adjusts graphic inclusion directive (\includegraphics, includesvg and \overpic) accordingly. It supports the \graphicspath directive and SVG, EPS and PDF images.

3
  • It seems that this tool also tries to do something clever with \def, and this goes easily wrong. Commented Aug 8, 2017 at 21:09
  • This is correct, the processing of \def is under development, but unfortunately, I have little time to devote to FLaP these days. If you have a specific use case, I invite you to post an issue on the related GitHub page and we will look at it.
    – fchauvel
    Commented Aug 10, 2017 at 6:42
  • Is there a possibility to flatten a preamble that I have in a seperate .sty-file and include that in the main .tex file ? Commented Jul 6, 2022 at 14:04
6

I tried all the options mentioned in this post, but the original flatex works best for me. There appears to be a newer version, but I was unable to compile it as I am not familiar with multi-file C sources. If anyone could try it and let me know about the outcome that would be great.

The original flatex has an issue as it only creates a .flt (the flattened document), even when you specify a different .tex as the output. But that's not really an issue. I have created a batch file that sorts that out and allows integration with Subversion as well (see this post on my website http://www.jwe.cc/2012/02/workflow-with-subversion-and-latex/).

5

The question asks for a single TeX file, but the existing answers appear to address the case of \include and \input statements only. If those statements are generated dynamically by LaTeX commands, then static replacement by a script will miss inclusion of such files.

A solution that captures all files used is:

The resulting archive can then be used with one of the methods described in the other answers (e.g., latexpand, flatex, flatten, or FLaP) in order to generate a single LaTeX file, along the image and other non-LaTeX files included by that file.

(Thanks to this answer.)

4

I've extended a Python script to handle \include, \input, and \import hierarchically: master2single.py

USAGE: master2single.py masterfile.tex flattenfile.tex [-v] [-c]
        masterfile.tex      : Input file
        flattenfile.tex     : Flattened file
        -v                  : Verbose mode
        -c                  : Remove all comments
3

There is also texdirflatten which is a perl script.

This worked best for me. YMMV. There is also the consideration that perl is not installed by default on Windows machines.

3

An alternative is to use Pandoc. Not Pandoc's intended usage, but works more or less:

pandoc -s file_nested.tex -o file_flat.tex

This will convert all \input as well as tables etc to a flat file. When I tried, it did not convert my preamble correctly, so I had to copy paste that part. But all the text in the body of the file got flattened.

1
  • Without the -s the ouput is just the text without preamble, but anyway is constructed according to the default template. This can produce unexpected results as change a \chapter{foo} to a \hypertarget{baz}{\section{baz}\label{baz}} even if you have not hyperref in the source file, but this can be solved using a custom template.
    – Fran
    Commented Apr 22, 2023 at 10:38
0

Late to the party, but here's another python script (let's call it merge-tex.py) for the same thing. It could be shorter if there were no diagnostics, but it's not recursive, and doesn't do \include.

Use as python merge_tex.py main.tex merge.tex.

import sys
import re
from pathlib import Path

if len(sys.argv) < 3:
    raise ValueError("Expected two arguments")

in_file = Path(sys.argv[1])
out_file = Path(sys.argv[2])

# https://regex101.com/r/azkAmW/1
regex = re.compile(r"^([^%\\]*)(.*)\\input{(.*)}(.*)$")

with open(out_file, "w") as fout:
    with open(in_file, "r") as fin:
        print(f"Reading: {in_file}")
        for line in fin:
            match = regex.findall(line)
            if not match:
                fout.write(line)
            else:
                # regex groups
                start, comment, input_file, end = match[0]
                if comment:  # nothing to do
                    fout.write(line)
                else:
                    if start:
                        fout.write(start)
                    print(f"Merging: {input_file}.tex")
                    with open(Path(input_file + ".tex"), "r") as finput:
                        [fout.write(inp) for inp in finput]
                    if end:
                        fout.write(end)

print(f"Finished. Merged to: {out_file}")

You must log in to answer this question.

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