如何使用 listofitems 包解析 `key[value]` 字符串

如何使用 listofitems 包解析 `key[value]` 字符串

通过@StevenB.Segletes 之前的两个回答(12),我 99% 能够解析SGF 文件/字符串B分别W表示黑棋和白棋的移动;AB分别AW表示添加或编辑的黑棋和白棋。(我试图处理的是 SGF 的单分支版本,而不是全部。)

在这些答案中,@StevenB.Segletes 使用这个来解析事物:

\setsepchar{;||(||)/[||]}

;将 SGF 字符串拆分为节点,然后其余部分将字符串拆分为key[value]数据。但是,不幸的是,我花了很长时间才意识到这一点,AB并且AW通常写在同一个节点内。所以我认为,最好直接将字符串解析为key[value]。我已经尝试过这个(和其他一些变体),但没有用:

\setsepchar{[||]}

按照以前的方式,这个 SGF 字符串:

(;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05];B[as];W[bs];B[cs];PL[W]AB[dq]AW[eq])

基本上是这样的:

(
  ;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05]
  ;B[as]
  ;W[bs]
  ;B[cs]
  ;PL[W]AB[dq]AW[eq]
)

并且我认为会被解析成这样(在解析第一个之前key[value]):

  • (
  • GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05]
  • B[as]
  • W[bs]
  • B[cs]
  • PL[W]AB[dq]AW[eq]
  • )

