#include "ltx2mathmlclasses.h"
#include "ltx2mathmltables.h"
#include "ltx2mathmlexceptions.h"
#include <ctype.h>
#include <string>

using namespace std;
// GLOBALS


enum sub_expression { se_none, se_use_default, se_braced, se_optional_param, se_inline_math, se_fence,
                                         se_matrix };//, se_eqalign, se_array, se_eqnarray  };

enum element_type { et_unknown, et_tag_off, et_tag_on, et_tag_open, et_tag_on_open };


enum skip_input { sp_skip_all, sp_skip_once, sp_no_skip };

enum { MAX_CONTROL_NAME = 32, EXTRA_BUF = 8 };


#define char_null          '\0'
#define char_backslash '\\'
#define char_prime         '\''


struct ArrayStruct {
       short maxColumn, columnCount;
       //sub_expression subType;
       command_id id;
};

struct ErrorMessage {
       const char *msg;
       int index;
       int code;
       string msg2;
};

struct InputStream {
       token_type token;
       char buffer[MAX_CONTROL_NAME+EXTRA_BUF+1];
       char *start;
       char nextChar;
};


static SymbolTable limits[] = {
       { "<munder>", "</munder>" },
       { "<mover>", "</mover>" },
       { "<munderover>", "</munderover>" }
};

static SymbolTable nolimits[] = {
       { "<msub>", "</msub>" },
       { "<msup>", "</msup>" },
       { "<msubsup>", "</msubsup>" }
};

static SymbolTable limitsMovable[] = {
       { "<msub movablelimits='true'>", "</msub>" },
       { "<msup movablelimits='true'>", "</msup>" },
       { "<msubsup movablelimits='true'>", "</msubsup>" }
};

static char *pStart             = NULL;
static char *pCur               = NULL;
static char *pEnd               = NULL;
static bool isNumberedFormula = false;
static Buffer globalBuf, eqNumber;
static ErrorMessage errMsg;


void onDigit( Buffer &prevBuf );
void onAlpha( Buffer &prevBuf );
void onSymbol( Buffer &prevBuf, InputStream &input, bool checkSubSup = true );
void onSubscript( Buffer &prevBuf );
void onSuperscript( Buffer &prevBuf );
void onControlName( Buffer &prevBuf, InputStream &input, bool &quit );
void onEntity( Buffer &prevBuf, EntityStruct *entity, bool checkLimits = true, bool checkSubSup = true );
void onFunction( Buffer &prevBuf, FunctionStruct *function, bool checkLimits = true );
bool onCommand( Buffer &prevBuf, ControlStruct &control, sub_expression subType, void *paramExtra );
void getCommandParam( Buffer &prevBuf, sub_expression subType );
bool followedBy( char **p, const char *pattern, skip_input skip );
bool parseExpression(const char *input, int len, int *errorIndex );
void runLoop( Buffer &prevBuf, sub_expression subType, void *paramExtra = NULL );
EnvironmentStruct *getEnvironmentType();
void onBeginEnvironment( Buffer &prevBuf );
bool onEndEnvironment( sub_expression subType, void *paramExtra );
void precondition( char **p );
void skipSpaces( char **p );
void skipChar( char **p );
bool getInput( InputStream &input, skip_input white_space );
bool scriptNext( char *p );
int  getLastTagIndex(const char *p, size_t length, element_type element );
token_type getControlTypeEx( InputStream &input, ControlStruct &control );
void onColumn( Buffer &prevBuf, const char *pos, ArrayStruct &ar );
void onRow( Buffer &prevBuf, ArrayStruct &ar );
bool needsMrow(const char *p );
void onMathFont( Buffer &prevBuf, const char *tagOn, const char *tagOff );
void onTextFont( Buffer &prevBuf, const char *tagOn, const char *tagOff, command_id id, bool allowInline = true );
bool onFence( Buffer &prevBuf, command_id id, sub_expression subType, const char *tagOn, const char *tagOff );
void onEndExpression( sub_expression subType, token_type token, CommandStruct *command );
void getPrime( char **p, char *buf );
void onPrime( Buffer &prevBuf );

bool convertFormula(const char *input, int len, int *errorIndex )
{

       if( len < 0 )
       {
               len = (int) strlen( input );
       }

       if( len == 0 )
       {
               return false;
       }

       return parseExpression( input, len, errorIndex );
}

bool parseExpression( const char *input, int len, int *errorIndex )
{

       bool result;

       pStart    = (char *)input;
       pCur      = pStart;
       pEnd      = pStart + len;

       ZeroMemory( &errMsg, sizeof( errMsg ) );

       globalBuf.destroy();
       eqNumber.destroy();
       isNumberedFormula = false;

       result = true;
       try
       {
               precondition( &pCur );
               runLoop( globalBuf, se_use_default );
               if( needsMrow( globalBuf.data() ) )
               {
                       globalBuf.insertAt( 0, "<mrow>" );
                       globalBuf.write( "</mrow>" );
               }
               if( isNumberedFormula )
               {
                       globalBuf.insertAt( 0, eqNumber.data() );
                       globalBuf.write( "</mtd></mlabeledtr></mtable>" );
               }
       }
       catch( const ErrorMessage &err )
       {
               result      = false;
               *errorIndex = err.index;
       }

       return result;
}


const char *getMathMLOutput()
{
       if( globalBuf.length() != 0 )
       {
               return globalBuf.data();
       }

       return NULL;
}

bool getMathMLOutput(string& buf, bool display)
{
       if (globalBuf.length() != 0)
       {
               const char *data = globalBuf.data();

               buf = display ? "<math display=\"block\" xmlns=\"http://www.w3.org/1998/Math/MathML\">" : "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">";

               buf.append(data);

               buf.append("</math>");

               return true;
       }
       return false;
}

static void getControlName(const char* start, string& name)
{
       char* p = (char*)(start+1);

       name.push_back('\\');

       if (!isalpha(*p))
       {
               name.push_back(*p);
               return;
       }
       while (*p && isalnum(*p))
       {
               name.push_back(*p);
               ++p;
       }
}

ErrorMessage &error(const char *index, ex_exception code )
{
       errMsg.code  = (int) code;
       errMsg.index = (int) (index - pStart);

       if (ex_undefined_control_sequence == code)
       {
               string name;

               errMsg.msg2 = getErrorMsg(code);
               errMsg.msg2.append(": ");

               getControlName(index, name);

               errMsg.msg2.append(name);

               errMsg.msg = errMsg.msg2.c_str();
       }
       else
       {
               errMsg.msg = getErrorMsg(code);
       }
       return errMsg;
}

ErrorMessage& error(const char* index, ex_exception code, const string &msg)
{
       errMsg.code = (int)code;
       errMsg.index = (int)(index - pStart);
       errMsg.msg = getErrorMsg(code);

       return errMsg;
}


const char *getLastError()
{
       return errMsg.msg;
}

/*

PRECONDITION traps as many errors as possible

*/


static void precondition( char **p )
{
       int braces;
       char *s, *lastLeftBrace;

       skipSpaces( p );

       // these chars can't start an equation

       //lastPos = *p;

       s = *p;

       switch( *s )
       {
       case '}':
               throw error( s, ex_missing_lbrace );
       case '^':
               throw error( s, ex_prefix_superscript );
       case '_':
               throw error( s, ex_prefix_subscript );
       case '&':
               throw error( s, ex_misplaced_column_separator );
       }

       braces = 0;
       lastLeftBrace = NULL;

       while( *s )
       {
               if( *s == '{' )
               {
                       lastLeftBrace = s;
                       ++braces;
                       skipChar( &s );
                       if( *s == '}' )
                       {
                               skipChar( &s );
                               --braces;
                       }
                       switch( *s )
                       {
                       case '^':
                               throw error( s, ex_prefix_superscript );
                       case '_':
                               throw error( s, ex_prefix_subscript );
                       }
               }
               else if( *s == '}' )
               {
                       --braces;
                       if( braces < 0 )
                       {
                               throw error( s, ex_more_rbrace_than_lbrace );
                       }
                       ++s;
               }
               else if( *s == char_backslash )
               {
                       ++s;
                       if( isdigit( *s ) )
                       {
                               throw error( s-1, ex_undefined_control_sequence );
                       }
                       else if( isalpha( *s ) )
                       {
                               char *start;

                               start = s;

                               // compute the length of the control name
                               do
                               {
                                       ++s;
                               }
                               while( isalpha( *s ) );

                               if( ( s - start ) > MAX_CONTROL_NAME )
                               {
                                       throw error( start - 1, ex_control_name_too_long );
                               }
                       }
                       else
                       {
                               // only the following chars can be escaped

                               switch( *s )
                               {
                               case '}':
                               case '{':
                               case '&':
                               case '^':
                               case '_':
                               case '-':
                               case '$':
                               case '#':
                               case '!':
                               case ';':
                               case '>':
                               case ':':       // check
                               case ',':
                               case '|':
                               case ' ':
                                       ++s;
                                       break;
                               case char_backslash:
                                       skipChar( &s );
                                       if( scriptNext( s ) )
                                       {
                                               if( *s == '_' )
                                               {
                                                       throw error( s, ex_prefix_subscript );
                                               }
                                               else
                                               {
                                                       throw error( s, ex_prefix_superscript );
                                               }
                                       }
                                       break;
                               default:
                                       throw error( s-1, ex_undefined_control_sequence );
                               }
                       }
               }
               else if( *s == '&' )
               {
                       skipChar( &s );
                       if( scriptNext( s ) )
                       {
                               if( *s == '_' )
                               {
                                       throw error( s, ex_prefix_subscript );
                               }
                               else
                               {
                                       throw error( s, ex_prefix_superscript );
                               }
                       }
               }
               else if( *s == '$' )
               {
                       if( braces == 0 )
                       {
                               throw error( s, ex_misplaced_inline_formula );
                       }

                       skipChar( &s );
                       if( scriptNext( s ) )
                       {
                               if( *s == '_' )
                               {
                                       throw error( s, ex_prefix_subscript );
                               }
                               else
                               {
                                       throw error( s, ex_prefix_superscript );
                               }
                       }
               }
               else if( scriptNext( s ) )
               {
                       char *pos;

                       pos = s;
                       skipChar( &s );
                       switch( *s )
                       {
                       case char_null:
                       case '}':
                       case '$':
                       case '&':
                               throw error( pos, ex_missing_parameter );
                       case char_backslash:
                               if( s[1] == char_backslash ) // row separator
                               {
                                       throw error( pos, ex_missing_parameter );
                               }
                       }
               }
               else
               {
                       ++s;
               }

       }

       if( braces != 0 )
       {
               throw error( lastLeftBrace, ex_more_lbrace_than_rbrace );
       }
       // check backwards

       do {
               --s;
       }
       while( (s > *p ) && isspace( *s ) );

       pEnd = s+1;
}

static void skipSpaces( char **p )
{
       char *s;

       if( p == NULL )
       {
               return;
       }

       s = *p;

       while( (*s) && isspace( *s ) )
       {
               ++s;
       }

       *p = s;
}

static void skipChar( char **p )
{
       char *s;

       if( p == NULL )
       {
               return;
       }

       s = *p;

       ++s; // skip one char, then spaces

       while( (*s) && isspace( *s ) )
       {
               ++s;
       }

       *p = s;
}


static bool followedBy( char **p, const char *pattern, skip_input skip )
{
       char *s;

       if( p == NULL )
       {
               return false;
       }

       s = *p;

       do {
               if( *s == *pattern )
               {
                       ++s;
                       ++pattern;
               }
               else
               {
                       return false;
               }
       } while( *s && *pattern );

       if( ( *pattern == char_null ) && !isalpha( *s ) )
       {
               if( skip != sp_no_skip )
               {
                       if( isspace( *s ) )
                       {
                               skipSpaces( &s );
                       }
                       *p = s;
               }
               return true;
       }

       return false;
}


static bool scriptNext( char *p )
{
       return ( *p == '_' ) || ( *p == '^' );
}

static int  getLastTagIndex( char *p, size_t length, element_type element )
{
       int inside;                     // inside a tag
       element_type current;
       size_t i;

       if( ( p == NULL ) || (length == 0) )
       {
               return -1;
       }

       i           = length-1;
       inside  = 0;
   current = et_unknown;

       while( i >= 0 )
       {
               if( p[i] == '>' )
               {
                       if( p[i-1] == '/' )
                       {
                               current = et_tag_open;
                               --i;    // to skip twice
                       }
               }
               else if( p[i] == '<' )
               {
                       if( p[i+1] == '/' )
                       {
                               current = et_tag_off;
                               if( element == current )
                               {
                                       return (int) i;
                               }
                               inside++;
                       }
                       else
                       {
                               --inside;
                               if( inside == 0 )
                               {
                                       return (int) i;
                               }
                       }
               }
               if( i == 0 )
               {
                       break;
               }
               --i;
       }

       return -1;
}

