为什么会有这篇随笔的内容?

最近因为有写一部数据分析教程的计划,主要的平台是 Jupyter,因此涉及到了从编辑好的 Jupyter Notebook 构建 \(\LaTeX\) 文档的问题。在这个过程中我遇到了一些基本的问题,比如:

  • 如何设置自定义的导出样式?
  • 如何设置兼容中文格式?
  • 如何设置导出 Tex 文件的大标题和小标题、作者、日期和摘要信息?
  • 在哪里可以找到开源的 Nbconvert 模板呢?

我翻遍全网找不到几个像样的教程,大部分的模板都是基于 HTML 而不是 \(\LaTeX\) 的。不仅包括国内的中文互联网,就连 GitHub 上,搜索 Jupyter Nbconvert Template 出来的资料也仅仅只有七八页。其中大部分都是 HTML 的模板。同样的,我在 StackOverflow 上也没有找到足够充分的资料。唯一一个可用的解决方案是华东石油大学的同学自己开源的 hakuna-max/course_report,这是一套《数据结构与算法》以及《商务智能分析》课程的课程报告 nbconvert 模板,可以基于该模板将 Jupyter Notebook 转换为符格式要求的课程报告。这个内容是我翻了整整七页的 GitHub repo、几乎已经无望的时候突然找到的。

如果可以的话,我真想写篇博客详细介绍一下这个项目,而我最近又很忙没有时间。所幸原作者很贴心的写了博客文章 从编写到提交:使用Jupyter Notebook完成实验报告,多少可以作为一个参照。

所以诸位现在看到的这篇随笔实际上相当于一篇快报,简单记录一下我在解决这个问题的时候踩的坑,顺便给后来看到本博客文章的人指条明路,指点一下找资料的方向。因为中文互联网上写这个的文档实在太少(反正我是没找到),而我又觉得肯定有很多人跟我有一样的需求。虽然很简陋不全面,但是博客能有我一篇也好。

简述一下我遇到的问题

Nbconvert 转换 .ipynb 文件的基本方法

使用 Nbconvert 转化名为 notebook.ipynb 的方法有两种:你既可以在 Jupyter Notebook 主界面的右上角直接 Download as Tex,也可以在命令行中执行:

jupyter nbconvert ./notebook.ipynb --to latex

当然,毫无疑问你需要将 notebook.ipynb 放在你执行命令的路径下面。

Jupyter Nbconvert 命令还可以指定生成的 \(\LaTeX\) 文件的路径,比如如果我的工作目录下面有一个 latex 文件夹,我就可以执行:

jupyter nbconvert ./notebook.ipynb --to latex --output-dir ./latex/

如果您的 Jupyter Notebook 包含代码运行生成的绘图,那么执行命令之后图画文件会被置于 --output-dir 指定的目录下的 notebook_file 文件夹。需要注意的是,您通过 ![]() 的 Markdown 语句插入的图片并不包含在内,而生成的 \(\LaTeX\) 文件将会保持图片目录的指定原封不动。因此,在编译 .tex 文件之前,你需要确定在 latex 目录下已另存了一份图片文件的副本。

cp ./image ./latex/
cd ./latex/
xelatex ./notebook.tex # 使用 xelatex 编译

Jupyter Nbconvert 构建中文 \(\LaTeX\) 文档的痛点

Jupyter Nbconvert 构建的 \(\LaTeX\) 文档并不会默认包含 CTEX 宏包,同时标题始终为 .ipynb 文件的文件名。如果仅仅只是手工去修改 .tex 文件,添加中文包、缩进设置、修正标题、修改小标题,就会很麻烦。再加上自动构建的 .tex 文档里包含了很多自动生成的文档风格的定义,想要找到一行文字难上加难。

再加上,我们平时在编写 .ipynb 的时候,Markdown 单元格往往都是直接使用 # 作为总标题的,而一级标题则使用 ##。但是通过这种方式构建文档的时候,# 会被当作一级标题——因为 .ipynb 的文件名成了大标题。

考虑到上述的原因,我就想到要使用 Nbconvert 的模板功能。可以使用如下的命令在 Nbconvert 中指定模板:

jupyter nbconvert ./notebook.ipynb --to latex --output-dir ./latex/ --template <模板名>

