summaryrefslogtreecommitdiff
path: root/macros/luatex/latex/odsfile
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/luatex/latex/odsfile
Initial commit
Diffstat (limited to 'macros/luatex/latex/odsfile')
-rw-r--r--macros/luatex/latex/odsfile/README53
-rw-r--r--macros/luatex/latex/odsfile/odsfile.lua300
-rw-r--r--macros/luatex/latex/odsfile/odsfile.pdfbin0 -> 253761 bytes
-rw-r--r--macros/luatex/latex/odsfile/odsfile.sty256
-rw-r--r--macros/luatex/latex/odsfile/odsfile.tex404
-rw-r--r--macros/luatex/latex/odsfile/pokus.odsbin0 -> 15481 bytes
6 files changed, 1013 insertions, 0 deletions
diff --git a/macros/luatex/latex/odsfile/README b/macros/luatex/latex/odsfile/README
new file mode 100644
index 0000000000..50ba5f3333
--- /dev/null
+++ b/macros/luatex/latex/odsfile/README
@@ -0,0 +1,53 @@
+Introduction
+------------
+
+The odsfile is LuaLaTeX package and lua library for working with
+opendocument spreadsheet (ods) documents from Open/Libre Office Calc.
+It is possible to select cells from sheets and typeset them as LaTeX tables,
+it is also possible to pass these data to LaTeX macros and make some plots,
+for example.
+
+There is also simple interface to add data to existing spreadsheets. This
+functionality requires external zip utility.
+
+This package uses LuaTeX's zip library and scripting to read these files.
+It also requires LuaXML library, which is available to download from
+CTAN site.
+
+Because of lua scripting, it is not possible to use this package with pdfLaTeX
+or XeLaTeX. On the other side, odsfile.lua library can be used from PlainTeX,
+ConTeXt or pure lua scripts.
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this license or
+(at your option) any later version. The latest version of this license is in
+
+ http://www.latex-project.org/lppl.txt
+
+and version 1.3 or later is part of all distributions of LaTeX version
+2005/12/01 or later.
+
+Basic usage
+-----------
+
+\usepackage{odsfile,booktabs}
+
+...
+
+\includespread[\begin{tabular}{l l}
+\includespread[file=pokus.ods]
+\end{tabular}
+
+...
+
+You can also use templates to specify tabular properties automaticaly
+\includespread[columns=top,template=booktabs,range=a3:c5]
+
+Author
+------
+Michal Hoftich
+Email: michal.h21@gmail.com
+
+If you are interested in the process of development you may observe
+
+ https://github.com/michal-h21/odsfile
diff --git a/macros/luatex/latex/odsfile/odsfile.lua b/macros/luatex/latex/odsfile/odsfile.lua
new file mode 100644
index 0000000000..5017b7bc16
--- /dev/null
+++ b/macros/luatex/latex/odsfile/odsfile.lua
@@ -0,0 +1,300 @@
+module(...,package.seeall)
+require "zip"
+local xmlparser = require ("luaxml-mod-xml")
+local handler = require("luaxml-mod-handler")
+
+local namedRanges = {}
+
+function load(filename)
+ local p = {
+ file = zip.open(filename),
+ content_file_name = "content.xml",
+ loadContent = function(self,filename)
+ local treehandler = handler.simpleTreeHandler()
+ local filename = filename or self.content_file_name
+ local xmlfile = self.file:open(filename)
+ local text = xmlfile:read("*a")
+ local xml = xmlparser.xmlParser(treehandler)
+ xml:parse(text)
+ return treehandler
+ end
+ }
+ return p
+end
+
+function getTable(x,table_name)
+ local t = getTable0(x,table_name)
+ local t2 = {}
+
+ for key, val in pairs(t) do
+ if key == "table:table-row" then
+ local rows = {}
+
+ for i = 1, #val do
+ local r = val[i]
+ local rowRep = r["_attr"]["table:number-rows-repeated"] or 1
+
+ row = {}
+ row["_attr"] = r["_attr"]
+ local cc = r["table:table-cell"] or {}
+ if #cc == 0 then
+ cc = {cc}
+ end
+
+ local columns = {}
+ --for j = 1, #cc do
+ -- local c = cc[j]
+ for _, c in ipairs(cc) do
+ c["_attr"] = c["_attr"] or {}
+ local colRep = c["_attr"]["table:number-columns-repeated"] or 1
+ for k = 1, colRep, 1 do
+ table.insert(columns, c)
+ end
+ end
+ row["table:table-cell"] = columns
+
+ for j = 1, rowRep, 1 do
+ table.insert(rows, row)
+ end
+ end
+
+ t2[key] = rows
+ else
+ t2[key] = val
+ end
+ end
+
+ return t2
+end
+
+function getTable0(x,table_name)
+ local tables = x.root["office:document-content"]["office:body"]["office:spreadsheet"]["table:table"]
+ namedRanges = loadNameRanges(x, table_name)
+ if #tables > 1 then
+ if type(tables) == "table" and table_name ~= nil then
+ for k,v in pairs(tables) do
+ if(v["_attr"]["table:name"]==table_name) then
+ return v, k
+ end
+ end
+ elseif type(tables) == "table" and table_name == nil then
+ return tables[1], 1
+ else
+ return tables
+ end
+ else
+ return tables
+ end
+end
+
+function getColumnCount(tbl)
+ local tbl = tbl or {}
+ local columns = tbl["table:table-column"] or {}
+ local x = 0
+ for _, c in pairs(columns) do
+ local rep = c["table:number-columns-repeated"] or 1
+ x = x + rep
+ end
+ return x
+end
+
+function loadNameRanges(root, tblname)
+ local tblname = tblname or ""
+ local t = {}
+ local ranges = root.root["office:document-content"]["office:body"]["office:spreadsheet"]["table:named-expressions"]
+ if not ranges then return {} end
+ ranges = ranges["table:named-range"] or {}
+ if #ranges == 0 then
+ ranges = {ranges}
+ end
+ for _,r in ipairs(ranges) do
+ local a = r["_attr"] or {}
+ local range = a["table:cell-range-address"] or ""
+ local name = a["table:name"]
+ if name and range:match("^"..tblname) then
+ range = range:gsub("^[^%.]*",""):gsub("[%$%.]","")
+ print("named range", name, range)
+ t[name] = range
+ end
+ end
+ return t
+end
+
+
+
+
+
+function tableValues(tbl,x1,y1,x2,y2)
+ local t= {}
+ local x1 = x1 or 1
+ local x2 = x2 or getColumnCount(tbl)
+ if type(tbl["table:table-row"])=="table" then
+ local rows = table_slice(tbl["table:table-row"],y1,y2)
+ for k,v in pairs(rows) do
+ -- In every sheet, there are two rows with no data at the bottom, we need to strip them
+ if(v["_attr"] and v["_attr"]["table:number-rows-repeated"] and tonumber(v["_attr"]["table:number-rows-repeated"])>10000) then break end
+ local j = {}
+ if #v["table:table-cell"] > 1 then
+ local r = table_slice(v["table:table-cell"],x1,x2)
+ for p,n in pairs(r) do
+ local attr = n["_attr"]
+ local cellValue = n["text:p"] or ""
+ table.insert(j,{value=cellValue,attr=attr})
+ end
+ else
+ local p = {value=v["table:table-cell"]["text:p"],attr=v["table:table-cell"]["_attr"]}
+ table.insert(j,p)
+ end
+ table.insert(t,j)
+ end
+ end
+ return t
+end
+
+function getRange(range)
+ local range = namedRanges[range] or range
+ local r = range:lower()
+ local function getNumber(s)
+ if s == "" or s == nil then return nil end
+ local f,ex = 0,0
+ for i in string.gmatch(s:reverse(),"(.)") do
+ f = f + (i:byte()-96) * 26 ^ ex
+ ex = ex + 1
+ end
+ return f
+ end
+ for x1,y1,x2,y2 in r:gmatch("(%a*)(%d*):*(%a*)(%d*)") do
+ return getNumber(x1),tonumber(y1),getNumber(x2),tonumber(y2)
+ --print(string.format("%s, %s, %s, %s",getNumber(x1),y1,getNumber(x2),y2))
+ end
+end
+
+function table_slice (values,i1,i2)
+ -- Function from http://snippets.luacode.org/snippets/Table_Slice_116
+ local res = {}
+ local n = #values
+ -- default values for range
+ i1 = i1 or 1
+ i2 = i2 or n
+ if i2 < 0 then
+ i2 = n + i2 + 1
+ elseif i2 > n then
+ i2 = n
+ end
+ if i1 < 1 or i1 > n then
+ return {}
+ end
+ local k = 1
+ for i = i1,i2 do
+ res[k] = values[i]
+ k = k + 1
+ end
+ return res
+end
+
+function interp(s, tab)
+ return (s:gsub('(-%b{})',
+ function(w)
+ s = w:sub(3, -2)
+ s = tonumber(s) or s
+ return tab[s] or w
+ end)
+ )
+end
+
+get_link = function(val)
+ local k = val["text:a"][1]
+ local href = val["text:a"]["_attr"]["xlink:href"]
+ return "\\odslink{"..href.."}{"..k.."}"
+end
+
+function escape(s)
+ return string.gsub(s, "([#%%$&])", "\\%1")
+end
+
+function get_cell(val, delim)
+ local val = val or ""
+ local typ = type(val)
+ if typ == "string" then
+ return escape(val)
+ elseif typ == "table" then
+ if val["text:a"] then
+ return get_link(val)
+ elseif val["text:span"] then
+ return get_cell(val["text:span"], delim)
+ elseif val["text:s"] then
+ -- return get_cell(val["text:s"], delim)
+ return table.concat(val, " ")
+ else
+ local t = {}
+ for _,v in ipairs(val) do
+ local c = get_cell(v, delim)
+ table.insert(t, c)
+ end
+ return table.concat(t,delim)
+ end
+ end
+end
+
+-- Interface for adding new rows to the spreadsheet
+
+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
+ return #sheet["table:table-row"]+1
+ end,
+ insert = function(self, sheet, pos)
+ local t = {}
+ local pos = pos or self:findLastRow(sheet)
+ print("pos je: ",pos)
+ if sheet["table:table-column"]["_attr"] and sheet["table:table-column"]["_attr"]["table:number-columns-repeated"] then
+ table_columns = sheet["table:table-column"]["_attr"]["table:number-columns-repeated"]
+ else
+ table_columns = #sheet["table:table-column"]
+ end
+ for i=1, table_columns 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
+
+
+-- function for updateing the archive. Depends on external zip utility
+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
diff --git a/macros/luatex/latex/odsfile/odsfile.pdf b/macros/luatex/latex/odsfile/odsfile.pdf
new file mode 100644
index 0000000000..3aaee3587b
--- /dev/null
+++ b/macros/luatex/latex/odsfile/odsfile.pdf
Binary files differ
diff --git a/macros/luatex/latex/odsfile/odsfile.sty b/macros/luatex/latex/odsfile/odsfile.sty
new file mode 100644
index 0000000000..97f9e0d9c6
--- /dev/null
+++ b/macros/luatex/latex/odsfile/odsfile.sty
@@ -0,0 +1,256 @@
+\ProvidesPackage{odsfile}
+\RequirePackage{luacode,xkeyval}
+
+%keyval keys
+
+\define@key{includespread}{file}{\loadodsfile{#1}}%
+\define@key{includespread}{sheet}{\luaexec{sheetname = "\luatexluaescapestring{#1}"}}%
+\define@key{includespread}{range}{\luaexec{%
+local x1,y1,x2,y2 = odsreader.getRange("\luatexluaescapestring{#1}")%
+range = {x1,y1,x2,y2}%
+}}
+\define@key{includespread}{template}{\luaexec{currenttemplate="\luatexluaescapestring{#1}"}}%
+\define@key{includespread}{rowtemplate}{\luaexec{rowtemplate="\luatexluaescapestring{\unexpanded{#1}}"}}%
+\define@key{includespread}{multicoltemplate}{\luaexec{multicoltpl="\luatexluaescapestring{\unexpanded{#1}}"}}%
+
+\define@choicekey*+{includespread}{rowseparator}[\val\nr]{tableline,hline,newline}[tableline]%
+{%
+\ifcase\nr\relax%
+ \luaexec{rowseparator=""}%
+\or%
+ \luaexec{rowseparator="\\hline "}%
+\or%
+\luaexec{%
+rowseparator=[[\\n]]
+}
+\fi%
+}{%
+ \luaexec{rowseparator="\luatexluaescapestring{#1}"}
+}
+\define@key{includespread}{columnbreak}{%
+ \luaexec{columnbreak="\luatexluaescapestring{\unexpanded{#1}}{}"}%
+}
+\define@key{includespread}{coltypes}{%
+ \luaexec{coltypes="\luatexluaescapestring{\unexpanded{#1}}"}%
+}
+\define@choicekey*+{includespread}{columns}[\val\nr]{head,top,none}{%
+\ifcase\nr\relax%
+ \luaexec{columns=1}%
+\or%
+ \luaexec{columns=2}%
+\fi%
+}{%
+\luaexec{%
+local function split(s,sep)
+ local sep, fields = sep or ":", {}
+ local pattern = string.format("([^\%s]+)", sep)
+ s:gsub(pattern, function(c) fields[\#fields+1] = c end)
+ return fields
+end
+local s="\luatexluaescapestring{#1}"
+columns = split(s,",")
+}%
+}{}%
+
+
+% Variable initialization and helper functions
+\begin{luacode*}
+odsreader = require("odsfile")
+odsfile = nil
+sheetname = nil
+range = {nil,nil,nil,nil}
+columns = nil
+templates = {}
+row = {}
+body = nil
+odsfilename = ""
+currenttemplate = nil
+rowtemplate = nil
+multicoltpl = "\\multicolumn{-{count}}{l}{-{value}}"
+\end{luacode*}
+
+\newcommand\loadodsfile[2][]{%
+ \setkeys{includespread}{#1}%
+ \luaexec{%
+ odsfilename = "\luatexluaescapestring{#2}"%
+ local ods = odsreader.load(odsfilename)%
+ odsfile, e = ods:loadContent()%
+ }%
+}
+
+\newcommand\tabletemplate[2]{%
+ \luaexec{%
+ templates["#1"]="\luatexluaescapestring{\unexpanded{#2}}"%
+ }
+}
+
+\newcommand\includespread[1][]{%
+ \luaexec{%
+ range = {nil,nil,nil,nil}
+ rowseparator = ""
+ columns=nil
+ currenttemplate = nil
+ rowtemplate = nil
+ columnbreak = "\\linebreak{}"
+ coltypes = nil
+ }%
+ \setkeys{includespread}{#1}%
+ \luaexec{%
+ body = odsreader.getTable(odsfile,sheetname)
+ local values = odsreader.tableValues(body,range[1],range[2],range[3],range[4])
+ %-- Conversion of odsfile table values to LaTeX tabular
+ local concatParagraphs = function(column)
+ % -- second returned value signalize whether cell contain paragraph, or not
+ local getCell = odsreader.get_cell
+ if type(column) =="table" then
+ return getCell(column, columnbreak), true
+ end
+ return getCell(column,""), false
+ end
+ local rowValues = function(row, headings)
+ local headings = headings or {}
+ local t={}
+ local i = 1
+ for _,column in pairs(row) do
+ local attr = column.attr or {}
+ local value, br = concatParagraphs(column.value)
+ value = value or ""
+ local x = attr["table:number-columns-spanned"] or "1"
+ x = tonumber(x)
+ if x > 1 then
+ value = odsreader.interp(multicoltpl, {value = value, count = x})
+ end
+ table.insert(t,value)
+ headings[i] = br
+ i = i + x
+ end
+ return t, headings
+ end
+ local makeColtypes = function(h)
+ local maxsize = tex.hsize / 65536
+ local h = h or {}
+ local p = 0
+ for _, c in pairs(h) do
+ if c then
+ p = p + 1
+ end
+ end
+ if p > 0 then
+ local j = {}
+ local size = tostring(math.floor(maxsize / \#h)) .. "pt"
+ for _, c in pairs(h) do
+ local k = "l"
+ if c then k = "p{"..size.."}" end
+ j[\#j+1]= k
+ end
+ return table.concat(j)
+ else
+ return string.rep("l",\#h)
+ end
+ end
+ if rowtemplate == nil then
+ local headings = {}
+ local currow = {}
+ currenttemplate = currenttemplate or "default"
+ content = {}
+ for i,row in pairs(values) do
+ currow, headings = rowValues(row, headings)
+ table.insert(content,table.concat(currow," & "))
+ end
+ %-- Column headings handling
+ local colheading=""
+ if type(columns) == "number" and columns == 1 then
+ columns = rowValues(values[1])
+ content = odsreader.table_slice(content,2,nil)
+ elseif type(columns) == "number" and columns == 2 then
+ local t = odsreader.tableValues(body,range[1],1,range[3],2)
+ columns = rowValues(t[1])
+ end
+ if type(columns) == "table" then colheading = table.concat(columns," & ") .. "\\\\" end
+ % coltypes = ""
+ if type(content)== "table" then
+ % coltypes= string.rep("l",\#content[1])
+ if not coltypes then
+ coltypes = makeColtypes(headings)
+ end
+ end
+ content = table.concat(content, "\\\\ "..rowseparator) .. "\\\\"
+ local result = odsreader.interp(templates[currenttemplate],{content=content,coltypes=coltypes,colheading=colheading,rowsep=rowseparator})
+ print(result)
+ tex.print(result)
+ else
+ local content = {}
+ currenttemplate = currenttemplate or "empty"
+ for _,row in pairs(values) do
+ table.insert(content,odsreader.interp(rowtemplate,rowValues(row)))
+ end
+ content = table.concat(content,rowseparator)
+ local result = odsreader.interp(templates[currenttemplate],{content=content,coltypes=coltypes,colheading=colheading,rowsep=rowseparator})
+ print(result)
+ tex.sprint(result)
+ end
+ }%
+}%
+
+\tabletemplate{empty}{-{content}}
+
+\tabletemplate{default}{-{colheading}-{rowsep}-{content}}
+
+\tabletemplate{booktabs}{%
+\begin{tabular}{-{coltypes}}
+\toprule
+-{colheading}
+\midrule
+-{content}
+\\ \bottomrule
+\end{tabular}
+}
+
+% Interface for adding of new rows
+
+\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 = odsreader.newRow()%
+}%
+}{%
+\luaexec{%
+body = body or odsreader.getTable(odsfile)
+row:insert(body,pos)%
+}%
+}
+
+% Interface for saving the spreadsheet
+
+\newcommand\savespreadsheet{%
+\luaexec{%
+ local xml = require("luaxml-mod-xml")
+ f = io.open("content.xml","w")%
+ f:write(xml.serialize(odsfile.root))%
+ f:close()%
+ odsreader.updateZip(odsfilename,"content.xml")%
+}%
+}
+
+% support for hyperlinks in cells
+
+\newcommand\odslink[2]{\texttt{#2}}
+
+\AtBeginDocument{%
+ \@ifpackageloaded{hyperref}{%
+ \renewcommand\odslink[2]{\href{#1}{#2}}%
+ }{}
+}
+\endinput
diff --git a/macros/luatex/latex/odsfile/odsfile.tex b/macros/luatex/latex/odsfile/odsfile.tex
new file mode 100644
index 0000000000..cf2d141f6a
--- /dev/null
+++ b/macros/luatex/latex/odsfile/odsfile.tex
@@ -0,0 +1,404 @@
+\documentclass{ltxdoc}
+\usepackage[utf8]{luainputenc}
+\usepackage[T1]{fontenc}
+\usepackage{odsfile,tgschola,metalogo,hyperref,xspace,microtype,showexpl,booktabs,url}
+\author{Michal Hoftich (\url{michal.h21@gmail.com})}
+\title{The \textsf{odsfile} package:\\
+accessing of the \textsf{opendocument spreadsheet} from \LaTeX{}
+documents\thanks{Version 0.6, last revisited 2015-09-20.}
+}
+\usepackage[english]{babel}
+\lstloadlanguages{[LaTeX]Tex}
+\lstset{%
+ basicstyle=\ttfamily,
+ commentstyle=\itshape\ttfamily,
+ showspaces=false,
+ showstringspaces=false,
+ breaklines=true,
+ breakautoindent=true,
+ captionpos=t
+}
+
+\newcommand\ods{\textsf{ods}\xspace}
+
+\begin{document}
+\maketitle
+
+\tableofcontents
+
+\section{Introduction}
+
+This is \LuaLaTeX{} package and \textsf{lua} library for working with
+\textsf{opendocument spreadsheet} (\ods) documents from Open/Libre Office Calc.
+Their contents can be read as \LaTeX{} tables, can be pass to macros,
+ you can also add new data to existing spreadsheets.
+
+\ods format consist of number of |xml| files packed in the |zip| file.
+This package uses \LuaTeX's zip library, LuaXML library\footnote{Pure |lua| library
+for working with |xml| files, it is available form CTAN or \url{https://github.com/michal-h21/LuaXML}} and lua scripting to read |xml| content from this archive,
+which means that it is not possible to use this package with pdf\LaTeX{} or \XeLaTeX.
+On the other side, |odsfile.lua| library can be used from Plain\TeX,
+Con\TeX{}t or pure |lua| scripts.
+
+Creation of this package was motivated by question\footnote{\url{http://tex.stackexchange.com/questions/60378/insert-libreoffice-table-as-input}} on site \url{http://tex.stackexchange.com/}. Development version of the package can be found at \url{https://github.com/michal-h21/odsfile}, all contributions and comments are welcome.
+\section{Usage}
+
+You can load |odsfile| classically with
+\begin{verbatim}
+\usepackage{odsfile}
+\end{verbatim}
+There are macros:
+\begin{itemize}
+\item \cmd{\includespread}
+\item \cmd{\tabletemplate}
+\item \cmd{\loadodsfile}
+\item \cmd{\savespreadsheet}
+\item |AddRow| environment
+\end{itemize}
+
+\noindent Main command is\marginpar{\cmd{\includespread}} \cmd{\includespread}. It's syntax is:\\
+\cmd{\includespread}\oarg{key-value list}
+
+Options are:
+
+\begin{description}
+\item[file] Filename of file to be loaded. You should specify this only on first use of \cmd{\includespread}.
+\item[sheet] Name of sheet to be loaded. If it's not specified on first use of
+ \cmd{\includespread}, then first sheet from the file is loaded. The sheet
+ remains selected until another use of |sheet|.
+
+\begin{LTXexample}
+\begin{tabular}{l l}
+\includespread[file=pokus.ods,sheet=List2]
+\end{tabular}
+\end{LTXexample}
+
+\item[range] Selects range from table to be inserted. Range is specified in
+ format similar to spreadsheet processors, like |a2:c4|, selecting cells
+ starting at first column, second row and ending and third column, fourth row.
+ Other variant of supported ranges are \textit{named ranges}, which can be
+ saved in the |ods file|.
+
+\begin{LTXexample}
+\begin{tabular}{lll}
+\includespread[sheet=List1,range=a2:c4]
+\end{tabular}
+\end{LTXexample}
+You can omit some or both of the numbers:
+\begin{LTXexample}
+\begin{tabular}{lll}
+\includespread[range=a:c4]
+\end{tabular}
+\end{LTXexample}
+
+\begin{LTXexample}
+\begin{tabular}{ll}
+\includespread[range=a:b]
+\end{tabular}
+\end{LTXexample}
+
+\begin{LTXexample}
+\begin{tabular}{ll}
+\includespread[range=b2:c]
+\end{tabular}
+\end{LTXexample}
+\item[columns] Column heading specification. It can be either |head|, |top|, or comma separated list of values.
+\begin{description}
+\item[top] Use as headers first line from the table.
+\begin{LTXexample}
+\begin{tabular}{ll}
+\includespread[range=b3:c5,columns=top]
+\end{tabular}
+\end{LTXexample}
+Note that if you include whole table, first line is included twice:
+\begin{LTXexample}
+\begin{tabular}{lll}
+\includespread[columns=top]
+\end{tabular}
+\end{LTXexample}
+in this case you can use
+\item[head] use first row from selection as headings.
+\begin{LTXexample}
+\begin{tabular}{lll}
+\includespread[columns=head,range=a:c3]
+\end{tabular}
+\end{LTXexample}
+\item[manually specified list] If column headings are not specified in the file, you can set them manually.
+\begin{LTXexample}
+\begin{tabular}{ll}
+\includespread[columns={title,amount},sheet=List2]
+\end{tabular}
+\end{LTXexample}
+\end{description}
+\item[columnbreak] Command inserted in manual linebreaks in cells. Default value is |\linebreak|
+
+\item[rowseparator] Rows are normally separated with newlines, if you really want, you can separate them with hlines.
+
+Possible values:
+\begin{description}
+\item[tableline (default)] Inserts |\\| character
+\item[hline] Inserts |\\ \\hline|
+\item[newline] Inserts blank line
+\item[user specified separator] useful in conjunction with \ref{it:rowtemplate}
+(p. \pageref{it:rowtemplate}), for example if you want to include sheets
+as plaintext or input for plotting functions.
+\end{description}
+\begin{LTXexample}
+\begin{tabular}{lll}
+\includespread[columns=top,sheet=List1,rowseparator=hline,range=a2:b5]
+\end{tabular}
+\end{LTXexample}
+
+\item[template] Templates are simple mechanism to insert whole tabular environment with column specification. All columns are aligned to the left, if you want to do more advanced stuff with column specifications, you must enter them manually as in all previous examples.
+\begin{LTXexample}
+\includespread[columns=top,template=booktabs,range=a3]
+\end{LTXexample}
+For more info about templates, see next section \ref{sec:tpl}
+
+\item[coltypes] When using template, column types are inferred automatically. If that doesn't work well in your case, you can specify them manually with |coltypes| option.
+
+ \begin{LTXexample}
+ \includespread[columns=head, template=booktabs, coltypes=lrr]
+ \end{LTXexample}
+
+\item[rowtemplate]
+\makeatletter
+\edef\@currentlabel{rowtemplate}
+\label{it:rowtemplate} Enables to convert tabular data to something different than \LaTeX\ tables.
+Syntax for rowtemplates is similar to the table templates, variables are inserted with |-{number}|, where
+number is the position of the cell from beginning of the selection.
+\makeatother
+\begin{LTXexample}
+\includespread[range=a2:b,rowseparator={,\ },rowtemplate={\textit{-{1}}:-{2}}]
+\end{LTXexample}
+
+\item[multicoltemplate]
+supports merged cells. Default template uses left aligned \verb|\multicolumn|
+command
+\begin{LTXexample}
+ \begin{tabular}{lll}
+ \includespread[sheet=Sheet3, multicoltemplate={\multicolumn{-{count}}{r}{-{value}}}]
+ \end{tabular}
+\end{LTXexample}
+
+\end{description}
+
+
+\section{Templates}\label{sec:tpl}
+
+If you don't want to specify tabular environment by hand, you can use simple templating mechanism to insert tabular environment by hand.
+
+Templates are defined with macro\marginpar{\cmd{\tabletemplate}}\\
+\cmd{\tabletemplate}\marg{template name}\marg{template code}\\
+there is default template:
+\begin{verbatim}
+\tabletemplate{default}{-{colheading}-{rowsep}-{content}}
+\end{verbatim}
+
+Code |-{variable name}| inserts one of the following variables:
+
+\begin{description}
+\item[coltypes] This is code to be inserted in |\begin{tabular}{coltypes}|. %In current version, it inserts |l| for left alignment column, for all columns of inserted table.
+%It should be possible to use more intelligent method based on types of column content, or \ods styles, maybe in future versions some of them will be used. If you want other alignment of columns now, you have to specify |\begin{tabular}{column types}| manually.
+The |p| column specifier is used for each column, where cell with manual line break occurs, |l| is used otherwise.
+
+\item[colheading] Column headings.
+\item[rowsep] It inserts row separator defined with |rowsepartor| key of |\includespread|. It is used in default style to delimit column headings and table contents.
+\item[content] Tabular data.
+\end{description}
+
+\paragraph{More powerful template for the \textsc{booktabs} package}
+
+\begin{verbatim}
+\tabletemplate{booktabs}{%
+\begin{tabular}{-{coltypes}}
+\toprule
+-{colheading}
+\midrule
+-{content}
+\\ \bottomrule
+\end{tabular}
+}
+\end{verbatim}
+
+\section{Adding data}
+
+There is simple interface for adding new rows to the spreadsheet.
+
+|AddRow|\oarg{row number}\marginpar{|AddRow|} environment for adding new row to the current sheet. Optional argument \oarg{row number} specifies where it should be inserted, if blank, it will be inserted at end.
+
+Inside |AddRow|, you can use
+\begin{itemize}
+ \item \cmd{\AddString}\marg{text}\marg{position}
+ \item \cmd{\AddNumber}\marg{number}\marg{position}
+\end{itemize}
+Position specifies cell, where data should be added, if you leave it blank, it will be laced next to the previous one.
+
+\begin{LTXexample}
+\includespread[columns=head,template=booktabs]
+\begin{AddRow}
+ \AddString{last row}{}
+\end{AddRow}
+\begin{AddRow}[3]
+ \AddString{third row}{}
+ \AddNumber{22}{2}
+\end{AddRow}
+\includespread[columns=head,template=booktabs]
+\end{LTXexample}
+
+\section{Loading and saving of the \ods file}
+
+You can explicitly load \ods file with \marginpar{\cmd{\loadodsfile}}\cmd{\loadodsfile}\oarg{key val list}\marg{filename}. This can be useful, if you only want to write some data to the file, otherwise it is better to use \cmd{\includespread}.
+
+For saving spreadsheets modified with |AddRow|, you can use \cmd{\savespreadsheet}\marginpar{\cmd{\savespreadsheet}}. This command uses call to external |zip| utility, so you should have installed it and you have to call lua\LaTeX with |lualatex --shell-escape filename|. Lua\LaTeX also must have write permissions for accessing the \ods file. This command creates file |content.xml| in the current directory, so if externall call fails, you can run
+\begin{verbatim}
+zip -r filename.ods content.xml
+\end{verbatim}
+by hand.
+\section{Lua library}
+
+
+The |lua| library uses |luazip| library integrated to \LuaTeX{} and |LuaXML|\footnote{\url{https://github.com/michal-h21/LuaXML}}, pure |lua| library for working with |XML| files.
+
+To use library in your code, you can use:
+
+\begin{verbatim}
+require("odsfile")
+\end{verbatim}
+
+Function |odsfile.load(filename)| returns |odsfile| object, with |loadContent()| method, which returns |lua| table representing |content.xml| file. We can select sheet from the spreadsheet with |odsfile.getTable(xmlobject,sheet_name)|. If we omit |sheet_name|, first sheet from spreadsheet is selected.
+
+Data from sheet can be read with |odsfile.tableValues(sheet, x1, y1, x2, y2)|. |x1 - y2| are range to be selected, they can be |nil|, in which case whole rows and cells are selected. For converting of standard range expressions of form |a1:b2| to this representation, function |odsfile.getRange(range)| can be used.
+
+\paragraph{Example usage -- file \textsf{odsexample.lua}}
+
+\begin{verbatim}
+require "odsfile"
+
+-- Helper function to print structure of the table
+function printable(tb, level)
+ level = level or 1
+ local spaces = string.rep(' ', level*2)
+ for k,v in pairs(tb) do
+ if type(v) ~= "table" then
+ print(spaces .. k..'='..v)
+ else
+ print(spaces .. k)
+ level = level + 1
+ printable(v, level)
+ end
+ end
+end
+
+local ods = odsfile.load("filename.ods")
+local f, e = ods:loadContent()
+
+-- Get First sheet from the table
+body= odsfile.getTable(f)
+-- Print structure of the range a4:b5
+printable(odsfile.tableValues(body,odsfile.getRange("a4:b5")))
+\end{verbatim}
+
+Run the example with |texlua odsexample.lua| from the command line, you should get following result:
+
+\begin{verbatim}
+ 1
+ 1
+ value=AA
+ attr
+ office:value-type=string
+ 2
+ value=3
+ attr
+ office:value-type=float
+ office:value=3
+ 2
+ 1
+ value=BB
+ attr
+ office:value-type=string
+ 2
+ value=4
+ attr
+ office:value-type=float
+ office:value=4
+\end{verbatim}
+
+To convert this structure to \LaTeX{} tabular code, you can use following function:
+
+\begin{verbatim}
+function tableToTabular(values)
+ local rowValues = function(row)
+ local t={}
+ for _,column in pairs(row) do table.insert(t,column.value) end
+ return t
+ end
+ content = {}
+ for i,row in pairs(values) do
+ table.insert(content,table.concat(rowValues(row)," & "))
+ end
+ return table.concat(content,"\\\\\n")
+end
+-- Now use it with objects from previous example
+print(tableToTabular(odsfile.tableValues(body)))
+\end{verbatim}
+
+This example yields
+
+\begin{verbatim}
+Label & Position & Count\\
+Hello & 1 & 3\\
+World & 2 & 4\\
+AA & 3 & 5\\
+BB & 4 & 6\\
+CC & 5 & 7
+\end{verbatim}
+
+
+
+\section{Changes}
+
+\begin{description}
+
+\item[v0.6]
+ \begin{itemize}
+ \item Fixed bug in handling of rows with only one cell\footnote{Thanks to Ulrike Fisher}
+ \item Added support for named ranges
+ \item Fixed text:s handling. Beware that using multiple formatting styles
+ in one cell may result in discarding whole cell, or wrong order. This is
+ caused by underlying LuaXML library and it is likely to remain.
+\end{itemize}
+\item[v0.5]
+\begin{itemize}
+ \item Fixed bug: cell attributes weren't saved
+ \item Added support for merged cells
+ \item Added \texttt{multicoltemplate} option
+\end{itemize}
+
+\item[v0.4]
+\begin{itemize}
+ \item Fixed bugs in loading sheets without ranges
+ \item Fixed bugs in behaviour of empty cells\footnote{Thanks to \href{https://github.com/TripleWhy}{TrippleWhy}}
+ \item Fixed bug in row counting\footnote{Thanks to \href{https://github.com/yamsu}{yamsu}}
+ \item Added support for children element in column paragraphs
+ \item Added cell value escaping
+\end{itemize}
+\item[v0.3]
+\begin{itemize}
+ \item Added support for multiline cells
+ \item Improved automatic column types generation
+ \item Added new options, |coltypes| and |columnbreak|
+\end{itemize}
+\item[v0.2]
+\begin{itemize}
+ \item LuaXML is now distributed as separate library, so other projects can use it.
+ \item New |AddRow| environment for adding data to the \ods file
+ \item New command |\savespreadsheet| for saving \ods file
+ \item Bug fixes: corrected loading of the sheets, corrected behaviour of blank cell
+\end{itemize}
+\item[v0.1] First version
+\end{description}
+
+
+
+\end{document}
diff --git a/macros/luatex/latex/odsfile/pokus.ods b/macros/luatex/latex/odsfile/pokus.ods
new file mode 100644
index 0000000000..e59b876215
--- /dev/null
+++ b/macros/luatex/latex/odsfile/pokus.ods
Binary files differ