/* ==================================================================== 
 * The Kannel Software License, Version 1.0 
 * 
 * Copyright (c) 2001-2018 Kannel Group  
 * Copyright (c) 1998-2001 WapIT Ltd.   
 * All rights reserved. 
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions 
 * are met: 
 * 
 * 1. Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer. 
 * 
 * 2. Redistributions in binary form must reproduce the above copyright 
 *    notice, this list of conditions and the following disclaimer in 
 *    the documentation and/or other materials provided with the 
 *    distribution. 
 * 
 * 3. The end-user documentation included with the redistribution, 
 *    if any, must include the following acknowledgment: 
 *       "This product includes software developed by the 
 *        Kannel Group (http://www.kannel.org/)." 
 *    Alternately, this acknowledgment may appear in the software itself, 
 *    if and wherever such third-party acknowledgments normally appear. 
 * 
 * 4. The names "Kannel" and "Kannel Group" must not be used to 
 *    endorse or promote products derived from this software without 
 *    prior written permission. For written permission, please  
 *    contact org@kannel.org. 
 * 
 * 5. Products derived from this software may not be called "Kannel", 
 *    nor may "Kannel" appear in their name, without prior written 
 *    permission of the Kannel Group. 
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED.  IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS 
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,  
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT  
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR  
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE  
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 * ==================================================================== 
 * 
 * This software consists of voluntary contributions made by many 
 * individuals on behalf of the Kannel Group.  For more information on  
 * the Kannel Group, please see <http://www.kannel.org/>. 
 * 
 * Portions of this software are based upon software originally written at  
 * WapIT Ltd., Helsinki, Finland for the Kannel project.  
 */ 

/*
 * decompile.c - A program to test the WML compiler. This tool was written
 *               from the WBXML 1.2 and WML 1.1 specs.
 *
 * Author: Chris Wulff, Vanteon (cwulff@vanteon.com)
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>

#include "decompile.h"

const WBXML_MB_U_INT32 ZERO_WBXML_MB_U_INT32 = {0,0,0,0};
long dtd_id;

#define INDENT_SIZE		4

DTD_TYPE_LIST DTDTypeList[] =
{
	{1, "UNKNOWN"},
	{2, "-//WAPFORUM//DTD WML 1.0//EN\"\n"
	 "\"http://www.wapforum.org/DTD/wml.xml"},
	{3, "-//WAPFORUM//DTD WTA 1.0//EN"},
	{4, "-//WAPFORUM//DTD WML 1.1//EN\"\n"
	 "\"http://www.wapforum.org/DTD/wml_1.1.xml"},
	{5, "-//WAPFORUM//DTD SI 1.0//EN\"\n"
	 "\"http://www.wapforum.org/DTD/si.dtd"},
	{6, "-//WAPFORUM//DTD SL 1.0//EN\"\n"
	 "\"http://www.wapforum.org/DTD/sl.dtd"},
	{7, "-//WAPFORUM//DTD CO 1.0//EN"},
	{8, "-//WAPFORUM//DTD CHANNEL 1.1//EN"},
	{9, "-//WAPFORUM//DTD WML 1.2//EN\"\n"
	 "\"http://www.wapforum.org/DTD/wml12.dtd"},
	{0, NULL}
};


/**************************************
 * DTD Public Type 4 (WML 1.1) Tables *
 **************************************/

CODEPAGE_TAG_NAME_LIST CodepageTagNames[] =
{
	{4, "a",         0, 0x1c},
	{4, "anchor",    0, 0x22},
	{4, "access",    0, 0x23},
	{4, "b",         0, 0x24},
	{4, "big",       0, 0x25},
	{4, "br",        0, 0x26},
	{4, "card",      0, 0x27},
	{4, "do",        0, 0x28},
	{4, "em",        0, 0x29},
	{4, "fieldset",  0, 0x2a},
	{4, "go",        0, 0x2b},
	{4, "head",      0, 0x2c},
	{4, "i",         0, 0x2d},
	{4, "img",       0, 0x2e},
	{4, "input",     0, 0x2f},
	{4, "meta",      0, 0x30},
	{4, "noop",      0, 0x31},
	{4, "p",         0, 0x20},
	{4, "postfield", 0, 0x21},
	{4, "pre",       0, 0x1b},
	{4, "prev",      0, 0x32},
	{4, "onevent",   0, 0x33},
	{4, "optgroup",  0, 0x34},
	{4, "option",    0, 0x35},
	{4, "refresh",   0, 0x36},
	{4, "select",    0, 0x37},
	{4, "setvar",    0, 0x3e},
	{4, "small",     0, 0x38},
	{4, "strong",    0, 0x39},
	{4, "table",     0, 0x1f},
	{4, "td",        0, 0x1d},
	{4, "template",  0, 0x3b},
	{4, "timer",     0, 0x3c},
	{4, "tr",        0, 0x1e},
	{4, "u",         0, 0x3d},
	{4, "wml",       0, 0x3f},

	{6, "TAG_05",    1, 0x05},
	{6, "TAG_06",    1, 0x06},
	{6, "TAG_07",    1, 0x07},

	{0, NULL, 0, 0}
};

