/*
 * zXml.c
 * 
 * The Zen API 
 * Copyright (C) 2008-2009 by LMI Technologies Inc.
 * 
 * Licensed under The MIT License.
 * Redistributions of files must retain the above copyright notice.
 */
#include <z/zzXml.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

struct EscSeq
{
    char character;
    char szSeq[25];
};

static struct EscSeq escSeqs[] = {
    { '&', "amp;" },
    { '<', "lt;" },
    { '>', "gt;" },
    { '\"', "quot;" },
    { '\'', "apos;" },
    { 0xA, "#x0A;" },
    { 0xD, "#x0D;" }
};

#define zXML_PATH_DELIMINATORS      "\\/"
#define zXML_DEFAULT_HEADER         "?xml version=\"1.0\" ?"
#define zXML_NEW_LINE               "\r\n"
#define zXML_INDENTATION            "  "

void ftoa32(float Value, char* Buffer); // in tchar.c

static void CopyItemData(const zXmlNode *pSource, zXmlNode *pDestination);
static zBool ParseRawItemName(zXmlClass *pXML, const char *pszItem, zXmlNode *pItem, zBool *pbAbort);
static zXmlNode* AllocNewItem();
static zXmlItemAttr* AllocNewAttr();
static void ReleaseAttr(zXmlItemAttr *pXMLAttr);
static char* AddXMLEscapeSequences(const char *pszText);
static char* RemoveXMLEscapeSequences(const char *pszText, zBool bTrimWhitespace);
static char* newString(const char *pszString);
static void modifyString(char **pStrVar, const char *pszString);
static void concatString(char **pStrVar, const char *pszString);
static void addCharToString(char **pStrVar, char cChar);
static char* leftString(const char *pszString, unsigned int nChars);
static char* rightString(const char *pszString, unsigned int nChars);
static void trimStringLeft(char **pStrVar);
static void trimStringRight(char **pStrVar);

static zBool ValidateXmlName(const zChar *name);
static zBool IsNameChar(zChar ch);
static zBool IsLetter(zChar ch);

static void writeString(zChar* buffer, z32u *currentLength, const zChar* string);


ZAPI_EXPORT(zStatus) zXml_Construct(zXml* xml)
{
    zXmlClass* p = zNULL;  
    
    zCHECK(zCalloc(&p, 1, sizeof(zXmlClass)));
        
    p->pszHeader = newString("");
    p->root = zNIL;

    zHANDLE(xml, p); 

    return zOK; 
}

ZAPI_EXPORT(zStatus) zXml_Destroy(zXml xml)
{
    zXmlClass *p = (zXmlClass*) xml;
 
    if (!zIS_NULL(p))
    {
        modifyString(&p->pszHeader, "");
        zXml_RecursiveRelease(p, p->root);

        zFree(p->pszHeader); 
        zFree(p); 
    }
        
    return zOK;
}

ZAPI_EXPORT(zStatus) zXml_FromString(zXml xml, const zChar* buffer)
{    
    zXmlClass *p = (zXmlClass*)xml;
    zXmlNode *pCurrentItem, *pNewItem, *pChildIterate;
    zBool bAbort, bInsideItem, bIsItem;
    char *pszParse, *pszEscValue, *pszEndItem;
    char *pszLeft, *pszRight;
    z32u curChar = 0;
    z32s level = -1; //set to uninitialized. 0 triggers stop

    zXml_Clear(xml);

    pCurrentItem = 0;
    bAbort = zFALSE;
    bInsideItem = zFALSE;
    bIsItem = zFALSE; 
    pszParse = newString("");

    while ((buffer[curChar] != 0) && !bAbort && (level != 0))
    {	
        switch(buffer[curChar])
        {
            case '<':
            {
                if (bInsideItem == zTRUE)
                    { bAbort = zTRUE; }
                else
                {
                    if (pCurrentItem != 0)
                    {
                        pszEscValue = RemoveXMLEscapeSequences(pszParse, zTRUE);
                        concatString(&pCurrentItem->value, pszEscValue);
                        zFree(pszEscValue);
                    }
                    bInsideItem = zTRUE;
                    modifyString(&pszParse, "");
                }
                break;
            }
            case '>':
            {
                if (bInsideItem == zFALSE)
                    { bAbort = zTRUE; }
                else
                {
                    if (strlen(pszParse) < 1)
                        { bAbort = zTRUE; }
                    else
                    {
                        trimStringLeft(&pszParse);
                        trimStringRight(&pszParse);

                        pszLeft = leftString(pszParse, 1);
                        pszRight = rightString(pszParse, 1);

                        //check for special tags

                        if(strcmp(pszLeft, "!") == 0) //possible comment, check
                        {
                            zChar* pszCommentLeft = leftString(pszParse, 3);
                            zChar* pszCommentRight = rightString(pszParse, 2);

                            if( (strcmp(pszCommentLeft, "!--")==0) && (strcmp(pszCommentRight, "--")==0) ) //is comment
                                bIsItem = zFALSE;

                            zFree(pszCommentLeft);
                            zFree(pszCommentRight);
                        }
                        else if( (strcmp(pszLeft, "?") == 0) && (strcmp(pszRight, "?") == 0) ) //is header
                        {
                            bIsItem = zFALSE;
                            if(strlen(p->pszHeader) < 1)
                                modifyString(&p->pszHeader, pszParse); 
                        }
                        else //is a regular tag
                            bIsItem = zTRUE;

                        //process regular item tags

                        if(bIsItem)
                        {
                            if (((strcmp(pszLeft, "/") == 0)) && (pCurrentItem != 0)) //is end tag
                            {
                                pszEndItem = rightString(pszParse, (z32u)strlen(pszParse) - 1);

                                if (strcmp(pszEndItem, pCurrentItem->name) == 0)
                                {
                                    pCurrentItem = pCurrentItem->parent;
                                }
                                else
                                {
                                    bAbort = zTRUE;
                                }

                                zFree(pszEndItem);

                                level--;
                            }
                            else //start tag
                            {
                                pNewItem = AllocNewItem();
                                pNewItem->parent = pCurrentItem;

                                if (pCurrentItem != 0)
                                {
                                    if (zIS_NIL(pCurrentItem->child))
                                    {
                                        pCurrentItem->child = pNewItem;
                                    }
                                    else
                                    {
                                        pChildIterate = pCurrentItem->child;
                                        while (pChildIterate->next != 0)
                                            pChildIterate = pChildIterate->next;
                                        pNewItem->prev = pChildIterate;
                                        pChildIterate->next = pNewItem;
                                    }
                                }
                                else
                                {
                                    if (p->root != 0)
                                    {
                                        pChildIterate = p->root;
                                        while (pChildIterate->next != 0)
                                            { pChildIterate = pChildIterate->next; }
                                        pNewItem->prev = pChildIterate;
                                        pChildIterate->next = pNewItem;
                                    }
                                    else
                                        { p->root = pNewItem; }
                                }
                                
                                pCurrentItem = pNewItem;
                                if (ParseRawItemName(p, pszParse, pCurrentItem, &bAbort) == zTRUE)
                                    { pCurrentItem = pCurrentItem->parent; }

                                if(strcmp(pszRight, "/") != 0)
                                {
                                    // only increment the level if item is not single e.g. <item />
                                    if(level != -1) //initialized
                                        level++;
                                    else //uninitialized
                                        level = 1;
                                }
                            }
                        }

                        bInsideItem = zFALSE;
                        modifyString(&pszParse, "");

                        zFree(pszLeft);
                        zFree(pszRight);
                    }
                }
                break;
            }
            case '\x0D': //CR
            case '\x0A': //LF
            case '\t': //Tab
            {
                addCharToString(&pszParse, ' ');
                break;
            }
            default:
            {
                addCharToString(&pszParse, buffer[curChar]);
                break;
            }
        }
        curChar++;
    }

    if (pCurrentItem != 0)
        { bAbort = zTRUE; }

    zFree(pszParse);

    if (bAbort == zTRUE)
    {
        zXml_Clear((zXml) p);
        return zERROR;
    }

    return zOK;
}

