如何使用 XString 来计算具有“错误” catcode 的字符串中的字符数?

如何使用 XString 来计算具有“错误” catcode 的字符串中的字符数?

我创建了一个 TiZ 坐标系来解决我遇到的定位问题。代码如下所示。但是,代码不起作用;如果你运行它,你将得到

太多 .

尽管测试 tikzpicture 中每个坐标上的点数都是正确的,但仍然会显示错误消息。仔细调查后发现,宏中\sidecs@numberofats没有填充正确的值:它最终总是为零。

由于我之前使用过\makeatletter定义坐标系代码,但“@”在tikzpicture坐标出现的位置是“其他”字符,因此我假设这是一个 catcode 问题。这一假设得到了以下事实的支持:如果您将其出现的所有位置(显然宏名称除外)更改@为,文档将成功编译并具有预期的外观。:

如何让 XString 计算@字符数?大多数 XString 宏都有一个带星号的版本(如手册所述)忽略 catcode,但无论出于何种原因,\StrCount它都没有。

(我想使用内置的 PGF 解析库而不是 XString 来执行此操作,但它的功能不够齐全。具体来说,似乎您无法提供适用于“任何不在单独指定的字符中的字符”的规则,并且因为我想接受节点名称 - 可能包含几乎任何字符 - 这不好。)

\documentclass{standalone}

\usepackage{tikz, xstring}

