スタックベースの MathML 入力システム mathml-lifoinput

はじめに

ブログに MathML+MathJax で数式を表示することにした. しかし,MathML を手打ちするのは大変だったので,入力システムを作った. https://www.downcastingsoft.net/7e5/mathml-lifoinput/ からアクセスできる.

入力の様子

Git リポジトリは 1995hnagamin/mathml-lifoinput にある. XML を帰りがけ順で記述する入力システムというものは,わざわざ自作しなくても既に誰かがやっていそうだが, React の勉強もかねて自分で作ってみることにした.

たとえば, x = - b ± b 2 - 4 a c 2 a という数式を書きたい場合は,次のように入力する.

x
=
-
b
±
b
2
msup
-
4
a
c
\packit 3
mrow 3
msqrt
mrow 4
2
a
\packit 2
mfrac
mrow 3
こうすると,次のような XML が生成される.
<mrow>
  <mi>x</mi>
  <mo>=</mo>
  <mfrac>
    <mrow>
      <mo>-</mo>
      <mi>b</mi>
      <mo>&#xB1;</mo>
      <msqrt>
        <mrow>
          <msup>
            <mi>b</mi>
            <mn>2</mn>
          </msup>
          <mo>-</mo>
          <mrow>
            <mn>4</mn>
            <mo>&#x2062;</mo>
            <mi>a</mi>
            <mo>&#x2062;</mo>
            <mi>c</mi>
          </mrow>
        </mrow>
      </msqrt>
    </mrow>
    <mrow>
      <mn>2</mn>
      <mo>&#x2062;</mo>
      <mi>a</mi>
    </mrow>
  </mfrac>
</mrow>

機能

トークン要素の入力

トークン要素 (token elements) は,数や識別子など数式を構成する基本要素を表現するための XML 要素である. MathML 3 では次の6つのトークン要素が定義されている(§3.2).

  • 識別子を表現する <mi> 要素
  • 数を表現する <mn> 要素
  • 演算子,関係記号,括弧類,区切り記号,アクセント記号などを表現する <mo> 要素
  • テキストを表現する <mtext> 要素
  • (計算機科学において)文字列リテラルを表現する <ms> 要素
  • 空白を表現する <mspace> 要素
mathml-lifoinput は,現在のところ <mi>, <mn>, <mo> 要素の入力に対応している.

<mi> 要素を作成するには,mi "{入力したい識別子}" という形式で入力を行う. たとえば, sin という識別子を入力するには mi "sin" と入力する. 識別子がアルファベット1文字の場合は,mi ", " の部分を省略できる.

mi "sin"
mi "x"
x             // これは,2行目と同じ
こうすると,<mi>sin</mi>, <mi>x</mi>, <mi>x</mi> がスタックにプッシュされる.

<mn> 要素を作成するには,mn "{入力したい数値表現}" という形式で入力を行う. たとえば, 123を入力するには mn "123" と入力する. 一部の数値表現に対しては mn ", " の部分を省略できる.

mn "0xFFEF"
2
0.123
2.1e10
こうすると, <mn>0xFFEF</mn>, <mn>2</mn>, <mn>0.123</mn>, <mn>2.1e10</mn> がスタックにプッシュされる.

<mo> 要素を作成するには,mo "{入力したい演算子}" という形式で入力を行う. たとえば, +を入力するには mo "+" と入力する. 一部の演算子に対しては mo ", " の部分を省略できる (App.tsx も参照).

mo "+"
-
&PlusMinus;
<
こうすると, <mo>+</mo>, <mo>-</mo>, <mo>&#xB1;</mo>, <mo>&lt;</mo> がスタックにプッシュされる.

<mrow> 要素の入力

<mrow> 要素は,いくつかの要素をグループ化してひとつにまとめるための要素である. <mrow> 要素を作成するには,mrow {子要素の数}という形式で入力を行う. たとえば,mrow 3 と入力すると,スタックから3つの要素がポップされ, この3つの要素を子要素としてもつ mrow 要素がプッシュされる. 子要素はプッシュされた順に並べられる.

a
+
b
mrow 3
こうすると,
<mrow>
  <mi>a</mi>
  <mo>+</mo>
  <mi>b</mi>
</mrow>
がスタックにプッシュされる.

分数の入力

分数を入力するには <mfrac> 要素を用いる. mfrac と入力すると,スタックから2つの要素がポップされ, これらを子要素としてもつ <mfrac> 要素がプッシュされる.

a
b
mfrac
こうすると,
<mfrac>
  <mi>a</mi>
  <mi>b</mi>
</mfrac>
がスタックにプッシュされる.

属性の入力

スタックの先頭にある XML 要素に属性を追加するには, @{属性名}="{値}" という形式で入力を行う. たとえば,

A
@mathvariant="fraktur"
こうすると, <mi mathvariant="fraktur">A</mi> (A) がスタックにプッシュされる.

マクロ

入力を楽にするためにいくつかの特殊な命令を定義している. たとえば,いくつかの変数の積を入力したいときは,

4
&InvisibleTimes;
a
&InvisibleTimes;
c
mrow 5
のように入力すればよいが,これは少し面倒である. そこで, \packit 命令が定義されている. \packit {要素の数} という形式で入力を行うと, 与えられた数の要素をスタックからポップし, &InvisibleTimes; 演算子ではさんだ上でこれらをまとめた mrow 要素がプッシュされる.
4
a
c
\packit 3

今後の課題

内容 MathML に対応する

Content MathML に対応したい. そのためには2つのアプローチがあると思う. ひとつは自明なサポート,すなわち, content MathML を単に木構造と見て,対応する命令(apply, eq など)を追加する方法. この場合は命令の仕様が決まっているので,スタック上でどのように表示するかだけを検討すればよい. MathML の構造を細かく制御できる点が mathml-lifoinput の特徴であるからこれらも実装する必要があると思う.

もうひとつは,presentation MathML と content MathML の内容を同期しながら編集する方法. この場合は特別なモードとして実装した方がよいと思う.

複数命令入力に対応する

今のところ各行で1命令だけを受理する仕組みになっているので,たまに不便と感じることがある.

x ; =
-;b;mrow 2; &PlusMinus; ; b;2;msup; -; 4;a;c;\packit 3; mrow 3; msqrt; mrow 3
2;a;\packit 2
mfrac
mrow 3
のように,1行に複数の命令を入力できるようにしたい.

React のパフォーマンスチューニング

今のところあまり困っていないが,React の勉強も兼ねてパフォーマンスチューニングの方法を学んでみたい. スタックの部分に react-window を使うとよさそう?

更新履歴

  • 2021-07-14: 公開
  • 2023-04-02: Document ID を追加

Permanent ID of this document: 95d1b628a42e0c7fbff8eb12f3eb2a75