The following code was motivated by a discussion in the chatroom. The idea here is to leave the data retrieval and processing to an external entity, set a .tex
template that will hold the data, populate it and generate the final .tex
file.
This is what template engines basically do. Although we can exploit Lua (see Patrick's great answer), sometimes you might rely on just a .tex
template for the sake of simplicity. :)
Here we go, the code is fractionated to include some comments on what's happening in each part. I opted to code a small script in Python and rely solely on hardcoded data and template to ease the process.
from xml.dom import minidom
from Cheetah.Template import Template
I decided to stick with some data expressed in the XML format because it was just a plain example of how to get data and incorporate it to TeX. Of course, the way we acquire content is irrelevant now, since we are just interested on how the data object is represented in our script. :)
I used minidom
to read the XML excerpt and the Cheetah template engine to help me produce the resulting .tex
file. For now, those are just imports, we will discuss implementation in the next steps.
def getNodeValue(node, key):
return node.getElementsByTagName(key)[0].childNodes[0].nodeValue
def getData(source):
a = []
b = minidom.parseString(source)
c = b.firstChild
for d in c.childNodes:
if d.nodeType == 1:
e = {}
e['name'] = getNodeValue(d, 'name')
e['surname'] = getNodeValue(d, 'surname')
e['grade'] = float(getNodeValue(d, 'grade'))
a.append(e)
return a
Those two functions were written just to help me traverse the XML content and fetch the data I want to use. The result will be an array containing dictionaries in the form
{
'name' : 'John',
'surname' : 'Doe',
'grade' : 10
}
extracted from the following XML, represented in the very own Python code as a raw string (note the r
prepended in the string definition):
xml = r"""<?xml version="1.0" ?>
<persons>
<person>
<name>Paulo</name>
<surname>Cereda</surname>
<grade>3.6</grade>
</person>
<person>
<name>Enrico</name>
<surname>Gregorio</surname>
<grade>8.9</grade>
</person>
<person>
<name>David</name>
<surname>Carlisle</surname>
<grade>8.8</grade>
</person>
</persons>"""
Now, let's start the fun part. Once we have everything ready, let's read the XML content, parse it and create the array. I do this by calling the getData
function defined previously. Just to be fancy, I apply a sorted
function to the resulting array providing a lambda function that sets the order by the name
key:
data = sorted(getData(xml), key=lambda y: y['name'])
The data
variable contains an array of dictionaries. Now let's create our .tex
template, pretty much along the same lines we did with our XML file, that is, through a raw string:
template = r"""\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{siunitx}
\begin{document}
\begin{tabular}{llS}
\hline
Name & Surname & {Grade}\\
\hline
#for $person in $data
$person['name'] & $person['surname'] & $person['grade']\\
#end for
\hline
\end{tabular}
\end{document}"""
The template is pretty straightforward, it's a simple .tex
file which will give us a tabular
environment. The interesting part is:
#for $person in $data
$person['name'] & $person['surname'] & $person['grade']\\
#end for
This part is part of the Cheetah language description. Simply put, this is a for
loop where we iterate through all dictionaries in the data
array. Note that $
is used as a placeholder and not as math mode, as we are used in TeX. Don't worry with them, they'll be gone once we merge everything. :)
Since we are dealing with a dictionary, we access values from its keys, so $person['name']
will return the person's name. :)
Now, let's create the template from the source above and add the data
array associated with a key, and print the template. I should output the merging to a new file, but for now, let's just print the resulting code to the terminal:
definition = Template(source=template, searchList=[ { 'data': data } ])
print(definition)
And we are done. :)
Let me post the code as a whole, just for easy copy and paste:
from xml.dom import minidom
from Cheetah.Template import Template
def getNodeValue(node, key):
return node.getElementsByTagName(key)[0].childNodes[0].nodeValue
def getData(source):
a = []
b = minidom.parseString(source)
c = b.firstChild
for d in c.childNodes:
if d.nodeType == 1:
e = {}
e['name'] = getNodeValue(d, 'name')
e['surname'] = getNodeValue(d, 'surname')
e['grade'] = float(getNodeValue(d, 'grade'))
a.append(e)
return a
xml = r"""<?xml version="1.0" ?>
<persons>
<person>
<name>Paulo</name>
<surname>Cereda</surname>
<grade>3.6</grade>
</person>
<person>
<name>Enrico</name>
<surname>Gregorio</surname>
<grade>8.9</grade>
</person>
<person>
<name>David</name>
<surname>Carlisle</surname>
<grade>8.8</grade>
</person>
</persons>"""
data = sorted(getData(xml), key=lambda y: y['name'])
template = r"""\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{siunitx}
\begin{document}
\begin{tabular}{llS}
\hline
Name & Surname & {Grade}\\
\hline
#for $person in $data
$person['name'] & $person['surname'] & $person['grade']\\
#end for
\hline
\end{tabular}
\end{document}"""
definition = Template(source=template, searchList=[ { 'data': data } ])
print(definition)
In order to run this code, we need a Python interpreter (mine is Python 2.7.5
) and the Cheetah template engine. I'm almost sure you can install it via your favourite package manager, say
# yum install python-cheetah
or probably via pip
:
pip install Cheetah
Let's run the code now, shall we? :)
Allons-y!
paulo@alexandria ~$ python mycode.py
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{siunitx}
\begin{document}
\begin{tabular}{llS}
\hline
Name & Surname & {Grade}\\
\hline
David & Carlisle & 8.8\\
Enrico & Gregorio & 8.9\\
Paulo & Cereda & 3.6\\
\hline
\end{tabular}
\end{document}
The resulting .tex
file produces the following output:

And that's it. :)
LuaTeX
, I strongly recommend you install the latest TeXLive 2011.:-)