#はじめに
映画「千と千尋の神隠し」の登場人物である湯婆婆と、主人公の千尋とのやりとりを実装したものが、@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#)を選択してください。
そして、プロジェクトの作成先フォルダを選択します。これにより、ブロジェクトの雛形が作成されます。
次に、yubabaソリューション上で右クリックし、「新しい項目」を選択します。ここで、リボン(ビジュアルなデザイナー)を選択してください。
ツールボックスにより、yubabaRibbon.cs にボタンを追加します。
そして、湯婆婆ボタンにアイコンを追加します。
湯婆婆ボタンのColtrolSizeは、RibbonControlSizeLargeを選択してください。湯婆婆の顔をはっきりと見せるためです。そして、グループとタブのラベルを適宜設定してください。ここではグループのラベルに "Qiita"を設定し、タブのラベルに "AppLint" を設定しています。
そして、契約書フォームを追加します。フォームについては、普通のC#アプリケーションと同様です。
#NMeCabの組み込み
yubabaプロジェクトの「参照」を選択して右クリックし、コンテキストメニューの「参照の追加」を選択してください。そして、LibNMeCab.dll (単一バイナリのNMeCabのDLL)を選択してください。
#動作説明
WordのAppLintタブに、湯婆婆アイコンが表示されています。
湯婆婆アイコンをクリックすると、「契約書だよ。そこ(乙)に名前を書きな。」をWord画面に追加して、以下の契約書フォームを表示します。乙に人名(ここでは荻原千尋)を入力してOKボタンをクリックします。
湯婆婆は下の名前だけを口にして、この下の名前から1文字を残して名前の他の部分を奪います。
#ソースコード説明
ワード(アプリケーション)は、Globals.ThisAddIn.Applicationオブジェクトです。このうち、Selection(選択範囲)オブジェクトを操作することで、ワード画面に段落や文字を入力できます。なお、Selectionオブジェクトとは、現在の選択範囲に係るオブジェクトのことです。
app.Selection.TypeParagraph(); は、ワードのパラグラフの入力です。
app.Selection.TypeText(); は、ワードへのテキスト入力です。ここでは、「契約書だよ。そこ(乙)に名前を書きな。」を画面に入力したのち、契約書フォームを呼び出して名前を入力させています。
cf.yourName は、ユーザが入力した名前です。
入力した名前をYNameクラスに設定し、人名の下の名前 (yn.firstName) が取得できたならば、1文字だけ残した新たな名前 (yn.newName) を表示しています。
なお、形態素解析(NMeCab)によって、下の名前が検知できなかった場合には、「フン、" + yn.strangeName + "というのかい、おかしな名だねえ」と表示させています。このとき、yn.strangeNameには、入力した全ての文字列が格納されます。
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として返すことが記載されています。
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文字を取り出して新しい名としています。
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#でアクセスしやすくなります。
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サーバに発行することも可能なようですが、試していません。
そして、規定のインストールパスをCD-ROMまたはDVD-ROMとします。
これによりSETUP.EXE が作成されました。
SETUP.EXEの実行により、湯婆婆がインストールされます。
#余談
現総理の「菅義偉」を入力すると、下の名前は「義」と認識されます。前総理の「安倍晋三」を入力すると、下の名前は「晋」と認識されます。NMeCabの固有名詞辞書にない名前はやや難しいようです。