这里的模板必须是内建的模板。换句话说,如果你想使用自定义的模板,则需要将你的模板文件放置在 Python 安装目录下的 .\Python<版本号>\share\jupyter\nbconvert\templates 目录下面。如果您还没有自己载入过其他任何的模板文件,那么现在您看到的文件夹应该就是默认的模板。

Jupyter Nbconvert 只接受 JinJa2 模板引擎的输入

Nbconvert 的模板一般不会是一个文件,而是一个文件夹以及其中的若干个文件,而在调用模板的时候,则需要在 --template 后面加上 template 路径下的模板文件夹的名字。文件夹中的模板文件名是 .j2 结尾,这是因为应用到了一种名为 JinJa2 的模板引擎。比如如果是 \(\LaTeX\) 模板,则文件以 .tex.j2 结尾;而如果是 HTML 模板就以 .html.j2 结尾。

需要注意的是:您可能在网上见到一些教程告诉你如何使用 --template-file 参数在 Nbconvert 构建 .tex 的时候指定一个 .tplx 模板。然而经过本人的考证,Jupyter Nbconvert 现在的版本确实已经不再使用 .tplx 格式的模板了。这意味着掌握基本的 \(\LaTeX\) 语法不足以让您学会自行制作模板,您必须学习 JinJa2 模板语言。

临时的解决方案

勉强可用的模板文件

因为实在找不到一套行之有效的解决方案,我只好在 hakuna-max 同学的模板的基础上做了一些删减,删去了如 Bib 引用格式、封面页、DataFrame 转 \(\LaTeX\) 表格、图表 Caption 交叉引用和编号以及页眉中华东石油大学的字样之类的内容。由于不懂 JinJa2 也不是很了解 \(\LaTeX\) 的各种高级写法,只好凭着代码直觉去删改。现在已经能够正常展示导出的中文文档。删减阉割后的简易模板文件我会放在本文的附录里面。

Jupyter Notebook 的元数据功能

这套模板设置大小标题和作者姓名需要编辑 Jupyter Notebook 的元数据来设置。元数据编辑这个功能用的比较少,我在这里写一下:如果您在使用 Jupyter Notebook 则元数据编辑功能可以在这里找到:

而如果您在使用 Jupyter Lab 则可以在这里编辑修改元数据:

具体的编辑方法就是添加如下的内容,具体的操作方法可参考原模板作者的博客文章 从编写到提交:使用Jupyter Notebook完成实验报告。由于我做了删改,因此剩下可用的元数据项只有下面这些。注意这些内容都是 JSON 格式,所以必须按照 JSON 的规范,写在最大一级的括号里面。

}
...
"title": "文件的大标题",
"subtitle": "文件的小标题",
"authors": [
{
"name": "作者1"
},
{
"name": "作者2"
},
{
"name": "作者3"
}
],
...
}

附录:模板文件

模板文件一共是四个,三个 JinJa2 文件和一个 JSON。把这些文件复制到 Python 安装目录下的 .\Python<版本号>\share\jupyter\nbconvert\templates\quickreport 文件夹下,注意要按照给定的文件名称来保存。我给文件夹取名 quickreport,实际上你也可以给文件夹随便命名,只是在引用模板的时候要和文件夹同名。比如:

jupyter nbconvert ./notebook.ipynb --to latex --output-dir ./latex/ --template quickreport --debug

记得带上 --debug 参数,否则 Nbconvert 就算出错了也不会有输出。

base.tex.j2 文件

((*- extends 'latex/base.tex.j2' -*))