static bool getInput( InputStream &input, skip_input white_space )
{

       ZeroMemory( &input, sizeof( input ) );

       if( white_space == sp_skip_all )
       {
               if( isspace( *pCur ) )
               {
                       skipSpaces( &pCur );
               }
       }

       if( *pCur == char_null )
       {
               input.token = token_eof;
               return false;
       }


       while( *pCur )
       {
               input.start = pCur;
               if( isalpha( *pCur ) )
               {
                       input.token = token_alpha;
                       break;
               }
               else if( isdigit( *pCur ) )
               {
                       input.token = token_digit;
                       break;
               }
               else if( *pCur == '&' )
               {
                       input.token = token_column_sep;
                       skipChar( &pCur );
                       break;
               }
               else if( *pCur == '{' )
               {
                       input.token = token_left_brace;
                       skipChar( &pCur );
                       break;
               }
               else if( *pCur == '}' )
               {
                       input.token = token_right_brace;
                       skipChar( &pCur );
                       break;
               }
               else if( *pCur == '^' )
               {
                       input.token = token_superscript;
                       skipChar( &pCur );
                       break;
               }
               else if( *pCur == '_' )
               {
                       input.token = token_subscript;
                       skipChar( &pCur );
                       break;
               }
               else if( isspace( *pCur ) )
               {
                       if( *pCur == ' ' )
                       {
                               if( white_space == sp_skip_all )
                               {
                                  skipSpaces( &pCur );
                               }
                               else if ( white_space == sp_skip_once )
                               {

                                       input.token = token_white_space;
                                       ++pCur;
                                       break;
                               }
                               else
                               {
                                       //input.token = token_white_space;
                                       skipSpaces( &pCur );
                               }
                       }
                       else            // other spaces \n\r
                       {
                               skipSpaces( &pCur );
                       }
               }
               else if( *pCur == char_backslash )
               {
                       ++pCur;
                       if( isalpha( *pCur ) )
                       {
                               input.token = token_control_name;
                               for( int i = 0; i < MAX_CONTROL_NAME; ++i )
                               {
                                       input.buffer[i] = *pCur;
                                       ++pCur;
                                       if( !isalpha( *pCur ) )
                                       {
                                               break;
                                       }
                               }
                               // use this to determine whether control name
                               // is followed IMMEDIATELY by digits
                               // cf. \abc123 vs.\abc   123
                               input.nextChar = *pCur;
                               if( isspace( *pCur ) )
                               {
                                       skipSpaces( &pCur );
                               }
                       }
                       else
                       {
                               if( *pCur == char_backslash )
                               {
                                       input.token  = token_row_sep;
                               }
                               else
                               {
                                       input.token  = token_control_symbol;
                               }
                               input.buffer[0] = char_backslash;
                               input.buffer[1] = *pCur;
                               skipChar( &pCur );
                               input.nextChar = *pCur;
                       }
                       break;
               }
               else if( *pCur == ']' )
               {
                       input.token  = token_right_sq_bracket;
                       input.buffer[0] = *pCur;
                       skipChar( &pCur );
                       input.nextChar = *pCur;
                       break;
               }
               else if( *pCur == '$' )
               {
                       input.token  = token_inline_math;
                       input.buffer[0] = *pCur;
                       //skipChar( &pCur ); don't skip
                       input.nextChar = *pCur;
                       break;
               }
               else if( *pCur == char_prime )
               {
                       input.token  = token_prime;
                       input.buffer[0] = *pCur;
                       input.nextChar = pCur[1];
                       // don't skip
                       break;
               }
               else    // symbols
               {
                       input.token  = token_symbol;
                       input.buffer[0] = *pCur;
                       skipChar( &pCur );
                       input.nextChar = *pCur;
                       break;
               }
       }

       return true;
}

static void onPrime( Buffer &prevBuf )
{
       SymbolStruct *sym;
       char buf[5];

       getPrime( &pCur, buf );

       sym = getSymbol( buf );

       prevBuf.write( sym->element );
}

static void getPrime( char **p, char *buf )
{
       int i = 0;
       do {
               *buf = **p;
               ++buf;
               ++(*p);
               ++i;
               if( i == 3 )
               {
                       break;
               }
       } while( **p == char_prime );

       *buf = char_null;

       if( isspace( *pCur ) )
       {
               skipSpaces( &pCur );
       }
}

static token_type getControlTypeEx( InputStream &input, ControlStruct &control )
{
       Buffer buf;

       control.start = input.start;
       input.token               = getControlType( input.buffer, control );

       if( input.token != token_unknown )
       {
               return input.token;
       }

       if( isdigit( input.nextChar )  )
       {
               buf.write( input.buffer );
               buf.write( pCur, 1 );           // write the next digit
               ++pCur;
       }
       else
       {
               return input.token;
       }

       while( ( input.token = getControlType( buf.data(), control ) ) == token_unknown )
       {
               if( isdigit( *pCur ) )
               {
                       buf.write( pCur, 1 );
                       ++pCur;
               }
               else
               {
                       break;
               }
       }

       return input.token;
}

static void runLoop( Buffer &prevBuf, sub_expression subType, void *paramExtra )
{
       InputStream input;
       ControlStruct control;
       Buffer str;
       bool quitLoop;

       ZeroMemory( &control, sizeof( control ) );

       quitLoop = false;

       while( getInput( input, sp_skip_all ) )
       {
               switch( input.token )
               {
               case token_alpha:
                       onAlpha( str );
                       break;

               case token_digit:
                       onDigit( str );
                       break;

               case token_prime:
                       onPrime( str );
                       break;
               case token_symbol:
               case token_control_symbol:
                       onSymbol( str, input );
                       break;
               /*
               case token_white_space:
                       onWhiteSpace( str );
                       break;
               */
               case token_inline_math:

                       if( subType != se_inline_math )
                       {
                               throw error( pCur, ex_misplaced_inline_formula );
                       }
                       quitLoop = true;
                       break;
               case token_left_brace:
                       runLoop( str, se_braced );
                       break;
               case token_right_brace:
                       quitLoop = true;
                       break;
               case token_right_sq_bracket:
                       if( subType == se_optional_param )
                       {
                               quitLoop = true;
                       }
                       else
                       {
                               onSymbol( str, input );
                       }
                       break;
               case token_superscript:
                       onSuperscript( str );
                       break;

               case token_subscript:
                       onSubscript( str );
                       break;

               case token_column_sep:
                       if( subType < se_matrix )
                       {
                               throw error( input.start, ex_misplaced_column_separator );
                       }
                       else
                       {
                               onColumn( str, input.start, *((ArrayStruct *)paramExtra) );
                       }
                       break;
               case token_row_sep:
                       if( subType < se_matrix )
                       {
                               throw error( input.start, ex_misplaced_row_separator );
                       }
                       else
                       {
                               onRow( str, *((ArrayStruct *)paramExtra) );
                       }
                       break;
               case token_control_name:
                       //onControlName( str, input, quit );

                               input.token = getControlTypeEx( input, control );

                               switch( input.token )
                               {
                               case token_control_entity:
                                       onEntity( str, control.entity );
                                       break;
                               case token_control_command:
                                       quitLoop = onCommand( str, control, subType, paramExtra );
                                       break;
                               case token_control_function:
                                       onFunction( str, control.function );
                                       break;
                               //case token_unknown:
                               default:
                                       throw error( input.start, ex_undefined_control_sequence );
                               }

                       break;
               }

               if( quitLoop )
               {
                       break;
               }
       }

       onEndExpression( subType, input.token, control.command );

       prevBuf.append( str, true );
}

