Quero incluir um arquivo ods usando o pacote odsfile em combinação com o pacote tabularray. No entanto, isso resulta em um Misplaced alignment tab character &
erro. Aqui está um M(n)NÓS:
%% Compile with lualatex
\documentclass{article}
\usepackage{odsfile}
\usepackage{tabularray}
\tabletemplate{tblr}{%
\begin{tblr}{-{coltypes}}
-{content}
\end{tblr}
}
\begin{document}
%% Works:
\begin{tabular}{ll}
\includespread[file=data.ods,range=a1:b3]
\end{tabular}
%% This is what I want (in particular automaticaly determining the number of columns), but doesn't work
\begin{tblr}{}
\includespread[file=data.ods,range=a1:b3]
\end{tblr}
%% For testing purposes I specified the column types (which I don't want), but it works.
\begin{tblr}{ll}
\includespread[file=data.ods,range=a1:b3]
\end{tblr}
%% This works, but I don't want to use a template in this case
\includespread[file=data.ods,range=a1:b3,template=tblr]
\end{document}
O arquivo data.ods
contém apenas:
X Y
1 a
2 b
O erro é como:
1 & a\\ 2 & b\\
X & Y\\ 1 & a\\ 2 & b\\
! Misplaced alignment tab character &.
l.1 X &
Y\\ 1 & a\\ 2 & b\\
l.24 \end
{tblr}
Como posso fazer isso funcionar?
EditarComo indica a resposta de michal.h21, encontrar uma solução completa parece ser bastante complexo. Então, tento fornecer mais informações sobre o que desejo alcançar em particular:
%% Compile with lualatex
\documentclass{article}
\usepackage{odsfile}
\usepackage{tabularray}
\begin{document}
\begin{tabular}{cc}
\includespread[file=data.ods,range=a1:b1]
\includespread[file=data.ods,range=a3:b3]
%% Some more rows...
\end{tabular}
\end{document}
O ponto principal deste exemplo é que ele seleciona a primeira linha e a terceira linha (e possivelmente mais algumas linhas não conrows) do odsfile e as mescla em uma tabela LaTeX.
Quero que isso funcione com o tblr, pois não quero contar ou calcular o número de linhas a cada vez (o que é trivial, mas irritante para projetos maiores) e porque quero usar os grandes recursos do tabularray para modificar a aparência da tabela.
Então, se isso funcionasse com um modelo, acho que seria suficiente para o meu caso de uso atual.
Responder1
Existem vários problemas. A primeira é que o Tabularray tem, em geral, problemas com conteúdo que épassou da Lua. Não consegui passar \includespread
para o tblr
ambiente de uma forma que ampliasse ele, sempre dava erro. Receio que o caminho com template
a opção for \includespread
seja o único que funciona.
Outro problema se deve ao fato de odsfile
não utilizar \\
para separar linhas em tabelas, mas sim comandos próprios, \OdsNl
e \OdsLastNl
. Como tblr
precisa de caracteres explícitos \\
, você obtém uma tabela errada, mesmo que não receba um erro de compilação.
Aqui está uma versão fixa do odsfile.sty
, que adiciona duas novas opções, newline
e \lastnewline
. Com eles, você pode especificar o uso do \\
comando:
% Package odsfile. Author Michal Hoftich <[email protected]>
% 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,",")
}%
}{}%
\define@key{includespread}{newline}{%
\luaexec{odsnl="\luatexluaescapestring{\unexpanded{#1}}"}%
}
\define@key{includespread}{lastnewline}{%
\luaexec{odslastnl="\luatexluaescapestring{\unexpanded{#1}}"}%
}
% 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"
odsnl = "\\OdsNl"
odslastnl = "\\OdsLastNl"
\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.sprint(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
Pode ser usado desta forma:
\includespread[file=data.ods,range=a1:b3,template=tblr, newline={\\},lastnewline={\\}]
Este é o resultado: