スキルシート用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記法で書いてたので更新しました。