//se_optional_param, se_inline_math, se_fence,
//                                        se_matrix
static void onEndExpression( sub_expression subType, token_type token, CommandStruct *command )
{
       switch( subType )
       {
       case se_optional_param:
               if( token != token_right_sq_bracket )
               {
                       throw error( pCur, ex_missing_right_sq_bracket );
               }
               break;
       case se_inline_math:
               if( token != token_inline_math )
               {
                       throw error( pCur, ex_missing_dollar_symbol );
               }
               break;
       case se_matrix:
               if( token != token_control_command )
               {
                       throw error( pCur, ex_missing_end );
               }
               else if( command->id != ci_end )
               {
                       throw error( pCur, ex_missing_end );
               }
               break;
       case se_fence:
               if( token != token_control_command )
               {
                       throw error( pCur, ex_missing_right_fence );
               }
               else if( command->id != ci_right )
               {
                       throw error( pCur, ex_missing_right_fence );
               }
       }

}


static void onAlpha( Buffer &prevBuf )
{
       char tag[] = "<mi>?</mi>";
       char *p;
       Buffer str;


       str.setlength( 50 );

       p = strchr( tag, '?' );

       do {
               *p = *pCur;
               str.write( tag, sizeof( tag ) - 1 );
               ++pCur;
       }
       while( isalpha( *pCur ) );

       prevBuf.append( str, true );

}

static void onDigit( Buffer &prevBuf )
{
       char tagOn[] = "<mn>";
       char tagOff[] = "</mn>";

       Buffer str;
       char *start;


       str.setlength( 50 );


       str.write(tagOn, sizeof( tagOn ) - 1 );


       start = pCur;

       do {
               ++pCur;
       }
       while( isdigit( *pCur ) );

       str.write( start, (pCur - start) );

       str.write(tagOff, sizeof( tagOff ) - 1 );

       prevBuf.append( str, true );

}

static void onSymbol( Buffer &prevBuf, InputStream &input, bool checkSubSup )
{
       SymbolStruct *symbol;

       symbol = getSymbol( input.buffer );

       if( symbol == NULL )
       {
               throw error( pCur, ex_unknown_character );
       }


       switch( symbol->mathType )
       {
       case mt_fence:
       case mt_left_fence:
       case mt_right_fence:
               if( checkSubSup && scriptNext( pCur ) )
               {
                       throw error( pCur, ex_ambiguous_script );
               }
               break;
       }

       prevBuf.write( symbol->element );
}

static bool needsMrow( const char *p )
{
       element_type et;
       short inside, count;

       if( p == NULL )
       {
               return false;
       }

       inside = count = 0;
       et         = et_unknown;

       while( *p )
       {
               if( *p == '<' )
               {
                       if( p[1] == '/' )
                       {
                               et = et_tag_off;
                               ++p;
                       }
                       else
                       {
                               et = et_tag_on;
                       }
               }
               else if( *p == '>' )
               {
                       if( *(p-1) == '/' ) // tag open
                       {
                               if( inside == 0 )
                               {
                                       ++count;
                               }
                       }
                       else if( et == et_tag_off )
                       {
                               --inside;
                       }
                       else
                       {
                               if( inside == 0 )
                               {
                                       ++count;
                               }
                               ++inside;
                       }
                       if( count > 1 )
                       {
                               return true;
                       }
               }
               ++p;
       }

       return false;
}

static void getCommandParam( Buffer &prevBuf, sub_expression subType )
{
       InputStream input;
       Buffer str;
       ControlStruct control;

       getInput( input, sp_skip_all );

       switch( input.token )
       {
       case token_alpha:
               prevBuf.format( "<mi>%c</mi>", *pCur );
               skipChar( &pCur );
               break;

       case token_digit:
               prevBuf.format( "<mn>%c</mn>", *pCur );
               skipChar( &pCur );
               break;
       case token_prime:
               prevBuf.write( "<mo>&#x02032;</mo>" );
               skipChar( &pCur );
               break;
       case token_symbol:
       case token_control_symbol:
               onSymbol( prevBuf, input, false ); // ignore subscript/superscript
               break;

       case token_left_brace:
               if( subType == se_use_default )
               {
                       runLoop( str, se_braced );
               }
               else
               {
                       runLoop( str, subType );
               }
               if( needsMrow( str.data() ) )
               {
                       str.insertAt( 0, "<mrow>" );
                       str.write( "</mrow>" );
               }
               prevBuf.append( str, true );
               break;

       case token_right_brace:
       case token_superscript:
       case token_subscript:
       case token_column_sep:
       case token_row_sep:
       case token_eof:
               throw error( pCur, ex_missing_parameter );

       case token_control_name:
                       //onControlName( str, input, quit );

               input.token = getControlTypeEx( input, control );

               switch( input.token )
               {
               case token_control_entity:
                       // don't check limits and subscript
                       onEntity( prevBuf, control.entity, false, false );
                       break;
               case token_control_command:
                       if( control.command->id == ci_frac )
                       {
                               onCommand( str, control, se_use_default, NULL );
                               prevBuf.append( str, true );
                       }
                       else
                       {
                               throw error( input.start, ex_no_command_allowed );
                       }
                       break;
               case token_control_function:
                       onFunction( prevBuf, control.function, false );
                       break;
               //case token_unknown:
               default:
                       throw error( input.start, ex_undefined_control_sequence );
               }
               break;
       default:
               break;
       }
}

