Odsfile 套件與 tabularray 結合:錯位對齊製表符和錯誤

Odsfile 套件與 tabularray 結合:錯位對齊製表符和錯誤

我想使用 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 的第一行和第三行(可能還有一些非連續行)並將它們合併到一個 LaTeX 表中。

我希望它可以與tblr 一起使用,因為我不想每次都計算或計算行數(這對於較大的項目來說很瑣碎但很煩人),並且因為我想使用tabularray 的強大功能來修改表的外觀。

因此,如果這可以與模板一起使用,我認為這對於我當前的用例來說就足夠了。

答案1

有幾個問題。首先是 Tabularray 的內容有一般問題從 Lua 傳遞過來。我無法以擴展環境的方式傳遞\includespreadtblr,我總是遇到錯誤。恐怕只有templateoption for 的方式\includespread才有效。

另一個問題是由於它odsfile不用於\\分隔表中的行,而是使用它自己的命令\OdsNl\OdsLastNl.由於tblr需要顯式\\字符,即使沒有出現編譯錯誤,也會得到錯誤的表。

這是 的修復版本odsfile.sty,其中新增了兩個新選項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={\\}]

這是結果:

在此輸入影像描述

相關內容