ZAPI_EXPORT(zStatus) zXml_ToString(zXml xml, zChar* buffer, z32u *written)
{
    zXmlClass *obj = zXML(xml);
    zChar* header;
    z32u totalLength = 0;
    
    if (!zIS_NULL(buffer))
    {
        buffer[0] = 0; 
    }

    zEXCEPT_TRY 
    {
        header = newString("<");
        if(strlen(obj->pszHeader) != 0)
        {
            concatString(&header,obj->pszHeader);
        }
        else
        {
            concatString(&header,zXML_DEFAULT_HEADER);
        }
        concatString(&header, ">");
        concatString(&header, zXML_NEW_LINE);

        writeString(buffer, &totalLength, header);
        zFree(header);
                
        if(!zIS_NIL(obj->root))
        {
            zEXCEPT_CHECK(zXml_ItemToString(xml, buffer, &totalLength, obj->root, 0));
        }        
      
        if (!zIS_NULL(written))
        {
            *written = totalLength + 1;
        }
    } 
    zEXCEPT_CATCH(zNULL) {}
    zEXCEPT_FINALLY
    {
        zEXCEPT_CLOSE();
    }

    return zOK;
}

ZAPI_EXPORT(zStatus) zXml_Load(zXml* xml, const zChar* fileName)
{
    zXml out = zNIL;  
    FILE* file = zNULL;
    zChar* buffer = zNULL;
    zStatus exception; 
    z32u size;  
  
    zEXCEPT_TRY
    {    
        zEXCEPT_CHECK(zXml_Construct(&out)); 

        zEXCEPT_CHECK(!zIS_NULL(file = fopen(fileName, "rb")));         
        zEXCEPT_CHECK(fseek(file, 0, SEEK_END) == 0); 
        size = (z32u) ftell(file); 
        zEXCEPT_CHECK(fseek(file, 0, SEEK_SET) == 0); 

        zEXCEPT_CHECK(zCalloc(&buffer, sizeof(zByte), size+1)); 
        zEXCEPT_CHECK(fread(buffer, sizeof(zByte), size, file) == size); 

        zEXCEPT_CHECK(zXml_FromString(out, buffer));

        *xml = out; 
    }
    zEXCEPT_CATCH(&exception) 
    {
        if (!zIS_NIL(out))
        {
            zXml_Destroy(out);
        }
        zEXCEPT_SET(exception); 
    }
    zEXCEPT_FINALLY
    {   
        if (!zIS_NULL(file))       fclose(file);
        if (!zIS_NULL(buffer))     zFree(buffer);
       
        zEXCEPT_CLOSE();
    }
    
    return zOK;
}

ZAPI_EXPORT(zStatus) zXml_Save(zXml xml, const zChar* fileName)
{
    FILE* file = zNULL;
    zChar* buffer = zNULL;
    z32u size;  
    
    zEXCEPT_TRY
    {        
        zEXCEPT_CHECK(zXml_ToString(xml, zNULL, &size));        
        zEXCEPT_CHECK(zCalloc(&buffer, sizeof(zChar), size)); 
        zEXCEPT_CHECK(zXml_ToString(xml, buffer, zNULL));        
        
        size -= 1; 

        zEXCEPT_CHECK(!zIS_NULL(file = fopen(fileName, "wb"))); 
        zEXCEPT_CHECK(fwrite(buffer, sizeof(zByte), size, file) == size); 
    }
    zEXCEPT_CATCH(zNULL) {}
    zEXCEPT_FINALLY
    {   
        if (!zIS_NULL(file))        fclose(file);
        if (!zIS_NULL(buffer))      zFree(buffer);
       
        zEXCEPT_CLOSE();
    }

    return zOK;
}

ZAPI_EXPORT(zStatus) zXml_SaveBytes(zXml xml, zByte** data, zUIntPtr* size)
{
    zChar* output = zNULL; 
    zStatus exception; 
    z32u written = 0; 

    zEXCEPT_TRY
    {
        zEXCEPT_CHECK(zXml_ToString(xml, zNULL, &written)); 
        zEXCEPT_CHECK(zMalloc(&output, written)); 
        zEXCEPT_CHECK(zXml_ToString(xml, output, &written)); 
        
        *size = written - 1; 
        *data = (void*)output; 
    }
    zEXCEPT_CATCH(&exception)    
    {
        zFree(output); 
        zEXCEPT_SET(exception); 
    }
    zEXCEPT_FINALLY
    {
        zEXCEPT_CLOSE(); 
    }

    return zOK; 
}

ZAPI_EXPORT(zStatus) zXml_LoadBytes(zXml* xml, const zByte* data, zUIntPtr size)
{
    zXml output = zNIL; 
    zChar* buffer = zNULL; 
    zStatus exception; 

    zEXCEPT_TRY
    {
        zEXCEPT_CHECK(zMalloc(&buffer, size + 1)); 
        memcpy(buffer, data, size); 
        buffer[size] = 0; 

        zEXCEPT_CHECK(zXml_Construct(&output)); 
        zEXCEPT_CHECK(zXml_FromString(output, buffer)); 
        
        *xml = output; 
    }
    zEXCEPT_CATCH(&exception)
    {
        zXml_Destroy(output); 
        zEXCEPT_SET(exception); 
    }
    zEXCEPT_FINALLY       
    {
        zFree(buffer); 
        zEXCEPT_CLOSE(); 
    }

    return zOK; 
}

ZAPI_EXPORT(zStatus) zXml_Clear(zXml xml)
{
    zXmlClass *p = (zXmlClass*)xml;
    if(!zIS_NIL(p->root))
    {
        zCHECK(zXml_ReleaseItem(xml, p->root));
        p->root = zNIL;
    }
    return zOK;
}

ZAPI_EXPORT(zXmlItem) zXml_Root(zXml xml)
{
    zXmlClass *p = (zXmlClass*)xml;
     
    return p->root;
}

ZAPI_EXPORT(zXmlItem) zXml_Child(zXml xml, zXmlItem parent, const zChar* path)
{
    zChar *pathBuffer = zNULL;
    zChar *token = zNULL;
    zXmlNode *pItem = zXML_ITEM(parent);
    zXmlItem item;

    pathBuffer = newString(path);
    if(zIS_NULL(pathBuffer))
    {
        return zNIL;
    }

    // parse path into items
    token = strtok(pathBuffer, zXML_PATH_DELIMINATORS);
    while(!zIS_NULL(token))
    {
        zChar *indexBegin = NULL;
        zChar *indexEnd = NULL;
        z32u index = 0;

        // parse each item into name and index
        indexBegin = strchr(token, '[');
        indexEnd = strrchr(token, ']');
        if(indexBegin != NULL && indexEnd != NULL)
        {
            // has index, parse it out and isolate name
            *indexBegin = '\0';
            *indexEnd = '\0';
            index = atoi(indexBegin + 1);
        }

        pItem = zXml_FindChildByName(xml, pItem, token, index);
        if(zIS_NULL(pItem))
        {
            break;
        }

        // restore token, perhaps unnecessary
        if(indexBegin != NULL && indexEnd != NULL)
        {
            *indexBegin = '[';
            *indexEnd = ']';
        }

        token = strtok(NULL, zXML_PATH_DELIMINATORS);
    }

    zFree(pathBuffer);

    zHANDLE(&item, pItem);
    return item;
}

ZAPI_EXPORT(zXmlItem) zXml_Parent(zXml xml, zXmlItem item)
{
    return zXML_ITEM(item)->parent;
}

ZAPI_EXPORT(zXmlItem) zXml_FirstChild(zXml xml, zXmlItem parent)
{
    return zXML_ITEM(parent)->child;
}

