expl3控制序列变体

expl3控制序列变体

我愿意创建类似矩阵的节点,因此我实现了\DrawMatrixx下面的草稿功能。

Matrixx/body另外,我对称为矩阵或主体本身的节点和尺寸有通用样式,即相对于主体上方和左侧的节点(不在下面的 MWE 中),称为Matrixx/dim

一般来说,这些样式我都还行,但有时我需要更精确地控制节点的行为,options/body例如,我希望使用 keyval 来实现。为了能够覆盖和扩展默认Matrixz/body样式,我需要将此选项放在样式之后 — 请参阅 的注释\matrixx_draw_node:n。但万一option/body={dashed, text=red}出现错误,正如预期的那样。

我怀疑我可以通过复杂的组合来实现我想要的\expandafter,但我希望有更清晰的方法吗expl3

\documentclass{article}


\usepackage{tikz}
\usepackage{xparse}


\tikzstyle{Matrixx/body}=[
    draw,
    rounded corners,
    inner sep=2pt,
    outer sep=0pt,
]

\tikzstyle{Matrixx/dim}=[
    draw=none,
    font=\footnotesize,
    outer sep=2pt,
    inner sep=3pt
]

\ExplSyntaxOn

\tl_set:Nn \g_matrixx_minimum_height_tl {1cm}
\tl_set:Nn \g_matrixx_minimum_width_tl {1cm}

\NewDocumentCommand{\DrawMatrixx}{ m }{%
    \matrixx_draw_node:n { #1 }
}

\keys_define:nn { Matrixx }
{%
    name .tl_set:N = \l__matrixx_name_tl,
    minimum height .tl_set:N = \l__matrixx_minimum_height_tl,
    minimum width .tl_set:N = \l__matrixx_minimum_width_tl,
    content .tl_set:N = \l__matrixx_content_tl,
    options/body .tl_set:N = \l_matrixx_options_body_tl,
    options/dim .tl_set:N = \l_matrixx_options_dim_tl
}

\cs_new_protected:Nn \matrixx_draw_node:n 
{%
    \group_begin:

        \keys_set:nn { Matrixx } {
            minimum height=\g_matrixx_minimum_height_tl,
            minimum width=\g_matrixx_minimum_width_tl,
            content=,
            options/body=,
            options/dim=,
            name=,
            #1
        }
        \node[%
            Matrixx/body,
            minimum~height=\l__matrixx_minimum_height_tl,
            minimum~width=\l__matrixx_minimum_width_tl,
            % Here should be expanded \l_matrixx_options_body_tl if any.
        ] (\l__matrixx_name_tl/body) {\l__matrixx_content_tl};

        \node[%
            Matrixx/dim,%
            anchor=south,
            % Here should be expanded \l_matrixx_options_dim_tl if any.
        ] (\l__matrixx_name_tl/dim/top) at (\l__matrixx_name_tl/body.north) {n};
    \group_end:
}
\ExplSyntaxOff



\begin{document}

\begin{tikzpicture}
    \DrawMatrixx{
        name=Name,
        content=H,
        options/body={%
            thick,
            text=blue,
        },
        options/dim={%
            text=red,
        }
    }
\end{tikzpicture}

\end{document}

谢谢。

PS 抱歉,如果这个问题很简单,但是我无法正确地谷歌搜索——今天才开始深入研究expl3,因为今天开会越来越频繁,expl3TeX 大师提供了各种清晰而漂亮的解决方案,例如埃格尔

答案1

您怀疑用 来解决这个问题\expandafter并不符合expl3精神,这是正确的(即使 LaTeX3 语言中有一个等效函数,即\exp_args:No,它通常会使代码比必要的更难阅读和维护)。

expl3控制序列变体

回答你的问题的关键概念是expl3控制序列变体。我建议你阅读定义变体的方法LaTeX3 界面可从此处获取珍贵的“l3kernel”链接。特别是,该函数对于定义标准函数和您自己的函数\cs_generate_variant:Nn的变体非常有用,并且正是您在这里所需要的。expl3

参数V说明符传递价值将指定变量的值传递给基函数。例如,如果你有一个\l_my_tl包含以下函数的标记列表变量abc def并按以下方式调用该函数:

\my_function:V \l_my_tl

将会发生的事情是,基础函数\my_function:n将被调用,并以abc def(的“值” \l_my_tl) 作为其参数。也就是说,在这种情况下,\my_function:V \l_my_tl将相当于\my_function:n { abc~def }(在\ExplSyntaxOn语法制度下),即不一样\my_function:n { \l_my_tl }在后一种情况下,参数是一个标记:\l_my_tl,而是abc~def由 7 个标记组成)。

为了实现此功能,您需要:

  1. 定义使用其参数的基函数\my_function:n,就好像它是“完整”输入的一样(abc def);换句话说,\my_function:n不应该尝试对该参数进行任何特殊处理,例如扩展。

  2. 告诉 LaTeX3 生成V基本函数的变体:

    \cs_generate_variant:Nn \my_function:n { V }
    

