Пакет Odsfile в сочетании с tabularray: Неправильное расположение символа табуляции выравнивания и ошибка

Пакет Odsfile в сочетании с tabularray: Неправильное расположение символа табуляции выравнивания и ошибка

Я хочу включить файл ods, используя пакет odsfile в сочетании с пакетом tabularray. Однако это приводит к ошибке 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 имеет общие проблемы с контентом, которыйперешел из Луа. Я не смог перейти \includespreadк tblrсреде таким образом, чтобы расширить ее, я всегда получал ошибку. Боюсь, что способ с templateопцией 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={\\}]

Вот результат:

введите описание изображения здесь

Связанный контент