为 tabularx 行定义一个宏,该宏扩展了计数器值,但不会在 & 上出错

为 tabularx 行定义一个宏,该宏扩展了计数器值,但不会在 & 上出错

我可以用蛮力做到这一点,但我在几种设置中都遇到了这个问题,似乎无法让宏扩展按照我想要的方式工作。这是我当前的代码(非常接近最小代码);目标是使用 forloops 来加快构建一些例程表,这些表包含每个单元格的命令,然后可以更新这些命令以将内容放置在这些单元格中……

\documentclass{article}

\usepackage{forloop}
\usepackage{tabularx}

\newcounter{Iter1}% For iteration
\setcounter{Iter1}{0}% For iteration
\newcounter{Iter2}% For iteration
\setcounter{Iter2}{0}% For iteration

\newcounter{day}% For adding new office hours
\newcounter{period}% For adding new office hours

\newcommand{\createMacros}{% 
    % Syntax: Creates a set of macros, one for each day and period number combo, 
    %           of the form \dayiiperiodiv for roman numerals ii and iv.

    \forloop{Iter1}{1}{\arabic{Iter1} < 8}{% Day counter
        \forloop{Iter2}{1}{\arabic{Iter2} < 11}{% Period Counter
            \expandafter\def\csname day\roman{Iter2}period\roman{Iter1}\endcsname{}
        }
    }
% Create all the period header macros:
\newcommand{\periodiHeader}{\textbf{Period 1} \\ \textbf{7:25-8:15}}
\newcommand{\periodiiHeader}{\textbf{Period 2} \\ \textbf{8:30-9:20}}
\newcommand{\periodiiiHeader}{\textbf{Period 3} \\ \textbf{9:35-10:25}}
\newcommand{\periodivHeader}{\textbf{Period 4} \\ \textbf{10:40-11:30}}
\newcommand{\periodvHeader}{\textbf{Period 5} \\ \textbf{11:45-12:35}}
\newcommand{\periodviHeader}{\textbf{Period 6} \\ \textbf{12:50-1:40}}
\newcommand{\periodviiHeader}{\textbf{Period 7} \\ \textbf{1:55-2:45}}
\newcommand{\periodviiiHeader}{\textbf{Period 8} \\ \textbf{3:00-3:50}}
\newcommand{\periodixHeader}{\textbf{Period 9} \\ \textbf{4:05-4:55}}
\newcommand{\periodxHeader}{\textbf{Period 10} \\ \textbf{5:10-6:00}}
}