CODEPAGE_ATTRSTART_NAME_LIST CodepageAttrstartNames[] =
{
	{4, "accept-charset",  NULL,                                0, 0x05},
	{4, "accesskey",       NULL,                                0, 0x5e},
	{4, "align",           NULL,                                0, 0x52},
	{4, "align",           "bottom",                            0, 0x06},
	{4, "align",           "center",                            0, 0x07},
	{4, "align",           "left",                              0, 0x08},
	{4, "align",           "middle",                            0, 0x09},
	{4, "align",           "right",                             0, 0x0a},
	{4, "align",           "top",                               0, 0x0b},
	{4, "alt",             NULL,                                0, 0x0c},
	{4, "class",           NULL,                                0, 0x54},
	{4, "columns",         NULL,                                0, 0x53},
	{4, "content",         NULL,                                0, 0x0d},
	{4, "content",         "application/vnd.wap.wmlc;charset=", 0, 0x5c},
	{4, "domain",          NULL,                                0, 0x0f},
	{4, "emptyok",         "false",                             0, 0x10},
	{4, "emptyok",         "true",                              0, 0x11},
	{4, "enctype",         NULL,                                0, 0x5f},
	{4, "enctype",         "application/x-www-form-urlencoded", 0, 0x60},
	{4, "enctype",         "multipart/form-data",               0, 0x61},
	{4, "format",          NULL,                                0, 0x12},
	{4, "forua",           "false",                             0, 0x56},
	{4, "forua",           "true",                              0, 0x57},
	{4, "height",          NULL,                                0, 0x13},
	{4, "href",            NULL,                                0, 0x4a},
	{4, "href",            "http://",                           0, 0x4b},
	{4, "href",            "https://",                          0, 0x4c},
	{4, "hspace",          NULL,                                0, 0x14},
	{4, "http-equiv",      NULL,                                0, 0x5a},
	{4, "http-equiv",      "Content-Type",                      0, 0x5b},
	{4, "http-equiv",      "Expires",                           0, 0x5d},
	{4, "id",              NULL,                                0, 0x55},
	{4, "ivalue",          NULL,                                0, 0x15},
	{4, "iname",           NULL,                                0, 0x16},
	{4, "label",           NULL,                                0, 0x18},
	{4, "localsrc",        NULL,                                0, 0x19},
	{4, "maxlength",       NULL,                                0, 0x1a},
	{4, "method",          "get",                               0, 0x1b},
	{4, "method",          "post",                              0, 0x1c},
	{4, "mode",            "nowrap",                            0, 0x1d},
	{4, "mode",            "wrap",                              0, 0x1e},
	{4, "multiple",        "false",                             0, 0x1f},
	{4, "multiple",        "true",                              0, 0x20},
	{4, "name",            NULL,                                0, 0x21},
	{4, "newcontext",      "false",                             0, 0x22},
	{4, "newcontext",      "true",                              0, 0x23},
	{4, "onenterbackward", NULL,                                0, 0x25},
	{4, "onenterforward",  NULL,                                0, 0x26},
	{4, "onpick",          NULL,                                0, 0x24},
	{4, "ontimer",         NULL,                                0, 0x27},
	{4, "optional",        "false",                             0, 0x28},
	{4, "optional",        "true",                              0, 0x29},
	{4, "path",            NULL,                                0, 0x2a},
	{4, "scheme",          NULL,                                0, 0x2e},
	{4, "sendreferer",     "false",                             0, 0x2f},
	{4, "sendreferer",     "true",                              0, 0x30},
	{4, "size",            NULL,                                0, 0x31},
	{4, "src",             NULL,                                0, 0x32},
	{4, "src",             "http://",                           0, 0x58},
	{4, "src",             "https://",                          0, 0x59},
	{4, "ordered",         "true",                              0, 0x33},
	{4, "ordered",         "false",                             0, 0x34},
	{4, "tabindex",        NULL,                                0, 0x35},
	{4, "title",           NULL,                                0, 0x36},
	{4, "type",            NULL,                                0, 0x37},
	{4, "type",            "accept",                            0, 0x38},
	{4, "type",            "delete",                            0, 0x39},
	{4, "type",            "help",                              0, 0x3a},
	{4, "type",            "password",                          0, 0x3b},
	{4, "type",            "onpick",                            0, 0x3c},
	{4, "type",            "onenterbackward",                   0, 0x3d},
	{4, "type",            "onenterforward",                    0, 0x3e},
	{4, "type",            "ontimer",                           0, 0x3f},
	{4, "type",            "options",                           0, 0x45},
	{4, "type",            "prev",                              0, 0x46},
	{4, "type",            "reset",                             0, 0x47},
	{4, "type",            "text",                              0, 0x48},
	{4, "type",            "vnd.",                              0, 0x49},
	{4, "value",           NULL,                                0, 0x4d},
	{4, "vspace",          NULL,                                0, 0x4e},
	{4, "width",           NULL,                                0, 0x4f},
	{4, "xml:lang",        NULL,                                0, 0x50},

	{6, "ATTR_06",         NULL,                                1, 0x06},
	{6, "ATTR_07",         NULL,                                1, 0x07},
	{6, "ATTR_08",         NULL,                                1, 0x08},
	{6, "ATTR_11",         NULL,                                1, 0x11},
	{6, "ATTR_12",         NULL,                                1, 0x12},
	{6, "ATTR_13",         NULL,                                1, 0x13},
	{6, "ATTR_14",         NULL,                                1, 0x14},
	{6, "ATTR_15",         NULL,                                1, 0x15},
	{6, "ATTR_21",         NULL,                                1, 0x21},
	{6, "ATTR_22",         NULL,                                1, 0x22},
	{6, "ATTR_23",         NULL,                                1, 0x23},
	{6, "ATTR_24",         NULL,                                1, 0x24},
	{6, "ATTR_28",         NULL,                                1, 0x28},
	{6, "ATTR_29",         NULL,                                1, 0x29},
	{6, "ATTR_45",         NULL,                                1, 0x45},
	{6, "ATTR_61",         NULL,                                1, 0x61},
	{6, "ATTR_62",         NULL,                                1, 0x62},
	{6, "ATTR_63",         NULL,                                1, 0x63},
	{6, "ATTR_64",         NULL,                                1, 0x64},
	{6, "ATTR_6A",         NULL,                                1, 0x6A},
	{6, "ATTR_6B",         NULL,                                1, 0x6B},
	{6, "ATTR_6C",         NULL,                                1, 0x6C},
	{6, "ATTR_70",         NULL,                                1, 0x70},
	{6, "ATTR_71",         NULL,                                1, 0x71},
	{6, "ATTR_73",         NULL,                                1, 0x73},
	{6, "ATTR_74",         NULL,                                1, 0x74},

	{0, NULL,              NULL,                                0, 0}
};

CODEPAGE_ATTRVALUE_NAME_LIST CodepageAttrvalueNames[] =
{
	{4, ".com/",           0, 0x85},
	{4, ".edu/",           0, 0x86},
	{4, ".net/",           0, 0x87},
	{4, ".org/",           0, 0x88},
	{4, "accept",          0, 0x89},
	{4, "bottom",          0, 0x8a},
	{4, "clear",           0, 0x8b},
	{4, "delete",          0, 0x8c},
	{4, "help",            0, 0x8d},
	{4, "http://",         0, 0x8e},
	{4, "http://www.",     0, 0x8f},
	{4, "https://",        0, 0x90},
	{4, "https://www.",    0, 0x91},
	{4, "middle",          0, 0x93},
	{4, "nowrap",          0, 0x94},
	{4, "onenterbackward", 0, 0x96},
	{4, "onenterforward",  0, 0x97},
	{4, "onpick",          0, 0x95},
	{4, "ontimer",         0, 0x98},
	{4, "options",         0, 0x99},
	{4, "password",        0, 0x9a},
	{4, "reset",           0, 0x9b},
	{4, "text",            0, 0x9d},
	{4, "top",             0, 0x9e},
	{4, "unknown",         0, 0x9f},
	{4, "wrap",            0, 0xa0},
	{4, "www.",            0, 0xa1},
	{0, NULL, 0, 0}
};


/**************************
 * Node Tree Construction *
 **************************/

/*
 * Function: NewNode
 *
 * Description:
 *
 *  Allocate and initialize a new node. This links the new node
 *  as the first child of the current node in the buffer. This causes
 *  child nodes to be linked in reverse order. If there is no current
 *  node, then the new node will be linked in as the first child at the
 *  top of the tree.
 *
 * Parameters:
 *
 *  buffer - WBXML buffer to link the new node into
 *  type   - Type of node to allocate
 *
 * Return value:
 *
 *  P_WBXML_NODE - A pointer to the newly allocated node.
 *
 */
