编辑

编辑

这是我一直在基于的答案中使用的部分代码的简化版本chronos,试图创建一些类似于有问题的替代品的东西chronosys

这段代码可以正常工作,但它依赖于一种奇怪的黑客技术。如何在不需要任何奇怪的黑客技术的情况下做到这一点?

相关的奇怪的黑客(即我知道很奇怪的黑客)涉及到以下几行

  \tl_replace_all:Nnn \l_chronos_dateformat_tl { ~ } { @ }

      { @ } { ~ }

本质上,这样做的目的是用 替换用户定义的日期格式中的所有空格,@然后@在排版格式化的日期时用空格替换该日期格式中的所有空格。

这个想法是,用户可以说类似这样的话

\tikzset{%
   date format={ B d, Y},
}

为了将时间线事件的日期打印为September 15, 2013,例如,或

\tikzset{%
  date format={b d Y},
}

比如说,对于。(这里的最小代码无法做到这一点,因为我已经删除了 和所需Sept 15 2013的代码,但我想解释一下为什么我要以这种迂回的方式重新输入用户输入的年份。)bBd

其理念是,任何未被代码识别为日期格式字符的字符都将原封不动地通过。但是,空格是某些日期格式中自然使用的字符,因此问题在于在处理其余字符时保留这些字符。

