我有一个很坏的习惯,就是将我的包从一个 LaTeX 文档复制粘贴到另一个文档。有没有什么方法可以检测未使用的包(即\usepackage
命令),以便将它们从 LaTeX 文档中删除?
答案1
LaTeX(或其他格式)中的包基本上有三种不同类型:
- 扩展 LaTeX 功能的软件包(例如,
multicol
提供额外的命令和环境来平衡列) - 修改行为和布局但不提供任何附加功能的软件包
- 可同时执行上述两项功能的混合包。
大多数软件包都属于类型 1) 和 3),即大多数软件包仅更改布局,但通常仍提供一些额外的参数化功能,例如,geometry
的主要目的是设置页面布局尺寸,但为了做到这一点,它提供了额外的命令来自定义它(这是类型 3),而不是 2))。但归根结底,这三种类型都存在。
1 类包装
如果您不使用附加功能,则加载或不加载应该没有任何区别。因此,如果您只有这样的包,那么测试将是:“注释掉加载,如果文档编译通过,则您不需要该包”。
2 类包装
这里的情况有所不同:如果不加载它们,文档仍会编译,但输出会有所不同。因此,在这种情况下,测试应该针对对产生的输出的任何更改。
一种简单的测试方法是添加\showoutput
序言。这将为每个输出页面生成一个.log
可以进行比较的精确符号内容结构。
日志首先需要进行一些清理,以便不会比较其他内容,但本质上它会告诉您:当我没有加载包时文件是否发生了变化。
现在,如果包属于这种类型,为什么它并不总是改变所有文档的行为? 只是因为它可能只改变某些方面,而那些特定的方面不会出现在当前文档中。
3 类包裹
作为混合体,您需要对两者进行测试,如果您不使用扩展功能(丢失时会破坏您的文档),您仍然可能会有布局差异。
结论
由于没有适当的分类来说明软件包属于哪个类别,因此必须假设它们都属于类型 3,因此在编译时如果没有软件包,请注意布局差异。缺失的功能将免费测试(因为它们会导致错误)。
此类测试的完全自动化编程可能有点困难,但也不是那么难。
答案2
我尝试过实现弗兰克·米特尔巴赫的提议每次删除一个包,然后测试每个包是否会改变输出。
这是由一个纯用 LaTeX 编写的脚本自动完成的(需要--shell-escape
)。生成的代码非常慢:它必须在文件上运行 TeX 很多次(软件包数量的两倍),并且 TeX 不是解析生成的日志文件的最快工具(实际上,该步骤可以更快)。
要使用它,请将以下代码存储在一个文件中,例如packagecheck.tex
,然后调用您通常输入的命令pdflatex --shell-escape packagecheck <engine> <filename>
。<engine> <filename>
%https://tex.stackexchange.com/questions/78073/detecting-unused-packages-in-latex-document?lq=1
\ifnum\pdfshellescape=1\else
\PackageError{packagecheck}
{Misused\MessageBreak
pdflatex --shell-escape packagecheck\MessageBreak
<engine> <file-name>}
{}
\csname @@end\expandafter\endcsname
\csname bye\expandafter\endcsname
\fi
\RequirePackage{expl3, l3regex}
\ExplSyntaxOn
% \begin{macro}{\log_map_inline:nn, \log_map_inline:Vn}
% \begin{syntax}
% \cs{log_map_inline:nn} \Arg{file name} \Arg{code}
% \end{syntax}
% Goes through the file \meta{file name}\texttt{.log} and performs the
% \meta{code} for each logical line of the log file (assuming that any
% line with $79$ characters in the log file is to be concatenated with
% the following one). The \meta{code} receives the line as |#1|, as a
% string. For instance the following shows each line in |texput.log|.
% \begin{verbatim}
% \log_map_inline:nn { texput } { \tl_show:n {#1} }
% \end{verbatim}
% \begin{macrocode}
\int_const:Nn \c_log_line_count_int { 79 }
\int_new:N \l_log_line_int
\int_new:N \l_log_max_line_int
\ior_new:N \g_log_ior
\str_new:N \l_log_line_str
\str_new:N \l_log_tmp_str
\cs_new_protected:Npn \__log_tmp:n #1 { }
\cs_new_protected:Npn \log_map_inline:nn #1#2 % not nestable
{
%<*trace>
\int_zero:N \l_log_line_int
\ior_open:Nn \g_log_ior { #1 . log }
\ior_str_map_inline:Nn \g_log_ior { \int_incr:N \l_log_line_int }
\ior_close:N \g_log_ior
\msg_term:n
{
Opening~file~#1.log~( \int_use:N \l_log_line_int \ lines).~
One~dot~per~100~lines.
}
\int_zero:N \l_log_line_int
%</trace>
\cs_set_protected:Npn \__log_tmp:n ##1 {#2}
\ior_open:Nn \g_log_ior { #1 . log }
\ior_str_map_inline:Nn \g_log_ior
{
%<*trace>
\int_incr:N \l_log_line_int
\if_int_compare:w \l_log_line_int > 100 \scan_stop:
\tex_message:D { . }
\int_zero:N \l_log_line_int
\fi:
%</trace>
\str_set:Nn \l_log_tmp_str {##1}
\tl_set_eq:NN \l_log_line_str \l_log_tmp_str
\int_until_do:nNnn
{ \str_count:N \l_log_tmp_str } < \c_log_line_count_int
{
\ior_get_str:NN \g_log_ior \l_log_tmp_str
\str_put_right:Nx \l_log_line_str \l_log_tmp_str
}
\exp_args:No \__log_tmp:n \l_log_line_str
}
\ior_close:N \g_log_ior
}
\cs_generate_variant:Nn \log_map_inline:nn { V }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\log_get_packages:nN, \log_get_packages:VN}
% \begin{macrocode}
\seq_new:N \l_log_packages_seq
\seq_new:N \l_log_tmp_seq
\regex_const:Nn \c_package_regex { \w* \. sty }
\cs_new_protected:Npn \log_get_packages:nN #1#2
{
\seq_clear:N \l_log_packages_seq
\log_map_inline:nn {#1}
{
\regex_extract_all:NnN \c_package_regex {##1} \l_log_tmp_seq
\seq_concat:NNN \l_log_packages_seq
\l_log_packages_seq \l_log_tmp_seq
}
\seq_remove_duplicates:N \l_log_packages_seq
\tl_set:Nx #2
{ \seq_map_function:NN \l_log_packages_seq \prg_do_nothing: }
}
\cs_generate_variant:Nn \log_get_packages:nN { V }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\log_get_pages:nN}
% \begin{macrocode}
\tl_new:N \l_log_pages_tl
\str_new:N \l_log_page_str
\bool_new:N \l_log_in_page_bool
\cs_new_protected:Npn \log_get_pages:nN #1#2
{
\tl_clear:N \l_log_pages_tl
\tl_clear:N \l_log_page_str
\bool_set_false:N \l_log_in_page_bool
\log_map_inline:nn {#1} % could be optimized away
{
\bool_if:NTF \l_log_in_page_bool
{
\tl_if_empty:nTF {##1}
{
\bool_set_false:N \l_log_in_page_bool
\tl_put_right:Nx \l_log_pages_tl { { \l_log_page_str } }
}
{ \str_put_right:Nx \l_log_page_str { ##1 \iow_newline: } }
}
{
\str_if_eq_x:nnT
{ Completed } { \__log_get_pages_test:ww ##1 ~ \q_stop }
{
\str_if_eq_x:nnT
{ Completed~box~being~shipped~out~ }
{ \str_substr:nnn {##1} { 1 } { 32 } }
{
\bool_set_true:N \l_log_in_page_bool
\str_put_right:Nx \l_log_page_str { ##1 \iow_newline: }
}
}
}
}
\tl_set_eq:NN #2 \l_log_pages_tl
}
\cs_new:Npn \__log_get_pages_test:ww #1 ~ #2 \q_stop {#1}
\cs_generate_variant:Nn \log_get_pages:nN { V }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}[aux]{\__run:n}
% Compile the file once, halting if there is an error, and in
% batchmode to suppress the output. First output a line to separate
% the output of the internal \TeX{} run from the main run.
% \begin{macrocode}
\cs_new_protected:Npn \__run:n #1
{
\iow_term:x { }
\tex_immediate:D \tex_write:D 18
{
\l_run_engine_str \c_space_tl
-halt-on-error \c_space_tl
-interaction=batchmode \c_space_tl
'\tl_to_str:n { #1 \input }' \c_space_tl
\l_run_file_str
}
}
% \end{macrocode}
% \end{macro}
%
\str_new:N \l_run_engine_str
\str_new:N \l_run_file_str
\tl_new:N \l_run_packages_tl
\tl_new:N \l_run_normal_pages_tl
\tl_new:N \l_run_pages_tl
\seq_new:N \l_run_used_packages_seq
\seq_new:N \l_run_unused_packages_seq
\int_new:N \l_run_package_int
\int_new:N \l_run_package_max_int
\cs_new_protected:Npn \__run_get_pages:nN #1#2
{
\__run:n {#1}
\__run:n { #1 \loggingoutput \batchmode }
\log_get_pages:VN \l_run_file_str #2
}
\cs_new_protected:Npn \run:ww #1~#2~
{
\str_set:Nx \l_run_engine_str {#1}
\str_set:Nx \l_run_file_str {#2}
\regex_replace_once:nnN { \. tex \Z } { } \l_run_file_str
\__run:n { }
\log_get_packages:VN \l_run_file_str \l_run_packages_tl
\__run_get_pages:nN { } \l_run_normal_pages_tl
%<*trace>
\int_set:Nn \l_run_package_max_int { \tl_count:N \l_run_packages_tl }
\int_zero:N \l_run_package_int
%</trace>
\tl_map_inline:Nn \l_run_packages_tl
{
%<*trace>
\int_incr:N \l_run_package_int
\msg_term:n
{
Package~\int_use:N \l_run_package_int of~\int_use:N \l_run_package_max_int
}
%</trace>
\__run_get_pages:nN
{ \expandafter \let \csname ver @ ##1 \endcsname \empty }
\l_run_pages_tl
\tl_if_eq:NNTF \l_run_pages_tl \l_run_normal_pages_tl
{ \seq_put_right:Nn \l_run_unused_packages_seq {##1} }
{ \seq_put_right:Nn \l_run_used_packages_seq {##1} }
}
\seq_if_empty:NF \l_run_used_packages_seq
{
\msg_info:nnxx { run } { used-packages }
{ \seq_count:N \l_run_used_packages_seq }
{
\seq_use:Nnnn \l_run_used_packages_seq
{ ~ and ~ } { , ~ } { , ~ and ~ }
}
}
\seq_if_empty:NF \l_run_unused_packages_seq
{
\msg_warning:nnxx { run } { unused-packages }
{ \seq_count:N \l_run_unused_packages_seq }
{
\seq_use:Nnnn \l_run_unused_packages_seq
{ ~ and ~ } { , ~ } { , ~ and ~ }
}
}
\msg_term:n
{
\fp_to_tl:n { round(\pdfelapsedtime/65536,1) }
\ seconds
}
\tex_end:D
}
\msg_new:nnn { run } { used-packages }
{
\int_compare:nTF { #1 = 1 }
{ The~package~#2~is~useful~in~this~document }
{ The~#1~packages~#2~are~useful~in~this~document. }
}
\msg_new:nnn { run } { unused-packages }
{
Removing~
\int_compare:nTF { #1 = 1 } { the~package~ } { any~of~the~packages~ }
#2~changes~nothing~in~the~resulting~output.
}
\use:n
{
\ExplSyntaxOff
\tex_endinput:D
\exp_after:wN \run:ww
}%
正如我所说,这非常慢,例如,pdflatex --shell-escape packagecheck pdflatex test.tex
在文件上运行test.tex
\documentclass{article}
\usepackage{lipsum}
\usepackage{amsgen}
\begin{document}
\lipsum[1-10]
\end{document}
在我的笔记本电脑上花了 5 分钟才告诉我amsgen
该文档中未使用,但是lipsum
确实使用了。
工作原理:主要工具是调用\immediate\write18{<engine> -halt-on-error -interaction=batchmode <TeX code> <filename>}
其中<engine>
,例如,pdflatex
确保-halt-on-error
TeX 不会尝试继续执行第一个错误(删除某些包时会出现错误),-interaction=batchmode
最小化“内部”运行的输出,<TeX code>
是一些随不同运行而变化的代码,并且<filename>
,嗯,是需要编译的文件的名称。
第一次运行,没有额外的代码。这会生成一个日志文件,我们会在其中查找所有匹配的内容
\w*.sty
(单词字符后跟sty
)。也许我应该搜索[\w\-]*.sty
,或者其他内容。然后运行两次以产生正确的输出,第一次不运行,第二次运行
\loggingoutput\batchmode
,这确保(1)输出页面在日志文件中详细列出(2)没有任何其他内容写入终端。然后,对于每个包,运行两次,第一次不使用,第二次使用
\loggingoutput\batchmode
,通过在每次运行之前定义来禁用该特定包的加载\ver@<package name>.sty
,以使 LaTeX 认为该包已加载。将日志文件中找到的页面与上一步中找到的正常页面进行比较,以确定该包是否有用。最后,显示所有无用包的列表以及已用时间。
答案3
上面由 Vasily Sidorov (@Bazzilic) 提出的解决方案很不错,但也存在一些限制,它只能在 Windows 上运行,仅限于 pdflatex,编辑原始项目的 .tex 文件,......
在与 Vasily 断绝联系后,我分叉了代码,现在它可以在 Linux 和 Windows 上运行。您可以选择任何 latex 引擎:latex、pdflatex、xelatex 和 lualatex,以及用于文献处理的 BibTeX 或 BibLaTeX。它在您的 src 代码副本上运行,并具有 --debug 标志,可留下所有中间文件。
我还添加了一个 --visual 选项,将 DVI/PDF 文件转换为高分辨率 JPG,并比较它们,尽管 PDF 级别存在差异。
它已经经过了广泛的测试,并且运行良好。当然,我们希望听到其他测试人员的意见和改进建议。
代码可从以下位置获取:
https://github.com/TarasKuzyo/LaTeXpkges
答案4
我自己也在寻找这个问题的解决方案,然后偶然发现了这个 tex.SE 主题。意识到这个问题没有真正的解决方案,我擅自编写了一个简单的实用程序脚本来解决这个问题。
该实用程序是用 Python 编写的,它是开源的,可以在这里访问:https://github.com/bazzilic/LaTeXpkges。有关其工作原理的更多信息,请参阅 github 上的自述文件。
它是为 Windows 平台编写的,可能无法在其他平台上使用。它目前处于非常基本的形式,我只在我的论文中测试过它(它运行良好)。如果您在系统上安装了 Python,您可以下载脚本,或者转到 Release 选项卡并下载带有 Windows 可执行文件的 zip(使用 py2exe 创建)。
重要的:做不是针对原始文件运行它!制作一份副本并针对副本运行它!