]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/ASCIIMathPHP/ASCIIMathPHP.class.php
Add svn:keywords
[SourceForge/phpwiki.git] / lib / ASCIIMathPHP / ASCIIMathPHP.class.php
1 <?php // $Id$
2
3 /****
4  * ASCIIMathPHP and associated classes:
5  * -- XMLNode
6  * -- MathMLNode extends XMLNode
7  *
8  * These classes are a PHP port of ASCIIMath(c) Peter Jipsen http://www.chapman.edu/~jipsen
9  *
10  * ASCIIMathPHP Version 1.12.1, 06 Jan 2007, (c) Kee-Lin Steven Chan (kc56@cornell.edu)
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or (at
15  * your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful, 
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * General Public License (at http://www.gnu.org/copyleft/gpl.html) 
21  * for more details.
22  *
23  * ChangeLog
24  *
25  * Ver 1.12.1
26  * -- Included the missing setCurrExpr() method
27  *
28  * Ver 1.12
29  * -- Added changes that David Lippman <DLippman@pierce.ctc.edu> made to bring ASCIIMathPHP up to 
30  * ASCIIMath 1.4.7 functionality.
31  * -- Added parseIntExpr, for intermediate expression parsing rule, allowing x^2/x^3 to render as (x^2)/(x^3)
32  * -- Added quotes as another way of designating text; "hello" is equivalent to text(hello)
33  * -- Added FUNC designator to allow sin, cos, etc to act as functions, so sin(x)/x renders as {sin(x)}/x
34  *
35  * Ver 1.11
36  * -- Fixed bug that stopped script execution for incomplete expressions
37  * -- Changed the algorithm for parsing expressions so that it matches the longest string possible (greedy)
38  *
39  * Ver 1.10
40  * -- Added definition support
41  * -- Added stackrel support
42  * -- Added a bunch of different symbols etc. >>, << and definitions like dx, dy, dz etc.
43  *
44  * Ver 1.02
45  * -- Fixed bug with mbox and text
46  * -- Fixed spacing bug with mbox and text
47  *
48  * Ver 1.01
49  * -- Fixed Bug that did not parse symbols greater than a single character
50  * correctly when appearing at end of expression.
51  *
52  ***/
53
54 class XMLNode
55 {
56     // Private variables
57     var $_id;
58     var $_name;
59     var $_content;
60     var $_mt_elem_flg;
61     var $_attr_arr;
62     var $_child_arr;
63     var $_nmspc;
64     var $_nmspc_alias;
65     var $_parent_id;
66     var $_parent_node;
67     
68     function & XMLNode($id = NULL)
69     {
70         $this->_id = isset($id) ? $id : md5(uniqid(rand(),1));
71         $this->_name = '';
72         $this->_content = '';
73         $this->_mt_elem_flg = FALSE;
74         $this->_attr_arr = array();
75         $this->_child_arr = array();
76         $this->_nmspc = '';
77         $this->_nmspc_alias = '';
78         $this->_parent_id = FALSE;
79         $this->_parent_node = NULL;
80         return $this;
81     }
82     
83     function addChild(&$node)
84     {
85         $this->_child_arr[$node->getId()] =& $node;
86         $node->setParentId($this->_id);
87         $node->setParentNode($this);
88     }
89     
90     function addChildArr(&$node_arr)
91     {
92         $key_arr = array_keys($node_arr);
93         $num_key = count($key_arr);
94         
95         for ($i = 0; $i < $num_key; $i++) {
96             $node =& $node_arr[$key_arr[$i]];
97             $this->addChild($node);
98         }
99     }
100     
101     function insertChildBefore($idx,&$node)
102     {
103         $key_arr = array_keys($this->_child_arr);
104         $num_key = count($key_arr);
105         $tmp_arr = arry();
106         
107         for ($i = 0;$i < $num_key;$i++) {
108             if ($i == $idx) {
109                 $tmp_arr[$node->getId()] =& $node;
110             }
111             $tmp_arr[$key_arr[$i]] =& $this->_child_arr[$key_arr[$i]];
112         }
113         $this->_child_arr =& $tmp_arr;
114     }
115     
116     function insertChildAfter($idx,&$node)
117     {
118         $key_arr = array_keys($this->_child_arr);
119         $num_key = count($key_arr);
120         $tmp_arr = arry();
121         
122         for ($i = 0;$i < $num_key;$i++) {
123             $tmp_arr[$key_arr[$i]] =& $this->_child_arr[$key_arr[$i]];
124             if ($i == $idx) {
125                 $tmp_arr[$node->getId()] =& $node;
126             }
127         }
128         $this->_child_arr =& $tmp_arr;
129     }
130     
131     function setId($id)
132     {
133         $this->_id = $id;
134     }
135     
136     function setName($name)
137     {
138         $this->_name = $name;
139     }
140     
141     function setNamepace($nmspc)
142     {
143         $this->_nmspc = $nmspc;
144     }
145     
146     function setNamespaceAlias($nmspc_alias)
147     {
148         $this->_nmspc_alias = $nmspc_alias;
149     }
150     
151     function setContent($content)
152     {
153         $this->_content = $content;
154     }
155     
156     function setEmptyElem($mt_elem_flg)
157     {
158         $this->_mt_elem_flg = $mt_elem_flg;
159     }
160     
161     function setAttr($attr_nm,$attr_val)
162     {
163         $this->_attr_arr[$attr_nm] = $attr_val;
164     }
165     
166     function setAttrArr($attr_arr)
167     {
168         $this->_attr_arr = $attr_arr;
169     }
170     
171     function setParentId($id)
172     {
173         $this->_parent_id = $id;
174     }
175     
176     function setParentNode(&$node)
177     {
178         $this->_parent_node =& $node;
179     }
180     
181     function getId()
182     {
183         return($this->_id);
184     }
185     
186     function getName()
187     {
188         return($this->_name);
189     }
190     
191     function getNamespace()
192     {
193         return($this->_nmspc);
194     }
195     
196     function getNamespaceAlias()
197     {
198         return($this->_nmspc_alias);
199     }
200     
201     function getContent()
202     {
203         return($this->_content);
204     }
205     
206     function getAttr($attr_nm)
207     {
208         if (isset($this->_attr_arr[$attr_nm])) {
209             return($this->_attr_arr[$attr_nm]);
210         } else {
211             return(NULL);
212         }
213     }
214     
215     function getAttrArr()
216     {
217         return($this->_attr_arr);
218     }
219     
220     function getParentId()
221     {
222         return($this->parent_id);
223     }
224     
225     function & getParentNode()
226     {
227         return($this->_parent_node);
228     }
229     
230     function & getChild($id)
231     {
232         if (isset($this->_child_arr[$id])) {
233             return($this->_child_arr[$id]);
234         } else {
235             return(FALSE);
236         }
237     }
238     
239     function & getFirstChild()
240     {
241         $id_arr = array_keys($this->_child_arr);
242         $num_child = count($id_arr);
243         
244         if ($num_child > 0) {
245             return($this->_child_arr[$id_arr[0]]);
246         } else {
247             return(FALSE);
248         }
249     }
250     
251     function & getLastChild()
252     {
253         $id_arr = array_keys($this->_child_arr);
254         $num_child = count($id_arr);
255         
256         if ($num_child > 0) {
257             return($this->_child_arr[$id_arr[$num_child - 1]]);
258         } else {
259             return(FALSE);
260         }
261     }
262     
263     function & getChildByIdx($idx)
264     {
265         $id_arr = array_keys($this->_child_arr);
266         
267         if (isset($this->_child_arr[$id_arr[$idx]])) {
268             return($this->_child_arr[$id_arr[$idx]]);
269         } else {
270             return(FALSE);
271         }
272     }
273     
274     function getNumChild()
275     {
276         return(count($this->_child_arr));
277     }
278     
279     function removeChild($id)
280     {
281         unset($this->_child_arr[$id]);
282     }
283     
284     function removeChildByIdx($idx)
285     {
286         $key_arr = array_keys($this->_child_arr);
287         unset($this->_child_arr[$key_arr[$idx]]);
288     }
289     
290     function removeFirstChild()
291     {
292         $key_arr = array_keys($this->_child_arr);
293         unset($this->_child_arr[$key_arr[0]]);
294     }
295     
296     function removeLastChild()
297     {
298         $key_arr = array_keys($this->_child_arr);
299         unset($this->_child_arr[$key_arr[count($key_arr)-1]]);
300     }
301     
302     function dumpXML($indent_str = "\t")
303     {
304         $attr_txt = $this->_dumpAttr();
305         $name = $this->_dumpName();
306         $xmlns = $this->_dumpXmlns();
307         $lvl = $this->_getCurrentLevel();
308         $indent = str_pad('',$lvl,$indent_str);
309         
310         if ($this->_mt_elem_flg) {
311             $tag = "$indent<$name$xmlns$attr_txt />";
312             return($tag);
313         } else {
314             $key_arr = array_keys($this->_child_arr);
315             $num_child = count($key_arr);
316             
317             $tag = "$indent<$name$xmlns$attr_txt>$this->_content";
318             
319             for ($i = 0;$i < $num_child;$i++) {
320                 $node =& $this->_child_arr[$key_arr[$i]];
321                 
322                 $child_txt = $node->dumpXML($indent_str);
323                 $tag .= "\n$child_txt";
324             }
325             
326             $tag .= ($num_child > 0 ? "\n$indent</$name>" : "</$name>");
327             return($tag);
328         }
329     }
330     
331     function _dumpAttr()
332     {
333         $id_arr = array_keys($this->_attr_arr);
334         $id_arr_cnt = count($id_arr);
335         $attr_txt = '';
336         
337         for($i = 0;$i < $id_arr_cnt;$i++) {
338             $key = $id_arr[$i];
339             $attr_txt .= " $key=\"{$this->_attr_arr[$key]}\"";
340         }
341         
342         return($attr_txt);
343     }
344     
345     function _dumpName()
346     {
347         $alias = $this->getNamespaceAlias();
348         if ($alias == '') {
349             return($this->getName());
350         } else {
351             return("$alias:" . $this->getName());
352         }
353     }
354     
355     function _dumpXmlns()
356     {
357         $nmspc = $this->getNamespace();
358         $alias = $this->getNamespaceAlias();
359         
360         if ($nmspc != '') {
361             if ($alias == '') {
362                 return(" xmlns=\"" . $nmspc . "\"");
363             } else {
364                 return(" xmlns:$alias=\"" . $nmspc . "\"");
365             }
366         } else {
367             return('');
368         }
369     }
370     
371     function _getCurrentLevel()
372     {
373         if ($this->_parent_id === FALSE) {
374             return(0);
375         } else {
376             $node =& $this->getParentNode();
377             $lvl = $node->_getCurrentLevel();
378             $lvl++;
379             return($lvl);
380         }
381     }
382 }
383
384 class MathMLNode extends XMLNode
385 {
386     function & MathMLNode($id = NULL)
387     {
388         return parent::XMLNode($id);
389     }
390     
391     function removeBrackets()
392     {
393         if ($this->_name == 'mrow') {
394             if ($c_node_0 =& $this->getFirstChild()) {
395                 $c_node_0->isLeftBracket() ? $this->removeFirstChild() : 0;
396             }
397             
398             if ($c_node_0 =& $this->getLastChild()) {
399                 $c_node_0->isRightBracket() ? $this->removeLastChild() : 0;
400             }
401         }
402     }
403     
404     function isLeftBracket()
405     {
406         switch ($this->_content) {
407             case '{':
408             case '[':
409             case '(':
410                 return(TRUE);
411                 break;
412         }
413         return(FALSE);
414     }
415     
416     function isRightBracket()
417     {
418         switch ($this->_content) {
419             case '}':
420             case ']':
421             case ')':
422                 return(TRUE);
423                 break;
424         }
425         return(FALSE);
426     }
427 }
428
429 class ASCIIMathPHP
430 {
431     var $_expr;
432     var $_curr_expr;
433     var $_prev_expr;
434     var $_symbol_arr;
435     var $_node_arr;
436     var $_node_cntr;
437     
438     function ASCIIMathPHP($symbol_arr,$expr = NULL)
439     {
440         $this->_symbol_arr = $symbol_arr;
441         if (isset($expr)) {
442             $this->setExpr($expr);
443         }
444     }
445     
446     /**
447      * Returns an empty node (containing a non-breaking space) 26-Apr-2006
448      * 
449      * Used when an expression is incomplete
450      *
451      * @return object
452      *
453      * @access private
454      */
455     function & emptyNode()
456     {
457         $tmp_node =& $this->createNode();
458         $tmp_node->setName('mn');
459         $tmp_node->setContent('&#' . hexdec('200B') . ';');
460         return $tmp_node;
461     }
462     
463     function pushExpr($prefix) // 2005-06-11 wes
464     {
465         $this->_curr_expr = $prefix . $this->_curr_expr;
466     }
467
468     function setExpr($expr)
469     {
470         $this->_expr = $expr;
471         $this->_curr_expr = $expr;
472         $this->_prev_expr = $expr;
473         
474         $this->_node_arr = array();
475         $this->_node_cntr = 0;
476     }
477     
478     function genMathML($attr_arr = NULL)
479     {
480         // <math> node
481         $node_0 =& $this->createNode();
482         $node_0->setName('math');
483         $node_0->setNamepace('http://www.w3.org/1998/Math/MathML');
484         
485         // <mstyle> node
486         if (isset($attr_arr)) {
487             $node_1 =& $this->createNode();
488             $node_1->setName('mstyle');
489             $node_1->setAttrArr($attr_arr);
490             
491             $node_arr = $this->parseExpr();
492             
493             $node_1->addChildArr($node_arr);
494             $node_0->addChild($node_1);
495         } else {
496             $node_arr = $this->parseExpr();
497             $node_0->addChildArr($node_arr);
498         }
499         
500         return TRUE;
501     }
502     
503     /*
504     function & mergeNodeArr(&$node_arr_0,&$node_arr_1)
505     {
506         $key_arr_0 = array_keys($node_arr_0);
507         $key_arr_1 = array_keys($node_arr_1);
508         
509         $num_key_0 = count($key_arr_0);
510         $num_key_1 = count($key_arr_1);
511         
512         $merge_arr = array();
513         
514         for ($i = 0;$i < $num_key_0;$i++) {
515             $merge_arr[$key_arr_0[$i]] =& $node_arr_0[$key_arr_0[$i]];
516         }
517         
518         for ($j = 0;$j < $num_key_1;$i++) {
519             $merge_arr[$key_arr_1[$i]] =& $node_arr_1[$key_arr_1[$i]];
520         }
521         
522         return($merge_arr);
523     }
524     */
525     
526     //Broken out of parseExpr Sept 7, 2006 David Lippman for
527     //ASCIIMathML 1.4.7 compatibility
528     function & parseIntExpr()
529     {
530         $sym_0 = $this->getSymbol();
531         $node_0 =& $this->parseSmplExpr();
532         $sym = $this->getSymbol();
533         
534         if (isset($sym['infix']) && $sym['input'] != '/') {
535             $this->chopExpr($sym['symlen']);
536             $node_1 =& $this->parseSmplExpr();
537             
538             if ($node_1 === FALSE) { //show box in place of missing argument
539                 $node_1 =& $this->emptyNode();//??
540             } else {
541                 $node_1->removeBrackets();
542             }
543             
544             // If 'sub' -- subscript
545             if ($sym['input'] == '_') {
546                 
547                 $sym_1 = $this->getSymbol();
548                 
549                 // If 'sup' -- superscript
550                 if ($sym_1['input'] == '^') { 
551                     $this->chopExpr($sym_1['symlen']);
552                     $node_2 =& $this->parseSmplExpr();
553                     $node_2->removeBrackets();
554                     
555                     $node_3 =& $this->createNode();
556                     $node_3->setName(isset($sym_0['underover']) ? 'munderover' : 'msubsup');
557                     $node_3->addChild($node_0);
558                     $node_3->addChild($node_1);
559                     $node_3->addChild($node_2);
560                     
561                     $node_4 =& $this->createNode();
562                     $node_4->setName('mrow');
563                     $node_4->addChild($node_3);
564                     
565                     return $node_4;
566                 } else {
567                     $node_2 =& $this->createNode();
568                     $node_2->setName(isset($sym_0['underover']) ? 'munder' : 'msub');
569                     $node_2->addChild($node_0);
570                     $node_2->addChild($node_1);
571                     
572                     return $node_2;
573                 }
574             } else {
575                 $node_2 =& $this->createNode();
576                 $node_2->setName($sym['tag']);
577                 $node_2->addChild($node_0);
578                 $node_2->addChild($node_1);
579                 
580                 return($node_2);
581             }
582         } elseif ($node_0 !== FALSE) {
583             return($node_0);
584         } else {
585             return $this->emptyNode();
586         }
587         
588     }
589     
590     function parseExpr()
591     {
592         // Child/Fragment array
593         $node_arr = array();
594         
595         // Deal whole expressions like 'ax + by + c = 0' etc.
596         do {
597             $sym_0 = $this->getSymbol();
598             $node_0 =& $this->parseIntExpr();
599             $sym = $this->getSymbol();
600             // var_dump($sym);
601             
602             if (isset($sym['infix']) && $sym['input'] == '/') {
603                 $this->chopExpr($sym['symlen']);
604                 $node_1 =& $this->parseIntExpr();
605                 
606                 if ($node_1 === FALSE) { //should show box in place of missing argument
607                     $node_1 =& $this->emptyNode();
608                     continue;
609                 }
610
611                 $node_1->removeBrackets();
612                 
613                 // If 'div' -- divide
614                 $node_0->removeBrackets();
615                 $node_2 =& $this->createNode();
616                 $node_2->setName($sym['tag']);
617                 $node_2->addChild($node_0);
618                 $node_2->addChild($node_1);
619                 $node_arr[$node_2->getId()] =& $node_2;
620             
621             } elseif ($node_0 !== FALSE) {
622                 $node_arr[$node_0->getId()] =& $node_0;
623             }
624         } while (!isset($sym['right_bracket']) && $sym !== FALSE && $sym['output'] != '');
625         
626         //var_dump($sym);
627         // Possibly to deal with matrices
628         if (isset($sym['right_bracket'])) {
629             $node_cnt = count($node_arr);
630             $key_node_arr = array_keys($node_arr);
631             
632             if ($node_cnt > 1) {
633                 $node_5 =& $node_arr[$key_node_arr[$node_cnt-1]];
634                 $node_6 =& $node_arr[$key_node_arr[$node_cnt-2]];
635             } else {
636                 $node_5 = FALSE;
637                 $node_6 = FALSE;
638             }
639             
640             // Dealing with matrices
641             if ($node_5 !== FALSE && $node_6 !== FALSE &&
642                 $node_cnt > 1 && 
643                 $node_5->getName() == 'mrow' && 
644                 $node_6->getName() == 'mo' &&
645                 $node_6->getContent() == ',') {
646                 
647                 // Checking if Node 5 has a LastChild
648                 if ($node_7 =& $node_5->getLastChild()) {
649                     $node_7_cntnt = $node_7->getContent();
650                 } else {
651                     $node_7_cntnt = FALSE;
652                 }
653                 
654                 // If there is a right bracket
655                 if ($node_7 !== FALSE && ($node_7_cntnt == ']' || $node_7_cntnt == ')')) {
656                     
657                     // Checking if Node 5 has a firstChild
658                     if ($node_8 =& $node_5->getFirstChild()) {
659                         $node_8_cntnt = $node_8->getContent();
660                     } else {
661                         $node_8_cntnt = FALSE;
662                     }
663                     
664                     // If there is a matching left bracket
665                     if ($node_8 !== FALSE && 
666                         (($node_8_cntnt == '(' && $node_7_cntnt == ')' && $sym['output'] != '}') ||
667                         ($node_8_cntnt == '[' && $node_7_cntnt == ']'))) {
668                             
669                         $is_mtrx_flg = TRUE;
670                         $comma_pos_arr = array();
671                         
672                         $i = 0;
673                         
674                         while ($i < $node_cnt && $is_mtrx_flg) {
675                             $tmp_node =& $node_arr[$key_node_arr[$i]];
676                             
677                             if($tmp_node_first =& $tmp_node->getFirstChild()) {
678                                 $tnfc = $tmp_node_first->getContent();
679                             } else {
680                                 $tnfc = FALSE;
681                             }
682                             
683                             if($tmp_node_last =& $tmp_node->getLastChild()) {
684                                 $tnlc = $tmp_node_last->getContent();
685                             } else {
686                                 $tnlc = FALSE;
687                             }
688                             
689                             if (isset($key_node_arr[$i+1])) {
690                                 $next_tmp_node =& $node_arr[$key_node_arr[$i+1]];
691                                 $ntnn = $next_tmp_node->getName();
692                                 $ntnc = $next_tmp_node->getContent();
693                             } else {
694                                 $ntnn = FALSE;
695                                 $ntnc = FALSE;
696                             }
697                             
698                             // Checking each node in node array for matrix criteria
699                             if ($is_mtrx_flg) {
700                                 $is_mtrx_flg = $tmp_node->getName() == 'mrow' &&
701                                     ($i == $node_cnt-1 || $ntnn == 'mo' && $ntnc == ',') &&
702                                     $tnfc == $node_8_cntnt && $tnlc == $node_7_cntnt;
703                             }
704
705                             if ($is_mtrx_flg) {
706                                 for ($j = 0;$j < $tmp_node->getNumChild();$j++) {
707                                     $tmp_c_node =& $tmp_node->getChildByIdx($j);
708                                     
709                                     if ($tmp_c_node->getContent() == ',') {
710                                         $comma_pos_arr[$i][] = $j;
711                                     }
712                                 }
713                             }    
714                             
715                             if ($is_mtrx_flg && $i > 1) {
716                                 
717                                 $cnt_cpan = isset($comma_pos_arr[$i]) ? count($comma_pos_arr[$i]) : NULL;
718                                 $cnt_cpap = isset($comma_pos_arr[$i-2]) ? count($comma_pos_arr[$i-2]) : NULL;
719                                 $is_mtrx_flg = $cnt_cpan == $cnt_cpap;
720                             }
721                             
722                             $i += 2;
723                         }
724                         
725                         // If the node passes the matrix tests
726                         if ($is_mtrx_flg) {
727                             $tab_node_arr = array();
728                             
729                             for ($i = 0;$i < $node_cnt;$i += 2) {
730                                 $tmp_key_node_arr = array_keys($node_arr);
731                                 if (!($tmp_node =& $node_arr[$tmp_key_node_arr[0]])) {
732                                     break;
733                                 }
734                                 $num_child = $tmp_node->getNumChild();
735                                 $k = 0;
736                                 
737                                 $tmp_node->removeFirstChild();
738                                 
739                                 $row_node_arr = array();
740                                 $row_frag_node_arr = array();
741                                 
742                                 for ($j = 1;$j < ($num_child-1);$j++) {
743                                     if (isset($comma_pos_arr[$i][$k]) && 
744                                         $j == $comma_pos_arr[$i][$k]) {
745                                         
746                                         $tmp_node->removeFirstChild();
747                                         
748                                         $tmp_c_node =& $this->createNode();
749                                         $tmp_c_node->setName('mtd');
750                                         $tmp_c_node->addChildArr($row_frag_node_arr);
751                                         $row_frag_node_arr = array();
752                                         
753                                         $row_node_arr[$tmp_c_node->getId()] =& $tmp_c_node;
754                                         
755                                         $k++;
756                                     } else {
757                                         
758                                         if ($tmp_c_node =& $tmp_node->getFirstChild()) {
759                                             $row_frag_node_arr[$tmp_c_node->getId()] =& $tmp_c_node;
760                                             $tmp_node->removeFirstChild();
761                                         }
762                                     }
763                                 }
764                                 
765                                 $tmp_c_node =& $this->createNode();
766                                 $tmp_c_node->setName('mtd');
767                                 $tmp_c_node->addChildArr($row_frag_node_arr);
768                                 
769                                 $row_node_arr[$tmp_c_node->getId()] =& $tmp_c_node;
770                                 
771                                 if (count($node_arr) > 2) {
772                                     $tmp_key_node_arr = array_keys($node_arr);
773                                     unset($node_arr[$tmp_key_node_arr[0]]);
774                                     unset($node_arr[$tmp_key_node_arr[1]]);
775                                 }
776                                 
777                                 $tmp_c_node =& $this->createNode();
778                                 $tmp_c_node->setName('mtr');
779                                 $tmp_c_node->addChildArr($row_node_arr);
780                                 
781                                 $tab_node_arr[$tmp_c_node->getId()] =& $tmp_c_node;
782                             }
783                             
784                             $tmp_c_node =& $this->createNode();
785                             $tmp_c_node->setName('mtable');
786                             $tmp_c_node->addChildArr($tab_node_arr);
787                             
788                             if (isset($sym['invisible'])) {
789                                 $tmp_c_node->setAttr('columnalign','left');
790                             }
791                             
792                             $key_node_arr = array_keys($node_arr);
793                             $tmp_c_node->setId($key_node_arr[0]);
794                             
795                             $node_arr[$tmp_c_node->getId()] =& $tmp_c_node;
796                         }
797                     }
798                 }
799             }
800             
801             $this->chopExpr($sym['symlen']);
802             if (!isset($sym['invisible'])) {
803                 $node_7 =& $this->createNode();
804                 $node_7->setName('mo');
805                 $node_7->setContent($sym['output']);
806                 $node_arr[$node_7->getId()] =& $node_7;
807             }
808         }
809         
810         return($node_arr);
811     }
812     
813     function & parseSmplExpr()
814     {
815         $sym = $this->getSymbol();
816         
817         if (!$sym || isset($sym['right_bracket'])) //return FALSE; 
818             return $this->emptyNode();
819         
820         $this->chopExpr($sym['symlen']);
821         
822         // 2005-06-11 wes: add definition type support
823         if(isset($sym['definition'])) {
824             $this->pushExpr($sym['output']);
825             $sym = $this->getSymbol();
826             $this->chopExpr($sym['symlen']);
827         }
828
829         if (isset($sym['left_bracket'])) {
830             $node_arr = $this->parseExpr();
831             
832             if (isset($sym['invisible'])) {
833                 $node_0 =& $this->createNode();
834                 $node_0->setName('mrow');
835                 $node_0->addChildArr($node_arr);
836                 
837                 return($node_0);
838             } else {
839                 $node_0 =& $this->createNode();
840                 $node_0->setName('mo');
841                 $node_0->setContent($sym['output']);
842                 
843                 $node_1 =& $this->createNode();
844                 $node_1->setName('mrow');
845                 $node_1->addChild($node_0);
846                 $node_1->addChildArr($node_arr);
847                 
848                 return($node_1);
849             }
850         } elseif (isset($sym['unary'])) {
851             
852             if ($sym['input'] == 'sqrt') {
853                 $node_0 =& $this->parseSmplExpr();
854                 $node_0->removeBrackets();
855                 
856                 $node_1 =& $this->createNode();
857                 $node_1->setName($sym['tag']);
858                 $node_1->addChild($node_0);
859                 
860                 return($node_1);
861             } elseif (isset($sym['func'])) { //added 2006-9-7 David Lippman
862                 $expr = ltrim($this->getCurrExpr());
863                 $st = $expr{0};
864                 $node_0 =& $this->parseSmplExpr();
865                 //$node_0->removeBrackets();
866                 if ($st=='^' || $st == '_' || $st=='/' || $st=='|' || $st==',') {
867                     $node_1 =& $this->createNode();
868                     $node_1->setName($sym['tag']);
869                     $node_1->setContent($sym['output']);
870                     $this->setCurrExpr($expr);
871                     return($node_1);
872                 } else {
873                     $node_1 =& $this->createNode();
874                     $node_1->setName('mrow');
875                     $node_2 =& $this->createNode();
876                     $node_2->setName($sym['tag']);
877                     $node_2->setContent($sym['output']);
878                     $node_1->addChild($node_2);
879                     $node_1->addChild($node_0);
880                     return($node_1);
881                 }
882             } elseif ($sym['input'] == 'text' || $sym['input'] == 'mbox' || $sym['input'] == '"') {
883                 $expr = ltrim($this->getCurrExpr());
884                 if ($sym['input']=='"') {
885                     $end_brckt = '"';
886                     $txt = substr($expr,0,strpos($expr,$end_brckt));
887                 } else {
888                     switch($expr{0}) {
889                         case '(':
890                             $end_brckt = ')';
891                             break;
892                         case '[':
893                             $end_brckt = ']';
894                             break;
895                         case '{':
896                             $end_brckt = '}';
897                             break;
898                         default:
899                             $end_brckt = chr(11); // A character that will never be matched.
900                             break;
901                     }
902                     $txt = substr($expr,1,strpos($expr,$end_brckt)-1);
903                 }
904                 
905                 //$txt = substr($expr,1,strpos($expr,$end_brckt)-1);
906                 $len = strlen($txt);
907                 
908                 $node_0 =& $this->createNode();
909                 $node_0->setName('mrow');
910                 
911                 if ($len > 0) {
912                     if ($txt{0} == " ") {
913                         $node_1 =& $this->createNode();
914                         $node_1->setName('mspace');
915                         $node_1->setAttr('width','1ex');
916                         
917                         $node_0->addChild($node_1);
918                     }
919                     
920                     $node_3 =& $this->createNode();
921                     $node_3->setName($sym['tag']);
922                     $node_3->setContent(trim($txt));
923                     
924                     $node_0->addChild($node_3);
925                     
926                     if ($len > 1 && $txt{$len-1} == " ") {
927                         $node_2 =& $this->createNode();
928                         $node_2->setName('mspace');
929                         $node_2->setAttr('width','1ex');
930                         
931                         $node_0->addChild($node_2);
932                     }
933                     
934                     $this->chopExpr($len+2);
935                 }
936                 return($node_0);
937                 
938             } elseif (isset($sym['acc'])) {
939                 $node_0 =& $this->parseSmplExpr();
940                 $node_0->removeBrackets();
941                 
942                 $node_1 =& $this->createNode();
943                 $node_1->setName($sym['tag']);
944                 $node_1->addChild($node_0);
945                 
946                 $node_2 =& $this->createNode();
947                 $node_2->setName('mo');
948                 $node_2->setContent($sym['output']);
949                 
950                 $node_1->addChild($node_2);
951                 return($node_1);
952             } else {
953                 // Font change commands -- to complete
954             }
955         } elseif (isset($sym['binary'])) {
956             $node_arr = array();
957             
958             $node_0 =& $this->parseSmplExpr();
959             $node_0->removeBrackets();
960             
961             $node_1 =& $this->parseSmplExpr();
962             $node_1->removeBrackets();
963             
964             /* 2005-06-05 wes: added stackrel */
965             if ($sym['input'] == 'root' || $sym['input'] == 'stackrel') {
966                 $node_arr[$node_1->getId()] =& $node_1;
967                 $node_arr[$node_0->getId()] =& $node_0;
968             } elseif ($sym['input'] == 'frac') {
969                 $node_arr[$node_0->getId()] =& $node_0;
970                 $node_arr[$node_1->getId()] =& $node_1;
971             }
972             
973             $node_2 =& $this->createNode();
974             $node_2->setName($sym['tag']);
975             $node_2->addChildArr($node_arr);
976             
977             return($node_2);
978         } elseif (isset($sym['infix'])) {
979             $node_0 =& $this->createNode();
980             $node_0->setName('mo');
981             $node_0->setContent($sym['output']);
982             
983             return($node_0);
984         } elseif (isset($sym['space'])) {
985             $node_0 =& $this->createNode();
986             $node_0->setName('mrow');
987             
988             $node_1 =& $this->createNode();
989             $node_1->setName('mspace');
990             $node_1->setAttr('width',$sym['space']);
991             
992             $node_2 =& $this->createNode();
993             $node_2->setName($sym['tag']);
994             $node_2->setContent($sym['output']);
995             
996             $node_3 =& $this->createNode();
997             $node_3->setName('mspace');
998             $node_3->setAttr('width',$sym['space']);
999             
1000             $node_0->addChild($node_1);
1001             $node_0->addChild($node_2);
1002             $node_0->addChild($node_3);
1003             
1004             return($node_0);
1005         } else {
1006             
1007             // A constant
1008             $node_0 =& $this->createNode();
1009             $node_0->setName($sym['tag']);
1010             $node_0->setContent($sym['output']);
1011             return($node_0);
1012         }
1013         
1014         // Return an empty node
1015         return $this->emptyNode();
1016     }
1017     
1018     function getMathML()
1019     {
1020         $root =& $this->_node_arr[0];
1021         return($root->dumpXML());
1022     }
1023     
1024     function getCurrExpr()
1025     {
1026         return($this->_curr_expr);
1027     }
1028     
1029     function setCurrExpr($str)
1030     {
1031         $this->_curr_expr = $str;
1032     }
1033     
1034     function getExpr()
1035     {
1036         return($this->_expr);
1037     }
1038     
1039     function getPrevExpr()
1040     {
1041         return($this->_prev_expr);
1042     }
1043     
1044     function & createNode()
1045     {
1046         $node =& new MathMLNode($this->_node_cntr);
1047         // $node->setNamespaceAlias('m');
1048         $this->_node_arr[$this->_node_cntr] =& $node;
1049         $this->_node_cntr++;
1050         return($node);
1051     }
1052     
1053     /**
1054      * Gets the largest symbol in the expression (greedy). Changed from non-greedy 26-Apr-2006
1055      * 
1056      * @parameter boolean[optional] Chop original string?
1057      *
1058      * @return mixed
1059      * 
1060      * @access private
1061      */
1062     function getSymbol($chop_flg = FALSE)
1063     {
1064         // Implemented a reverse symbol matcher. 
1065         // Instead of going front to back, it goes back to front. Steven 26-Apr-2006
1066         $chr_cnt = strlen($this->_curr_expr);
1067         
1068         if ($chr_cnt == 0) return FALSE;
1069         
1070         for ($i = $chr_cnt; $i > 0; $i--) {
1071             $sym_0 = substr($this->_curr_expr,0,$i);
1072             
1073             // Reading string for numeric values
1074             if (is_numeric($sym_0)) {
1075                 
1076                 if ($chop_flg) $this->chopExpr($i);
1077                 return array('input'=>$sym_0, 'tag'=>'mn', 'output'=>$sym_0, 'symlen'=>$i);
1078                 
1079             } elseif (isset($this->_symbol_arr[$sym_0])) {
1080                 
1081                 if ($chop_flg) $this->chopExpr($i);
1082                 $sym_arr = $this->_symbol_arr[$sym_0];
1083                 $sym_arr['symlen'] = $i;
1084                 return $sym_arr;
1085             }
1086         }
1087         
1088         // Reading string for alphabetic constants and the minus sign
1089         $char = $this->_curr_expr{0};
1090         $len_left = $chop_flg ? $this->chopExpr(1) : strlen($this->_curr_expr)-1;
1091         
1092         // Deals with expressions of length 1
1093         if ($len_left == 0 && isset($this->_symbol_arr[$char])) {
1094             $sym_arr = $this->_symbol_arr[$char];
1095             $sym_arr['symlen'] = 1;
1096             return $sym_arr;
1097         } else {
1098             $tag = preg_match('/[a-z]/i',$char) ? 'mi' : 'mo';
1099             return array('input'=>$char, 'tag'=>$tag, 'output'=>$char, 'symlen'=>1);
1100         }
1101     }
1102     
1103     function chopExpr($strlen)
1104     {
1105         $this->_prev_expr = $this->_curr_expr;
1106         
1107         if ($strlen == strlen($this->_curr_expr)) {
1108             $this->_curr_expr = '';
1109             return(0);
1110         } else {
1111             $this->_curr_expr = ltrim(substr($this->_curr_expr,$strlen));
1112             return(strlen($this->_curr_expr));
1113         }
1114     }    
1115 }
1116 ?>