RedPenにスペルチェック機能を追加する

uncategorized
Published: 2015-11-23

前置き

もともとこのブログはWordpressで運用していました。ですが、アウトプットすることが目的であってCMSを運用することは目的ではありませんので、HUGO + Github Pagesの構成に切り替えました。

切り替えに伴い、ブログ用のGithubリポジトリにMarkdownをPushすると、CircleCIがHUGOを使ってブログをビルドし、HTMLをGithub PagesにPushする、という仕組みにしました。

文章をテストする

ブログを公開するプロセスにCircleCIがいますので、ブログの文章に対してCIを行うことがでいます。何をするか考えた結果、文章をテストすることにしました。ブログ用のGithubリポジトリにPushされたMarkdownをCircleCIでテストし、誤記や文法の誤りなどがあればテスト失敗とみなしビルドを行わない。こうすることで、ブログに誤った情報を含むエントリーが乗ってしまう可能性を減らすことができます。

文章をテストするツールを探してみると、textlint/textlintredpen-cc/redpenが見つかりました。asciidocとasciidocter-pdfを利用した気軽なドキュメント作成を模索していることもありますので、今回はasciidocをサポートするRedPenを使うことにします。

実践

UTM製品であるフォーティゲートの正しいスペルは「FortiGate」です。FortigateやFroutigateではありません。ネットワークエンジニアとして、ネットワーク機器のスペルを間違えるのは恥ずかしい。そこでRedPenに、自分が指定するキーワードを利用したスペルチェック機能を実装します。

RedPenはJavaScriptを利用した機能拡張をサポートしています。「RedPenのValidatorをJavaScript で書くには」を参考に、JavaScriptによる機能拡張を実装します。

スペルチェックについては編集距離を利用します。編集距離を求める関数は「JavaScriptで編集距離(レーベンシュタイン距離)を計算する 」をそのまま使わせていただきます。

以下コードの通り、RedPenが検知した名詞とスペルチェックの確認対象との編集距離を求め、0ではなく3以下であればスペルミスとみなします。編集距離を3としたのは勢いです。適当な値はこれから模索していきます。

function validateSentence(sentence) {

  var levenshteinDistance = function(a, b) {
    var matrix = new Array(a.length + 1);
    for (var i = 0; i < a.length + 1; i++) {
      matrix[i] = new Array(b.length + 1);
    }

    for (var i = 0; i < a.length + 1; i++) {
      matrix[i][0] = i;
    }

    for (var j = 0; j < b.length + 1; j++) {
      matrix[0][j] = j;
    }

    for (var i = 1; i < a.length + 1; i++) {
      for (var j = 1; j < b.length + 1; j++) {
        var x = a[i - 1] == b[j -1] ? 0 : 1;
        matrix[i][j] = Math.min(
          matrix[i - 1][j] + 1,
          matrix[i][j - 1] + 1,
          matrix[i - 1][j- 1] + x
        );
      }
    }

    return matrix[a.length][b.length];
  }


  console = {
      log: print,
      warn: print,
      error: print
  };

  var checkKeywordArray = [
    'FortiGate',
    'メルセデスベンツ',
    'BIG-IP',
    'インデックス',
    'JavaScript'
  ]

  for (var i = 0; i < sentence.tokens.length; i++) {
    // 名詞だけを対象に
    if (sentence.tokens[i].tags[0] == '名詞') {
      for (var j=0; j < checkKeywordArray.length; j++) {
        //console.log('checking : ' + sentence.tokens[i].surface + ' and ' + checkKeywordArray[j])
        var dist = levenshteinDistance(sentence.tokens[i].surface,checkKeywordArray[j])        
        //console.log(sentence.tokens[i].surface +' is ' + dist)
        if (dist <= 3 && dist != 0){
          addError(sentence.tokens[i].surface + 'はスペルミスの可能性があります', sentence);
        }
      }
    }  
  }
}

JavaScriptを利用したvalidatorをRedPenのコンフィグで有効にします。

