从 luatex 创建 LibreOffice 电子表格

从 luatex 创建 LibreOffice 电子表格

看完之后这个惊人的答案,我想知道是否可以做同样的事情,但反过来,即从 luatex 创建一个 LibreOffice 电子表格,将文件中的数据导出tex到电子表格。

我想到的是:假设我正在用一个文档类排版一份考试的解决方案,该文档类定义了一个将\answer在以下上下文中使用的宏。

\documentclass{myclass}

\begin{document}

\section{Test 1}

\begin{enumerate}
\item The \answer{definition of entropy} states that \answer{$S = k_B
    \ln \Omega$}. 
\end{enumerate}

\end{document}

我使用 LibreOffice 电子表格为学生评分。因此,我想luatex创建(或修改)电子表格,以便自动获得如下屏幕截图所示的结果。

电子表格的屏幕截图

我明白那个LibreOffice 的 lua 接口不存在。因此,我认为有三种方法:

  1. 从头创建符合.ods标准的 zip 文件。
  2. 使用一些魔法将 LibreOffice API 与 luatex 连接起来(使用LuaJava?)。
  3. 使用一些 perl 或其他类型的魔法来完成这项工作。

正如@canaaerus 在评论中所建议的那样,我可以使用 csv 文件导出内容tex,但我希望有一个真正的.ods开箱即用功能,以便我可以拥有样式信息(例如粗体、居中文本等)和公式。

答案1

编辑 14-08-2012 新版本odsfile结合了此答案中的想法。现在可以使用以下代码:

\documentclass{article}
\usepackage{lmodern,odsfile,booktabs}
\begin{document}
\loadodsfile{template.ods}

\begin{AddRow}
\AddString{Hello world}{}
\AddNumber{46}{3}
\end{AddRow}

\includespread[template=booktabs,columns=head]
\savespreadsheet
\end{document}

软件包现已包含在内TeX Live,并且xml库单独分发http://ctan.org/pkg/luaxml,所以现在它可以被其他包使用了。


这是我使用lualatex和开发版本的解决方案ods文件。首先,我创建了从LuaXML表到的序列化函数xml

代码

xml = require("xml-mod")
local t = {html={head = {title = "Hello world"}}} -- Table in format as created with LuaXML parser
print(xml.serialize(t))

输出

<?xml version="1.0" encoding="UTF-8"?>
<html>
  <head>
    <title>Hello world</title>
  </head>
</html>

然后有一个小库用于向 ods 文件添加内容,exams.lua

module(...,package.seeall)
require("odsfile")
xml = require("xml-mod")


function updateZip(zipfile, updatefile)
  local command  =  string.format("zip %s %s",zipfile, updatefile)
  print ("Updating an ods file.\n" ..command .."\n Return code: ", os.execute(command))  
end

-- Object for adding new rows 

function newRow()
  local p = {
    pos = 0,
    cells = {},
    -- Generic function for inserting cell
    addCell = function(self,val, attr,pos)
      if pos then
        table.insert(self.cells,pos,{["text:p"] = val, ["_attr"] = attr})
        self.pos = pos
      else
        self.pos = self.pos + 1
        table.insert(self.cells,self.pos,{["text:p"] = val, ["_attr"] = attr})
      end
    end, 
    addString = function(self,s,attr,pos)
      local attr = attr or {}
      attr["office:value-type"] = "string"
      self:addCell(s,attr,pos)
    end,
    addFloat = function(self,i,attr,pos)
      local attr = attr or {}
      local s = tonumber(i) or 0
      s = tostring(s)
      attr["office:value-type"] = "float"
      attr["office:value"] = s
      self:addCell(s,attr,pos)
    end, 
    findLastRow = function(self,sheet)
      for i= #sheet["table:table-row"],1,-1 do
        if sheet["table:table-row"][i]["_attr"]["table:number-rows-repeated"] then
          return i
        end
      end
    end,
    insert = function(self, sheet, pos)
      local t = {}
      local pos = pos or self:findLastRow(sheet)
      for i=1, #sheet["table:table-column"] do
        table.insert(t,self.cells[i] or {})  
      end
      t = {["table:table-cell"]=t}
      table.insert(sheet["table:table-row"],pos,t)
    end
  }
  return p
end

它有一个函数和一个对象。updateZip(zipfile, updatefile)尝试运行 zip 实用程序,该实用程序必须安装在您的系统中。不幸的是,我的电脑访问权限有些问题,所以它在这里不起作用。相反,我只是使用生成的命令行运行此命令,xml它就可以正常工作。

