C# で最初に作ったツールは「特定のリストを作って専用のフォーマットに整形した上でテキストデータで出力するもの」という感じでしたが、そこで躓いた部分は正規表現による置換でした。
その置換についてメモしておきたいと思って用意したのが下図のツールです。
ここでいきなりUE4の話になりますが、UE4のアセット管理ツールであるコンテンツブラウザ上でアセットを右クリック「リファレンスをコピー」を選択すると、クリップボードにアセットの情報がコピーされます。
それは以下のような書式です。
アセットの種類 + ' + アセットのフルパス + . + アセット名 + '
で、例えばあるアセットの場所をメールでお知らせするような時には、フルパスの前後の文字列が邪魔だったりします。それを正規表現の置換で除去するのが今回のツールです。
ちなみに余談ですが、コンテンツブラウザ上でアセットを選択して Ctrl + C でリファレンスをコピーできることを最近同僚に教えてもらいました。
知りませんでした。。これは便利!
そしてアセットのリファレンス情報をツールの上段のテキストボックス内にペーストします。
すると下図のように貼り付けられました。
先に書いた通り、メールに記載したりするには余計な情報がごちゃっと付いています。
次に「パスから余計な情報を除去」ボタンを押すと、下段のテキストボックスに置換された結果が表示されます。
あとはオマケで「クリップボードへコピー」ボタンと「クリア」ボタンも追加しました。
実装したもの
さて、ここで実装した内容は以下になります。
・テキストボックス内で Ctrl + A を入力すると文字列を全選択する
・「クリア」ボタンでテキストボックス内の文字列を全消去する
・「クリップボードへコピー」ボタンで文字列をクリップボードにコピーする
・「パスから余計な情報を除去」ボタンでリファレンス情報を単なるフルパス情報に整形する
1つずつ見ていきます。
テキストボックス内での文字列の全選択
ツールを作ってみて初めて気付いたのですが、普段はテキスト入力できるパネル内で当たり前のように可能な全選択ショートカットですが、これは実装しないと可能になりません。
実装は簡単で、テキストボックスの KeyDown イベントに下記の2行を追記するだけです。
(青文字部分はテキストボックスのNameプロパティで設定した名前です)
//Ctrl + A で全選択可能にする
private void textBoxInput_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyCode == Keys.A)
textBoxInput.SelectAll();
}
文字列のクリアとクリップボードへのコピー
これらも実装は簡単で、ボタンのクリックイベントそれぞれに下記1行を追加するだけです。
(青文字部分はテキストボックスのNameプロパティで設定した名前です)
//「クリア」ボタンでtextBoxの内容を消去
private void buttonInputClear_Click(object sender, EventArgs e)
{
textBoxInput.ResetText();
}
//「クリップボードへコピーする」ボタンの挙動
private void buttonResultCopy_Click(object sender, EventArgs e)
{
Clipboard.SetText(textBoxResult.Text);
}
//「クリア」ボタンでtextBoxの内容を消去
private void buttonResultClear_Click(object sender, EventArgs e)
{
textBoxResult.ResetText();
}
正規表現による置換
問題はここからです。
正規表現の置換には「RegularExpressions」を利用しました。
LINQを使うとか色々方法があるっぽいのですが、よく検索で出てくるこちらを試しました。
まずは Form1.cs の先頭に下記の1行を追加します。
using System.Text.RegularExpressions;
次にボタンクリックイベントでの置換の内容です。
//「パスから余計な情報を除去」ボタンの挙動
private void buttonReplace_Click(object sender, EventArgs e)
{
//一時的に文字列を扱うための変数
string st = textBoxInput.Text;//先頭の余分な情報を置換でカット
st = Regex.Replace(
st,
"^[^/]+'", //行頭から連続するスラッシュ以外の文字列
"", //空に置換(文末以外の空白行も削除される)
RegexOptions.Multiline); //改行記号も1つの文末として処理する
//末尾の余分な情報を置換でカット
st = Regex.Replace(
st,
"[.][^/]+'", //行末まで連続した.で始まるスラッシュ以外の文字列
"", //空に置換
RegexOptions.Multiline); //改行記号も1つの文末として処理する
//文末の空行を削除
st = Regex.Replace(
st,
"[\r\n]*$", //文末で0回以上繰り返す改行
"\r\n"); //改行1つだけに置換textBoxResult.AppendText(st);
}
文字列を改行部分で分割して配列に代入し、for文で1行ずつ置換していく‥といったような面倒なことをしなくても、改行を含んだ文字列に対して「Multiline(複数行)」オプションを指定してそのまま置換するだけでいけました。
3度の 置換によって
・行頭の情報の除去(文末以外の空行も同時に除去)
・行末の情報の除去
・文末に空行が複数ある場合に1行だけにする
‥を行っています。
正規表現のルールはこちらの記事を参考にさせて頂きました。
正規表現の基本: .NET Tips: C#, VB.NET
正規表現の厄介なところは、文字列の指定が記号だらけになって内容も結果も分かり辛く、なぜ置換がうまくいかないかも判断し辛いところですね。。
ただ、上記の置換のコード先頭でブレークポイントを設定して、変数 st をウォッチリストに追加して、1つの処理を順に進めて(ステップ実行して)いくと、変数 st の内容がどう変わっていくかをチェックすることができます。
(このあたりは1つ前に投稿した記事で紹介した書籍でも丁寧に解説されています)
例えばピリオド(\n以外の任意の文字列)を使って .+ で置換するとキャリッジリターンまで置換対象になって改行文字の「\r\n」が「\n」だけになってしまい、テキスト出力すると謎の連続改行が入ってしまうといったことが起こり、原因究明にしばらく手間取りましたが、ステップ実行して変数の中身を調べると原因がすぐに分かりました。
行末の情報を除去するための対象文字列に、行末を表す $ を使用していないのはそのためで、$ を使うと「.+」によって「\r」が削られてしまいました。
ここでは Path.GetFileName のようなメソッドで親フォルダパスを取得する方法は使わずあくまで置換でやりたかったので対処療法な感じになりましたが、どこかのタイミングで本職の方にちゃんとした指定方法を教えてもらおうと思っています。。
▼ウォッチリストの変数 st の状態。最初は空っぽ(null)
▼ステップ実行でテキストボックスの内容が代入された様子("\r\n"が改行を表す部分)
▼行頭の余計な情報が除去された(文末以外の空行も一緒に除去)
▼行末の余計な情報が除去された
ちなみに、置換結果の文字列はテキストボックスの中へ書き換えるのではなく「AppendText」を使って追記する形にしています。
すでに別の置換結果の文字列が存在する場合に新しい行へ追記したいと思ったからです。
そのためには、置換の最後には必ず1回分の改行を行って終わるように記述しています。
プロジェクトデータと実行ファイル
Googleドライブにアップしました。
MyTool_UE4PassReplace.zip - Google ドライブ
binフォルダの中にexeが入っています。
2017.11.18追記
うわー今見るとすごい遠回りな置換をしてる。。https://t.co/Mp8QY8RKmX
— moko (@moko_03_25) 2017年11月11日
"^.*?'(?<pass>[^.]+)"
— moko (@moko_03_25) 2017年11月11日
m.Groups["pass"].Value;
これで取り出すのが手っ取り早そう。
アセット名を含まないパスとアセット名を別々に取り出したい時は
— moko (@moko_03_25) 2017年11月11日
".*?'(?<pass>/(.*?/)+)(?<name>[^.]+)"
m.Groups["pass"].Value;
m.Groups["name"].Value;
これで抜き出せた。。
UE4のアセットのリファレンス情報から命名規則チェックツールを作りたくて正規表現のルールを確認し直し中。。こちらの動画がとても分かりやすかった。https://t.co/uXsaZ8m15X
— moko (@moko_03_25) 2017年11月11日
こちらにもお世話になりました。https://t.co/BT7F7WsVOV
— moko (@moko_03_25) 2017年11月11日