« WEBクライアントでJavaを動かす:DOMアクセスする | トップページ | ◆金属を描き分ける »

WEB上で動的MathML生成:クライアントで構文解析を実行

ごくごく単純なレベルのMathML記述生成をWEB上で実施できるようにしてみました。 HTML上に書いた簡素な式記述からMathML記述を作り表示します。
MathMLはHTML上で数式を表現する機能で次期HTMLであるHTML5の目玉機能の一つ です。しかし、その為の入力記述はとても人が直接書くことができるものでは ありません。そこで、人が入力可能な記述からMathML記述をWEBクライアント側 で動的に作成してみることを試してみたものです。
MathMLはfirefoxで動作します。firefox4では デフォルトで動作します。firefox3の場合about:configにアクセスし、html5-enable をtrueにしてください。HTML5,MathMLを使ってみる参照


MathMLgenerator





MathML記述は直接書いておらず、[MathMLに変換]ボタンが押されると 簡素な記述からMathMLを生成し、<p>のinnerHTMLにセットしています。 MathML非対応のブラウザでも変換結果を確認するためのalertも入れました。
自分で記述を入れて変換させることも可能です。

 コード

次のように実装されています。青文字の部分が構文定義の拡張BNF記述です。

<applet id="app"  archive="../../../java/symphonie.jar"
        code="otsu.symphonie.SymphonieApp"
        width="1" height="1"></applet>
<hr>
<style type="text/css">math{ display:block }</style>
<p id=m1><math><mfrac><mn>MathML</mn><mn>generator</mn></mfrac><math></p>
<script type="text/javascript">
var syntax=[
 '//#omit expression term-list term op-term math-list'
,'//#omit mrow_0s function paren func integ vect'
,'@         ::= ?"[ \\t\\n\\f\\r\\u3000]+"'
,'@         ::= ?"//.*$"'
,'@mn       ::= ?"[0-9]+\\.[0-9]+" | ?"[0-9]+" '
,'@mi       ::= ?"[a-zA-Z][0-9a-zA-Z]*" | "&pi;"/"π" | "&ExponentialE;"'
,'@mo       ::= "+" | "-" | "/" | "&times;"/"×" | "&divide;" / "(÷)" | "=" '
,'            | "&lt;"/"<" | "&gt;"/">"  | "&le;"/"<=" | "&ge;"/">="'
,'            | "&InvisibleTimes;" / "×" | "&PlusMinus;"/ "±"/"+-"'
,'            |  "&[A-Za-z]+;"'
,'@mo.diff  ::= "&DifferentialD;"/"#D."'
,'@mo.pSta  ::= "("'
,'@mo.pEnd  ::= ")"'
,'@mo.sum   ::= "&sum;"/"∑"/"Σ"/"#SUM"'
,'@mo.integ ::= "&int;"/"#INTEG"'
,'@mo.func  ::= "&ApplyFunction;"/"#F"'
,'math-list ::= {math &";"}'
,'math      ::= expression'
,'expression::= {op-term}'
,'op-term   ::= mo | term'
,'term      ::= mn | mi | mrow | msqrt | mfrac | msup | msub | mroot |vect'
,'            | integ | func | mrow.paren | munderover.sum | mrow.diff'
,'mrow      ::= "{" expression "}"'
,'msqrt     ::= "√" term | "&sqrt;" term | "#SQRT" term'
,'mfrac     ::= term "÷" term'
,'msup      ::= term "^" term'
,'mroot     ::= "#ROOT" "[" mrow.0 "," mrow.0 "]"'
,'mfenced   ::= "[" mrow_0s "]"'
,'mrow_0s   ::= { mrow.0 &","}'
,'mrow.0    ::= expression'
,'msub      ::= "#[" term "," term "]"'
,'vect      ::= "#VECT" mfenced'
,'integ     ::= mo.integ mrow.integ | msubsup.integ mrow.integ'
,'func      ::= mi mo.func mfenced'
,'mrow.paren     ::= mo.pSta expression mo.pEnd'
,'mrow.diff      ::= mo.diff mi'
,'munderover.sum ::= mo.sum "[" mrow.0 "," mrow.0  "]"'
,'msubsup.integ  ::= mo.integ "["  mrow.0 "," mrow.0 "]"'
,'mrow.integ     ::= mrow.0 mrow.diff'];
function $E(id){ return document.getElementById(id); }// アンチ・ジュゲム関数
var math;
function test(txt,target){
   if(math==undefined)document.app.readSyntaxLines(syntax); //Opera対策
   math= document.app.parseString(txt,"te"); // 'document.'付加はfirefox対策
   alert(math); // MathML記述確認
   $E(target).innerHTML=math;
   }
