No Programming, No Life

プログラミング関連の話題や雑記

GroovyでMarkdownパーサーを作ろう #4「Headers(見出し)の実装」その2

このシリーズの一覧


Groovy! (挨拶)

どうも、ふも(@)です。

はじめに

さてそれでは、前回の続きで、Headers(見出し)の具体的な実装に入って行きたいと思います。

一行ずつ処理する

前回までは、ファイルから読み取った内容を一気にHTMLのBODYタグに埋め込んでいましたが、
今回実装しようとしているAtx形式のヘッダーは行単位で処理してゆくのが適していそうです。
そこでまず、ファイルから一行一行取り出して処理するように変更したとして、
その一行一行に対する処理を考えてみましょう。
ということでまず、fumomarkdowngの冒頭部分を以下のように変更します。

// 第一引数の内容を読み込む
def lines = new File(args[0]).readLines()

// Markdown形式をHTMLに変換
def md = new Markdown()
def contents = lines.collect {
  md.headerAtx(it)
}.join('\n')

マークダウン形式のファイルを読み込んで一行ずつのリストにして(#readLines)、変数linesに格納します。
次に、Markdownクラスを作成*1して、読み込んだ各行について、Markdown#headerAtxを呼び出し変換処理を行う。
変換した結果は、改行文字で#joinして、contentsの出来上がりという流れ。
これで、外枠の読み込み部分はできましたので、次は変換処理のメインに行きましょう。

Markdownクラスの作成

今後、色々な変換処理を実装することになりそうなので、Markdownというクラスを作成することにしました。
今回はとりあえずAtx形式変換するメソッド #headerAtx を実装することにしましょう。

Groovyではクラス定義の仕方はJavaと同じです。

class Markdown {
   // ...
}

メソッド定義もJavaと同じです。さらにGroovyでは引数を省略してdefキーワードも使えるのですが、今回は明示的にStringにしておきましょうか。
引数はString mdで、ファイルから読み取った1行の文字列が来る想定です。

class Markdown {
  /** 見出し:Atx形式 */
  String headerAtx(String md) {
    // ...
  }
}

行の先頭をまず確認します。具体的には "#" で始まっているかどうか?を確認する必要がありますね。

次に、#headerAtxの中身です。まずは、'#'の数を数えておきましょう。

def sharpSize = md.find(/^#+/).size()

#の数は1〜6の範囲です。それ以上でも以下でもないです。
"#" が所定の数で始まっているなら、次のステップへ移行します。

'#'の数が1〜6なら所定の処理を行う部分は、Groovyならin演算子を使って以下のように書けますね。

if (sharpSize in 1..6) {
  // ...
} else {
  return md 
}

"#" が所定の数で始まっていなかったり(例えば7個とかね) そもそも "#" で始まっていなかったりした場合はそのままその行は終了。

'#'の数が範囲外なら、何もせずにそのままreturn mdします。

さて、範囲内だった場合、いよいよ変換処理なわけですが、'#'の数がそのままHタグの数値部分になるのですが、
'#'の数はすでにわかっているので、

その行の先頭と末尾の連続する "#" を取り除きます。

def contents = md.replaceAll(/^#+\s*|\s*#+$/, '').trim()

もしくは、同じことが以下のようにも書けます。

def contents = md.replaceAll(/(?x) # enable whitespace and comments
  ^\#+   # 先頭からはじまるシャープ
  |      #   または
  \#+$   # 最後のシャープ
/, '').trim()

(?x)はGroovyの正規表現拡張で、Groovyの正規表現リテラルのようなもの*2の中で、改行を入れたり # でコメントを入れたりして見やすく正規表現が書けるようにするものです。
(ただ、今回は '#' 自体をどうにかしたい関係で、 ¥# のようにエスケープが必要になってしまい、あまり見やすいとは言えないのですが。)
上記のどちらでもやりたいことは一緒で、先頭と末尾の連続する '#' を削除したあと、trimしています。

最後に "#" を取り除いた結果をトリムして、Hタグでくるんであげれば変換完了です。

ということで、変換結果はcontentsに入っているはずなので、以下のようにして変換完了です。

def tags = [  "<h${sharpSize}>",
             "</h${sharpSize}>" ]
return "${tags.first()}${contents}${tags.last()}"

おわりに

少し急ぎすぎた感じもありますが、これで期待どおりの結果が得られます。

今回使用したソースはこちら

前の記事:GroovyでMarkdownパーサーを作ろう #3「Headers(見出し)の実装」 - No Programming, No Life

*1:この後すぐ示します。

*2:/ ではじまり / で終わる文字列。