Ubuntu20.04+TeXLive2022平台。
在基于l3draw用Expl3开发chinesechess中国象棋排版宏包中已发布于CTAN: https://www.ctan.org/pkg/chinesechess,现需对setcchessman*环境打谱过程的棋谱进行记录输出。
初步的想法是通过在环境中将打谱过程记录在\l__cchess_manual_clist列表中,在环境结束时用\refstepcounter{setman}递增计数器,并根据环境的label选项添加label,然后将棋谱列表中的数据写入由label选项值和计数器值拼接成的文件中,如test011.man`。
再设计一个命令\printman,将label值作为其参数传入,然后根据label值和\ref{label}结果再拼接出文件名,如test011.man。读取这个文件,将棋谱正常输出即可。
目前,存入文件测试通过,读取文件也测试通过,并且直接使用\ref也可以实现正确的交叉引用。
但是,在读取文件前,却无法根据label值和\ref{label}结果拼接出文件名,目前查到的是无法得到正确的\ref{label}结果的原因。
期望网友能够分析一下该MWE,给一个解决方案,以期能完善chinesechess中国象棋排版宏包。该功能的MWE如下:
\documentclass{ctexart}
\usepackage{xparse}
\ExplSyntaxOn
% \label命令变体
\cs_set_eq:NN \__cchess_setman_label:n \label
\cs_generate_variant:Nn \__cchess_setman_label:n { x }
% 是否输出棋谱
\bool_new:N   \l__cchess_with_setman_bool
% 棋谱文字说明列表(如车一进二等)
\clist_new:N \l__cchess_manual_clist
% 打谱环境棋谱标签
\tl_new:N    \l__cchess_setman_label_tl
\tl_new:N    \l__cchess_setman_label_num_tl
% 打谱环境用计数器
\newcounter{setman}
\coffin_new:N \l__cchess_manual_coffin
% key_value选项设计
\keys_define:nn { cchess }
  {
    % 棋盘背景图片
    label .tl_gset:N  = \l__cchess_setman_label_tl ,
    label .initial:n = {} ,
  }
% 打谱排版环境用户接口
\NewDocumentEnvironment{ setcchessman* }{  O{} +b }
  {
    \group_begin:
      \bool_set_true:N  \l__cchess_with_setman_bool
      \keys_set:nn { cchess } { #1 }
      \__cchess_setcchessman_pre_setup:n { #2 }
  }{
      \__cchess_setcchessman_post_setup:
    \group_end:
  }
% 棋谱输出用户接口
% #1 棋谱label
\NewDocumentCommand{\printman}{ m }
  {
    \__cchess_setman_print:n { #1 }
  }
% 打谱环境前处理函数
% #1 打谱命令
\cs_new:Npn \__cchess_setcchessman_pre_setup:n #1
  {
    \hcoffin_set:Nn \l__cchess_manual_coffin
      { 这是一个棋盘 }
    \clist_put_right:Nn \l__cchess_manual_clist { 车九进一 }
    \clist_put_right:Nn \l__cchess_manual_clist { 马3退2   }
  }
% 打谱环境后处理函数
\cs_new:Nn \__cchess_setcchessman_post_setup:
  {
    % 输出结果盒子容器
    \coffin_typeset:Nnnnn \l__cchess_manual_coffin
      { l }{ b } { 0pt } { 0pt }
    % 星号环境需要输出打谱记录
    \bool_if:NT \l__cchess_with_setman_bool
      {
        % 递增计数器
        \refstepcounter{setman}
        % 设置label标签
        \__cchess_setman_label:x { \l__cchess_setman_label_tl }
        % 构造文件名
        \iow_open:Nn \g_tmpa_iow { \l__cchess_setman_label_tl\thesetman .man }
        % 遍历打谱记录列表,输出打谱记录
        \bool_until_do:nn { \clist_if_empty_p:N \l__cchess_manual_clist }
          {
            \clist_pop:NN \l__cchess_manual_clist \l_tmpa_tl
            \iow_now:Nx \g_tmpa_iow { \l_tmpa_tl }
          }
        \iow_close:N \g_tmpa_iow
      }
  }
% 棋谱输出
\cs_new:Npn \__cchess_setman_print:n #1
  {
    % 根据棋谱label构建文件名
    % 此处无法构建文件名
    \tl_set:Nf \l_tmpa_tl { #1 \ref { #1 } .man }
    % \tl_show:N \l_tmpa_tl
    \ior_open:NnTF \g_tmpb_ior {test011.man}
      {
        \ior_str_map_inline:Nn
        % \ior_map_inline:Nn
          \g_tmpb_ior
          {
            ##1\par
          }
      }
      { \msg_error:nnn { csv } { file-not-found } { test011.man } }
    \iow_close:N \g_tmpa_ior
  }
% 文件不存在错误提示
\msg_new:nnn { cchess } { file-not-found } { File~`#1'~not~found. }
\ExplSyntaxOff
\begin{document}
天圆地方大战的棋谱如棋谱\ref{test01}所示。
\begin{setcchessman*}[label=test01]
  % 打谱命令
