在给家教学生出题的过程中想实现节标题的自动化, 由于题目是每天都有,
所以我想交替用 \examsection
和 \answersection
来输出节标题, 并且设置页眉和目录, 目标效果如下:
1 2 3 4 5 6 7
| \begin{document} \setdate[2022, 2, 14] \examsection \answersection \examsection* \answersection* \end{document}
|
我刚刚开始学习 LaTeX3, 对一些函数的使用可能不太得当,
欢迎指出.
实现思路
首先我们定义一些变量:
1 2 3 4 5 6 7 8 9 10 11
| \int_new:N \g__dailyexam_year_int \int_new:N \g__dailyexam_month_int \int_new:N \g__dailyexam_day_int
\int_new:N \c__dailyexam_month_max_int \int_set:Nn \c__dailyexam_month_max_int { 12 } \int_new:N \l__dailyexam_day_max_int \int_new:N \l__dailyexam_day_feb_max_int
\clist_new:N \l__dailyexam_date_clist
|
然后定义一个 prop
, 来存储每个月的名称,
在这里用来输出报错信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| \prop_const_from_keyval:Nn \c__dailyexam_month_prop { { 1 } = { January }, { 2 } = { February }, { 3 } = { March }, { 4 } = { April }, { 5 } = { May }, { 6 } = { June }, { 7 } = { July }, { 8 } = { August }, { 9 } = { September }, { 10 } = { October }, { 11 } = { November }, { 12 } = { December} }
|
再定义一些信息, 分别为不合法的年, 月, 日, 因为闰年是从 1582
年的格里高利历开始的记法, 所以这里年要大于 1582 年.
1 2 3 4 5 6 7
| \msg_new:nnn { dailyexam } { year~is~illegal } { "year"~must~later~than~1582 } \msg_new:nnn { dailyexam } { month~is~illegal } { "month"~must~be~an~integer~among~1,~2,~3,...,~12 } \msg_new:nnn { dailyexam } { day~is~illegal } { #1~of~#2~has~only~#3~days, \\ "day"~must~be~an~integer~among~1,~2,~3,...,~#3 }
|
然后我们来根据月份来设置最大天数
\l_dailyexam_day_max_int
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| \cs_new:Nn \__dailyexam_set_max_day: { \int_case:nnT { \g__dailyexam_month_int } { { 1 } { } { 3 } { } { 5 } { } { 7 } { } { 8 } { } { 10 } { } { 12 } { } } { \int_set:Nn \l__dailyexam_day_max_int { 31 } }
\int_case:nnT { \g__dailyexam_month_int } { { 4 } { } { 6 } { } { 9 } { } { 11 } { } } { \int_set:Nn \l__dailyexam_day_max_int { 30 } } \int_compare:nNnTF { \g__dailyexam_year_int } < { 1582 } { \msg_error:nn { dailyexam } { year~is~illegal } } { \bool_if:nTF { ( \int_compare_p:nNn { \int_mod:nn { \g__dailyexam_year_int } { 100 } } = { 0 } && \int_compare_p:nNn { \int_mod:nn { \g__dailyexam_year_int } { 400 } } = { 0 } ) || ( ! \int_compare_p:nNn { \int_mod:nn { \g__dailyexam_year_int } { 100 } } = { 0 } && \int_compare_p:nNn { \int_mod:nn { \g__dailyexam_year_int } { 4 } } = { 0 } ) } { \int_set:Nn \l__dailyexam_day_feb_max_int { 29 } } { \int_set:Nn \l__dailyexam_day_feb_max_int { 28 } } } \int_compare:nNnT { \g__dailyexam_month_int } = { 2 } { \int_set_eq:NN \l__dailyexam_day_max_int \l__dailyexam_day_feb_max_int } }
|
下面是关键的一个函数, 将给定的一个日期向后移动一天:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| \cs_new:Nn \__dailyexam_next_day: { \int_compare:nNnTF { \g__dailyexam_day_int } = { \l__dailyexam_day_max_int } { \int_gset:Nn \g__dailyexam_day_int { 1 } \int_compare:nNnTF { \g__dailyexam_month_int } = { \c__dailyexam_month_max_int } { \int_gset:Nn \g__dailyexam_month_int { 1 } \int_gincr:N \g__dailyexam_year_int } { \int_gincr:N \g__dailyexam_month_int } \__dailyexam_set_max_day: } { \int_gincr:N \g__dailyexam_day_int } }
|
由于我们要使用 \setdate
来设置起始日期,
那么就需要防止用户输入不合法的日期, 如 13 月 32 日:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| \cs_new:Nn \__dailyexam_if_legal: { \int_compare:nNnT { \g__dailyexam_year_int } < { 1582 } { \msg_error:nn { dailyexam } { year~is~illegal } } \int_compare:nTF { 0 < \g__dailyexam_month_int <= \c__dailyexam_month_max_int } { \__dailyexam_set_max_day: } { \msg_error:nn { dailyexam } { month~is~illegal } } \int_compare:nF { 0 < \g__dailyexam_day_int <= \l__dailyexam_day_max_int } { \exp_args:NNx \prop_get:NnN \c__dailyexam_month_prop { \int_use:N \g__dailyexam_month_int } \l_tmpb_tl \msg_error:nnxxx { dailyexam } { day~is~illegal } { \tl_use:N \l_tmpb_tl } { \int_use:N \g__dailyexam_year_int } { \int_use:N \l__dailyexam_day_max_int } } }
|
下面来进行 \setdate
的内部函数的编写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
\cs_new:Npn \__dailyexam_setdate:n #1 { \clist_set:Nn \l__dailyexam_date_clist { #1 } \int_case:nnTF { \clist_count:N \l__dailyexam_date_clist } { { 3 } { \exp_args:NNx \int_gset:Nn \g__dailyexam_year_int { \clist_item:Nn \l__dailyexam_date_clist { 1 } } \clist_pop:NN \l__dailyexam_date_clist \l_tmpa_tl } { 2 } { \int_gset_eq:NN \g__dailyexam_year_int \c_sys_year_int } } { \exp_args:NNx \int_gset:Nn \g__dailyexam_month_int { \clist_item:Nn \l__dailyexam_date_clist { 1 } } \exp_args:NNx \int_gset:Nn \g__dailyexam_day_int { \clist_item:Nn \l__dailyexam_date_clist { 2 } } \__dailyexam_if_legal: } { \msg_new:nnn { dailyexam } { date~type~error } { You~must~input~"year,month,day"~or~"month,day"~type~argument. } \msg_error:nn { dailyexam } { date~type~error } } }
|
下面是输出当前日期的函数:
1 2 3 4 5 6 7 8 9 10
| \cs_set:Npn \__dailyexam_current_date:n #1 { \str_if_eq:nnT { #1 } { year } { \int_use:N \g__dailyexam_year_int . } \int_use:N \g__dailyexam_month_int . \int_use:N \g__dailyexam_day_int }
|
接下来是用户层的命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| \NewDocumentCommand { \currentdate } { o } { \__dailyexam_current_date:n { #1 } }
\NewDocumentCommand { \setdate } { O{ \int_use:N \c_sys_year_int, \int_use:N \c_sys_month_int, \int_use:N \c_sys_day_int } } { \__dailyexam_setdate:n { #1 } }
\NewDocumentCommand { \nextdate } { } { \__dailyexam_next_day: }
|
由于我们还有页眉的要求, 并需要控制展开的顺序, 所以先将
\fancyhead
与 \section*
设置为 LaTeX3
格式的函数, 同时给 \section*
加入了目录
1 2 3 4 5 6 7 8 9 10
| \cs_new:Npn \fancyhdr_fancyhead:nn #1#2 { \fancyhead[#1]{#2} } \cs_new:Npn \section_star:n #1 { \section*{ #1 } \addcontentsline{toc}{section}{#1} }
|
最后是目标中的两个函数 \examsection
和
\answersection
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
\NewDocumentCommand { \examsection } { s } { \clearpage \tl_set:Nn \l_tmpa_tl { \IfBooleanTF { #1 } { \__dailyexam_current_date:n { year } } { \__dailyexam_current_date:n { } } } \exp_args:Nx \section_star:n { Exercise~of~\tl_use:N \l_tmpa_tl } \exp_args:Nnx \fancyhdr_fancyhead:nn { L } { Exercise~of~\tl_use:N \l_tmpa_tl } }
\NewDocumentCommand { \answersection } { s } { \clearpage \tl_set:Nn \l_tmpa_tl { \IfBooleanTF { #1 } { \__dailyexam_current_date:n { year } } { \__dailyexam_current_date:n { } } } \exp_args:Nx \section_star:n { Answer~of~\tl_use:N \l_tmpa_tl } \exp_args:Nnx \fancyhdr_fancyhead:nn { L } { Answer~of~\tl_use:N \l_tmpa_tl } \__dailyexam_next_date: }
|
完整代码
读者可自行更改 \setdate
的参数来查看
\msg_error
的效果. 为了节约空间, 输出省略了大片的空白
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
| \documentclass{article} \usepackage{fancyhdr} \pagestyle{fancy} \fancyhf{} \fancyhead[R]{\thepage}
\ExplSyntaxOn
\cs_new:Npn \fancyhdr_fancyhead:nn #1#2 { \fancyhead[#1]{#2} } \cs_new:Npn \section_star:n #1 { \section*{ #1 } \addcontentsline{toc}{section}{#1} }
\int_new:N \g__dailyexam_year_int \int_new:N \g__dailyexam_month_int \int_new:N \g__dailyexam_day_int
\int_new:N \c__dailyexam_month_max_int \int_set:Nn \c__dailyexam_month_max_int { 12 } \int_new:N \l__dailyexam_day_max_int \int_new:N \l__dailyexam_day_feb_max_int
\clist_new:N \l__dailyexam_date_clist
\prop_const_from_keyval:Nn \c__dailyexam_month_prop { { 1 } = { January }, { 2 } = { February }, { 3 } = { March }, { 4 } = { April }, { 5 } = { May }, { 6 } = { June }, { 7 } = { July }, { 8 } = { August }, { 9 } = { September }, { 10 } = { October }, { 11 } = { November }, { 12 } = { December} }
\msg_new:nnn { dailyexam } { year~is~illegal } { "year"~must~later~than~1582 } \msg_new:nnn { dailyexam } { month~is~illegal } { "month"~must~be~integer~among~1,~2,~3,...,~12 } \msg_new:nnn { dailyexam } { day~is~illegal } { #1~of~#2~has~only~#3~days, \\ "day"~must~be~integer~among~1,~2,~3,...,~#3 }
\cs_new:Nn \__dailyexam_set_max_day: { \int_case:nnT { \g__dailyexam_month_int } { { 1 } { } { 3 } { } { 5 } { } { 7 } { } { 8 } { } { 10 } { } { 12 } { } } { \int_set:Nn \l__dailyexam_day_max_int { 31 } }
\int_case:nnT { \g__dailyexam_month_int } { { 4 } { } { 6 } { } { 9 } { } { 11 } { } } { \int_set:Nn \l__dailyexam_day_max_int { 30 } } \int_compare:nNnTF { \g__dailyexam_year_int } < { 1582 } { \msg_error:nn { dailyexam } { year~is~illegal } } { \bool_if:nTF { ( \int_compare_p:nNn { \int_mod:nn { \g__dailyexam_year_int } { 100 } } = { 0 } && \int_compare_p:nNn { \int_mod:nn { \g__dailyexam_year_int } { 400 } } = { 0 } ) || ( ! \int_compare_p:nNn { \int_mod:nn { \g__dailyexam_year_int } { 100 } } = { 0 } && \int_compare_p:nNn { \int_mod:nn { \g__dailyexam_year_int } { 4 } } = { 0 } ) } { \int_set:Nn \l__dailyexam_day_feb_max_int { 29 } } { \int_set:Nn \l__dailyexam_day_feb_max_int { 28 } } } \int_compare:nNnT { \g__dailyexam_month_int } = { 2 } { \int_set_eq:NN \l__dailyexam_day_max_int \l__dailyexam_day_feb_max_int } }
\cs_new:Nn \__dailyexam_next_date: { \int_compare:nNnTF { \g__dailyexam_day_int } = { \l__dailyexam_day_max_int } { \int_gset:Nn \g__dailyexam_day_int { 1 } \int_compare:nNnTF { \g__dailyexam_month_int } = { \c__dailyexam_month_max_int } { \int_gset:Nn \g__dailyexam_month_int { 1 } \int_gincr:N \g__dailyexam_year_int } { \int_gincr:N \g__dailyexam_month_int } \__dailyexam_set_max_day: } { \int_gincr:N \g__dailyexam_day_int } }
\cs_new:Nn \__dailyexam_if_legal: { \int_compare:nNnT { \g__dailyexam_year_int } < { 1582 } { \msg_error:nn { dailyexam } { year~is~illegal } } \int_compare:nTF { 0 < \g__dailyexam_month_int <= \c__dailyexam_month_max_int } { \__dailyexam_set_max_day: } { \msg_error:nn { dailyexam } { month~is~illegal } } \int_compare:nF { 0 < \g__dailyexam_day_int <= \l__dailyexam_day_max_int } { \exp_args:NNx \prop_get:NnN \c__dailyexam_month_prop { \int_use:N \g__dailyexam_month_int } \l_tmpb_tl \msg_error:nnxxx { dailyexam } { day~is~illegal } { \tl_use:N \l_tmpb_tl } { \int_use:N \g__dailyexam_year_int } { \int_use:N \l__dailyexam_day_max_int } } }
\cs_new:Npn \__dailyexam_setdate:n #1 { \clist_set:Nn \l__dailyexam_date_clist { #1 } \int_case:nnTF { \clist_count:N \l__dailyexam_date_clist } { { 3 } { \exp_args:NNx \int_gset:Nn \g__dailyexam_year_int { \clist_item:Nn \l__dailyexam_date_clist { 1 } } \clist_pop:NN \l__dailyexam_date_clist \l_tmpa_tl } { 2 } { \int_gset_eq:NN \g__dailyexam_year_int \c_sys_year_int } } { \exp_args:NNx \int_gset:Nn \g__dailyexam_month_int { \clist_item:Nn \l__dailyexam_date_clist { 1 } } \exp_args:NNx \int_gset:Nn \g__dailyexam_day_int { \clist_item:Nn \l__dailyexam_date_clist { 2 } } \__dailyexam_if_legal: } { \msg_new:nnn { dailyexam } { date~type~error } { You~must~input~"year,month,day"~or~"month,day"~type~argument. } \msg_error:nn { dailyexam } { date~type~error } } }
\cs_set:Npn \__dailyexam_current_date:n #1 { \str_if_eq:nnT { #1 } { year } { \int_use:N \g__dailyexam_year_int . } \int_use:N \g__dailyexam_month_int . \int_use:N \g__dailyexam_day_int }
\NewDocumentCommand { \currentdate } { o } { \__dailyexam_current_date:n { #1 } }
\NewDocumentCommand { \setdate } { O{ \int_use:N \c_sys_year_int, \int_use:N \c_sys_month_int, \int_use:N \c_sys_day_int } } { \__dailyexam_setdate:n { #1 } }
\NewDocumentCommand { \nextdate } { } { \__dailyexam_next_date: }
\NewDocumentCommand { \examsection } { s } { \clearpage \tl_set:Nn \l_tmpa_tl { \IfBooleanTF { #1 } { \__dailyexam_current_date:n { year } } { \__dailyexam_current_date:n { } } } \exp_args:Nx \section_star:n { Exercise~of~\tl_use:N \l_tmpa_tl } \exp_args:Nnx \fancyhdr_fancyhead:nn { L } { Exercise~of~\tl_use:N \l_tmpa_tl } }
\NewDocumentCommand { \answersection } { s } { \clearpage \tl_set:Nn \l_tmpa_tl { \IfBooleanTF { #1 } { \__dailyexam_current_date:n { year } } { \__dailyexam_current_date:n { } } } \exp_args:Nx \section_star:n { Answer~of~\tl_use:N \l_tmpa_tl } \exp_args:Nnx \fancyhdr_fancyhead:nn { L } { Answer~of~\tl_use:N \l_tmpa_tl } \__dailyexam_next_date: } \ExplSyntaxOff \begin{document} \tableofcontents \setdate[2022, 2, 28] \examsection \answersection \examsection* \answersection* \end{document}
|
小结
这是我第一次使用 LaTeX3 做一个稍微像样的东西, 感觉到了比 LaTeX2e
更方便的宏展开, 以及各种规范定义带来的时间上的便利, 最后非常感谢 夏大鱼羊 在我学习 LaTeX3
的过程中给我提供的帮助.