((* block packages *))
\usepackage{xeCJK}
\usepackage{ctex}
\usepackage{natbib}
\usepackage{setspace}
\usepackage{indentfirst}
\usepackage[twoside]{fancyhdr} ((* block definitions *))
((( super() )))
\onehalfspacing % 设置1.5倍行距
\setlength{\parindent}{2em} % 设置段落首行缩进为2个字符大小
((* endblock definitions *)) ((* block title -*))
((*- set nb_title = nb.metadata.get('title', '') -*))
((*- set nb_subtitle = nb.metadata.get('subtitle', '') -*))
\title{\textbf{((( nb_title | escape_latex ))) \\[20pt] \Large{((( nb_subtitle | escape_latex )))}} \\[25pt]}
((*- endblock title *)) ((*- block input_group -*))
((*- if not cell.metadata.get('hide', False) -*))
((( super() )))
((*- endif -*))
((*- endblock input_group -*)) ((*- block markdowncell scoped -*))
((*- if not cell.metadata.get('hide', False) -*))
((*- if cell.source.startswith('![') -*))
\begin{figure}[htbp]
\centering
((*- set image_path = cell.source.split('](')[1].rstrip(')') -*))
\includegraphics[width=0.8\linewidth]{ (((image_path))) }
\vspace{10pt}
((*- if cell.metadata.figcaption -*))
\caption{(((cell.metadata.figcaption)))}
((*- else -*))
% \textbf{\\ \textcolor{magenta}{Warning: No caption specified. Please set a caption in the notebook metadata.}} \\
((*- endif -*))
((*- if cell.metadata.figlabel -*))
\label{(((cell.metadata.figlabel)))}
((*- endif -*))
\end{figure}
((*- else -*))
((( cell.source | citation2latex | strip_files_prefix | convert_pandoc('markdown+tex_math_double_backslash', 'latex'))))
((*- endif -*))
((*- endif -*))
((*- endblock markdowncell -*)) ((* block predoc *))
((* block maketitle *))
\maketitle
% \thispagestyle{empty} % No page number on the title page
((* endblock maketitle *)) % Reset page numbering for the main content
\pagenumbering{arabic}
\setcounter{page}{1}
\pagestyle{fancy}
\fancyhead{} % clear all header fields
\fancyhead[LO,RE]{\textbf{((( nb.metadata.subtitle | escape_latex )))}}
\fancyfoot[RE,LO]{\textit{\footnotesize{((( nb.metadata.authors | join(', ', attribute='name') )))}}}
((* endblock predoc *)) ((* block postdoc *))
((( make_bibliography() )))
((* endblock postdoc *)) %===============================================================================
% SUPPORT MACROS
%===============================================================================
% Add abstract and keywords
((* macro make_abstract() *))
((*- set nb_abstract = nb.metadata.get('abstract', '') -*))
((*- set nb_keywords = nb.metadata.get('keywords', '') -*))
\begin{center}
((*- if nb_abstract: -*))
\parbox{0.8\columnwidth}{
\textbf{摘要:}(((nb_abstract)))}
\par\vspace{0.5cm}
((*- endif -*))
((*- if nb_keywords: -*))
\parbox{0.8\columnwidth}{\textbf{关键词:}(((nb_keywords)))}
\par\vspace{1cm}
((*- endif -*))
\end{center}
((*- endmacro *)) % Add bibliography
((* macro make_bibliography() *))
((* block bibliography *))
((( add_bibstyle() )))
((( add_bibfile() )))
((* endblock bibliography *))
((* endmacro *)) ((* macro add_bibstyle() *))
((*- set nb_bibstyle = nb.metadata.get('bibstyle', '') -*))
((*- if nb_bibstyle: -*))
\bibliographystyle{(((nb_bibstyle)))}
((*- else -*))
% \textbf{\textcolor{magenta}{Warning: No bibstyle specified. Please set a bibliography style in the notebook metadata.}} \\
((*- endif -*))
((* endmacro *)) ((* macro add_bibfile() *))
((*- if nb.metadata["bibfile"]: -*))
\bibliography{(((nb.metadata["bibfile"])))}
((*- else -*))
% \noindent \textbf{\textcolor{magenta}{Warning: No bibfile specified. Please set a bibliography file in the notebook metadata.}}
((*- endif -*))
((* endmacro *))

conf.json 文件

{
"base_template": "latex",
"mimetypes": {
"text/latex": true,
"text/tex": true,
"application/pdf": true
}
}

document_contents.tex.j2 文件

((*- extends 'display_priority.j2' -*))