但是,我认为下面的代码无法捕获AB或。我认为如果我们直接解析,那么就可以做到这一点。字符串将如下所示:AWkey[value]

  • (;GM[1]
  • FF[4]
  • ...
  • ;B[as]
  • ;W[bs]
  • ...
  • ;PL[W]
  • AB[dq]
  • AW[eq]

事实上,我们随后必须检查;B;W但我认为,这差不多就是它了。或者也许我们应该;直接去掉 ?


这是代码的主要部分:

\long\def\Firstof#1#2\endFirstof{#1}

\newcommand\thecolorofB{black}
\newcommand\thecolorofAB{black}
\newcommand\thecolorofW{white}
\newcommand\thecolorofAW{white}

\ignoreemptyitems
\newcommand{\parseSgf}[1]{
  % Maybe first strip the SGF string from `(`, `)`, and `;`?

  \setsepchar{;||(||)/[||]} % This is where things should change the most, I think
  \readlist*\Z{#1}

  \foreachitem \i \in \Z[]{
    \itemtomacro\Z[\icnt, 1]\Color
    \itemtomacro\Z[\icnt, 2]\sgfCoords
    \edef\tmp{{\csname thecolorof\Color\endcsname}{\sgfCoords}}

    \ifthenelse{
      \equal{\Color}{B}  \OR % maybe use some sort of `\contains` in TeX to simplify this if?
      \equal{\Color}{W}  \OR
      \equal{\Color}{AB} \OR
      \equal{\Color}{AW}
    }{
      \expandafter\drawStoneFromSgfCoords\tmp
    }{}
  }
}

下面是我使用的代码,略作修改先前的答案(重要的部分是\parseSgf宏):

\documentclass{article}

\usepackage{tikz}
\usepackage{listofitems}
\usepackage{ifthen}

% From [this answer by @DavidCarlisle](https://tex.stackexchange.com/a/708876/64441).
\newcommand\notwhite{black}
\newcommand\notblack{white}

% From [this answer by @DavidCarlisle](https://tex.stackexchange.com/a/708893/64441).
\ExplSyntaxOn
  \cs_generate_variant:Nn \int_from_alph:n {e}

  \NewExpandableDocumentCommand{\stringToCoordX}{ m }{
    \int_from_alph:e { \use_i:nn #1 }
  }
  \NewExpandableDocumentCommand{\stringToCoordY}{ m }{
    \int_from_alph:e { \use_ii:nn #1 }
  }
\ExplSyntaxOff

\newcommand{\drawStoneFromSgfCoords}[2]{
  \pgfmathsetmacro{\x}{\stringToCoordX{#2} - 1}
  \pgfmathsetmacro{\y}{\stringToCoordY{#2} - 1}

  \draw[draw = \UseName{not#1}, fill = #1, line width = 0.1mm]
    (\x * 10cm / 18, \y * 10cm / 18)
    circle [radius = 0.2575cm];
}

\long\def\Firstof#1#2\endFirstof{#1}

\newcommand\thecolorofB{black}
\newcommand\thecolorofAB{black}
\newcommand\thecolorofW{white}
\newcommand\thecolorofAW{white}

\ignoreemptyitems
\newcommand{\parseSgf}[1]{
  \setsepchar{;||(||)/[||]} % This is where things should change, I think
  \readlist*\Z{#1}

  \foreachitem \i \in \Z[]{
    \itemtomacro\Z[\icnt, 1]\Color
    \itemtomacro\Z[\icnt, 2]\sgfCoords
    \edef\tmp{{\csname thecolorof\Color\endcsname}{\sgfCoords}}

    \ifthenelse{
      \equal{\Color}{B}  \OR
      \equal{\Color}{W}  \OR
      \equal{\Color}{AB} \OR
      \equal{\Color}{AW}
    }{
      \expandafter\drawStoneFromSgfCoords\tmp
    }{}
  }
}

\def\sgfA{;B[ab];W[cd]}
\def\sgfB{(;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05];B[as];W[bs];B[cs])}
\def\sgfC{(;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05];B[as];W[bs];B[cs];PL[W]AB[dq]AW[eq])}

\begin{document}
  \begin{tikzpicture}
    \pgfmathsetmacro{\step}{10 / 18}

    \draw[step=\step] (0, 0) grid (10, 10);

    % \drawStoneFromSgfCoords{black}{ab}
    % \drawStoneFromSgfCoords{white}{cd}
    
    \parseSgf{\sgfC}
  \end{tikzpicture}
\end{document}

答案1

首先,相对于我之前的答案,我必须提高解析 SGF 输入的能力。下面是一个简短的 MWE,仅显示该解析。我用来listofitems执行 3 深度(嵌套)解析:最高级别解析;(),第二级解析],第三级解析[

列表中的空白项被设置为被忽略,因此(;实际上对解析列表没有任何贡献(而不是在(和之间贡献一个空白条目;)。

首先,在输出中,\KeyName[\KeyValue]嵌套循环的每个解析\foreachitem都以空格分隔符显示。如果检测到颜色键,则\rightarrow在条目前插入一个。我们看到,所有类型为BWAB和的键AW都被成功检测为颜色键,而所有其他键都被排除,这是本练习的目标。

为了查看解析的详细信息,MWE 的最后三行输出被清楚地提供。MWE 输入中给出的 SGF 定义如下

\def\sgfC{(
  ;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05]
  ;B[as]
  ;W[bs]
  ;B[cs]
  ;PL[W]AB[dq]AW[eq]
)}

被解析为名为 的列表\Z。我们看到 的第 5 个元素\Z,记为Z[5],是,对应于位于字符、或PL[W]AB[dq]AW[eq]之间的第 5 个非空文本块。;()

第二级解析以]作为分隔符,应用于 的每个元素<i>\Z[<i>]元素\Z[5,2]是 的子列表\Z[5],显示为AB[dq。现在,我们能够分离与其关联值链接的每个键。最后,第三级解析应用于次级列表的每个项目,这样\Z[5,2,1]给出为AB\Z[5,2,2]给出为dq。因此,我们分离了每个键并可以独立访问与其关联的值。

与本系列问题中的先前答案一样,我们引入了诸如这样的宏,\newcommand\thecolorofB{black}以便可以将诸如这样的 SGF 键B转换为实际的颜色black。但我们现在还引入了诸如这样的宏,\newcommand\thekeytypeofAB{C}以指示该键AB是与颜色属性相关联的键。

\documentclass{article}
\usepackage{listofitems}
\long\def\Firstof#1#2\endFirstof{#1}
\newcommand\thecolorofB{black}
\newcommand\thecolorofAB{black}
\newcommand\thecolorofW{white}
\newcommand\thecolorofAW{white}
\long\def\Keytypeof#1{\csname thekeytypeof#1\endcsname}
\newcommand\thekeytypeofB{C}% C DENOTING A "COLOR" KEY
\newcommand\thekeytypeofAB{C}
\newcommand\thekeytypeofW{C}
\newcommand\thekeytypeofAW{C}
\ignoreemptyitems
\newcommand{\parseSgf}[1]{%
  \setsepchar{;||(||)/]/[}%
  \readlist*\Z{#1}%
  \foreachitem \i \in \Z[]{%
    \foreachitem \z \in \Z[\icnt]{%
    \itemtomacro\Z[\icnt, \zcnt, 1]\KeyName
    \itemtomacro\Z[\icnt, \zcnt, 2]\KeyValue
    \if\Keytypeof\KeyName C
      \edef\tmp{{\csname thecolorof\KeyName\endcsname}{\KeyValue}}%
      \expandafter\drawStoneFromSgfCoords\tmp
    \fi
    \KeyName[\KeyValue]
  }}%
}
\newcommand{\drawStoneFromSgfCoords}[2]{\unskip$\rightarrow$}
\def\sgfC{(
  ;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05]
  ;B[as]
  ;W[bs]
  ;B[cs]
  ;PL[W]AB[dq]AW[eq]
)}
\begin{document}
\parseSgf{\sgfC}

\bigskip\textbackslash Z[5] is \Z[5]

\bigskip\textbackslash Z[5,2] is \Z[5,2]

\bigskip\textbackslash Z[5,2,1] and \textbackslash Z[5,2,2] 
  are \Z[5,2,1] and \Z[5,2,2]
\end{document}

在此处输入图片描述

现在,我们准备将上面显示的解析方法应用于\drawStoneFromSgfCoordsOP 提供的实际宏。完成后,不仅会绘制BW键中的棋子,还会绘制ABAW命令中的棋子——实际上,任何颜色键都会引发绘制操作。

\documentclass{article}

\usepackage{tikz}

% From [this answer by @DavidCarlisle](https://tex.stackexchange.com/a/708876/64441).
\newcommand\notwhite{black}
\newcommand\notblack{white}

% From [this answer by @DavidCarlisle](https://tex.stackexchange.com/a/708893/64441).
\ExplSyntaxOn
  \cs_generate_variant:Nn \int_from_alph:n {e}

  \NewExpandableDocumentCommand{\stringToCoordX}{ m }{
    \int_from_alph:e { \use_i:nn #1 }
  }
  \NewExpandableDocumentCommand{\stringToCoordY}{ m }{
    \int_from_alph:e { \use_ii:nn #1 }
  }
\ExplSyntaxOff

\newcommand{\drawStoneFromSgfCoords}[2]{%
  \pgfmathsetmacro{\x}{\stringToCoordX{#2} - 1}
  \pgfmathsetmacro{\y}{\stringToCoordY{#2} - 1}
%
  \draw[draw = \UseName{not#1}, fill = #1, line width = 0.1mm]
    (\x * 10cm / 18, \y * 10cm / 18)
    circle [radius = 0.2575cm];
}

\usepackage{listofitems}
% From [this answer by @StevenB.Segletes](https://tex.stackexchange.com/a/709014/64441).
\long\def\Firstof#1#2\endFirstof{#1}
\newcommand\thecolorofB{black}
\newcommand\thecolorofAB{black}
\newcommand\thecolorofW{white}
\newcommand\thecolorofAW{white}
\long\def\Keytypeof#1{\csname thekeytypeof#1\endcsname}
\newcommand\thekeytypeofB{C}% C DENOTING A "COLOR" KEY
\newcommand\thekeytypeofAB{C}
\newcommand\thekeytypeofW{C}
\newcommand\thekeytypeofAW{C}
\ignoreemptyitems
\newcommand{\parseSgf}[1]{%
  \setsepchar{;||(||)/]/[}%
  \readlist*\Z{#1}%
  \foreachitem \i \in \Z[]{%
    \foreachitem \z \in \Z[\icnt]{%
    \itemtomacro\Z[\icnt, \zcnt, 1]\KeyName
    \itemtomacro\Z[\icnt, \zcnt, 2]\KeyValue
    \if\Keytypeof\KeyName C%
      \edef\tmp{{\csname thecolorof\KeyName\endcsname}{\KeyValue}}%
      \expandafter\drawStoneFromSgfCoords\tmp
    \fi
  }}%
}

\def\sgfA{;B[ab];W[cd]}
\def\sgfB{(
  ;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05]
  ;B[as]
  ;W[bs]
  ;B[cs]
)}
\def\sgfC{(
  ;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05]
  ;B[as]
  ;W[bs]
  ;B[cs]
  ;PL[W]AB[dq]AW[eq]
)}

\begin{document}
  \begin{tikzpicture}
    \pgfmathsetmacro{\step}{10 / 18}
    \draw[step=\step] (0, 0) grid (10, 10);
    \parseSgf{\sgfC}
  \end{tikzpicture}
\end{document}

在此处输入图片描述

奖金

针对如何处理拆分键(按 拆分:)的评论,以下是如何...基于 添加另一层解析。我已经定义了检测定义的拆分键(必须包含分隔符)以及可选拆分键(可能包含或不包含分隔符):的逻辑。::

\documentclass{article}
\usepackage{listofitems}
\long\def\Firstof#1#2\endFirstof{#1}
\newcommand\thecolorofB{black}
\newcommand\thecolorofAB{black}
\newcommand\thecolorofW{white}
\newcommand\thecolorofAW{white}
\long\def\Keytypeof#1{\csname thekeytypeof#1\endcsname}
\newcommand\thekeytypeofB{C}% C DENOTING A "COLOR" KEY
\newcommand\thekeytypeofAB{C}
\newcommand\thekeytypeofW{C}
\newcommand\thekeytypeofAW{C}
\newcommand\thekeytypeofAP{S}% DENOTING SPLIT KEY VALUE
\ignoreemptyitems
\newcommand{\parseSgf}[1]{%
  \setsepchar{;||(||)/]/[/:}%
  \readlist*\Z{#1}%
  \foreachitem \i \in \Z[]{%
    \foreachitem \z \in \Z[\icnt]{%
    \itemtomacro\Z[\icnt, \zcnt, 1]\KeyName
    \itemtomacro\Z[\icnt, \zcnt, 2]\KeyValue
    \if\Keytypeof\KeyName C
      \edef\tmp{{\csname thecolorof\KeyName\endcsname}{\KeyValue}}%
      \expandafter\drawStoneFromSgfCoords\tmp
    \fi
    \if \Keytypeof\KeyName S
      DEFINED SPLIT KEY ``\Z[\icnt, \zcnt, 2, 1]'' and 
                ``\Z[\icnt, \zcnt, 2, 2]''
    \else
    \ifnum\listlen\Z[\icnt, \zcnt, 2]=2
      OPTIONAL SPLIT KEY ``\Z[\icnt, \zcnt, 2, 1]'' and 
                ``\Z[\icnt, \zcnt, 2, 2]''
    \fi\fi
    \KeyName[\KeyValue]
  }}%
}
\newcommand{\drawStoneFromSgfCoords}[2]{\unskip$\rightarrow$}
\def\sgfC{(
  ;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05]
  ;B[as]
  ;W[bs]
  ;B[cs]
  ;PL[W]AB[dq]AW[eq]LB[bs:labelname]
)}
\begin{document}
\parseSgf{\sgfC}
\end{document}

在此处输入图片描述

相关内容