还值得一提的是,日期不会像这里一样立即排版,而是作为 Ti 的一部分进行存储和输出Z 节点稍后会介绍。这解释了格式化本身无法解释的一些复杂性(例如,使用 来#4创建标签以检索日期,如thing)。(事实上,大多数设备都是出于这个原因,而不是因为格式解析要求。)

我有点担心我可能没有充分解释为什么我这样做,所以如果看起来有一些明显的、简单的解决方案,可以消除我引入的大部分复杂性,那么我当然感兴趣,但请在花时间写答案之前要求澄清!

\documentclass[border=10pt,multi,tikz]{standalone}
\usepackage{xparse}
\makeatletter
\ExplSyntaxOn
\tl_new:N \l_chronos_dateformat_tl
\tl_set:Nn \l_chronos_dateformat_tl { d/m/Y }
\cs_generate_variant:Nn \int_abs:n { c }
\cs_new_protected_nopar:Npn \chronos_show_date:n #1
{
  \tl_map_inline:Nn \l_chronos_dateformat_tl
  {
    \str_case:nnF { ##1 }
    {
      { Y } { \int_abs:c { chronos@#1year } } % for each Y in the date format, output the year in YYYY, omitting any leading minus sign
      { @ } { ~ } % un-hackery: for each @ s in the date format, output a space when showing a date
    }
    {
      ##1
    }
  }
}
\cs_new_protected_nopar:Npn \chronos_set_dateformat:n #1
{
  \tl_set:Nn \l_chronos_dateformat_tl { #1 }
  \tl_replace_all:Nnn \l_chronos_dateformat_tl { ~ } { @ } % hackery - substitute any spaces in the input with @
}
\NewDocumentCommand \chronos@setdateformat { m }
{
  \chronos_set_dateformat:n { #1 }
}
\NewDocumentCommand \chronos@showdate { o m }
{
  \group_begin:
    \IfValueT { #1 }
    {
      \chronos_set_dateformat:n { #1 }
    }
    \chronos_show_date:n { #2 }
  \group_end:
}
\ExplSyntaxOff
\newcounter{chronos@thingdate}
\tikzset{%
  chronos set date/.code args={#1:#2:#3:#4}{%
    \expandafter\def\csname chronos@#4year\endcsname{#1}%
    \chronos@showdate{#4}% This doesn't usually happen here: instead, the date is saved with a tag such as 'thing' so that the date or parts of the date can be retrieved and formatted later, possibly more than once with different formatting. Multiple tags allow the storing of multiple sets of date/date part information. The information gets used in various ways to calculate, construct and annotate a timeline.
  },
  chronos date/.style args={#1-#2-#3}{%
    /tikz/chronos set date/.expanded={#1:#2:#3:thing}% In the full code 'thing' is one of several tags used to tag dates for the timeline.
  },
  date format/.code={% Let the user change the format of the date without doing anything special to include spaces.
    \chronos@setdateformat{#1}%
  },
}
\makeatother
\begin{document}
  \tikzset{%
    date format={date: Y},
    chronos date=2016-04-25,
  }
\end{document}

编辑

根据 Manuel 在评论中的建议(某种程度上),我可以去掉 -ery,@但代价是复杂性会稍微增加一些。(但请注意,我完全不确定这是否是 Manuel 的本意,所以错肯定在我!)

\documentclass[border=10pt,multi,tikz]{standalone}
\usepackage{xparse}
\makeatletter
\ExplSyntaxOn
\seq_new:N \l_chronos_date_seq
\seq_new:N \l_chronos_dateformat_seq
\seq_set_split:Nnn \l_chronos_dateformat_seq { ~ } { d/m/Y }
\cs_generate_variant:Nn \int_abs:n { c }
\cs_new_protected_nopar:Npn \chronos_show_date:n #1
{
  \seq_clear:N \l_chronos_date_seq
  \seq_map_inline:Nn \l_chronos_dateformat_seq
  {
    \seq_put_right:Nn \l_chronos_date_seq
    {
      \tl_map_inline:nn { ##1 }
      {
        \str_case:nnF { ####1 }
        {
          { Y } { \int_abs:c { chronos@#1year } }
        }
        {
          ####1
        }
      }
    }
  }
  \seq_use:Nn \l_chronos_date_seq { ~ }
}
\cs_new_protected_nopar:Npn \chronos_set_dateformat:n #1
{
  \seq_set_split:Nnn \l_chronos_dateformat_seq { ~ } { #1 }
}
\NewDocumentCommand \chronos@setdateformat { m }
{
  \chronos_set_dateformat:n { #1 }
}
\NewDocumentCommand \chronos@showdate { o m }
{
  \group_begin:
    \IfValueT { #1 }
    {
      \chronos_set_dateformat:n { #1 }
    }
    \chronos_show_date:n { #2 }
  \group_end:
}
\ExplSyntaxOff
\newcounter{chronos@thingdate}
\tikzset{%
  chronos set date/.code args={#1:#2:#3:#4}{%
    \expandafter\def\csname chronos@#4year\endcsname{#1}%
    \chronos@showdate{#4}%
  },
  chronos date/.style args={#1-#2-#3}{%
    /tikz/chronos set date/.expanded={#1:#2:#3:thing}%
  },
  date format/.code={%
    \chronos@setdateformat{#1}%
  },
}
\makeatother
\begin{document}
  \tikzset{%
    chronos date=2016-04-25,
    date format={date: Y},
    chronos date=2016-04-25,
  }
\end{document}

再次,这有效。但这似乎是一种非常复杂的方法,用于完成一些非常常见的需要,即在用户输入中保留空格 - 这肯定不是最好的方法?!

编辑2

好的,这比编辑比原版更少黑客气息。但我不确定它有多安全/强大/合适。

这基本上取代了原来的黑客行为

  \tl_replace_all:Nnn \l_chronos_dateformat_tl { ~ } { \c_space_token }

至少对于最小示例来说,这似乎有效。它似乎也适用于对我的完整代码进行的最小测试,并且似乎是迄今为止“最不奇怪”的方法。但这并不是说这是一个好策略……

\documentclass[border=10pt,multi,tikz]{standalone}
\usepackage{xparse}
\makeatletter
\ExplSyntaxOn
\tl_new:N \l_chronos_dateformat_tl
\tl_set:Nn \l_chronos_dateformat_tl { d/m/Y }
\cs_generate_variant:Nn \int_abs:n { c }
\cs_new_protected_nopar:Npn \chronos_show_date:n #1
{
  \tl_map_inline:Nn \l_chronos_dateformat_tl
  {
    \str_case:nnF { ##1 }
    {
      { Y } { \int_abs:c { chronos@#1year } } % for each Y in the date format, output the year in YYYY, omitting any leading minus sign
    }
    {
      ##1
    }
  }
}
\cs_new_protected_nopar:Npn \chronos_set_dateformat:n #1
{
  \tl_set:Nn \l_chronos_dateformat_tl { #1 }
  \tl_replace_all:Nnn \l_chronos_dateformat_tl { ~ } { \c_space_token }
}
\NewDocumentCommand \chronos@setdateformat { m }
{
  \chronos_set_dateformat:n { #1 }
}
\NewDocumentCommand \chronos@showdate { o m }
{
  \group_begin:
    \IfValueT { #1 }
    {
      \chronos_set_dateformat:n { #1 }
    }
    \chronos_show_date:n { #2 }
  \group_end:
}
\ExplSyntaxOff
\newcounter{chronos@thingdate}
\tikzset{%
  chronos set date/.code args={#1:#2:#3:#4}{%
    \expandafter\def\csname chronos@#4year\endcsname{#1}%
    \chronos@showdate{#4}% This doesn't usually happen here: instead, the date is saved with a tag such as 'thing' so that the date or parts of the date can be retrieved and formatted later, possibly more than once with different formatting. Multiple tags allow the storing of multiple sets of date/date part information. The information gets used in various ways to calculate, construct and annotate a timeline.
  },
  chronos date/.style args={#1-#2-#3}{%
    /tikz/chronos set date/.expanded={#1:#2:#3:thing}% In the full code 'thing' is one of several tags used to tag dates for the timeline.
  },
  date format/.code={% Let the user change the format of the date without doing anything special to include spaces.
    \chronos@setdateformat{#1}%
  },
}
\makeatother
\begin{document}
  \tikzset{%
    date format={date: Y},
    chronos date=2016-04-25,
  }
\end{document}

答案1

这个问题实际上分为两个部分,一个是用例的细节,另一个是更一般的“如何使用空格进行迭代”。我将分别解决这两个问题。


对于此处用实际值替换数字部分“持有者”的具体情况,我根本不会使用循环。没有必要特别查看每个标记:简单的搜索和替换就可以完成工作。我也不会使用单个字母标记作为“标记”:这是在自找麻烦。在下文中,我选择了YYYY作为全年、MM作为给定月份、ETC。:可以安排零填充,转换为文本等等。

\documentclass{standalone}
\usepackage{pgfkeys}
\usepackage{expl3}
\ExplSyntaxOn
\tl_new:N \l_chronos_format_tl
\tl_new:N \l__chronos_tmp_tl
\cs_new_protected:Npn \chronos_format:n #1
  { \tl_set:Nn \l_chronos_format_tl {#1} }
\cs_new_protected:Npn \chronos_print:n #1
  {  \__chronos_print:w #1 - - - \q_stop }
\cs_new_protected:Npn \__chronos_print:w #1 - #2 - #3 - #4 \q_stop
  {
    \bool_if:nTF
      {
        \tl_if_blank_p:n {#1} ||
        \tl_if_blank_p:n {#2} ||
        \tl_if_blank_p:n {#3}
      }
      { \ERROR }
      { \chronos_print:nnn {#1} {#2} {#3} }
  }
\cs_new_protected:Npn \chronos_print:nnn #1#2#3
  {
    \tl_set_eq:NN \l__chronos_tmp_tl \l_chronos_format_tl
    \tl_replace_all:Nnn \l__chronos_tmp_tl { YYYY } {#1}
    \tl_replace_all:Nnn \l__chronos_tmp_tl { MM }   {#2}
    \tl_replace_all:Nnn \l__chronos_tmp_tl { DD }   {#3}
    \tl_use:N \l__chronos_tmp_tl
  }
\ExplSyntaxOff
\pgfkeys{
  chronos/.is family,
  chronos/format/.ecode =
    \unexpanded\expandafter{\csname\detokenize{chronos_format:n}\endcsname}%
      {#1},
  chronos/date/.code =
    \unexpanded\expandafter{\csname\detokenize{chronos_print:n}\endcsname}%
      {#1},
}
\begin{document}

\pgfkeys{chronos/format = date: YYYY, chronos/date = 2016-04-25}

\end{document}

我采用了一种方法,\pgfkeys通过使用扩展来输入正确的标记,从而避免因空格而导致的 catcode 问题。可能有一个用于设置的文档级界面:由您决定。


如果你有比你想列出的更多的案例,\tl_replace_all:Nnn那么我可能会对各种案例使用循环,并为每个案例使用一个助手

\documentclass{standalone}
\usepackage{pgfkeys}
\usepackage{expl3}
\ExplSyntaxOn
\tl_new:N \l_chronos_format_tl
\tl_new:N \l__chronos_tmp_tl
\cs_new_protected:Npn \chronos_format:n #1
  { \tl_set:Nn \l_chronos_format_tl {#1} }
\cs_new_protected:Npn \chronos_print:n #1
  {  \__chronos_print:w #1 - - - \q_stop }
\cs_new_protected:Npn \__chronos_print:w #1 - #2 - #3 - #4 \q_stop
  {
    \bool_if:nTF
      {
        \tl_if_blank_p:n {#1} ||
        \tl_if_blank_p:n {#2} ||
        \tl_if_blank_p:n {#3}
      }
      { \ERROR }
      { \chronos_print:nnn {#1} {#2} {#3} }
  }
\cs_new_protected:Npn \chronos_print:nnn #1#2#3
  {
    \tl_set_eq:NN \l__chronos_tmp_tl \l_chronos_format_tl
    \clist_map_inline:nn { YYYY , MM , DD }
      {
        \tl_replace_all:Nnx \l__chronos_tmp_tl {##1}
          { \use:c { __chronos_replace_ ##1 :nnn } {#1} {#2} {#3} }
      }
    \tl_use:N \l__chronos_tmp_tl
  }
\cs_new:Npn \__chronos_replace_YYYY:nnn #1#2#3 {#1}
\cs_new:Npn \__chronos_replace_MM:nnn #1#2#3 {#2}
\cs_new:Npn \__chronos_replace_DD:nnn #1#2#3 {#3}
\cs_generate_variant:Nn \tl_replace_all:Nnn { Nnx }
\ExplSyntaxOff
\pgfkeys{
  chronos/.is family,
  chronos/format/.ecode =
    \unexpanded\expandafter{\csname\detokenize{chronos_format:n}\endcsname}%
      {#1},
  chronos/date/.code =
    \unexpanded\expandafter{\csname\detokenize{chronos_print:n}\endcsname}%
      {#1},
}
\begin{document}

\pgfkeys{chronos/format = date: YYYY, chronos/date = 2016-04-25}

\end{document}

关于使用空格进行循环的更一般问题,我认为这实际上是一个单独的问题。与采用其他一些更集中的方法相比,您很少需要执行这样的循环。因此,在expl3at preset 中没有针对此类结果的公共函数。为完成该项工作而设立的内部机构。

您可以通过两种方式循环包含空格的标记。第一种,如果只关心空格,则使用两个循环:一个在空格上拆分,然后每个“单词”内一个。后者不必担心空格,而外循环可以根据需要重新插入空格。更复杂的方法是还要担心括号组,在更简单的实现中,括号组会丢失。这可以以可扩展或不可扩展的方式完成:只有后者可以保留组的字符标记。(这是一个非常不寻常的担忧,但值得注意。)方法是使用测试输入的“头部”

  • 空间
  • 开始组标记
  • 还要别的吗

然后针对每种情况使用适当的辅助词。有关此想法的版本的详细信息,请\__tl_act:NNNnn参阅expl3-code.tex(或l3tl.dtx) 。expl3


后一种方法类似于

\documentclass{standalone}
\usepackage{pgfkeys}
\usepackage{expl3}
\ExplSyntaxOn
\tl_new:N \l_chronos_format_tl
\tl_new:N \l__chronos_tmp_tl
\cs_new_protected:Npn \chronos_format:n #1
  { \tl_set:Nn \l_chronos_format_tl {#1} }
\cs_new:Npn \chronos_print:n #1
  {  \__chronos_print:w #1 - - - \q_stop }
\cs_new:Npn \__chronos_print:w #1 - #2 - #3 - #4 \q_stop
  {
    \bool_if:nTF
      {
        \tl_if_blank_p:n {#1} ||
        \tl_if_blank_p:n {#2} ||
        \tl_if_blank_p:n {#3}
      }
      { \ERROR }
      { \chronos_print:nnn {#1} {#2} {#3} }
  }
\cs_new:Npn \chronos_print:nnn #1#2#3
  {
    \exp_after:wN \__chronos_loop:wnnn \l_chronos_format_tl
      \q_recursion_tail \q_recursion_stop {#1} {#2} {#3}
  }
\cs_new:Npn \__chronos_loop:wnnn #1 \q_recursion_stop % Three n-type follow
  {
    \tl_if_head_is_N_type:nTF {#1}
      { \__chronos_N_type:Nwnnn }
      {
        \tl_if_head_is_group:nTF {#1}
          { \__chronos_group:nwnnn }
          { \__chronos_space:wwnnn }
      }
    #1 \q_recursion_stop
  }
\cs_new:Npn \__chronos_N_type:Nwnnn #1#2 \q_recursion_stop #3#4#5
  {
    \if_meaning:w \q_recursion_tail #1
      \exp_after:wN \__chronos_end:wnnn
    \fi:
    \str_case:nnF {#1}
      {
        { Y } {#3}
        { M } {#4}
        { D } {#5}
      }
      {#1}
    \__chronos_loop:wnnn #2 \q_recursion_stop {#3} {#4} {#5}
  }
\cs_new:Npn \__chronos_group:nwnnn #1#2 \q_recursion_stop
  {
    {#1}
    \__chronos_loop:wnnn #2 \q_recursion_stop
  }
\exp_last_unbraced:NNo
  \cs_new:Npn \__chronos_space:wwnnn \c_space_tl #1 \q_recursion_stop
  {
    \c_space_tl
    \__chronos_loop:wnnn #1 \q_recursion_stop
  }
\cs_new:Npn \__chronos_end:wnnn #1 \q_recursion_stop #2#3#4 { }
\ExplSyntaxOff
\pgfkeys{
  chronos/.is family,
  chronos/format/.ecode =
    \unexpanded\expandafter{\csname\detokenize{chronos_format:n}\endcsname}%
      {#1},
  chronos/date/.code =
    \unexpanded\expandafter{\csname\detokenize{chronos_print:n}\endcsname}%
      {#1},
}
\begin{document}

\pgfkeys{chronos/format = date: Y, chronos/date = 2016-04-25}

\end{document}

可以手动编写测试(以提高效率),但我已使用 中的预构建测试expl3。这里的核心思想是使用分隔参数,以便我们能够#1使用前导空格或括号组进行抓取,并能够对其进行测试。此方法最适合用于单标记标记:通过这种方式查找多标记标记可能会花费更多工作。

由于这里没有分配,我使代码可扩展:这可能是也可能不是所希望的。

可以使用查找表来查找 token 类型

\documentclass{standalone}
\usepackage{pgfkeys}
\usepackage{expl3}
\ExplSyntaxOn
\tl_new:N \l_chronos_format_tl
\tl_new:N \l__chronos_tmp_tl
\cs_new_protected:Npn \chronos_format:n #1
  { \tl_set:Nn \l_chronos_format_tl {#1} }
\cs_new:Npn \chronos_print:n #1
  {  \__chronos_print:w #1 - - - \q_stop }
\cs_new:Npn \__chronos_print:w #1 - #2 - #3 - #4 \q_stop
  {
    \bool_if:nTF
      {
        \tl_if_blank_p:n {#1} ||
        \tl_if_blank_p:n {#2} ||
        \tl_if_blank_p:n {#3}
      }
      { \ERROR }
      { \chronos_print:nnn {#1} {#2} {#3} }
  }
\cs_new:Npn \chronos_print:nnn #1#2#3
  {
    \exp_after:wN \__chronos_loop:wnnn \l_chronos_format_tl
      \q_recursion_tail \q_recursion_stop {#1} {#2} {#3}
  }
\cs_new:Npn \__chronos_loop:wnnn #1 \q_recursion_stop % Three n-type follow
  {
    \tl_if_head_is_N_type:nTF {#1}
      { \__chronos_N_type:Nwnnn }
      {
        \tl_if_head_is_group:nTF {#1}
          { \__chronos_group:nwnnn }
          { \__chronos_space:wwnnn }
      }
    #1 \q_recursion_stop
  }
\cs_new:Npn \__chronos_N_type:Nwnnn #1#2 \q_recursion_stop #3#4#5
  {
    \if_meaning:w \q_recursion_tail #1
      \exp_after:wN \__chronos_end:wnnn
    \fi:
    \cs_if_exist_use:cTF { __chronos_replace_ #1 :nnn }
      { {#3} {#4} {#5} }
      {#1}
    \__chronos_loop:wnnn #2 \q_recursion_stop {#3} {#4} {#5}
  }
\cs_new:Npn \__chronos_replace_Y:nnn #1#2#3 {#1}
\cs_new:Npn \__chronos_replace_M:nnn #1#2#3 {#2}
\cs_new:Npn \__chronos_replace_D:nnn #1#2#3 {#3}
\cs_new:Npn \__chronos_group:nwnnn #1#2 \q_recursion_stop
  {
    {#1}
    \__chronos_loop:wnnn #2 \q_recursion_stop
  }
\exp_last_unbraced:NNo
  \cs_new:Npn \__chronos_space:wwnnn \c_space_tl #1 \q_recursion_stop
  {
    \c_space_tl
    \__chronos_loop:wnnn #1 \q_recursion_stop
  }
\cs_new:Npn \__chronos_end:wnnn #1 \q_recursion_stop #2#3#4 { }
\ExplSyntaxOff
\pgfkeys{
  chronos/.is family,
  chronos/format/.ecode =
    \unexpanded\expandafter{\csname\detokenize{chronos_format:n}\endcsname}%
      {#1},
  chronos/date/.code =
    \unexpanded\expandafter{\csname\detokenize{chronos_print:n}\endcsname}%
      {#1},
}
\begin{document}

\pgfkeys{chronos/format = date: Y, chronos/date = 2016-04-25}

\end{document}

(我们对这种循环的代码expl3是内部的,因为我们不想更广泛地推广它。如果有更多的用例,我会提供记录的代码接口。)

相关内容