スキルシート用WitchTreeプラグイン
久々WitchTree
スキルシートのスキルツリーを作るのにWitchTreeを使っていたのだが、いちいちそこからSQLを書くのが面倒くさかったのでプラグインを作ってしまった。
で、参照設定がめんどくさくね?
これVisual Studio無いとしんどくね?
とか、色々ぶー垂れながらダサいのを作った。
謎のSkyleGenerator
C# 2.0専用。
using System; using System.Collections.Generic; using System.Text; using com.ast8.tm.plugin; using com.ast8.tm.plugin.exception; using System.Reflection; using System.IO; namespace com.cuvel.skyle { public class SkillInsertGenerator : AbstractTMPlugin { #region Assembly Attribute Accessors public string AssemblyTitle { get { // Get all Title attributes on this assembly object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false); // If there is at least one Title attribute if (attributes.Length > 0) { // Select the first one AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0]; // If it is not an empty string, return it if (titleAttribute.Title != "") return titleAttribute.Title; } // If there was no Title attribute, or if the Title attribute was the empty string, return the .exe name return System.IO.Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase); } } public string AssemblyVersion { get { return Assembly.GetExecutingAssembly().GetName().Version.ToString(); } } public string AssemblyDescription { get { // Get all Description attributes on this assembly object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false); // If there aren't any Description attributes, return an empty string if (attributes.Length == 0) return ""; // If there is a Description attribute, return its value return ((AssemblyDescriptionAttribute)attributes[0]).Description; } } public string AssemblyProduct { get { // Get all Product attributes on this assembly object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false); // If there aren't any Product attributes, return an empty string if (attributes.Length == 0) return ""; // If there is a Product attribute, return its value return ((AssemblyProductAttribute)attributes[0]).Product; } } public string AssemblyCopyright { get { // Get all Copyright attributes on this assembly object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false); // If there aren't any Copyright attributes, return an empty string if (attributes.Length == 0) return ""; // If there is a Copyright attribute, return its value return ((AssemblyCopyrightAttribute)attributes[0]).Copyright; } } public string AssemblyCompany { get { // Get all Company attributes on this assembly object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false); // If there aren't any Company attributes, return an empty string if (attributes.Length == 0) return ""; // If there is a Company attribute, return its value return ((AssemblyCompanyAttribute)attributes[0]).Company; } } #endregion #region override プロパティ /// <summary> /// 保存イベント処理が実装されていることをシステムに通知する /// </summary> public override bool IsSaveRequestedImpl { get { return true; } } #endregion #region override メソッド /// <summary> /// プラグインの作者を返す /// </summary> /// <returns>プラグインの作者。この情報はアセンブリのCompanyと等価</returns> public override string getAuthor() { return this.AssemblyCompany; } /// <summary> /// プラグインの名称を返す /// </summary> /// <returns>プラグインの名称。この情報はアセンブリのタイトルと等価</returns> public override string getName() { return this.AssemblyTitle; } /// <summary> /// プラグインのバージョンを返す /// </summary> /// <returns>プラグインのバージョン。この情報はアセンブリのVersionと等価</returns> public override string getVersion() { return this.AssemblyVersion; } /// <summary> /// プラグインに対する補足説明を返す /// </summary> /// <returns>プラグインに対する補足説明。この情報はアセンブリのDescription等価</returns> public override string getComment() { return this.AssemblyDescription; } /// <summary> /// 保存メニューに表示される文字列を返す /// </summary> /// <returns>XML形式で保存(&X)</returns> public override string getSaveMenuText() { return "Generate Skyle Insert SQL(&I)"; } #endregion #region overrideイベントハンドラ public override void doSaveRequested(object sender, com.ast8.tm.plugin.SaveRequestedEventArgs e) { int skillId = 1; List<SkillDataStructure> nodes = getAllElement(e.Nodes, 0, ref skillId); StringBuilder buf = new StringBuilder(4096); for (int i = 0; i < nodes.Count; i++) { buf.Append("insert into sky_skill (skill_id, skill_name, parent_skill_id, note)"); buf.Append(" values ("); buf.Append(nodes[i].id); buf.Append(",'"); buf.Append(sqlChars(nodes[i].name)); buf.Append("',"); buf.Append(nodes[i].parentid); buf.Append(",'"); buf.Append(sqlChars(nodes[i].note)); buf.Append("');"); buf.Append(e.LineCode); } Stream fs = null; TextWriter fsw = null; Encoding enc = null; // open! try { fs = File.Open(e.Path, FileMode.Create, FileAccess.Write, FileShare.Read); // エンコード指定。指定がない場合はDefault出力 enc = (e.Encoding == null) ? Encoding.Default : e.Encoding; fsw = new StreamWriter(fs, enc); fsw.Write(buf.ToString()); } catch (IOException exIO) { // FileNotFound, DirectoryNotFound, PathTooLong throw new PluginIOException(getSaveErrorMessage(exIO.Message), exIO); } catch (ArgumentException exA) { // Argument, ArgumentNull throw new PluginIOException(getSaveErrorMessage(exA.Message), exA); } catch (UnauthorizedAccessException exUA) { throw new PluginIOException(getSaveErrorMessage(exUA.Message), exUA); } catch (NotSupportedException exNS) { throw new PluginIOException(getSaveErrorMessage(exNS.Message), exNS); } finally { try { if (fsw != null) fsw.Close(); } catch (Exception) { // ほんとはあまりよくないが仕方ないので無視 } try { if (fs != null) fs.Close(); } catch (Exception) { // ほんとはあまりよくないが仕方ないので無視 } } } #endregion #region protected method protected List<SkillDataStructure> getAllElement(System.Collections.IList nodes, int parentId, ref int skillId) { if (nodes == null) return null; List<SkillDataStructure> childs = new List<SkillDataStructure>(nodes.Count); for (int i = 0; i < nodes.Count; i++) { com.ast8.tm.data.TMNode node = nodes[i] as com.ast8.tm.data.TMNode; if (node != null) { SkillDataStructure st = new SkillDataStructure(skillId++, parentId, node.Text, node.CardText); childs.Add(st); List<SkillDataStructure> childNodes = getAllElement(node.Nodes, st.parentid, ref skillId); if (childNodes != null) { childs.AddRange(childNodes); } } } return childs; } #endregion #region private 系 private const string ERROR_MESSAGE_SAVE = "保存に失敗しました。\n{0}"; private static string sqlChars(string source) { return getUnescapeString(source).Replace("'", "''"); } /// <summary> /// 保存時のエラーメッセージを返す /// </summary> /// <param name="extParam">エラーメッセージに追加するメッセージ</param> /// <returns>保存時のエラーメッセージ</returns> private string getSaveErrorMessage(string extParam) { return string.Format(ERROR_MESSAGE_SAVE, extParam); } private static string getEscapeString(string source) { char[] c = source.ToCharArray(); int j = 0, cLength = c.Length; char[] ret = new char[cLength]; for (int i = 0; i < cLength; i++) { if (c[i] == '\\') { i++; // 最後が\\で終わっている if (i == cLength) throw new System.ArgumentException("invalid escape sequence"); switch (c[i]) { case 'r': ret[j++] = '\r'; break; case 'n': ret[j++] = '\n'; break; case 't': ret[j++] = '\t'; break; case '\\': ret[j++] = '\\'; break; default: // 謎のエスケープシーケンス ret[j++] = '\\'; ret[j++] = c[i]; break; } } else { ret[j++] = c[i]; } } return new string(ret, 0, j); } /// <summary> /// getEscapeString関数の逆関数 /// </summary> /// <remarks> /// でも実は逆じゃない。 /// 本当はこっちがgetEscapeString、getEscapeStringはgetFormatStringがいいのかもしれん・・。 /// </remarks> /// <param name="source">元文字列</param> /// <returns>生成後文字列</returns> private static string getUnescapeString(string source) { char[] c = source.ToCharArray(); int j = 0, cLength = c.Length; char[] ret = new char[cLength * 2]; for (int i = 0; i < cLength; i++) { switch (c[i]) { case '\\': ret[j++] = '\\'; ret[j++] = '\\'; break; case '\n': ret[j++] = '\\'; ret[j++] = 'n'; break; case '\r': ret[j++] = '\\'; ret[j++] = 'r'; break; case '\t': ret[j++] = '\\'; ret[j++] = 't'; break; default: ret[j++] = c[i]; break; } } return new string(ret, 0, j); } #endregion } public interface ISkillDataId { int id { get; set; } int parentid { get; set; } } public interface ISkillDataText { string name { get; set; } string note { get; set; } } public class SkillDataStructure : ISkillDataId, ISkillDataText { #region private field private int m_id; private int m_parentid; private string m_name; private string m_note; #endregion #region コンストラクタ public SkillDataStructure() { } public SkillDataStructure(int id, int parentid) : this(id, parentid, string.Empty, string.Empty) { } public SkillDataStructure(string name, string note) : this(0, 0, name, note) { } public SkillDataStructure(ISkillDataId id_data) : this(id_data.id, id_data.parentid, string.Empty, string.Empty) { } public SkillDataStructure(ISkillDataText text_data) : this(0, 0, text_data.name, text_data.note) { } public SkillDataStructure(ISkillDataId id_data, ISkillDataText text_data) : this(id_data.id, id_data.parentid, text_data.name, text_data.note) { } public SkillDataStructure(int id, int parentid, string name, string note) { this.id = id; this.parentid = parentid; this.name = name; this.note = note; } #endregion #region ISkillDataId メンバ public int id { get { return this.m_id; } set { this.m_id = value; } } public int parentid { get { return this.m_parentid; } set { this.m_parentid = value; } } #endregion #region ISkillDataText メンバ public string name { get { return this.m_name; } set { this.m_name = value; } } public string note { get { return this.m_note; } set { this.m_note = value; } } #endregion } }
getEscapeStringとか、使ってないメソッドも混じっているのはご愛嬌。
Skyleとかいう謎の名前もご愛嬌。スペルミスじゃないよ、念のため。システムの仮名だが、Skypeに怒られそうなので変更すると思う(笑)
Genericの特性を全然覚えてないので、なんかまずってるかもしれん。WitchTreeのインターフェースを思い出しつつ、テキスト出力エンジン辺りから色々パクりつつ、30分くらいでやっつけたやつなので、知っている人に教えてもらえると嬉しかったりする。
感想
やっぱマイツールはいいなぁ。自由自在だ。
ただ最近使ってて要素の数すらわからないのに苛々しているから、WitchTree本体に手を入れたくてしゃあないのだが、著作権の問題で手がつけられん。
せめてポップアップメニューの拡張とプラグインメニュー(またはツールメニュー)くらい作らないと、使っている俺がしんどい。
日本帰ったら交渉してみよう。
追記
すんません、DokuWiki記法で書いてたので更新しました。