解析单分支 SGF 文件的主分支(使用正则表达式?)

解析单分支 SGF 文件的主分支(使用正则表达式?)

我想要完成的是将 SGF 文件(文本文件)解析为 TikZ 图表,如下所示(理想情况下,我会指向 SGF 文件本身,而不是内联字符串):

\parsesgf{(;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])}

在底层,变成这样(我还添加了一些样板,这样我们就有一个最小的、完整的示例):

\documentclass{article}

\usepackage{tikz}

\newlength{\step}

\begin{document}
  \begin{tikzpicture}
    \setlength{\step}{\dimexpr 10cm / 18 \relax}

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

    \draw[draw = white, fill = black, line width = 0.1mm]
      (0 * 10cm / 18, 0 * 10cm / 18)
      circle [radius = 0.2575cm]
      node[color = white] {1};
    \draw[draw = black, fill = white, line width = 0.1mm]
      (1 * 10cm / 18, 0 * 10cm / 18)
      circle [radius = 0.2575cm]
      node[color = black] {2};
    \draw[draw = white, fill = black, line width = 0.1mm]
      (2 * 10cm / 18, 0 * 10cm / 18)
      circle [radius = 0.2575cm]
      node[color = white] {3};
  \end{tikzpicture}
\end{document}

我真的不知道最好的方法是什么。有没有这种东西的好包?我想正则表达式实际上可以做到这一点,有没有更好的方法?我会在尝试更多东西时更新这个问题。

这个答案有人帮我提供了一种 PCRE 完整解析 SGF 的方法(在 PHP 中),但我不知道在 TeX 中可以做到什么程度。无论如何,也许该配方的键值部分很有用:

/(?<key>[A-Z]+)\[(?<value>(?:\\\]|[^\]])*)\]/gy

我想要构建一个包,其中包含解析 SGF 的功能这里希望您可以在那里找到一些有关构建 Go 图的有用的宏。

SGF 是文本中树结构的表示,但是为了简单起见,首先,我想我只想解析只有一个分支的文件中的主分支。

(目前有一些 JS 中的 SGF 解析器可用——包括沙巴基— 也许它们是有用的参考,或者也许它们可以通过某种方式被 TeX 调用?)


在围棋游戏中,由于棋盘比国际象棋大得多,我们通常不使用坐标,而是使用可视化编辑器(或纸质 kifus)——其中最好的两个编辑器是哥班佐巴基— 为了保存游戏。最终,智能游戏格式 (SGF) 是标准(实际上它也支持其他游戏),并且它最终还是以文本格式保存文件。

以下是 SGF 文件的一个示例:

(;GM[1]FF[4]CA[UTF-8]AP[CGoban:3]ST[2]
RU[Japanese]SZ[19]KM[6.50]
DT[2023-12-25]
;B[pd]
;W[dd]
;B[dp]
;W[pp]
;AW[ji]AB[jj]PL[B]
;B[jq]CR[pp]LB[dd:A][jd:C][pd:B]TR[jj]SQ[ji]MA[dp])
  • ()表示分支,数据在[]
  • 之前的标签[]表示数据类型
  • ;是节点分隔符
  • BW分别是黑棋和白棋
  • ABAW添加或编辑石头
  • LB是坐标上方的标签,:AA标签
  • CR是一个圆形标签
  • TR是一个三角形标签
  • SQ是一个方形标签
  • MA是一个十字 ( X) 标签

整个语法可以定义为

Collection     = { GameTree }
GameTree       = "(" RootNode NodeSequence { Tail } ")"
Tail           = "(" NodeSequence { Tail } ")"
NodeSequence   = { Node }
RootNode       = Node
Node           = ";" { Property }
Property       = PropIdent PropValue { PropValue }
PropIdent      = UcLetter { UcLetter }
PropValue      = "[" Value "]"
UcLetter       = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" |
                 "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" |
                 "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"

根据@StevenB.Segletes 的回答进行编辑

关注@StevenB.Segletes'listofitems包,以及非常有用的答案,尤其是这个,我已经能够创建以下 MWE:

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{shapes.geometric}
\usepackage{listofitems}

%-----------------------------------------------------------
% Drawing Stones

