Google Chart を使った数式ツールを作ってみた(3)

執筆日時:

f:id:daruyanagi:20130115205946p:plain

ネスト(入れ子)が認識できない。あと、[Shift]+[Tab]キーで逆向きに移動したいけれど、これがなかなかめんどくさい。{} だけじゃなくて () にも対応させたい、なんて考えだすと破たんするのが目に見えてるし。

というわけで、解決策は正規表現か、構文解析かって感じなんだけど。正規表現も大変だし、しかも限界が見えているので、ここは頑張って簡単な構文解析をするべきかと思っている。

Google Chart を使った数式ツールを作ってみた(2) - だるろぐ

構文解析というか、対応する { と } をペアにして、その出現位置をメモる方向で考えてみた。括弧の種類が増えていけば破たんするけれど、とりあえず最初は動けばいいや。アルゴリズムは、

みたいな感じ。大雑把に言えば、{ は前から詰めて、} は後ろから詰める、と。

たとえば、

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
\ f r a c { \ f r a c { } { } }

だったら、

{ の出現位置 (対応する)} の出現位置
5 15
11 12
13 14

こういうリストを得るのがゴールになるかな。もしかしたら再帰でイケるのかな? と思ったけど、よくわかんなかったので素直に for を使って書くことにした。あと、? には text.Length - 1 を突っ込んでおいた。int? にして null をプレースホルダにしてもよかったのだけれど、ちょっとめんどくさいかな、と思って。

private Dictionary<int, int> BuidBracketIndo(string text)
{
var result = new Dictionary<int, int>();
var last_index = text.Length - 1;

for (int i = 0; i <= last_index; i++) { switch (text[i]) { case '{': result.Add(i, last_index); break; case '}': try { var item = result.Last(_ => _.Value == last_index); result[item.Key] = i; } catch { } break; } }

return result; }

これを TextChanged イベントで呼んで括弧の対応位置情報を毎回リフレッシュし、[Tab]キーの入力時に利用する。

// 「\frac{}{}」(分数の TeX 表現)を挿入する。
// 挿入後は[Tab]キーの押し下げを一度行い、
// 最初の {} の間にキャレットを移動させる
private void Button_Click_4(object sender, RoutedEventArgs e)
{
// 途中でFormulaText.SelectionStart == 0 になってしまう(?)ので
// キャレット位置を保存しておく
var start = FormulaText.SelectionStart;
FormulaText.Text = FormulaText.Text
.Remove(start, FormulaText.SelectionLength)
.Insert(start, @"\frac{}{}");
FormulaText.Focus();
FormulaText.SelectionStart = start;

// [Tab]キーの押し下げをエミュレート
var tab_key_down_event_args = new KeyEventArgs(
Keyboard.PrimaryDevice,
PresentationSource.FromVisual(FormulaText),
(int)DateTime.Now.Ticks,
Key.Tab
);
tab_key_down_event_args.RoutedEvent = Keyboard.PreviewKeyDownEvent;
FormulaText_PreviewKeyDown(sender, tab_key_down_event_args);
}

// 括弧の位置情報: TextChanged イベントでリフレッシュされる
private Dictionary<int, int> brackets = null;

// キーの押し下げイベントを処理
private void FormulaText_PreviewKeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Tab:
try
{
/* [Tab]キーで次のブラケット内へ進む */
if ((Keyboard.Modifiers & ModifierKeys.Shift) != ModifierKeys.Shift)
{
var b = brackets.First(_ => FormulaText.SelectionStart < _.Key);
FormulaText.SelectionStart = b.Key + 1;
FormulaText.SelectionLength = b.Value - b.Key - 1;
}
/* [Shift]+[Tab]キーで前のブラケット内へ戻る */
else
{
var b = brackets.Last(_ => _.Key < FormulaText.SelectionStart - 1);
FormulaText.SelectionStart = b.Key + 1;
FormulaText.SelectionLength = b.Value - b.Key - 1;
}
}
catch
{
/* 不正な操作を行ったらビープ音を鳴らす */
System.Media.SystemSounds.Beep.Play();
}
finally
{
e.Handled = true; // イベントを握りつぶす!
}
break;

case Key.Escape:
/* 選択状態を解除する */
FormulaText.SelectionLength = 0;
break;
}
}

ついでに[Esc]キーで選択を解除できるようにしておいた。手元ではちゃんと動いている気がするけど、もう少し様子を見てから、アイコンなんかをつけて公開しようかと思う。

追記

ご指摘感謝! ブログの方はそのままにしておくので適当に読み直してください。List<KeyValuePair<int,int>> に書き換えればいいんですよね?