%===============================================================================
% Support blocks
%=============================================================================== % Displaying simple data text
((* block data_text *))
\begin{Verbatim}[commandchars=\\\{\}]
((( output.data['text/plain'] | escape_latex | ansi2latex )))
\end{Verbatim}
((* endblock data_text *)) % Display python error text with colored frame (saves printer ink vs bkgnd)
((* block error *))
\begin{Verbatim}[commandchars=\\\{\}, frame=single, framerule=2mm, rulecolor=\color{outerrorbackground}]
(((- super() )))
\end{Verbatim}
((* endblock error *))
% Display error lines with coloring
((*- block traceback_line *))
((( line | escape_latex | ansi2latex )))
((*- endblock traceback_line *)) % Display stream ouput with coloring
((* block stream *))
\begin{Verbatim}[commandchars=\\\{\}]
((( output.text | escape_latex | ansi2latex )))
\end{Verbatim}
((* endblock stream *)) % Display latex
((* block data_latex -*))
((( output.data['text/latex'] | strip_files_prefix )))
((* endblock data_latex *)) % Display markdown
((* block data_markdown -*))
((( output.data['text/markdown'] | citation2latex | strip_files_prefix | convert_pandoc('markdown+tex_math_double_backslash', 'latex'))))
((* endblock data_markdown *)) % Default mechanism for rendering figures
((*- block data_png -*))((( draw_figure(output.metadata.filenames['image/png']) )))((*- endblock -*))
((*- block data_jpg -*))((( draw_figure(output.metadata.filenames['image/jpeg']) )))((*- endblock -*))
((*- block data_svg -*))((( draw_figure(output.metadata.filenames['image/svg+xml']) )))((*- endblock -*))
((*- block data_pdf -*))((( draw_figure(output.metadata.filenames['application/pdf']) )))((*- endblock -*)) % Draw a figure using the graphicx package.
((* macro draw_figure(filename) -*))
((* set filename = filename | posix_path *))
((*- block figure scoped -*))
\begin{center}
\adjustimage{max size={0.9\linewidth}{0.9\paperheight}}{((( filename )))}
\end{center}
{ \hspace*{\fill} \\}
((*- endblock figure -*))
((*- endmacro *)) % Redirect execute_result to display data priority.
((* block execute_result scoped *))
((* block data_priority scoped *))
((( super() )))
((* endblock *))
((* endblock execute_result *)) % Render markdown
((* block markdowncell scoped *))
((( cell.source | citation2latex | strip_files_prefix | convert_pandoc('markdown+tex_math_double_backslash', 'json',extra_args=[]) | resolve_references | convert_explicitly_relative_paths | convert_pandoc('json','latex'))))
((* endblock markdowncell *)) % Don't display unknown types
((* block unknowncell scoped *))
((* endblock unknowncell *))

index.tex.j2 文件

((*- extends 'latex/index.tex.j2' -*))

%===============================================================================
% Latex Article
%=============================================================================== ((*- block docclass -*))
\documentclass[12pt]{article}
((*- endblock docclass -*))