if(navigator.userAgent.indexOf('Opera')==-1){ // Opera対策
   if(navigator.userAgent.indexOf('Safari')!=-1){ // Safari対策
      math= document.app.readSyntaxString(syntax.join('\n'));
      }
   else math= document.app.readSyntaxLines(syntax);;
   }
</script>
<input type=text id=in style="width:20em" value="a=1-d÷{c-b}×(d+e)+2*X*Y+A(÷)B">
<input type=button value="MathMLに変換"
   onClick='test($E("in").value,"m1")'><br><br>
<input type=button value="a=1-d÷{c-b}×(d+e)+2*X*Y+A(÷)B"
   onClick='$E("in").value="a=1-d÷{c-b}×(d+e)+2*X*Y+A(÷)B"'>&ensp;
<input type=button value="ローレンツ変換"
   onClick='$E("in").value="t2=t1 ÷ √{1-{v^2}÷{C^2}}"'>&ensp;
<input type=button value="二次方程式の解"
   onClick='$E("in").value="x={-b±√{b^2-4*a*c}} ÷ {2*a}"'><br>
<input type=button value="総和"
   onClick='$E("in").value="#SUM[i=1,5]#[K,i]"'>&ensp;
<input type=button value="微分"
   onClick='$E("in").value="#D.y÷#D.x=x"'>&ensp;
<input type=button value="積分"
   onClick='$E("in").value="#INTEG(x^2+x)#D.x"'>&ensp;
<input type=button value="定積分"
   onClick='$E("in").value="#INTEG[0,π]cos#F[x]#D.x"'>&ensp;
<hr>

appletとして用意された汎用の構文解析ツールインスタンスappMathML簡素記述の構文定義readSyntaxString()関数で設定しています
Operaではアプレットの動作が可能となる前に次が動き始め、 動作が可能となるタイミングを得ることができませんので、当面の対応として ボタンを押すまで構文設定をしないようにしてあります。 ココログ+firefoxではオブジェクト名ダイレクトではアクセスできませんので、 document.を付けています。safariでは 配列がうまく伝わらないので文字列連結しました。
このツールは構文木をXMLとして出力する機能を持っており、 構文定義は解析結果の構文木がそのままMathML構文になるよう定義されています。
入力文の解析はparseString()関数で行います。"te"引数はXMLの作り方のオプション 指定で、トークンをタグ内に置くことと&を&amp;にしないことを指定しています。

次の文をparse()すると、

a=1-b÷c
次の構文木が得られます。この木はそのままMathMLの記述スタイルとなっています。
<math>
  <mi>a</mi>
  <mo>=</mo>
  <mn>1</mn>
  <mo>-</mo>
  <mfrac>
    <mi>b</mi>
    <mi>c</mi>
  </mfrac>
</math>

構文解析ツールsymphonieは汎用のツールであってMathML用ではありません。 汎用のツールに構文定義を与えるだけで、MathML記述は極めて簡素なもので 済むことが分かると思います。

 HTML上に書けるMathML用簡素記述

MathMLは到底普通の人間が直接書けるようなものでは有りません。

簡便な記述からオフラインでMathMLを生成するツールはあるとは 思いますが、本記事の手法はHTML上に直接簡便な記述を置けるもの です。

