1

I can do this with brute force, but I've run into this problem in several settings and can't seem to get the macro expansion to work out the way I want. Here's my current code (which is pretty close to minimal as is); the goal is to use forloops to expedite the building of some routine tables that have commands for each cell that can then be renewed to place contents in those cells...

\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}

This produces a predictable (incorrect) outcome of 5 lines of "period 8" rows instead of the desired periods 1 through 5. I know this is due to the expansion of the counters in the forloop, and normally I'd use an \edef or \let instead of the \def, but this results in the "incorrectly placed alignment character &" error because it can't expand the & in the line. So, is there some workaround for this? I haven't gotten noexpand to work on the & and I can't seem to figure out how to force expansion on the counter but not on the & character. I suspect I'm just doing something silly, but I can't seem to find a solution via Googling either.

Thanks!

1

Let's look at \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
        }
    }
}

This command initiates a \forloop. Due to the \forloop macros \Periodi, \Periodii, \Periodiii, \Periodiv and \Periodv get defined.

Each of these \Period...-macros in turn expands to \csname..\endcsname-constructs wherefrom control sequence tokens get formed at the time of carrying out the \Period...-macro in question.
Besides this, each of these \Period...-macros also spits out a command for at the time of carrying out the \Period...-macro in question delivering a lowercase-roman representation of the value of the Iter3-counter current at the time of carrying out the \Period...-macro in question.

But:

  • The control-sequence-tokens coming from the \csname..\endcsname-constructs should not be formed at the time of carrying out the \Period...-macro in question but should be formed at the time of defining the \Period...-macro in question.

  • The \roman{Iter3}-thingie should not be carried out at the time of carrying out the \Period...-macro in question but also at the time of defining the \Period...-macro in question.

Thus you need to trigger one-level-expansion of the \csname..\endcsname-constructs and total expansion of \roman{Iter3} at the time of defining the \Period...-macros already.

On the one hand the \edef- and the \xdef-primitives are there for triggering expansion at the time of defining. On the other hand you can suppress expansion via \noexpand. (In LaTeX you can also use \protected@edef and take advantage of the \protect-mechanism. But this is not needed in this specific scenario.)

Just use \edef, put \noexpand in front of everything that shall not be expanded at definition-time but at the time of carrying out the macro that is to be defined, and put \expandafter\noexpand in front of everything whereof you need only one-level-expansion at definition-time—in the code below, I also did something about superfluous spaces that come from un-commented line-endings etc:

\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}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.