4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

NMeCabを使ったパーフェクトVSTO湯婆婆

Last updated at Posted at 2020-12-12

#はじめに
 映画「千と千尋の神隠し」の登場人物である湯婆婆と、主人公の千尋とのやりとりを実装したものが、@NemesisさんのJavaで湯婆婆を実装してみるの記事です。これに触発されて、WORD VBAで湯婆婆や、Autohotkeyでトースト湯婆婆の記事を投稿いたしました。
 そして、「NameDividerを触媒にパーフェクト湯婆婆を創造する」の記事を見て思いました。まだ自分のコーディングしたものはパーフェクトではなかった、と・・・。なお、パーフェクト湯婆婆の動作仕様は以下です。

千尋は契約書にフルネームを書き、湯婆婆は下の名前だけを口にしたうえで、下の名前から1文字を残し、名前の他の部分を奪った、のです。

 しかし、パーフェクト湯婆婆の作成には、日本語の形態素解析エンジンを組み込むことが必要であり、AutohotkeyやVBAでは対応できません。
 そこで作成したのが、NMeCabを使ったパーフェクトVSTO湯婆婆です。なお、この記事は、VSTO湯婆婆の紹介と共に、単一バイナリのNMeCabのdllの紹介も兼ねております。
 パーフェクトVSTO湯婆婆は、GitHubにソースコードとインストーラ込みで公開しています。
https://github.com/k-ayaki/yubaba

#MeCabとは
 MeCabとは、MeCabは 京都大学情報学研究科−日本電信電話株式会社コミュニケーション科学基礎研究所 共同研究ユニットプロジェクトを通じて開発されたオープンソース 形態素解析エンジンです。開発者は工藤卓氏であり、C++で記述されています。

#NMeCabとは、
 NMeCabとは、日本語形態素解析エンジンであるMeCabをC#に移植したものです。MeCabの様々な移植のうち .Net Framework環境ではもっとも安定して動作します。

 余談ですが、筆者はOSDNにアップロードされているNMeCabをベースに作業していました。Githubに新しい版がアップロードされてることに今更ながら気づいた次第です。

#NMeCab単一バイナリ版とは
 NMeCab単一バイナリ版とは、筆者がNMeCabをdll化して、かつ、辞書をリソースとして含んだものです。これにより、インストール時や動作時における辞書パスの設定に悩まされずにすみます。今回は、このNMeCabの単一バイナリ版を用いて、パーフェクト湯婆婆を作成します。なお、単一バイナリのNMeCabのDLLは、以下に公開しています。
 https://github.com/k-ayaki/NMeCabSb

#VSTOとは
 VSTO とは、Visual Studio Tools for Officeの略称であります。簡単にいうと、VBAと同様なWORD(Office)アドインを作成するためのフレームワークです。Visual Studioにソリューションのスケルトンが実装されています。VSTOを使うと、Microsoft Word上のテキストを直接に操作するアドインをC#で作成可能です。VBAの独特のクセが気になる方にはお勧めです。

 詳しくは、Office ソリューションの開発の概要 (VSTO)や、VSTOって何よってお話をご覧ください。なお、日本語の書籍としては、10年以上もまえに出版されたVSTOとSharePoint Server 2007による開発技術~Visual Studio 2008で構築するOBAソリューションがあるだけのようです。VSTOについては、VBAと比べて技術情報が少なく、やや苦労します。

