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