ZAPI_EXPORT(zXmlItem) zXml_LastChild(zXml xml, zXmlItem parent)
{
    zXmlItem last = zXml_FirstChild(xml, parent);

    if(zIS_NIL(last))
        return zNIL;

    while(!zIS_NIL(zXML_ITEM(last)->next))
    {
        last = zXML_ITEM(last)->next;
    }
    return last;
}

ZAPI_EXPORT(zXmlItem) zXml_NextSibling(zXml xml, zXmlItem item)
{
    return zXML_ITEM(item)->next;
}

ZAPI_EXPORT(zXmlItem) zXml_PreviousSibling(zXml xml, zXmlItem item)
{
    return zXML_ITEM(item)->prev;
}

ZAPI_EXPORT(zBool) zXml_ChildExists(zXml xml, zXmlItem parent, const zChar* path)
{
    return zIS_NIL(zXml_Child(xml, parent, path)) ? zFALSE : zTRUE;
}

ZAPI_EXPORT(z32u) zXml_ChildCount(zXml xml, zXmlItem parent)
{
    zXmlNode *pItem = zXML_ITEM(parent);
    zXmlItem child = pItem->child;
    z32u count = 0;

    while(!zIS_NIL(child))
    {
        ++count;
        child = zXML_ITEM(child)->next;
    }
    return count;
}

ZAPI_EXPORT(zXmlItem) zXml_ChildAt(zXml xml, zXmlItem parent, z32u index)
{
    zXmlNode *pParent = zXML_ITEM(parent);
    zXmlItem child = pParent->child;
    z32u i;

    for(i = 0; (i < index) && !zIS_NIL(child); ++i)
    {
        child = zXML_ITEM(child)->next;
    }
    return child;
}

ZAPI_EXPORT(zStatus) zXml_AddItem(zXml xml, zXmlItem parent, const zChar* name, zXmlItem* item)
{
    zXmlNode *pNewItem;
    zStatus status;

    if(!ValidateXmlName(name))
        return zERROR_PARAMETER;

    pNewItem = AllocNewItem();
    modifyString(&pNewItem->name, name);

    zHANDLE(item, pNewItem);

    status = zXml_InsertItemObjectUnder(xml, parent, *item);
    if(!zSUCCESS(status))
    {
        zXml_ReleaseItem(xml, pNewItem);
        return status;
    }
    
    return zOK;
}

ZAPI_EXPORT(zStatus) zXml_InsertItem(zXml xml, zXmlItem before, const zChar* name, zXmlItem* item)
{
    zXmlNode *pNewItem;
    zStatus status;

    if(zIS_NIL(before)) return zERROR_PARAMETER;
    if(!ValidateXmlName(name))
        return zERROR_PARAMETER;

    // create new item
    pNewItem = AllocNewItem();
    modifyString(&pNewItem->name, name);
    zHANDLE(item, pNewItem);

    // insert into proper place
    status = zXml_InsertItemObjectAfter(xml, before, *item);
    if(!zSUCCESS(status))
    {
        zXml_ReleaseItem(xml, pNewItem);
        return status;
    }
    return zTRUE;
}

ZAPI_EXPORT(zStatus) zXml_CopyItem(zXml xml, zXmlItem parent, zXmlItem before,
                                  zXml srcXml, zXmlItem srcItem, zXmlItem* item)
{
    zXmlNode *newItem = zNULL;
    zStatus status;

    // If we have nothing to copied, return ok
    if (zIS_NIL(srcItem)) return zOK;

    zCHECK(zXml_CloneItem(xml, zXML_ITEM(srcItem), &newItem));

    zHANDLE(item, newItem);

    if(zIS_NIL(parent))
    {
        if(zIS_NIL(before))
        {
            status = zXml_InsertItemObjectUnder(xml, zNIL, *item);
        }
        else
        {
            status = zXml_InsertItemObjectAfter(xml, before, *item);
        }
    }
    else
    {
        if(zIS_NIL(before))
        {
            status = zXml_InsertItemObjectUnder(xml, parent, *item);
        }
        else
        {
            if(zXML_ITEM(before)->parent != parent)
                status = zERROR_PARAMETER;
            else
                status = zXml_InsertItemObjectAfter(xml, before, *item);
        }
    }

    if(!zSUCCESS(status))
    {
        zXml_ReleaseItem(xml, newItem);
        return status;
    }

    return status;
}

ZAPI_EXPORT(const zChar*) zXml_ItemName(zXml xml, zXmlItem item)
{
    return zXML_ITEM(item)->name;
}

ZAPI_EXPORT(zStatus) zXml_SetItemName(zXml xml, zXmlItem item, const zChar* name)
{
    zXmlNode *pItem = zXML_ITEM(item);
    zFree(pItem->name);
    pItem->name = newString(name);
    return !zIS_NULL(pItem->name) ? zOK : zERROR;
}

ZAPI_EXPORT(zStatus) zXml_DeleteItem(zXml xml, zXmlItem item)
{
    zXmlNode *pItem = zXML_ITEM(item);
    zXmlNode *pParent = zXML_ITEM(pItem->parent);

    // disassociate from parent
    if(pParent->child == item)
        pParent->child = pItem->next;

    // disassociate from prev
    if(!zIS_NIL(pItem->prev))
        zXML_ITEM(pItem->prev)->next = pItem->next;

    // from next
    if(!zIS_NIL(pItem->next))
        zXML_ITEM(pItem->next)->prev = pItem->prev;

    zCHECK(zXml_ReleaseItem(xml, pItem));

    return zOK;
}

ZAPI_EXPORT(zStatus) zXml_ItemString(zXml xml, zXmlItem item, zChar** str)
{
    zXmlNode *pItem = zXML_ITEM(item);
    zChar* output = zNULL; 

    zCHECK(zMalloc(&output, strlen(pItem->value) * sizeof(zChar) + 1)); 
    strcpy(output, pItem->value); 

    *str = output; 
    return zOK;
}

ZAPI_EXPORT(zStatus) zXml_ItemText(zXml xml, zXmlItem item, zChar* str, z32u capacity)
{
    zXmlNode *pItem = zXML_ITEM(item);
    if(capacity <= strlen(pItem->value))
        return zERROR_MEMORY;
    strcpy(str, pItem->value);
    return zOK;
}

ZAPI_EXPORT(zStatus) zXml_Item32u(zXml xml, zXmlItem item, z32u* value)
{
    return zXml_Parse32u(xml, zXML_ITEM(item)->value, value);
}

ZAPI_EXPORT(zStatus) zXml_Item32s(zXml xml, zXmlItem item, z32s* value)
{
    return zXml_Parse32s(xml, zXML_ITEM(item)->value, value);
}

ZAPI_EXPORT(zStatus) zXml_Item64u(zXml xml, zXmlItem item, z64u* value)
{
    return zXml_Parse64u(xml, zXML_ITEM(item)->value, value);
}

ZAPI_EXPORT(zStatus) zXml_Item64s(zXml xml, zXmlItem item, z64s* value)
{
    return zXml_Parse64s(xml, zXML_ITEM(item)->value, value);
}

ZAPI_EXPORT(zStatus) zXml_Item32f(zXml xml, zXmlItem item, z32f* value)
{
    return zXml_Parse32f(xml, zXML_ITEM(item)->value, value);
}

ZAPI_EXPORT(zStatus) zXml_Item64f(zXml xml, zXmlItem item, z64f* value)
{
    return zXml_Parse64f(xml, zXML_ITEM(item)->value, value);
}

ZAPI_EXPORT(zStatus) zXml_SetItemText(zXml xml, zXmlItem item, const zChar* str)
{
    zXmlNode *pItem = zXML_ITEM(item);

    if(!zIS_NULL(pItem->value))
        zFree(pItem->value);
    pItem->value = newString(str);
    return zIS_NULL(pItem->value) ? zERROR_MEMORY : zOK;
}