#VSTOのプロジェクトの設定
Visual Studio 2019にて「新しいプロジェクトの作成」を選択して、Word VSTO アドイン(C#)を選択してください。
yubaba00.PNG

そして、プロジェクトの作成先フォルダを選択します。これにより、ブロジェクトの雛形が作成されます。
yubaba01.PNG

次に、yubabaソリューション上で右クリックし、「新しい項目」を選択します。ここで、リボン(ビジュアルなデザイナー)を選択してください。
yubaba02.PNG

これにより、yubabaRibbon.csが追加されます。
yubaba03.PNG

ツールボックスにより、yubabaRibbon.cs にボタンを追加します。
yubaba04.PNG

そして、湯婆婆ボタンにアイコンを追加します。

yubaba64.png

 湯婆婆ボタンのColtrolSizeは、RibbonControlSizeLargeを選択してください。湯婆婆の顔をはっきりと見せるためです。そして、グループとタブのラベルを適宜設定してください。ここではグループのラベルに "Qiita"を設定し、タブのラベルに "AppLint" を設定しています。
yubaba08.PNG

そして、契約書フォームを追加します。フォームについては、普通のC#アプリケーションと同様です。
yubaba09.PNG

#NMeCabの組み込み
yubabaプロジェクトの「参照」を選択して右クリックし、コンテキストメニューの「参照の追加」を選択してください。そして、LibNMeCab.dll (単一バイナリのNMeCabのDLL)を選択してください。

yubaba10.png

#動作説明
 WordのAppLintタブに、湯婆婆アイコンが表示されています。
yubaba_word11.png

 湯婆婆アイコンをクリックすると、「契約書だよ。そこ(乙)に名前を書きな。」をWord画面に追加して、以下の契約書フォームを表示します。乙に人名(ここでは荻原千尋)を入力してOKボタンをクリックします。
yubaba_word12.png

湯婆婆は下の名前だけを口にして、この下の名前から1文字を残して名前の他の部分を奪います。

 yubaba_word13.png

#ソースコード説明

 ワード(アプリケーション)は、Globals.ThisAddIn.Applicationオブジェクトです。このうち、Selection(選択範囲)オブジェクトを操作することで、ワード画面に段落や文字を入力できます。なお、Selectionオブジェクトとは、現在の選択範囲に係るオブジェクトのことです。
 app.Selection.TypeParagraph(); は、ワードのパラグラフの入力です。
 app.Selection.TypeText(); は、ワードへのテキスト入力です。ここでは、「契約書だよ。そこ(乙)に名前を書きな。」を画面に入力したのち、契約書フォームを呼び出して名前を入力させています。
 cf.yourName は、ユーザが入力した名前です。
 入力した名前をYNameクラスに設定し、人名の下の名前 (yn.firstName) が取得できたならば、1文字だけ残した新たな名前 (yn.newName) を表示しています。
 なお、形態素解析(NMeCab)によって、下の名前が検知できなかった場合には、「フン、" + yn.strangeName + "というのかい、おかしな名だねえ」と表示させています。このとき、yn.strangeNameには、入力した全ての文字列が格納されます。

yubabaRibbon.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Tools.Ribbon;
using System.Windows.Forms;
using yubaba.Name;
using Microsoft.Office.Interop.Word;

namespace yubaba
{
    public partial class yubabaRibbon
    {
        private void yubabaRibbon_Load(object sender, RibbonUIEventArgs e)
        {

        }

        private void button1_Click(object sender, RibbonControlEventArgs e)
        {
            Microsoft.Office.Interop.Word.Application app = Globals.ThisAddIn.Application;
            app.Selection.TypeParagraph();
            app.Selection.TypeText("湯婆婆「契約書だよ。そこ(乙)に名前を書きな。」");
            app.Selection.TypeParagraph();

            contractForm cf = new contractForm();
            cf.ShowDialog();
            YName yn = new YName(cf.yourName);

            if(yn.fStrange == false)
            {
                app.Selection.TypeText("湯婆婆「フン、" + yn.firstName + "というのかい、贅沢な名だねえ」");
                app.Selection.TypeParagraph();
                app.Selection.TypeText("湯婆婆「今日からお前の名前は" + yn.newName + "だ、いいかい、" + yn.newName + "だよ。分かったら返事をするんだ、" + yn.newName + "」");
            }
            else
            {
                app.Selection.TypeText("湯婆婆「フン、" + yn.strangeName + "というのかい、おかしな名だねえ」");
                app.Selection.TypeParagraph();
                app.Selection.TypeText("湯婆婆「今日からお前の名前は" + yn.newName + "だ、いいかい、" + yn.newName + "だよ。分かったら返事をするんだ、" + yn.newName + "」");
            }
        }
    }
}

契約書フォームは、OKボタンをクリックしたときに当該フォームを閉じることと、textBox1に入力されたテキストをyourNameとして返すことが記載されています。

contractForm.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace yubaba
{
    public partial class contractForm : Form
    {
        public contractForm()
        {
            InitializeComponent();
        }
        public string yourName
        {
            get
            {
                return textBox1.Text;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.OK;
            this.Close();
        }
    }
}

YNameクラスは、NMeCabを呼び出して入力文字列 inString を形態素解析し、名詞・固有名詞・人名・名のチャンクがあれば下の名前として、その中から1文字を取り出して新しい名としています。名詞・固有名詞・人名・名のチャンクがなければ、inString を strangeName に代入して、その中から1文字を取り出して新しい名としています。

YName.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NMeCab;

namespace yubaba.Name
{
    class YName
    {
        public string firstName { get; }
        public string strangeName { get; }
        public string newName { get; }
        public bool fStrange { get; }
        public YName(string inString)
        {
            this.firstName = "";
            this.strangeName = "";
            this.newName = "";
            this.fStrange = true;
            var mecab = MeCabTagger.Create();
            MeCabNode node = mecab.ParseToNode(inString);
            node = node.Next;
            while (node != null)
            {
                if (node.Feature != "BOS/EOS,*,*,*,*,*,*,*,*")
                {
                    Chunk chunk = new Chunk(node);
                    if (chunk.isPartOfSpeech("名詞","固有名詞","人名","名"))
                    {
                        this.firstName = node.Surface;
                        fStrange = false;
                    }
                }
                node = node.Next;
            }
            mecab.Dispose();

            var random = new Random();
            if (this.firstName.Length > 0)
            {
                newName = firstName.Substring((int)(random.Next(0, firstName.Length)), 1);
            } else
            {
                strangeName = inString;
                newName = strangeName.Substring((int)(random.Next(0, strangeName.Length)), 1);
            }
        }
    }
}

Chunkクラスは、形態素解析後の各ノードを記憶するものです。
なお、NMeCabが、「荻原千尋」を形態素解析すると以下となります。

Surface="荻原", Feature="名詞,固有名詞,人名,姓,,,荻原,オギワラ,オギワラ"
Surface="千尋", Feature="名詞,固有名詞,人名,名,,,千尋,チヒロ,チヒロ"

Surface を表層形のメンバー変数に、Feature をカンマで区切って各メンバー変数に設定しています。これにより、C#でアクセスしやすくなります。

Chunk.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NMeCab;

namespace yubaba.Name
{
    class Chunk
    {
        public string 表層形 { get; set; }
        public string 品詞 { get; set; }
        public string 品詞細分類1 { get; set; }
        public string 品詞細分類2 { get; set; }
        public string 品詞細分類3 { get; set; }
        public string 活用形 { get; set; }
        public string 活用型 { get; set; }
        public string 原形 { get; set; }
        public string 読み { get; set; }
        public string 発音 { get; set; }
        public string padding { get; set; }
        public string feature { get; set; }
        public MeCabNodeStat stat { get; set; }
        public Chunk(MeCabNode node)
        {
            表層形 = node.Surface;
            stat = node.Stat;

            feature = node.Feature;
            string[] features = node.Feature.Split(',');
            品詞 = "未定義";
            品詞細分類1 = "";
            品詞細分類2 = "";
            品詞細分類3 = "";
            活用形 = "";
            活用型 = "";
            原形 = "";
            読み = "";
            発音 = "";
            if (1 <= features.Length) 品詞 = features[0];
            if (2 <= features.Length) 品詞細分類1 = features[1];
            if (3 <= features.Length) 品詞細分類2 = features[2];
            if (4 <= features.Length) 品詞細分類3 = features[3];
            if (5 <= features.Length) 活用形 = features[4];
            if (6 <= features.Length) 活用型 = features[5];
            if (7 <= features.Length) 原形 = features[6];
            if (8 <= features.Length) 読み = features[7];
            if (9 <= features.Length) 発音 = features[8];
        }
        // 品詞とチャンクとの照合
        public bool isPartOfSpeech(string szPartOfSpeech, string szPartOfSpeechSC1 = null, string szPartOfSpeechSC2 = null, string szPartOfSpeechSC3 = null)
        {
            if (品詞 != szPartOfSpeech)
            {
                return false;
            }
            if (szPartOfSpeechSC1 == null)
            {
                return true;
            }
            if (品詞細分類1 != szPartOfSpeechSC1)
            {
                return false;
            }
            if (szPartOfSpeechSC2 == null)
            {
                return true;
            }
            if (品詞細分類2 != szPartOfSpeechSC2)
            {
                return false;
            }
            if (szPartOfSpeechSC3 == null)
            {
                return true;
            }
            if (品詞細分類3 != szPartOfSpeechSC3)
            {
                return false;
            }
            return true;
        }
    }
}

#インストーラ

 VSTOで最もありがたいのは、インストーラ作成がVisual Studio 2019上で簡単にできることだとおもいます。

 最初にビルドメニューから、「yubabaの発行」をクリックすると、以下の公開ウィザードが表示されますので、ディスクパスを入力します。なお、共有サーバやFTPサーバに発行することも可能なようですが、試していません。
yubabaSetup01.PNG

そして、規定のインストールパスをCD-ROMまたはDVD-ROMとします。

yubabaSetup02.PNG

これによりSETUP.EXE が作成されました。

yubabaSetup03.PNG

SETUP.EXEの実行により、湯婆婆がインストールされます。

yubabaSetup04.PNG

#余談
 現総理の「菅義偉」を入力すると、下の名前は「義」と認識されます。前総理の「安倍晋三」を入力すると、下の名前は「晋」と認識されます。NMeCabの固有名詞辞書にない名前はやや難しいようです。
yubaba_word14.png

yubaba_word15.png

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?