static P_WBXML_NODE NewNode(P_WBXML_INFO buffer, WBXML_NODE_TYPE type)
{
	if (buffer)
	{
		P_WBXML_NODE newnode = malloc(sizeof(WBXML_NODE));

		if (newnode)
		{
			newnode->m_prev = NULL;
			newnode->m_child = NULL;

			if (buffer->m_curnode)
			{
				/* Insert this node as the first child of the current node */
				newnode->m_parent = buffer->m_curnode;
				newnode->m_next = buffer->m_curnode->m_child;

				if (buffer->m_curnode->m_child)
				{
					((P_WBXML_NODE)buffer->m_curnode->m_child)->m_prev = newnode;
				}

				buffer->m_curnode->m_child = newnode;
			}
			else
			{
				/* Insert this node at the top of the tree */
				newnode->m_parent = NULL;
				newnode->m_next = buffer->m_tree;
				
				if (buffer->m_tree)
				{
					buffer->m_tree->m_prev = newnode;
				}

				buffer->m_tree = newnode;
			}

			newnode->m_page = buffer->m_curpage;
			newnode->m_type = type;
			newnode->m_data = NULL;
		}
		else
		{
			ParseError(ERR_NOT_ENOUGH_MEMORY);
		}

		return newnode;
	}
	else
	{
		ParseError(ERR_INTERNAL_BAD_PARAM);
	}

	return NULL;
}

/*
 * Function: FreeNode
 *
 * Description:
 *
 *  Free a node, all its children and forward siblings.
 *
 * Parameters:
 *
 *  node - The node to free
 *
 */
static void FreeNode(P_WBXML_NODE node)
{
	if (node)
	{
		if (node->m_child)
		{
			FreeNode(node->m_child);
		}

		if (node->m_next)
		{
			FreeNode(node->m_next);
		}

		free(node);
	}
}

static void AddDTDNode(P_WBXML_INFO buffer, const WBXML_DTD_TYPE dtdnum, const WBXML_MB_U_INT32 index)
{
	P_WBXML_NODE newnode = NewNode(buffer, NODE_DTD_TYPE);
	newnode->m_data = malloc(sizeof(DTD_NODE_DATA));
	memcpy( &( ((DTD_NODE_DATA*)newnode->m_data)->m_dtdnum ), &(dtdnum[0]), sizeof(WBXML_MB_U_INT32) );
	memcpy( &( ((DTD_NODE_DATA*)newnode->m_data)->m_index ), &(index[0]), sizeof(WBXML_MB_U_INT32) );
	dtd_id = (long) dtdnum[0];
}

static void AddStringTableNode(P_WBXML_INFO buffer, const P_WBXML_STRING_TABLE strings)
{
	P_WBXML_NODE newnode = NewNode(buffer, NODE_STRING_TABLE);
	newnode->m_data = malloc(sizeof(WBXML_STRING_TABLE));
	memcpy( newnode->m_data, strings, sizeof(WBXML_STRING_TABLE) );
}

static void AddCodepageTagNode(P_WBXML_INFO buffer, WBXML_TAG tag)
{
	P_WBXML_NODE newnode = NewNode(buffer, NODE_CODEPAGE_TAG);
	newnode->m_data = malloc(sizeof(WBXML_TAG));
	*((P_WBXML_TAG)newnode->m_data) = tag;
}

static void AddCodepageLiteralTagNode(P_WBXML_INFO buffer, WBXML_MB_U_INT32 index)
{
	P_WBXML_NODE newnode = NewNode(buffer, NODE_CODEPAGE_LITERAL_TAG);
	newnode->m_data = malloc(sizeof(WBXML_MB_U_INT32));
	memcpy( ((P_WBXML_MB_U_INT32)newnode->m_data), &index, sizeof(WBXML_MB_U_INT32) );
}

static void AddAttrStartNode(P_WBXML_INFO buffer, WBXML_TAG tag)
{
	P_WBXML_NODE newnode = NewNode(buffer, NODE_ATTRSTART);
	newnode->m_data = malloc(sizeof(WBXML_TAG));
	*((P_WBXML_TAG)newnode->m_data) = tag;
}

static void AddAttrStartLiteralNode(P_WBXML_INFO buffer, WBXML_MB_U_INT32 index)
{
	P_WBXML_NODE newnode = NewNode(buffer, NODE_ATTRSTART_LITERAL);
	newnode->m_data = malloc(sizeof(WBXML_MB_U_INT32));
	memcpy( ((P_WBXML_MB_U_INT32)newnode->m_data), &index, sizeof(WBXML_MB_U_INT32) );
}

static void AddAttrValueNode(P_WBXML_INFO buffer, WBXML_TAG tag)
{
	P_WBXML_NODE newnode = NewNode(buffer, NODE_ATTRVALUE);
	newnode->m_data = malloc(sizeof(WBXML_TAG));
	*((P_WBXML_TAG)newnode->m_data) = tag;
}

static void AddAttrEndNode(P_WBXML_INFO buffer)
{
	P_WBXML_NODE newnode = NewNode(buffer, NODE_ATTREND);
	newnode->m_data = NULL;
}

static void AddStringNode(P_WBXML_INFO buffer, char* string)
{
	P_WBXML_NODE newnode = NewNode(buffer, NODE_STRING);
	newnode->m_data = strdup(string);
}

static void AddVariableStringNode(P_WBXML_INFO buffer, char* string, WBXML_VARIABLE_TYPE type)
{
	/* TODO: add this node */
}

static void AddVariableIndexNode(P_WBXML_INFO buffer, char* string, WBXML_VARIABLE_TYPE type)
{
	/* TODO: add this node */
}


/****************
 * Flow Control *
 ****************/

void Message(char* msg)
{
  printf("%s\n", msg);
}

void ParseError(WBXML_PARSE_ERROR error)
{
  switch (error)
  {
    case ERR_END_OF_DATA:
      Message("Input stream is incomplete (EOF).");
      break;

    case ERR_INTERNAL_BAD_PARAM:
      Message("Internal error: Bad parameter.");
      break;

	case ERR_TAG_NOT_FOUND:
      Message("Tag not found.");
      break;
		
	case ERR_FILE_NOT_FOUND:
      Message("File not found.");
      break;

	case ERR_FILE_NOT_READ:
      Message("File read error.");
      break;

	case ERR_NOT_ENOUGH_MEMORY:
      Message("Not enough memory");
      break;

    default:
      Message("Unknown error.");
      break;
  }

  exit(error);
}

void ParseWarning(WBXML_PARSE_WARNING warning)
{
  switch (warning)
  {
    case WARN_FUTURE_EXPANSION_EXT_0:
      Message("Token EXT_0 encountered. This token is reserved for future expansion.");
      break;

    case WARN_FUTURE_EXPANSION_EXT_1:
      Message("Token EXT_1 encountered. This token is reserved for future expansion.");
      break;

    case WARN_FUTURE_EXPANSION_EXT_2:
      Message("Token EXT_2 encountered. This token is reserved for future expansion.");
      break;

    default:
      Message("Unknown warning.");
      break;
  }
}

