はじめに
映画「千と千尋の神隠し」の登場人物である湯婆婆と、主人公の千尋とのやりとりを実装したものが、@NemesisさんのJavaで湯婆婆を実装してみるの記事です。これに触発されて、WORD VBAで湯婆婆や、Autohotkeyでトースト湯婆婆の記事を投稿いたしました。
そして、NameDividerを触媒にパーフェクト湯婆婆を創造するを見て思いました。まだ自分のコーディングしたものはパーフェクトではなかった、と・・・。なお、パーフェクト湯婆婆の動作は以下です。
千尋は契約書にフルネームを書き、湯婆婆は下の名前だけを口にしたうえで、下の名前から1文字を残し、名前の他の部分を奪った、のです。
しかし、パーフェクト湯婆婆の作成には、日本語の形態素解析エンジンを組み込むことが必要であり、AutohotkeyやVBAでは対応できません。
そこで作成したのが、NMeCabを使ったパーフェクトVSTO湯婆婆です。単一バイナリのNMeCabのdllの紹介も兼ねております。
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オブジェクトを操作することで、ワード画面に段落や文字を入力できます。
app.Selection.TypeParagraph(); は、ワードのパラグラフの入力です。
app.Selection.TypeText(); は、ワードへのテキスト入力です。ここでは、「契約書だよ。そこ(乙)に名前を書きな。」を画面に入力したのち、契約書フォームを呼び出して名前を入力させています。
cf.yourName は、ユーザが入力した名前です。
入力した名前をYNameクラスに設定し、人名の下の名前 (yn.firstName) が取得できたならば、1文字だけ残した新たな名前 (yn.newName) を表示しています。
なお、形態素解析(NMeCab)によって、下の名前が検知できなかった場合には、「フン、" + yn.strangeName + "というのかい、おかしな名だねえ」と表示させています。このとき、yn.strangeNameには、入力した全ての文字列が格納されます。
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingMicrosoft.Office.Tools.Ribbon;usingSystem.Windows.Forms;usingyubaba.Name;usingMicrosoft.Office.Interop.Word;namespaceyubaba{publicpartialclassyubabaRibbon{privatevoidyubabaRibbon_Load(objectsender,RibbonUIEventArgse){}privatevoidbutton1_Click(objectsender,RibbonControlEventArgse){Microsoft.Office.Interop.Word.Applicationapp=Globals.ThisAddIn.Application;app.Selection.TypeParagraph();app.Selection.TypeText("湯婆婆「契約書だよ。そこ(乙)に名前を書きな。」");app.Selection.TypeParagraph();contractFormcf=newcontractForm();cf.ShowDialog();YNameyn=newYName(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として返すことが記載されています。
usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Windows.Forms;namespaceyubaba{publicpartialclasscontractForm:Form{publiccontractForm(){InitializeComponent();}publicstringyourName{get{returntextBox1.Text;}}privatevoidbutton1_Click(objectsender,EventArgse){this.DialogResult=DialogResult.OK;this.Close();}}}YNameクラスは、NMeCabを呼び出して入力文字列 inString を形態素解析し、名詞・固有名詞・人名・名のチャンクがあれば下の名前として、その中から1文字を取り出して新しい名としています。名詞・固有名詞・人名・名のチャンクがなければ、inString を strangeName に代入して、その中から1文字を取り出して新しい名としています。
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingNMeCab;namespaceyubaba.Name{classYName{publicstringfirstName{get;}publicstringstrangeName{get;}publicstringnewName{get;}publicboolfStrange{get;}publicYName(stringinString){this.firstName="";this.strangeName="";this.newName="";this.fStrange=true;varmecab=MeCabTagger.Create();MeCabNodenode=mecab.ParseToNode(inString);node=node.Next;while(node!=null){if(node.Feature!="BOS/EOS,*,*,*,*,*,*,*,*"){Chunk単語=newChunk(node);if(単語.isPartOfSpeech("名詞","固有名詞","人名","名")){this.firstName=node.Surface;fStrange=false;}}node=node.Next;}mecab.Dispose();varrandom=newRandom();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#でアクセスしやすくなります。
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingNMeCab;namespaceyubaba.Name{classChunk{publicstring表層形{get;set;}publicstring品詞{get;set;}publicstring品詞細分類1{get;set;}publicstring品詞細分類2{get;set;}publicstring品詞細分類3{get;set;}publicstring活用形{get;set;}publicstring活用型{get;set;}publicstring原形{get;set;}publicstring読み{get;set;}publicstring発音{get;set;}publicstringpadding{get;set;}publicstringfeature{get;set;}publicMeCabNodeStatstat{get;set;}publicChunk(MeCabNodenode){表層形=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(7<=features.Length)読み=features[6];if(8<=features.Length)発音=features[7];}// 品詞とチャンクとの照合publicboolisPartOfSpeech(stringszPartOfSpeech,stringszPartOfSpeechSC1=null,stringszPartOfSpeechSC2=null,stringszPartOfSpeechSC3=null){if(品詞!=szPartOfSpeech){returnfalse;}if(szPartOfSpeechSC1==null){returntrue;}if(品詞細分類1!=szPartOfSpeechSC1){returnfalse;}if(szPartOfSpeechSC2==null){returntrue;}if(品詞細分類2!=szPartOfSpeechSC2){returnfalse;}if(szPartOfSpeechSC3==null){returntrue;}if(品詞細分類3!=szPartOfSpeechSC3){returnfalse;}returntrue;}}}余談
現総理の「菅義偉」を入力すると、下の名前は「義」と認識されます。前総理の「安倍晋三」を入力すると、下の名前は「晋」と認識されます。NMeCabの固有名詞辞書にない名前はやや難しいようです。