newRow() 返回具有一些方法的对象:

  • addCell(val,attr,pos)
  • addString(val,attr,pos)
  • addNumber(val,attr,pos) 这些方法将列添加到行中。addCell是通用的,不应该使用。attr 是带有属性的数组,您可以将其留空。pos表示列在行中的位置,也可以是nil
  • insert(sheet, pos) 此函数将行插入到工作表中,您可以使用指定行位置pos

现在进行一些简单的LaTeX包装,exams.sty

\ProvidesPackage{exams}
\RequirePackage{luacode}
\begin{luacode}
exams = require("exams")
require("odsfile")
xml = require("xml-mod")
--exams.test()
row      = {}
sheet    = {}
ods      = {}
filename = ""
\end{luacode}

\newcommand\examspreadsheet[2]{%
 \luaexec{%
  sh = "\luatexluaescapestring{#2}"
  if sh == "" then sh = nil end
  filename = "\luatexluaescapestring{#1}"
  local f   = odsfile.load(filename)
  ods = f:loadContent()
  sheet = odsfile.getTable(ods,sh)
 }
}

\newcommand\savespreadsheet{%
\luaexec{exams.updateZip(filename,"content.xml")}
}
\newcommand\answer[1]{%
\begingroup%
\AddRow%
\AddNumber{\theenumi}{}%
\AddString{#1}{}%
\endAddRow%
\endgroup%
#1%
}
\newenvironment{exam}[1]{%
\section{#1}
\AddRow
\AddString{#1}{}
\endAddRow
\enumerate%
}{\endenumerate%
 \luaexec{%
   f = io.open("content.xml","w")
   f:write(xml.serialize(ods.root))
 }
}

\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 = exams.newRow()%
}
}{%
\luaexec{%
row:insert(sheet,pos)%
}
}

这个包定义了三个用户命令和一个环境:

  • \examspreadsheet{odsfile}{sheet}从文件加载工作表。您可以留空sheet,然后将选择第一张工作表
  • \answer{text}在电子表格中添加行
  • \savespreadsheet保存工作表至ods file
  • 环境exam中所有的答案都应该在里面,最后,它将文件保存content.xml在当前目录中。

注意: 的定义中有一些\answer我找不到的虚假空间。

现在有一些示例文件:

\documentclass{article}
\usepackage{lmodern,exams}
\begin{document}
\examspreadsheet{template.ods}{}

\begin{exam}{Test1}
\item Bla bla \answer{Test of special characters < > \& "}
\item The \answer{definition of entropy} states that \answer{$S = k_B
    \ln \Omega$}. 
\end{exam}
\savespreadsheet
\end{document}

使用以下方式编译

lualatex --shell-escape  sample.tex

如果在控制台输出中收到类似以下错误消息:

zip I/O error: Permission denied

zip error: Could not create output file (was replacing the original zip file)
Updating an ods file.
zip template.ods content.xml
 Return code:   15

然后你必须更改ods文件的文件权限以允许其他用户写入,或者只需运行命令

zip -r template.ods content.xml

结果是openoffice calc

在此处输入图片描述

答案2

这是一个长评论而不是答案。

在 ConTeXt 中,您可以生成与文档结构对应的 XML。例如,与您的 LaTeX 示例对应的 ConTeXt 文档是:

\setupbackend[export=yes]
\definehighlight[answer] %[style=..., color=...]

\starttext
\startsection[title={Test 1}]
  \startitemize[n]
    \item The \answer{definition of entropy} states that \answer{$S=k_B ≤ Ω$}
  \stopitemize
\stopsection
\stoptext

这里最重要的是\setupbackend顶部的命令。我不知道你想如何直观地显示\answer宏的输出,所以我把它留空了。

编译此文件将生成以下\jobname.export文件:

<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>

<!-- input filename   : test              -->
<!-- processing date  : Thu Jul 26 14:21:19 2012 -->
<!-- context version  : 2012.07.19 17:52  -->
<!-- exporter version : 0.30              -->


<document language="en" file="test" date="Thu Jul 26 14:21:19 2012" context="2012.07.19 17:52" version="0.30" xmlns:m="http://www.w3.org/1998/Math/MathML">
  <section detail="section" location='aut:1'>
    <sectionnumber>1</sectionnumber> 
    <sectiontitle>Test 1</sectiontitle> 
    <sectioncontent>
      <itemgroup detail="itemize" symbol="n">
        <item>
          <itemtag>1.</itemtag>
          <itemcontent>The <highlight detail="answer">definition of entropy</highlight> states that <highlight detail="answer"><m:math display="inline"><m:mrow><m:mi>

相关内容