WBXML_LENGTH BytesLeft(P_WBXML_INFO buffer)
{
  if (buffer)
  {
    WBXML_LENGTH bytesRead = (buffer->m_curpos - buffer->m_start);
    if (bytesRead >= buffer->m_length)
    {
      return 0;
    }
    else
    {
      return (buffer->m_length - bytesRead);
    }
  }
  else
  {
    ParseError(ERR_INTERNAL_BAD_PARAM);
  }

  return 0;
}

BOOL IsTag(P_WBXML_INFO buffer, WBXML_TAG tag)
{
  BOOL result = FALSE;

  if (buffer)
  {
    if (BytesLeft(buffer) >= sizeof(WBXML_TAG))
    {
      result = ((*((WBXML_TAG*) buffer->m_curpos)) == tag);
    }
    else
    {
		/* No more data, so nope, not this tag */
      result = FALSE;
    }
  }
  else
  {
    ParseError(ERR_INTERNAL_BAD_PARAM);
  }

  return result;
}

BOOL IsCodepageTag(P_WBXML_INFO buffer, CP_TAG_TYPE type)
{
	WBXML_U_INT8 result = *(buffer->m_curpos);

	/* NOTE THAT THESE ARE NOT UNIQUE! */
	switch (type)
	{
		case CP_TAG_TAG:
			return TRUE;
		case CP_TAG_ATTRSTART:
			return ((result & 0x80) != 0x80);
		case CP_TAG_ATTRVALUE:
			return ((result & 0x80) == 0x80);
		default:
			return FALSE;
	}
}

BOOL Is_attrValue  (P_WBXML_INFO buffer)
{
	WBXML_INFO tmpbuffer;
	memcpy(&tmpbuffer, buffer, sizeof(WBXML_INFO));
	tmpbuffer.m_curpos += SWITCHPAGE_SIZE;

	return ((Is_switchPage(buffer) && IsCodepageTag(&tmpbuffer, CP_TAG_ATTRVALUE)) ||
		    IsCodepageTag(buffer, CP_TAG_ATTRVALUE) ||
			Is_string(buffer) ||
			Is_extension(buffer) ||
			Is_entity(buffer) ||
			Is_pi(buffer) ||
			Is_opaque(buffer));
}

BOOL Is_extension  (P_WBXML_INFO buffer)
{
	WBXML_INFO tmpbuffer;
	memcpy(&tmpbuffer, buffer, sizeof(WBXML_INFO));
	tmpbuffer.m_curpos += SWITCHPAGE_SIZE;

	return ((Is_switchPage(buffer) &&
		     (IsTag(&tmpbuffer, TAG_EXT_0) ||
		      IsTag(&tmpbuffer, TAG_EXT_1) ||
		      IsTag(&tmpbuffer, TAG_EXT_2) ||
		      IsTag(&tmpbuffer, TAG_EXT_T_0) ||
		      IsTag(&tmpbuffer, TAG_EXT_T_1) ||
		      IsTag(&tmpbuffer, TAG_EXT_T_2) ||
		      IsTag(&tmpbuffer, TAG_EXT_I_0) ||
		      IsTag(&tmpbuffer, TAG_EXT_I_1) ||
		      IsTag(&tmpbuffer, TAG_EXT_I_2))) ||
		    (IsTag(buffer, TAG_EXT_0) ||
		     IsTag(buffer, TAG_EXT_1) ||
		     IsTag(buffer, TAG_EXT_2) ||
		     IsTag(buffer, TAG_EXT_T_0) ||
		     IsTag(buffer, TAG_EXT_T_1) ||
		     IsTag(buffer, TAG_EXT_T_2) ||
		     IsTag(buffer, TAG_EXT_I_0) ||
		     IsTag(buffer, TAG_EXT_I_1) ||
		     IsTag(buffer, TAG_EXT_I_2)));
}

BOOL Is_string     (P_WBXML_INFO buffer)
{
	return (Is_inline(buffer) ||
		    Is_tableref(buffer));
}

BOOL Is_switchPage (P_WBXML_INFO buffer)
{
	return IsTag(buffer, TAG_SWITCH_PAGE);
}

BOOL Is_inline     (P_WBXML_INFO buffer)
{
	return IsTag(buffer, TAG_STR_I);
}

BOOL Is_tableref   (P_WBXML_INFO buffer)
{
	return IsTag(buffer, TAG_STR_T);
}

BOOL Is_entity     (P_WBXML_INFO buffer)
{
	return IsTag(buffer, TAG_ENTITY);
}

BOOL Is_pi         (P_WBXML_INFO buffer)
{
	return IsTag(buffer, TAG_PI);
}

BOOL Is_opaque     (P_WBXML_INFO buffer)
{
	return IsTag(buffer, TAG_OPAQUE);
}

BOOL Is_zero(P_WBXML_INFO buffer)
{
  BOOL result = FALSE;

  if (buffer) 
  {
    if (BytesLeft(buffer) >= 1) 
    {
      result = ((*buffer->m_curpos) == 0);
    }
    else
    {
      ParseError(ERR_END_OF_DATA);
    }
  }
  else
  {
    ParseError(ERR_INTERNAL_BAD_PARAM);
  }

  return result;
}


/***********************
 * Basic Type Decoders *
 ***********************/

void Read_u_int8(P_WBXML_INFO buffer, P_WBXML_U_INT8 result)
{
  if (buffer && result)
  {
    if (BytesLeft(buffer) >= 1) 
    {
      *result = *(buffer->m_curpos);
      (buffer->m_curpos)++;
    }
    else
    {
      ParseError(ERR_END_OF_DATA);
    }
  }
  else
  {
    ParseError(ERR_INTERNAL_BAD_PARAM);
  }
}

void Read_mb_u_int32(P_WBXML_INFO buffer, P_WBXML_MB_U_INT32 result)
{
  if (buffer && result)
  {
    int i;
    for (i = 0; i < MAX_MB_U_INT32_BYTES; i++)
    {
      if (BytesLeft(buffer) >= 1)
      {
        (*result)[i] = *(buffer->m_curpos);
        (buffer->m_curpos)++;

        if ( !( (*result)[i] & 0x80 ) )
          break;
      }
      else
      {
        ParseError(ERR_END_OF_DATA);
      }
    }
  }
  else
  {
    ParseError(ERR_INTERNAL_BAD_PARAM);
  }
}

void Read_bytes(P_WBXML_INFO buffer, WBXML_LENGTH length, P_WBXML_BYTES result)
{
  if (buffer && result)
  {
    if (BytesLeft(buffer) >= length) 
    {
      *result = (WBXML_BYTES) malloc(length*sizeof(unsigned char));
      memcpy(*result, buffer->m_curpos, length);
      buffer->m_curpos += length;
    }
    else
    {
      ParseError(ERR_END_OF_DATA);
    }
  }
  else
  {
    ParseError(ERR_INTERNAL_BAD_PARAM);
  }
}

