夕暮ログ

C#やJavascript、最近はAndroidなんかも好きなtinqのブログ。「夕暮れログ」

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

C#で日本語タイピングができるライブラリを公開

とりあえず、バグらしいバグもないので公開します。

利用する方がいらっしゃるかわかりませんが、一応ライセンスはMITとしておきます。 隅っこに私の名前やサイト名を表示していただければ無制限に利用が可能です。

極力不具合がないよう確認していますが、正常に動作しない場合、バグがある場合はご報告ください。

<使い方>

  1. 普通のクラスのようにnewでインスタンスを作ります。複数のインスタンスを作ることも可能です。
  2. SetQuestionで問題文を設定します。
  3. Input関数で半角の入力を渡します。日本語の入力には対応していません。大文字、小文字は区別されません。
  4. Input関数からはInputResult列挙体で入力の結果が返ります。
    None無効値です。これが戻ることはありません。
    Good正しい入力です。入力は受け付けられました。
    Bad間違った入力です。
    CharEnd日本語の1音節文の入力が終わりました。「あ」「ちゃ」「っしゃ」などもこれが帰ります。1文字ではないので注意してください。取得する必要がある場合、Locationで位置をいろいろやればいいと思われます。
    CharEndNCharEndと同様ですが、「ん」の場合で、次に1度だけNを入力できます。
    CharEndNNCharEndNの後に、[N]が入力された場合です。CharEndNをCharEndと同じく処理したなら、これは無視するのがいいでしょう。
    StrEnd問題文の入力が完了しました。もちろん、CharEndよりも優先して戻ります。
  5. 入力候補や、ユーザーの入力した文字列などを取得するには、公開されているプロパティを利用します。

その他

問題文として使えるのは、以下です。
  • ひらがな
  • ヴ(ひらがなが存在しない音のため)
  • 英数字(全角、半角ともに対応)
  • 記号  []()<>,.!?-%+-=*:;/_"&'および、その全角
使える文字は、定義ファイル及び、変換テーブルを操作することで簡単に変更できます。
サポートされない文字列があった場合、NotSupportedExceptionが発生します。(これは普通のプログラムが発生させるべきものなのか?)
現在、候補表示の変更は対応していません。


詳しい仕様及びソースファイルは日本語タイピングクラスの使い方をご覧ください。 2011/7/20更新 Ver1.1

ソースコード

ソース単体では動きません。キー定義ファイルが必要です。
また、このソースは若干古い可能性があります。
最新の情報及びキー定義ファイルはこちらからダウンロードできます。
/*
以下をusingする必要があります。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
*/class NotSetQuestionException : Exception{public NotSetQuestionException() : base("問題が設定されていません。"){}}
enum InputResult
{
    None,       //無効
    Good,       //正しい入力
    Bad,        //間違った入力
    CharEnd,    //1音節を入力し終えた
    CharEndN,   //1音節を入力し終えた。次にNの入力が許容される
    CharEndNN,  //前回CharEndNを返した。次にNがきたため、入力が許容された。次はNを入力することは許されない
    StrEnd      //文字列の入力が完了した。
}
class Typing
{
    #region static
    /// 
    /// 問題文が正しいかをチェックします。
    /// 
    /// 検証する問題文
    /// 正しい問題文の場合はnull。間違っている場合は使えない文字。nullや空文字の場合はから文字が帰る
    public static string QuestionCheck(string text)
    {
        if (string.IsNullOrEmpty(text)) return "";
        StringBuilder sb = new StringBuilder();
        foreach (var item in text)
        {
            if (!Keys.Keys.Contains(item.ToString()))
            {
                sb.Append(item);
            }
        }
        return sb.Length == 0 ? null : sb.ToString();
    }
    /// 
    /// キー一覧を読み込む。
    /// 
    static void LoadKeyList()
    {
        XDocument xdoc = XDocument.Load(@"../../Keys.xml");
        Keys.Clear();
        foreach (var x in xdoc.Element("Keys").Elements("Key"))
        {
            if (x.Attribute("Roma").Value == ",") Keys.Add(x.Attribute("Char").Value, new[] { "," });
            else
                Keys.Add(x.Attribute("Char").Value, x.Attribute("Roma").Value.Split(new[] { ',' }));
        }
    }
    static Dictionary Keys;  //ローマ字入力対応
    #endregion

    //private
    string question;    //問題文
    int location;       //現在位置(日本語)
    string userInput;   //実際にユーザーに入力されたデータ
    int charLocation;   //音節入力の何文字目か
    string charInput;   //音節入力でユーザーが入力した値

    bool NFlag;     //Nの入力を許容する

    //public
    public Typing()
    {
        if (Keys == null)
        {
            Keys = new Dictionary();
            LoadKeyList();
        }
        charInput = "";
        charLocation = 0;
    }

    /// 
    /// 問題文
    /// 
    public string QuestionText { get { return question; } }
    /// 
    /// ユーザーが入力したアルファベット
    /// 
    public string UserInput { get { return userInput + charInput; } }
    /// 
    /// ユーザーが入力し終えた日本語
    /// 
    public string AlreadyInput { get { return question.Substring(0, location); } }
    /// 
    /// ユーザーがまだ入力していない日本語
    /// 
    public string RemainInput { get { return question.Substring(location); } }
    /// 
    /// 日本語の入力位置
    /// 
    public int InputPosition { get { return location; } }
    /// 
    /// アルファベットの候補。UserInputをつなげれば全体になる。
    /// 
    public string RomaKoho
    {
        get
        {
            if (question == null) throw new NotSetQuestionException();
            int start = 1;
            StringBuilder sb = new StringBuilder();
            var dic = GetKohoList(location);
            IEnumerable temp;
            if (dic.ContainsKey(3) && (temp = (from x in dic[3] where x.StartsWith(charInput) select x)).Count() != 0)
            {
                sb.Append(temp.First().Remove(0, charLocation));
                start += 2;
            }
            else if (dic.ContainsKey(2) && (temp = (from x in dic[2] where x.StartsWith(charInput) select x)).Count() != 0)
            {
                sb.Append(temp.First().Remove(0, charLocation));
                start++;
            }
            else
            {
                sb.Append((from x in dic[1] where x.StartsWith(charInput) select x).First().Remove(0, charLocation));
            }

            for (int i = location + start; i < question.Length; i++)
            {
                if (question[i] == ' ' || question[i] == ' ') i++;
                dic = GetKohoList(i);
                if (dic.ContainsKey(3)) { sb.Append(dic[3][0]); i += 2; }
                else if (dic.ContainsKey(2)) { sb.Append(dic[2][0]); i++; }
                else sb.Append(dic[1][0]);
            }
            return sb.ToString();
        }
    }


