![使用 \message 防止哈希值重复](https://linux22.com/image/405901/%E4%BD%BF%E7%94%A8%20%5Cmessage%20%E9%98%B2%E6%AD%A2%E5%93%88%E5%B8%8C%E5%80%BC%E9%87%8D%E5%A4%8D.png)
假设一个标记序列:
标记 1:类别代码 6 的显式哈希字符。
标记 2:类别代码 12 的显式 1。
标记 3:类别代码 6 的显式哈希字符。
标记 4:控制字标记\relax
。
标记 5:类别代码 6 的显式哈希字符。
令牌 6:类别代码 12 的显式 2。
就像是:,,,,,
#6
112
#6
\relaxcontrol word token
#6
212
基于此标记序列,您如何才能\message
在屏幕上获得哈希值未加倍的内容,其在屏幕上显示如下内容:
#1#\relax #2
,它在屏幕上看起来像这样: ?
传统 TeX 中是否存在一种方法来转换 catcode 6 的哈希值,且不会使其加倍?
答案1
您可以搜索#
并将其替换为 catcode 12 井号,然后显示它。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\hmessage}{m}
{
\tl_set:Nn \l__jewdokija_hmessage_tl { #1 }
\regex_replace_all:nnN { \cP\# } { \cO\# } \l__jewdokija_hmessage_tl
\iow_term:V \l__jewdokija_hmessage_tl
}
\tl_new:N \l__jewdokija_hmessage_tl
\cs_generate_variant:Nn \iow_term:n { V }
\ExplSyntaxOff
\hmessage{#1#\relax#2}
终端将显示
> pdflatex jewdokija.tex
This is pdfTeX, Version 3.14159265-2.6-1.40.20 (TeX Live 2019) (preloaded format=pdflatex)
restricted \write18 enabled.
entering extended mode
(./jewdokija.tex
LaTeX2e <2019-10-01> patch level 3
(/usr/local/texlive/2019/texmf-dist/tex/latex/base/article.cls
Document Class: article 2019/10/25 v1.4k Standard LaTeX document class
(/usr/local/texlive/2019/texmf-dist/tex/latex/base/size10.clo))
(/usr/local/texlive/2019/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
(/usr/local/texlive/2019/texmf-dist/tex/latex/l3kernel/expl3.sty
(/usr/local/texlive/2019/texmf-dist/tex/latex/l3kernel/expl3-code.tex
(/usr/local/texlive/2019/texmf-dist/tex/latex/l3kernel/l3deprecation.def))
(/usr/local/texlive/2019/texmf-dist/tex/latex/l3backend/l3backend-pdfmode.def))
)
#1#\relax #2
答案2
您可以通过 Lua 往返来对字符串进行去标记化,而无需哈希加倍。如果您的字符串完全可扩展,那么这也可以完全扩展。
\documentclass{article}
\begin{document}
\message{\directlua{tex.sprint(-2, "\luaescapestring{#1#\relax#2}")}}
\end{document}
$ lualatex test.tex
This is LuaTeX, Version 1.11.2 (TeX Live 2020/dev)
restricted system commands enabled.
(./test.tex
LaTeX2e <2019-10-01> patch level 3
luaotfload | main : initialization completed in 0.140 seconds
(/opt/texlive/2019/texmf-dist/tex/latex/base/article.cls
Document Class: article 2019/10/25 v1.4k Standard LaTeX document class
(/opt/texlive/2019/texmf-dist/tex/latex/base/size10.clo)) (./test.aux)
#1#\relax #2 (./test.aux))
384 words of node memory still in use:
2 hlist, 1 vlist, 1 rule, 2 glue, 3 kern, 1 glyph, 5 attribute, 44 glue_spec
, 5 attribute_list, 2 write nodes
avail lists: 2:7,3:1,4:1,5:4,7:1,9:1
warning (pdf backend): no pages of output.
Transcript written on test.log.
答案3
该tokcycle
包根据用户定义的指令逐个处理其输入的 token。它具有环境形式和宏形式。无论哪种形式,#
token 都被捕获为 cat-6,即\catSIXtrue
设置一个标志,但它们会暂时转换为 cat-12 以供 Character 指令处理。
在这里,我将只告诉 character 指令输出它#
获得的(现在是 cat-12 )标记,而不管\catSIXtrue
(这实际上是默认值)。
我介绍了三种使用这种技术的方法。第一种方法是,只需将命令\message
作为参数应用于\tokcyclexpress
宏。然后,重新整理标记列表\the\cytoks
。
在第二种环境方法中,将\message{...}
命令包装在\tokencyclexpress...\endtokencyclexpress
。
最后,在第三种方法中,我定义了一个新tokcycle
环境\msg...\endmsg
,您只需在其中插入原本\message
参数。
因为#
现在令牌循环中处理的令牌是 cat-12,所以它们不会加倍。
\documentclass{article}
\usepackage{tokcycle}[2021/03/10]
\begin{document}
Direct Macro Method:
\tokcyclexpress{\message{#1#\relax#2}}
\the\cytoks
Direct Environment Method:
\tokencyclexpress\message{#1#\relax#2}\endtokencyclexpress
Special environment method:
\xtokcycleenvironment\msg
{\addcytoks{##1}}
{\processtoks{##1}}
{\addcytoks{##1}}
{\addcytoks{##1}}
{}
{\cytoks\expandafter{\expandafter\message\expandafter{\the\cytoks}}}
\msg #1#\relax#2\endmsg
Now go check the log file.
\end{document}
日志文件的屏幕截图:
答案4
我可以提供一个\MakeUnexpandedMessageWithSingleHashes
带有底层循环的例程\StringifyBracesAndHashesLoop
,该例程处理一个参数并将类别代码 1(开始组)或 2(结束组)或 6(参数)的每个显式字符标记字符串化,然后将 eTeX 应用于\detokenize
其结果。
%% Copyright (C) 2019 by Ulrich Diez ([email protected])
%%
%% This work may be distributed and/or modified under the
%% conditions of the LaTeX Project Public Licence (LPPL), either
%% version 1.3 of this license or (at your option) any later
%% version. (The latest version of this license is in:
%% http://www.latex-project.org/lppl.txt
%% and version 1.3 or later is part of all distributions of LaTeX
%% version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included
%% documentation nor for any other part/component of this work.
%% If something breaks, you usually may keep the pieces.
%%
%% For this example to compile properly, LaTeX 2e with eTeX-extensions
%% is required: \UD@CheckWhetherHash and \StringifyBracesAndHashes
%% require \detokenize.
%%\errorcontextlines=10000
\documentclass{article}
\makeatletter
%%=============================================================================
%% Paraphernalia:
%% \UD@firstoftwo, \UD@secondoftwo,
%% \UD@PassFirstToSecond, \UD@Exchange, \UD@removespace
%% \UD@CheckWhetherNull, \UD@CheckWhetherBrace,
%% \UD@CheckWhetherLeadingSpace, \UD@CheckWhetherHash (requires eTeX's
%% \detokenize), \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% 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>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
{\UD@firstoftwo\expandafter{} \UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has leading
%% catcode-1-token>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has no leading
%% catcode-1-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
\romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\UD@firstoftwo\expandafter{} \UD@firstoftwo}%
{\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
\romannumeral0\UD@CheckWhetherNull{#1}%
{\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
{\expandafter\UD@secondoftwo\string{\UD@@CheckWhetherLeadingSpace.#1 }{}}%
}%
\@ifdefinable\UD@@CheckWhetherLeadingSpace{%
\long\def\UD@@CheckWhetherLeadingSpace#1 {%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter}\expandafter\expandafter
\expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument (which mut be a single token!!!!) is an
%% explicit character token of category code 6(parameter)
%%.............................................................................
%% \UD@CheckWhetherHash{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument which is to
%% be checked> is an explicit character token of category
%% code 6>}%
%% {<Tokens to be delivered in case <argument which is to
%% be checked> is not an explicit character token of
%% category code 6>}%
\newcommand\UD@CheckWhetherHash[1]{%
\romannumeral0%
\expandafter\UD@CheckWhetherLeadingSpace\expandafter{\string#1}{%
\UD@@CheckWhetherHash{\UD@removespace}{#1}%
}{%
\UD@@CheckWhetherHash{\UD@firstoftwo\expandafter{\expandafter}}{#1}%
}%
{%
\UD@firstoftwo\expandafter{} \UD@secondoftwo
}%
}%
\newcommand\UD@@CheckWhetherHash[2]{%
\expandafter\expandafter\expandafter\UD@CheckWhetherNull
\expandafter\expandafter\expandafter{%
\expandafter#1\string#2}{%
\expandafter\expandafter\expandafter\UD@CheckWhetherNull
\expandafter\expandafter\expandafter{%
\expandafter#1\detokenize{#2}}{%
% Single token
\UD@firstoftwo\expandafter{} \UD@secondoftwo
}{% Explicit catcode-6-character-token
\UD@firstoftwo\expandafter{} \UD@firstoftwo
}%
}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%% \UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% \UD@ExtractFirstArg{{AB}CDE} yields {AB}
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
\romannumeral0%
\UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{ #1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% In case an argument's first token is an opening brace, stringify that and
%% add another opening brace before that and remove everything behind the
%% matching closing brace:
%% \UD@StringifyOpeningBrace{{Foo}bar} yields {{Foo} whereby the second
%% opening brace is stringified:
%%.............................................................................
\newcommand\UD@StringifyOpeningBrace[1]{%
\romannumeral0%
\expandafter\UD@ExtractFirstArgLoop\expandafter{%
\romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter\expandafter
\expandafter {%
\expandafter\UD@firstoftwo
\expandafter{%
\expandafter}%
\romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter\string
\expandafter}%
\string#1%
\UD@SelDOm}%
}%
%%-----------------------------------------------------------------------------
%% In case an argument's first token is an opening brace, remove everything till
%% finding the corresponding closing brace. Then stringify that closing brace:
%% \UD@StringifyClosingBrace{{Foo}bar} yields: {}bar} whereby the first closing
%% brace is stringified:
%%.............................................................................
\newcommand\UD@StringifyClosingBrace[1]{%
\romannumeral0\expandafter\expandafter\expandafter
\UD@StringifyClosingBraceloop
\UD@ExtractFirstArg{#1}{#1}%
}%
\newcommand\UD@CheckWhetherStringifiedOpenBraceIsSpace[1]{%
%% This can happen when character 32 (space) has catcode 1...
\expandafter\UD@CheckWhetherLeadingSpace\expandafter{%
\romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter\UD@secondoftwo
\expandafter{%
\expandafter}%
\expandafter{%
\romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter\UD@firstoftwo
\expandafter{%
\expandafter}%
\romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter\string
\expandafter}%
\string#1%
}%
}%
\newcommand\UD@TerminateStringifyClosingBraceloop[2]{%
\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter\expandafter
\expandafter{%
\expandafter\string
\romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter#1%
\string#2%
}%
}%
\newcommand\UD@StringifyClosingBraceloopRemoveElement[4]{%
\expandafter\UD@PassFirstToSecond\expandafter{\expandafter
{\romannumeral0\expandafter\UD@secondoftwo\string}{}%
\UD@CheckWhetherStringifiedOpenBraceIsSpace{#4}{%
\UD@Exchange{\UD@removespace}%
}{%
\UD@Exchange{\UD@firstoftwo\expandafter{\expandafter}}%
}{%
\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter#1%
\romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter
}%
\string#4%
}{\expandafter\UD@StringifyClosingBraceloop\expandafter{#2#3}}%
}%
\newcommand\UD@StringifyClosingBraceloop[2]{%
\UD@CheckWhetherNull{#1}{%
\UD@CheckWhetherStringifiedOpenBraceIsSpace{#2}{%
\UD@TerminateStringifyClosingBraceloop{\UD@removespace}%
}{%
\UD@TerminateStringifyClosingBraceloop{\UD@firstoftwo\expandafter{\expandafter}}%
}%
{#2}%
}{%
\UD@CheckWhetherLeadingSpace{#1}{%
\UD@StringifyClosingBraceloopRemoveElement
{\UD@removespace}{\UD@removespace}%
}{%
\UD@StringifyClosingBraceloopRemoveElement
{\UD@firstoftwo\expandafter{\expandafter}}{\UD@firstoftwo{}}%
}%
{#1}{#2}%
}%
}%
%%-----------------------------------------------------------------------------
%% Create message-text from unexpanded brace balanced set of tokens where
%% hashes are _not_ doubled:
%%
%% \MakeUnexpandedMessageWithSingleHashes{<token 1><token 2>...<token n>}
%%
%% yields: <if token 1 is explicit character token of catcode 6, then
%% stringification+detokenization of token 1, else detokenization
%% of token 1>%
%% <if token 2 is explicit character token of catcode 6, then
%% stringification+detokenization of token 2, else detokenization
%% of token 2>%
%% ...
%% <if token n is explicit character token of catcode 6, then
%% stringification+detokenization of token n, else detokenization
%% of token n>%
%%
%% whereby "stringification of token" means the result of applying \string
%% to the token in question.
%% Due to \detokenize/\romannumeral-expansion the result is delivered after two
%% \expandafter-chains.
%%.............................................................................
\newcommand\MakeUnexpandedMessageWithSingleHashes[1]{%
\detokenize\expandafter{\romannumeral0\StringifyBracesAndHashesLoop{}{#1}}%
}%
%%-----------------------------------------------------------------------------
%% Stringify explicit character tokens of catcode 1 or 2 or 6:
%%
%% \romannumeral0\StringifyBracesAndHashesLoop{}{<token 1><token 2>...<token n>}
%%
%% yields: <if token is explicit character token of catcode 1/2/6, then
%% stringification of token 1, else token 1>%
%% <if token is explicit character token of catcode 1/2/6, then
%% stringification of token 2, else token 2>%
%% ...
%% <if token is explicit character token of catcode 1/2/6, then
%% stringification of token n, else token n>%
%%
%% whereby "stringification of token" means the result of applying \string
%% to the token in question.
%% Due to \romannumeral-expansion the result is delivered after two
%% \expandafter-chains.
%%
%% Syntax:
%%
%% \StringifyBracesAndHashesLoop{<token 1 or stringification of token 1>%
%% ...%
%% <token k-1 or stringification of token k-1>}%
%% {<token k>...<token n>}
%%.............................................................................
\newcommand\StringifyBracesAndHashesLoop[2]{%
\UD@CheckWhetherNull{#2}{ #1}{%
\UD@CheckWhetherBrace{#2}{%
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\UD@StringifyClosingBrace{#2}%
}{%
\expandafter\StringifyBracesAndHashesLoop\expandafter{%
\romannumeral0%
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{\UD@StringifyOpeningBrace{#2}}{\StringifyBracesAndHashesLoop{#1}}%
}%
}%
}{%
\UD@CheckWhetherLeadingSpace{#2}{%
\expandafter\UD@PassFirstToSecond\expandafter{\UD@removespace#2}{%
\StringifyBracesAndHashesLoop{#1 }%
}%
}{%
\expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#2}{%
\expandafter\StringifyBracesAndHashesLoop\expandafter{%
\romannumeral0%
\expandafter\UD@CheckWhetherHash\romannumeral0\UD@ExtractFirstArgLoop{#2\UD@SelDOm}{%
\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter\string
\expandafter\UD@Exchange
\romannumeral0\UD@ExtractFirstArgLoop{#2\UD@SelDOm}{}%
}%
}{%
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\UD@Exchange
\romannumeral0\UD@ExtractFirstArgLoop{#2\UD@SelDOm}{}%
}%
}{ #1}%
}%
}%
}%
}%
}%
}%
\makeatother
\begin{document}
\message{^^JHere come the messages^^J======================^^J^^J}%
\message{\MakeUnexpandedMessageWithSingleHashes{#1#\relax#2}}%
\message{^^J^^J}
{%
\catcode`\R=6 %
\message{\MakeUnexpandedMessageWithSingleHashes{R1#\relaxR2 s p a c e s {br{a{c}}es} }}%
\message{^^J^^J}
}%
\end{document}