这样说似乎有些冗长,而且不太令人印象深刻。但是您可以对所有参数执行此操作,然后与使用 制作的等效解决方案相比,代码的可读性、简单性和清晰度将大大提高\expandafter(请参见下面的示例,其中包含两个参数和经过我更改后的代码 - 传递给 的四个参数\matrixx_draw_node_aux:VVxV,其中第三个参数在结果传递给基函数 之前完全展开\matrixx_draw_node_aux:nnnn,其他参数每个参数展开一次以获取相应标记列表变量的值)。

V只是可用的变体类型之一。还有x对应于 的变体\edef(请参阅我建议的代码中的示例用法)、e类似但可用于仅扩展上下文的变体(它们在非最新 TeX 引擎上速度很慢)、在将结果控制序列标记传递给基函数之前c执行 的变体、对参数执行一个扩展步骤的变体等。每个参数使用的变体可以不同,并且它们的生成与上例一样简单。\csname ... \endcsnameo

其他示例。假设您有一个接受两个参数的函数\my_other_func:Nn,第一个参数是控制序列名称,第二个参数是标记列表(“正常”参数: type n)。假设在编程的某个阶段,您需要:

  • 传递\my_other_func:Nn一个控制序列标记的第一个参数,该标记输入起来不太方便,或者是从几个部分动态构建的,因此您宁愿输入\my_other_func:cn { difficult@#1@to@enter } { abc~def }(其中#1具有来自周围代码的合适值 - 可以是标识符)而不是\expandafter \my_other_func:Nn \csname difficult@#1@to@enter \endcsname { abc~def }(使用 LaTeX2e 样式);

  • 将第二个参数用作标记列表变量(或整数、字符串等)的值,这样,前面的中等复杂代码\csname需要变得更加复杂,才能在\endcsname后面的内容\my_other_func:Nn本身扩展后进行扩展。让我们\l_my_tl再次调用此变量来理清思路,但它可以是其他东西,例如像这样的参数标记#7

然后您需要做的就是以最直接的方式expl3定义,然后执行:\my_other_func:Nn

\cs_generate_variant:Nn \my_other_func:Nn { cV }

并使用如下代码:

\cs_new_protected:Nn \my_caller_func:n
  {
    % Typically set \l_my_tl here, for instance by concatenating elements
    % from a sequence, or anything else.
    (...)
    \my_other_func:cV { difficult@#1@to@enter } \l_my_tl
  }

易于阅读,易于编写,非常干净且易读。很好地使用变体可以清楚地了解传递给下游函数的所有内容如何以及何时被扩展。

修复代码的建议

对于您的代码,我建议如下:

\documentclass{article}
\usepackage{tikz}
\usepackage{xparse}

\tikzset{Matrixx/body/.style={
            draw,
            rounded corners,
            inner sep=2pt,
            outer sep=0pt,
         },
         Matrixx/dim/.style={
            draw=none,
            font=\footnotesize,
            outer sep=2pt,
            inner sep=3pt,
         }
}

\ExplSyntaxOn

\tl_new:N \l_matrixx_name_tl
\tl_new:N \l_matrixx_minimum_height_opt_tl
\tl_new:N \l_matrixx_minimum_width_opt_tl
\tl_new:N \l_matrixx_content_tl
\tl_new:N \l_matrixx_options_body_tl
\tl_new:N \l_matrixx_options_dim_tl

\keys_define:nn { Matrixx }
  {
    name .tl_set:N = \l_matrixx_name_tl,
    name .value_required:n = true,

    minimum~height .code:n = {
      \tl_set:Nn \l_matrixx_minimum_height_opt_tl { minimum~height = #1 } },
    minimum~height .value_required:n = true,
    minimum~height .initial:n = { 1cm },

    minimum~width .code:n = {
      \tl_set:Nn \l_matrixx_minimum_width_opt_tl { minimum~width = #1 } },
    minimum~width .value_required:n = true,
    minimum~width .initial:n = { 1cm },

    content .tl_set:N = \l_matrixx_content_tl,
    content .value_required:n = true,

    options/body .tl_set:N = \l_matrixx_options_body_tl,
    options/body .value_required:n = true,

    options/dim .tl_set:N = \l_matrixx_options_dim_tl,
    options/dim .value_required:n = true,
  }

\seq_new:N \l__matrixx_draw_node_tmp_seq

\cs_new_protected:Npn \matrixx_draw_node:n #1
  {
    \group_begin:
    \keys_set:nn { Matrixx } {#1}

    % Collect in \l__matrixx_draw_node_tmp_seq the additional options to pass
    % to the matrix body node.
    \seq_clear:N \l__matrixx_draw_node_tmp_seq
    \seq_put_right:NV \l__matrixx_draw_node_tmp_seq
                      \l_matrixx_minimum_height_opt_tl
    \seq_put_right:NV \l__matrixx_draw_node_tmp_seq
                      \l_matrixx_minimum_width_opt_tl
    \seq_put_right:NV \l__matrixx_draw_node_tmp_seq
                      \l_matrixx_options_body_tl

    % Concatenate the arguments we assembled with a comma separator, then call
    % the base function passing it the values of the other tl variables.
    \matrixx_draw_node_aux:VVxV
      \l_matrixx_name_tl
      \l_matrixx_content_tl
      { \seq_use:Nn \l__matrixx_draw_node_tmp_seq { , } }
      \l_matrixx_options_dim_tl
    \group_end:
  }

% #1: matrix name (stem of the two nodes)
% #2: matrix node contents
% #3: options to pass to the body node
% #4: additional options to pass to the 'dim' node (whatever this means...)
\cs_new_protected:Npn \matrixx_draw_node_aux:nnnn #1#2#3#4
  {
    \node [ Matrixx/body, #3 ] (#1/body) {#2};

    \node [ Matrixx/dim, anchor = south, #4 ]
          (#1/dim/top) at (#1/body.north) {n};
  }

% 'x' stands for “full eXpansion” and 'V' for “value” (here: of token list
% variables; i.e., their contents).
\cs_generate_variant:Nn \matrixx_draw_node_aux:nnnn { VVxV }

\NewDocumentCommand \DrawMatrixx { m }
  {
    \matrixx_draw_node:n {#1}
  }

% Convenient function for setting defaults at document level. It respects the
% normal TeX grouping rules (in other words, the settings are not \global).
% You can use it in various parts of your document to override the initial
% values set by the above \keys_define:nn call.
\NewDocumentCommand \MatrixxSetup { m }
  {
    \keys_set:nn { Matrixx } {#1}
  }

\ExplSyntaxOff

\begin{document}

\begin{tikzpicture}
  \DrawMatrixx{
      name=Name,
      content=H,
      options/body={
          thick,
          text=blue,
      },
      options/dim={
          text=red,
      }
  }
\end{tikzpicture}

\end{document}

截屏

Ti 的特殊情况

Z 解析器相当宽容;该解析器的一个值得注意的特性是,它经常负责扩展在环境内编写的用户宏,tikzpicture以便这些宏可以生成有效的 TiZ 代码,解析器随后将成功处理该代码。事实上,如果你考虑一下我们的代码,它不会与非常“严格”的解析器一起工作,因为这\DrawMatrixx不是 Ti 中描述的命令Z & PGF 手册!因此,\DrawMatrixx由 Ti 扩展Z 而不是被报告为非意外命令,并且解析继续使用结果。使用更严格的解析器,我们需要编写类似以下内容的内容:

\cs_new_protected:Npn \my_function:n #1
  {
    \begin{tikzpicture} #1 \end{tikzpicture}
  }

\my_function:V使用例如包含 Ti 中计算程序的标记列表变量进行调用Z 语言,直接放在\begin{tikzpicture}和之间\end{tikzpicture}。这将是万无一失的方法,但 TiZ 努力扩展用户提供的内容,我们可以避免这种麻烦。

因此,在很多情况下,尤其expl3是不使用时,你可以通过让 TiZ 解析器自己扩展事物,而不是仔细准备图形代码,使其仅包含 TiZ 命令和“常量”。人们总是这么做,但可能并非所有人都意识到了这一点。在我的示例中,您可以检查将变量替换VVxVnnxV1是否会产生有效的代码。但这不适用于nnxn,因此自动起作用的内容并不容易预测……

你可能有时会这样做,但了解如何正确准备 TiZ 输入,因为这项技能将始终帮助您进行 LaTeX 编程(以我的拙见)。依靠运气,希望命令能够以您需要的方式扩展您的参数(或环境主体)等,这不是我觉得有趣的事情,而且当它不起作用时会导致严重的头痛。学习精确控制何时以及如何扩展事物以便提供命令或环境输入,就像您手写的那样非常有用,并且使您能够解决许多自动化问题:如果您可以手动在 TeX 中执行某些操作并知道如何以编程方式生成相同的输入,那么您就可以将其自动化。

在官方expl3文档中

我建议你也阅读一下“l3kernel” 链接我提到要更熟悉expl3。它们都很短,除了LaTeX3 界面,这是一本参考手册,不需要从头到尾阅读。对于那本书,我的建议是仔细阅读目录以了解可用的内容,根据您的时间浏览或阅读前几章,然后根据需要参考文档的其余部分(我需要做这做那,序列[例如]可能是正确的工具;让我们看看他们章节的简介说了什么;是的,这就是我需要的;让我们看看有哪些功能可用→尝试它们→问题解决)。

如果你认真看看这些文件,你会发现,你可能不需要阅读语法更改现在开始创建文档——只需知道它的存在就足够了。在我看来,当开始使用时expl3LaTeX3 编程语言也称为expl3.pdf,以及LaTeX3 风格指南也称为l3styleguide.pdf,确实非常重要;一开始阅读它们不会花很长时间,而且确实是一项时间投资。

不相关:正如我在评论中所说,已被弃用,我已在你的代码中将\tikzstyle其替换为。\tikzset


脚注

  1. \cs_generate_variant:Nn和调用的地方\matrixx_draw_node_aux:VVxV。你可能想在结果中的前两个参数周围添加括号\matrixx_draw_node_aux:nnxV以使此测试真正干净。关于这些括号,LaTeX3 界面说:

    通常,如果您使用单个标记作为参数n,那么一切都会很好。

    我让你体会一下“通常”这个词。:-)

相关内容