void ReadFixedTag(P_WBXML_INFO buffer, WBXML_TAG tag)
{
  if (buffer)
  {
    if (BytesLeft(buffer) >= sizeof(WBXML_TAG))
    {
      if ((*((WBXML_TAG*) buffer->m_curpos)) == tag)
      {
        buffer->m_curpos += sizeof(WBXML_TAG);
      }
      else
      {
        ParseError(ERR_TAG_NOT_FOUND);
      }
    }
    else
    {
      ParseError(ERR_END_OF_DATA);
    }
  }
  else
  {
    ParseError(ERR_INTERNAL_BAD_PARAM);
  }
}

WBXML_TAG ReadCodepageTag (P_WBXML_INFO buffer, CP_TAG_TYPE type)
{
  WBXML_TAG tag = 0;

  if (buffer)
  {
    if (BytesLeft(buffer) >= sizeof(WBXML_TAG))
    {
      tag = *((WBXML_TAG*) buffer->m_curpos);

	  switch (type)
	  {
	    case CP_TAG_TAG:
		  buffer->m_curpos += sizeof(WBXML_TAG);
		  break;

		case CP_TAG_ATTRSTART:
		  if ((tag & 0x80) != 0x80)
		  {
		    buffer->m_curpos += sizeof(WBXML_TAG);
		  }
		  else
		  {
			  ParseError(ERR_TAG_NOT_FOUND);
		  }
		  break;

		case CP_TAG_ATTRVALUE:
		  if ((tag & 0x80) == 0x80)
		  {
		    buffer->m_curpos += sizeof(WBXML_TAG);
		  }
		  else
		  {
			  ParseError(ERR_TAG_NOT_FOUND);
		  }
		  break;

		default:
		  ParseError(ERR_TAG_NOT_FOUND);
		  break;
      }
    }
    else
    {
      ParseError(ERR_END_OF_DATA);
    }
  }
  else
  {
    ParseError(ERR_INTERNAL_BAD_PARAM);
  }

  return tag;
}


/**************************
 * Basic Type Conversions *
 **************************/

long mb_u_int32_to_long(P_WBXML_MB_U_INT32 value)
{
  long result = 0;

  if (value)
  {
    int i;
    for (i = 0; i < MAX_MB_U_INT32_BYTES; i++)
    {
      result <<= 7;
      result |= ((*value)[i] & 0x7f);

      if ( !( (*value)[i] & 0x80 ) )
        break;
    }
  }
  else
  {
    ParseError(ERR_INTERNAL_BAD_PARAM);
  }

  return result;
}

static void OutputEncodedString(const unsigned char* str)
{
	/* Work our way down the string looking for illegal chars */
	while (*str != 0)
	{
		if ((*str < 0x20) || (*str > 0x7F))
		{
			/* Out of range... encode */
			printf("&#x%02x;", *str);
		}
		else
		{
			switch (*str)
			{
				case '<':
				case '>':
				case '&':
				case '\'':
				case '\"':
					/* Special symbol... encode */
					printf("&#x%2x", *str);
					break;

				default:
					printf("%c", *str);
					break;
			}
		}

		str++;
	}
}


/*******************************
 * Document Structure Decoders *
 *******************************/

void Read_start      (P_WBXML_INFO buffer)
{
  Read_version(buffer);
  Read_publicid(buffer);
  Read_charset(buffer);
  Read_strtbl(buffer);
  Read_body(buffer);
}

void Read_strtbl     (P_WBXML_INFO buffer)
{
  WBXML_STRING_TABLE result;
  Read_mb_u_int32(buffer, &(result.m_length));
  Read_bytes(buffer, mb_u_int32_to_long(&(result.m_length)), &(result.m_strings));

  AddStringTableNode(buffer, &result);
}

void Read_body       (P_WBXML_INFO buffer)
{
  while (Is_pi(buffer))
  {
    Read_pi(buffer);
  }

  Read_element(buffer);

  while (Is_pi(buffer))
  {
    Read_pi(buffer);
  }
}

void Read_element    (P_WBXML_INFO buffer)
{
  WBXML_TAG stagvalue = 0;

  if (Is_switchPage(buffer))
  {
    Read_switchPage(buffer);
  }

  stagvalue = Read_stag(buffer);

  /* move the current node down to this one in the tree */
  if (buffer->m_curnode)
	  buffer->m_curnode = buffer->m_curnode->m_child;
  else buffer->m_curnode = buffer->m_tree;

  if ((stagvalue & CODEPAGE_TAG_HAS_ATTRS) == CODEPAGE_TAG_HAS_ATTRS)
  {
    do
    {
      Read_attribute(buffer);

    } while (!IsTag(buffer, TAG_END));

    ReadFixedTag(buffer, TAG_END);

	AddAttrEndNode(buffer);
  }

  if ((stagvalue & CODEPAGE_TAG_HAS_CONTENT) == CODEPAGE_TAG_HAS_CONTENT)
  {
    while (!IsTag(buffer, TAG_END))
	{
      Read_content(buffer);
	}

    ReadFixedTag(buffer, TAG_END);
  }

  /* move the current node back up one */
  buffer->m_curnode = buffer->m_curnode->m_parent;
}

void Read_content    (P_WBXML_INFO buffer)
{
	if (Is_string(buffer))
	{
		Read_string(buffer);
	}
	else if (Is_extension(buffer))
	{
		Read_extension(buffer);
	}
	else if (Is_entity(buffer))
	{
		Read_entity(buffer);
	}
	else if (Is_pi(buffer))
	{
		Read_pi(buffer);
	}
	else if (Is_opaque(buffer))
	{
		Read_opaque(buffer);
	}
	else
	{
		/* Assume it is an element */
		Read_element(buffer);
	}
}

WBXML_TAG Read_stag       (P_WBXML_INFO buffer)
{
	if (IsCodepageTag(buffer, CP_TAG_TAG))
	{
		WBXML_TAG tag = ReadCodepageTag(buffer, CP_TAG_TAG);

		AddCodepageTagNode(buffer, tag);

		return tag;
	}
	else if (IsTag(buffer, TAG_LITERAL))
	{
		WBXML_MB_U_INT32 index;

		ReadFixedTag(buffer, TAG_LITERAL);
		Read_index(buffer, &index);

		AddCodepageLiteralTagNode(buffer, index);
	}
	else
	{
		ParseError(ERR_TAG_NOT_FOUND);
	}

	return 0;
}

void Read_attribute  (P_WBXML_INFO buffer)
{
	Read_attrStart(buffer);

	while (Is_attrValue(buffer))
	{
		Read_attrValue(buffer);
	}
}

void Read_attrStart  (P_WBXML_INFO buffer)
{
  if (Is_switchPage(buffer))
  {
	WBXML_TAG tag;
    Read_switchPage(buffer);
    tag = ReadCodepageTag(buffer, CP_TAG_ATTRSTART);

	AddAttrStartNode(buffer, tag);
  }
  else if (IsCodepageTag(buffer, CP_TAG_ATTRSTART))
  {
	WBXML_TAG tag;
    tag = ReadCodepageTag(buffer, CP_TAG_ATTRSTART);

	AddAttrStartNode(buffer, tag);
  }
  else if (IsTag(buffer, TAG_LITERAL))
  {
    WBXML_MB_U_INT32 index;

    ReadFixedTag(buffer, TAG_LITERAL);
    Read_index(buffer, &index);

	AddAttrStartLiteralNode(buffer, index);
  }
  else
  {
    ParseError(ERR_TAG_NOT_FOUND);
  }
}

