4 * ASCIIMathPHP and associated classes:
6 * -- MathMLNode extends XMLNode
8 * These classes are a PHP port of ASCIIMath
9 * Version 1.3 Feb 19 2004, (c) Peter Jipsen http://www.chapman.edu/~jipsen
11 * ASCIIMathPHP Version 1.02, 02 Oct 2004, (c) Kee-Lin Steven Chan (kc56@cornell.edu)
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or (at
16 * your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * General Public License (at http://www.gnu.org/copyleft/gpl.html)
26 * -- Fixed bug with mbox and text
27 * -- Fixed spacing bug with mbox and text
30 * -- Fixed Bug that did not parse symbols greater than a single character
31 * correctly when appearing at end of expression.
49 function & XMLNode($id = NULL)
51 $this->_id = isset($id) ? $id : md5(uniqid(rand(),1));
54 $this->_mt_elem_flg = FALSE;
55 $this->_attr_arr = array();
56 $this->_child_arr = array();
58 $this->_nmspc_alias = '';
59 $this->_parent_id = FALSE;
60 $this->_parent_node = NULL;
63 function addChild(&$node)
65 $this->_child_arr[$node->getId()] =& $node;
66 $node->setParentId($this->_id);
67 $node->setParentNode($this);
70 function addChildArr(&$node_arr)
72 $key_arr = array_keys($node_arr);
73 $num_key = count($key_arr);
75 for ($i = 0; $i < $num_key; $i++) {
76 $node =& $node_arr[$key_arr[$i]];
77 $this->addChild($node);
81 function insertChildBefore($idx,&$node)
83 $key_arr = array_keys($this->_child_arr);
84 $num_key = count($key_arr);
87 for ($i = 0;$i < $num_key;$i++) {
89 $tmp_arr[$node->getId()] =& $node;
91 $tmp_arr[$key_arr[$i]] =& $this->_child_arr[$key_arr[$i]];
93 $this->_child_arr =& $tmp_arr;
96 function insertChildAfter($idx,&$node)
98 $key_arr = array_keys($this->_child_arr);
99 $num_key = count($key_arr);
102 for ($i = 0;$i < $num_key;$i++) {
103 $tmp_arr[$key_arr[$i]] =& $this->_child_arr[$key_arr[$i]];
105 $tmp_arr[$node->getId()] =& $node;
108 $this->_child_arr =& $tmp_arr;
116 function setName($name)
118 $this->_name = $name;
121 function setNamepace($nmspc)
123 $this->_nmspc = $nmspc;
126 function setNamespaceAlias($nmspc_alias)
128 $this->_nmspc_alias = $nmspc_alias;
131 function setContent($content)
133 $this->_content = $content;
136 function setEmptyElem($mt_elem_flg)
138 $this->_mt_elem_flg = $mt_elem_flg;
141 function setAttr($attr_nm,$attr_val)
143 $this->_attr_arr[$attr_nm] = $attr_val;
146 function setAttrArr($attr_arr)
148 $this->_attr_arr = $attr_arr;
151 function setParentId($id)
153 $this->_parent_id = $id;
156 function setParentNode(&$node)
158 $this->_parent_node =& $node;
168 return($this->_name);
171 function getNamespace()
173 return($this->_nmspc);
176 function getNamespaceAlias()
178 return($this->_nmspc_alias);
181 function getContent()
183 return($this->_content);
186 function getAttr($attr_nm)
188 if (isset($this->_attr_arr[$attr_nm])) {
189 return($this->_attr_arr[$attr_nm]);
195 function getAttrArr()
197 return($this->_attr_arr);
200 function getParentId()
202 return($this->parent_id);
205 function & getParentNode()
207 return($this->_parent_node);
210 function & getChild($id)
212 if (isset($this->_child_arr[$id])) {
213 return($this->_child_arr[$id]);
219 function & getFirstChild()
221 $id_arr = array_keys($this->_child_arr);
222 $num_child = count($id_arr);
224 if ($num_child > 0) {
225 return($this->_child_arr[$id_arr[0]]);
231 function & getLastChild()
233 $id_arr = array_keys($this->_child_arr);
234 $num_child = count($id_arr);
236 if ($num_child > 0) {
237 return($this->_child_arr[$id_arr[$num_child - 1]]);
243 function & getChildByIdx($idx)
245 $id_arr = array_keys($this->_child_arr);
247 if (isset($this->_child_arr[$id_arr[$idx]])) {
248 return($this->_child_arr[$id_arr[$idx]]);
254 function getNumChild()
256 return(count($this->_child_arr));
259 function removeChild($id)
261 unset($this->_child_arr[$id]);
264 function removeChildByIdx($idx)
266 $key_arr = array_keys($this->_child_arr);
267 unset($this->_child_arr[$key_arr[$idx]]);
270 function removeFirstChild()
272 $key_arr = array_keys($this->_child_arr);
273 unset($this->_child_arr[$key_arr[0]]);
276 function removeLastChild()
278 $key_arr = array_keys($this->_child_arr);
279 unset($this->_child_arr[$key_arr[count($key_arr)-1]]);
282 function dumpXML($indent_str = "\t")
284 $attr_txt = $this->_dumpAttr();
285 $name = $this->_dumpName();
286 $xmlns = $this->_dumpXmlns();
287 $lvl = $this->_getCurrentLevel();
288 $indent = str_pad('',$lvl,$indent_str);
290 if ($this->_mt_elem_flg) {
291 $tag = "$indent<$name$xmlns$attr_txt />";
294 $key_arr = array_keys($this->_child_arr);
295 $num_child = count($key_arr);
297 $tag = "$indent<$name$xmlns$attr_txt>$this->_content";
299 for ($i = 0;$i < $num_child;$i++) {
300 $node =& $this->_child_arr[$key_arr[$i]];
302 $child_txt = $node->dumpXML($indent_str);
303 $tag .= "\n$child_txt";
306 $tag .= ($num_child > 0 ? "\n$indent</$name>" : "</$name>");
313 $id_arr = array_keys($this->_attr_arr);
314 $id_arr_cnt = count($id_arr);
317 for($i = 0;$i < $id_arr_cnt;$i++) {
319 $attr_txt .= " $key=\"{$this->_attr_arr[$key]}\"";
327 $alias = $this->getNamespaceAlias();
329 return($this->getName());
331 return("$alias:" . $this->getName());
335 function _dumpXmlns()
337 $nmspc = $this->getNamespace();
338 $alias = $this->getNamespaceAlias();
342 return(" xmlns=\"" . $nmspc . "\"");
344 return(" xmlns:$alias=\"" . $nmspc . "\"");
351 function _getCurrentLevel()
353 if ($this->_parent_id === FALSE) {
356 $node =& $this->getParentNode();
357 $lvl = $node->_getCurrentLevel();
364 class MathMLNode extends XMLNode
366 function & MathMLNode($id = NULL)
368 parent::XMLNode($id);
371 function removeBrackets()
373 if ($this->_name == 'mrow') {
374 if ($c_node_0 =& $this->getFirstChild()) {
375 $c_node_0->isLeftBracket() ? $this->removeFirstChild() : 0;
378 if ($c_node_0 =& $this->getLastChild()) {
379 $c_node_0->isRightBracket() ? $this->removeLastChild() : 0;
384 function isLeftBracket()
386 switch ($this->_content) {
396 function isRightBracket()
398 switch ($this->_content) {
418 function ASCIIMathPHP($symbol_arr,$expr = NULL)
420 $this->_symbol_arr = $symbol_arr;
422 $this->setExpr($expr);
426 function setExpr($expr)
428 $this->_expr = $expr;
429 $this->_curr_expr = $expr;
430 $this->_prev_expr = $expr;
432 $this->_node_arr = array();
433 $this->_node_cntr = 0;
436 function genMathML($attr_arr = NULL)
439 $node_0 =& $this->createNode();
440 $node_0->setName('math');
441 $node_0->setNamepace('http://www.w3.org/1998/Math/MathML');
444 if (isset($attr_arr)) {
445 $node_1 =& $this->createNode();
446 $node_1->setName('mstyle');
447 $node_1->setAttrArr($attr_arr);
449 $node_arr =& $this->parseExpr();
451 $node_1->addChildArr($node_arr);
452 $node_0->addChild($node_1);
454 $node_arr =& $this->parseExpr();
455 $node_0->addChildArr($node_arr);
462 function & mergeNodeArr(&$node_arr_0,&$node_arr_1)
464 $key_arr_0 = array_keys($node_arr_0);
465 $key_arr_1 = array_keys($node_arr_1);
467 $num_key_0 = count($key_arr_0);
468 $num_key_1 = count($key_arr_1);
470 $merge_arr = array();
472 for ($i = 0;$i < $num_key_0;$i++) {
473 $merge_arr[$key_arr_0[$i]] =& $node_arr_0[$key_arr_0[$i]];
476 for ($j = 0;$j < $num_key_1;$i++) {
477 $merge_arr[$key_arr_1[$i]] =& $node_arr_1[$key_arr_1[$i]];
484 function & parseExpr()
486 // Child/Fragment array
489 // Deal whole expressions like 'ax + by + c = 0' etc.
491 $sym_0 = $this->getSymbol();
492 $node_0 =& $this->parseSmplExpr();
493 $sym = $this->getSymbol();
496 if (isset($sym['infix'])) {
497 $this->chopExpr($sym['symlen']);
498 $node_1 =& $this->parseSmplExpr();
500 if ($node_1 === FALSE) {
504 $node_1->removeBrackets();
506 // If 'div' -- divide
507 if ($sym['input'] == '/') {
508 $node_0->removeBrackets();
511 // If 'sub' -- subscript
512 if ($sym['input'] == '_') {
513 $sym_1 = $this->getSymbol();
515 // If 'sup' -- superscript
516 if ($sym_1['input'] == '^') {
517 $this->chopExpr($sym_1['symlen']);
518 $node_2 =& $this->parseSmplExpr();
519 $node_2->removeBrackets();
521 $node_3 =& $this->createNode();
522 $node_3->setName(isset($sym_0['underover']) ? 'munderover' : 'msubsup');
523 $node_3->addChild($node_0);
524 $node_3->addChild($node_1);
525 $node_3->addChild($node_2);
527 $node_4 =& $this->createNode();
528 $node_4->setName('mrow');
529 $node_4->addChild($node_3);
531 $node_arr[$node_4->getId()] =& $node_4;
533 $node_2 =& $this->createNode();
534 $node_2->setName(isset($sym_0['underover']) ? 'munder' : 'msub');
535 $node_2->addChild($node_0);
536 $node_2->addChild($node_1);
538 $node_arr[$node_2->getId()] =& $node_2;
541 $node_2 =& $this->createNode();
542 $node_2->setName($sym['tag']);
543 $node_2->addChild($node_0);
544 $node_2->addChild($node_1);
546 $node_arr[$node_2->getId()] =& $node_2;
549 } elseif ($node_0 !== FALSE) {
550 $node_arr[$node_0->getId()] =& $node_0;
552 } while (!isset($sym['right_bracket']) && $sym !== FALSE && $sym['output'] != '');
555 // Possibly to deal with matrices
556 if (isset($sym['right_bracket'])) {
557 $node_cnt = count($node_arr);
558 $key_node_arr = array_keys($node_arr);
561 $node_5 =& $node_arr[$key_node_arr[$node_cnt-1]];
562 $node_6 =& $node_arr[$key_node_arr[$node_cnt-2]];
568 // Dealing with matrices
569 if ($node_5 !== FALSE && $node_6 !== FALSE &&
571 $node_5->getName() == 'mrow' &&
572 $node_6->getName() == 'mo' &&
573 $node_6->getContent() == ',') {
575 // Checking if Node 5 has a LastChild
576 if ($node_7 =& $node_5->getLastChild()) {
577 $node_7_cntnt = $node_7->getContent();
579 $node_7_cntnt = FALSE;
582 // If there is a right bracket
583 if ($node_7 !== FALSE && ($node_7_cntnt == ']' || $node_7_cntnt == ')')) {
585 // Checking if Node 5 has a firstChild
586 if ($node_8 =& $node_5->getFirstChild()) {
587 $node_8_cntnt = $node_8->getContent();
589 $node_8_cntnt = FALSE;
592 // If there is a matching left bracket
593 if ($node_8 !== FALSE &&
594 (($node_8_cntnt == '(' && $node_7_cntnt == ')' && $sym['output'] != '}') ||
595 ($node_8_cntnt == '[' && $node_7_cntnt == ']'))) {
598 $comma_pos_arr = array();
602 while ($i < $node_cnt && $is_mtrx_flg) {
603 $tmp_node =& $node_arr[$key_node_arr[$i]];
605 if($tmp_node_first =& $tmp_node->getFirstChild()) {
606 $tnfc = $tmp_node_first->getContent();
611 if($tmp_node_last =& $tmp_node->getLastChild()) {
612 $tnlc = $tmp_node_last->getContent();
617 if (isset($key_node_arr[$i+1])) {
618 $next_tmp_node =& $node_arr[$key_node_arr[$i+1]];
619 $ntnn = $next_tmp_node->getName();
620 $ntnc = $next_tmp_node->getContent();
626 // Checking each node in node array for matrix criteria
628 $is_mtrx_flg = $tmp_node->getName() == 'mrow' &&
629 ($i == $node_cnt-1 || $ntnn == 'mo' && $ntnc == ',') &&
630 $tnfc == $node_8_cntnt && $tnlc == $node_7_cntnt;
634 for ($j = 0;$j < $tmp_node->getNumChild();$j++) {
635 $tmp_c_node =& $tmp_node->getChildByIdx($j);
637 if ($tmp_c_node->getContent() == ',') {
638 $comma_pos_arr[$i][] = $j;
643 if ($is_mtrx_flg && $i > 1) {
645 $cnt_cpan = isset($comma_pos_arr[$i]) ? count($comma_pos_arr[$i]) : NULL;
646 $cnt_cpap = isset($comma_pos_arr[$i-2]) ? count($comma_pos_arr[$i-2]) : NULL;
647 $is_mtrx_flg = $cnt_cpan == $cnt_cpap;
653 // If the node passes the matrix tests
655 $tab_node_arr = array();
657 for ($i = 0;$i < $node_cnt;$i += 2) {
658 $tmp_key_node_arr = array_keys($node_arr);
659 if (!($tmp_node =& $node_arr[$tmp_key_node_arr[0]])) {
662 $num_child = $tmp_node->getNumChild();
665 $tmp_node->removeFirstChild();
667 $row_node_arr = array();
668 $row_frag_node_arr = array();
670 for ($j = 1;$j < ($num_child-1);$j++) {
671 if (isset($comma_pos_arr[$i][$k]) &&
672 $j == $comma_pos_arr[$i][$k]) {
674 $tmp_node->removeFirstChild();
676 $tmp_c_node =& $this->createNode();
677 $tmp_c_node->setName('mtd');
678 $tmp_c_node->addChildArr($row_frag_node_arr);
679 $row_frag_node_arr = array();
681 $row_node_arr[$tmp_c_node->getId()] =& $tmp_c_node;
686 if ($tmp_c_node =& $tmp_node->getFirstChild()) {
687 $row_frag_node_arr[$tmp_c_node->getId()] =& $tmp_c_node;
688 $tmp_node->removeFirstChild();
693 $tmp_c_node =& $this->createNode();
694 $tmp_c_node->setName('mtd');
695 $tmp_c_node->addChildArr($row_frag_node_arr);
697 $row_node_arr[$tmp_c_node->getId()] =& $tmp_c_node;
699 if (count($node_arr) > 2) {
700 $tmp_key_node_arr = array_keys($node_arr);
701 unset($node_arr[$tmp_key_node_arr[0]]);
702 unset($node_arr[$tmp_key_node_arr[1]]);
705 $tmp_c_node =& $this->createNode();
706 $tmp_c_node->setName('mtr');
707 $tmp_c_node->addChildArr($row_node_arr);
709 $tab_node_arr[$tmp_c_node->getId()] =& $tmp_c_node;
712 $tmp_c_node =& $this->createNode();
713 $tmp_c_node->setName('mtable');
714 $tmp_c_node->addChildArr($tab_node_arr);
716 if (isset($sym['invisible'])) {
717 $tmp_c_node->setAttr('columnalign','left');
720 $key_node_arr = array_keys($node_arr);
721 $tmp_c_node->setId($key_node_arr[0]);
723 $node_arr[$tmp_c_node->getId()] =& $tmp_c_node;
729 $this->chopExpr($sym['symlen']);
730 if (!isset($sym['invisible'])) {
731 $node_7 =& $this->createNode();
732 $node_7->setName('mo');
733 $node_7->setContent($sym['output']);
734 $node_arr[$node_7->getId()] =& $node_7;
741 function & parseSmplExpr()
743 $sym = $this->getSymbol();
745 if (!$sym || isset($sym['right_bracket'])) {
749 $this->chopExpr($sym['symlen']);
751 if (isset($sym['left_bracket'])) {
752 $node_arr =& $this->parseExpr();
754 if (isset($sym['invisible'])) {
755 $node_0 =& $this->createNode();
756 $node_0->setName('mrow');
757 $node_0->addChildArr($node_arr);
761 $node_0 =& $this->createNode();
762 $node_0->setName('mo');
763 $node_0->setContent($sym['output']);
765 $node_1 =& $this->createNode();
766 $node_1->setName('mrow');
767 $node_1->addChild($node_0);
768 $node_1->addChildArr($node_arr);
772 } elseif (isset($sym['unary'])) {
774 if ($sym['input'] == 'sqrt') {
775 $node_0 =& $this->parseSmplExpr();
776 $node_0->removeBrackets();
778 $node_1 =& $this->createNode();
779 $node_1->setName($sym['tag']);
780 $node_1->addChild($node_0);
783 } elseif ($sym['input'] == 'text' || $sym['input'] == 'mbox') {
784 $expr = ltrim($this->getCurrExpr());
797 $end_brckt = chr(11); // A chracter that will never be matched.
801 $txt = substr($expr,1,strpos($expr,$end_brckt)-1);
804 $node_0 =& $this->createNode();
805 $node_0->setName('mrow');
808 if ($txt{0} == " ") {
809 $node_1 =& $this->createNode();
810 $node_1->setName('mspace');
811 $node_1->setAttr('width','1ex');
813 $node_0->addChild($node_1);
816 $node_3 =& $this->createNode();
817 $node_3->setName($sym['tag']);
818 $node_3->setContent(trim($txt));
820 $node_0->addChild($node_3);
822 if ($len > 1 && $txt{$len-1} == " ") {
823 $node_2 =& $this->createNode();
824 $node_2->setName('mspace');
825 $node_2->setAttr('width','1ex');
827 $node_0->addChild($node_2);
830 $this->chopExpr($len+2);
834 } elseif (isset($sym['acc'])) {
835 $node_0 =& $this->parseSmplExpr();
836 $node_0->removeBrackets();
838 $node_1 =& $this->createNode();
839 $node_1->setName($sym['tag']);
840 $node_1->addChild($node_0);
842 $node_2 =& $this->createNode();
843 $node_2->setName('mo');
844 $node_2->setContent($sym['output']);
846 $node_1->addChild($node_2);
849 // Font change commands -- to complete
851 } elseif (isset($sym['binary'])) {
854 $node_0 =& $this->parseSmplExpr();
855 $node_0->removeBrackets();
857 $node_1 =& $this->parseSmplExpr();
858 $node_1->removeBrackets();
860 if ($sym['input'] == 'root') {
861 $node_arr[$node_1->getId()] =& $node_1;
862 $node_arr[$node_0->getId()] =& $node_0;
863 } elseif ($sym['input'] == 'frac') {
864 $node_arr[$node_0->getId()] =& $node_0;
865 $node_arr[$node_1->getId()] =& $node_1;
868 $node_2 =& $this->createNode();
869 $node_2->setName($sym['tag']);
870 $node_2->addChildArr($node_arr);
873 } elseif (isset($sym['infix'])) {
874 $node_0 =& $this->createNode();
875 $node_0->setName('mo');
876 $node_0->setContent($sym['output']);
879 } elseif (isset($sym['space'])) {
880 $node_0 =& $this->createNode();
881 $node_0->setName('mrow');
883 $node_1 =& $this->createNode();
884 $node_1->setName('mspace');
885 $node_1->setAttr('width',$sym['space']);
887 $node_2 =& $this->createNode();
888 $node_2->setName($sym['tag']);
889 $node_2->setContent($sym['output']);
891 $node_3 =& $this->createNode();
892 $node_3->setName('mspace');
893 $node_3->setAttr('width',$sym['space']);
895 $node_0->addChild($node_1);
896 $node_0->addChild($node_2);
897 $node_0->addChild($node_3);
903 $node_0 =& $this->createNode();
904 $node_0->setName($sym['tag']);
905 $node_0->setContent($sym['output']);
914 $root =& $this->_node_arr[0];
915 return($root->dumpXML());
918 function getCurrExpr()
920 return($this->_curr_expr);
925 return($this->_expr);
928 function getPrevExpr()
930 return($this->_prev_expr);
933 function & createNode()
935 $node =& new MathMLNode($this->_node_cntr);
936 // $node->setNamespaceAlias('m');
937 $this->_node_arr[$this->_node_cntr] =& $node;
942 function getSymbol($chop_flg = FALSE)
944 $chr_cnt = strlen($this->_curr_expr);
950 for ($i = 1; $i < $chr_cnt; $i++) {
951 $sym_0 = substr($this->_curr_expr,0,$i);
952 $sym_1 = substr($this->_curr_expr,0,$i+1);
954 // Reading string for numeric values
955 if (is_numeric($sym_0)) {
957 if (!is_numeric($sym_1)) {
958 $chop_flg ? $this->chopExpr($i) : 0;
959 return(array('input'=>$sym_0, 'tag'=>'mn', 'output'=>$sym_0, 'symlen'=>$i));
960 } elseif (is_numeric($sym_1) && $i == ($chr_cnt - 1)) {
961 $chop_flg ? $this->chopExpr($i+1) : 0;
962 return(array('input'=>$sym_1, 'tag'=>'mn', 'output'=>$sym_1, 'symlen'=>($i+1)));
965 } elseif (isset($this->_symbol_arr[$sym_0]) && !isset($this->_symbol_arr[$sym_1])) {
967 $chop_flg ? $this->chopExpr($i) : 0;
968 $sym_arr = $this->_symbol_arr[$sym_0];
969 $sym_arr['symlen'] = $i;
972 } elseif (isset($this->_symbol_arr[$sym_1]) && $i == ($chr_cnt - 1)) {
974 $chop_flg ? $this->chopExpr($i+1) : 0;
975 $sym_arr = $this->_symbol_arr[$sym_1];
976 $sym_arr['symlen'] = $i+1;
981 // Reading string for alphabetic constants and the minus sign
982 $char = $this->_curr_expr{0};
983 $len_left = $chop_flg ? $this->chopExpr(1) : strlen($this->_curr_expr)-1;
985 // Deals with expressions of length 1
986 if ($len_left == 0 && isset($this->_symbol_arr[$char])) {
987 $sym_arr = $this->_symbol_arr[$char];
988 $sym_arr['symlen'] = 1;
991 $tag = preg_match('/[a-z]/i',$char) ? 'mi' : 'mo';
992 return(array('input'=>$char, 'tag'=>$tag, 'output'=>$char, 'symlen'=>1));
996 function chopExpr($strlen)
998 $this->_prev_expr = $this->_curr_expr;
1000 if ($strlen == strlen($this->_curr_expr)) {
1001 $this->_curr_expr = '';
1004 $this->_curr_expr = ltrim(substr($this->_curr_expr,$strlen));
1005 return(strlen($this->_curr_expr));