如何制作一个宏,使其在 ConTeXt 中的段落中最后一次出现时显示不同的文本?

如何制作一个宏,使其在 ConTeXt 中的段落中最后一次出现时显示不同的文本?

我有一个简单的宏,它在文档中显示字母“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,则早期版本已归档为要点

替换宏的实际作用

有两件事正在发生。

  1. 计算段落数并存储宏的出现次数。
  2. 在宏调用时,将评估一组条件来确定输出。

您需要定义一个条件集并将其挂接到宏中。该集合通过 定义\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}

在此处输入图片描述

相关内容