odsfile パッケージと tabularray パッケージを組み合わせて ods ファイルをインクルードしたいのですが、Misplaced alignment tab character &
エラーが発生します。以下は M(n)WE です。
%% 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}
ファイル data.ods
以下のみが含まれます:
X Y
1 a
2 b
エラーは次のようになります:
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}
どうすれば動作させることができますか?
編集michal.h21 さんの回答が示すように、完全な解決策を見つけるのはかなり複雑であるようです。そこで、特に私が達成したいことの背景をもう少し説明したいと思います。
%% 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}
この例のポイントは、odsfile の最初の行と 3 番目の行 (および場合によってはその他の非連結行) を選択し、それらを 1 つの LaTeX テーブルに結合することです。
毎回行数を数えたり計算したりしたくないので (これは些細なことですが、大規模なプロジェクトでは面倒です)、また tabularray の優れた機能を使用してテーブルの外観を変更したいので、これを tblr で動作させたいと思います。
したがって、これがテンプレートで機能するのであれば、現在の使用例では十分だと思います。
答え1
いくつかの問題があります。まず、Tabularrayは一般的に、Luaから渡される\includespread
。環境にtblr
拡張する方法で渡すことができず、常にエラーが発生しました。template
オプションを使用する方法\includespread
だけが機能するのではないかと思います。
もう 1 つの問題は、テーブル内の行を区切るために ではなく、独自のコマンドと が使用odsfile
されていることに起因します。明示的な文字が必要なため、コンパイル エラーが発生しなくても間違ったテーブルが生成されます。\\
\OdsNl
\OdsLastNl
tblr
\\
以下は の修正バージョンです。これにはとodsfile.sty
という 2 つの新しいオプションが追加されています。これらを使用すると、次のコマンドを使用するように指定できます。newline
\lastnewline
\\
% 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
次のように使用できます:
\includespread[file=data.ods,range=a1:b3,template=tblr, newline={\\},lastnewline={\\}]
結果は次のとおりです。