% Package odsfile. Author Michal Hoftich % This package is subject of LPPL license, version 1.3c \ProvidesPackage{odsfile}[2023/09/07 v0.8 odsfile package to select cells from ODS sheets and typeset them as LaTeX tables] \RequirePackage{luacode,xkeyval,xparse} %keyval keys \define@key{includespread}{file}{\loadodsfile{#1}}% \define@key{includespread}{sheet}{\luaexec{sheetname = "\luatexluaescapestring{#1}"}}% \define@key{includespread}{range}{\luaexec{range="\luatexluaescapestring{#1}"}}% \define@key{includespread}{template}{\luaexec{currenttemplate="\luatexluaescapestring{#1}"}}% \define@key{includespread}{rowtemplate}{\luaexec{rowtemplate="\luatexluaescapestring{\detokenize{#1}}"}}% \define@key{includespread}{celltemplate}{\luaexec{celltpl="\luatexluaescapestring{\detokenize{#1}}"}}% \define@key{includespread}{multicoltemplate}{\luaexec{multicoltpl="\luatexluaescapestring{\unexpanded{#1}}"}}% \define@key{includespread}{escape}{\luaexec{latexescape="\luatexluaescapestring{\unexpanded{#1}}"}}% \newcommand\OdsNl{\\} \newcommand\OdsLastNl{\\} \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 celltpl = "-{value}" multicoltpl = "\\multicolumn{-{count}}{l}{-{value}}" latexescape = "true" \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}}"% } } \NewDocumentCommand\includespread{s o}{% \IfBooleanTF#1% {\ods@includespread@star{#2}}% {\ods@includespread@unstar{#2}}% } \newcommand\ods@includespread@star[1]{% \let\ods@tmp@lastNL\OdsLastNl% \renewcommand\OdsLastNl{}% \ods@includespread@unstar{#1}% \let\OdsLastNl\ods@tmp@lastNL% } \newcommand\ods@includespread@unstar[1]{% \luaexec{% range = nil rowseparator = "" columns=nil currenttemplate = nil rowtemplate = nil celltpl = "-{value}" columnbreak = "\\linebreak{}" coltypes = nil latexescape = "true" }% \setkeys{includespread}{#1}% \luaexec{% body = odsreader.getTable(odsfile,sheetname) local real_range = odsreader.getRange(range) local values = odsreader.tableValues(body,real_range[1],real_range[2],real_range[3],real_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}) else value = odsreader.interp(celltpl, {value = value}) end % table.insert(t,value) t[i] = 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,real_range[1],1,real_range[3],2) columns = rowValues(t[1]) end if type(columns) == "table" then colheading = table.concat(columns," & ") .. "\\OdsNl " 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, "\\OdsNl "..rowseparator) .. "\\OdsLastNl" 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)% % we must save the updated table to the original table odsfile.root["office:document-content"]["office:body"]["office:spreadsheet"]["table:table"] = body% }% } % 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