void Read_attrValue  (P_WBXML_INFO buffer)
{
  if (Is_switchPage(buffer))
  {
	WBXML_TAG tag;
    Read_switchPage(buffer);
    tag = ReadCodepageTag(buffer, CP_TAG_ATTRVALUE);
	AddAttrValueNode(buffer, tag);
  }
  else if (IsCodepageTag(buffer, CP_TAG_ATTRVALUE))
  {
	WBXML_TAG tag;
    tag = ReadCodepageTag(buffer, CP_TAG_ATTRVALUE);
	AddAttrValueNode(buffer, tag);
  }
  else if (Is_string(buffer))
  {
    Read_string(buffer);
  }
  else if (Is_extension(buffer))
  {
    Read_extension(buffer);
  }
  else if (Is_entity(buffer))
  {
    Read_entity(buffer);
  }
  else if (Is_opaque(buffer))
  {
    Read_opaque(buffer);
  }
  else
  {
    ParseError(ERR_TAG_NOT_FOUND);
  }
}

void Read_extension  (P_WBXML_INFO buffer)
{
	if (Is_switchPage(buffer))
	{
		Read_switchPage(buffer);
	}

	if (IsTag(buffer, TAG_EXT_I_0))
	{
		char* str = NULL;

		ReadFixedTag(buffer, TAG_EXT_I_0);
		Read_termstr_rtn(buffer, &str);

		AddVariableStringNode(buffer, str, VAR_ESCAPED); 
	}
	else if (IsTag(buffer, TAG_EXT_I_1))
	{
		char* str = NULL;

		ReadFixedTag(buffer, TAG_EXT_I_1);
		Read_termstr_rtn(buffer, &str);

		AddVariableStringNode(buffer, str, VAR_UNESCAPED); 
	}
	else if (IsTag(buffer, TAG_EXT_I_2))
	{
		char* str = NULL;

		ReadFixedTag(buffer, TAG_EXT_I_2);
		Read_termstr_rtn(buffer, &str);

		AddVariableStringNode(buffer, str, VAR_UNCHANGED); 
	}
	else if (IsTag(buffer, TAG_EXT_T_0))
	{
		WBXML_MB_U_INT32 index;

		ReadFixedTag(buffer, TAG_EXT_T_0);
		Read_index(buffer, &index);

		AddVariableIndexNode(buffer, index, VAR_ESCAPED);
	}
	else if (IsTag(buffer, TAG_EXT_T_1))
	{
		WBXML_MB_U_INT32 index;

		ReadFixedTag(buffer, TAG_EXT_T_1);
		Read_index(buffer, &index);

		AddVariableIndexNode(buffer, index, VAR_UNESCAPED);
	}
	else if (IsTag(buffer, TAG_EXT_T_2))
	{
		WBXML_MB_U_INT32 index;

		ReadFixedTag(buffer, TAG_EXT_T_2);
		Read_index(buffer, &index);

		AddVariableIndexNode(buffer, index, VAR_UNCHANGED);
	}
	else if (IsTag(buffer, TAG_EXT_0))
	{
		ReadFixedTag(buffer, TAG_EXT_0);

		ParseWarning(WARN_FUTURE_EXPANSION_EXT_0);
	}
	else if (IsTag(buffer, TAG_EXT_1))
	{
		ReadFixedTag(buffer, TAG_EXT_1);

		ParseWarning(WARN_FUTURE_EXPANSION_EXT_1);
	}
	else if (IsTag(buffer, TAG_EXT_2))
	{
		ReadFixedTag(buffer, TAG_EXT_2);

		ParseWarning(WARN_FUTURE_EXPANSION_EXT_2);
	}
	else
	{
		ParseError(ERR_TAG_NOT_FOUND);
	}
}

void Read_string     (P_WBXML_INFO buffer)
{
	if (Is_inline(buffer))
	{
		Read_inline(buffer);
	}
	else if (Is_tableref(buffer))
	{
		Read_tableref(buffer);
	}
	else
	{
		ParseError(ERR_TAG_NOT_FOUND);
	}
}

void Read_switchPage (P_WBXML_INFO buffer)
{
  WBXML_U_INT8 pageindex;

  ReadFixedTag(buffer, TAG_SWITCH_PAGE);
  Read_pageindex(buffer, &pageindex);

  /* Use the new codepage */
  buffer->m_curpage = pageindex;
}

void Read_inline     (P_WBXML_INFO buffer)
{
	ReadFixedTag(buffer, TAG_STR_I);
	Read_termstr(buffer);
}

void Read_tableref   (P_WBXML_INFO buffer)
{
  WBXML_MB_U_INT32 index;

  ReadFixedTag(buffer, TAG_STR_T);
  Read_index(buffer, &index);
}

void Read_entity     (P_WBXML_INFO buffer)
{
	ReadFixedTag(buffer, TAG_ENTITY);
	Read_entcode(buffer);
}

void Read_entcode    (P_WBXML_INFO buffer)
{
	WBXML_MB_U_INT32 result;
	Read_mb_u_int32(buffer, &result);
}

void Read_pi         (P_WBXML_INFO buffer)
{
  ReadFixedTag(buffer, TAG_PI);
  Read_attrStart(buffer);
  
  while (Is_attrValue(buffer))
  {
    Read_attrValue(buffer);
  }

  ReadFixedTag(buffer, TAG_END);
}

void Read_opaque     (P_WBXML_INFO buffer)
{
  WBXML_MB_U_INT32 length;
  WBXML_BYTES      data;

  ReadFixedTag(buffer, TAG_OPAQUE);
  Read_length(buffer, &length);
  Read_bytes(buffer, mb_u_int32_to_long(&length), &data);
}

void Read_version    (P_WBXML_INFO buffer)
{
  WBXML_U_INT8 result;

  Read_u_int8(buffer, &result);
}

void Read_publicid   (P_WBXML_INFO buffer)
{
  if (Is_zero(buffer))
  {
    WBXML_MB_U_INT32 index;

    Read_index(buffer, &index);

	AddDTDNode(buffer, ZERO_WBXML_MB_U_INT32, index);
  }
  else
  {
    WBXML_MB_U_INT32 result;

    Read_mb_u_int32(buffer, &result);

	AddDTDNode(buffer, result, ZERO_WBXML_MB_U_INT32);
  }
}

void Read_charset    (P_WBXML_INFO buffer)
{
  WBXML_MB_U_INT32 result;

  Read_mb_u_int32(buffer, &result);
}

