我试图将包含(并且需要保留)换行符和各种特殊字符(例如ä
、$
、"
、\
、 ...)的数据传递给命令。这个想法是将数据写入文件,通过创建图片的外部解析器运行它,最后包含图片。这将允许将各种语言(可以解析为图像或 PDF 等)直接写入文件中LaTeX
。
除了令我疑惑的特殊字符外,我已经让它工作了:
- 我怎样才能按原样传递和使用数据(当然,除非它包含类似
\end
)?最好的/最简单/最万无一失/最广泛解决这个问题的方法是什么?
答案1
我假设您希望一个宏/命令可以在外部文本文件中生成 .tex 输入文件那部分的精确副本,该副本在 .tex 输入文件中表示该宏的参数。
如果您没有要求命令/如果环境足够,我建议使用filecontents*
LaTeX 2ε 内核的环境。
对于 2019 年秋季之前的 LaTeX 版本,filecontents*
环境只能在文档的序言中使用,并且不会覆盖现有文件。
但有一个包文件内容通过提供相同的环境来消除这些限制,其中环境的使用不仅限于序言,并且现有文件将被覆盖。
因此,您可以使用filecontents*
-environment 创建临时文件,这些文件可以在通过\input
/ \include
/ \read
/whatsoever 读取它们之后立即被覆盖,或者在通过其他程序对它们进行后期处理之后,后期处理可能可以通过 TeX 的功能从 (La)TeX-run 内部启动\write18
。
\write18
对-feature的一些解释可以在https://www.texdev.net,由编写和维护约瑟夫·赖特。
当使用较新的基于 LuaTeX 的引擎时,\write18
由于这些引擎提供了实现此功能的方法,因此默认情况下此功能不可用,壳牌-package 可能会引起人们的兴趣。
如果您希望创建 .eps 格式的图像文件并使用 .eps\includegraphics
文件图形-package 运行 pdflatex/xelatex 来创建 .pdf 文件时,包epstopdf可能会引起人们的兴趣。该软件包在 pdflatex 运行期间“动态”将 .eps 文件转换为 .pdf 文件。对于较新的 TeXLive 发行版,当用于包含 .eps 图像时,pdflatex 将epstopdf
在后台调用。对于其他 TeX 发行版\includegraphics
epstopdf可能需要明确加载后 图形。
下面是一个示例,其中filecontents*
-environment 用于创建一个 .eps 文件(其名称将是 .tex 文件的文件名 + 短语Picture
,扩展名为.eps
)和一个包含 latex 图片环境的文本文件(其名称将是 .tex 文件的文件名 + 短语Picture
,扩展名为.lpe
—lpe 是我对它的缩写大号aTeX-磷图片-埃环境)。
我目前使用 TeXLive 2019 的 pdflatex,其中 .eps 到 .pdf 的转换是在后台自动完成的,因此在我的系统上,该示例可以毫无问题地进行编译。
如果将示例另存为test.tex
,则(除了常见的辅助文件和 .log 文件之外)文件testPicture.eps
和testPicture.lpe
将在使用 pdflatex 编译期间创建。testPicture.eps
是一个 .eps 图像,可以使用 GSview 或 gv 等进行查看。testPicture.lpe
是一个包含 -environment 的文本文件picture
。将 .eps 文件转换为 .pdf 将提供一个带有 .pdf 图像的文件,其名称为testPicture-eps-converted-to.pdf
。
\documentclass[a4paper]{article}
%\usepackage{shellesc} % <- implements the \write18-interface (which is
% needed by epstopdf) for more recent LuaTeX-
% based engines
\usepackage{graphicx}
%\usepackage{epstopdf}
% An .eps graphic by means of filecontents:
\begin{filecontents*}{\jobname Picture.eps}
%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: 0 0 50 50
%%Title: (EXAMPLE.EPS)
%%CreationDate: 18/02/2020 18:09:07 +0200
%%EndComments
newpath 25 25 24 0 360 arc stroke
newpath 25 25 18 225 315 arc stroke
newpath 18 33 2 0 360 arc fill stroke
newpath 32 33 2 0 360 arc fill stroke
showpage
%%EOF
\end{filecontents*}
% A LaTeX 2e picture-environment by means of filecontents:
\begin{filecontents*}{\jobname Picture.lpe}
\begingroup
\unitlength=1pt\relax
\thicklines
\begin{picture}(51,51)(0,0)
\put(26,26){\circle{40}}
\put(19,34){\circle*{4}}
\put(33,34){\circle*{4}}
\put(26,22){\oval(24,24)[b]}
\end{picture}
\endgroup
\end{filecontents*}
\begin{document}
\section{A picture by LaTeX's picture-environment}
\begin{figure}[h]
\centering
\input{\jobname Picture.lpe}
\caption{A picture by LaTeX's picture-environment}
\end{figure}
\section{An .eps-graphic (probably auto-converted to .pdf via the pstopdf-package)}
\begin{figure}[h]
\centering
\includegraphics{\jobname Picture.eps}%
\caption{An .eps-graphic (probably auto-converted to .pdf via the pstopdf-package)}
\end{figure}
\end{document}
顺便一提:
默认情况下,filecontents
/环境会将水平制表符(在 TeX符号filecontents*
中,ASCII 和 Unicode 中的代码点编号均为 9)转换为空格。如果您不想这样,在许多 TeX 平台上,只需在应用/之前将水平制表符的类别代码临时设置为 12(其他)即可。^^I
^^
filecontents
filecontents*
它可能看起来像这样:
\begingroup
\catcode`\^^I=12 %
\begin{filecontents*}
[...the content of the filecontents*-environment...]
\end{filecontents*}
\endgroup
到目前为止,我详细说明了filecontents*
哪一个是环境。
但你没有要求环境。
你要求的是命令。
在介绍如何实现一个示例之前,我想先对 TeX 做一些初步的说明。命令:
TeX 使用 .tex 输入文件作为生成标记的指令。例如,控制序列标记。例如,字符标记。这些标记被附加到标记流中。这发生在 TeX 的嘴里。标记流进入 TeX 的食道。在这里,可扩展标记被扩展。扩展的结果到达胃部进行进一步处理。
关键点是:
TeX 对于创建标记有细致的规则。
例如,每当 TeX 开始读取一行输入时,它首先会做的事情是:
- 将字符从计算机平台的字符编码方案转换为 TeX 引擎的内部字符表示方案,传统 TeX 引擎为 ASCII,而基于 LuaTeX/XeTeX 的 TeX 引擎为 Unicode。
- 删除行尾的所有空格字符。(在 TeX 引擎的内部字符表示方案中,空格字符的代码点编号为 32。)
- 在行尾附加一个字符,该字符在 TeX 引擎的内部字符表示方案中的代码点编号与整数参数的值相对应
\endlinechar
。如果该值超出了 TeX 引擎的内部字符表示方案中可能的代码点编号范围,则不会附加任何字符。
第 1 项意味着写入外部文件需要一个例程来转换回计算机平台的字符编码方案。对于许多 TeX 平台,可以通过所谓的 TeX-character-translation-files/.tcx-files 配置此例程。在某些 TeX 平台/某些情况下,该例程可能被配置^^
为为某些特殊字符提供 -notation,尽管您的计算机平台可以处理这些字符。这可能会导致复制的输出与 .tex 输入文件中输入的内容不同。
第 2 项意味着 TeX 永远不会以任何方式处理 .tex 输入文件中行尾的空格字符。
即,您永远不能使用 TeX 复制 .tex 输入文件中行末的一系列空格字符。
例如,类别代码 0(转义)的字符(通常反斜杠字符属于该类别代码)被视为信号,表示不从单个字符收集字符标记(类别代码/字符代码对),而是从后续字符收集控制序列标记的名称。(我只想传达一个大致的画面,因此我在这里省略了从 .tex 输入文件收集控制序列标记名称的确切规则。)
例如,在创建控制字标记之后,读取设备被设置为状态 S(跳过空格),这意味着类别代码 10(空格)的后续输入字符将被忽略/跳过/删除,而不是产生标记。
例如,.tex 输入文件中类别代码 14(注释)的字符会导致 TeX 忽略/跳过/丢弃 .tex 输入文件当前行的剩余输入字符,而不是从中形成标记,并将读取装置切换到状态 N(新行)并开始读取 .tex 输入文件的下一行(如果存在)。在状态 N 下,.tex 输入文件中发现的类别代码 10(空格)的字符将被忽略/跳过/丢弃,而不是产生标记。
例如,.tex 输入文件中类别代码 5(行尾)的字符会导致 TeX 忽略/跳过/丢弃 .tex 输入文件当前行的剩余字符,并根据读取装置的状态向标记流附加(在状态 N,新行中)标记\par
,或(在状态 M,行中间)空格标记,或(在状态 S,跳过空格)根本不附加标记,并且将读取装置切换到状态 N(新行)并开始读取 .tex 输入文件的下一行(如果存在)。
您是否注意到了 .tex 输入中的字符与字符标记(带有字符代码和类别代码)之间的区别,这些字符标记是在 TeX 程序运行时产生的,并在编译期间进行处理?
是因为它可能。
所有这些微妙之处都与 TeX 在读取 .tex 输入文件和创建标记时如何根据类别代码和读取设备的状态处理输入字符有关,从而得出这样的结论:如果您希望“按原样”处理参数,则需要切换到类别代码机制,在该机制中,在标记化过程中不会忽略/跳过/删除 .tex 输入的任何字符,但每个字符都会产生一个(字符)标记,在将标记写入文件时,将从中恢复该字符,并且读取设备在标记化一行 .tex 输入时将始终保持状态 M(行中间),并且在任何情况下,在遇到一行 .tex 输入的第一个字符时都会从状态 N 切换到状态 M,从而不会忽略 .tex 输入行开头的空格字符。 (上面提到的第 2 项意味着即使在这样的类别代码制度下,您也无法阻止忽略 .tex-input 行中只包含空格字符的空格字符。)
在这种情况下,命令\verb|...|
和环境(暂时)切换到类别代码制度。此后,此类别代码制度称为verbatim
“逐字逐句-猫码制度”。
TeX 对于将标记写入外部文本文件有微妙的规则。
例如, -指令将始终扩展可扩展标记(宏和一些原语),除非它们来自在操作期间\write
通过扩展标记寄存器,或者它们作为\the
\write
⟨平衡文本⟩\unexpanded
操作期间的指令\write
。
例如,类别代码 6(参数)的显式字符标记,即诸如井号(#
)之类的内容,在书写时将会加倍。
例如,TeX 在未扩展写入控制字标记时总是附加一个空格字符。
例如,\write
-指令旨在写入一行文本。因此⟨你所在平台的换行符⟩\write
将被附加到由-directive 从其写入参数中形成的字符之后。
\immediate\write\MyNiceFile{\noexpand\LaTeX\relax}
将传递 来自不同-directive 的内容在文本文件的同一行中是不可能的。
\LaTeX⟨space character⟩\relax⟨space character⟩⟨line-break-characters of your platform⟩
\write
关于“按原样使用/写入参数”的另一个有趣的问题是如何处理 .tex-input-file 中缩进的代码。
我尝试了一种方法,其中 xparser 的+v
-argument-type(+v
表示要从 verbatim-catcode-régime 下的 .tex-input-file 中读取和标记参数)用于读取/标记参数,以便
- 换行符等均被保留,
- 哈希值不会翻倍,
- 不会丢失空格字符(但会丢失行尾的空格字符)等,
^^
-输入中的符号将不会被转换,
并且通过令牌寄存器进行写入,从而抑制了不必要的活动字符扩展:
通过以下示例 定义宏(及其所有辅助宏)。
\MyWriteFile{⟨text-file's name⟩}{⟨text-file's content⟩}
该宏重新创建了一个名为的文本文件⟨文本文件的名称⟩并写道⟨文本文件的内容⟩對它來說。
在下面的示例中,该宏用于创建文本文件MyFirstBizarreTemp.tex
和MySecondBizarreTemp.tex
。
这\verbatiminput*
逐字-package 用于显示这些文本文件的内容。
\documentclass{article}
\usepackage{xparse}
\usepackage{verbatim}% <- \verbatiminput* from that package is used for
% displaying the content of text-files
% that got created during compilation.
% The package is not used otherwise.
\makeatletter
%%=============================================================================
%% Check whether argument is empty:
%%=============================================================================
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral0\if\relax\detokenize{#1}\relax
\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{\@firstoftwo{\expandafter}{} \@firstoftwo}%
{\@firstoftwo{\expandafter}{} \@secondoftwo}%
}%
%%=============================================================================
%% Exchange arguments
%%=============================================================================
\newcommand\UD@Exchange[2]{#2#1}%
%%/////////////////////////////////////////////////////////////////////////////
%% Code for \ReadAndProcessVerbatimizedStuff
\begingroup
% Dummy-definition, will be overridden. Is used only to get ^^M of
% category code 12(other) as #1 into subsequent definition-texts:
\NewDocumentCommand\UD@CheckWhetherLeadingEndl{+m}{%
\endgroup
%%===========================================================================
%% Check whether_verbatimized_ argument starts with a endline-character
%%===========================================================================
%% \UD@CheckWhetherLeadingEndl{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% endline-charactern>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a endline-character>}%
\newcommand\UD@CheckWhetherLeadingEndl[1]{%
\UD@@CheckWhetherLeadingEndl\UD@SelDom##1\UD@SelDom#1\UD@@SelDom
}%
\@ifdefinable\UD@@CheckWhetherLeadingEndl{%
\long\def\UD@@CheckWhetherLeadingEndl##1\UD@SelDom#1##2\UD@@SelDom{%
\UD@CheckWhetherNull{##2}{\@secondoftwo}{\@firstoftwo}%
}%
}%
%%===========================================================================
%% Remove one leading endline-character from _verbatimized_ argument
%%===========================================================================
\@ifdefinable\UD@@TrimLeadingEndl{\long\def\UD@@TrimLeadingEndl#1{}}%
%%===========================================================================
%% Check whether _verbatimized_ argument ends with a endline-character
%%===========================================================================
%% \UD@CheckWhetherTrailingEndl{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s last token is a
%% endline-charactern>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s last token is not
%% a endline-character>}%
\newcommand\UD@CheckWhetherTrailingEndl[1]{%
\UD@@CheckWhetherTrailingEndl##1\UD@SelDom#1\UD@SelDom\UD@@SelDom
}%
\@ifdefinable\UD@@CheckWhetherTrailingEndl{%
\long\def\UD@@CheckWhetherTrailingEndl##1#1\UD@SelDom##2\UD@@SelDom{%
\UD@CheckWhetherNull{##2}{\@secondoftwo}{\@firstoftwo}%
}%
}%
%%===========================================================================
%% Remove one trailing endline-character from _verbatimized_ argument
%%===========================================================================
\newcommand\UD@TrimTrailingEndl[1]{\UD@@TrimTrailingEndl##1\UD@SelDom}%
\@ifdefinable\UD@@TrimTrailingEndl{%
\long\def\UD@@TrimTrailingEndl##1#1\UD@SelDom{##1}%
}%
%%===========================================================================
%% Remove one leading and one trailing endline-character from _verbatimized_
%% argument if present. Due to \romannumeral0-expansion the result is
%% delivered in 2 expansion-steps:
%%===========================================================================
\newcommand\UD@RemoveLeadingNTrailingEndl[1]{%
\romannumeral0%
\UD@CheckWhetherLeadingEndl{##1}{%
\UD@CheckWhetherTrailingEndl{##1}{%
\UD@Exchange{ }{%
\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter
}%
\expandafter\UD@TrimTrailingEndl\expandafter{\UD@@TrimLeadingEndl##1}%
}{%
\UD@Exchange{ }{\expandafter}%
\UD@@TrimLeadingEndl##1%
}%
}{%
\UD@CheckWhetherTrailingEndl{##1}{%
\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\UD@TrimTrailingEndl{##1}%
}{ ##1}%
}%
}%
%%===========================================================================
%% \ReadAndProcessVerbatimizedStuff{%
%% <Processor-macro><Processor-macro's arguments except Processor-macro's
%% last argument>%
%% }{<Argument to be read and tokenized under verbatim-catcode-regime>}
%%
%% yields:
%%
%% <Processor-macro><Processor-macro's arguments except Processor-macro's
%% last argument>{<Argument to be read and tokenized under verbatim-
%% catcode-regime>}
%%
%% I.e.,
%% \ReadAndProcessVerbatimizedStuff reads and tokenizes under verbatim-
%% catcode-régime from the .tex-input-file the last argument for
%% <Processor-macro> .
%%
%%===========================================================================
\newcommand*\ReadAndProcessVerbatimizedStuff[1]{%
% ##1 = file-name
\begingroup
% Make sure horizontal tabs won't cause problems when
% \ReadAndProcessVerbatimizedStuff reads its +v-argument:
\@makeother\^^I%
\InnerReadAndProcessVerbatimizedStuff{##1}%
}%
\NewDocumentCommand{\InnerReadAndProcessVerbatimizedStuff}{m+v}{%
% ##1 = file-name
% ##2 = argument that got read and tokenized under verbatim-catcode-regime.
\endgroup
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter{\UD@RemoveLeadingNTrailingEndl{##2}}}{##1}%
}%
}%
%%-----------------------------------------------------------------------------
%% Now let's change the catcode of ^^M and then call the dummy-definition
%% of \UD@CheckWhetherLeadingEndl so that it can fetch the catcode-12-^^M,
%% close the group, override itself and define all those macros where that
%% catcode-12-^^M is needed:
\catcode`\^^M=12 %
\UD@CheckWhetherLeadingEndl{^^M}%
%%End of code for \ReadAndProcessVerbatimizedStuff
%%/////////////////////////////////////////////////////////////////////////////
%%=============================================================================
%% Let's define a processor-macro whose first argument is a file-name
%% and whose second argument is the verbatimized-argument:
%% \MyWriteFileProcessor{<file>}{%
%% <argument that already got tokenized under
%% verbatim-catcode-regime>%
%% }
%% writes the <argument that already got tokenized under verbatim-
%% catcode-regime> to file if the <file> does
%% not exist. If the <file> does exist, you get a message on screen.
%%=============================================================================
\newcommand\MyWriteFileProcessor[2]{%
\begingroup
\openin\@inputcheck#1\relax
\ifeof\@inputcheck
\@latex@warning@no@line{Writing file `\@currdir #1'}%
\chardef\reserved@c15\relax
\ch@ck7 \reserved@c\write
\immediate\openout\reserved@c#1\relax
\toks@{#2}%
\begingroup
% Make sure endline-chars (carriage-return, ^^M in ^^-notation )
% yield linebreaks:
\newlinechar=`\^^M %
\immediate\write\reserved@c{\the\toks@}%
\endgroup
\immediate\closeout\reserved@c#1\relax
\else
\closein\@inputcheck
\@latex@warning@no@line{%
File `#1' already exists on the system.\MessageBreak
Not generating it from this source%
}
\fi
\endgroup
}%
%%=============================================================================
%% Let's define a macro which reads things verbatimized and writes them to file
%% \MyWriteFile{<file-name>}{<Argument to be read and tokenized
%% under verbatim-catcode-regime and to be written to a new file
%% with name <file-name> "as is">}
%%=============================================================================
\newcommand\MyWriteFile[1]{%
% #1 = <file-name> ;
% \ReadAndProcessVerbatimizedStuff will read and tokenize the
% <Argument to be read and tokenized under verbatim-catcode-
% regime> and append the result as another argument to
% \MyWriteFileProcessor{<file-name>}
\ReadAndProcessVerbatimizedStuff{\MyWriteFileProcessor{#1}}%
}%
\makeatother
\pagestyle{empty}
\begin{document}
\enlargethispage{4cm}
\vspace*{-2cm}
\noindent\textbf{The input}
\begin{verbatim*}
\MyWriteFile{MyFirstBizarreTemp.tex}|
[This time the content is within verbatim-delimiters.]
{%
\Huge
another
piece
of
text%
äöü
}\\
{\scriptsize another piece of text}
X|
\end{verbatim*}
\MyWriteFile{MyFirstBizarreTemp.tex}|
[This time the content is within verbatim-delimiters.]
{%
\Huge
another
piece
of
text%
äöü
}\\
{\scriptsize another piece of text}
X|
\noindent\textbf{yields \texttt{MyFirstBizarreTemp.tex} with following content:}
\verbatiminput*{MyFirstBizarreTemp.tex}
\noindent\hrulefill\null\vspace{\ht\strutbox}
\noindent\textbf{The input}
\begin{verbatim*}
\MyWriteFile{MySecondBizarreTemp.tex}{
[This time the content is within curly braces.]
{%
\Huge
another
piece
of
text%
äöü
}\\
{\scriptsize another piece of text}
X}
\end{verbatim*}
\MyWriteFile{MySecondBizarreTemp.tex}{
[This time the content is within curly braces.]
{%
\Huge
another
piece
of
text%
äöü
}\\
{\scriptsize another piece of text}
X}
\noindent\textbf{yields \texttt{MySecondBizarreTemp.tex} with following content:}
\verbatiminput*{MySecondBizarreTemp.tex}
\end{document}