\end{setcchessman*}
\bigskip
这是一个棋谱
\printman{ test01 }
\bigskip
\bigskip
昏天黑地大战的棋谱如棋谱\ref{test02}所示。
\begin{setcchessman*}[label=test02]
  % 打谱命令
\end{setcchessman*}
\end{document}有两个问题,
\ref 不能被完全展开,因此不能用于 f、x、e 等展开类型中,否则会出错,必须使用底层的宏来得到 ref;\printman{ test01 },时,一般两侧的空格是不需要的,使用 \NewDocumentCommand { >{\TrimSpaces} m } 在传参时将其去除。改动的地方只有两个:
\makeatletter
% 棋谱输出用户接口
% #1 棋谱label
\NewDocumentCommand{\printman}{ >{\TrimSpaces} m } % 去掉两侧空格
  {
    \__cchess_setman_print:n { #1 }
  }
\cs_set:Npn \use_i:nnnnn #1#2#3#4#5 {#1} % LaTeX3 并未定义 \use_i:nnnnn
% 棋谱输出
\cs_new:Npn \__cchess_setman_print:n #1
  {
    \cs_if_exist:cTF { r@#1 }
      {
        % 这一步是得到 ref,它保存在 \r@#1 中。\r@#1 有两项,当使用 hyperref 时,
        % \r@#1 有 5 项,这里使用 \empty 统一解决
        \tl_set:Nx \l_tmpa_tl 
          { #1 \exp_args:NNc \exp_after:wN \use_i:nnnnn { r@ #1 } \c_empty_tl \c_empty_tl \c_empty_tl .man }
        \ior_open:NnTF \g_tmpb_ior { \l_tmpa_tl }
          {
            \ior_str_map_inline:Nn
            % \ior_map_inline:Nn
              \g_tmpb_ior
                { ##1\par }
          }
          { \msg_error:nnx { csv } { file-not-found } { \l_tmpa_tl } }
        \iow_close:N \g_tmpa_ior
      }
    { \G@refundefinedtrue }% 引用未定义
  }完整代码:
\documentclass{ctexart}
\usepackage{xparse}
%\usepackage{hyperref}
%\usepackage{nameref,cleveref}
\makeatletter
\ExplSyntaxOn
% \label命令变体
\cs_new_protected_nopar:Npn \__cchess_setman_label:n { \label }
\cs_generate_variant:Nn \__cchess_setman_label:n { x }
\cs_set:Npn \use_i:nnnnn #1#2#3#4#5 {#1}
% 是否输出棋谱
\bool_new:N   \l__cchess_with_setman_bool
% 棋谱文字说明列表(如车一进二等)
\clist_new:N \l__cchess_manual_clist
% 打谱环境棋谱标签
\tl_new:N    \l__cchess_setman_label_tl
\tl_new:N    \l__cchess_setman_label_num_tl
% 打谱环境用计数器
\newcounter{setman}
\coffin_new:N \l__cchess_manual_coffin
% key_value选项设计
\keys_define:nn { cchess }
  {
    % 棋盘背景图片
    label .tl_gset:N  = \l__cchess_setman_label_tl ,
    label .initial:n = {} ,
  }
% 打谱排版环境用户接口
\NewDocumentEnvironment{ setcchessman* }{  O{} +b }
  {
    \group_begin:
      \bool_set_true:N  \l__cchess_with_setman_bool
      \keys_set:nn { cchess } { #1 }
      \__cchess_setcchessman_pre_setup:n { #2 }
  }{
      \__cchess_setcchessman_post_setup:
    \group_end:
  }
% 棋谱输出用户接口
% #1 棋谱label
\NewDocumentCommand{\printman}{ >{\TrimSpaces} m }
  {
    \__cchess_setman_print:n { #1 }
  }
% 打谱环境前处理函数
% #1 打谱命令
\cs_new:Npn \__cchess_setcchessman_pre_setup:n #1
  {
    \hcoffin_set:Nn \l__cchess_manual_coffin
      { 这是一个棋盘 }
    \clist_put_right:Nn \l__cchess_manual_clist { 车九进一 }
    \clist_put_right:Nn \l__cchess_manual_clist { 马3退2   }
  }
% 打谱环境后处理函数
\cs_new:Nn \__cchess_setcchessman_post_setup:
  {
    % 输出结果盒子容器
    \coffin_typeset:Nnnnn \l__cchess_manual_coffin
      { l }{ b } { 0pt } { 0pt }
    % 星号环境需要输出打谱记录
    \bool_if:NT \l__cchess_with_setman_bool
      {
        % 递增计数器
        \refstepcounter{setman}
        % 设置label标签
        \__cchess_setman_label:x { \l__cchess_setman_label_tl }
        % 构造文件名
        \iow_open:Nn \g_tmpa_iow { \l__cchess_setman_label_tl\thesetman .man }
        % 遍历打谱记录列表,输出打谱记录
        \bool_until_do:nn { \clist_if_empty_p:N \l__cchess_manual_clist }
          {
            \clist_pop:NN \l__cchess_manual_clist \l_tmpa_tl
            \iow_now:Nx \g_tmpa_iow { \l_tmpa_tl }
          }
        \iow_close:N \g_tmpa_iow
      }
  }
% 棋谱输出
\cs_new:Npn \__cchess_setman_print:n #1
  {
    % 根据棋谱label构建文件名
    % 此处无法构建文件名
    \cs_if_exist:cTF { r@#1 }
      {
        \tl_set:Nx \l_tmpa_tl 
          { #1 \exp_args:NNc \exp_after:wN \use_i:nnnnn { r@ #1 } \c_empty_tl \c_empty_tl \c_empty_tl .man }
        \ior_open:NnTF \g_tmpb_ior { \l_tmpa_tl }
          {
            \ior_str_map_inline:Nn
            % \ior_map_inline:Nn
              \g_tmpb_ior
                { ##1\par }
          }
          { \msg_error:nnx { csv } { file-not-found } { \l_tmpa_tl } }
        \iow_close:N \g_tmpa_ior
      }
    { \G@refundefinedtrue }% 引用未定义
  }
% 文件不存在错误提示
\msg_new:nnn { cchess } { file-not-found } { File~`#1'~not~found. }
\ExplSyntaxOff
\begin{document}
天圆地方大战的棋谱如棋谱 \ref{test01} 所示。
\begin{setcchessman*}[label=test01]
  % 打谱命令
\end{setcchessman*}
\bigskip
这是一个棋谱
\printman{ test01 }
\bigskip
\bigskip
昏天黑地大战的棋谱如棋谱 \ref{test02} 所示。
\begin{setcchessman*}[label=test02]
  % 打谱命令
\end{setcchessman*}
\end{document}写入临时文件时,可以在文件名前加上 \jobname(\c_sys_jobname_str)与其它主文件的辅助文件区分开来。
非常感谢,采用该方案解决了这个问题:

其它细节问题,我再时行必要的完善。