AsciiDocの表(Table)のcols属性の仕様＆Asciidoctorの内部実装について調べてみた


AsciiDocの表(Table)のチートシートをまとめようと思ったが、深みにハマってしまったので、まずはそのメモを残してみる。

# はじめに - AsciiDoc と Asciidoctor の違いについて

さっき理解した(;・∀・)

[AsciiDoc - Wikipedia](https://ja.wikipedia.org/wiki/AsciiDoc)より引用：
> 「Asciidoctor」と呼ばれるAsciiDoc翻訳機のRubyによる実装がGitHubにおいて発表された

ざっくりまとめると
AsciiDoc　＝　言語仕様
Asciidoctor　＝　AsciiDocのデータ(.adocファイル)を処理してHTMLとかに変換するソフトウェア

# 表の属性

```:例

[grid="rows",format="csv"]
[options="header",cols="^,<,<s,<,>m"]
```


ググるとサンプルは多数見つけられるが、網羅的なドキュメントが見つからなかったので、実行ファイルの付近を検索してマニュアルを漁ってみた。結果、それらしいadocファイル（下記）が見つかったので、asciidoctorでコンパイル(?)してみた。

GitHub探したらドキュメントありました。

[syntax.adoc](https://github.com/asciidoctor/asciidoctor/blob/master/data/reference/syntax.adoc#tables)[^1]より以下に抜粋
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/2bebf82c-43ca-e267-8733-703192ea767c.png)

<b>`colspec`の説明が見当たらない・・・</b>
例でいうと `cols="^,<,<s,<,>m"` の内容にあたる。

# そして内部実装を調べ始める・・・

`colspec`の説明が見当たらないのでソースを見てみた。
（ソースは [GitHubのこのへん](https://github.com/asciidoctor/asciidoctor/tree/master/lib/asciidoctor)。インストールしていれば <i>C:\Ruby26-x64\lib\ruby\gems\2.6.0\gems\asciidoctor-2.0.12\lib\asciidoctor\ </i> あたりにある。）

## 'cols'を拾っている処理

```ruby:parser.rb（抜粋）

  def self.parse_table(table_reader, parent, attributes)
    ：
    if (attributes.key? 'cols') && !(colspecs = parse_colspecs attributes['cols']).empty?
    ：
```

```ruby:parser.rb(抜粋＆補足)

                                                                                              # Line
def self.parse_colspecs records                                                                 #  1
  records = records.delete ' ' if records.include? ' '                                          #  2
  # check for deprecated syntax: single number, equal column spread                             #  3
  if records == records.to_i.to_s                                                               #  4
    return ::Array.new(records.to_i) { { 'width' => 1 } }                                       #  5
  end                                                                                           #  6
                                                                                                #  7
  specs = []                                                                                    #  8
  # NOTE -1 argument ensures we don't drop empty records                                        #  9
  ((records.include? ',') ? (records.split ',', -1) : (records.split ';', -1)).each do |record| # 10
    if record.empty?                                                                            # 11
      specs << { 'width' => 1 }                                                                 # 12
    # TODO might want to use scan rather than this mega-regexp                                  # 13
    elsif (m = ColumnSpecRx.match(record))                                                      # 14
    ：
```

- 4～6行目のところで、`cols=4`のように列数のみの指定のケースを処理している。
- 10行目以降で、`cols="^,<,<s,<,>m"`のように列個別で書式指定しているケースを処理している。<br>`,` があれば `,` で分割し、なければ `;` で分割しているよう。
- 14行目以降については、以下で詳しくみていく。

## 'cols="xxx,xxx,xxx"' のパラメータの中身xxx

まずは上記の14行目の正規表現`ColumnSpecRx`を調べる。

```ruby:rx.rb（抜粋＆補足）
#                   m[1]     m[2]                                    m[3]      m[4]
#                   <--->    <-------------------------------------> <-------> <----->
ColumnSpecRx = /^(?:(\d+)\*)?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?(\d+%?|~)?([a-z])?$/
```

【概要】いずれも省略可能

- m[1]：`整数*` ・・・ 列の繰り返し回数
- m[2]：横方向(`<`or`^`or`>`) `.` 縦方向(`<`or`^`or`>`)・・・ アライメント 横(左寄せ、中央寄せ、右寄せ) と 縦(上寄せ、中央寄せ、下寄せ)。<br>`.`以降を省略可。もしくは、`.`の前を省略することもできる。
- m[3]：`整数%` or `~` ・・・ 列幅。`%`は省略可。
- m[4]：`文字`・・・書式指定

```ruby:parser.rb(抜粋＆補足)

elsif (m = ColumnSpecRx.match(record))                                 # 14
  spec = {}                                                            # 15
  if m[2]                                                              # 16 m[2]
    # make this an operation                                           # 17
    colspec, rowspec = m[2].split '.'                                  # 18
    if !colspec.nil_or_empty? && TableCellHorzAlignments.key?(colspec) # 19
      spec['halign'] = TableCellHorzAlignments[colspec]                # 20
    end                                                                # 21
    if !rowspec.nil_or_empty? && TableCellVertAlignments.key?(rowspec) # 22
      spec['valign'] = TableCellVertAlignments[rowspec]                # 23
    end                                                                # 24
  end                                                                  # 25
                                                                       # 26
  if (width = m[3])                                                    # 27 m[3]
    # to_i will strip the optional %                                   # 28
    spec['width'] = width == '~' ? -1 : width.to_i                     # 29
  else                                                                 # 30
    spec['width'] = 1                                                  # 31
  end                                                                  # 32
                                                                       # 33
  # make this an operation                                             # 34
  if m[4] && TableCellStyles.key?(m[4])                                # 35 m[4]
    spec['style'] = TableCellStyles[m[4]]                              # 36
  end                                                                  # 37
                                                                       # 38
  if m[1]                                                              # 39 m[1]
    1.upto(m[1].to_i) { specs << spec.merge }                          # 40
  else                                                                 # 41
    specs << spec                                                      # 42
  end                                                                  # 43
end                                                                    # 44
：

```


## アライメント および 書式指定 に使用できる文字

```ruby:parser.rb（抜粋）

  # Internal: A Hash mapping horizontal alignment abbreviations to alignments
  # that can be applied to a table cell (or to all cells in a column)
  TableCellHorzAlignments = {
    '<' => 'left',
    '>' => 'right',
    '^' => 'center'
  }

  # Internal: A Hash mapping vertical alignment abbreviations to alignments
  # that can be applied to a table cell (or to all cells in a column)
  TableCellVertAlignments = {
    '<' => 'top',
    '>' => 'bottom',
    '^' => 'middle'
  }

  # Internal: A Hash mapping styles abbreviations to styles that can be applied
  # to a table cell (or to all cells in a column)
  TableCellStyles = {
    'd' => :none,
    's' => :strong,
    'e' => :emphasis,
    'm' => :monospaced,
    'h' => :header,
    'l' => :literal,
    'a' => :asciidoc
  }
```

# 続編（セルの書式設定について調べてみた）

[AsciiDocの表(Table)のセル属性の指定方法の仕様＆Asciidoctorの内部実装について調べてみた - Qiita](https://qiita.com/kob58im/items/2b6e955c71046da14504)


# 環境

Ruby 2.6.0
Asciidoctor 2.0.12

Rubyは過去の自分がいつのまにかインストールしてたので、記憶がない。。。
Asciidoctorは、[Asciidoctorインストール手順（Windows10）](https://qiita.com/aki-nasu/items/450fa0f9dc5cdd7f0acb)を参考にインストールしました。

自分の環境では、下記にインストールされている。（ソースやマニュアルなども入っている。）
`C:\Ruby26-x64\lib\ruby\gems\2.6.0\gems\asciidoctor-2.0.12\`


# 参考サイト

※記事の作成途中で趣旨が変わり、あまり関係がなくなってしまったので、もともと書きたかった内容の記事ができたら適宜そこに引っ越したいと思います。

 - [GitHub - asciidoctor](https://github.com/asciidoctor/asciidoctor)
 - [AsciiDoc Writer’s Guide | Asciidoctor](https://asciidoctor.org/docs/asciidoc-writers-guide/#a-new-perspective-on-tables)
 - [AsciiDoc Syntax Quick Reference | Asciidoctor #tables](https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#tables)
 - [Asciidoctor 文法クイックリファレンス(日本語訳) #_テーブル](https://takumon.github.io/asciidoc-syntax-quick-reference-japanese-translation/#_%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB)
 - [Asciidoctor User Manual #tables](https://asciidoctor.org/docs/user-manual/#tables)
 - [AsciiDoc cheatsheet](https://powerman.name/doc/asciidoc)


[^1]: インストールしていれば C:\Ruby26-x64\lib\ruby\gems\2.6.0\gems\asciidoctor-2.0.12\data\reference\syntax.adoc</i> あたりにある。