我知道有一些命令行应用程序(例如 pdflatex)可以帮助我从 C#.Net 编译 LaTex 文档(使用 System.Diagnostics.Process 类)。
我想知道编译 TeX 文档的最佳命令行工具是什么?
答案1
所以我们又见面了,C#。:)
给出答案并不容易,但我会尝试。
大多数情况下,我们得到的文档很复杂,可能需要多次运行才能达到稳定状态。因此,您可能需要多次运行才能获得正确的输出。
如果您坚持使用 之类的引擎pdflatex
,您可能需要确保对其和/或其他辅助程序(例如makeindex
)的调用次数最少。这可能是一个负担,特别是当您事先不知道步骤时。
latexmk
和等工具rubber
的创建是为了简化编译过程,即尝试猜测您的文档是否需要再次运行,或调用其他程序。如果您的文档永远不会达到稳定状态,它们甚至会放弃编译。因此,它们可能会让您的生活更轻松。但当然,我们也有人类因素。:)
这些工具可能无法按预期工作(例如,rubber
无法识别biber
),因此您可能需要使用配置文件或指令来调整这些工具。
现在,我有了arara
,这是我的一个不起眼的项目(抱歉这么无耻地自我推销)。这个工具与latexmk
和不同rubber
,因为它只会执行您告诉它的内容。没有猜测,没有辅助文件解析或其他很酷的东西。通过向源代码添加指令(例如,% arara: pdflatex: { shell: yes }
,arara
将根据明确定义的规则将这些指令扩展为命令,然后执行它们。如果您真的知道引擎内部发生了什么,它可能会很强大,否则可能很难理解这个概念。由于 Marco Daniel(项目合作者)和 Joseph Wright 的巨大贡献,该项目仍在大力开发中。
现在回到你的问题。:)
如果您能告诉我们您的 C# 应用程序,那就太好了。有时,根据您的需求,可以使用更可靠的解决方案。例如,宽恕者的回答是一种有趣的调用方式pdflatex
。如果您需要更通用的配置,可以使用以下几行中的某些内容:
using System;
using System.Xml;
using System.Diagnostics;
namespace MyProgram
{
class MyTeXWrapper
{
static void Main(string[] args)
{
if (args.Length != 2)
{
Console.WriteLine("Sorry, I expect 2 arguments.");
System.Environment.Exit(-1);
}
string command = "";
string arguments = "";
try
{
XmlDocument document = new XmlDocument();
document.Load(args[1]);
XmlElement rootElement = document.DocumentElement;
XmlNodeList nodes = rootElement.ChildNodes;
foreach (XmlNode node in nodes)
{
if (node.Name.Equals("command"))
{
command = node.InnerText;
}
else
{
if (node.Name.Equals("arg"))
{
if (node.Attributes.Count == 1)
{
if (node.Attributes.Item(0).Name.Equals("value"))
{
arguments = arguments + node.Attributes.Item(0).Value + " ";
}
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
arguments = arguments + args[0];
Process process = new Process();
process.StartInfo.FileName = command;
process.StartInfo.Arguments = arguments;
process.StartInfo.UseShellExecute = false;
process.Start();
process.WaitForExit();
if (process.ExitCode == 0)
{
Console.WriteLine("Command was successfully executed.");
}
else
{
Console.WriteLine("An error occurred.");
}
}
}
}
这个想法是有一个基于 XML 的配置文件,它将决定如何调用命令。示例配置文件:
<configuration>
<command>pdflatex</command>
<arg value="--interaction=nonstopmode"/>
<arg value="--shell-escape"/>
</configuration>
考虑以下tex
文件:
\documentclass{article}
\begin{document}
Hello world!
\end{document}
现在,我可以使用以下命令运行我的代码MyProgram.exe hello config.xml
:
我用单核细胞增多症。:)
请注意,我使用了--interaction=nonstopmode
作为一个参数。根据您的应用程序的行为方式,使用交互式命令可能会有问题。例如,如果我们tex
上面的代码有语法错误,并且没有--interaction=nonstopmode
,pdflatex
则会提示您一条消息并等待您的输入。对于arara
我将输出流(stdout
和stderr
)重定向到单独的线程,但我仍然在努力处理输入流(stdin
)。例如,如果程序需要交互,TeXworks 可以提示输入框:
值得庆幸的是,TeX 程序提供了退出状态,因此您可以检查运行是否成功。如果您有一个非常复杂的文档,也许创建一个工作线程并让它处理调用是明智之举,避免阻塞您的应用程序(David 在聊天室中提到 Frank 的书大约需要 20 分钟才能编译完成)。:)
那么,哪种工具最好呢?我完全不知道。:)
我的想法是:如果你想更好地控制你的工作流程,请坚持使用原始的命令行程序。现在,灵活性和易用性是这里的关键词,不妨latexmk
一rubber
试。:)
答案2
步骤 1:创建批处理文件
为了避免重新编译 C# 源代码以与要使用的 (La)TeX 编译器同步,创建批处理文件会很方便。批处理文件在后台执行 (La)TeX 编译器。将批处理文件保存在任何位置,并将 PATH 系统变量设置为批处理位置。
最简单的批次可能如下
rem %1 represents the file name with no extension.
pdflatex -shell-escape %1
步骤 2:创建 C# 代码并编译
最简单的例子如下:
using System.Diagnostics;
class MyApp
{
public static void Main(string[] args)
{
Process p1 = new Process();
p1.StartInfo.FileName = "batch.bat";
p1.StartInfo.Arguments = args[0];
p1.StartInfo.UseShellExecute = false;
p1.Start();
p1.WaitForExit();
}
}
使用 C# 编译器进行编译以获取MyApp.exe
示例。
第 3 步:完成,您就可以尝试了
MyApp.exe
需要一个参数,即没有扩展名的 TeX 输入文件名。
从命令提示符下,执行MyApp.exe TeXInputFileNameWithNoExtension
。
答案3
我想提供一个使用该工具的答案arara
。
该工具arara
有详尽的文档
- 在开发者网站上:阿拉拉-- 酷炫的 TeX 自动化工具
- 可打印文档:阿拉拉-- pdf 文档
您还可以查看 Joseph 撰写的一篇小博客文章: arara:按照自己的方式制作 LaTeX 文件
根据给定的链接,您可以.tex
使用 编译文件arara filename
。编译过程可以通过 中的规则指定.tex
。例如,一个简单的 |pdflatex| 如下所示:
% arara: pdflatex
arara
提供了直接在 |.tex| 文件内设置选项的可能性。
试一下arara
! ;-)
答案4
我编写了一个后台工作程序来捕获写入 pdflatex 日志文件的内容。然后我检查每条写入的消息是否有“重新运行”一词。如果出现这种情况,则必须重新运行。我想我自己并没有完全发现这一点,但在某个地方找到了原理。
我快速地剪切并粘贴了一些内容,但您肯定可以看到它的作用。
private void BackgroundWorker_DoWork(object worker, DoWorkEventArgs e)
{
var latexCompiler = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName =
Config.LatexCompiler,
WindowStyle = ProcessWindowStyle.Minimized,
Arguments = Config.LatexArgs
+ $" -job-name={run} --recorder -record-package-usages=MyApp.packages -output-directory=\"{ProgramOutputFolder}{ProjectName}\""
+ $" \"{ProgramOutputFolder}{LatexOut}\"",
RedirectStandardOutput = true,
RedirectStandardError = false,
UseShellExecute = false,
CreateNoWindow = true
}
};
latexCompiler.OutputDataReceived +=
(sender, ev) =>
{
if (!doRerun && !string.IsNullOrEmpty(ev.Data))
{
if (ev.Data.Contains("Rerun"))
{
doRerun = true;
latexCompiler.CancelOutputRead();
}
}
};
rerun = 0;
var prgrs = progress;
do
{
doRerun = false;
latexCompiler.Start();
latexCompiler.BeginOutputReadLine();
latexCompiler.WaitForExit();
latexCompiler.CancelOutputRead();
rerun++; // may need more than 1 run to resolve references etc.
((BackgroundWorker) worker).ReportProgress(progress += (100 - prgrs) / 3);
} while (rerun < 5 && doRerun); // max 5 runs for safety.
// report progress to the main thread:
((BackgroundWorker) worker).ReportProgress(progress = 100);
// sleep, so the "ready" message is displayed for some time:
Thread.Sleep(2000);
// now we may exit, so the "ready" message can be removed.
// The PDF will be on disk.
e.Result = latexCompiler.ExitCode; // should be 0.
}