    /// 
    /// 現在の入力を破棄して新しい問題文を設定する
    /// 
    /// 設定する問題文
    public void SetQuestion(string qtext)
    {
        string OutStr;
        if( (OutStr = QuestionCheck(qtext)) != null )
        {
            throw new NotSupportedException("サポートされない文字が問題文に含まれています。「" + OutStr + "」");
        }
        question = qtext.Trim();    //最初と最後の空白を取り除く
        location = 0;
        userInput = "";
        charLocation = 0;
        charInput = "";
        NFlag = false;
    }
    /// 
    /// 入力を行う
    /// 
    /// 入力された半角英数。大文字小文字は区別されない
    /// 入力結果
    public InputResult Input(char ch)
    {
        if (question == null) throw new NotSetQuestionException();
        ch = Char.ToUpper(ch);
        if (NFlag && ch == 'N') //N許容
        {
            userInput += 'N';
            NFlag = false;
            return InputResult.CharEndNN;
        }

        var koho = GetKohoList(location);
        foreach (var item in koho)
        {
            foreach (var k in item.Value)
            {
                if (k.StartsWith(charInput))    //現在入力と適しているもののみ
                {
                    if (k[charLocation] == ch)  //入力が適切
                    {
                        charInput += ch;
                        charLocation++;
                        if (charInput == k) //入力が完全一致した
                        {
                            NFlag = false;
                            if (k == "N") NFlag = true;    //N一文字ならNFlagを立てる
                            location += item.Key;
                            userInput += k; //入力文字列に追加
                            charInput = "";
                            charLocation = 0;

                            if (location == question.Length) { return InputResult.StrEnd; }                //入力完了
                            while (question[location] == ' ' || question[location] == ' ') location++;    //空白のスキップ
                            return NFlag ? InputResult.CharEndN : InputResult.CharEnd;
                        }
                        NFlag = false;
                        return InputResult.Good;
                    }
                }
            }
        }
        return InputResult.Bad;
    }

    /// 
    /// 入力候補の一覧を取得する
    /// 
    /// 入力候補を取得する位置
    /// 入力候補一覧。キーが1の配列にはひらがな1文字分の入力候補が、2にはひらがな二文字分(っか、ちゅ、など)3には3文字(っちゃ、など)
    public Dictionary GetKohoList(int loc)
    {
        if (question == null) throw new NotSetQuestionException();
        var dic = new Dictionary();

        if (question[loc] == 'ん')  //んの例外処理
        {
            if (question.Length == (loc + 1) || Regex.IsMatch(question[loc + 1].ToString(), "[あいうえおなにぬねのん]"))
            {
                dic.Add(1, new[] { "NN", "XN" });
                if (question[loc + 1] == 'ん') dic.Add(2, new string[] { "NXN" });
            }
            else
            {
                dic.Add(1, new[] { "N", "XN" });    //受け取る側が入力処理の場合、確認してNFlagを立てること
            }
        }
        else
        {
            //1文字候補リストを取得
            dic.Add(1, Keys[question[loc].ToString()].ToArray());   //現在の入力を元にフィルタをしていない
            if (question.Length > (loc + 1) && Keys.ContainsKey(question[loc].ToString() + question[loc + 1]))
            {
                dic.Add(2, Keys[question[loc].ToString() + question[loc + 1]]);
            }
            //っの例外処理
            if (question[loc] == 'っ')
            {
                //文字列の最後ではなく、次の文字が重ねられない文字ではない
                if (question.Length > (loc + 1) && !Regex.IsMatch(question[loc + 1].ToString(), "[あいうえおなにぬねの]"))
                {
                    if (question[loc + 1] == 'ん') { dic.Add(2, new string[] { "XXN" }); }
                    else
                    {
                        var next = GetKohoList(loc + 1);
                        dic.Add(2, (from x in next[1] select x[0] + x).ToArray());
                        if (next.ContainsKey(2)) dic.Add(3, (from x in next[2] select x[0] + x).ToArray());
                    }
                }
            }
        }
        if (dic.ContainsKey(1) && dic[1].Length == 0) dic.Remove(1);
        if (dic.ContainsKey(2) && dic[2].Length == 0) dic.Remove(2);
        if (dic.ContainsKey(3) && dic[3].Length == 0) dic.Remove(3);
        return dic;
    }
}
関連記事

コメント

ここをクリックしてコメントを投稿

非公開コメント

トラックバック

http://tinqwill.blog59.fc2.com/tb.php/57-36cfe024

« next  ホーム  prev »

プロフィール

tinq tinq(もしくはTinqWill)

Sky  For   Every 改装予定

プログラミングお勉強中の高校生。月一くらいは更新したい

最新記事

カテゴリ

月別アーカイブ

検索フォーム

最新コメント

リンク

最新トラックバック

FC2Ad

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。