AsciiDocの表(Table)のセル属性の指定方法の仕様＆Asciidoctorの内部実装について調べてみた

[AsciiDocの表(Table)のcols属性の仕様＆Asciidoctorの内部実装について調べてみた](https://qiita.com/kob58im/items/2f8a9a5a60f445b083b0) のセル版。

参考サイト1 [Asciidoctor User Manual #cell](https://asciidoctor.org/docs/user-manual/#cell) に割と詳しく書いてありそう。

# セル結合の記述例

セル（`|`で始まる）の直<b>前</b>で指定する。下記の例でいうと、`.2+^.^` や `3+^` の部分。

```:AsciiDoc

[options="header"]
|===
      |Date          |Duration | Location | Plan
.2+^.^|2021.01.04    |08:00    | XXX      | game
                     |09:00    | YYY      | coding
      |2021.01.05    |08:00    | XXX      | no plan
      |2021.01.06 3+^| work
|===
```

出力は下記のようになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/e8715560-2ba6-7036-a1e1-078a4db0d0aa.png)

# 内部実装を追う

parser.rb の `self.parse_table`メソッドから呼ばれている `parse_cellspec`メソッド

```ruby:parser.rb（抜粋＆行番号補足）

# Internal: Parse the cell specs for the current cell.
#
# The cell specs dictate the cell's alignments, styles or filters,
# colspan, rowspan and/or repeating content.
#
# The default spec when pos == :end is {} since we already know we're at a
# delimiter. When pos == :start, we *may* be at a delimiter, nil indicates
# we're not.
#
# returns the Hash of attributes that indicate how to layout
# and style this cell in the table.
def self.parse_cellspec(line, pos = :end, delimiter = nil)             #  1
  m, rest = nil, ''                                                    #  2
                                                                       #  3
  if pos == :start                                                     #  4
    if line.include? delimiter                                         #  5
      spec_part, delimiter, rest = line.partition delimiter            #  6
      if (m = CellSpecStartRx.match spec_part)                         #  7
        return [{}, rest] if m[0].empty?                               #  8
      else                                                             #  9
        return [nil, line]                                             # 10
      end                                                              # 11
    else                                                               # 12
      return [nil, line]                                               # 13
    end                                                                # 14
  else # pos == :end                                                   # 15
    if (m = CellSpecEndRx.match line)                                  # 16
      # NOTE return the line stripped of trailing whitespace if no cellspec is found in this case # 17
      return [{}, line.rstrip] if m[0].lstrip.empty?                   # 18
      rest = m.pre_match                                               # 19
    else                                                               # 20
      return [{}, line]                                                # 21
    end                                                                # 22
  end                                                                  # 23
                                                                       # 24
  spec = {}                                                            # 25
  if m[1]                                                              # 26
    colspec, rowspec = m[1].split '.'                                  # 27
    colspec = colspec.nil_or_empty? ? 1 : colspec.to_i                 # 28
    rowspec = rowspec.nil_or_empty? ? 1 : rowspec.to_i                 # 29
    if m[2] == '+'                                                     # 30
      spec['colspan'] = colspec unless colspec == 1                    # 31
      spec['rowspan'] = rowspec unless rowspec == 1                    # 32
    elsif m[2] == '*'                                                  # 33
      spec['repeatcol'] = colspec unless colspec == 1                  # 34
    end                                                                # 35
  end                                                                  # 36
                                                                       # 37
  if m[3]                                                              # 38
    colspec, rowspec = m[3].split '.'                                  # 39
    if !colspec.nil_or_empty? && TableCellHorzAlignments.key?(colspec) # 40
      spec['halign'] = TableCellHorzAlignments[colspec]                # 41
    end                                                                # 42
    if !rowspec.nil_or_empty? && TableCellVertAlignments.key?(rowspec) # 43
      spec['valign'] = TableCellVertAlignments[rowspec]                # 44
    end                                                                # 45
  end                                                                  # 46
                                                                       # 47
  if m[4] && TableCellStyles.key?(m[4])                                # 48
    spec['style'] = TableCellStyles[m[4]]                              # 49
  end                                                                  # 50
                                                                       # 51
  [spec, rest]                                                         # 52
end                                                                    # 53
```

7行目および16行目でマッチング処理をしている。使用している正規表現は下記。

```ruby:rx.rb（抜粋＆コメント一部省略＆補足）

# Examples
#   2.3+<.>m
#                            m[1]                         m[2]    m[3]                                    m[4]
#                            <---------------------------><---->  <-------------------------------------> <----->
CellSpecStartRx = /^[ \t]*(?:(\d+(?:\.\d*)?|(?:\d*\.)?\d+)([*+]))?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?([a-z])?$/
CellSpecEndRx   =  /[ \t]+(?:(\d+(?:\.\d*)?|(?:\d*\.)?\d+)([*+]))?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?([a-z])?$/
```

`CellSpecStartRx`は行頭用、`CellSpecEndRx`はそれ以外用っぽい。

- m[1]m[2]：`整数` `.` `整数` `+` の場合・・・ 
結合する列数（横方向）と結合する行数（縦方向）。<br>`.`以降を省略可。もしくは、`.`の前を省略することもできる。
- m[1]m[2]：`整数` `*` の場合・・・ 指定した列数だけ同じセルを繰り返す（参考サイト1参照）。
- m[3]：横方向(`<`or`^`or`>`) `.` 縦方向(`<`or`^`or`>`)・・・ アライメント 横(左寄せ、中央寄せ、右寄せ) と 縦(上寄せ、中央寄せ、下寄せ)。<br>`.`以降を省略可。もしくは、`.`の前を省略することもできる。
- m[4]：`文字`・・・[書式指定](https://qiita.com/kob58im/items/2f8a9a5a60f445b083b0#%E3%82%A2%E3%83%A9%E3%82%A4%E3%83%A1%E3%83%B3%E3%83%88-%E3%81%8A%E3%82%88%E3%81%B3-%E6%9B%B8%E5%BC%8F%E6%8C%87%E5%AE%9A-%E3%81%AB%E4%BD%BF%E7%94%A8%E3%81%A7%E3%81%8D%E3%82%8B%E6%96%87%E5%AD%97)


# 参考サイト

1. [Asciidoctor User Manual #cell](https://asciidoctor.org/docs/user-manual/#cell)
- [AsciiDoc cheatsheet](https://powerman.name/doc/asciidoc)