\newcommand{\addOH}[4]{% Adds a new office hour to a time frame.
    %Syntax: \addOH{DAY}{PERIOD}{NAME}{OFFICE} Adds an office hour for NAME in OFFICE on DAY at PERIOD time.
    \setcounter{day}{#1}
    \setcounter{period}{#2}
    \expandafter\let\expandafter\tempMacro\csname day\roman{day}period\roman{period}\endcsname
    \expandafter\def\csname day\roman{day}period\roman{period}\endcsname{#3 (LIT: #4)\tempMacro}
}
\newcounter{Iter3}
\setcounter{Iter3}{0}
\newcommand{\CreatePeriods}{
\setcounter{Iter1}{1}
    \forloop{Iter1}{1}{\arabic{Iter1} < 8}{% Period counter
    \refstepcounter{Iter3}
        \expandafter\def\csname Period\roman{Iter1}\endcsname{
            \csname period\roman{Iter1}Header\endcsname Test \roman{Iter3} & 
            \csname dayiperiod\roman{Iter1}\endcsname & 
            \csname dayiiperiod\roman{Iter1}\endcsname & 
            \csname dayiiiperiod\roman{Iter1}\endcsname & 
            \csname dayivperiod\roman{Iter1}\endcsname & 
            \csname dayvperiod\roman{Iter1}\endcsname \\ \hline
        }
    }
}

\begin{document}

\createMacros
\addOH{2}{2}{Jason}{475}

\CreatePeriods

\begin{tabularx}{\textwidth}{|c|X|X|X|X|X|X|X|}
\hline
& \textbf{Monday} & \textbf{Tuesday} & \textbf{Wednesday} & \textbf{Thursday} & \textbf{Friday} \\ \hline
\Periodi
\Periodii
\Periodiii
\Periodiv
\Periodv

\end{tabularx}

\end{document}

这会产生可预测的(不正确的)结果,即 5 行“周期 8”行,而不是所需的周期 1 到 5。我知道这是由于 for 循环中计数器的扩展造成的,通常我会使用 or\edef\let不是\def,但这会导致“对齐字符 & 放置不正确”错误,因为它无法扩展行中的 &。那么,有没有什么解决方法?我还没有让 noexpand 在 & 上工作,而且我似乎无法弄清楚如何强制扩展计数器而不是 & 字符。我怀疑我只是做了一些愚蠢的事情,但我似乎也无法通过谷歌搜索找到解决方案。

谢谢!

答案1

让我们看看\CreatePeriods

\newcommand{\CreatePeriods}{
    \setcounter{Iter1}{1}
    \forloop{Iter1}{1}{\arabic{Iter1} < 8}{% Period counter
        \refstepcounter{Iter3}
        \expandafter\def\csname Period\roman{Iter1}\endcsname{
            \csname period\roman{Iter1}Header\endcsname Test \roman{Iter3} & 
            \csname dayiperiod\roman{Iter1}\endcsname & 
            \csname dayiiperiod\roman{Iter1}\endcsname & 
            \csname dayiiiperiod\roman{Iter1}\endcsname & 
            \csname dayivperiod\roman{Iter1}\endcsname & 
            \csname dayvperiod\roman{Iter1}\endcsname \\ \hline
        }
    }
}

此命令启动\forloop。由于\forloop\Periodi\Periodii\Periodiii\Periodiv\Periodv定义。

这些\Period...宏依次扩展为\csname..\endcsname构造,从而形成控制序列标记在执行时有问题的宏\Period...
除此之外,这些\Period...-macro 还会发出一个命令在执行时所讨论的宏\Period...在执行所讨论的宏时提供 Iter3 计数器电流值的罗马小写表示\Period...

但:

  • 来自构造的控制序列标记\csname..\endcsname不应该在执行时形成\Period...不应在执行相关宏在定义时有问题的宏\Period...

  • -thingie\roman{Iter3}不应在执行相关 -macro 时执行,也应在定义相关 -macro\Period...时执行。\Period...

因此,您需要在定义宏时触发构造的一级扩展\csname..\endcsname和总体扩展。\roman{Iter3}\Period...

一方面,\edef- 和\xdef- 基元用于在定义时触发扩展。另一方面,您可以通过 抑制扩展\noexpand。(在 LaTeX 中,您也可以使用\protected@edef并利用\protect- 机制。但在此特定场景中不需要这样做。)

只需使用\edef,把\noexpand所有在定义时不需要扩展但在执行要定义的宏时需要扩展的内容放在前面,并把\expandafter\noexpand所有在定义时只需要一级扩展的内容放在前面 - 在下面的代码中,我还对来自未注释的行尾等的多余空格做了一些处理:

\documentclass{article}

\usepackage{forloop}
\usepackage{tabularx}

\newcounter{Iter1}% For iteration
\setcounter{Iter1}{0}% For iteration
\newcounter{Iter2}% For iteration
\setcounter{Iter2}{0}% For iteration

\newcounter{day}% For adding new office hours
\newcounter{period}% For adding new office hours

\newcommand{\createMacros}{% 
    % Syntax: Creates a set of macros, one for each day and period number combo, 
    %           of the form \dayiiperiodiv for roman numerals ii and iv.
       \forloop{Iter1}{1}{\arabic{Iter1} < 8}{% Day counter
        \forloop{Iter2}{1}{\arabic{Iter2} < 11}{% Period Counter
            \expandafter\def\csname day\roman{Iter2}period\roman{Iter1}\endcsname{}%
        }%
    }%
  % Create all the period header macros:
  \newcommand{\periodiHeader}{\textbf{Period 1}&&&&&\\%
              \textbf{7:25-8:15}}%
  \newcommand{\periodiiHeader}{\textbf{Period 2}&&&&&\\%
              \textbf{8:30-9:20}}%
  \newcommand{\periodiiiHeader}{\textbf{Period 3}&&&&&\\%
              \textbf{9:35-10:25}}%
  \newcommand{\periodivHeader}{\textbf{Period 4}&&&&&\\%
              \textbf{10:40-11:30}}%
  \newcommand{\periodvHeader}{\textbf{Period 5}&&&&&\\%
              \textbf{11:45-12:35}}%
  \newcommand{\periodviHeader}{\textbf{Period 6}&&&&&\\%
              \textbf{12:50-1:40}}%
  \newcommand{\periodviiHeader}{\textbf{Period 7}&&&&&\\%
              \textbf{1:55-2:45}}%
  \newcommand{\periodviiiHeader}{\textbf{Period 8}&&&&&\\%
              \textbf{3:00-3:50}}%
  \newcommand{\periodixHeader}{\textbf{Period 9}&&&&&\\%
              \textbf{4:05-4:55}}%
  \newcommand{\periodxHeader}{\textbf{Period 10}&&&&&\\%
              \textbf{5:10-6:00}}%
}

\newcommand{\addOH}[4]{% Adds a new office hour to a time frame.
    %Syntax: \addOH{DAY}{PERIOD}{NAME}{OFFICE} Adds an office hour for NAME in OFFICE on DAY at PERIOD time.
    \setcounter{day}{#1}%
    \setcounter{period}{#2}%
    \expandafter\let\expandafter\tempMacro\csname day\roman{day}period\roman{period}\endcsname
    \expandafter\def\csname day\roman{day}period\roman{period}\endcsname{#3 (LIT: #4)\tempMacro}%
}
\newcounter{Iter3}
\setcounter{Iter3}{0}
\newcommand{\CreatePeriods}{%
    \setcounter{Iter1}{1}
    \forloop{Iter1}{1}{\arabic{Iter1} < 8}{% Period counter
        \stepcounter{Iter3}% <- I think \refstepcounter is not needed
        % Use `\edef` for triggering expansion of `\csname..\endcsname` for
        % obtaining the control-sequence-token and expansion of `\roman{Iter3}`
        % at definition-time already:
        \expandafter\edef\csname Period\roman{Iter1}\endcsname{%
            \expandafter\noexpand\csname period\roman{Iter1}Header\endcsname Test \roman{Iter3} &%
            \expandafter\noexpand\csname dayiperiod\roman{Iter1}\endcsname &%
            \expandafter\noexpand\csname dayiiperiod\roman{Iter1}\endcsname &%
            \expandafter\noexpand\csname dayiiiperiod\roman{Iter1}\endcsname &%
            \expandafter\noexpand\csname dayivperiod\roman{Iter1}\endcsname &%
            \expandafter\noexpand\csname dayvperiod\roman{Iter1}\endcsname
            \noexpand\\%
            \noexpand\hline
        }%
    }%
}%

\begin{document}

\createMacros
\addOH{2}{2}{Jason}{475}

\CreatePeriods

\begin{tabularx}{\textwidth}{|c|X|X|X|X|X|X|X|}
\hline
& \textbf{Monday} & \textbf{Tuesday} & \textbf{Wednesday} & \textbf{Thursday} & \textbf{Friday} \\ \hline
\Periodi
\Periodii
\Periodiii
\Periodiv
\Periodv

\end{tabularx}

\end{document}

相关内容