关于 Jupyter Nbconvert 自定义 LaTeX 模板,中文兼容与格式设置,从 Notebook 构建 LaTeX PDF 文档的更多相关文章

  1. [Latex] 所有字体embedded: Type3 PDF文档处理 / True Type转换为Type 1

    目录: [正文] Adobe Acrobat打印解决字体嵌入问题 [Appendix I] Type3转TRUE Type/Type 1 [Appendix II] TRUE Type转Type 1 ...

  2. JAVA使用itext根据模板生成PDF文档

    1.制作PDF模板 网址打开:https://www.pdfescape.com/open/ 我们这里先在线上把基础的内容用word文档做好,然后转成PDF模板,直接上传到网站上,这样方便点 假设我们 ...

  3. 使用TCPDF输出完美的中文PDF文档

    TCPDF是一个用于快速生成PDF文件的PHP5函数包.TCPDF基于FPDF进行扩展和改进.支持UTF-8,Unicode,HTML和XHTML.在基于PHP开发的Web应用中,使用它来输出PDF文 ...

  4. iTextSharp带中文转换出来的PDF文档显示乱码

    刚才有写一个小练习<Html代码保存为Pdf文件>http://www.cnblogs.com/insus/p/4323224.html.马上有网友说,当截取块有中文时,保存的pdf文件将 ...

  5. ITEXT5.5.8转html为pdf文档解决linux不显示中文问题

    在windows中支持中文,在linux中不显示中文. 解决方法:添加字体库 下载simsun.ttc字体文件,把这文件拷贝到Linux系统的 /usr/share/fonts/ 下就可以了.

  6. Ubuntu环境下使用Jupyter Notebook查找桌面.csv文档的方法

    这个问题困扰了我很久,最后在一个老师发来的完成结果里找到了答案.(奇怪的是教材里没有.老师也不讲.尤其是百度也没有啊啊啊啊) 好了进入正题.教材里的原话是这样的 这行代码实现的环境应该是在window ...

  7. 手机打开PDF文档中文英文支持(乱码问题)解决攻略

    电子书的优点很多,随时随地阅读,无论白天黑夜走路坐车都能阅读:想确认一下某句话是不是这本书里的,搜索一下就可以知道:搬家也不用发愁,几万本书带在身上,依然轻松步行.我买了一台平板主要动因就是为了看书, ...

  8. 关于根据模板生成pdf文档,差入图片和加密

    import com.alibaba.fastjson.JSONObject; import com.aliyun.oss.OSSClient; import com.itextpdf.text.pd ...

  9. 国内大学毕业论文LaTeX模板集合

    国内大学毕业论文LaTeX模板集合 薛瑞尼的清华大学学位论文LaTeX模板http://sourceforge.net/projects/thuthesis/ 北大论文文档 LaTeX 模板 pkut ...

  10. 一份不太简短的LaTeX模板

    编译环境: Ubuntu16.04 texllive2016 sublime text3 + latextools 该模板使用与自己写文档,记笔记,记录代码,写作业等等. %!TEX program ...

随机推荐

  1. [Go] Golang defer 与 MySQL 连接关闭的陷阱 (database is closed)

    在 golang 某些 orm 中,你经常会看到这种用法: func main() { db, err := gorm.Open("sqlite3", "test.db& ...

  2. 用 SetWindowPos 方法设置一个停止响应的窗口将卡调用方

    我使用 User32 的 SetWindowPos 方法去设置一个跨进程的窗口,这个窗口是停止响应的,将让调用的 SetWindowPos 方法卡住,不继续执行逻辑.通过堆栈分析是卡在 NtUserS ...

  3. WPF 使用 VideoDrawing 播放视频

    本文告诉大家如何在 WPF 使用 VideoDrawing 进行视频播放 用这个方法有什么优势?其实只是想作为某个控件的背景,某个控件的背景使用视频而已 控件的背景使用 DrawingBrush 传入 ...

  4. SAP HANA计算视图

    Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. 越来越多的SAP用户正在将SAP HANA实施为现有SAP BW的基础和数据库. ...

  5. 【zabbix】snmp监控linux主机

    1.安装net-snmp # yum install -y net-snmp 2.配置文件 /etc/snmp/snmpd.conf 添加如下内容 view systemview included . ...

  6. Swift中的nil

    Swift中的nil和OC中的nil不一样.OC中的nil表示不存在的对象,你无法给NSInteger类型的变量赋值nil,但是Swift中的nil表示不存在,可以给任何Optional的变量或者常量 ...

  7. leaflet 使用kriging.js实现前端自定义插值

    1.GitHub地址:https://github.com/oeo4b/kriging.js 2.核心代码 var variogram = kriging.train(t, x, y, model, ...

  8. Android开发环境配置 JDK及SDK

    已经搭建过无数次开发环境,今天把搭建环境记录下,下次不用去搜索别人博客,有些博客都是复制粘贴,有些关键信息都缺失了. 1.首先第一步:下载JDK,配置JDK环境变量.JDK可以在Oracle官网下载, ...

  9. JDK源码阅读-------自学笔记(十八)(java.lang.Enum枚举类)

    枚举类简介 如果有必要定义一组常量的时候使用 所有的枚举类型隐性地继承自 java.lang.Enum,枚举实质上还是类. 每一个枚举中的成员,就相当于枚举的一个对象,默认都是public stati ...

  10. PasteSpider之接口的授权实现为什么不采用JWT方式

    PasteTemplate序列的接口权限控制使用的都是一套逻辑 包括不限于PasteSpider,PasteTimer,PasteTicker等 大致逻辑一致,具体的细节可能会根据项目做一些调整! 实 ...