static void getSuperscript( Buffer &prevBuf, bool subsup )
{
       if( *pCur == char_prime )
       {
               throw error( pCur, ex_missing_lbrace );
       }

       getCommandParam( prevBuf, se_use_default );

       if( ( *pCur == '^' ) || ( *pCur == char_prime ) )
       {
               throw error( pCur, ex_double_superscript );
       }
       else if( *pCur == '_' )
       {
               if( subsup )
               {
                       throw error( pCur, ex_double_subscript );
               }
               else
               {
                       throw error( pCur, ex_use_subscript_before_superscript );
               }
       }
}

static void onSuperscript( Buffer &prevBuf )
{
       Buffer str;
       int index;
       SymbolTable *sup;

       sup = &nolimits[1];


       index = getLastTagIndex( prevBuf.data(), prevBuf.length(), et_tag_on_open );

       if( index < 0 )
       {
               throw error( pCur, ex_missing_subsup_base );
       }

   str.setlength( 50 );

       prevBuf.insertAt( index, sup->tagOn );

       getSuperscript( str, false );

       str.write( sup->tagOff );

       prevBuf.append( str, true );
}

static void getSubscript( Buffer &prevBuf, command_id &which )
{

       if( *pCur == char_prime )
       {
               throw error( pCur, ex_missing_lbrace );
       }

       getCommandParam( prevBuf, se_use_default );

       if( *pCur == '^' )
       {
               skipChar( &pCur );
               getSuperscript( prevBuf, true );

               which = ci_msubsup;
       }
       else if( *pCur == char_prime )
       {
               onPrime( prevBuf );
               which = ci_msubsup;
       }
       else
       {
               which = ci_msub;
       }
}

static void onSubscript( Buffer &prevBuf )
{
       Buffer str;
       int index;
       command_id which;
       SymbolTable *sub;

       index = getLastTagIndex( prevBuf.data(), prevBuf.length(), et_tag_on_open );

       if( index < 0 )
       {
               throw error( pCur, ex_missing_subsup_base );
       }

   str.setlength( 50 );

       getSubscript( str, which );

       if( which == ci_msub )
       {
               sub = &nolimits[0];
       }
       else
       {
               sub = &nolimits[2];
       }

       prevBuf.insertAt( index, sub->tagOn );
       str.write( sub->tagOff );

       prevBuf.append( str, true );
}



enum limits_type { lt_default, lt_subsup, lt_underover };

static void onLimits( Buffer &prevBuf, math_type mathType )
{
       limits_type useLimits;

       useLimits = lt_default;

       do {
               if( followedBy( &pCur, "\\limits", sp_skip_all ) )
               {
                       useLimits = lt_underover;
               }
               else if( followedBy( &pCur, "\\nolimits", sp_skip_all ) )
               {
                       useLimits = lt_subsup;
               }
               else
               {
                       break;
               }
       } while( 1 );

       if( *pCur == char_null )
       {
               return;
       }
       else if( scriptNext( pCur ) || *pCur == char_prime )
       {
               Buffer str;
               command_id which;
               //char *nextChar;
               int index;
               SymbolTable *lim;

               index = getLastTagIndex( prevBuf.data(), prevBuf.length(), et_tag_on_open );

               if( index < 0 )
               {
                       index = 0;
               }


               if( *pCur == '_' )
               {
                       skipChar( &pCur );
                       getSubscript( str, which );
               }
               else if( *pCur == '^' )
               {
                       skipChar( &pCur );
                       getSuperscript( str, false );
                       which = ci_msup;
               }
               else // prime/////
               {
                       onPrime( str );
                       which = ci_msup;

                       if( *pCur == '^' || *pCur == char_prime )
                       {
                               throw error( pCur, ex_double_superscript );
                       }
                       else if( *pCur == '_' )
                       {
                               throw error( pCur, ex_use_subscript_before_superscript );
                       }
               }

               if( useLimits == lt_underover )
               {
                       lim = limits;
               }
               else if( useLimits == lt_subsup )
               {
                       lim = nolimits;
               }
               else
               {
                       if( mathType == mt_limits )
                       {
                               lim = nolimits;
                       }
                       else
                       {
                               lim = limitsMovable;
                       }
               }

               switch( which )
               {
               case ci_msub:
                       prevBuf.insertAt( index, lim[0].tagOn );
                       str.write( lim[0].tagOff );
                       break;
               case ci_msup:
                       prevBuf.insertAt( index, lim[1].tagOn );
                       str.write( lim[1].tagOff );
                       break;
               case ci_msubsup:
                       prevBuf.insertAt( index, lim[2].tagOn );
                       str.write( lim[2].tagOff );
                       break;
               }
               prevBuf.append( str, true );
       }
}

static void onEntity( Buffer &prevBuf, EntityStruct *entity, bool checkLimits, bool checkSubSup )
{

       switch( entity->mathType )
       {
       case mt_ident:
               prevBuf.format( "<mi>&#x%x;</mi>", entity->code );
               break;
       case mt_digit:
               prevBuf.format( "<mn>&#x%x;</mn>", entity->code );
               break;
       case mt_ord:
       case mt_punct:
               prevBuf.format( "<mo>&#x%x;</mo>", entity->code );
               break;
       case mt_limits:
       case mt_mov_limits:

               prevBuf.format( "<mo>&#x%x;</mo>", entity->code );

               if( checkLimits )
               {
                       onLimits( prevBuf, entity->mathType );
               }
               break;
       case mt_left_fence:
       case mt_right_fence:
       case mt_fence:
               if( checkSubSup && scriptNext( pCur ) )
               {
                       throw error( pCur, ex_ambiguous_script );
               }
               prevBuf.format( "<mo mathsize='1'>&#x%x;</mo>", entity->code );
               break;
       case mt_text:
               prevBuf.format( "<mtext>&#x%x;</mtext>", entity->code );
               break;
       case mt_rel:
       case mt_bin:
       case mt_unary:
       case mt_bin_unary:
               prevBuf.format( "<mo>&#x%x;</mo>", entity->code );
               break;
       default:
               throw error( pCur, ex_unhandled_mathtype );
       }
}

static void onFunction( Buffer &prevBuf, FunctionStruct *function, bool checkLimits  )
{
       prevBuf.format( "<mi>%s</mi>", function->output );

       if( ( function->mathType == mt_func_limits ) && checkLimits )
       {
               onLimits( prevBuf, function->mathType );
       }
}
/*
enum param_type { pt_unknown, pt_none, pt_one, pt_two, pt_three, pt_table, pt_others,
                                 pt_especial };
*/

