关于 Jupyter Nbconvert 自定义 LaTeX 模板,中文兼容与格式设置,从 Notebook 构建 LaTeX PDF 文档
为什么会有这篇随笔的内容?
最近因为有写一部数据分析教程的计划,主要的平台是 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 文档的更多相关文章
- [Latex] 所有字体embedded: Type3 PDF文档处理 / True Type转换为Type 1
目录: [正文] Adobe Acrobat打印解决字体嵌入问题 [Appendix I] Type3转TRUE Type/Type 1 [Appendix II] TRUE Type转Type 1 ...
- JAVA使用itext根据模板生成PDF文档
1.制作PDF模板 网址打开:https://www.pdfescape.com/open/ 我们这里先在线上把基础的内容用word文档做好,然后转成PDF模板,直接上传到网站上,这样方便点 假设我们 ...
- 使用TCPDF输出完美的中文PDF文档
TCPDF是一个用于快速生成PDF文件的PHP5函数包.TCPDF基于FPDF进行扩展和改进.支持UTF-8,Unicode,HTML和XHTML.在基于PHP开发的Web应用中,使用它来输出PDF文 ...
- iTextSharp带中文转换出来的PDF文档显示乱码
刚才有写一个小练习<Html代码保存为Pdf文件>http://www.cnblogs.com/insus/p/4323224.html.马上有网友说,当截取块有中文时,保存的pdf文件将 ...
- ITEXT5.5.8转html为pdf文档解决linux不显示中文问题
在windows中支持中文,在linux中不显示中文. 解决方法:添加字体库 下载simsun.ttc字体文件,把这文件拷贝到Linux系统的 /usr/share/fonts/ 下就可以了.
- Ubuntu环境下使用Jupyter Notebook查找桌面.csv文档的方法
这个问题困扰了我很久,最后在一个老师发来的完成结果里找到了答案.(奇怪的是教材里没有.老师也不讲.尤其是百度也没有啊啊啊啊) 好了进入正题.教材里的原话是这样的 这行代码实现的环境应该是在window ...
- 手机打开PDF文档中文英文支持(乱码问题)解决攻略
电子书的优点很多,随时随地阅读,无论白天黑夜走路坐车都能阅读:想确认一下某句话是不是这本书里的,搜索一下就可以知道:搬家也不用发愁,几万本书带在身上,依然轻松步行.我买了一台平板主要动因就是为了看书, ...
- 关于根据模板生成pdf文档,差入图片和加密
import com.alibaba.fastjson.JSONObject; import com.aliyun.oss.OSSClient; import com.itextpdf.text.pd ...
- 国内大学毕业论文LaTeX模板集合
国内大学毕业论文LaTeX模板集合 薛瑞尼的清华大学学位论文LaTeX模板http://sourceforge.net/projects/thuthesis/ 北大论文文档 LaTeX 模板 pkut ...
- 一份不太简短的LaTeX模板
编译环境: Ubuntu16.04 texllive2016 sublime text3 + latextools 该模板使用与自己写文档,记笔记,记录代码,写作业等等. %!TEX program ...
随机推荐
- netcore3.1 程序在cento8下运行selenium
我需要在linux下运行selenium抓取数据,本人不熟悉Python,所以只能用netcore.在带linux界面上运行爬取程序,驱动chromedriver比较简单.界面化安装好chrome,下 ...
- pde复习笔记 第一章 波动方程 第三节 分离变量法
教材 谷超豪<数学物理方程>第四版,虽然我们老师用的第三版,但是除了页码对不上,习题多了一点,也似乎没有多少区别. 打算开个新栏专门总结一下pde的各种计算问题,在图书馆算的手麻了,但是习 ...
- surging版本有哪些?
surging 一直在升级开发各个版本,以下是各个版本的费用,有需要可以联系或者可以聘请我为架构师,技术顾问,后期会升级多语言版本,推广至海外. 基于surging 的物联网技术架构 平台版本:
- 7、yum 仓库服务与 PXE 网络装机
1.部署 yum 软件仓库 1.1.使用本地 yum 挂载 umount /dev/sr0 mount /dev/sr0 /media/mkdir /root/yum.bakmv /etc/yum.r ...
- ruby操作excel
操作xlsx axlsx插件 操作xls spreadsheet插件
- 一个list分成 list长度/step_length 向上取整个小list集合
一.具体实现方法 /** * 将一个list按照新的步长分成list长度/step_length 向上取整个小list * @param list * @param step_length * @re ...
- 虚拟服务器VirtualBox不要太好用
在工作和学习前端的路上遇到过太多的坑,就是跳进坑里了,还要勇敢的爬起来. 本章真的想真心实意的推荐一下,超好用的虚拟服务器.你还在纠结window环境和Mac本的区别吗?是不是上班用的window电脑 ...
- C语言:贮油点建设问题(详解题目意思)
!!!!先看解析,后面附有代码!!!!!!! ,希望大家不懂的能认真看看,这些都是我在写的过程中不能理解,遇到的困难,然后弄懂之后总结出来给大家的,想学的一定要认真看完. 规律是: 贮油点之间相差50 ...
- 【C#】做一个winform版本的软考成绩查询软件
返回的json SWCJ代表 上午的成绩 XWCJ代表下午的成绩. 主要步骤: 1. 获取验证码图片 2. 获取cookie 3. 发送验证验证码请求 4 发送成绩查询请求,并获取返回的json ...
- .net C# System.Text.Json 如何将 string类型的“true”转换为布尔值 解决方案
直接上解决方法的代码 先定义一个转换顺,代码如下: public sealed class AnhBoolConverter : JsonConverter<bool?> { public ...