void Read_termstr_rtn(P_WBXML_INFO buffer, char** result)
{

#define STRING_BLOCK_SIZE 256

	int buflen = STRING_BLOCK_SIZE;
	char* strbuf = (char*) malloc(buflen);
	BOOL doubled = FALSE;
	int i = 0;

	if (!result)
		ParseError(ERR_INTERNAL_BAD_PARAM);

	while ( (BytesLeft(buffer) >= 1) && (*(buffer->m_curpos) != 0) )
	{
		if (i>=buflen)
		{
			buflen += STRING_BLOCK_SIZE;
			strbuf = realloc(strbuf, buflen);
		}

		if (*(buffer->m_curpos) != '$' || doubled == TRUE)
		{
			strbuf[i] = *(buffer->m_curpos);
			buffer->m_curpos++;
			i++;
			if (doubled == TRUE)
				doubled = FALSE;
		}
		else
		{
			strbuf[i] = *(buffer->m_curpos);
			i++;
			doubled = TRUE;
		}
	}

	strbuf[i] = 0;
	buffer->m_curpos++;

	if (*result)
		free(*result);

	*result = strbuf;
}

void Read_termstr    (P_WBXML_INFO buffer)
{
	char* strbuf = NULL;

	Read_termstr_rtn(buffer, &strbuf);

	AddStringNode(buffer, strbuf);

	free(strbuf);
}

void Read_index      (P_WBXML_INFO buffer, P_WBXML_MB_U_INT32 result)
{
  Read_mb_u_int32(buffer, result);
}

void Read_length     (P_WBXML_INFO buffer, P_WBXML_MB_U_INT32 result)
{
  Read_mb_u_int32(buffer, result);
}

void Read_zero       (P_WBXML_INFO buffer)
{
  WBXML_U_INT8 result;

  Read_u_int8(buffer, &result);

  if (result != (WBXML_U_INT8) 0)
  {
    ParseError(ERR_TAG_NOT_FOUND);
  }
}

void Read_pageindex  (P_WBXML_INFO buffer, P_WBXML_U_INT8 result)
{
  Read_u_int8(buffer, result);
}

static void Init(P_WBXML_INFO buffer)
{
	buffer->m_start = NULL;
	buffer->m_curpos = NULL;
	buffer->m_length = 0;
	buffer->m_tree = NULL;
	buffer->m_curnode = NULL;
	buffer->m_curpage = 0;
}

static size_t BufferLength(P_WBXML_INFO buffer)
{
	size_t ret;

	while (buffer->m_curpos != '\0')
		buffer->m_curpos++;

	ret = buffer->m_curpos - buffer->m_start;
	buffer->m_curpos = buffer->m_start;
	return ret;
}

static void Free(P_WBXML_INFO buffer)
{
	if (buffer->m_start)
	{
		free(buffer->m_start);
		buffer->m_start = NULL;
	}

	buffer->m_curpos = NULL;
	buffer->m_length = 0;

	FreeNode(buffer->m_tree);
	buffer->m_tree = NULL;
}

static long FileSize(FILE* file)
{
	long curpos = ftell(file);
	long endpos;
	fseek(file, 0, SEEK_END);
	endpos = ftell(file);
	fseek(file, curpos, SEEK_SET);

	return endpos;
}

static void ReadBinary(P_WBXML_INFO buffer, FILE* file)
{
	char buf[4096];
	int m = 1;
	long n;

	if (buffer && file)
	{
		if (file != stdin)
		{
			buffer->m_length = FileSize(file);
			buffer->m_start = (P_WBXML) malloc(buffer->m_length);
			buffer->m_curpos = buffer->m_start;

			if (!buffer->m_start)
			{
				fclose(file);
				ParseError(ERR_NOT_ENOUGH_MEMORY);
			}

			if (fread(buffer->m_start, 1, buffer->m_length, file) != buffer->m_length)
			{
				fclose(file);
				ParseError(ERR_FILE_NOT_READ);
			}
			else
			{
				fclose(file);
			}
		}
		else
		{
			while ((n = fread(buf, 1, sizeof(buf), file)) > 0)
			{
				buffer->m_start = (P_WBXML) realloc(buffer->m_start, sizeof(buf) * m);
				memcpy(buffer->m_start + (sizeof(buf) * (m - 1)), buf, sizeof(buf));
				m++;
			}
			buffer->m_length = BufferLength(buffer);
			buffer->m_curpos = buffer->m_start;
		}
				
	}
	else
	{
		ParseError(ERR_INTERNAL_BAD_PARAM);
	}
}

static const char* DTDTypeName(long dtdnum)
{
	int i = 0;

	/* Search the DTD list for a match */
	while (DTDTypeList[i].m_name)
	{
		if (DTDTypeList[i].m_id == dtdnum)
		{
			break;
		}

		i++;
	}

	return DTDTypeList[i].m_name;
}

static const char* CodepageTagName(WBXML_CODEPAGE page, WBXML_TAG tag)
{
	int i = 0;

	/* Strip flags off of the tag */
	tag = (WBXML_TAG) (tag & CODEPAGE_TAG_MASK);

	/* Search the tag list for a match */
	while (CodepageTagNames[i].m_name)
	{
		if ((CodepageTagNames[i].m_dtd_id == dtd_id) &&
			(CodepageTagNames[i].m_page == page) &&
			(CodepageTagNames[i].m_tag == tag))
		{
			break;
		}

		i++;
	}

	return CodepageTagNames[i].m_name;
}

static const char* CodepageAttrstartName(WBXML_CODEPAGE page, WBXML_TAG tag, char** value)
{
	int i = 0;

	/* Check Parameters */
	if (!value)
	{
		ParseError(ERR_INTERNAL_BAD_PARAM);
	}

	/* Search the tag list for a match */
	while (CodepageAttrstartNames[i].m_name)
	{
		if ((CodepageAttrstartNames[i].m_dtd_id == dtd_id) &&
			(CodepageAttrstartNames[i].m_page == page) &&
			(CodepageAttrstartNames[i].m_tag == tag))
		{
			break;
		}

		i++;
	}

	/* Duplicate the value because it may be concatenated to */
	if (CodepageAttrstartNames[i].m_valueprefix)
	{
		*value = strdup(CodepageAttrstartNames[i].m_valueprefix);
	}
	else
	{
		*value = NULL;
	}

	/* Return the tag name */
	return CodepageAttrstartNames[i].m_name;
}

static void CodepageAttrvalueName(WBXML_CODEPAGE page, WBXML_TAG tag, char** value)
{
	int i = 0;

	/* Check Parameters */
	if (!value)
	{
		ParseError(ERR_INTERNAL_BAD_PARAM);
	}

	/* Search the tag list for a match */
	while (CodepageAttrvalueNames[i].m_name)
	{
		if ((CodepageAttrvalueNames[i].m_dtd_id == dtd_id) &&
			(CodepageAttrvalueNames[i].m_page == page) &&
			(CodepageAttrvalueNames[i].m_tag == tag))
		{
			break;
		}

		i++;
	}

	/* concatenate the value */
	if (CodepageAttrvalueNames[i].m_name)
	{
		if (*value)
		{
			*value = realloc(*value, strlen(*value) + strlen(CodepageAttrvalueNames[i].m_name) + 1);
			strcat(*value, CodepageAttrvalueNames[i].m_name);
		}
		else
		{
			*value = strdup(CodepageAttrvalueNames[i].m_name);
		}
	}
}