こまごまとプログラム作ればもっと良い記述法もありうるとは 思います。しかし、このレベルの記述でも圧倒的に楽になるはず です。

例えば分数はMathMLは2つの要素を持つブロックで現しますが、 本記述ではオペレータ%の前後に要素を置きます。分数は割り算 の別表現でありこの形が適切であると思います。
また、&InvisibleTimes;も@一文字で済みます。もちろん &InvisibleTimes;と書いても構いません。

この版は可能性を試しただけでフルサポートはしていません。 しかし、仕様を定めればフルサポートは容易です。

なお、この記事では試験のため、text-inputの内容をダイナミックに 反映させていますが、実際の応用時は文の中に式記述を置き、

<p>
二次方程式
<span class=mathExp>a*x^2 + b*x + c =0</span>
の解は、
<span class=mathExp>x={-b±√{b^2-4*a*c}} ÷ {2*a}</span>
となります。
</p>
のようにすることになると思います。spanあるいはpの内容を読んで mathに置き換えます。上記記述から試験的実装で出したfirefox画面のキャプチャを参考 までに載せます。

 構文解析ツールsymphonie

Symphonieは拡張BNFによる構文定義に従い入力文を解析 するツールでJavaで作成されています。yacc/lexと異なり 構文定義から生成されるプログラムのコンパイルという手続き を必要としないので、今回の応用のようにWEBクライアント でも使用できます。
仮公開のsymphonie.jarとサンプルHTMLをまとめたセットを用意 しました。

正式版はオツアンドサンズで公開されています。API仕様や、構文定義法仕様 などもここで見られます。

 アプレットSymphonieAppの仕様

symphonie.jarに搭載されているアップレットSymphonieAppは次の機能を持ちます。

  • 構文定義の設定:文字列による定義
    String app.readSyntaxSting(String 構文定義文字列)
    戻り値は、構文定義に異常が無い場合"ok"、異常がある場合異常を現す メッセージとなっています。
  • 構文定義の設定:文字列配列による定義
    String app.readSyntaxLines(String[] 構文定義文字列配列)
    文字列配列で構文定義を設定します。配列の各要素は行と見なされます。 文字列自体に改行コードは不要です。
    戻り値は、構文定義に異常が無い場合"ok"、異常がある場合異常を現す メッセージとなっています。
    readSyntaxStringに比べ若干軽いのですが、残念ながらsafari5.0ではうまく文字列配列 が伝わらないようです。
  • 入力文を解析し構文木をXMLで得る:文字列による入力文
    String app.parseString(String 入力文,String オプション指定)
    オプション指定は"te"を指定してください。
    戻り値はXMLによる構文木で、異常がある場合は先頭文字が"-"で 後ろにエラーを現すメッセージが続きます。
  • 入力文を解析し構文木をXMLで得る:文字列配列による入力文
    String app.parseLines(String[] 入力文,String オプション指定)
    文字列配列による文の解析を行います。配列の各要素は行と見なされます。 文字列自体に改行コードは不要です。
    オプション指定は"te"を指定してください。
    戻り値はXMLによる構文木で、異常がある場合は先頭文字が"-"で 後ろにエラーを現すメッセージが続きます。
    parseStringに比べ若干軽いのですが、残念ながらsafari5.0ではうまく文字列配列 が伝わらないようです。

 ###

2011/5/11:
式をより把握しやすいよう次の修正を入れました。
・分数を%から÷に変更
・非表示乗算を@から*に変更
・乗算を*から×に変更
・割り算÷は(÷)に変更

|

« WEBクライアントでJavaを動かす:DOMアクセスする | トップページ | ◆金属を描き分ける »

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック


この記事へのトラックバック一覧です: WEB上で動的MathML生成:クライアントで構文解析を実行:

« WEBクライアントでJavaを動かす:DOMアクセスする | トップページ | ◆金属を描き分ける »