LaTeX 立即评估

发布于 2024-09-29 03:51:19 字数 817 浏览 3 评论 0原文

我正在尝试在 LaTeX 中编写一个命令,该命令接受诸如 8:00A 之类的字符串并将其转换为分钟数,作为使用 TikZ 绘制课程表的脚本的一部分。然而,我遇到了一些问题 - 似乎 LaTeX 实际上并不评估命令的内容。

我的命令目前是:

\newcommand{\timetominutes}[1]{
  \IfSubStr{#1}{P}{720}{0}+\IfSubStr{#1}{P}{\StrBetween{#1}{:}{P}}{\StrBetween{#1}{:}{A}}+60*\StrBefore{#1}{:}
}

如果您打印出其中的文本,它将正确计算从午夜开始的分钟数。但是,如果在另一个函数中使用,很明显它实际上并不运行任何这些命令 - 它只是返回包含这些命令的文本。因此,如果我写:

\myfunc{\timetominutes{8:00A}}

\myfunc 看到的不是像 0+00+60*8 这样有用的东西,而是看到 \IfSubStr{8:00A}{P} {720}{0}+\IfSubStr{8:00A}{P}{\StrBetween{8:00A}{:}{P}}{\StrBetween{8:00A}{:}{A}}+60* \StrBefore{8:00A}{:}。这对我来说绝对没用,而且我找不到一种方法来强制 LaTeX 在主命令之前执行子命令。我认为有办法做到这一点,但 LaTeX 文档很少,我似乎找不到任何东西。

或者,如果有一种方法可以让 LaTeX 停止抱怨太多的 }(当我有正确的数字时),那就可以了。

I'm trying to write a command in LaTeX that takes a string such as 8:00A and converts it into the number of minutes, as part of a script to draw a class schedule using TikZ. However, I'm running into some issues - it seems that LaTeX doesn't actually evaluate the contents of a command.

My command is currently:

\newcommand{\timetominutes}[1]{
  \IfSubStr{#1}{P}{720}{0}+\IfSubStr{#1}{P}{\StrBetween{#1}{:}{P}}{\StrBetween{#1}{:}{A}}+60*\StrBefore{#1}{:}
}

If you print out the text from it, it will correctly calculate the number of minutes from midnight. However, if used inside another function, it becomes apparent that it doesn't actually run any of those commands - it merely returns text including those commands. So if I write:

\myfunc{\timetominutes{8:00A}}

Instead of \myfunc seeing something useful like 0+00+60*8, it sees \IfSubStr{8:00A}{P}{720}{0}+\IfSubStr{8:00A}{P}{\StrBetween{8:00A}{:}{P}}{\StrBetween{8:00A}{:}{A}}+60*\StrBefore{8:00A}{:}. This is absolutely useless to me and I can't seen to find a way to force LaTeX to execute subcommands before the main one. I assume there's a way to do it, but LaTeX documentation is scarce and I can't seem to find anything.

Alternatively, if there's a way to get LaTeX to stop complaining about too many }s (when I have the correct number), that could work.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

横笛休吹塞上声 2024-10-06 03:51:19

不幸的是,没有。正如 xstring 包所述:

这个包的宏不是纯粹可扩展的

(xtring_doc_en.pdf 的第 3.2 节)。

如果您不熟悉这个“可扩展”概念,那么它在 TeX 中是一个相当棘手的主题。简而言之,不可扩展的东西不能被评估为参数。在大多数 TeX 变体中,任何在某处使用赋值的东西都保证不可扩展,但也存在其他不可扩展的触发器。对于不熟悉 TeX“嘴”(TeX 中处理扩展等事务的部分)内部工作原理的人来说,解决此类问题相当困难。

提示:如果 LaTeX 代码是由脚本生成的:使用脚本来转换时间表达式,因为在字符串操作方面几乎任何编程语言都比 TeX 更容易使用。 (或者任何其他与此相关的内容。)


xstring 包确实暗示了一种解决方法:您可以通过添加 [\variable]< 将大多数操作的结果存储在变量中。 /code> 到通话结束。这意味着您需要将 \timetominutes 重写为逐段构建结果的内容,然后将该结果存储在命令序列中以供稍后使用。

用法:

\timetominutesinto\somevar{8:00A} % \somevar contains 48
\expandafter\myfunc\expandafter{\somevar} % calls \myfunc{48}

注意 \expandafter 的使用,它告诉 TeX 在下一个命令序列之后对命令序列进行简单的一级扩展(求值)。如果您没有使用两个 \expandafter,您将得到 \somevar 作为 \myfunc 的参数,而不是 48..

(注意:前面是丑陋的 TeX 代码!)

\makeatletter % allow @ in command names
\def\timetominutesinto#1#2{% 
  % #1 = command to store the result in
  % #2 = the text to parse
  % \ttm@tempa and \ttm@tempb are temporary variables for this macro
  \let\ttm@tempa\empty % make the command empty
  \IfSubStr{#2}{P}{%
    \def\ttm@tempa{720+}% set tempa to "720+"
    \StrBetween{#2}{:}{P}[\ttm@tempb]% store substring between : and P into tempb
    \edef\ttm@tempa{\ttm@tempa \ttm@tempb}% set tempa to tempa + tempb
  }{%
    \def\ttm@tempa{0+}% set tempa to 0+
    \StrBetween{#2}{:}{A}[\ttm@tempb]% store substring between : and A into tempb
    \edef\ttm@tempa{\ttm@tempa \ttm@tempb}% set tempa to tempa + tempb
  }%
  \edef\ttm@tempa{\ttm@tempa+60*}% set tempa to tempa + "+60*"
  \StrBefore{#2}{:}[\ttm@tempb]% store substring before : into tempb
  % now, set #1 to the result of evaluating the formula returned by concatenating
  % tempa and tempb
  \edef#1{\numexpr \ttm@tempa \ttm@tempb}%
}

\def 是与 LaTeX 的 \newcommand/\renewcommand 相对应的 TeX 原语。 \edef 表示扩展\def,它在将结果分配给命令序列之前评估定义。 \numexpr 计算一个简单的数字表达式,例如由上述命令创建的 x + m + h * 60。

还可以通过使用整数算术立即将结果计算为数字,而无需建立公式。但这会使代码更加偏离您的初衷。

可以通过 TeX 本身进行这些字符串操作,而无需使用 xstring 包(在这种特殊情况下甚至是可扩展的)。但这是相当低级的东西,如果您不是 TeX 专家,就很难重复这些内容。

Unfortunately, no. As the xstring package states:

The macros of this package are not purely expandable

(Section 3.2 of xtring_doc_en.pdf.)

This "expandable" concept, if you're not familiar with it, is quite a hairy subject in TeX. Simply put, something that is not expandable cannot be evaluated as an argument. Anything that uses an assignment somewhere is guaranteed not to be expandable in most TeX varieties, but other non-expandable triggers exist as well. The solution to such problems is rather difficult for anyone that's not familiar with the inner workings of TeX's "mouth" (the part of TeX that handles things like expansion).

Hint: If the LaTeX code is generated by a script: use the script to convert the time expressions, because just about any programming language is easier to use than TeX when it comes to string manipulation. (Or just about anything else for that matter.)


The xstring package does hint at a way out: You can store the result of most operations in a variable, by adding [\variable] to the end of the calls. This means you'd need to rewrite \timetominutes to something that builds up the result piece by piece, and then store that result in a command sequence for use later on.

Usage:

\timetominutesinto\somevar{8:00A} % \somevar contains 48
\expandafter\myfunc\expandafter{\somevar} % calls \myfunc{48}

Note the use of \expandafter, which tells TeX to do a simple one-level expansion (evaluation) of a command sequence after the next. If you didn't use the two \expandafters, you'd get \somevar as an argument to \myfunc, not 48.

(Caution: ugly TeX code ahead!)

\makeatletter % allow @ in command names
\def\timetominutesinto#1#2{% 
  % #1 = command to store the result in
  % #2 = the text to parse
  % \ttm@tempa and \ttm@tempb are temporary variables for this macro
  \let\ttm@tempa\empty % make the command empty
  \IfSubStr{#2}{P}{%
    \def\ttm@tempa{720+}% set tempa to "720+"
    \StrBetween{#2}{:}{P}[\ttm@tempb]% store substring between : and P into tempb
    \edef\ttm@tempa{\ttm@tempa \ttm@tempb}% set tempa to tempa + tempb
  }{%
    \def\ttm@tempa{0+}% set tempa to 0+
    \StrBetween{#2}{:}{A}[\ttm@tempb]% store substring between : and A into tempb
    \edef\ttm@tempa{\ttm@tempa \ttm@tempb}% set tempa to tempa + tempb
  }%
  \edef\ttm@tempa{\ttm@tempa+60*}% set tempa to tempa + "+60*"
  \StrBefore{#2}{:}[\ttm@tempb]% store substring before : into tempb
  % now, set #1 to the result of evaluating the formula returned by concatenating
  % tempa and tempb
  \edef#1{\numexpr \ttm@tempa \ttm@tempb}%
}

\def is the TeX primitive corresponding to LaTeX's \newcommand/\renewcommand. \edef means Expanding \def, which evaluates the definition before assigning the result to a command sequence. \numexpr evaluates a simple number expression, like x + m + h * 60 created by the command above.

It is also possible to calculate the result immediately as a number, without building up the formula, by using integer arithmetic. But that would make the code even more remote from your original intent.

It is possible to do these string manipulations through TeX itself, without using the xstring package (even expandible in this particular case). But that's pretty low level stuff, which cannot easily be repeated if you're not a TeX wizzard.

冷弦 2024-10-06 03:51:19

我是 PerlTeX 的忠实粉丝,因为它可以在 LaTeX 中进行编程交互。使用 Perl 定义函数通常比在 TeX 中编程更容易,并且对于更多的编程操作,它可以更强大。 PerlTeX 为您提供了 perl 和 Latex 之间的双向通信,这意味着您可以将 Perl 代码片段嵌入到 LaTeX 源代码中,而不必编写创建整个源文件的脚本。

这是您想要的示例。使用 perltex --latex=pdflatex test.tex 进行编译,其中文件已保存为 test.tex,并且您可以省略可选的 --latex=pdflatex 以生成 dvi。

\documentclass{article}
\usepackage{perltex}

\perlnewcommand{\timetominutes}[1]{
  #Argument is [h]h:mm[ ][a/p[m]] ie 8:00am or 4:05 P or 15:30 (24hr fmt)
  my $input = shift;
  my ($hours, $minutes, $AMPM) = $input =~ /(\d+)\:(\d+)\ *(\w*)/;
  if ($AMPM =~ /p/i) {
    $hours += 12;
  } elsif ($AMPM =~ /a/i and $hours == 12) {
    $hours = 0;
  }
  my $numberofminutes = 60*$hours + $minutes;
  return $numberofminutes;
  ## or you could
  # return '\myfunc{' . $numberofminutes . '}';
  ## or simply
  # return "\\myfunc\{$numberofminutes\}"; 
  ## I can't remember if you need to escape the curly braces
}

\begin{document}

It has been \timetominutes{8:00 pm} minutes since midnight.

\end{document}

就扩展而言,如果您仍然遇到 \myfunc 问题,无论它是什么,您都可以将它或它的包装函数定义为 perltex 函数,或者使用提供的备用返回值。

I am a big fan of PerlTeX for programmatic interaction in LaTeX. Using Perl do define functions is often easier than programming in TeX and for more programmatic manipulation it can be far more powerful. PerlTeX gives you a two-way communication between perl and latex, meaning that you embed snippets of Perl code in your LaTeX source, rather than having to write a script that creates your entire source file.

Here is an example of what you wanted. Compile with perltex --latex=pdflatex test.tex where the file has been saved as test.tex and you may omit the optional --latex=pdflatex for dvi generation.

\documentclass{article}
\usepackage{perltex}

\perlnewcommand{\timetominutes}[1]{
  #Argument is [h]h:mm[ ][a/p[m]] ie 8:00am or 4:05 P or 15:30 (24hr fmt)
  my $input = shift;
  my ($hours, $minutes, $AMPM) = $input =~ /(\d+)\:(\d+)\ *(\w*)/;
  if ($AMPM =~ /p/i) {
    $hours += 12;
  } elsif ($AMPM =~ /a/i and $hours == 12) {
    $hours = 0;
  }
  my $numberofminutes = 60*$hours + $minutes;
  return $numberofminutes;
  ## or you could
  # return '\myfunc{' . $numberofminutes . '}';
  ## or simply
  # return "\\myfunc\{$numberofminutes\}"; 
  ## I can't remember if you need to escape the curly braces
}

\begin{document}

It has been \timetominutes{8:00 pm} minutes since midnight.

\end{document}

As far as expansion goes, if you still have problems with \myfunc, whatever it is, you can define it or a wrapper function for it as a perltex function or use the alternate returns provided.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文