\makeatletter
    \def\sidecs@expandtotop{top}
    \def\sidecs@expandtobottom{bottom}
    \def\sidecs@expandtoleft{left}
    \def\sidecs@expandtoright{right}

    \def\sidecs@trimspacesleft#1{%
        \IfBeginWith{#1}%
                    { }%
                    {\StrGobbleLeft{#1}{1}[#1]\sidecs@trimspacesleft{#1}}%
                    {}%
    }
    \def\sidecs@trimspacesright#1{%
        \IfEndWith{#1}%
                  { }%
                  {\StrGobbleRight{#1}{1}[#1]\sidecs@trimspacesright{#1}}%
                  {}%
    }

    \tikzdeclarecoordinatesystem{side}{
        \begingroup
            \fullexpandarg
            \edef\sidecs@expandedarg{#1}
            \StrCount{\sidecs@expandedarg}{@}[\sidecs@numberofats]
            \if\sidecs@numberofats0
                \let\sidecs@partbeforeat\sidecs@expandedarg
                \def\sidecs@pos{0.5}
                \def\sidecs@posreversed{0.5}
            \else\if\sidecs@numberofats1
                \StrCut{\sidecs@expandedarg}{@}{\sidecs@partbeforeat}{\sidecs@pos}
                \pgfmathsubtract{1}{\sidecs@pos}
                \let\sidecs@posreversed\pgfmathresult
            \else
                \errmessage{Too many @'s in side cs coordinate}
            \fi\fi
            \StrCount{\sidecs@partbeforeat}{.}[\sidecs@numberofdots]
            \if\sidecs@numberofdots0
                \errmessage{Not enough .'s in side cs coordinate}
            \else\if\sidecs@numberofdots1
            \else
                \errmessage{Too many .'s in side cs coordinate}
            \fi\fi
            \StrCut{\sidecs@partbeforeat}{.}{\sidecs@node}{\sidecs@side}
            \sidecs@trimspacesleft{\sidecs@node}
            \sidecs@trimspacesright{\sidecs@node}
            \sidecs@trimspacesleft{\sidecs@side}
            \sidecs@trimspacesright{\sidecs@side}
            \ifx\sidecs@side\sidecs@expandtotop
                \def\sidecs@anchorone{north west}
                \def\sidecs@anchortwo{north east}
            \else\ifx\sidecs@side\sidecs@expandtobottom
                \def\sidecs@anchorone{south west}
                \def\sidecs@anchortwo{south east}
            \else\ifx\sidecs@side\sidecs@expandtoleft
                \def\sidecs@anchorone{north west}
                \def\sidecs@anchortwo{south west}
            \else\ifx\sidecs@side\sidecs@expandtoright
                \def\sidecs@anchorone{north east}
                \def\sidecs@anchortwo{south east}
            \else
                \errmessage{Unknown side}
            \fi\fi\fi\fi
            \pgfpointadd%
                {\pgfpointscale{\sidecs@posreversed}%
                               {\pgfpointanchor{\sidecs@node}{\sidecs@anchorone}}}%
                {\pgfpointscale{\sidecs@pos}%
                               {\pgfpointanchor{\sidecs@node}{\sidecs@anchortwo}}}
            \global\pgf@x=\pgf@x
            \global\pgf@y=\pgf@y
        \endgroup
    }
\makeatother

\begin{document}
    \begin{tikzpicture}
        \begin{scope}[x = 35mm, y = 3mm, every node/.style = draw]
            \node (x) at (0,  0) {XXXXXXXXXX};

            \node (a) at (1,  7) {AAAAAAAAAA};
            \node (b) at (1,  5) {BBBBBBBBBB};
            \node (c) at (1,  3) {CCCCCCCCCC};
            \node (d) at (1,  1) {DDDDDDDDDD};
            \node (e) at (1, -1) {EEEEEEEEEE};
            \node (f) at (1, -3) {FFFFFFFFFF};
            \node (g) at (1, -5) {GGGGGGGGGG};
            \node (h) at (1, -7) {HHHHHHHHHH};
        \end{scope}

        \begin{scope}[->,
                      tl/.style = {out = 90, in = 180},
                      bl/.style = {out = 270, in = 180}]
            \draw [tl] (side cs: x.top    @ 0.5) to (side cs: a.left @ 0.5);
            \draw [tl] (side cs: x.top    @ 0.6) to (side cs: b.left @ 0.5);
            \draw [tl] (side cs: x.top    @ 0.7) to (side cs: c.left @ 0.5);
            \draw [tl] (side cs: x.top    @ 0.8) to (side cs: d.left @ 0.5);
            \draw [bl] (side cs: x.bottom @ 0.8) to (side cs: e.left @ 0.5);
            \draw [bl] (side cs: x.bottom @ 0.7) to (side cs: f.left @ 0.5);
            \draw [bl] (side cs: x.bottom @ 0.6) to (side cs: g.left @ 0.5);
            \draw [bl] (side cs: x.bottom @ 0.5) to (side cs: h.left @ 0.5);
        \end{scope}
    \end{tikzpicture}
\end{document}

答案1

有几种方法可以解决这个问题。

  1. 因为你在下面,\fullexpandarg你可以简单地做

    \StrCut{\sidecs@expandedarg}{\string @}{\sidecs@partbeforeat}{\sidecs@pos}
    
  2. 如果不在\fullexpandarg,您可以使用字符串\expandafter

    \@xp\StrCut\@xp{\@xp\sidecs@expandedarg\@xp}\@xp{\string @}{\sidecs@partbeforeat}{\sidecs@pos}
    

    (我使用\@xp\expandafter如果你加载 则可用amsmath

  3. 或者,\lowercase诀窍是:

    \begingroup\lccode`?=`@ \lowercase{\endgroup
      \StrCut{\sidecs@expandedarg}{?}{\sidecs@partbeforeat}{\sidecs@pos}%
    }
    

    \lowercase它利用了不改变类别代码的事实。

对于以下行也类似\StrCount

\StrCount{\sidecs@expandedarg}{\string @}[\sidecs@numberofats]

或者

\@xp\StrCount\@xp{\@xp\sidecs@expandedarg\@xp}\@xp{\string @}[\sidecs@numberofats]

或者

\begingroup\lccode`?=`@ \lowercase{\endgroup
  \StrCount{\sidecs@expandedarg}{?}[\sidecs@numberofats]%
}

答案2

根据用户 egreg 的评论,一种可能的方法是使用\string@ 来表示“side cs”。这会在执行时将其转换为带有 catcode“other”的“@”,而这正是 XString 在本例中要查找的内容。

在代码开头或附近添加以下行:

\let\ea\expandafter

将第一\StrCount行替换为以下内容:

\ea\StrCount\ea{\ea\sidecs@expandedarg\ea}\ea{\string @}[\sidecs@numberofats]

\StrCut将条件语句中的第一行替换为以下内容:

\ea\StrCut\ea{\ea\sidecs@expandedarg\ea}\ea{\string @}{\sidecs@partbeforeat}{\sidecs@pos}

相关内容