static const char* GetStringTableString(P_WBXML_NODE node, long index)
{
	/* Find the string table node */

	P_WBXML_NODE pStringsNode = node;

	while (pStringsNode->m_parent)
	{
		pStringsNode = pStringsNode->m_parent;
	}

	while (pStringsNode->m_next)
	{
		pStringsNode = pStringsNode->m_next;
	}

	while (pStringsNode->m_prev && pStringsNode->m_type != NODE_STRING_TABLE)
	{
		pStringsNode = pStringsNode->m_prev;
	}

	if (pStringsNode->m_type != NODE_STRING_TABLE)
	{
		return "!!NO STRING TABLE!!";
	}

	/* Find the indexed string */

	if ((index >= 0) && (index < mb_u_int32_to_long(&((P_WBXML_STRING_TABLE)pStringsNode->m_data)->m_length)))
	{
		return (const char*) &(((P_WBXML_STRING_TABLE)pStringsNode->m_data)->m_strings[index]);
	}
	else
	{
		return "!!STRING TABLE INDEX TOO LARGE!!";
	}
}

static void DumpNode(P_WBXML_NODE node, int indent, BOOL *inattrs, BOOL hascontent, char** value)
{
	P_WBXML_NODE curnode = node->m_child;

	WBXML_TAG nodetype = 0;
	long dtdnum = 0;

	BOOL bAttributesFollow = FALSE;
	BOOL bHasContent = FALSE;

	int i;

	if (!(*inattrs))
	{
		for (i=0; i<indent; i++)
		{
			printf(" ");
		}
	}
	else
	{
		if ((node->m_type != NODE_ATTRVALUE) && (*value))
		{
			printf("=\"");
			OutputEncodedString((unsigned char*) *value);
			printf("\"");
			free(*value);
			*value = NULL;
		}
	}

	switch (node->m_type)
	{
		case NODE_DTD_TYPE:
			printf("<?xml version=\"1.0\"?>\n<!DOCTYPE wml PUBLIC ");

			dtdnum = mb_u_int32_to_long( &((DTD_NODE_DATA*)node->m_data)->m_dtdnum );
			if ( dtdnum == 0)
			{
				printf("\"%s\">\n\n", GetStringTableString(node, mb_u_int32_to_long(&((DTD_NODE_DATA*)node->m_data)->m_index)) );
			}
			else
			{
				printf("\"%s\">\n\n", DTDTypeName(dtdnum) );
			}
			break;

		case NODE_CODEPAGE_TAG:
			nodetype = *((P_WBXML_TAG)node->m_data);
			if ((nodetype & CODEPAGE_TAG_MASK) == nodetype)
			{
				printf("<%s/>\n", CodepageTagName(node->m_page, nodetype));
			}
			else
			{
				if ((nodetype & CODEPAGE_TAG_HAS_CONTENT) == CODEPAGE_TAG_HAS_CONTENT)
				{
					bHasContent = TRUE;
				}

				if ((nodetype & CODEPAGE_TAG_HAS_ATTRS) == CODEPAGE_TAG_HAS_ATTRS)
				{
					printf("<%s", CodepageTagName(node->m_page, nodetype));
					bAttributesFollow = TRUE;
				}
				else
				{
					printf("<%s>\n", CodepageTagName(node->m_page, nodetype));
				}
			}
			break;

		case NODE_CODEPAGE_LITERAL_TAG:
			printf("<%s>\n", GetStringTableString(node, mb_u_int32_to_long(((P_WBXML_MB_U_INT32)node->m_data))) );
			break;

		case NODE_ATTRSTART:
			printf(" %s", CodepageAttrstartName(node->m_page, *((P_WBXML_TAG)node->m_data), value) );
			break;

		case NODE_ATTRSTART_LITERAL:
			printf(" %s", GetStringTableString(node, mb_u_int32_to_long(((P_WBXML_MB_U_INT32)node->m_data))) );
			break;

		case NODE_ATTRVALUE:
			CodepageAttrvalueName(node->m_page, *((P_WBXML_TAG)node->m_data), value);
			break;

		case NODE_ATTREND:
			if (!hascontent)
			{
				printf("/");
			}
			printf(">\n");
			*inattrs = FALSE;
			break;

		case NODE_STRING:
			if (*inattrs)
			{
				/* concatenate the value */
				if (*value)
				{
					if (node->m_data)
					{
						*value = realloc(*value, strlen(*value) + strlen((char*) node->m_data) + 1);
						strcat(*value, (char*) node->m_data);
					}
				}
				else
				{
					if (node->m_data)
					{
						*value = strdup((char*) node->m_data);
					}
				}
			}
			else
			{
				OutputEncodedString((unsigned char*) node->m_data);
				printf("\n");
			}
			break;

		case NODE_VARIABLE_STRING:
			/* TODO: output variable string */
			break;

		case NODE_VARIABLE_INDEX:
			/* TODO: output variable string */
			break;

		default:
			break;
	}

	indent += INDENT_SIZE;

	if (curnode)
	{
		while (curnode->m_next) curnode = curnode->m_next;

		while (curnode)
		{
			DumpNode(curnode, indent, &bAttributesFollow, bHasContent, value);
			curnode = curnode->m_prev;
		}
	}

	indent -= INDENT_SIZE;

	/* Output the element end if we have one */
	if ((nodetype & CODEPAGE_TAG_HAS_CONTENT) == CODEPAGE_TAG_HAS_CONTENT)
	{
		for (i=0; i<indent; i++)
		{
			printf(" ");
		}

		switch (node->m_type)
		{
			case NODE_CODEPAGE_TAG:
				printf("</%s>\n", CodepageTagName(node->m_page, *((P_WBXML_TAG)node->m_data)) );
				break;

			case NODE_CODEPAGE_LITERAL_TAG:
				printf("</%s>\n", GetStringTableString(node, mb_u_int32_to_long(((P_WBXML_MB_U_INT32)node->m_data))) );
				break;

			default:
				break;
		}
	}
}

static void DumpNodes(P_WBXML_INFO buffer)
{
	P_WBXML_NODE curnode = buffer->m_tree;
	BOOL bAttrsFollow = FALSE;
	char* value = NULL;

	if (curnode)
	{
		while (curnode->m_next) curnode = curnode->m_next;

		while (curnode)
		{
			DumpNode(curnode, 0, &bAttrsFollow, FALSE, &value);
			curnode = curnode->m_prev;
		}
	}
}

int main(int argc, char** argv)
{
	WBXML_INFO buffer;
	FILE* file;

	if (argc < 2)
	{
		file = stdin;
	}
	else
	{
	        file = fopen(argv[1], "r");
		if (!file)
		{
			ParseError(ERR_FILE_NOT_FOUND);
		}
	}

    Init(&buffer);
	ReadBinary(&buffer, file);
	Read_start(&buffer);
	DumpNodes(&buffer);
	Free(&buffer);

	return 0;
}