$ cat conf/redpen-conf-ja-custom.xml                                   
<redpen-conf lang="ja">
    <validators>
        <validator name="SentenceLength">
            <property name="max_len" value="100"/>
        </validator>
        <validator name="KatakanaEndHyphen"/>
        <validator name="SectionLength">
            <property name="max_num" value="1500"/>
        </validator>
        <validator name="ParagraphNumber"/>
        <validator name="SuccessiveWord" />
        <validator name="JavaScript" />  
    </validators>
</redpen-conf>

テストするMarkdownは以下の通りです。あえて誤記をまぜます。

$ more test.md

## どうだー

インデックスは正しいです

インデデクスは間違っています

メルセデスベンスは間違っています

メルセデスベンツは正しいです

FortiGateは正しいです

Fortigateは間違っています

Frotigateも間違っています

テストします。間違ったスペルのセンテンスだけをエラーとして検知していますね。

redpen-distribution-1.4.1]$ bin/redpen -c conf/redpen-conf-ja-custom.xml -f markdown test.md
[2015-11-23 18:55:02.902][INFO ] cc.redpen.Main - Configuration file: /home/aimless/study/document/redpen/redpen-distribution-1.4.1/conf/redpen-conf-ja-custom.xml
[2015-11-23 18:55:02.910][INFO ] cc.redpen.ConfigurationLoader - Loading config from specified config file: "/home/aimless/study/document/redpen/redpen-distribution-1.4.1/conf/redpen-conf-ja-custom.xml"
[2015-11-23 18:55:02.921][INFO ] cc.redpen.ConfigurationLoader - Succeeded to load configuration file
[2015-11-23 18:55:02.921][INFO ] cc.redpen.ConfigurationLoader - Language is set to "ja"
[2015-11-23 18:55:02.921][WARN ] cc.redpen.ConfigurationLoader - No type configuration...
[2015-11-23 18:55:02.922][INFO ] cc.redpen.ConfigurationLoader - No "symbols" block found in the configuration
[2015-11-23 18:55:02.990][INFO ] cc.redpen.config.SymbolTable - "ja" is specified.
[2015-11-23 18:55:02.990][INFO ] cc.redpen.config.SymbolTable - "normal" type is specified
[2015-11-23 18:55:03.497][INFO ] cc.redpen.parser.SentenceExtractor - "[。, ?, !]" are added as a end of sentence characters
[2015-11-23 18:55:03.498][INFO ] cc.redpen.parser.SentenceExtractor - "[’, ”]" are added as a right quotation characters
[2015-11-23 18:55:03.512][INFO ] cc.redpen.validator.Validator - max_len is set to 100
[2015-11-23 18:55:03.515][INFO ] cc.redpen.validator.Validator - max_num is set to 1500
[2015-11-23 18:55:03.516][INFO ] cc.redpen.validator.Validator - max_num is not set. Use default value of 5
[2015-11-23 18:55:03.519][INFO ] cc.redpen.validator.JavaScriptValidator - JavaScript validators directory: /home/aimless/study/document/redpen/redpen-distribution-1.4.1/js
test.md:1: ValidationError[ParagraphNumber], The number of paragraphs exceeds the maximum of 7. at line: どうだー
test.md:5: ValidationError[JavaScript], [spellcheck.js] インデデクスはスペルミスの可能性があります at line: インデデクスは間違っています
test.md:7: ValidationError[JavaScript], [spellcheck.js] メルセデスベンスはスペルミスの可能性があります at line: メルセデスベンスは間違っています
test.md:13: ValidationError[JavaScript], [spellcheck.js] Fortigateはスペルミスの可能性があります at line: Fortigateは間違っています
test.md:15: ValidationError[JavaScript], [spellcheck.js] Frotigateはスペルミスの可能性があります at line: Frotigateも間違っています

[2015-11-23 18:55:04.483][ERROR] cc.redpen.Main - The number of errors "5" is larger than specified (limit is "1").
[aimless@dev redpen-distribution-1.4.1]$

所感

RedPenを利用したスペルチェックがローカル環境で動くことを確認しました。JavaScriptでチェック項目を拡張できるのがいいですね。次はスペルチェック用辞書の単語を増やした上で、CircleCI上で動作させてみようと思います。