static void onSqrt( Buffer &prevBuf, const char *tagOn, const char *tagOff )
{

       if( *pCur == char_prime )
       {
               throw error( pCur, ex_missing_lbrace );
       }
       else if( *pCur == '[' )
       {
               Buffer str, radix;

               skipChar( &pCur );
               runLoop( radix, se_optional_param );
               if( radix.length() != 0 )
               {
                       str.write( "<mroot>" );
                       getCommandParam( str, se_use_default );
                       if( needsMrow( radix.data() ) )
                       {
                               radix.insertAt( 0, "<mrow>" );
                               radix.write( "</mrow>" );
                       }
                       str.append( radix, true );
                       str.write( "</mroot>" );
               }
               else
               {
                       prevBuf.write( tagOn );
                       getCommandParam( str, se_use_default );
                       str.write( tagOff );
               }

               prevBuf.append( str, true );
       }
       else
       {
               prevBuf.write( tagOn );
               getCommandParam( prevBuf, se_use_default );
               prevBuf.write( tagOff );
       }
}

static void getAttribute( Buffer &prevBuf, char lastChar )
{
       char *start, *end;

       start = pCur;

       while( *pCur && ( *pCur != lastChar ) )
       {
               ++pCur;
       }

       if( *pCur != lastChar )
       {
               throw error( pCur, ex_missing_end_tag );
       }

       end = pCur - 1;


       while( isspace( *end ) )
       {
               --end;
       }

       if( *end == char_backslash )
       {
               end += 2;
       }
       else
       {
               end++;
       }

       prevBuf.write( start, (end - start ) );

       skipChar( &pCur );

}

static void onMiMnMo( Buffer &prevBuf, const char *tagOn, const char *tagOff )
{
       Buffer str;
       const char* attrib;
       char *start;

       if( *pCur == '[' )
       {
               start = pCur+1;
               skipChar( &pCur );
               getAttribute( str, ']' );

               attrib = getMathVariant( str.data() );

               if( attrib == NULL )
               {
                       throw error( start, ex_unknown_attribute );
               }

               str.destroy();
               str.write( tagOn, strlen(tagOn) - 1 ); // don't include '>'
               str.format( " mathvariant='%s'>", attrib );
       }
       else
       {
               str.write( tagOn );
       }
       onMathFont( prevBuf, str.data(), tagOff );
       //prevBuf.write( tagOff );
}


static EnvironmentStruct *getEnvironmentType()
{
       Buffer str;
       char *curPos;
       EnvironmentStruct *environment;

       if( *pCur != '{' )
       {
               throw error( pCur, ex_missing_lbrace );
       }

       skipChar( &pCur);

       curPos = pCur;

       getAttribute( str, '}' );

       environment = getEnvironmentType( str.data() );

       if( environment != NULL )
       {
               return environment;
       }
       else
       {
               throw error( curPos, ex_undefined_environment_type );
       }
}

static void getColumnAlignment( Buffer &align, short &maxColumn, const char *tagOn )
{
       char *curPos, *p, *attrib;
       Buffer str;

       if( *pCur != '{' )
       {
               throw error( pCur, ex_missing_lbrace );
       }

       skipChar( &pCur );

       curPos = pCur;

       getAttribute( str, '}' );

       if( str.length() == 0 )
       {
               throw error( pCur, ex_missing_column_alignment );
       }

       p = str.data();

       maxColumn = 0;

       attrib = (char *)strchr( tagOn, '>' );

       align.write( tagOn, size_t( attrib - tagOn ) );

       align.write( " columnalign='" );

       while( *p )
       {
               switch( *p )
               {
               case 'l':
                       align.write( "left" );
                       break;
               case 'c':
                       align.write( "center" );
                       break;
               case 'r':
                       align.write( "right" );
                       break;
               default:
                       if( isspace( *p ) )
                       {
                               ++p;
                               continue;
                       }
                       throw error( curPos,    ex_unknown_alignment_character );
               }

               ++maxColumn;
               ++p;
               if( *p )
               {
                       align.write( " " );
               }
       }
       align.format( "'%s", attrib );
}

static void onBeginEnvironment( Buffer &prevBuf  )
{
       ArrayStruct ar;
       Buffer str, align;
       EnvironmentStruct *environment;

       environment = getEnvironmentType();

       ar.id              = environment->id;
       ar.columnCount = 1;
       ar.maxColumn   = 5000; // arbitrary



       switch( environment->id )
       {
       case ci_array:
               getColumnAlignment( align, ar.maxColumn, environment->tagOn );
               prevBuf.append( align, true );
               runLoop( str, se_matrix, &ar );
               break;
       case ci_eqnarray:
               ar.maxColumn = 3;
               // fall through
       default:
               prevBuf.write( environment->tagOn );
               runLoop( str, se_matrix, &ar );
               break;
       }
       str.write( environment->tagOff );
       prevBuf.append( str, true );
}

static bool onEndEnvironment( sub_expression subType, void *paramExtra )
{
       command_id id;
       char *temp;
       EnvironmentStruct *environment;

       temp = pCur;
       if( subType == se_matrix )
       {
               id = ((ArrayStruct *)paramExtra)->id;
       }
       else
       {
               throw error( temp, ex_missing_begin );
       }

       environment = getEnvironmentType();

       if( environment->id != id )
       {
               throw error( temp, ex_mismatched_environment_type );
       }
       return true;
}

static void onColumn( Buffer &prevBuf, const char *pos, ArrayStruct &ar )
{
       ++ar.columnCount;

       if( ar.columnCount > ar.maxColumn )
       {
               throw error( pos, ex_too_many_columns );
       }

       prevBuf.write( "</mtd><mtd>" );
}

static void onRow( Buffer &prevBuf, ArrayStruct &ar )
{
       if( (*pCur == char_backslash ) && (pCur[1] == 'e' ) ) // \end?
       {
               if( followedBy( &pCur, "\\end", sp_no_skip ) )
               {
                       return; // do nothing
               }
       }

       prevBuf.write( "</mtd></mtr><mtr><mtd>" );
       ar.columnCount = 1; // reset columns
}


static void onHfill( Buffer &prevBuf, sub_expression subType, void *paramExtra )
{
       if( scriptNext( pCur ) )
       {
               if( *pCur == '^' )
               {
                       throw error( pCur, ex_prefix_superscript );
               }
               else
               {
                       throw error( pCur, ex_prefix_subscript );
               }
       }
       if( subType < se_matrix ) // not in a table
       {
               return;
       }
}

