1#!/usr/bin/python
2
3import xml.etree.ElementTree as etree
4import os
5import shutil
6import re
7from subprocess import call
8
9FG_ROOT="../"
10
11def tex_escape(text):
12    """
13        :param text: a plain text message
14        :return: the message escaped to appear correctly in LaTeX
15    """
16    conv = {
17        '&': r'\&',
18        '%': r'\%',
19        '$': r'\$',
20        '#': r'\#',
21        '_': r'\_',
22        '{': r'\{',
23        '}': r'\}',
24        '~': r'\textasciitilde{}',
25        '^': r'\^{}',
26        '\\': r'\textbackslash{}',
27        '<': r'\textless',
28        '>': r'\textgreater',
29    }
30    regex = re.compile('|'.join(re.escape(unicode(key)) for key in sorted(conv.keys(), key = lambda item: - len(item))))
31    return regex.sub(lambda match: conv[match.group()], text)
32
33def generate_airplane_latex(source):
34    """
35        Generates the LaTeX files and directory structure for the procedures
36    """
37    root = os.path.join("procedures", source['name'])
38    checklists_path = os.path.join(root, "checklists")
39    if(not os.path.isdir(root)):
40        os.mkdir(root)
41    if(not os.path.isdir(checklists_path)):
42        os.mkdir(checklists_path)
43
44    # Write some warning notes
45    preamble = open(os.path.join(root, "preamble.tex"), "w")
46
47    preamble.write("\\section{Preamble}")
48    preamble.write("This procedure list should be considered incomplete and it is not intended for real world use. It is automatically generated documentation intended for FlightGear flight simulator.")
49
50    documentation_tex = open(os.path.join(root, source['dir'] + "_documentation.tex"), "w")
51    documentation_tex.write("\\documentclass{article}\n")
52    documentation_tex.write("\\usepackage{hyperref}\n\usepackage{graphicx}\n"
53            "\\usepackage{fancyhdr}\\pagestyle{fancy}\n\\lfoot{Intended for FlightGear}\\rfoot{Not for real world use!}")
54    documentation_tex.write("\\title{"+source['name']+" documentation}\n")
55    documentation_tex.write("\\author{FlightGear team}\n")
56    documentation_tex.write("\\renewcommand{\\familydefault}{\\sfdefault}")
57    documentation_tex.write("\\begin{document}\n")
58    documentation_tex.write("\\maketitle\n")
59
60    checklist_tex = open(os.path.join(root, source['dir'] + "_checklist.tex"), "w")
61    checklist_tex.write("\\documentclass{article}\n")
62    checklist_tex.write("\\usepackage{fancyhdr}\\pagestyle{fancy}\n\\lfoot{Intended for FlightGear}\\rfoot{Not for real world use!}")
63    checklist_tex.write("\\begin{document}\n")
64
65    # If there's a thumbnail available, copy it into the documentation and include it
66    # just before the beginning of the document, just after the title.
67    if(source['thumbnail']):
68        shutil.copyfile(FG_ROOT + "Aircraft/" + source['dir'] + "/thumbnail.jpg",
69                os.path.join(root, "thumbnail.jpg"))
70        documentation_tex.write("\\begin{figure}[h!]\\centering")
71        documentation_tex.write("\\includegraphics[width=5cm,height=5cm,keepaspectratio]{thumbnail.jpg}")
72        documentation_tex.write("\\end{figure}")
73    documentation_tex.write("\\tableofcontents\n")
74    documentation_tex.write("\\input{preamble.tex}\n")
75
76    # If any additional documentation, copy it to the procedures directory and
77    # add it to the procedures.tex file
78    if source['extra_documentation']:
79        extra_docs_dir = os.path.join(FG_ROOT + "Aircraft/" + source['dir'], "Docs")
80        extra_docs_dest_dir = os.path.join("procedures/" + source['name'], "Docs")
81        if os.path.isdir(extra_docs_dest_dir):
82            shutil.rmtree(extra_docs_dest_dir)
83        shutil.copytree(extra_docs_dir, extra_docs_dest_dir)
84        documentation_tex.write("\\input{Docs/" + source['dir'] + "_documentation}\n")
85
86    # If there are any checklist files parsed, write a title
87    if(source['sources'] > 0):
88        documentation_tex.write("\\input{checklists.tex}\n")
89        checklists_tex = open(os.path.join(root, "checklists.tex"), "w")
90        checklists_tex.write("\\section{Checklists}\n")
91
92    # and the checklists themselves.
93    for xmlfile in source['sources']:
94        tree = etree.parse(xmlfile)
95        chkl_root = tree.getroot()
96        for checklist in chkl_root:
97            if checklist.tag != "checklist":
98                print "Unrecognised tag in", xmlfile
99                continue
100            title = checklist.find("title")
101            if title is None:
102                title = "Untitled"
103            else:
104                title = tex_escape(title.text)
105            items = []
106
107            for item in checklist.findall("item"):
108                name = item.find("name")
109                value = item.find("value")
110                if name is None or value is None:
111                    continue
112                if name.text is None or value.text is None:
113                    continue
114                items.append({
115                    'name': tex_escape(name.text),
116                    'value': tex_escape(value.text)
117                    })
118            filename = title + ".tex"
119            filename = filename.replace("/", "_")
120            filename = filename.replace(" ", "_")
121            checklists_tex.write("\\subsection{" + title + "}\n")
122            checklist_tex.write("\\section*{" + title + "}\n")
123            checklists_tex.write("\\input{checklists/"+filename+"}\n")
124            checklist_tex.write("\\input{checklists/"+filename+"}\n")
125            f = open(os.path.join(checklists_path,filename), "w")
126            if len(items) > 0:
127                f.write("\\begin{description}\n")
128                for item in items:
129                    f.write("\\item["+item['name']+"] \dotfill " + item['value']+"\n")
130                f.write("\\end{description}\n")
131            checklist_tex.write("\\clearpage\n")
132
133    documentation_tex.write("\\end{document}\n")
134    checklist_tex.write("\\end{document}\n")
135
136def gather_aircraft_metadata(directory = FG_ROOT + "Aircraft"):
137    aircrafts = []
138    for aircraft_directory in os.listdir(directory):
139        for rootfile in os.listdir(os.path.join(directory, aircraft_directory)):
140            rootfile = os.path.join(os.path.join(directory, aircraft_directory), rootfile)
141            if not (os.path.isfile(rootfile) and rootfile.endswith("-set.xml")):
142                continue
143            # Houston, we found a set.xml. Let's read it. This implies an aircraft
144            tree = etree.parse(rootfile)
145            name = aircraft_directory
146
147
148            for descr in tree.iter('description'):
149                name = descr.text
150                break
151            aircraft = {
152                    'sources'  : [],
153                    'name'     : name,
154                    'dir'     : aircraft_directory,
155                    'thumbnail': False,
156                    'extra_documentation': False
157                    }
158            # Check if the aircraft provides additional documentation
159            aircraft['extra_documentation'] = os.path.isdir(os.path.join(directory,
160                aircraft_directory
161                + "/Docs/")) & os.path.isfile(os.path.join(directory, aircraft_directory
162                    + "/Docs/"
163                    + aircraft_directory
164                    + "_documentation.tex"))
165            # Check if there is a thumbnail
166            if os.path.isfile(os.path.join(directory, aircraft_directory + "/thumbnail.jpg")):
167                aircraft['thumbnail'] = True
168
169            for checklist in tree.iter('checklists'):
170                aircraft['sources'].append(os.path.join(directory, aircraft_directory, checklist.attrib['include']))
171            if 'include' in tree.getroot().attrib:
172                try:
173                    tree = etree.parse(os.path.join(directory, aircraft_directory) + "/" + tree.getroot().attrib['include'])
174                    for checklist in tree.iter('checklists'):
175                        if 'include' in checklist.attrib:
176                            aircraft['sources'].append(os.path.join(directory, aircraft_directory, checklist.attrib['include']))
177                except:
178                    pass
179
180
181
182            if(len(aircraft['sources']) > 0 or aircraft['extra_documentation']):
183                aircrafts.append(aircraft)
184
185        # Check if there are checklists that can be parsed
186        # for root, dirs, files in os.walk(os.path.join(directory, aircraft_directory), topdown=False):
187        #     for name in files:
188        #         if(name.endswith("checklists.xml")):
189        #             aircraft['sources'].append(os.path.join(root,name))
190
191    return aircrafts
192
193
194def compile_airplane_latex(source):
195    """
196    Compile the procedures using pdflatex
197    """
198    wd = os.getcwd()
199    root = os.path.join("procedures", source['name'])
200    os.chdir(root)
201    with open(os.devnull, "w") as fnull:
202        mainfile = source['dir'] + "_documentation.tex"
203        call(["pdflatex", "-interaction", "nonstopmode", mainfile], stdout=fnull,stderr=fnull)
204        call(["pdflatex", "-interaction", "nonstopmode", mainfile], stdout=fnull,stderr=fnull)
205        mainfile = source['dir'] + "_checklist.tex"
206        call(["pdflatex", "-interaction", "nonstopmode", mainfile], stdout=fnull,stderr=fnull)
207        call(["pdflatex", "-interaction", "nonstopmode", mainfile], stdout=fnull,stderr=fnull)
208    os.chdir(wd)
209
210
211def generate_airplane_documentation():
212    # First generate the index of what should be included in the
213    # documentation
214    aircrafts = gather_aircraft_metadata("../Aircraft")
215    if(len(aircrafts) == 0 ):
216        print "No aircraft found; wrong directory?"
217    if(not os.path.isdir("procedures")):
218        os.mkdir("procedures")
219
220    # Then generate the documentation per airplane
221    for aircraft in aircrafts:
222        generate_airplane_latex(aircraft)
223        compile_airplane_latex(aircraft)
224
225# The main procedure
226generate_airplane_documentation()
227