自己顕示欲の開放治療所

erg, programming and something.

別名:Laughing and Grief 雑記

Latin and Greekは習ったこともない

真面目な記事の他、特定の方には不快と思われる事柄に関して言及を行うことがあります。ちょっと頑張りますが、Blog内で解決できなかった場合要望があれば別ページに技術記事は書き直します

よくわかっていないツールと言語でよくわかっていないマークアップのCustom Writerをやっていく

この記事は

EWB アドベントカレンダー2019の21日目の記事です。

adventar.org

Pandocアドベントカレンダー2019の21日目の記事です。

https://adventar.org/calendars/4338

20日目は某ZR(ざんねん🙃) (@zr_tex8r) | Twitterさんで https://zrbabbler.hatenablog.com/entry/2019/12/04/234053でした。

最初に書いておくと完成してません。 明日夜だと思っていたタスクが朝締切だったりするなど、師走を実感しますね。

前置き

さて、EWBの情報を色々と集めてきたわけですが、

www.slideshare.net

「reSTをEWBに!」「いいですとも!」

ということで、他の軽量マークアップ形式からEWBにして作成された書籍がありました。残念ながら変換処理については出版社内部で行われたようで詳細は分かりませんが、DocutilsはreST以外のReader、\LaTeX{}やHTML以外のWriterを独自に作ることもできるので、この辺りを活用するとちょっと楽そうですね。楽だとは言っていませんが。

そんな感じに記事のネタを探していたワケですが、うっかり21日にPandocアドベントカレンダーの登録をしていたことを思い出しました。

そういえば鹿野さんの記事でPandocとDocutilsの相似について言及されていました。

golden-lucky.hatenablog.com

ヨシ!

EWBの記法おさらい

EWB文書本体のマークアップには 「組版トリガ」と「編集トリガ」 があります。このうち「組版トリガ」は書籍上の見た目に関わるものなので、今回Pandocで変換元とするMarkdownからは考えなくとも良さそうです。

編集トリガの基本文法は以下のようになります。実はブロックかインラインかは記法上は区別がなく、各トリガそれぞれで異なり、なんだったら書き換えられます。

//<文字列>{ ...//<文字列>}...

見出しなどは開始タグ閉じタグがつきません。

//i 見出し

リストの基本は以下のようになります。

///k1{
<記号> <文字列>
<記号> <文字列>
//k1}

テーブルはシンプルな構造であれば タブ文字区切りで似たことをやればよいので、基本は大体これでいける気がしてきました。

今回はこれらの基本的なところに絞り、 Custom Writerに挑戦してみます。

みりあやんないよ

  • EWBでは改行が反映されますが、今回は正しく処理できるかは考慮しないものとします。一応SoftBreak設定してやれば動くハズ?
  • EWBで使える文字種はJIS全角範囲とされています。EWBで処理時にエラーを吐いてくれるはずなので今回はスルーします。

Pandoc Custom Writer

Pandocアドベントカレンダー2019にはReaderの記事がありますが、

golden-lucky.hatenablog.com

Writerは既にPandocに渡っている構造の出力を弄るだけなのでより簡単、な筈、です。

カスタムライターを作るために、先ずはLuaにおける出力例の一覧を取得します*1

これを書き換えていきます。

例えば

-- sample.lua
function Emph(s)
  return "<em>" .. s .. "</em>"
end

-- ewbfilter.lua
function Emph(s)
  return "//g{" .. s .. "//g}"
end

になる、といった感じですね。実際のEWBでは 文字エンコーディングSJISEUC-JPになりますが、そんなんは後で変換かければええ。

エスケープ

さて、出力毎に調整が必要な機能として エスケープがあります。

(Pandoc)Markdownでは問題の無かった表現がEWBではNG、というものですね。

EWBのトリガは先頭に//があるので、ここから3種くらいの区切りまでがトリガとされます。//がきたら特別な場合を除き ////に直してやる必要があります。