% 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 }{
    \boardSize + 1 - ~\int_from_alph:e { \use_ii:nn #1 }
  }
\ExplSyntaxOff

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

\newcommand{\drawStoneFromSgfCoords}[2]{%
  \setCoords{#2}

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

\newcommand{\drawMoveFromSgfCoords}[2]{
  \drawStoneFromSgfCoords{#1}{#2}
  \textLabel{#1}{#2}{\themoveCounter}

  \stepMoveCounter
}

\newcounter{moveCounter}
\setcounter{moveCounter}{1}

\newcommand{\stepMoveCounter}{
  \stepcounter{moveCounter}
}

%-----------------------------------------------------------
% Labels

\newcommand{\textLabel}[3]{
  \setCoords{#2}

  \draw (\x * \step, \y * \step) 
    node[color = \UseName{not#1}] {#3};
}

\newcommand{\crossLabel}[2]{
  \setCoords{#2}

  \draw (\x * \step, \y * \step) 
    node[color = \UseName{not#1}] {X};
}

\newcommand{\triangleLabel}[2]{
  \setCoords{#2}

  \draw (\x * \step, \y * \step) 
    node[
      isosceles triangle,
      draw                          = #1,
      line width                    = 0.5mm,
      fill                          = \UseName{not#1},
      minimum height                = \step * 10,
      minimum width                 = \step * 10,
      rotate                        = 90,
      isosceles triangle apex angle = 60,
      inner sep                     = 0pt,
    ] {};
}

\newcommand{\squareLabel}[2]{
  \setCoords{#2}

  \draw (\x * \step, \y * \step) 
    node[
      draw         = #1,
      line width   = 0.5mm,
      fill         = \UseName{not#1},
      minimum size = \step * 10,
      inner sep    = 0pt,
    ] {};
}

\newcommand{\circleLabel}[2]{
  \setCoords{#2}

  \draw[
    draw         = #1,
    line width   = 0.5mm,
    fill         = \UseName{not#1},
    inner sep    = 0pt,
  ] (\x * \step, \y * \step) 
    circle[radius = \step / 4];
}

%-----------------------------------------------------------
% SGF Parser

% 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}
\newcommand\thecolorofMA{white}
\newcommand\thecolorofCR{white}
\newcommand\thecolorofTR{white}
\newcommand\thecolorofSQ{white}
\newcommand\thecolorofLB{white}

\long\def\Keytypeof#1{\csname thekeytypeof#1\endcsname}

\newcommand\thekeytypeofB{M}  % black move
\newcommand\thekeytypeofAB{A} % added (edited) black stone
\newcommand\thekeytypeofW{M}  % white move
\newcommand\thekeytypeofAW{A} % added (edited) white stone
\newcommand\thekeytypeofMA{K} % cross (mark) label
\newcommand\thekeytypeofCR{C} % circle label
\newcommand\thekeytypeofTR{T} % triangle label
\newcommand\thekeytypeofSQ{S} % square label
\newcommand\thekeytypeofLB{L} % text label

\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

      \edef\tmp{{\csname thecolorof\KeyName\endcsname}{\KeyValue}}

      \if\Keytypeof\KeyName M
        \expandafter\drawMoveFromSgfCoords\tmp
      \fi
      \if\Keytypeof\KeyName A
        \expandafter\drawStoneFromSgfCoords\tmp
      \fi
      \if\Keytypeof\KeyName K
        \expandafter\crossLabel\tmp
      \fi
      \if\Keytypeof\KeyName C
        \expandafter\circleLabel\tmp
      \fi
      \if\Keytypeof\KeyName T
        \expandafter\triangleLabel\tmp
      \fi
      \if\Keytypeof\KeyName S
        \expandafter\squareLabel\tmp
      \fi
      \if\Keytypeof\KeyName L
        \expandafter\textLabel\tmp % this macro has 1 extra argument compared to the other ones (the text itself)
      \fi
    }
  }%
}

%-----------------------------------------------------------
% SGFs

\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]
)}
\def\sgfD{( % Basically `sgfE` with no labels (`LB`)
  ;GM[1]FF[4]CA[UTF-8]AP[CGoban:3]ST[2]RU[Japanese]SZ[19]KM[6.50]DT[2023-12-25]
  ;B[pd]
  ;W[dd]
  ;B[dp]
  ;W[pp]
  ;AW[ji]AB[jj]PL[B]
  ;B[jq]CR[pp]TR[jj]SQ[ji]MA[dp]
)}
\def\sgfE{(
  ;GM[1]FF[4]CA[UTF-8]AP[CGoban:3]ST[2]RU[Japanese]SZ[19]KM[6.50]DT[2023-12-25]
  ;B[pd]
  ;W[dd]
  ;B[dp]
  ;W[pp]
  ;AW[ji]AB[jj]PL[B]
  ;B[jq]CR[pp]LB[dd:A][jd:C][pd:B]TR[jj]SQ[ji]MA[dp]
  % The `LB` data is apparently the only weird one.
  % Besides having the `LB[<coords>:<label>]` format,
  % If many of them are on the same node, there will only be one `LB` for all of them.
)}

%-----------------------------------------------------------
% Setup

\pgfmathsetmacro{\boardDimension}{10}
\pgfmathsetmacro{\boardSize}{19}
\pgfmathsetmacro{\step}{\boardDimension / (\boardSize - 1)}

%-----------------------------------------------------------

\begin{document}
  \begin{tikzpicture}
    \draw[step=\step] (0, 0) grid (10, 10);

    \parseSgf{\sgfD}
    
    \textLabel{white}{ab}{A}
    \triangleLabel{black}{ac}
    \squareLabel{black}{ad}
    \circleLabel{black}{ae}
    \crossLabel{white}{af}
  \end{tikzpicture}
\end{document}

本质上,缺少的是:

  • 在必需的键中,标签键 ( LB) 是唯一格式奇怪的键。在上一个答案中,@StevenB.Segletes 确实设法涵盖了格式LB[<coords>:<label>],但是,当多个标签位于同一节点上时,情况会变得更加奇怪,因为它们会组合在一起,而没有键,例如LB[dd:A][jd:C][pd:B](这实际上显然发生在每个标签键上,请参见下面的示例)。
  • 由于标签通常是在放置棋子后才标记的,所以我不知道在解析时如何找到合适的颜色。解析时,由于解析器不知道标签下面是黑色还是白色棋子,所以它不知道要用哪种颜色绘制。也许应该有一个坐标与颜色的列表,然后解析器应该在绘制标签之前进行检查,我不知道。

实际上,显示的分组LB发生在每个标签和编辑或添加的石头上:

(
  ;GM[1]FF[4]CA[UTF-8]AP[CGoban:3]ST[2]RU[Japanese]SZ[19]KM[6.50]DT[2023-12-25]
  ;B[pd]
  ;W[dd]
  ;B[dp]
  ;W[pp]
  ;AW[ji][jn][kn][ln]AB[jj][jm][km]TR[jp][kp][lp]PL[B]
  ;B[jq]CR[pp]LB[dd:A][jd:C][pd:B]TR[jj]SQ[ji]MA[dp]
)

答案1

我认为这解决了 OP 所要求的所有问题。

与 OP 之前的问题相比,这个答案的挑战在于,OP 对 SGF 文件的描述中引入了一种新的有效语法。之前,暗示键总是<Keyname>[<Value>]采取了或的形式<Keyname>[<Value>:<Label>]。事实上,新引入的语法允许<Keyname>[<Value1>][<Value2>][<Value3>]...,这导致我之前的解析逻辑无效。这种允许的语法变化可能出现在任何有效的密钥名称。

解决方案是不再忽略列表解析中的空项,而是允许它们,以便<Key1>[<Value1>][<Value2>]可以有效地解析和理解为的等价物<Key1>[<Value1>]<implied-Key1>[<Value2>]。当然,允许空白列表项会弄乱必须解决的解析的其他方面。

最后,我要求进行 5 级解析:

  1. 任何被()我称为分支的东西

  2. 在每个分支中,任何被;我称为“组”的东西

  3. 在每个组中,任何由]我称为键分隔的内容(尽管它将包括键名称及其关联值)

  4. 在每个键中,[分隔符将键名与键值分隔开

  5. 在每个键值中,:可以使用来分隔实际值与关联标签。

通过这种方法,我可以有效地解析OP的测试SGF:

(
  ;GM[1]FF[4]CA[UTF-8]AP[CGoban:3]ST[2]RU[Japanese]SZ[19]KM[6.50]DT[2023-12-25]
  ;B[pd]
  ;W[dd]
  ;B[dp]
  ;W[pp]
  ;AW[ji][jn][kn][ln]AB[jj][jm][km]TR[jp][kp][lp]PL[B]
  ;B[jq]CR[pp]LB[dd:A][jd:C][pd:B]TR[jj]SQ[ji]MA[dp]
)

以下是 MWE:

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{shapes.geometric}
\usepackage{listofitems}

%-----------------------------------------------------------
% Drawing Stones

% 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 }{
    \boardSize + 1 - ~\int_from_alph:e { \use_ii:nn #1 }
  }
\ExplSyntaxOff

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

\newcommand{\drawStoneFromSgfCoords}[2]{%
  \setCoords{#2}

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

\newcommand{\drawMoveFromSgfCoords}[2]{
  \expandafter\xdef\csname thecolorat#2\endcsname{#1}% SAVE COLOR AT #2
  \drawStoneFromSgfCoords{#1}{#2}
  \textLabel{#1}{#2}{\themoveCounter}

  \stepMoveCounter
}

\newcounter{moveCounter}
\setcounter{moveCounter}{1}

\newcommand{\stepMoveCounter}{
  \stepcounter{moveCounter}
}

%-----------------------------------------------------------
% Labels

\newcommand\StoneColor[1]{% COMPLEMENTARY COLOR OF REFERENCED STONE
  \ifcsname thecolorat#1\endcsname
    \csname thecolorat#1\endcsname
  \else
    white% COMPLEMENT OF DEFAULT LABEL COLOR WHEN NOT ATOP EXISTING STONE
  \fi
}

\newcommand{\textLabel}[3]{%
  \setCoords{#2}%
  \draw (\x * \step, \y * \step) 
  node[
    color = -\StoneColor{#2},
    fill = \StoneColor{#2},
    inner sep=1pt
  ] {#3};
}

\newcommand{\crossLabel}[2]{
  \setCoords{#2}
  \draw (\x * \step, \y * \step) 
  node[
    color = -\StoneColor{#2},
    fill = \StoneColor{#2},
    inner sep=1pt
  ] {$\times$};
}

\newcommand{\triangleLabel}[2]{
  \setCoords{#2}
  \draw (\x * \step, \y * \step) 
    node[
      isosceles triangle,
      draw                          = #1,
      line width                    = 0.5mm,
      color                         = -\StoneColor{#2},
      fill                          = -\StoneColor{#2},
      minimum height                = \step * 10,
      minimum width                 = \step * 10,
      rotate                        = 90,
      isosceles triangle apex angle = 60,
      inner sep                     = 0pt,
    ] {};
}

\newcommand{\squareLabel}[2]{
  \setCoords{#2}

  \draw (\x * \step, \y * \step) 
    node[
      draw         = #1,
      line width   = 0.5mm,
      color        = -\StoneColor{#2},
      fill         = -\StoneColor{#2},
      minimum size = \step * 10,
      inner sep    = 0pt,
    ] {};
}

\newcommand{\circleLabel}[2]{
  \setCoords{#2}

  \draw[
    draw         = #1,
    line width   = 0.5mm,
    color        = -\StoneColor{#2},
    fill         = -\StoneColor{#2},
    inner sep    = 0pt,
  ] (\x * \step, \y * \step) 
    circle[radius = \step / 4];
}

%-----------------------------------------------------------
% SGF Parser

% 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}
\newcommand\thecolorofMA{white}
\newcommand\thecolorofCR{white}
\newcommand\thecolorofTR{white}
\newcommand\thecolorofSQ{white}
\newcommand\thecolorofLB{white}

\long\def\Keytypeof#1{\csname thekeytypeof#1\endcsname}

\newcommand\thekeytypeofB{M}  % black move
\newcommand\thekeytypeofAB{A} % added (edited) black stone
\newcommand\thekeytypeofW{M}  % white move
\newcommand\thekeytypeofAW{A} % added (edited) white stone
\newcommand\thekeytypeofMA{K} % cross (mark) label
\newcommand\thekeytypeofCR{C} % circle label
\newcommand\thekeytypeofTR{T} % triangle label
\newcommand\thekeytypeofSQ{S} % square label
\newcommand\thekeytypeofLB{L} % text label

\newcommand{\parseSgf}[1]{%
  \setsepchar{(||)/;/]/[/:}%
  \readlist*\Z{#1}%

  \foreachitem \Branch \in \Z[]{%
  \foreachitem \Group \in \Z[\Branchcnt]{%
    \foreachitem \Key \in \Z[\Branchcnt, \Groupcnt]{%
     \if\relax\Key\relax% IF BLANK KEY & VALUE, SKIP
     \else
      \itemtomacro\Z[\Branchcnt, \Groupcnt, \Keycnt, 1]\KeyName
      \if\relax\KeyName\relax% IF VALUE, BUT NO KEYNAME, USE RECENT KEYNAME
        \let\KeyName\MostRecentKeyname
      \else
        \xdef\MostRecentKeyname{\KeyName}%
      \fi
      \itemtomacro\Z[\Branchcnt, \Groupcnt, \Keycnt, 2, 1]\KeyValue

      \edef\tmp{{\csname thecolorof\KeyName\endcsname}{\KeyValue}}%

      \if\Keytypeof\KeyName M
        \expandafter\drawMoveFromSgfCoords\tmp
      \fi
      \if\Keytypeof\KeyName A
        \expandafter\drawStoneFromSgfCoords\tmp
      \fi
      \if\Keytypeof\KeyName K
        \expandafter\crossLabel\tmp
      \fi
      \if\Keytypeof\KeyName C
        \expandafter\circleLabel\tmp
      \fi
      \if\Keytypeof\KeyName T
        \expandafter\triangleLabel\tmp
      \fi
      \if\Keytypeof\KeyName S
        \expandafter\squareLabel\tmp
      \fi
      \if\Keytypeof\KeyName L
        \expandafter\textLabel\tmp{\Z[\Branchcnt, \Groupcnt, \Keycnt, 2, 2]} 
      \fi
     \fi
    }
  }%
  }%
}

%-----------------------------------------------------------
% SGFs

\def\sgfE{(
  ;GM[1]FF[4]CA[UTF-8]AP[CGoban:3]ST[2]RU[Japanese]SZ[19]KM[6.50]DT[2023-12-25]
  ;B[pd]
  ;W[dd]
  ;B[dp]
  ;W[pp]
  ;AW[ji][jn][kn][ln]AB[jj][jm][km]TR[jp][kp][lp]PL[B]
  ;B[jq]CR[pp]LB[dd:A][jd:C][pd:B]TR[jj]SQ[ji]MA[dp]
)}

%-----------------------------------------------------------
% Setup

\pgfmathsetmacro{\boardDimension}{10}
\pgfmathsetmacro{\boardSize}{19}
\pgfmathsetmacro{\step}{\boardDimension / (\boardSize - 1)}

%-----------------------------------------------------------

\begin{document}
  \begin{tikzpicture}
    \draw[step=\step] (0, 0) grid (10, 10);

    \parseSgf{\sgfE}
    
    \textLabel{white}{ab}{A}
    \triangleLabel{black}{ac}
    \squareLabel{black}{ad}
    \circleLabel{black}{ae}
    \crossLabel{white}{af}
  \end{tikzpicture}
\end{document}

在此处输入图片描述

需要克服几个问题:

  1. 要将 放置\textLabel在现有棋子上方,我必须记住底层棋子的颜色,这样我才能将标签设为互补色。我将棋子的颜色(在创建时)保存在 中\thecolorat<coordinate>。然后,当需要文本标签时,我使用\StoneColor{<coordinate>}来计算互补色……如果之前没有在该位置创建过棋子,我会使用默认颜色,假设底层颜色为white(这样互补标签就是black)。

  2. 当覆盖现有(默认)标签时,我必须添加fill石头颜色,并inner sep用 来消除先前的标签。

  3. 对于几何标签(正方形、圆形、三角形),需要同时color指定fill补色,以实现全线统一。

  4. 如前所述,需要 5 级解析,并通过获得\setsepchar{(||)/;/]/[/:}

  5. 如果代表“键”的项目完全为空(没有键名或值),例如(;...解析时的情况,则解析将跳到已解析的“键”列表中的下一个项目。

  6. 在迭代所有关键操作时,如果存在键名,则通过 保存它的全局副本\xdef\MostRecentKeyname{\KeyName}

  7. 如果键名为空,但关联的键值存在,则使用最新的非空键名,通过\let\KeyName\MostRecentKeyname。这涵盖以下情况默示键名,例如AB[jj][jm][km],其中在三个指定坐标处各添加一颗黑棋。

  8. 对于文本标签类型的键,例如LB[dd:A],需要第五级解析才能将坐标dd与标签分离A

  9. 对于交叉标签,我使用$\times$而不是X

相关内容