Home | History | Annotate | Download | only in ASCIIMathPHP-2.0
      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 (at) 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 (at) 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 
     58 class 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 
    387 class 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 
    432 class 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 ?>