static void onArrows( Buffer &prevBuf, const char *tagOn, const char *tagOff )
{
       Buffer str;

       //tagOn is the base
       if( *pCur == '[' )
       {
               Buffer underscript;

               skipChar( &pCur );
               runLoop( underscript, se_optional_param );
               if( underscript.length() != 0 )
               {
                       if( needsMrow( underscript.data() ) )
                       {
                               underscript.insertAt( 0, "<mrow>" );
                               underscript.write( "</mrow>" );
                       }
                       str.format( "<munderover>%s", tagOn );

                       str.append( underscript, true );

                       getCommandParam( str, se_use_default );

                       str.write( "</munderover>" );
                       prevBuf.append( str, true );
                       return;
               }
       }

       // fall through
       //prevBuf.write( tagOn );
       str.format( "<mover>%s", tagOn );
       getCommandParam( str, se_use_default );
       str.write( tagOff );
       prevBuf.append( str, true );
}

static void onCfrac( Buffer &prevBuf, const char *tagOn, const char *tagOff )
{
       Buffer str;
       const char *extra = "<mstyle displaystyle='true' scriptlevel='0'>";

       str.format( "%s%s", tagOn, extra );
       getCommandParam( str, se_use_default );
       str.format( "</mstyle>%s", extra );
       getCommandParam( str, se_use_default );
       str.write( tagOff );
       prevBuf.append( str, true );
}


static bool onCommand( Buffer &prevBuf, ControlStruct &control, sub_expression subType, void *paramExtra )
{
       Buffer str, str2;
       CommandStruct *command;

       command = control.command;

       switch( command->param )
       {
       case pt_plain:
               switch( command->id )
               {
               case ci_mn:
               case ci_mo:
                       onMiMnMo( prevBuf, command->tagOn, command->tagOff );
                       break;
               case ci_mathop:
                       onMathFont( str, command->tagOn, command->tagOff );
                       //str.write( command->tagOff );
                       onLimits(str, mt_limits );
                       prevBuf.append( str, true );
                       break;
               /*
               case ci_mathrm:
               case ci_mathit:
               case ci_mathbf:
               case ci_mathbi:

               case ci_mathord,
               case ci_func:
               */
               case ci_mathfont:
               case ci_mathord:
               case ci_mathbin:
               case ci_mathrel:
                       //str.write( command->tagOn );
                       onMathFont( str, command->tagOn, command->tagOff );
                       //str.write( command->tagOff );
                       prevBuf.append( str, true );
               }
               break;
       case pt_especial:
               switch( command->id )
               {
               //case ci_mi:

               case ci_sqrt:
                       onSqrt( prevBuf, command->tagOn, command->tagOff );
                       break;
               case ci_begin:
                               onBeginEnvironment( prevBuf );
                               break;
               case ci_end:
                           return onEndEnvironment( subType, paramExtra );
               case ci_stackrel:
                       str.write( command->tagOn );
                       getCommandParam( str2, se_use_default );
                       getCommandParam( str, se_use_default );
                       str.append( str2, true );
                       str.write( command->tagOff );
                       prevBuf.append( str, true );
                       break;
               case ci_hfill:
                       onHfill( prevBuf, subType, paramExtra );
                       break;
               case ci_strut:
                       prevBuf.write( command->tagOn );
                       break;
               case ci_limits:
               case ci_nolimits:
                       throw error( control.start, ex_misplaced_limits );
               case ci_mathstring:
                       onTextFont( prevBuf, command->tagOn, command->tagOff, command->id, false );
                       break;
               case ci_text:
                       onTextFont( prevBuf, command->tagOn, command->tagOff, command->id );
                       break;
               case ci_eqno:
               case ci_leqno:
                       if( subType != se_use_default )
                       {
                               throw error( pCur, ex_misplaced_eqno );
                       }
                       else if ( isNumberedFormula )
                       {
                               throw error( pCur, ex_duplicate_eqno );
                       }
                       isNumberedFormula = true;
                       onTextFont( eqNumber, command->tagOn, command->tagOff, ci_eqno, false );
                       break;
               case ci_left:
               case ci_right:
                       return onFence( prevBuf, command->id, subType, command->tagOn, command->tagOff );
               case ci_ext_arrows:
                       onArrows( prevBuf, command->tagOn, command->tagOff );
                       break;
               case ci_cfrac:
                       onCfrac( prevBuf, command->tagOn, command->tagOff );
                       break;
               case ci_underoverbrace:
                       str.write( command->tagOn );
                       getCommandParam( str, se_use_default );
                       str.write( command->tagOff );
                       onLimits( str, mt_mov_limits );
                       prevBuf.append( str, true );
                       break;
               case ci_lsub:
                       str.write( command->tagOn );
                       getCommandParam( str, se_use_default );
                       str.write( "<mprescripts/>" );
                       getCommandParam( str, se_use_default );
                       str.write( "<none/>" );
                       str.write( command->tagOff );
                       prevBuf.append( str, true );
                       break;
               case ci_lsup:
                       str.write( command->tagOn );
                       getCommandParam( str, se_use_default );
                       str.write( "<mprescripts/><none/>" );
                       getCommandParam( str, se_use_default );
                       str.write( command->tagOff );
                       prevBuf.append( str, true );
                       break;
               case ci_lsubsup:
                       str.write( command->tagOn );
                       getCommandParam( str, se_use_default );
                       str.write( "<mprescripts/>" );
                       getCommandParam( str, se_use_default );
                       getCommandParam( str, se_use_default );
                       str.write( command->tagOff );
                       prevBuf.append( str, true );
                       break;
               default:
                       break;
               }
               break;

       case pt_one:
               prevBuf.write( command->tagOn );
               getCommandParam( prevBuf, se_use_default );
               prevBuf.write( command->tagOff );
               break;

       case pt_two:
               prevBuf.write( command->tagOn );
               getCommandParam( prevBuf, se_use_default );
               getCommandParam( prevBuf, se_use_default );
               prevBuf.write( command->tagOff );
               break;

       case pt_three:
               break;

       case pt_table:
               break;

       case pt_none:
               break;

       case pt_others:
       default:
               break;
       }

       return false;
}

