在Expl3中通过交叉引用`\ref{label}`编号无法生成文件名字符串?

发布于 2022-05-05 21:55:18

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}

查看更多

关注者
0
被浏览
1.3k
雾月
雾月 2022-05-06
这家伙很懒,什么也没写!

有两个问题,

  1. \ref 不能被完全展开,因此不能用于 fxe 等展开类型中,否则会出错,必须使用底层的宏来得到 ref;
  2. 使用 \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)与其它主文件的辅助文件区分开来。

1 个回答

撰写答案

请登录后再发布答案,点击登录

发布
问题

分享
好友

手机
浏览

扫码手机浏览