Edit 14-08-2012
New version of odsfile
incorporates ideas from this answer. It is now possible to use following code:
\documentclass{article}
\usepackage{lmodern,odsfile,booktabs}
\begin{document}
\loadodsfile{template.ods}
\begin{AddRow}
\AddString{Hello world}{}
\AddNumber{46}{3}
\end{AddRow}
\includespread[template=booktabs,columns=head]
\savespreadsheet
\end{document}
Package is now included in TeX Live
and xml
library is distributed separately http://ctan.org/pkg/luaxml, so now it can be used by other packages.
This is my solution using
lualatex
and development version of
odsfile.
First of all, I created serialization function from
LuaXML
tables to
xml
.
Code
xml = require("xml-mod")
local t = {html={head = {title = "Hello world"}}} -- Table in format as created with LuaXML parser
print(xml.serialize(t))
outputs
<?xml version="1.0" encoding="UTF-8"?>
<html>
<head>
<title>Hello world</title>
</head>
</html>
Then there is small library for adding contents to ods file, exams.lua
module(...,package.seeall)
require("odsfile")
xml = require("xml-mod")
function updateZip(zipfile, updatefile)
local command = string.format("zip %s %s",zipfile, updatefile)
print ("Updating an ods file.\n" ..command .."\n Return code: ", os.execute(command))
end
-- Object for adding new rows
function newRow()
local p = {
pos = 0,
cells = {},
-- Generic function for inserting cell
addCell = function(self,val, attr,pos)
if pos then
table.insert(self.cells,pos,{["text:p"] = val, ["_attr"] = attr})
self.pos = pos
else
self.pos = self.pos + 1
table.insert(self.cells,self.pos,{["text:p"] = val, ["_attr"] = attr})
end
end,
addString = function(self,s,attr,pos)
local attr = attr or {}
attr["office:value-type"] = "string"
self:addCell(s,attr,pos)
end,
addFloat = function(self,i,attr,pos)
local attr = attr or {}
local s = tonumber(i) or 0
s = tostring(s)
attr["office:value-type"] = "float"
attr["office:value"] = s
self:addCell(s,attr,pos)
end,
findLastRow = function(self,sheet)
for i= #sheet["table:table-row"],1,-1 do
if sheet["table:table-row"][i]["_attr"]["table:number-rows-repeated"] then
return i
end
end
end,
insert = function(self, sheet, pos)
local t = {}
local pos = pos or self:findLastRow(sheet)
for i=1, #sheet["table:table-column"] do
table.insert(t,self.cells[i] or {})
end
t = {["table:table-cell"]=t}
table.insert(sheet["table:table-row"],pos,t)
end
}
return p
end
It has one function and one object. updateZip(zipfile, updatefile)
tries to run zip utility, which have to be installed in your system. Unfortunately, I have some problems with access rights on my computer, so it doesn't work here. Instead, I just run this command from commandline using generated xml
and it works just fine.
newRow() returns object with some methods:
addCell(val,attr,pos)
addString(val,attr,pos)
addNumber(val,attr,pos)
these methods add columns into row. addCell
is generic one and should not be used. attr is array with attributes, you can leave it empty. pos
means position of the column in the row, it can also be nil
insert(sheet, pos)
this function insert row to the sheet, you can specify row position with pos
Now some simple LaTeX
package, exams.sty
\ProvidesPackage{exams}
\RequirePackage{luacode}
\begin{luacode}
exams = require("exams")
require("odsfile")
xml = require("xml-mod")
--exams.test()
row = {}
sheet = {}
ods = {}
filename = ""
\end{luacode}
\newcommand\examspreadsheet[2]{%
\luaexec{%
sh = "\luatexluaescapestring{#2}"
if sh == "" then sh = nil end
filename = "\luatexluaescapestring{#1}"
local f = odsfile.load(filename)
ods = f:loadContent()
sheet = odsfile.getTable(ods,sh)
}
}
\newcommand\savespreadsheet{%
\luaexec{exams.updateZip(filename,"content.xml")}
}
\newcommand\answer[1]{%
\begingroup%
\AddRow%
\AddNumber{\theenumi}{}%
\AddString{#1}{}%
\endAddRow%
\endgroup%
#1%
}
\newenvironment{exam}[1]{%
\section{#1}
\AddRow
\AddString{#1}{}
\endAddRow
\enumerate%
}{\endenumerate%
\luaexec{%
f = io.open("content.xml","w")
f:write(xml.serialize(ods.root))
}
}
\newenvironment{AddRow}[1][]{%
\def\AddString##1##2{%
\luaexec{%
local pos = "\luatexluaescapestring{##2}"%
if pos == "" then pos = nil end; row:addString("\luatexluaescapestring{\unexpanded{##1}}",nil,pos)%
}%
}%
\def\AddNumber##1##2{%
\luaexec{%
local pos = "\luatexluaescapestring{##2}"%
if pos == "" then pos = nil end; row:addFloat("\luatexluaescapestring{##1}",nil,pos)%
}%
}%
\luaexec{%
pos = "\luatexluaescapestring{#1}"%
if pos == "" then pos = nil end; row = exams.newRow()%
}
}{%
\luaexec{%
row:insert(sheet,pos)%
}
}
This package defines three user commands and one environment:
\examspreadsheet{odsfile}{sheet}
loads sheet from file. you can left sheet
blank, then first sheet will be selected
\answer{text}
add row to the spreadsheet
\savespreadsheet
saves sheet to the ods file
- environment
exam
all answers should be inside it, at end, it saves file content.xml
in the current directory.
Note: there is some spurious space in definition of \answer
, that I cannot find.
Now some sample file:
\documentclass{article}
\usepackage{lmodern,exams}
\begin{document}
\examspreadsheet{template.ods}{}
\begin{exam}{Test1}
\item Bla bla \answer{Test of special characters < > \& "}
\item The \answer{definition of entropy} states that \answer{$S = k_B
\ln \Omega$}.
\end{exam}
\savespreadsheet
\end{document}
Compile with
lualatex --shell-escape sample.tex
If you get error message in the console output like:
zip I/O error: Permission denied
zip error: Could not create output file (was replacing the original zip file)
Updating an ods file.
zip template.ods content.xml
Return code: 15
then you have to change file permissions of the ods
file to enable writing for other users, or simply run command
zip -r template.ods content.xml
And the result in openoffice calc
:

csv
files as data source for both yourTeX
document and your spreadsheet. Thedatatool
package could help. – bodo Jul 24 '12 at 14:19csv
file fromTeX
and then open it withlibreoffice
. – bodo Jul 24 '12 at 14:38