static void onMathFont( Buffer &prevBuf, const char *tagOn, const char *tagOff )
{
       InputStream input;
       ControlStruct control;
       SymbolStruct *symbol;
       Buffer str;
       int brace;
       bool quitLoop;

       brace = 0;

       if( *pCur == '{' )
       {
               quitLoop  = false;      // loop
       }
       else
       {
               quitLoop = true;        // read only one char
       }


       while( getInput( input, sp_skip_all ) )
       {
               switch( input.token )
               {
               case token_alpha:
               case token_digit:
                       str.write( pCur, 1 );
                       ++pCur;
                       break;
               case token_prime:
                       {
                               SymbolStruct *sym;
                               char buf[5];

                               getPrime( &pCur, buf );

                               sym = getSymbol( buf );
                               str.write( sym->literal );
                       }
                       break;
               case token_symbol:
               case token_control_symbol:
               case token_right_sq_bracket:
                       symbol = getSymbol( input.buffer );
                       str.write( symbol->literal );
                       break;

               case token_white_space:
                       str.write( "&#x00A0;" );
                       if( isspace( *pCur ) )
                       {
                               skipSpaces( &pCur );
                       }
                       break;
               case token_left_brace:
                       ++brace;
                       break;
               case token_right_brace:
                       --brace;
                       if( brace < 0 )
                       {
                               throw error( input.start, ex_missing_parameter );
                       }
                       quitLoop = true;
                       break;
               case token_inline_math:
                       throw error( input.start, ex_misplaced_inline_formula );

               case token_superscript:
               case token_subscript:
                       throw error( input.start, ex_no_command_allowed );
               case token_column_sep:
                       throw error( input.start, ex_misplaced_column_separator );
               case token_row_sep:
                       throw error( input.start, ex_misplaced_row_separator );

               case token_control_name:

                       switch( getControlTypeEx( input, control ) )
                       {
                       case token_control_entity:
                               str.format( "&#x%x;", control.entity->code );
                               break;
                       case token_control_function:
                               str.write( control.function->output );
                               break;
                       case token_control_command:
                               throw error( input.start, ex_no_command_allowed );
                               break;
                       //case token_unknown:
                       default:
                               throw error( input.start, ex_undefined_control_sequence );
                       }
                       break;
               default:
                       break;
               }
               if( quitLoop )
               {
                       break;
               }
       }

       prevBuf.write( tagOn );
       str.write( tagOff );
       prevBuf.append( str, true );
}


static void onTextFont( Buffer &prevBuf, const char *tagOn, const char *tagOff, command_id id, bool allowInline )
{
       InputStream input;
       ControlStruct control;
       SymbolStruct *symbol;
       Buffer str, temp;
       int brace;
       bool quitLoop;

       brace = 0;

       if( ( id != ci_eqno ) && ( id != ci_leqno ) )
       {
               if( *pCur != '{' )
               {
                       throw error( pCur, ex_missing_lbrace );
               }
       }

       quitLoop           = false;

       while( getInput( input, sp_skip_once ) )
       {
               switch( input.token )
               {
               case token_alpha:
               case token_digit:
                       str.write( pCur, 1 );
                       ++pCur;
                       break;

               case token_symbol:
               case token_control_symbol:
               case token_right_sq_bracket:
                       symbol = getSymbol( input.buffer );
                       str.write( symbol->literal );
                       break;
               case token_prime:
                       if( pCur[1] == char_prime )
                       {
                               str.write( "&#x201D;" );
                               pCur += 2;
                       }
                       else
                       {
                               str.write( "&#x2019;" );
                               ++pCur;
                       }
                       break;
               case token_inline_math:
                       if( !allowInline )
                       {
                               throw error( input.start, ex_misplaced_inline_formula );
                       }
                       if( str.length() > 0 )
                       {
                               temp.write( tagOn );
                               str.write( tagOff );
                               temp.append( str, true );
                               str.reset();
                       }

                       skipChar( &pCur );
                       runLoop( str, se_inline_math, NULL );
                               // skip end $
                       ++pCur;

                       if( needsMrow( str.data() ) )
                       {
                               str.insertAt( 0, "<mrow>" );
                               str.write( "</mrow>" );
                       }
                       temp.append( str, true );
                       str.reset();
                       break;

               case token_white_space:
                       str.write( "&#x00A0;" );
                       if( isspace( *pCur ) )
                       {
                               skipSpaces( &pCur );
                       }
                       break;
               case token_left_brace:
                       ++brace;
                       break;
               case token_right_brace:
                       --brace;
                       if( brace < 0 )
                       {
                               throw error( input.start, ex_missing_parameter );
                       }
                       quitLoop = true;
                       break;
               case token_superscript:
               case token_subscript:
                       throw error( input.start, ex_no_command_allowed );
               case token_column_sep:
                       throw error( input.start, ex_misplaced_column_separator );
               case token_row_sep:
                       throw error( input.start, ex_misplaced_row_separator );

               case token_control_name:

                       switch( getControlTypeEx( input, control ) )
                       {
                       case token_control_entity:
                               str.format( "&#x%x;", control.entity->code );
                               break;
                       case token_control_function:
                               throw error( input.start, ex_not_math_mode );
                       case token_control_command:
                               throw error( input.start, ex_no_command_allowed );
                       //case token_unknown:
                       default:
                               throw error( input.start, ex_undefined_control_sequence );
                       }
                       break;
               default:
                       break;
               }
               if( quitLoop )
               {
                       break;
               }
       }


       if( str.length() )
       {
               temp.write( tagOn );
               str.write( tagOff );
       }
       temp.append( str, true );

       if( needsMrow( temp.data() ) )
       {
               temp.insertAt( 0, "<mrow>" );
               temp.write( "</mrow>" );
       }
       prevBuf.append( temp, true );
}

static void getFence( Buffer &prevBuf, const char *tagOn, command_id id )
{
       InputStream input;
       FenceStruct fence;
       size_t len;


       len = strlen( tagOn );

       getInput( input, sp_skip_all );

       switch( input.token )
       {
       case token_control_symbol:
       case token_symbol:
       case token_control_name:
               if( !getFenceType( input.buffer, fence ) )
               {
                       throw error( input.start, ex_missing_fence_parameter );
               }

               if ( id == ci_left )
               {
                       if( *input.start == '(' )
                       {
                               prevBuf.write( tagOn, len );
                       }
                       else
                       {
                               prevBuf.write( tagOn, len - 1 ); // don't write >
                               prevBuf.format( " left='%s'", fence.output );
                       }
               }
               else
               {
                       if( *input.start == ')' )
                       {
                               prevBuf.write( "><mrow>" );
                       }
                       else
                       {
                               prevBuf.format( " right='%s'><mrow>", fence.output );
                       }
               }
               break;
       default:
               throw error( input.start, ex_missing_fence_parameter );
       }
}

static bool onFence( Buffer &prevBuf, command_id id, sub_expression subType, const char *tagOn, const char *tagOff )
{
       Buffer str, fence;

       if( id == ci_right )
       {
               if( subType == se_fence )
               {
                       return true;
               }
               throw error( pCur, ex_missing_left_fence );
       }

       getFence( fence, tagOn, ci_left );
       runLoop( str, se_fence, NULL );
       getFence( fence, tagOn, ci_right );

       str.write( tagOff );
       prevBuf.append( fence, true );
       prevBuf.append( str, true );
       return false;
}