ZAPI_EXPORT(zStatus) zXml_SetItem32u(zXml xml, zXmlItem item, z32u value)
{
    return zXml_SetItemText(xml, item, zXml_Format32u(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetItem32s(zXml xml, zXmlItem item, z32s value)
{
    return zXml_SetItemText(xml, item, zXml_Format32s(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetItem64u(zXml xml, zXmlItem item, z64u value)
{
    return zXml_SetItemText(xml, item, zXml_Format64u(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetItem64s(zXml xml, zXmlItem item, z64s value)
{
    return zXml_SetItemText(xml, item, zXml_Format64s(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetItem32f(zXml xml, zXmlItem item, z32f value)
{
    return zXml_SetItemText(xml, item, zXml_Format32f(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetItem64f(zXml xml, zXmlItem item, z64f value)
{
    return zXml_SetItemText(xml, item, zXml_Format64f(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_ChildString(zXml xml, zXmlItem parent, const zChar* path, zChar** str)
{
    zXmlItem child = zXml_Child(xml, parent, path);
    if(zIS_NIL(child)) return zERROR_PARAMETER;
    return zXml_ItemString(xml, child, str);
}

ZAPI_EXPORT(zStatus) zXml_ChildText(zXml xml, zXmlItem parent, const zChar* path, zChar* str, z32u capacity)
{
    zXmlItem child = zXml_Child(xml, parent, path);
    if(zIS_NIL(child)) return zERROR_PARAMETER;
    return zXml_ItemText(xml, child, str, capacity);
}

ZAPI_EXPORT(zStatus) zXml_Child32u(zXml xml, zXmlItem parent, const zChar* path, z32u* value)
{
    zXmlItem child = zXml_Child(xml, parent, path);
    if(zIS_NIL(child)) return zERROR_PARAMETER;
    return zXml_Item32u(xml, child, value);
}

ZAPI_EXPORT(zStatus) zXml_Child32s(zXml xml, zXmlItem parent, const zChar* path, z32s* value)
{
    zXmlItem child = zXml_Child(xml, parent, path);
    if(zIS_NIL(child)) return zERROR_PARAMETER;
    return zXml_Item32s(xml, child, value);
}

ZAPI_EXPORT(zStatus) zXml_Child64u(zXml xml, zXmlItem parent, const zChar* path, z64u* value)
{
    zXmlItem child = zXml_Child(xml, parent, path);
    if(zIS_NIL(child)) return zERROR_PARAMETER;
    return zXml_Item64u(xml, child, value);
}

ZAPI_EXPORT(zStatus) zXml_Child64s(zXml xml, zXmlItem parent, const zChar* path, z64s* value)
{
    zXmlItem child = zXml_Child(xml, parent, path);
    if(zIS_NIL(child)) return zERROR_PARAMETER;
    return zXml_Item64s(xml, child, value);
}

ZAPI_EXPORT(zStatus) zXml_Child32f(zXml xml, zXmlItem parent, const zChar* path, z32f* value)
{
    zXmlItem child = zXml_Child(xml, parent, path);
    if(zIS_NIL(child)) return zERROR_PARAMETER;
    return zXml_Item32f(xml, child, value);
}

ZAPI_EXPORT(zStatus) zXml_Child64f(zXml xml, zXmlItem parent, const zChar* path, z64f* value)
{
    zXmlItem child = zXml_Child(xml, parent, path);
    if(zIS_NIL(child)) return zERROR_PARAMETER;
    return zXml_Item64f(xml, child, value);
}

ZAPI_EXPORT(zStatus) zXml_SetChildText(zXml xml, zXmlItem parent, const zChar* path, const zChar* str)
{
    zChar *pathBuffer = zNULL;
    zChar *token = zNULL;
    zXmlNode *pItem = zXML_ITEM(parent);

    pathBuffer = newString(path);
    if(zIS_NULL(pathBuffer))
    {
        return zERROR_MEMORY;
    }

    // for non-existent path items: if index is unspecified or 0, then the
    // item is created. if index is greater than 0 then it is not created,
    // an error is returned instead

    // parse path into items
    token = strtok(pathBuffer, zXML_PATH_DELIMINATORS);
    while(!zIS_NULL(token) && !zIS_NULL(pItem))
    {
        zChar *indexBegin = NULL;
        zChar *indexEnd = NULL;
        z32u index = 0;
        zXmlNode *pItemTemp;

        // parse each item into name and index
        indexBegin = strchr(token, '[');
        indexEnd = strrchr(token, ']');
        if(indexBegin != NULL && indexEnd != NULL)
        {
            // has index, parse it out and isolate name
            *indexBegin = '\0';
            *indexEnd = '\0';
            index = atoi(indexBegin + 1);
        }

        pItemTemp = zXml_FindChildByName(xml, pItem, token, index);
        if(zIS_NULL(pItemTemp) && index == 0)
        {
            zXmlItem newItem;

            zHANDLE(&newItem, pItem);
            zCHECK(zXml_AddItem(xml, pItem, token, &newItem));
            pItemTemp = zXML_ITEM(newItem);
        }
        pItem = pItemTemp;

        // restore token, perhaps unnecessary
        if(indexBegin != NULL && indexEnd != NULL)
        {
            *indexBegin = '[';
            *indexEnd = ']';
        }

        token = strtok(NULL, zXML_PATH_DELIMINATORS);
    }

    zFree(pathBuffer);

    if(!zIS_NULL(pItem))
    {
        zXmlItem item;
        zHANDLE(&item, pItem);
        return zXml_SetItemText(xml, item, str);
    }
    else
    {
        return zERROR_PARAMETER;
    }
}

ZAPI_EXPORT(zStatus) zXml_SetChild32u(zXml xml, zXmlItem parent, const zChar* path, z32u value)
{
    return zXml_SetChildText(xml, parent, path, zXml_Format32u(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetChild32s(zXml xml, zXmlItem parent, const zChar* path, z32s value)
{
    return zXml_SetChildText(xml, parent, path, zXml_Format32s(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetChild64u(zXml xml, zXmlItem parent, const zChar* path, z64u value)
{
    return zXml_SetChildText(xml, parent, path, zXml_Format64u(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetChild64s(zXml xml, zXmlItem parent, const zChar* path, z64s value)
{
    return zXml_SetChildText(xml, parent, path, zXml_Format64s(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetChild32f(zXml xml, zXmlItem parent, const zChar* path, z32f value)
{
    return zXml_SetChildText(xml, parent, path, zXml_Format32f(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetChild64f(zXml xml, zXmlItem parent, const zChar* path, z64f value)
{
    return zXml_SetChildText(xml, parent, path, zXml_Format64f(xml, value));
}

ZAPI_EXPORT(zBool) zXml_AttrExists(zXml xml, zXmlItem item, const zChar* name)
{
    zXmlItemAttr *attr = zXML_ITEM(item)->attributes;

    while(!zIS_NULL(attr))
    {
        if(strcmp(attr->name, name) == 0)
            return zTRUE;
        attr = attr->next;
    }
    return zFALSE;
}

ZAPI_EXPORT(z32u) zXml_AttrCount(zXml xml, zXmlItem item)
{
    zXmlItemAttr *attr = zXML_ITEM(item)->attributes;
    z32u count = 0;

    while(!zIS_NULL(attr))
    {
        count++;
        attr = attr->next;
    }
    return count;
}

ZAPI_EXPORT(const zChar*) zXml_AttrNameAt(zXml xml, zXmlItem item, z32u index)
{
    zXmlItemAttr *attr = zXML_ITEM(item)->attributes;
    z32u i;

    for(i = 0; (i < index) && (!zIS_NULL(attr)); ++i)
    {
        attr = attr->next;
    }
    return attr->value;
}

ZAPI_EXPORT(zStatus) zXml_DeleteAttr(zXml xml, zXmlItem item, const zChar* name)
{
    zXmlItemAttr *attr = zXML_ITEM(item)->attributes;
    zXmlItemAttr *prevAttr = zNULL;

    while(!zIS_NULL(attr))
    {
        if(strcmp(attr->name, name) == 0)
        {
            // relink the chain
            if(!zIS_NULL(prevAttr))
            {
                prevAttr->next = attr->next;
            }

            ReleaseAttr(attr);
            return zOK;
        }
        prevAttr = attr;
        attr = attr->next;
    }

    return zERROR_PARAMETER;
}

ZAPI_EXPORT(zStatus) zXml_AttrText(zXml xml, zXmlItem item, const zChar* name, zChar* str, z32u capacity)
{
    zXmlItemAttr *attr = zXml_FindItemAttributeObject(xml, item, name);
    if(zIS_NULL(attr)) return zERROR_PARAMETER;

    if(capacity <= strlen(attr->value))
        return zERROR_MEMORY;
    strcpy(str, attr->value);
    return zOK;
}

ZAPI_EXPORT(zStatus) zXml_Attr32u(zXml xml, zXmlItem item, const zChar* name, z32u* value)
{
    zXmlItemAttr *attr = zXml_FindItemAttributeObject(xml, item, name);
    if(zIS_NULL(attr)) return zERROR_PARAMETER;
    return zXml_Parse32u(xml, attr->value, value);
}

ZAPI_EXPORT(zStatus) zXml_Attr32s(zXml xml, zXmlItem item, const zChar* name, z32s* value)
{
    zXmlItemAttr *attr = zXml_FindItemAttributeObject(xml, item, name);
    if(zIS_NULL(attr)) return zERROR_PARAMETER;
    return zXml_Parse32s(xml, attr->value, value);
}

ZAPI_EXPORT(zStatus) zXml_Attr64u(zXml xml, zXmlItem item, const zChar* name, z64u* value)
{
    zXmlItemAttr *attr = zXml_FindItemAttributeObject(xml, item, name);
    if(zIS_NULL(attr)) return zERROR_PARAMETER;
    return zXml_Parse64u(xml, attr->value, value);
}

ZAPI_EXPORT(zStatus) zXml_Attr64s(zXml xml, zXmlItem item, const zChar* name, z64s* value)
{
    zXmlItemAttr *attr = zXml_FindItemAttributeObject(xml, item, name);
    if(zIS_NULL(attr)) return zERROR_PARAMETER;
    return zXml_Parse64s(xml, attr->value, value);
}

ZAPI_EXPORT(zStatus) zXml_Attr32f(zXml xml, zXmlItem item, const zChar* name, z32f* value)
{
    zXmlItemAttr *attr = zXml_FindItemAttributeObject(xml, item, name);
    if(zIS_NULL(attr)) return zERROR_PARAMETER;
    return zXml_Parse32f(xml, attr->value, value);
}

ZAPI_EXPORT(zStatus) zXml_Attr64f(zXml xml, zXmlItem item, const zChar* name, z64f* value)
{
    zXmlItemAttr *attr = zXml_FindItemAttributeObject(xml, item, name);
    if(zIS_NULL(attr)) return zERROR_PARAMETER;
    return zXml_Parse64f(xml, attr->value, value);
}

ZAPI_EXPORT(zStatus) zXml_SetAttrText(zXml xml, zXmlItem item, const zChar* name, const zChar* str)
{
    zXmlItemAttr *attr = zXml_FindItemAttributeObject(xml, item, name);

    // not exist do create
    if(zIS_NULL(attr))
    {
        zCHECK(zXml_InsertItemAttr(xml, item, name, &attr));
    }

    if(!zIS_NULL(attr->value))
    {
        zFree(attr->value);
    }
    attr->value = newString(str);
    if(zIS_NULL(attr->value))
        return zERROR_MEMORY;

    return zOK;
}

ZAPI_EXPORT(zStatus) zXml_SetAttr32u(zXml xml, zXmlItem item, const zChar* name, z32u value)
{
    return zXml_SetAttrText(xml, item, name, zXml_Format32u(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetAttr32s(zXml xml, zXmlItem item, const zChar* name, z32s value)
{
    return zXml_SetAttrText(xml, item, name, zXml_Format32s(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetAttr64u(zXml xml, zXmlItem item, const zChar* name, z64u value)
{
    return zXml_SetAttrText(xml, item, name, zXml_Format64u(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetAttr64s(zXml xml, zXmlItem item, const zChar* name, z64s value)
{
    return zXml_SetAttrText(xml, item, name, zXml_Format64s(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetAttr32f(zXml xml, zXmlItem item, const zChar* name, z32f value)
{
    return zXml_SetAttrText(xml, item, name, zXml_Format32f(xml, value));
}

ZAPI_EXPORT(zStatus) zXml_SetAttr64f(zXml xml, zXmlItem item, const zChar* name, z64f value)
{
    return zXml_SetAttrText(xml, item, name, zXml_Format64f(xml, value));
}

// Private functions

zStatus zXml_InsertItemObjectUnder(zXml xml, zXmlItem parent, zXmlItem item)
{
    zXmlClass *obj = zXML(xml);
    zXmlNode *pNew = zXML_ITEM(item);

    if(zIS_NIL(parent))
    {
        // insert as root
        if(zIS_NIL(obj->root))
        {
            pNew->parent = zNIL;
            pNew->prev = zNIL;
            pNew->next = zNIL;
            obj->root = pNew;
        }
        else
        {
            // no multiple roots allowed
            return zERROR_PARAMETER;
        }
    }
    else
    {
        // add as child of existing item

        zXmlItem lastChild = zNIL;
        zXmlNode *pParent = zXML_ITEM(parent);
        zXmlNode *pLastChild = zNULL;

        lastChild = zXml_LastChild(xml, parent);

        if(zIS_NIL(lastChild))
        {
            // add as only child

            pNew->parent = parent;
            pNew->prev = pNew->next = zNULL;
            pParent->child = pNew;
        }
        else
        {
            // add after last item

            pLastChild = zXML_ITEM(lastChild);
            pNew->parent = parent;
            pNew->prev = lastChild;
            pNew->next = zNIL;
            pLastChild->next = pNew;
        }
    }

    return zOK;
}

zStatus zXml_InsertItemObjectAfter(zXml xml, zXmlItem before, zXmlItem item)
{
    zXmlNode *pBefore = zXML_ITEM(before);
    zXmlNode *pNew = zXML_ITEM(item);

    // set up links
    pNew->parent = pBefore->parent;
    pNew->child = zNIL;
    pNew->prev = before;
    pNew->next = pBefore->next;

    // re-link original items;
    if(!zIS_NIL(pBefore->next))
    {
        zXML_ITEM(pBefore->next)->prev = item;
    }
    pBefore->next = item;

    return zOK;
}

zStatus zXml_RecursiveRelease(zXml xml, zXmlNode *item)
{
    zXmlClass *obj = zXML(xml);
    zXmlNode *removeNode;

    while (item != 0)
    {
        if (item->parent != 0)
        {
            if (item->parent->child == item)
                { item->parent->child = 0; }
        }

        if (obj->root == item)
            { obj->root = 0; }

        removeNode = item;
        item = item->next;

        zXml_ReleaseItem(xml, removeNode);
    }

    return zOK;
}

zStatus zXml_ReleaseItem(zXml xml, zXmlNode *item)
{
    zXmlNode *pItem = zXML_ITEM(item);
    zXmlNode *pChild = pItem->child;
    zXmlNode *pChildTemp = zNULL;
    zXmlItemAttr *pAttr, *pAttrTemp;

    // free children
    while(!zIS_NULL(pChild))
    {
        pChildTemp = pChild->next;
        zXml_ReleaseItem(xml, pChild);
        pChild = pChildTemp;
    }

    // free attributes
    pAttr = pItem->attributes;
    while(!zIS_NULL(pAttr))
    {
        pAttrTemp = pAttr->next;
        ReleaseAttr(pAttr);
        pAttr = pAttrTemp;
    }

    // free rest of data
    zFree(pItem->name);
    zFree(pItem->value);

    zFree(item);

    return zOK;
}

zStatus zXml_InsertItemAttr(zXml xml, zXmlItem item, const zChar *name, zXmlItemAttr **attr)
{
    zXmlNode *pItem = zXML_ITEM(item);
    zXmlItemAttr *newAttr = zNULL;
    zXmlItemAttr *lastAttr = zNULL;

    if(!ValidateXmlName(name))
        return zERROR_PARAMETER;

    newAttr = AllocNewAttr();
    if(zIS_NULL(newAttr)) return zERROR_MEMORY;
    modifyString(&newAttr->name, name);
    modifyString(&newAttr->value, "");

    // insert at end as to not disturb original ordering
    
    newAttr->next = zNULL;

    lastAttr = pItem->attributes;
    if(zIS_NULL(lastAttr))
    {
        pItem->attributes = newAttr;    
    }
    else
    {
        while(!zIS_NULL(lastAttr->next))
            lastAttr = lastAttr->next;
        lastAttr->next = newAttr;
    }

    *attr = newAttr;

    return zOK;
}

zXmlItemAttr* zXml_FindItemAttributeObject(zXml xml, zXmlItem item, const zChar *name)
{
    zXmlNode *pItem = zXML_ITEM(item);
    zXmlItemAttr *pAttr = pItem->attributes;

    while(!zIS_NULL(pAttr))
    {
        if(strcmp(pAttr->name, name) == 0)
            break;
        pAttr = pAttr->next;
    }
    return pAttr;
}

zXmlNode* zXml_FindChildByName(zXml xml, zXmlNode *parent, const zChar *name, z32u index)
{
    zXmlClass *obj = zXML(xml);

    if(zIS_NULL(parent))
    {
        // special case, item to find must be root and indexed 0
        if(zIS_NULL(obj->root) || index != 0)
            return zNULL;
        if(strcmp(obj->root->name, name) != 0)
            return zNULL;

        return obj->root;
    }
    else
    {
        z32u i = 0;
        zXmlNode *pChild = parent->child;

        while(!zIS_NULL(pChild))
        {
            if(strcmp(pChild->name, name) == 0)
            {
                if(i >= index)
                    break;
                i++;
            }
            pChild = pChild->next;
        }
        return pChild;
    }
}


// Data/String conversion


#ifdef ZAPI_MSVC

const zChar* zXml_Format32u(zXml xml, z32u val)
{
    sprintf(zXML(xml)->formatBuffer, "%u", val);
    return zXML(xml)->formatBuffer;
}

const zChar* zXml_Format32s(zXml xml, z32s val)
{
    sprintf(zXML(xml)->formatBuffer, "%d", val);
    return zXML(xml)->formatBuffer;
}

const zChar* zXml_Format64u(zXml xml, z64u val)
{
    sprintf(zXML(xml)->formatBuffer, "%I64u", val);
    return zXML(xml)->formatBuffer;
}

const zChar* zXml_Format64s(zXml xml, z64s val)
{
    sprintf(zXML(xml)->formatBuffer, "%I64d", val);
    return zXML(xml)->formatBuffer;
}

const zChar* zXml_Format32f(zXml xml, z32f val)
{
    sprintf(zXML(xml)->formatBuffer, "%f", val);
    return zXML(xml)->formatBuffer;
}

const zChar* zXml_Format64f(zXml xml, z64f val)
{
    sprintf(zXML(xml)->formatBuffer, "%lf", val);
    return zXML(xml)->formatBuffer;
}

zStatus zXml_Parse32u(zXml xml, const zChar *string, z32u* val)
{
    return (sscanf(string, "%u", val) == 1) ? zOK : zERROR_PARAMETER; 
}

zStatus zXml_Parse32s(zXml xml, const zChar *string, z32s* val)
{
    return (sscanf(string, "%d", val) == 1) ? zOK : zERROR_PARAMETER; 
}

zStatus zXml_Parse64u(zXml xml, const zChar *string, z64u* val)
{
    return (sscanf(string, "%I64u", val) == 1) ? zOK : zERROR_PARAMETER;
}

zStatus zXml_Parse64s(zXml xml, const zChar *string, z64s* val)
{
    return (sscanf(string, "%I64d", val) == 1) ? zOK : zERROR_PARAMETER;
}

zStatus zXml_Parse32f(zXml xml, const zChar *string, z32f* val)
{
    return (sscanf(string, "%f", val) == 1) ? zOK : zERROR_PARAMETER; 
}

zStatus zXml_Parse64f(zXml xml, const zChar *string, z64f* val)
{
    return (sscanf(string, "%lf", val) == 1) ? zOK : zERROR_PARAMETER; 
}

#elif defined(ZAPI_GCC)

const zChar* zXml_Format32u(zXml xml, z32u val)
{
    sprintf(zXML(xml)->formatBuffer, "%u", val);
    return zXML(xml)->formatBuffer;
}

const zChar* zXml_Format32s(zXml xml, z32s val)
{
    sprintf(zXML(xml)->formatBuffer, "%d", val);
    return zXML(xml)->formatBuffer;
}

const zChar* zXml_Format64u(zXml xml, z64u val)
{
    sprintf(zXML(xml)->formatBuffer, "%llu", val);
    return zXML(xml)->formatBuffer;
}

const zChar* zXml_Format64s(zXml xml, z64s val)
{
    sprintf(zXML(xml)->formatBuffer, "%lld", val);
    return zXML(xml)->formatBuffer;
}

const zChar* zXml_Format32f(zXml xml, z32f val)
{
    sprintf(zXML(xml)->formatBuffer, "%f", val);
    return zXML(xml)->formatBuffer;
}

const zChar* zXml_Format64f(zXml xml, z64f val)
{
    sprintf(zXML(xml)->formatBuffer, "%lf", val);
    return zXML(xml)->formatBuffer;
}

zStatus zXml_Parse32u(zXml xml, const zChar *string, z32u* val)
{
    return (sscanf(string, "%u", val) == 1) ? zOK : zERROR_PARAMETER; 
}

zStatus zXml_Parse32s(zXml xml, const zChar *string, z32s* val)
{
    return (sscanf(string, "%d", val) == 1) ? zOK : zERROR_PARAMETER; 
}

zStatus zXml_Parse64u(zXml xml, const zChar *string, z64u* val)
{
    return (sscanf(string, "%llu", val) == 1) ? zOK : zERROR_PARAMETER;
}

zStatus zXml_Parse64s(zXml xml, const zChar *string, z64s* val)
{
    return (sscanf(string, "%lld", val) == 1) ? zOK : zERROR_PARAMETER;
}

zStatus zXml_Parse32f(zXml xml, const zChar *string, z32f* val)
{
    return (sscanf(string, "%f", val) == 1) ? zOK : zERROR_PARAMETER; 
}

zStatus zXml_Parse64f(zXml xml, const zChar *string, z64f* val)
{
    return (sscanf(string, "%lf", val) == 1) ? zOK : zERROR_PARAMETER; 
}

#endif 

// String Manuplation Functions

static char* newString(const char *pszString)
{
    char *pszBuffer;

    if (pszString == 0)
    {
        zMalloc(&pszBuffer, sizeof(char)); 
        pszBuffer[0] = '\0';
    }
    else
    {
        zMalloc(&pszBuffer, sizeof(char) * (strlen(pszString) + 1)); 
        strcpy(pszBuffer, pszString);
    }
    return pszBuffer;
}


static void writeString(zChar* buffer, z32u *currentLength, const zChar* string)
{
    unsigned int length = (unsigned int) strlen(string);
    if(buffer != 0)
    {
        strcpy(&(buffer[*currentLength]),string);
    }
    
    *currentLength+= length;
    return;
}


static void modifyString(char **pStrVar, const char *pszString)
{
    if (*pStrVar != 0)
    {
        zFree(*pStrVar);
    }
    *pStrVar = newString(pszString);
    return;
}

static void concatString(char **pStrVar, const char *pszString)
{
    char *pszBuffer; 

    zMalloc(&pszBuffer, sizeof(char) * (strlen(*pStrVar) + strlen(pszString) + 1)); 

    strcpy(pszBuffer, *pStrVar);
    strcat(pszBuffer, pszString);

    if (*pStrVar != 0)
    {
        zFree(*pStrVar);
    }
    *pStrVar = pszBuffer;

    return;
}

static void addCharToString(char **pStrVar, char cChar)
{
    char szChar[2];
    szChar[0] = cChar;
    szChar[1] = '\0';
    concatString(pStrVar, szChar);
    return;
}

static char* leftString(const char *pszString, unsigned int nChars)
{
    int nOverlap;
    char *pszBuffer;
    
    nOverlap = (int) zMATH_MIN(nChars, strlen(pszString));

    zMalloc(&pszBuffer, sizeof(char) * (nOverlap + 1)); 

    strncpy(pszBuffer, pszString, nChars);
    pszBuffer[nOverlap] = '\0';

    return pszBuffer;
}

static char* rightString(const char *pszString, unsigned int nChars)
{
    unsigned int nOverlap;
    unsigned int nLength;
    char *pszBuffer;
    
    nLength = (unsigned int) strlen(pszString);
    nOverlap = zMATH_MIN(nChars, nLength);

    zMalloc(&pszBuffer, sizeof(char) * (nOverlap + 1)); 

    strcpy(pszBuffer, &pszString[nLength - nChars]);

    return pszBuffer;
}

static void trimStringLeft(char **pStrVar)
{
    int iChar, nLength;
    char *pszBuffer;
    zBool bNonWSFound = zFALSE;

    nLength = (int) strlen(*pStrVar);
    if (nLength < 1)
    {
        return;
    }

    for (iChar = 0; (iChar < nLength) && (bNonWSFound == zFALSE); iChar++)
    {
        switch((*pStrVar)[iChar])
        {
            case '\x09':
            case '\x0A':
            case '\x0D':
            case '\x20':
            {
                break;
            }
            default:
            {
                bNonWSFound = zTRUE;
                break;
            }
        }
    }
    
    iChar -= 1;

    if ((bNonWSFound == zTRUE) && (iChar == 0))
    {
        return;
    }

    if (bNonWSFound == zFALSE)
    {
        pszBuffer = newString("");
    }
    else
    {
        pszBuffer = rightString(*pStrVar, nLength - iChar);
    }

    zFree(*pStrVar);
    *pStrVar = pszBuffer;

    return;
}

static void trimStringRight(char **pStrVar)
{
    int iChar, nLength;
    char *pszBuffer;
    zBool bNonWSFound = zFALSE;

    nLength = (int) strlen(*pStrVar);
    if (nLength < 1)
    {
        return;
    }

    for (iChar = nLength - 1; (iChar >= 0) && (bNonWSFound == zFALSE); iChar--)
    {
        switch((*pStrVar)[iChar])
        {
            case '\x09':
            case '\x0A':
            case '\x0D':
            case '\x20':
            {
                break;
            }
            default:
            {
                bNonWSFound = zTRUE;
                break;
            }
        }
    }
    
    iChar += 1;

    if ((bNonWSFound == zTRUE) && (iChar == nLength - 1))
    {
        return;
    }

    if (bNonWSFound == zFALSE)
    {
        pszBuffer = newString("");
    }
    else
    {
        pszBuffer = leftString(*pStrVar, iChar + 1);
    }

    zFree(*pStrVar);
    *pStrVar = pszBuffer;

    return;
}


// Item Data Manuplation


static void CopyItemData(const zXmlNode *pSource, zXmlNode *pDestination)
{
    zXmlItemAttr *pCurrentAttr, *pTempAttr;
    zXmlItemAttr *pPreviousAttr, *pSourceAttr;
    zXmlItemAttr *pNewAttr;

    modifyString(&pDestination->name, pSource->name);
    modifyString(&pDestination->value, pSource->value);
    
    if (pDestination->attributes != 0)
    {
        pCurrentAttr = pDestination->attributes;
        while (pCurrentAttr != 0)
        {
            pTempAttr = pCurrentAttr;
            pCurrentAttr = pCurrentAttr->next;

            ReleaseAttr(pTempAttr);
        }
    }
    pDestination->attributes = 0;

    pPreviousAttr = 0;
    pSourceAttr = pSource->attributes;
    while (pSourceAttr != 0)
    {
        pNewAttr = AllocNewAttr();
        modifyString(&pNewAttr->name, pSourceAttr->name);
        modifyString(&pNewAttr->value, pSourceAttr->value);
        pNewAttr->next = 0;

        if (pPreviousAttr != 0)
            { pPreviousAttr->next = pNewAttr; }

        if (pDestination->attributes == 0)
            { pDestination->attributes = pNewAttr; }

        pPreviousAttr = pNewAttr;
        pSourceAttr = pSourceAttr->next;
    }

    return;
}

static zBool ParseRawItemName(zXmlClass *pXML, const char *pszItem, zXmlNode *pItem, zBool *pbAbort)
{
    zBool bItemClosed;
    zXmlItemAttr *pAttr, *pAttrIterate;
    zBool bAttr, bPropValue, bInQuotes; 
    int iChar, nChar;
    char *pszEscValue;

    bItemClosed = zFALSE;

    modifyString(&pItem->name, "");
    
    pAttr = 0;
    bAttr = zFALSE;
    bPropValue = zFALSE;
    bInQuotes = zFALSE;
    
    nChar = (int) strlen(pszItem);
    for (iChar = 0; (iChar < nChar) && (*pbAbort == zFALSE) && (bItemClosed == zFALSE); iChar++)
    {
        if (bAttr == zFALSE)
        {
            if (pszItem[iChar] == '\x20')
            {
                bAttr = zTRUE;
            }
            else
            {
                addCharToString(&pItem->name, pszItem[iChar]);
            }
        }
        else
        {
            if ((pszItem[iChar] == '\x20') && (bPropValue == zFALSE))
            {
                continue;
            }

            if (pszItem[iChar] == '/')
            {
                if (bInQuotes == zFALSE)
                {
                    bItemClosed = zTRUE;
                    if (bPropValue == zTRUE)
                    {
                        *pbAbort = zTRUE;
                    }
                    continue;
                }
            }

            if (pAttr == 0)
            {
                pAttr = AllocNewAttr();

                if (pItem->attributes == 0)
                    { pItem->attributes = pAttr; }
                else
                {
                    pAttrIterate = pItem->attributes;
                    while (pAttrIterate->next != 0)
                        { pAttrIterate = pAttrIterate->next; }
                    pAttrIterate->next = pAttr;
                }
            }

            if (bPropValue == zFALSE)
            {
                if (pszItem[iChar] == '=')
                {
                    bPropValue = zTRUE;
                    trimStringLeft(&pAttr->name);
                    trimStringRight(&pAttr->name);
                }
                else
                {
                    addCharToString(&pAttr->name, pszItem[iChar]);
                }
            }
            else
            {
                if (bInQuotes == zFALSE)
                {
                    if (pszItem[iChar] == '\"')
                    {
                        bInQuotes = zTRUE;
                    }
                    else
                    {
                        *pbAbort = zTRUE;
                    }
                }
                else
                {
                    if (pszItem[iChar] == '\"')
                    {
                        pszEscValue = RemoveXMLEscapeSequences(pAttr->value, zFALSE);
                        modifyString(&pAttr->value, pszEscValue);
                        zFree(pszEscValue);

                        bInQuotes = zFALSE;
                        bPropValue = zFALSE;
                        pAttr = 0;
                    }
                    else
                    {
                        addCharToString(&pAttr->value, pszItem[iChar]);
                    }
                }
            }
        }
    }

    return bItemClosed;
}

zStatus zXml_CloneItem(zXmlClass *obj, zXmlNode *pItem, zXmlNode **pClone)
{
    zXmlNode *pNewItem = zNULL;
    zXmlNode *pChild = zNULL;
    zXmlNode *pNewChild = zNULL;
    zXmlNode *pLastNewChild = zNULL;

    // copy current item

    pNewItem = AllocNewItem();
    CopyItemData(pItem, pNewItem);
    pNewItem->parent = zNULL;
    pNewItem->child = zNULL;
    pNewItem->prev = pNewItem->next = zNULL;

    // copy children

    pChild = pItem->child;
    while(!zIS_NULL(pChild))
    {
        zCHECK(zXml_CloneItem(obj, pChild, &pNewChild));

        pNewChild->parent = pNewItem;
        pNewChild->prev = pLastNewChild;
        pNewChild->next = zNULL;
        if(!zIS_NULL(pLastNewChild))
            pLastNewChild->next = pNewChild;
        else
            pNewItem->child = pNewChild;

        pLastNewChild = pNewChild;
        pChild = pChild->next; 
    }

    *pClone = pNewItem;

    return zOK;
}

static zXmlNode* AllocNewItem()
{
    zXmlNode *pXMLItem = zNULL;

    zCalloc(&pXMLItem, 1, sizeof(zXmlNode)); 
    
    pXMLItem->name = newString("");
    pXMLItem->value = newString("");
    pXMLItem->attributes = 0;

    pXMLItem->parent = 0;
    pXMLItem->child = 0;
    pXMLItem->prev = 0;
    pXMLItem->next = 0;

    return pXMLItem;
}

static zXmlItemAttr* AllocNewAttr()
{
    zXmlItemAttr *pXMLAttr = zNULL;

    zCalloc(&pXMLAttr, 1, sizeof(zXmlItemAttr)); 

    pXMLAttr->name = newString("");
    pXMLAttr->value = newString("");
    pXMLAttr->next = 0;

    return pXMLAttr;
}

static void ReleaseAttr(zXmlItemAttr *pXMLAttr)
{
    zFree(pXMLAttr->name);
    zFree(pXMLAttr->value);
    zFree(pXMLAttr);
    return;
}

zStatus zXml_ItemToString(zXml xml, zChar* buffer, z32u* totalLength, zXmlNode *pItem, z32u level)
{
    z32u i;
    zXmlItemAttr *pAttr = zNULL;
    zChar *pszEscValue;
    zChar* item;
        
    item = newString("");
    
    for (i = 0; i < level; ++i)
    {
        concatString(&item,zXML_INDENTATION);
    }

    concatString(&item, "<");
    concatString(&item, pItem->name);

    pAttr = pItem->attributes;
    while (pAttr != 0)
    {
        pszEscValue = AddXMLEscapeSequences(pAttr->value);

        concatString(&item, "\x20");
        concatString(&item, pAttr->name);
        concatString(&item, "=\"");
        concatString(&item, pszEscValue);
        concatString(&item, "\"");

        zFree(pszEscValue);

        pAttr = pAttr->next;
    }

    if ((strlen(pItem->value) > 0) || !zIS_NULL(pItem->child))
    {
        zXmlNode *pChild = pItem->child;

        concatString(&item, ">");
        if (!zIS_NULL(pItem->child))
        {
            concatString(&item, zXML_NEW_LINE);
        }

        pszEscValue = AddXMLEscapeSequences(pItem->value);
        concatString(&item, pszEscValue);
        zFree(pszEscValue);

        writeString(buffer, totalLength, item);
        zFree(item);

        while(!zIS_NULL(pChild))
        {
            zXml_ItemToString(xml, buffer, totalLength, pChild, level + 1);
            pChild = pChild->next;
        }

        item = newString("");


        if (!zIS_NULL(pItem->child))
        {
            for (i = 0; i < level; ++i)
            {
                concatString(&item,zXML_INDENTATION);
            }
        }
        concatString(&item, "</");
        concatString(&item, pItem->name);
    }
    else
    {
        concatString(&item, " /");
    }

    concatString(&item, ">");
    concatString(&item, zXML_NEW_LINE);

    writeString(buffer,totalLength,item);
    zFree(item);

    return zOK;
}

static char* AddXMLEscapeSequences(const zChar *pszText)
{
    z32u iChar, nChars;
    z32u iSeq, nEscSeqs;
    z32u nSeqLength;
    zChar *pszResult;
    zChar *pszLeft;
    zChar *pszRight;

    pszResult = newString(pszText);
    nEscSeqs = sizeof(escSeqs) / sizeof(struct EscSeq);

    nChars = (z32u) strlen(pszResult);
    for (iChar = 0; iChar < nChars; iChar++)
    {
        for (iSeq = 0; iSeq < nEscSeqs; iSeq++)
        {
            if (escSeqs[iSeq].character == pszResult[iChar])
            {
                break;
            }
        }

        if (iSeq < nEscSeqs)
        {
            nSeqLength = (z32u) strlen(escSeqs[iSeq].szSeq);

            pszLeft = leftString(pszResult, iChar);
            pszRight = rightString(pszResult, nChars - iChar - 1);
            zFree(pszResult);

            pszResult = pszLeft;
            concatString(&pszResult, "&");
            concatString(&pszResult, escSeqs[iSeq].szSeq);
            concatString(&pszResult, pszRight);

            zFree(pszRight);

            iChar += nSeqLength;
            nChars += nSeqLength;
        }
    }

    return pszResult;
}

static char* RemoveXMLEscapeSequences(const zChar *pszText, zBool bTrimWhitespace)
{
    z32u iChar, nChars;
    z32u iSeq, nEscSeqs;
    z32u nSeqLength;
    zChar szEscChar[2];
    zChar *pszResult;
    zChar *pszLeft;
    zChar *pszRight;

    pszResult = newString(pszText);
    nEscSeqs = sizeof(escSeqs) / sizeof(struct EscSeq);
    
    nChars = (z32u) strlen(pszResult);
    for (iChar = 0; iChar < nChars; iChar++)
    {
        if (pszResult[iChar] == '&')
        {
            for (iSeq = 0; iSeq < nEscSeqs; iSeq++)
            {
                nSeqLength = (z32u) strlen(escSeqs[iSeq].szSeq);

                if (strncmp(&pszResult[iChar+1], escSeqs[iSeq].szSeq, nSeqLength) == 0)
                {
                    szEscChar[0] = escSeqs[iSeq].character;
                    szEscChar[1] = '\0';

                    pszLeft = leftString(pszResult, iChar);
                    pszRight = rightString(pszResult, nChars - iChar - nSeqLength - 1);
                    zFree(pszResult);

                    pszResult = pszLeft;
                    concatString(&pszResult, szEscChar);
                    concatString(&pszResult, pszRight);

                    zFree(pszRight);
                    nChars -= nSeqLength;
                    break;
                }
            }
        }
    }

    if (bTrimWhitespace == zTRUE)
    {
        trimStringLeft(&pszResult);
        trimStringRight(&pszResult);
    }

    return pszResult;
}

static zBool ValidateXmlName(const zChar *name)
{
    z32u i;
    z32u nameLen = (z32u) strlen(name);

    if(nameLen < 1) return zFALSE;

    if(!IsLetter(name[0]) && name[0] != '_' && name[0] != ':')
        return zFALSE;

    for(i = 1; i < nameLen; ++i)
    {
        if(!IsNameChar(name[i]))
            return zFALSE;
    }

    return zTRUE;
}

static zBool IsNameChar(zChar ch)
{
    if(IsLetter(ch)) return zTRUE;
    if(isdigit(ch)) return zTRUE;
    switch(ch)
    {
    case '.':
    case '-':
    case '_':
    case ':':
        return zTRUE;
    }
    return zFALSE;
}

static zBool IsLetter(zChar ch)
{
    if( ((ch >= 'a') && (ch <= 'z') ) || ((ch >= 'A') && (ch <= 'Z') ))
    {
        return zTRUE;
    }
    return zFALSE;
}