/**hoge**といった並びのとき、素朴な置換をすると///g{hoge//g}となりますが、ここはEWB(ハンドブック)を信じてそのままに。

また、今回扱わないといった組版トリガも Markdown文中に表われるのはけしからんのでエスケープします。@@@@@@にします。

改行

function SoftBreak()
  return "\n"
end

function LineBreak()
  return "\n"
end

箇条書き

BulletList

ある意味単純ですね。ネストも問題無いハズ。 項目先頭の記号はとりあえずでハードコーディング。

function BulletList(items)
  local buf = {}
    for _, items in pairs(items) do
      table.insert(buf, "・ //|" .. item .. "\n")
    end

  return "//k1{{" ..table.concat(buf, "\n") .. "\n//k1}"
end
OrderedList

先程よりちょっと厄介なのは、\LaTeX{}やHTMLと違って自動インクリメントされるマークアップではないので、 数字をこちらで回すこと。まあそれだけのハズ。

function OrderedList(items)
  local buf = {}
    for i, item in pairs(items) do
      table.insert(buf, i .. ".//| " .. item .. "\n")
    end

  return "//k2{{" .. table.concat(buf, "\n") .. "\n//k2}}"
end

DefinitionList

まあ箇条書きのグループとしてみれば、やることは大体同じでしょう。

function DefinitionList(items)
  local buffer = {}
  for _, item in pairs(items) do
    for k, v in pairs(item) do
      table.insert(buf, k .. " //| ".. v .."\n")
    end
  end
  return "//k3{" .. table.concat(buf, "\n") .. "//k3}"
end

コードブロック

元のサンプルだとエスケープ処理挟んでたんですが、 アトリビュートはまあオプション引数に影響するにしても トリガ定義側でなんとかする奴なので今回は飛ばします。

ただ、コードブロック内部のコメントは特殊な処理をしてコメントであることを明示するようにしなければなりません。 えっアイデアが浮かばない。とりあえず空のローカル関数を定義して濁します。

function CodeBlock(s, attr)
  local cmt_parse = comment_perse(s)
  return "//list1{\n" .. cmt_perse .."\n//list1}"
end

local function comment_parse(s)
  return s
end

水平線

function HorizontalRule()
    return "//kei\n"
end

Caption付き図表

EWBでは、基本的には図表を文書中では扱いません。テーブル などは記述可能ですが、文書中には基本的にユニークな番号とキャプションの文字列だけです。実際に図表リストを別に作成するのは処理が複雑化するのでスルーします。 ユニークな番号に関しては数字以外でもいいはずですが、面倒なので数字ということにしましょう。実装も手を抜いて 使用直前にインクリメントでいきましょう。

local fignum = 0

function CaptionedImage(src, tit, caption, attr)
  fignum = fignum + 1
  return '//f'.. fignum  .. " " .. caption .. "\n"
end

Table

EWBのテーブルはそれなりに複雑な図表も可能ですが、シンプルテーブルの入力を仮定します。 寄せの指定は簡単ですが、カラム幅の文字数指定って互換ありそうでないんですよね。今回はやりません。

function Table(caption, aligns, widths, headers, rows)
  local buf = {}
  local function add(s)
    table.insert(buf, s)
  end

  add("//table1")
  for _, align_raw in pairs(align) do
    local align = ewb_align(align_raw)
    add("[" .. align .. "]")  
  end
  add("{\n")
  local header_row = {}
  local empty_header = true
  
  for i, h in pairs(headers) do
    empty_header = empty_header and h = ""
  end
  if empty_header then
  head = ""
  else
    add("//g{")
    for _, h in pairs(header_row) do
      add(h .. "\t")
    end
    add("//g}\n")

    for _, row in pairs(rows) do
      for i, c in pairs(row) do
        add(c .. "\t")
      end
      add("\n")
    end
  end
    add("//table1}")
    return table.concat(buf,'\n')
end

未完

とりあえずlocalでないsample.luaに出てくる関数を全部埋めれば完成するハズ。 SpanDevも、本格的に変換を作るなら編集トリガと対応を作るべきなんだろうなあ。

というワケで、Readerでの難しい話が嘘のようにWriterの出力を弄るのは楽かもしれないという結びです*2

明日の枠も空いてるみたいですが、明日はSphinxをやっている予定なのでこの続きで埋めるとかはないです。