WEB上で動的MathML生成:クライアントで構文解析を実行
ごくごく単純なレベルのMathML記述生成をWEB上で実施できるようにしてみました。
HTML上に書いた簡素な式記述からMathML記述を作り表示します。
MathMLはHTML上で数式を表現する機能で次期HTMLであるHTML5の目玉機能の一つ
です。しかし、その為の入力記述はとても人が直接書くことができるものでは
ありません。そこで、人が入力可能な記述からMathML記述をWEBクライアント側
で動的に作成してみることを試してみたものです。
MathMLはfirefoxで動作します。firefox4では
デフォルトで動作します。firefox3の場合about:configにアクセスし、html5-enable
をtrueにしてください。HTML5,MathMLを使ってみる参照
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]*" | "π"/"π" | "ⅇ"' ,'@mo ::= "+" | "-" | "/" | "×"/"×" | "÷" / "(÷)" | "=" ' ,' | "<"/"<" | ">"/">" | "≤"/"<=" | "≥"/">="' ,' | "⁢" / "×" | "±"/ "±"/"+-"' ,' | "&[A-Za-z]+;"' ,'@mo.diff ::= "ⅆ"/"#D."' ,'@mo.pSta ::= "("' ,'@mo.pEnd ::= ")"' ,'@mo.sum ::= "∑"/"∑"/"Σ"/"#SUM"' ,'@mo.integ ::= "∫"/"#INTEG"' ,'@mo.func ::= "⁡"/"#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"'>  <input type=button value="ローレンツ変換" onClick='$E("in").value="t2=t1 ÷ √{1-{v^2}÷{C^2}}"'>  <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]"'>  <input type=button value="微分" onClick='$E("in").value="#D.y÷#D.x=x"'>  <input type=button value="積分" onClick='$E("in").value="#INTEG(x^2+x)#D.x"'>  <input type=button value="定積分" onClick='$E("in").value="#INTEG[0,π]cos#F[x]#D.x"'>  <hr>
appletとして用意された汎用の構文解析ツールインスタンスappにMathML簡素記述の構文定義を
readSyntaxString()関数で設定しています。
Operaではアプレットの動作が可能となる前に次が動き始め、
動作が可能となるタイミングを得ることができませんので、当面の対応として
ボタンを押すまで構文設定をしないようにしてあります。
ココログ+firefoxではオブジェクト名ダイレクトではアクセスできませんので、
document.を付けています。safariでは
配列がうまく伝わらないので文字列連結しました。
このツールは構文木をXMLとして出力する機能を持っており、
構文定義は解析結果の構文木がそのままMathML構文になるよう定義されています。
入力文の解析はparseString()関数で行います。"te"引数はXMLの作り方のオプション
指定で、トークンをタグ内に置くことと&を&にしないことを指定しています。
次の文を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つの要素を持つブロックで現しますが、
本記述ではオペレータ%の前後に要素を置きます。分数は割り算
の別表現でありこの形が適切であると思います。
また、⁢も@一文字で済みます。もちろん
⁢と書いても構いません。
この版は可能性を試しただけでフルサポートはしていません。
しかし、仕様を定めればフルサポートは容易です。
なお、この記事では試験のため、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:
式をより把握しやすいよう次の修正を入れました。
・分数を%から÷に変更
・非表示乗算を@から*に変更
・乗算を*から×に変更
・割り算÷は(÷)に変更
| 固定リンク
コメント