我有一个简单的宏,它在文档中显示字母“A”:
\define\displayletter{A}
This is some text. \displayletter
This is some more text. \displayletter This is other text.
This is also some text. \displayletter
编译后,将会打印:
This is some text. A This is some more text. A This is other text. This is also some text. A
在某些段落中,我需要将最后一次出现的\displayletter
显示为字母“Z”。
对此可能有多种解决方案,但我认为在每个段落周围放置一个宏(例如这个)可以控制最后一个字母是否显示为“A”或“Z”:
\displayparagraph{0}{
This is some text. \displayletter
This is some more text. \displayletter This is other text.
This is also some text. \displayletter
}
如果#1
等于 0,则\displayletter
封闭段落中的每个段落都应显示为“A”。显示内容如下:
This is some text. A This is some more text. A This is other text. This is also some text. A
如果#1
大于 0,则\displayletter
封闭段落中最后一次出现的 应显示为“Z”,但\displayletter
段落中所有其他先前出现的 仍显示为“A”。这将显示:
This is some text. A This is some more text. A This is other text. This is also some text. Z
如何才能使一个宏在另一个宏中的最后一次出现显示不同的字母?
答案1
下面是我对此的方法,但它稍微修改了用户界面,使其看起来更像 Context。(您可以跳过实现并开始阅读“用户界面”部分的代码。)
编辑:代码已更新,以反映 2012-09-21 版 ConTeXt 中添加的新功能。不再需要 hack ;-) 此外,该规则final
现在是last
。如果您仍在使用较旧的 MkIV,则早期版本已归档为要点。
有两件事正在发生。
- 计算段落数并存储宏的出现次数。
- 在宏调用时,将评估一组条件来确定输出。
您需要定义一个条件集并将其挂接到宏中。该集合通过 定义\definesubstitutionset
。它采用将条件映射到输出的键值列表:
\definesubstitutionset [mysub] [1=a,2=b,default=x,final=y]
上面这一行的意思是,在给定的段落中,第一次出现这个字母A将被第二次打印b, 最后一次是对于所有其他情况X。
现在必须将此替换集分配给宏。使用创建一个新的宏\definesubstitute
并将其用作参数替代集:
\definesubstitute [displayfoo] [substitutionset=mysub]
(另外,尝试风格和颜色键!)现在您可以使用宏了\displayfoo
,它实际排版的内容将取决于指定的条件。
% macros=mkvi
\unprotect
%% 1. Declare a counter and increment it with every paragraph start.
\newcount\parcount \parcount0
\appendtoks \advance\parcount1 \to \everypar
\startluacode
local context = context
local jobdatasets = job.datasets
local tablefastcopy = table.fastcopy
-- 2. Retrieve the state of the previous run. NEW: Datasets make this
-- go smooth from the Lua end since version 2012-09-21.
local paragraph_info = jobdatasets.getdata(
"substitute",
"paragraph_info")
local occurrences, substitution_sets = { }, { }
documentdata.occurrences = occurrences
-- 3. (Ab)use the table to emulate a switch statement.
local do_display_letter = function (n, old_n, branches)
local s
if not old_n then -- paragraph count changed
s = branches.default
elseif n == old_n then
s = branches.last
else
s = branches[n] or branches.default
end
context(s)
end
-- 4. Bookkeeping: store away the number of times the macro is called
-- per paragraph. Then, continue with the response function.
local substitute = function (sub_id, set_id, parcount)
local previous = paragraph_info and paragraph_info[sub_id]
local current = occurrences[sub_id]
current[parcount] = current[parcount] or 0
current[parcount] = current[parcount] + 1
do_display_letter(
current[parcount],
previous and previous[parcount],
substitution_sets[set_id])
end
-- 5. Define a function that stores the counters of the current pass
-- in the .tuc file. The setter requires both a namespace
-- (“substitute”) and a tag (“paragraph_info”), so as to emulate
-- the usage of \type{\definedataset}.
local storeinfo = function ( )
jobdatasets.setdata {
name = "substitute",
tag = "paragraph_info",
data = occurrences,
}
end
-- 6. Code for key-value parser in Lua. Makes it easy to define
-- substitutions using setups.
local lpeg = require "lpeg"
local C, Cc, Cf, Cg, Ct = lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Ct
local P, S, V, lpegmatch = lpeg.P, lpeg.S, lpeg.V, lpeg.match
local p_args = P{
"args",
args = Cf(Ct"" * (V"kv_pair" + V"emptyline")^0, rawset),
kv_pair = Cg(V"key"
* V"separator"
* (V"value" * V"final"
+ V"empty"))
* V"rest_of_line"^-1
,
key = V"whitespace"^0 * C(V"key_char"^1),
key_char = (1 - V"whitespace" - V"eol" - V"equals")^1,
separator = V"whitespace"^0 * V"equals" * V"whitespace"^0,
empty = V"whitespace"^0 * V"comma" * V"rest_of_line"^-1
* Cc(false)
,
value = C((V"balanced" + (1 - V"final"))^1),
final = V"whitespace"^0 * V"comma" + V"rest_of_string",
rest_of_string = V"whitespace"^0
* V"eol_comment"^-1
* V"eol"^0
* V"eof"
,
rest_of_line = V"whitespace"^0 * V"eol_comment"^-1 * V"eol",
eol_comment = V"comment_string" * (1 - (V"eol" + V"eof"))^0,
comment_string = V"lua_comment" + V"TeX_comment",
TeX_comment = V"percent",
lua_comment = V"double_dash",
emptyline = V"rest_of_line",
balanced = V"balanced_brk" + V"balanced_brc",
balanced_brk = V"lbrk"
* (V"balanced" + (1 - V"rbrk"))^0
* V"rbrk"
,
balanced_brc = V"lbrc"
* (V"balanced" + (1 - V"rbrc"))^0
* V"rbrc"
,
-- Terminals
eol = P"\n\r" + P"\r\n" + P"\n" + P"\r",
eof = -P(1),
whitespace = S" \t\v",
equals = P"=",
dot = P".",
comma = P",",
dash = P"-", double_dash = V"dash" * V"dash",
percent = P"%",
lbrk = P"[", rbrk = P"]",
lbrc = P"{", rbrc = P"}",
}
local defaults = { -- EDIT: defaults are a tad more helpful
last = [[{\bold RULE “last” MISSING}]],
default = [[{\bold RULE “default” MISSING}]],
}
-- 7. The substitution sets are stored in a Lua by their id. If it’s
-- an integer, it will be stored on the array part. The special
-- conditions “default” and “last” are stored on the hash.
local define_substitutionset = function (set_id, parent, raw)
local current = parent and tablefastcopy(substitution_sets[parent])
or { }
local args = lpegmatch(p_args, raw)
--- sanitize arguments to have numbers on the array part
for k, v in next, args do
k = tonumber(k) or k
current[k] = v
end
--- make “.final” a synonym for “.last”
current.last = current.last or current.final
--- substitute defaults for missing mandatory rules
for k, v in next, defaults do current[k] = current[k] or v end
substitution_sets[set_id] = current
end
commands.conditional_substitution = substitute
commands.store_substitution_info = storeinfo
commands.define_substitutionset = define_substitutionset
\stopluacode
%% 8. Substitutions are assigned using \type{\definesubstitutionset},
%% which supports inheritance.
\def\definesubstitutionset{%
\dotripleempty\define_substitutionset_indeed%
}
\def\define_substitutionset_indeed[#id][#second][#third]{%
\ifthirdargument% inherit
\ctxcommand{define_substitutionset(
\!!bs#id\!!es,
\!!bs#second\!!es,
\!!bs\detokenize{#third}\!!es)}%
\else% new definition
\ctxcommand{define_substitutionset(
\!!bs#id\!!es,
false,
\!!bs\detokenize{#second}\!!es)}%
\fi%
}
%% 9. For the actual macro generator we rely on the generic namespace
%% functionality.
\installnamespace {substitute}
\installdefinehandler \????substitute {substitute}
\????substitute
\installsetuphandler \????substitute {substitute}
\installparameterhandler \????substitute {substitute}
\installstyleandcolorhandler \????substitute {substitute}
\appendtoks
\ctxlua{documentdata.occurrences["\currentsubstitute"] = { }}
\setuevalue{\currentsubstitute}%
{\substitute_direct[\currentsubstitute]}%
\to \everydefinesubstitute
\unexpanded\def\substitute_direct[#id]{%
\edef\currentsubstitute{#id}%
\dosingleempty\substitute_direct_indeed%
}
\def\substitute_direct_indeed[#setups]{%
\bgroup%
\iffirstargument\setupcurrentsubstitute[#setups]\fi
\usesubstitutestyleandcolor\c!style\c!color%% I♥ConTeXt
\ctxcommand{conditional_substitution(
\!!bs\currentsubstitute\!!es,
\!!bs\substituteparameter{substitutionset}\!!es,
\the\parcount)}%
\egroup%
}
%% 10. Store the counters in the .tuc file on exit.
\appendtoks \ctxcommand{store_substitution_info()} \to \everybye
\protect
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% user interface %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Usage:
%% First, define one or more substitution sets. These map occurrences
%% to output. Integers as key are interpreted as “use this for
%% the $n$-th occurrence. Two keys are special: “last” for the
%% last occurrence in a paragraph. “default” for any other
%% occurrence that is not explicitly defined.
%% {\em EDIT}: “final” has been made a synonym for “last”.
%%
%%
%% Second, define a substitution macro. These behave just as normal
%% ConTeXt macro generators including inheritance. They also
%% have corresponding \setup<id>. Just because it’s no effort
%% to implement, the “substitute” macros also respect “style”
%% and “color” parameters.
\definesubstitutionset [1] [default=A,last=A]
\definesubstitutionset [2] [1] [last=Z]
\definesubstitute [displayletter] [substitutionset=1,style=bold]
\definesubstitutionset [yetanotherset] [
1=Initial occurrence.,
3=Kilroy was here.,
last=Last occurrence.,
default=Ordinary occurrence.,
]
\definesubstitute [displaywhatever] [
substitutionset=yetanotherset,
style=smallcaps,
]
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% testing %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\starttext
foo \displayletter bar \displayletter baz \displayletter
foo \displayletter[substitutionset=2]
bar \displayletter[substitutionset=2]
baz \displayletter[substitutionset=2]
foo \displaywhatever bar \displaywhatever baz \displaywhatever
\stoptext \endinput
答案2
这将是我解决问题的方法。@phg 的全面回答启发了我,让我以更直接的方式尝试。因为我不太了解,所以ConTeXt
我举了一个LuaLatex
例子。但主要思想是在Lua
函数中实现的,所以我认为这没关系。
\documentclass{article}
\usepackage{filecontents}
\begin{filecontents*}{luaFunctions.lua}
function ReplaceLetter(text)
counter = 0
-- count the occurrences of '[displayletter]'
_, count = string.gsub(text, "%[displayletter%]", "")
-- define the replacements
replacements={[1]="\\textbf{A}",[2]="\\textbf{B}",[count]="\\textbf{Z}",["default"]="\\textbf{D}"}
-- replace all '[displayletter]'
out = string.gsub(text, "%[displayletter%]",
function(word)
counter = counter + 1
return replacements[counter] or replacements["default"]
end)
tex.print(out)
end
\end{filecontents*}
\directlua{dofile("luaFunctions.lua")}
\def\displayparagraph#1{\directlua{ReplaceLetter("#1")}}
\begin{document}
\displayparagraph{%
Lorem ipsum dolor sit amet, [displayletter] consectetur adipisicing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim [displayletter] ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis [displayletter] aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. [displayletter] Excepteur
sint occaecat cupidatat [displayletter] non proident, sunt in culpa qui
officia deserunt [displayletter] mollit anim id est laborum.}
\end{document}