/** 
 * @file    kXml.cpp
 *
 * @internal
 * Copyright (C) 2004-2022 by LMI Technologies Inc.
 * Licensed under the MIT License.
 * Redistributed files must retain the above copyright notice.
 */
#include <kApi/Data/kXml.h>
#include <kApi/Data/kArrayList.h>
#include <kApi/Data/kMath.h>
#include <kApi/Data/kString.h>
#include <kApi/Io/kFile.h>
#include <kApi/Io/kMemory.h>
#include <kApi/Io/kSerializer.h>

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

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

kBeginClassEx(k, kXml)

    kAddPrivateVersion(kXml, "kdat6", "6.0.0.0", "kXml-0", WriteDat6V0, ReadDat6V0)
    
    kAddPrivateVMethod(kXml, kObject, VRelease)
    kAddPrivateVMethod(kXml, kObject, VInitClone)
kEndClassEx() 

kBeginValueEx(k, xkXmlItemBlock)
    kAddField(xkXmlItemBlock, kPointer, items)
kEndValueEx()

kBeginValueEx(k, xkXmlAttrBlock)
    kAddField(xkXmlAttrBlock, kPointer, attrs)
kEndValueEx()

kFx(kStatus) kXml_Construct(kXml* xml, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator);
    kStatus status; 
    
    kCheck(kAlloc_GetObject(alloc, kTypeOf(kXml), xml)); 

    if (!kSuccess(status = xkXml_Init(*xml, alloc)))
    {
        kAlloc_FreeRef(alloc, xml); 
    }

    return status; 
} 

kFx(kStatus) xkXml_Init(kXml xml, kAlloc allocator)
{
    kObjR(kXml, xml);

    kCheck(kObject_Init(xml, kTypeOf(kXml), allocator));

    kCheck(kArrayList_Construct(&obj->pathParseBuffer, kTypeOf(kByte), 128, allocator));
    kCheck(kArrayList_Construct(&obj->itemBlocks, kTypeOf(xkXmlItemBlock), 1, allocator));
    kCheck(kArrayList_Construct(&obj->attrBlocks, kTypeOf(xkXmlAttrBlock), 1, allocator));

    obj->itemBlockCount = xkXML_DEFAULT_ITEM_BLOCK_COUNT;
    obj->attrBlockCount = xkXML_DEFAULT_ATTR_BLOCK_COUNT;
    obj->root = kNULL;
    obj->freeItems = kNULL;
    obj->freeAttrs = kNULL;

    return kOK;
}

kFx(kStatus) xkXml_VInitClone(kXml xml, kXml source, kAlloc allocator)
{
    kStatus status; 

    kCheck(xkXml_Init(xml, allocator));

    if (!kSuccess(status = kXml_Assign(xml, source)))
    {
        xkXml_VRelease(xml); 
        return status;
    }

    return kOK; 
}

kFx(kStatus) xkXml_VRelease(kXml xml)
{
    kObj(kXml, xml);

    kXml_Clear(xml);       
    xkXml_FreeItemBlocks(xml);
    xkXml_FreeAttrBlocks(xml);

    kDestroyRef(&obj->itemBlocks);
    kDestroyRef(&obj->attrBlocks);
    kDestroyRef(&obj->pathParseBuffer);

    kCheck(kObject_VRelease(xml));

    return kOK;
}

kFx(kStatus) xkXml_WriteDat6V0(kXml xml, kSerializer serializer)
{
    kString string = kNULL; 

    kTry
    {
        kTest(kString_Construct(&string, kNULL, kObject_Alloc(xml))); 

        kTest(kXml_ToString(xml, string)); 

        kTest(kSerializer_WriteObject(serializer, string)); 
    }
    kFinally
    {
        kObject_Destroy(string); 
        kEndFinally(); 
    }

    return kOK; 
}

kFx(kStatus) xkXml_ReadDat6V0(kXml xml, kSerializer serializer, kAlloc allocator)
{
    kString string = kNULL; 
    kStatus status; 
    
    kCheck(xkXml_Init(xml, allocator)); 

    kTry
    {
        kTest(kSerializer_ReadObject(serializer, &string, allocator)); 

        kTest(kXml_FromString(xml, string)); 
    }
    kCatchEx(&status)
    {
        xkXml_VRelease(xml); 
        kEndCatchEx(status); 
    }
    kFinallyEx
    {
        kCheck(kObject_Destroy(string)); 
        kEndFinallyEx(); 
    }

    return kOK; 
}

kFx(kStatus) kXml_Compact(kXml xml)
{
    kObj(kXml, xml);
    kXml newXml;
    kXmlClass* newObj;

    // Make a clean copy, which is compact.
    kCheck(kXml_Construct(&newXml, obj->base.alloc));
    kCheck(kXml_Assign(newXml, xml));
    newObj = xkXml_Cast(newXml);

    // Copy over the guts
    kCheck(kXml_Clear(xml));
    kCheck(kArrayList_Assign(obj->attrBlocks, newObj->attrBlocks));
    kCheck(kArrayList_Assign(obj->itemBlocks, newObj->itemBlocks));
    obj->root = newObj->root;
    obj->freeAttrs = newObj->freeAttrs;
    obj->freeItems = newObj->freeItems;

    // Remove references to copied data
    kCheck(kArrayList_Clear(newObj->attrBlocks));
    kCheck(kArrayList_Clear(newObj->itemBlocks));
    newObj->root = kNULL;
    newObj->freeAttrs = kNULL;
    newObj->freeItems = kNULL;

    kCheck(kObject_Destroy(newXml));

    return kOK;
}

kFx(kStatus) kXml_Assign(kXml xml, kXml source)
{
    kCheck(kXml_Clear(xml));   

    kCheck(kXml_CopyItem(xml, kNULL, kNULL, source, kXml_Root(source), kNULL));
 
    return kOK;
}

kFx(kStatus) kXml_Read(kXml xml, kStream stream)
{
    kObjR(kXml, xml); 
    xkXmlParseContext context;

    context.isStream = kTRUE;

    context.xml = xml;
    context.buffer = kNULL;
    context.currentItem = kNULL;
    context.position = kNULL;
    context.capacity = 0;
    context.size = 0;
    context.eof = kFALSE;
    context.stream = stream;

    kTry 
    {
        context.capacity = 1024*10;
        kTest(kAlloc_Get(obj->base.alloc, context.capacity, &context.buffer));
        context.position = context.buffer;

        kTest(xkXml_AdvanceParser(&context));
        context.position = context.buffer;
        kTest(xkXml_Parse(xml, &context));
    }
    kFinally
    {
        kAlloc_Free(obj->base.alloc, context.buffer);
        context.buffer = kNULL;
        kEndFinally();
    }
    return kOK;
}

kFx(kStatus) kXml_Write(kXml xml, kStream stream)
{
    kString str = kNULL;
    
    kTry
    {
        kTest(kString_Construct(&str, kNULL, kNULL));
        kTest(kXml_ToString(xml, str));
        kTest(kStream_Write(stream, kString_Chars(str), kString_Length(str)));
    }
    kFinally
    {
        kObject_Destroy(str);
        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) kXml_Load(kXml* xml, const kChar* fileName, kAlloc allocator)
{
    kFile file = kNULL;
    kXml result = kNULL; 

    kTry 
    {
        kTest(kXml_Construct(&result, allocator));
        
        kTest(kFile_Construct(&file, fileName, kFILE_MODE_READ, allocator));
        kTest(kFile_SetReadBuffer(file, xkXML_FILE_READ_BUFFER_SIZE)); 

        kTest(kXml_Read(result, file));

        *xml = result; 
        result = kNULL; 
    } 
    kFinally
    {
        kObject_Destroy(result);
        kObject_Destroy(file);
        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) kXml_Save(kXml xml, const kChar* fileName)
{
    kFile file = kNULL;

    kTry
    {
        kTest(kFile_Construct(&file, fileName, kFILE_MODE_WRITE, kNULL));
        kTest(kFile_SetWriteBuffer(file, xkXML_FILE_WRITE_BUFFER_SIZE)); 

        kTest(kXml_Write(xml, file));
    }
    kFinally
    {
        kObject_Destroy(file);
        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) kXml_FromString(kXml xml, kString str)
{
    xkXmlParseContext context;

    context.isStream = kFALSE;
    context.buffer = (kChar*) kString_Chars(str); // isn't modified for non-stream
    context.currentItem = kNULL;
    context.position = kString_Chars(str);
    context.xml = xml;

    // unused
    context.capacity = 0;
    context.size = 0;
    context.eof = kFALSE;
    context.stream = kNULL;

    kCheck(kXml_Clear(xml));  
    kCheck(xkXml_Parse(xml, &context));

    return kOK;
}

kFx(kStatus) kXml_ToString(kXml xml, kString str)
{
    kObj(kXml, xml);

    kCheck(kString_Import(str, "<", kFALSE));

    kCheck(kString_Import(str, xkXML_DEFAULT_HEADER, kTRUE));

    kCheck(kString_Import(str, ">", kTRUE));
    kCheck(kString_Import(str, xkXML_NEW_LINE, kTRUE));

    if (!kIsNull(obj->root))
    {
        kCheck(xkXml_ItemToString(xml, str, obj->root, 0));
    }

    return kOK;
}

kFx(kStatus) kXml_FromText(kXml* xml, const kChar* str, kAlloc allocator)
{
    kXml result = kNULL; 
    kStatus status;
    xkXmlParseContext context;

    context.isStream = kFALSE;
    context.buffer = (kChar*)str; // isn't modified for non-stream
    context.currentItem = kNULL;
    context.position = str;

    // unused
    context.capacity = 0;
    context.size = 0;
    context.eof = kFALSE;
    context.stream = kNULL;

    kTry 
    {
        kTest(kXml_Construct(&result, allocator));
        context.xml = result;
        kTest(xkXml_Parse(result, &context));

        *xml = result; 
    } 
    kCatch(&status) 
    {
        kObject_Destroy(result);
        kEndCatch(status); 
    }

    return kOK;
}

kFx(kStatus) kXml_SaveBytes(kXml xml, kByte** data, kSize* size, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator); 
    kMemory stream = kNULL; 
    kByte* output = kNULL; 

    kTry
    {
        kTest(kMemory_Construct(&stream, kNULL)); 
        kTest(kXml_Write(xml, stream)); 

        kTest(kAlloc_Get(alloc, (kSize)kMemory_Length(stream), &output)); 
        kTest(kMemCopy(output, kMemory_At(stream, 0), (kSize) kMemory_Length(stream)));  

        *size = (kSize) kMemory_Length(stream); 
        *data = output; 
        output = kNULL; 
    }
    kFinally
    {
        kCheck(kObject_Destroy(stream)); 
        kCheck(kAlloc_Free(alloc, output)); 

        kEndFinally(); 
    }

    return kOK; 
}

kFx(kStatus) kXml_LoadBytes(kXml* xml, const kByte* data, kSize size, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator); 
    kMemory stream = kNULL; 
    kXml output = kNULL; 

    kTry
    {
        kTest(kMemory_Construct(&stream, kNULL)); 
        kTest(kMemory_Attach(stream, (void*)data, 0, size, size)); 

        kTest(kXml_Construct(&output, alloc)); 
        kTest(kXml_Read(output, stream)); 

        *xml = output; 
        output = kNULL; 
    }
    kFinally
    {
        kCheck(kObject_Destroy(stream)); 
        kCheck(kObject_Destroy(output)); 

        kEndFinally(); 
    }

    return kOK; 
}

kFx(kStatus) kXml_Clear(kXml xml)
{
    kObj(kXml, xml);

    if (!kIsNull(obj->root))
    {
        kCheck(kXml_DeleteItem(xml, kXml_Root(xml)));
    }

    kCheck(xkXml_FreeItemBlocks(xml));
    kCheck(xkXml_FreeAttrBlocks(xml));

    return kOK;
}

kFx(kXmlItem) kXml_Root(kXml xml)
{
    kObj(kXml, xml);

    return obj->root;
}

kFx(kStatus) kXml_AddItem(kXml xml, kXmlItem parent, const kChar* name, kXmlItem* item)
{
    xkXmlItemClass* itemObj = kNULL; 

    kCheckArgs(xkXml_ValidateName(name)); 

    kCheck(xkXml_AllocItem(xml, &itemObj));
    kCheck(xkXml_LinkItemUnder(xml, kIsNull(parent) ? kNULL : xkXmlItem_Cast(parent), itemObj));

    kCheck(xkXml_TextFieldSetString(xml, &itemObj->name, name));
    kCheck(xkXml_TextFieldSetString(xml, &itemObj->value, ""));

    if (!kIsNull(item))
    {
        *item = itemObj;
    }

    return kOK;
}

kFx(kStatus) kXml_InsertItem(kXml xml, kXmlItem next, const kChar* name, kXmlItem* item)
{
    xkXmlItemClass* itemObj = kNULL;

    kCheckArgs(!kIsNull(next) && xkXml_ValidateName(name)); 

    kCheck(xkXml_AllocItem(xml, &itemObj));
    kCheck(xkXml_LinkItemBefore(xml, xkXmlItem_Cast(next), itemObj));

    kCheck(xkXml_TextFieldSetString(xml, &itemObj->name, name));
    kCheck(xkXml_TextFieldSetString(xml, &itemObj->value, ""));

    if (!kIsNull(item))
    {
        *item = itemObj;
    }

    return kOK;
}

kFx(kStatus) kXml_DeleteItem(kXml xml, kXmlItem item)
{
    xkXmlItemClass* itemObj = xkXmlItem_Cast(item);

    kCheckArgs(!kIsNull(item));  
    
    kCheck(xkXml_UnlinkItem(xml, itemObj));
    kCheck(xkXml_FreeItem(xml, itemObj));

    return kOK;
}

kFx(kStatus) kXml_EnsureChildExists(kXml xml, kXmlItem parent, const kChar* path, kXmlItem* item)
{
    const kChar* delim = kNULL;
    xkXmlItemClass* foundItem = xkXmlItem_Cast(parent);

    // 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

    do
    {
        kSize index;
        kSize tokenSize;
        xkXmlItemClass* tempItem;

        delim = xkXml_FindDelimiter(path, xkXML_PATH_DELIMITERS);
        tokenSize = (kSize) (delim - path);
        index = xkXml_ParseNameSubstrIndex(path, tokenSize, &tokenSize);

        if (!kSuccess(xkXml_SetPathParseBuffer(xml, path, tokenSize)))
        {
            return kNULL;
        }

        tempItem = xkXml_FindChildByName(xml, foundItem, xkXml_PathParseBuffer(xml), index);
        if (kIsNull(tempItem) && index == 0)
        {
            kXmlItem newItem;

            kCheck(kXml_AddItem(xml, foundItem, xkXml_PathParseBuffer(xml), &newItem));
            tempItem = xkXmlItem_Cast(newItem);
        }
        foundItem = tempItem;

        path = delim + 1;
    } 
    while (*delim != '\0' && !kIsNull(foundItem));

    if (kIsNull(foundItem))
    {
        return kERROR;
    }

    if (!kIsNull(item))
    {
        *item = foundItem; 
    }

    return kOK; 
}

kFx(kStatus) kXml_DeleteChildren(kXml xml, kXmlItem item)
{
    kXmlItem child = kNULL; 

    kCheckArgs(!kIsNull(item)); 

    child = kXml_FirstChild(xml, item); 

    while (!kIsNull(child))
    {
        kCheck(kXml_DeleteItem(xml, child)); 

        child = kXml_FirstChild(xml, item); 
    }

    return kOK; 
}

kFx(kStatus) kXml_SetItemName(kXml xml, kXmlItem item, const kChar* name)
{
    xkXmlItemClass* itemObj = xkXmlItem_Cast(item);

    kCheckArgs(!kIsNull(item));  
 
    return xkXml_TextFieldSetString(xml, &itemObj->name, name);
}

kFx(const kChar*) kXml_ItemName(kXml xml, kXmlItem item)
{
    xkXmlItemClass* itemObj = xkXmlItem_Cast(item);

    kAssert(!kIsNull(item));  

    return xkXml_TextFieldString(xml, &itemObj->name);
}

kFx(kStatus) kXml_SetItemText(kXml xml, kXmlItem item, const kChar* str)
{
    xkXmlItemClass* itemObj = xkXmlItem_Cast(item);

    kCheckArgs(!kIsNull(item));  

    return xkXml_TextFieldSetString(xml, &itemObj->value, str);
}

kFx(kStatus) kXml_ItemText(kXml xml, kXmlItem item, kChar* str, kSize capacity)
{
    xkXmlItemClass* itemObj = xkXmlItem_Cast(item);
    const kChar* value;

    kCheckArgs(!kIsNull(item));  

    value = xkXml_TextFieldString(xml, &itemObj->value);

    if (capacity <= kStrLength(value))
    {
        return kERROR_INCOMPLETE;
    }

    kCheck(kStrCopy(str, capacity, value)); 
    
    return kOK;
}

kFx(kStatus) kXml_ItemString(kXml xml, kXmlItem item, kString str)
{
    xkXmlItemClass* itemObj = xkXmlItem_Cast(item);

    kCheckArgs(!kIsNull(item) && !kIsNull(str)); 

    kCheck(kString_Import(str, xkXml_TextFieldString(xml, &itemObj->value), kFALSE));

    return kOK;
}

kFx(kStatus) kXml_SetAttrText(kXml xml, kXmlItem item, const kChar* name, const kChar* str)
{
    xkXmlAttrClass* attrObj;

    kCheckArgs(!kIsNull(item));  

    attrObj = xkXml_FindAttrByName(xml, xkXmlItem_Cast(item), name);

    if (kIsNull(attrObj))
    {
        kCheck(xkXml_AllocAttr(xml, &attrObj));
        kCheck(xkXml_LinkAttr(xml, xkXmlItem_Cast(item), attrObj));
    }

    kCheck(xkXml_TextFieldSetString(xml, &attrObj->name, name));
    kCheck(xkXml_TextFieldSetString(xml, &attrObj->value, str));

    return kOK;
}

kFx(kBool) kXml_AttrExists(kXml xml, kXmlItem item, const kChar* name)
{
    xkXmlAttrClass* attrObj;

    kAssert(!kIsNull(item));

    attrObj = xkXml_FindAttrByName(xml, xkXmlItem_Cast(item), name);
    return (!kIsNull(attrObj)) ? kTRUE : kFALSE;
}

kFx(kSize) kXml_AttrCount(kXml xml, kXmlItem item)
{
    xkXmlAttrClass* attrObj;
    kSize count = 0;

    kAssert(!kIsNull(item));

    attrObj = xkXmlItem_Cast(item)->firstAttr;
    while (attrObj != NULL)
    {
        ++count;
        attrObj = attrObj->next;
    }

    return count;
}

kFx(const kChar*) kXml_AttrNameAt(kXml xml, kXmlItem item, kSize index)
{
    xkXmlAttrClass* attrObj;
    kSize i;

    kAssert(!kIsNull(item));

    attrObj = xkXmlItem_Cast(item)->firstAttr;
    for (i = 0; (i < index) && (!kIsNull(attrObj)); ++i)
    {
        attrObj = attrObj->next;
    }

    return kIsNull(attrObj) ? kNULL : xkXml_TextFieldString(xml, &attrObj->name);
}

kFx(kStatus) kXml_DeleteAttr(kXml xml, kXmlItem item, const kChar* name)
{
    xkXmlItemClass* itemObj = xkXmlItem_Cast(item);
    xkXmlAttrClass* attrObj;
    xkXmlAttrClass* prevAttrObj;

    kCheckArgs(!kIsNull(item));

    attrObj = itemObj->firstAttr;
    prevAttrObj = kNULL;

    while (!kIsNull(attrObj))
    {
        if (kStrEquals(xkXml_TextFieldString(xml, &attrObj->name), name))
        {
            if (!kIsNull(prevAttrObj))
                prevAttrObj->next = attrObj->next;

            if (itemObj->firstAttr == attrObj)
                itemObj->firstAttr = attrObj->next;
            if (itemObj->lastAttr == attrObj)
                itemObj->lastAttr = prevAttrObj;

            return xkXml_FreeAttr(xml, attrObj);
        }
        prevAttrObj = attrObj;
        attrObj = attrObj->next;
    }

    return kERROR_NOT_FOUND;
}

kFx(kStatus) kXml_DeleteAttrs(kXml xml, kXmlItem item)
{
    xkXmlItemClass* itemObj = xkXmlItem_Cast(item);
    xkXmlAttrClass* attrObj = kNULL;
    xkXmlAttrClass* nextAttrObj = kNULL;

    kCheckArgs(!kIsNull(item));

    attrObj = itemObj->firstAttr;
    nextAttrObj = kNULL;

    while (!kIsNull(attrObj))
    {
        nextAttrObj = attrObj->next; 

        kCheck(xkXml_FreeAttr(xml, attrObj)); 

        attrObj = nextAttrObj; 
    }
        
    itemObj->firstAttr = kNULL; 
    itemObj->lastAttr = kNULL; 

    return kOK;
}

kFx(kStatus) kXml_AttrString(kXml xml, kXmlItem item, const kChar* name, kString string)
{
    xkXmlAttrClass* attrObj;

    kCheckArgs(!kIsNull(item));

    if (kIsNull(attrObj = xkXml_FindAttrByName(xml, xkXmlItem_Cast(item), name)))
    {
        return kERROR_NOT_FOUND; 
    }
      
    return kString_Set(string, xkXml_TextFieldString(xml, &attrObj->value));
}

kFx(kStatus) kXml_AttrText(kXml xml, kXmlItem item, const kChar* name, kChar* str, kSize capacity)
{
    xkXmlAttrClass* attrObj;
    const kChar* value;

    kCheckArgs(!kIsNull(item));

    if (kIsNull(attrObj = xkXml_FindAttrByName(xml, xkXmlItem_Cast(item), name)))
    {
        return kERROR_NOT_FOUND;
    }

    value = xkXml_TextFieldString(xml, &attrObj->value);
    
    if (capacity <= kStrLength(value))
    {
        return kERROR_MEMORY;
    }

    kCheck(kStrCopy(str, capacity, value)); 

    return kOK;
}

kFx(kStatus) kXml_FindRoot(kXml xml, kXmlItem* root)
{
    return kIsNull(*root = kXml_Root(xml)) ? kERROR_FORMAT : kOK; 
}

kFx(kStatus) kXml_FindChild(kXml xml, kXmlItem parent, const kChar* path, kXmlItem* child)
{
    return kIsNull(*child = kXml_Child(xml, parent, path)) ? kERROR_FORMAT : kOK; 
}

kFx(kBool) kXml_ChildExists(kXml xml, kXmlItem parent, const kChar* path)
{
    return kIsNull(kXml_Child(xml, parent, path)) ? kFALSE : kTRUE;
}

kFx(kSize) kXml_ChildCount(kXml xml, kXmlItem parent)
{
    xkXmlItemClass* parentObj = xkXmlItem_Cast(parent);
    xkXmlItemClass* childObj;
    kSize count = 0;

    kAssert(!kIsNull(parent));

    childObj = parentObj->firstChild;

    while (!kIsNull(childObj))
    {
        ++count;
        childObj = childObj->next;
    }

    return count;
}

kFx(kXmlItem) kXml_ChildAt(kXml xml, kXmlItem parent, kSize index)
{
    xkXmlItemClass* parentObj = xkXmlItem_Cast(parent);
    xkXmlItemClass* childObj;
    kSize i;

    kAssert(!kIsNull(parent));

    childObj = parentObj->firstChild;

    for (i = 0; (i < index) && !kIsNull(childObj); ++i)
    {
        childObj = childObj->next;
    }

    return childObj;
}

kFx(kStatus) kXml_SetChildText(kXml xml, kXmlItem parent, const kChar* path, const kChar* str)
{
    kXmlItem item = kNULL; 

    kCheck(kXml_EnsureChildExists(xml, parent, path, &item)); 

    kCheck(kXml_SetItemText(xml, item, str)); 

    return kOK; 
}

kFx(kXmlItem) kXml_Child(kXml xml, kXmlItem parent, const kChar* path)
{
    const kChar* delim = kNULL;
    xkXmlItemClass* foundItem = xkXmlItem_Cast(parent);

    do
    {
        kSize index;
        kSize tokenSize;

        delim = xkXml_FindDelimiter(path, xkXML_PATH_DELIMITERS);
        tokenSize = (kSize) (delim - path);
        index = xkXml_ParseNameSubstrIndex(path, tokenSize, &tokenSize);

        if (!kSuccess(xkXml_SetPathParseBuffer(xml, path, tokenSize)))
        {
            return kNULL;
        }

        foundItem = xkXml_FindChildByName(xml, foundItem, xkXml_PathParseBuffer(xml), index);
        if (kIsNull(foundItem))
        {
            break;
        }
        path = delim + 1;
    } 
    while (*delim != '\0');

    return foundItem;
}

kFx(kXmlItem) kXml_FirstChild(kXml xml, kXmlItem parent)
{
    xkXmlItemClass* parentObj = xkXmlItem_Cast(parent);

    kAssert(!kIsNull(parent));
    
    return parentObj->firstChild;
}

kFx(kXmlItem) kXml_LastChild(kXml xml, kXmlItem parent)
{
    xkXmlItemClass* parentObj = xkXmlItem_Cast(parent);

    kAssert(!kIsNull(parent));
    
    return parentObj->lastChild;
}

kFx(kXmlItem) kXml_Parent(kXml xml, kXmlItem item)
{
    xkXmlItemClass* itemObj = xkXmlItem_Cast(item);
    
    kAssert(!kIsNull(item));
    
    return itemObj->parent;
}

kFx(kXmlItem) kXml_NextSibling(kXml xml, kXmlItem item)
{
    xkXmlItemClass* itemObj = xkXmlItem_Cast(item);
    
    kAssert(!kIsNull(item));
    
    return itemObj->next;
}

kFx(kXmlItem) kXml_PreviousSibling(kXml xml, kXmlItem item)
{
    xkXmlItemClass* itemObj = xkXmlItem_Cast(item);
    
    kAssert(!kIsNull(item));
    
    return itemObj->prev;
}

kFx(kStatus) kXml_Item16u(kXml xml, kXmlItem item, k16u* value)
{
    return xkXml_Parse16u(xml, xkXml_ItemValue(xml, item), value);
}

kFx(kStatus) kXml_Item16s(kXml xml, kXmlItem item, k16s* value)
{
    return xkXml_Parse16s(xml, xkXml_ItemValue(xml, item), value);
}

kFx(kStatus) kXml_Item32u(kXml xml, kXmlItem item, k32u* value)
{
    return xkXml_Parse32u(xml, xkXml_ItemValue(xml, item), value);
}

kFx(kStatus) kXml_Item32s(kXml xml, kXmlItem item, k32s* value)
{
    return xkXml_Parse32s(xml, xkXml_ItemValue(xml, item), value);
}

kFx(kStatus) kXml_ItemBool(kXml xml, kXmlItem item, kBool* value)
{
    return xkXml_ParseBool(xml, xkXml_ItemValue(xml, item), value);
}

kFx(kStatus) kXml_Item64u(kXml xml, kXmlItem item, k64u* value)
{
    return xkXml_Parse64u(xml, xkXml_ItemValue(xml, item), value);
}

kFx(kStatus) kXml_Item64s(kXml xml, kXmlItem item, k64s* value)
{
    return xkXml_Parse64s(xml, xkXml_ItemValue(xml, item), value);
}

kFx(kStatus) kXml_ItemSize(kXml xml, kXmlItem item, kSize* value)
{
    return xkXml_ParseSize(xml, xkXml_ItemValue(xml, item), value);
}

kFx(kStatus) kXml_Item32f(kXml xml, kXmlItem item, k32f* value)
{
    return xkXml_Parse32f(xml, xkXml_ItemValue(xml, item), value);
}

kFx(kStatus) kXml_Item64f(kXml xml, kXmlItem item, k64f* value)
{
    return xkXml_Parse64f(xml, xkXml_ItemValue(xml, item), value);
}

kFx(kStatus) kXml_SetItem16u(kXml xml, kXmlItem item, k16u value)
{
    return kXml_SetItemText(xml, item, xkXml_Format16u(xml, value));
}

kFx(kStatus) kXml_SetItem16s(kXml xml, kXmlItem item, k16s value)
{
    return kXml_SetItemText(xml, item, xkXml_Format16s(xml, value));
}

kFx(kStatus) kXml_SetItem32u(kXml xml, kXmlItem item, k32u value)
{
    return kXml_SetItemText(xml, item, xkXml_Format32u(xml, value));
}

kFx(kStatus) kXml_SetItem32s(kXml xml, kXmlItem item, k32s value)
{
    return kXml_SetItemText(xml, item, xkXml_Format32s(xml, value));
}

kFx(kStatus) kXml_SetItemBool(kXml xml, kXmlItem item, kBool value)
{
    return kXml_SetItemText(xml, item, xkXml_FormatBool(xml, value));
}

kFx(kStatus) kXml_SetItem64u(kXml xml, kXmlItem item, k64u value)
{
    return kXml_SetItemText(xml, item, xkXml_Format64u(xml, value));
}

kFx(kStatus) kXml_SetItem64s(kXml xml, kXmlItem item, k64s value)
{
    return kXml_SetItemText(xml, item, xkXml_Format64s(xml, value));
}

kFx(kStatus) kXml_SetItemSize(kXml xml, kXmlItem item, kSize value)
{
    return kXml_SetItemText(xml, item, xkXml_FormatSize(xml, value));
}

kFx(kStatus) kXml_SetItem32f(kXml xml, kXmlItem item, k32f value)
{
    return kXml_SetItemText(xml, item, xkXml_Format32f(xml, value));
}

kFx(kStatus) kXml_SetItem64f(kXml xml, kXmlItem item, k64f value)
{
    return kXml_SetItemText(xml, item, xkXml_Format64f(xml, value));
}

kFx(kStatus) kXml_Attr16u(kXml xml, kXmlItem item, const kChar* name, k16u* value)
{
    const kChar* str = xkXml_AttrValue(xml, item, name);

    if (kIsNull(str))
    {
        return kERROR_NOT_FOUND;
    }

    return xkXml_Parse16u(xml, str, value);
}

kFx(kStatus) kXml_Attr16s(kXml xml, kXmlItem item, const kChar* name, k16s* value)
{
    const kChar* str = xkXml_AttrValue(xml, item, name);

    if (kIsNull(str))
    {
        return kERROR_NOT_FOUND;
    }
    
    return xkXml_Parse16s(xml, str, value);
}

kFx(kStatus) kXml_Attr32u(kXml xml, kXmlItem item, const kChar* name, k32u* value)
{
    const kChar* str = xkXml_AttrValue(xml, item, name);

    if (kIsNull(str))
    {
        return kERROR_NOT_FOUND;
    }

    return xkXml_Parse32u(xml, str, value);
}

kFx(kStatus) kXml_Attr32s(kXml xml, kXmlItem item, const kChar* name, k32s* value)
{
    const kChar* str = xkXml_AttrValue(xml, item, name);

    if (kIsNull(str))
    {
        return kERROR_NOT_FOUND;
    }
    
    return xkXml_Parse32s(xml, str, value);
}

kFx(kStatus) kXml_AttrBool(kXml xml, kXmlItem item, const kChar* name, kBool* value)
{
    const kChar* str = xkXml_AttrValue(xml, item, name);

    if (kIsNull(str))
    {
        return kERROR_NOT_FOUND;
    }
    
    return xkXml_ParseBool(xml, str, value);
}

kFx(kStatus) kXml_Attr64u(kXml xml, kXmlItem item, const kChar* name, k64u* value)
{
    const kChar* str = xkXml_AttrValue(xml, item, name);

    if (kIsNull(str))
    {
        return kERROR_NOT_FOUND;
    }
    
    return xkXml_Parse64u(xml, str, value);
}

kFx(kStatus) kXml_Attr64s(kXml xml, kXmlItem item, const kChar* name, k64s* value)
{
    const kChar* str = xkXml_AttrValue(xml, item, name);

    if (kIsNull(str))
    {
        return kERROR_NOT_FOUND;
    }
    
    return xkXml_Parse64s(xml, str, value);
}

kFx(kStatus) kXml_AttrSize(kXml xml, kXmlItem item, const kChar* name, kSize* value)
{
    const kChar* str = xkXml_AttrValue(xml, item, name);

    if (kIsNull(str))
    {
        return kERROR_NOT_FOUND;
    }
    
    return xkXml_ParseSize(xml, str, value);
} 

kFx(kStatus) kXml_Attr32f(kXml xml, kXmlItem item, const kChar* name, k32f* value)
{
    const kChar* str = xkXml_AttrValue(xml, item, name);

    if (kIsNull(str))
    {
        return kERROR_NOT_FOUND;
    }
    
    return xkXml_Parse32f(xml, str, value);
}

kFx(kStatus) kXml_Attr64f(kXml xml, kXmlItem item, const kChar* name, k64f* value)
{
    const kChar* str = xkXml_AttrValue(xml, item, name);

    if (kIsNull(str))
    {
        return kERROR_NOT_FOUND;
    }

    return xkXml_Parse64f(xml, str, value);
}

kFx(kStatus) kXml_SetAttr16u(kXml xml, kXmlItem item, const kChar* name, k16u value)
{
    return kXml_SetAttrText(xml, item, name, xkXml_Format16u(xml, value));
}

kFx(kStatus) kXml_SetAttr16s(kXml xml, kXmlItem item, const kChar* name, k16s value)
{
    return kXml_SetAttrText(xml, item, name, xkXml_Format16s(xml, value));
}

kFx(kStatus) kXml_SetAttr32u(kXml xml, kXmlItem item, const kChar* name, k32u value)
{
    return kXml_SetAttrText(xml, item, name, xkXml_Format32u(xml, value));
}

kFx(kStatus) kXml_SetAttr32s(kXml xml, kXmlItem item, const kChar* name, k32s value)
{
    return kXml_SetAttrText(xml, item, name, xkXml_Format32s(xml, value));
}

kFx(kStatus) kXml_SetAttrBool(kXml xml, kXmlItem item, const kChar* name, kBool value)
{
    return kXml_SetAttrText(xml, item, name, xkXml_FormatBool(xml, value));
}

kFx(kStatus) kXml_SetAttr64u(kXml xml, kXmlItem item, const kChar* name, k64u value)
{
    return kXml_SetAttrText(xml, item, name, xkXml_Format64u(xml, value));
}

kFx(kStatus) kXml_SetAttr64s(kXml xml, kXmlItem item, const kChar* name, k64s value)
{
    return kXml_SetAttrText(xml, item, name, xkXml_Format64s(xml, value));
}

kFx(kStatus) kXml_SetAttrSize(kXml xml, kXmlItem item, const kChar* name, kSize value)
{
    return kXml_SetAttrText(xml, item, name, xkXml_FormatSize(xml, value));
}

kFx(kStatus) kXml_SetAttr32f(kXml xml, kXmlItem item, const kChar* name, k32f value)
{
    return kXml_SetAttrText(xml, item, name, xkXml_Format32f(xml, value));
}

kFx(kStatus) kXml_SetAttr64f(kXml xml, kXmlItem item, const kChar* name, k64f value)
{
    return kXml_SetAttrText(xml, item, name, xkXml_Format64f(xml, value));
}

kFx(kStatus) kXml_SetChild16u(kXml xml, kXmlItem parent, const kChar* path, k16u value)
{
    return kXml_SetChildText(xml, parent, path, xkXml_Format16u(xml, value));
}

kFx(kStatus) kXml_SetChild16s(kXml xml, kXmlItem parent, const kChar* path, k16s value)
{
    return kXml_SetChildText(xml, parent, path, xkXml_Format16s(xml, value));
}

kFx(kStatus) kXml_SetChild32u(kXml xml, kXmlItem parent, const kChar* path, k32u value)
{
    return kXml_SetChildText(xml, parent, path, xkXml_Format32u(xml, value));
}

kFx(kStatus) kXml_SetChild32s(kXml xml, kXmlItem parent, const kChar* path, k32s value)
{
    return kXml_SetChildText(xml, parent, path, xkXml_Format32s(xml, value));
}

kFx(kStatus) kXml_SetChildBool(kXml xml, kXmlItem parent, const kChar* path, kBool value)
{
    return kXml_SetChildText(xml, parent, path, xkXml_FormatBool(xml, value));
}

kFx(kStatus) kXml_SetChild64u(kXml xml, kXmlItem parent, const kChar* path, k64u value)
{
    return kXml_SetChildText(xml, parent, path, xkXml_Format64u(xml, value));
}

kFx(kStatus) kXml_SetChild64s(kXml xml, kXmlItem parent, const kChar* path, k64s value)
{
    return kXml_SetChildText(xml, parent, path, xkXml_Format64s(xml, value));
}

kFx(kStatus) kXml_SetChildSize(kXml xml, kXmlItem parent, const kChar* path, kSize value)
{
    return kXml_SetChildText(xml, parent, path, xkXml_Format64s(xml, (k64s)value));
}

kFx(kStatus) kXml_SetChild32f(kXml xml, kXmlItem parent, const kChar* path, k32f value)
{
    return kXml_SetChildText(xml, parent, path, xkXml_Format32f(xml, value));
}

kFx(kStatus) kXml_SetChild64f(kXml xml, kXmlItem parent, const kChar* path, k64f value)
{
    return kXml_SetChildText(xml, parent, path, xkXml_Format64f(xml, value));
}

kFx(kStatus) kXml_ChildString(kXml xml, kXmlItem parent, const kChar* path, kString str)
{
    kXmlItem child = kXml_Child(xml, parent, path);

    kCheckArgs(!kIsNull(child)); 
    
    return kXml_ItemString(xml, child, str);
}

kFx(kStatus) kXml_ChildText(kXml xml, kXmlItem parent, const kChar* path, kChar* str, kSize capacity)
{
    kXmlItem child = kXml_Child(xml, parent, path);

    kCheckArgs(!kIsNull(child)); 
    
    return kXml_ItemText(xml, child, str, capacity);
}

kFx(kStatus) kXml_Child16u(kXml xml, kXmlItem parent, const kChar* path, k16u* value)
{
    kXmlItem child = kXml_Child(xml, parent, path);

    kCheckArgs(!kIsNull(child)); 
    
    return kXml_Item16u(xml, child, value);
}

kFx(kStatus) kXml_Child16s(kXml xml, kXmlItem parent, const kChar* path, k16s* value)
{
    kXmlItem child = kXml_Child(xml, parent, path);

    kCheckArgs(!kIsNull(child)); 
    
    return kXml_Item16s(xml, child, value);
}

kFx(kStatus) kXml_Child32u(kXml xml, kXmlItem parent, const kChar* path, k32u* value)
{
    kXmlItem child = kXml_Child(xml, parent, path);

    kCheckArgs(!kIsNull(child)); 
    
    return kXml_Item32u(xml, child, value);
}

kFx(kStatus) kXml_Child32s(kXml xml, kXmlItem parent, const kChar* path, k32s* value)
{
    kXmlItem child = kXml_Child(xml, parent, path);

    kCheckArgs(!kIsNull(child)); 
    
    return kXml_Item32s(xml, child, value);
}

kFx(kStatus) kXml_ChildBool(kXml xml, kXmlItem parent, const kChar* path, kBool* value)
{
    kXmlItem child = kXml_Child(xml, parent, path);
    
    kCheckArgs(!kIsNull(child)); 

    return kXml_ItemBool(xml, child, value);
}

kFx(kStatus) kXml_Child64u(kXml xml, kXmlItem parent, const kChar* path, k64u* value)
{
    kXmlItem child = kXml_Child(xml, parent, path);

    kCheckArgs(!kIsNull(child)); 

    return kXml_Item64u(xml, child, value);
}

kFx(kStatus) kXml_Child64s(kXml xml, kXmlItem parent, const kChar* path, k64s* value)
{
    kXmlItem child = kXml_Child(xml, parent, path);

    kCheckArgs(!kIsNull(child)); 
    
    return kXml_Item64s(xml, child, value);
}

kFx(kStatus) kXml_ChildSize(kXml xml, kXmlItem parent, const kChar* path, kSize* value)
{
    kXmlItem child = kXml_Child(xml, parent, path);

    kCheckArgs(!kIsNull(child)); 
    
    return kXml_ItemSize(xml, child, value);
}

kFx(kStatus) kXml_Child32f(kXml xml, kXmlItem parent, const kChar* path, k32f* value)
{
    kXmlItem child = kXml_Child(xml, parent, path);
    
    kCheckArgs(!kIsNull(child)); 
    
    return kXml_Item32f(xml, child, value);
}

kFx(kStatus) kXml_Child64f(kXml xml, kXmlItem parent, const kChar* path, k64f* value)
{
    kXmlItem child = kXml_Child(xml, parent, path);

    kCheckArgs(!kIsNull(child)); 

    return kXml_Item64f(xml, child, value);
}

// Private functions

kStatus xkXml_TextFieldInit(kXml xml, xkXmlTextField* field)
{
    field->text[0] = '\0';
    field->buffer = kNULL;
    field->capacity = 0;

    return kOK;
}

kStatus xkXml_TextFieldRelease(kXml xml, xkXmlTextField* field)
{
    kObj(kXml, xml);

    kAlloc_Free(obj->base.alloc, field->buffer);
    field->buffer = kNULL;
    field->capacity = 0;
    return kOK;
}

kStatus xkXml_TextFieldReserve(kXml xml, xkXmlTextField* field, kSize size)
{
    kObj(kXml, xml);
    kSize bufferSize = size + 1;

    if (bufferSize <= kCountOf(field->text))
    {
        if (!kIsNull(field->buffer))
        {
            kAlloc_Free(obj->base.alloc, field->buffer);
            field->buffer = kNULL;
            field->capacity = 0;
        }
    }
    else
    {
        if (!kIsNull(field->buffer) && field->capacity < bufferSize)
        {
            kAlloc_Free(obj->base.alloc, field->buffer);
            field->buffer = kNULL;
            field->capacity = 0;
        }

        if (kIsNull(field->buffer))
        {
            kCheck(kAlloc_Get(obj->base.alloc, bufferSize, &field->buffer));
            field->capacity = bufferSize;
        }
    }

    return kOK;
}

kStatus xkXml_TextFieldSetString(kXml xml, xkXmlTextField* field, const kChar* str)
{
    kObj(kXml, xml);
    kSize bufferSize = kStrLength(str) + 1;

    if (bufferSize <= kCountOf(field->text))
    {
        if (!kIsNull(field->buffer))
        {
            kAlloc_Free(obj->base.alloc, field->buffer);
            field->buffer = kNULL;
            field->capacity = 0;
        }
        kStrCopy(field->text, kCountOf(field->text), str);
    }
    else
    {
        kAlloc_Free(obj->base.alloc, field->buffer);
        field->buffer = kNULL;

        kCheck(kAlloc_Get(obj->base.alloc, bufferSize, &field->buffer));
        field->capacity = bufferSize;

        kStrCopy(field->buffer, field->capacity, str);
    }

    return kOK;
}

kChar* xkXml_TextFieldString(kXml xml, xkXmlTextField* field)
{
    return (!kIsNull(field->buffer)) ? field->buffer : field->text;
}

kStatus xkXml_AllocItemBlock(kXml xml)
{
    kObj(kXml, xml);
    xkXmlItemBlock block;
    kSize i;

    kCheck(kAlloc_Get(obj->base.alloc, sizeof(xkXmlItemClass) * obj->itemBlockCount, &block.items));

    for (i = 0; i < (obj->itemBlockCount - 1); ++i)
    {
        block.items[i].next = &block.items[i+1];
    }

    block.items[obj->itemBlockCount - 1].next = obj->freeItems;
    obj->freeItems = &block.items[0];

    kCheck(kArrayList_AddT(obj->itemBlocks, &block));

    return kOK;
}

kStatus xkXml_AllocAttrBlock(kXml xml)
{
    kObj(kXml, xml);
    xkXmlAttrBlock block;
    kSize i;

    kCheck(kAlloc_Get(obj->base.alloc, sizeof(xkXmlAttrClass) * obj->attrBlockCount, &block.attrs));

    for (i = 0; i < (obj->attrBlockCount - 1); ++i)
    {
        block.attrs[i].next = &block.attrs[i+1];
    }

    block.attrs[obj->attrBlockCount - 1].next = obj->freeAttrs;
    obj->freeAttrs = &block.attrs[0];

    kCheck(kArrayList_AddT(obj->attrBlocks, &block));

    return kOK;
}

kStatus xkXml_FreeItemBlocks(kXml xml)
{
    kObj(kXml, xml);
    kSize i;

    for (i = 0; i < kArrayList_Count(obj->itemBlocks); ++i)
    {
        xkXmlItemBlock* block = kArrayList_AtT(obj->itemBlocks, i, xkXmlItemBlock);
        kCheck(kAlloc_Free(obj->base.alloc, block->items));
    }

    kCheck(kArrayList_Clear(obj->itemBlocks));
    obj->freeItems = kNULL;

    return kOK;
}

kStatus xkXml_FreeAttrBlocks(kXml xml)
{
    kObj(kXml, xml);
    kSize i;

    for (i = 0; i < kArrayList_Count(obj->attrBlocks); ++i)
    {
        xkXmlAttrBlock* block = kArrayList_AtT(obj->attrBlocks, i, xkXmlAttrBlock);
        kCheck(kAlloc_Free(obj->base.alloc, block->attrs));
    }

    kCheck(kArrayList_Clear(obj->attrBlocks));
    obj->freeAttrs = kNULL;

    return kOK;
}

kStatus xkXml_AllocItem(kXml xml, xkXmlItemClass** item)
{
    kObj(kXml, xml);
    xkXmlItemClass* newItem;

    if (kIsNull(obj->freeItems))
    {
        kCheck(xkXml_AllocItemBlock(xml));
        kCheck(!kIsNull(obj->freeItems));
    }

    newItem = obj->freeItems;
    obj->freeItems = obj->freeItems->next;
    
    kCheck(xkXml_TextFieldInit(xml, &newItem->name));
    kCheck(xkXml_TextFieldInit(xml, &newItem->value));

    newItem->firstAttr = newItem->lastAttr = kNULL;
    newItem->parent = kNULL;
    newItem->firstChild = newItem->lastChild = kNULL;
    newItem->prev = newItem->next = kNULL;

    *item = newItem;

    return kOK;
}

kStatus xkXml_FreeItem(kXml xml, xkXmlItemClass* item)
{
    kObj(kXml, xml);
    xkXmlItemClass* child;
    xkXmlAttrClass* attr;

    for (attr = item->firstAttr; !kIsNull(attr); )
    {
        xkXmlAttrClass* next = attr->next;
        kCheck(xkXml_FreeAttr(xml, attr));
        attr = next;
    }

    for (child = item->firstChild; !kIsNull(child); )
    {
        xkXmlItemClass* next = child->next;
        kCheck(xkXml_FreeItem(xml, child));
        child = next;
    }

    kCheck(xkXml_TextFieldRelease(xml, &item->name));
    kCheck(xkXml_TextFieldRelease(xml, &item->value));

    item->next = obj->freeItems;
    obj->freeItems = item;

    return kOK;
}

kStatus xkXml_AllocAttr(kXml xml, xkXmlAttrClass** attr)
{
    kObj(kXml, xml);
    xkXmlAttrClass* newAttr;
    
    if (kIsNull(obj->freeAttrs))
    {
        kCheck(xkXml_AllocAttrBlock(xml));
        kCheck(!kIsNull(obj->freeAttrs));
    }

    newAttr = obj->freeAttrs;
    obj->freeAttrs = newAttr->next;

    kCheck(xkXml_TextFieldInit(xml, &newAttr->name));
    kCheck(xkXml_TextFieldInit(xml, &newAttr->value));
    newAttr->next = kNULL;

    *attr = newAttr;

    return kOK;
}

kStatus xkXml_FreeAttr(kXml xml, xkXmlAttrClass* attr)
{
    kObj(kXml, xml);

    kCheck(xkXml_TextFieldRelease(xml, &attr->name));
    kCheck(xkXml_TextFieldRelease(xml, &attr->value));

    attr->next = obj->freeAttrs;
    obj->freeAttrs = attr;

    return kOK;
}

kStatus xkXml_LinkItemUnder(kXml xml, xkXmlItemClass* parent, xkXmlItemClass* item)
{
    kObj(kXml, xml);

    if (kIsNull(parent))
    {
        if (!kIsNull(obj->root))
            return kERROR_PARAMETER;
        obj->root = item;
    }
    else
    {
        item->parent = parent;
        if (parent->lastChild)
        {
            xkXmlItemClass* lastChild = parent->lastChild;

            lastChild->next = item;
            item->prev = parent->lastChild;
            parent->lastChild = item;
        }
        else
        {
            parent->firstChild = item;
            parent->lastChild = item;
        }
    }

    return kOK;
}

kStatus xkXml_LinkItemBefore(kXml xml, xkXmlItemClass* next, xkXmlItemClass* item)
{
    item->parent = next->parent;
    item->prev = next->prev;
    item->next = next;

    if (!kIsNull(next->prev))
    {
        next->prev->next = item;
    }

    next->prev = item;

    if (!kIsNull(item->parent) && kIsNull(item->prev))
    {
        item->parent->firstChild = item;
    }

    return kOK;
}

kStatus xkXml_LinkItemAfter(kXml xml, xkXmlItemClass* prev, xkXmlItemClass* item)
{
    item->parent = prev->parent;
    item->prev = prev;
    item->next = prev->next;

    prev->next = item;

    if (!kIsNull(prev->next))
    {
        prev->next->prev = item;
    }

    if (!kIsNull(item->parent) && kIsNull(item->next))
    {
        item->parent->lastChild = item;
    }

    return kOK;
}

kStatus xkXml_UnlinkItem(kXml xml, xkXmlItemClass* item)
{
    kObj(kXml, xml);
    xkXmlItemClass* itemObj = xkXmlItem_Cast(item);
    xkXmlItemClass* parentObj = itemObj->parent;

    if (!kIsNull(parentObj))
    {
        if (parentObj->firstChild == itemObj)
        {
            parentObj->firstChild = itemObj->next;
        }
        if (parentObj->lastChild == itemObj)
        {
            parentObj->lastChild = itemObj->prev;
        }
    }
    else
    {
        obj->root = kNULL;
    }

    if (!kIsNull(itemObj->prev))
    {
        itemObj->prev->next = itemObj->next;
    }

    if (!kIsNull(itemObj->next))
    {
        itemObj->next->prev = itemObj->prev;
    }

    return kOK;
}

kStatus xkXml_LinkAttr(kXml xml, xkXmlItemClass* item, xkXmlAttrClass* attr)
{
    if (kIsNull(item->lastAttr))
    {
        item->firstAttr = attr;
        item->lastAttr = attr;
    }
    else
    {
        item->lastAttr->next = attr;
        item->lastAttr = attr;
    }

    return kOK;
}

xkXmlAttrClass* xkXml_FindAttrByName(kXml xml, xkXmlItemClass* item, const kChar* name)
{
    kObj(kXml, xml);
    xkXmlAttrClass* attr = item->firstAttr;

    while (!kIsNull(attr))
    {
        if (kStrEquals(xkXml_TextFieldString(obj, &attr->name), name))
        {
            return attr;
        }
        attr = attr->next;
    }

    return kNULL;
}

xkXmlItemClass* xkXml_FindChildByName(kXml xml, xkXmlItemClass* parent, const kChar* name, kSize index)
{
    kObj(kXml, xml);

    if (kIsNull(parent))
    {
        // special case, item to find must be root and indexed 0
        if (kIsNull(obj->root) || index != 0)
        {
            return kNULL;
        }
        if (!kStrEquals(xkXml_TextFieldString(xml, &obj->root->name), name))
        {
            return kNULL;
        }

        return obj->root;
    }
    else
    {
        xkXmlItemClass* child = parent->firstChild;
        kSize i = 0;

        while (!kIsNull(child))
        {
            if (kStrEquals(xkXml_TextFieldString(xml, &child->name), name))
            {
                if (i >= index)
                {
                    break;
                }
                i++;
            }
            child = child->next;
        }

        return child;
    }
}

kStatus xkXml_Substr(kXml xml, const kChar* input, kSize begin, kSize end, xkXmlTextField* str)
{
    kSize size = end - begin;
    kChar* cstr;

    kCheck(xkXml_TextFieldReserve(xml, str, size));
    cstr = xkXml_TextFieldString(xml, str);

    kCheck(kMemCopy(cstr, &input[begin], size));
    cstr[size] = '\0';
    
    return kOK;
}

kStatus xkXml_UnescapeSubstr(kXml xml, const kChar* input, kSize begin, kSize end, xkXmlTextField* str)
{
    kChar* writer;
    kSize i;
    kBool found;

    // a buffer that can hold the escaped string can also hold the unescaped string.
    kCheck(xkXml_TextFieldReserve(xml, str, end - begin));
    writer = xkXml_TextFieldString(xml, str);

    while (begin != end)
    {
        if (input[begin] == '&')
        {
            const kChar* escBegin;
            const kChar* escEnd;
            const kChar* escPos;

            ++begin;
            escBegin = &input[begin];

            while (begin != end && input[begin] != ';')
            {
                ++begin;
            }
            if (begin == end)
            {
                return kERROR;
            }
            escEnd = &input[begin];
            ++begin;

            found = kFALSE;

            for (i = 0; i < kCountOf(escSeqs); ++i)
            {
                if (strncmp(escSeqs[i].szSeq, escBegin, (kSize)(escEnd - escBegin)) == 0)
                {
                    *(writer++) = escSeqs[i].character;
                    found = kTRUE;
                    break;
                }
            }

            if (!found)
            {
                *(writer++) = '&';

                for (escPos = escBegin; escPos <= escEnd; ++escPos)
                {
                    *(writer++) = *escPos;
                }
            }
        }
        else
        {
            *(writer++) = input[begin++];
        }
    }

    *writer = '\0';

    return kOK;
}

// This function returns a pointer to '\0' if a delimiter is not found
const kChar* xkXml_FindDelimiter(const kChar* str, const kChar* delims)
{
    while (1)
    {
        const kChar* delimPtr;

        if (*str == '\0')
        {
            return str;
        }

        for (delimPtr = delims; *delimPtr != '\0'; ++delimPtr)
        {
            if (*delimPtr == *str)
            {
                return str;
            }
        }
        ++str;
    }
}

kSize xkXml_ParseNameSubstrIndex(const kChar* str, kSize size, kSize* adjustedSize)
{
    const kChar* openBracket = kNULL;
    const kChar* closeBracket = kNULL;
    const kChar* ptr;
    kChar buffer[128];

    for (ptr = str; ptr < (str + size); ++ptr)
    {
        if (*ptr == '[')
        {
            openBracket = ptr;
        }
        else if (*ptr == ']')
        {
            closeBracket = ptr;
        }

        if (openBracket && closeBracket)
        {
            break;
        }
    }

    if (openBracket && closeBracket && (openBracket < closeBracket))
    {
        kSize length = (kSize) (closeBracket - openBracket - 1);

        if (adjustedSize)
        {
            *adjustedSize = (kSize) (openBracket - str);
        }

        if (length > (kCountOf(buffer) - 1))
        {
            return 0;
        }

        kMemCopy(buffer, openBracket + 1, length);
        buffer[length] = '\0';
        
        return (kSize) atoi(buffer);
    }

    return 0;
}

kStatus xkXml_SetPathParseBuffer(kXml xml, const kChar* text, kSize size)
{
    kObj(kXml, xml);
    kSize bufferSize = size + 1;

    if (bufferSize > kArrayList_Capacity(obj->pathParseBuffer))
    {
        kCheck(kArrayList_Reserve(obj->pathParseBuffer, bufferSize));
    }

    kMemCopy(kArrayList_DataAt(obj->pathParseBuffer, 0), text, size);
    *(kChar*)kArrayList_DataAt(obj->pathParseBuffer, (kSSize)size) = '\0';

    return kOK;
}

const kChar* xkXml_PathParseBuffer(kXml xml)
{
    kObj(kXml, xml);
    return (kChar*)kArrayList_DataAt(obj->pathParseBuffer, 0);
}

kStatus xkXml_AddEscapedString(kXml xml, const kChar* in, kString out)
{
    kSize i, j;
    kSize len = kStrLength(in);
    const kChar* start;

    start = in;
    for (i = 0; i < len; ++i)
    {
        kChar c = in[i];          

        for (j = 0; j < kCountOf(escSeqs); ++j)
        {
            if (escSeqs[j].character == c)
            {
                break;
            }
        }

        // found
        if (j < kCountOf(escSeqs))
        {
            kCheck(kString_AddSubstring(out, start, 0u, (kSize) (&in[i] - start)));
            start = &in[i] + 1;
            kCheck(kString_Add(out, "&"));
            kCheck(kString_Add(out, escSeqs[j].szSeq));
            kCheck(kString_Add(out, ";"));
        }
    }

    kCheck(kString_AddSubstring(out, start, 0u, (kSize)(in + len - start)));

    return kOK;
}

kBool xkXml_ValidateName(const kChar *name)
{
    kSize nameLen = kStrLength(name);
    kSize i;

    if (nameLen < 1) 
    {
        return kFALSE;
    }

    if (!kChar_IsLetter(name[0]) && name[0] != '_' && name[0] != ':')
    {
        return kFALSE;
    }

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

    return kTRUE;
}

kBool xkXml_IsNameChar(kChar ch)
{
    if (kChar_IsLetter(ch)) 
    {
        return kTRUE;
    }

    if (kChar_IsDigit(ch)) 
    {
        return kTRUE;
    }

    switch(ch)
    {
    case '.':
    case '-':
    case '_':
    case ':':
        return kTRUE;

    default:
        return kFALSE; 
    }
}

const kChar* xkXml_ItemValue(kXml xml, kXmlItem item)
{
    xkXmlItemClass* itemObj = xkXmlItem_Cast(item);
    
    kAssert(!kIsNull(item));

    return xkXml_TextFieldString(xml, &itemObj->value);
}

const kChar* xkXml_AttrValue(kXml xml, kXmlItem item, const kChar* name)
{
    xkXmlAttrClass* attr = xkXml_FindAttrByName(xml, xkXmlItem_Cast(item), name);

    kAssert(!kIsNull(item));
    
    if (kIsNull(attr))
    {
        return kNULL;
    }

    return xkXml_TextFieldString(xml, &attr->value);
}

kStatus xkXml_ItemToString(kXml xml, kString str, xkXmlItemClass* item, kSize level)
{
    xkXmlAttrClass* attr;
    const kChar* value;
    const kChar* name;
    kSize freeSpace;
    kSize i;

    // Some rudimentary geometric buffer growth to improve performance - should be built into kString.
    // This is very important for larger XML objects.
    freeSpace = kString_Capacity(str) - kString_Length(str);
    if (freeSpace < xkXML_STRING_INIT_SIZE)
    {
        kCheck(kString_Reserve(str, kMath_Round32u_(kString_Capacity(str) * 1.5)));
    }

    value = xkXml_TextFieldString(xml, &item->value);
    name = xkXml_TextFieldString(xml, &item->name);

    for (i = 0; i < level; ++i)
    {
        kCheck(kString_Import(str, xkXML_INDENTATION, kTRUE));
    }

    kCheck(kString_Import(str, "<", kTRUE));
    kCheck(kString_Import(str, name, kTRUE));

    attr = item->firstAttr;
    while (!kIsNull(attr))
    {
        kCheck(kString_Import(str, "\x20", kTRUE));
        kCheck(kString_Import(str, xkXml_TextFieldString(xml, &attr->name), kTRUE));
        kCheck(kString_Import(str, "=\"", kTRUE));
        kCheck(xkXml_AddEscapedString(xml, xkXml_TextFieldString(xml, &attr->value), str));
        kCheck(kString_Import(str, "\"", kTRUE));

        attr = attr->next;
    }

    if ((kStrLength(value) > 0) || !kIsNull(item->firstChild))
    {
        xkXmlItemClass* child = item->firstChild;

        kCheck(kString_Import(str, ">", kTRUE));
        if (!kIsNull(child))
        {
            kCheck(kString_Import(str, xkXML_NEW_LINE, kTRUE));
        }

        kCheck(xkXml_AddEscapedString(xml, value, str));

        while (!kIsNull(child))
        {
            xkXml_ItemToString(xml, str, child, level + 1);
            child = child->next;
        }

        if (!kIsNull(item->firstChild))
        {
            for (i = 0; i < level; ++i)
            {
                kCheck(kString_Import(str, xkXML_INDENTATION, kTRUE));
            }
        }

        kCheck(kString_Import(str, "</", kTRUE));
        kCheck(kString_Import(str, name, kTRUE));
    }
    else
    {
        kCheck(kString_Import(str, " /", kTRUE));
    }
    
    kCheck(kString_Import(str, ">", kTRUE));
    kCheck(kString_Import(str, xkXML_NEW_LINE, kTRUE));

    return kOK;
}

kFx(kStatus) kXml_CopyItem(kXml xml, kXmlItem parent, kXmlItem before, kXml srcXml, kXmlItem srcItem, kXmlItem* item)
{
    kXmlItem newItem = kNULL;
    xkXmlItemClass* srcItemObj = xkXmlItem_Cast(srcItem);
    xkXmlAttrClass* attr;
    kXmlItem child;

    if (kIsNull(srcItem))
    {
        return kOK;
    }

    // Add item and set value
    if (kIsNull(before))
    {
        kCheck(kXml_AddItem(xml, parent, kXml_ItemName(srcXml, srcItem), &newItem));
    }
    else
    {
        if (!kIsNull(parent) && kXml_Parent(xml, before) != parent)
        {
            return kERROR_PARAMETER;
        }

        kCheck(kXml_InsertItem(xml, before, kXml_ItemName(srcXml, srcItem), &newItem));
    }
    kCheck(kXml_SetItemText(xml, newItem, xkXml_ItemValue(srcXml, srcItem)));

    // Copy attributes
    attr = srcItemObj->firstAttr;
    while (!kIsNull(attr))
    {
        kCheck(kXml_SetAttrText(xml, newItem,
            xkXml_TextFieldString(srcXml, &attr->name),
            xkXml_TextFieldString(srcXml, &attr->value)));
        attr = attr->next;
    }

    // Copy children
    child = kXml_FirstChild(srcXml, srcItem);
    while (!kIsNull(child))
    {
        kCheck(kXml_CopyItem(xml, newItem, kNULL, srcXml, child, kNULL));
        child = kXml_NextSibling(srcXml, child);
    }

    if (!kIsNull(item))
    {
        *item = newItem;
    }

    return kOK;
}

kFx(kStatus) kXml_OverwriteItem(kXml xml, kXmlItem destItem, kXml srcXml, kXmlItem srcItem)
{
    xkXmlItemClass* srcItemObj = xkXmlItem_Cast(srcItem);
    xkXmlAttrClass* attr;
    kXmlItem child;

    kCheckArgs(!kIsNull(destItem) && !kIsNull(srcXml) && !kIsNull(srcItem)); 

    kCheck(kXml_ClearItem(xml, destItem)); 

    //copy name
    kCheck(kXml_SetItemName(xml, destItem, kXml_ItemName(srcXml, srcItem))); 

    //copy attributes
    attr = srcItemObj->firstAttr;
    while (!kIsNull(attr))
    {
        kCheck(kXml_SetAttrText(xml, destItem, xkXml_TextFieldString(srcXml, &attr->name), xkXml_TextFieldString(srcXml, &attr->value)));
        attr = attr->next;
    }

    //copy children
    child = kXml_FirstChild(srcXml, srcItem);
    while (!kIsNull(child))
    {
        kCheck(kXml_CopyItem(xml, destItem, kNULL, srcXml, child, kNULL));
        child = kXml_NextSibling(srcXml, child);
    }

    return kOK;       
}


kFx(kStatus) kXml_ClearItem(kXml xml, kXmlItem item)
{
    kCheckArgs(!kIsNull(item)); 

    kCheck(kXml_DeleteChildren(xml, item)); 
    kCheck(kXml_DeleteAttrs(xml, item)); 

    return kOK; 
}

const kChar* xkXml_Format16u(kXml xml, k16u val)
{
    kObj(kXml, xml); 

    if (!kSuccess(k16u_Format(val, obj->formatBuffer, kCountOf(obj->formatBuffer))))
    {
        return kNULL;
    }

    return obj->formatBuffer;
}

const kChar* xkXml_Format16s(kXml xml, k16s val)
{
    kObj(kXml, xml); 

    if (!kSuccess(k16s_Format(val, obj->formatBuffer, kCountOf(obj->formatBuffer))))
    {
        return kNULL;
    }

    return obj->formatBuffer;
}

const kChar* xkXml_Format32u(kXml xml, k32u val)
{
    kObj(kXml, xml); 

    if (!kSuccess(k32u_Format(val, obj->formatBuffer, kCountOf(obj->formatBuffer))))
    {
        return kNULL;
    }

    return obj->formatBuffer;
}

const kChar* xkXml_Format32s(kXml xml, k32s val)
{
    kObj(kXml, xml); 

    if (!kSuccess(k32s_Format(val, obj->formatBuffer, kCountOf(obj->formatBuffer))))
    {
        return kNULL;
    }

    return obj->formatBuffer;
}

const kChar* xkXml_FormatBool(kXml xml, kBool val)
{
    kObj(kXml, xml); 

    if (!kSuccess(kBool_Format(val, obj->formatBuffer, kCountOf(obj->formatBuffer))))
    {
        return kNULL;
    }

    return obj->formatBuffer;
}

const kChar* xkXml_Format64u(kXml xml, k64u val)
{
    kObj(kXml, xml); 

    if (!kSuccess(k64u_Format(val, obj->formatBuffer, kCountOf(obj->formatBuffer))))
    {
        return kNULL;
    }

    return obj->formatBuffer;
}

const kChar* xkXml_Format64s(kXml xml, k64s val)
{
    kObj(kXml, xml); 

    if (!kSuccess(k64s_Format(val, obj->formatBuffer, kCountOf(obj->formatBuffer))))
    {
        return kNULL;
    }

    return obj->formatBuffer;
}

const kChar* xkXml_FormatSize(kXml xml, kSize val)
{
    kObj(kXml, xml); 

    if (!kSuccess(kSize_Format(val, obj->formatBuffer, kCountOf(obj->formatBuffer))))
    {
        return kNULL;
    }

    return obj->formatBuffer;
}

const kChar* xkXml_Format32f(kXml xml, k32f val)
{
    kObj(kXml, xml); 

    if (!kSuccess(xkStrFormat32f(val, obj->formatBuffer, kCountOf(obj->formatBuffer), k32F_DIGITS, kNULL)))
    {
        return kNULL;
    }

    return obj->formatBuffer;
}

const kChar* xkXml_Format64f(kXml xml, k64f val)
{
    kObj(kXml, xml); 

    if (!kSuccess(xkStrFormat64f(val, obj->formatBuffer, kCountOf(obj->formatBuffer), k64F_DIGITS, kNULL)))
    {
        return kNULL;
    }

    return obj->formatBuffer;
}

kStatus xkXml_Parse16u(kXml xml, const kChar *string, k16u *val)
{
    return k16u_Parse(val, string);
}

kStatus xkXml_Parse16s(kXml xml, const kChar *string, k16s *val)
{
    return k16s_Parse(val, string);
}

kStatus xkXml_Parse32u(kXml xml, const kChar *string, k32u *val)
{
    return k32u_Parse(val, string);
}

kStatus xkXml_Parse32s(kXml xml, const kChar *string, k32s *val)
{
    return k32s_Parse(val, string);
}

kStatus xkXml_ParseBool(kXml xml, const kChar *string, kBool *val)
{
    return kBool_Parse(val, string);
}

kStatus xkXml_Parse64u(kXml xml, const kChar *string, k64u *val)
{
    return k64u_Parse(val, string);
}

kStatus xkXml_Parse64s(kXml xml, const kChar *string, k64s *val)
{
    return k64s_Parse(val, string);
}

kStatus xkXml_ParseSize(kXml xml, const kChar *string, kSize *val)
{
    return kSize_Parse(val, string);
}

kStatus xkXml_Parse32f(kXml xml, const kChar *string, k32f *val)
{
    return k32f_Parse(val, string);
}

kStatus xkXml_Parse64f(kXml xml, const kChar *string, k64f *val)
{
    return k64f_Parse(val, string);
}

kStatus xkXml_AdvanceParser(xkXmlParseContext* context)
{
    kObj(kXml, context->xml);

    if (context->isStream)
    {
        kChar ch;
        kStatus status;

        if (context->eof)
        {
            return kERROR;
        }

        status = kStream_Read(context->stream, &ch, sizeof(kChar));
        if (!kSuccess(status))
        {
            context->eof = kTRUE;
            return kERROR;
        }

        if ((context->size + 1) > context->capacity)
        {
            kChar* newBuffer;
            kSize position = (kSize)(context->position - context->buffer);

            kCheck(kAlloc_Get(obj->base.alloc, context->capacity * 2, &newBuffer));
            kCheck(kMemCopy(newBuffer, context->buffer, context->size));
            kCheck(kAlloc_Free(obj->base.alloc, context->buffer));
            context->buffer = kNULL;
            context->buffer = newBuffer;
            context->capacity = context->capacity * 2;

            context->position = context->buffer + position;
        }
        context->buffer[context->size] = ch;
        ++context->size;
        ++context->position;
    }
    else
    {
        if (*context->position == '\0')
        {
            return kERROR;
        }
        ++context->position;
    }
    return kOK;
}

kStatus xkXml_SkipWhitespace(xkXmlParseContext* context)
{
    while (kChar_IsSpace(*context->position))
    {
        kCheck(xkXml_AdvanceParser(context));
    }
    return kOK;
}

kStatus xkXml_SkipToken(xkXmlParseContext* context)
{
    while (!kChar_IsSpace(*context->position) &&
        *context->position != '<' &&
        *context->position != '/' &&
        *context->position != '>' &&
        *context->position != '=')
    {
        kCheck(xkXml_AdvanceParser(context));
    }

    return kOK;
}

kStatus xkXml_Parse(kXml xml, xkXmlParseContext* context)
{
    kObj(kXml, xml);
    kSize valueBegin = 0; 
    kSize valueEnd = 0; 
    xkXmlTagType tagType = TAG_CLOSE;

    kXml_Clear(xml);

    while (*context->position != '\0')
    {
        if (*context->position == '<')
        {
            valueEnd = (kSize) (context->position - context->buffer);

            kCheck(xkXml_AdvanceParser(context));
            kCheck(xkXml_SkipWhitespace(context));

            // Comment/declaration parsing is a bit sloppy and probably not entirely spec-conforming but should do for our purposes.
            // TODO: make this more rigorous
            if (*context->position == '!')
            {
                kCheck(xkXml_ParseComment(xml, context));
            }
            else if (*context->position == '?')
            {
                kCheck(xkXml_ParseDeclaration(xml, context));
            }
            else
            {
                xkXmlTagType prevTagType = tagType;
                xkXmlItemClass* prevNode = context->currentItem;
                kBool done = kFALSE;
                
                kCheck(xkXml_ParseTag(xml, context, &tagType));

                // logic in this part needs some clean up
                if (kIsNull(context->currentItem) && (tagType == TAG_CLOSE || tagType == TAG_EMPTY))
                {
                    // root element is found (but don't stop yet, not necessarily completed)
                    done = kTRUE;
                }

                if (!done)
                {
                    kCheck(xkXml_AdvanceParser(context));
                }
 
                if (tagType == TAG_OPEN)
                {
                    valueBegin = (kSize) (context->position - context->buffer);
                }
                else if (tagType == TAG_CLOSE)
                {
                    // this detects a non-nested open/close pair. content within is considered as value.
                    // note: this doesn't support text nodes along side other nodes, but this isn't something
                    // we use. In fact there's no way on the API level to access this (only a single "value"
                    // per node, which isn't enough to represent text nodes).
                    // TODO: add this?
                    if (prevTagType == TAG_OPEN)
                    {
                        kCheck(xkXml_UnescapeSubstr(xml, context->buffer, valueBegin, valueEnd, &prevNode->value));
                    }
                    else
                    {
                        kCheck(xkXml_TextFieldSetString(xml, &prevNode->value, ""));
                    }
                }

                if (done)
                {
                    break;
                }
            }
        }
        else
        {
            if (*context->position != '\0')
            {
                kCheck(xkXml_AdvanceParser(context));
            }
        }
    }

    if (!kIsNull(context->currentItem) || kIsNull(obj->root))
    {
        return kERROR;
    }

    return kOK;
}

kStatus xkXml_ParseTag(kXml xml, xkXmlParseContext* context, xkXmlTagType* tagType)
{
    kSize tokenBegin;
    kSize tokenEnd;

    // read name
    kCheck(xkXml_SkipWhitespace(context));

    tokenBegin = (kSize) (context->position - context->buffer);
    
    kCheck(xkXml_AdvanceParser(context));
    kCheck(xkXml_SkipToken(context));
    
    if (!kChar_IsSpace(*context->position) && *context->position != '/' && *context->position != '>')
    {
        return kERROR;
    }

    tokenEnd = (kSize) (context->position - context->buffer);

    if (tokenBegin >= tokenEnd)
    {
        return kERROR; // zero length token is bad
    }

    if (context->buffer[tokenBegin] == '/') // closing item
    {       
        if (strncmp(xkXml_TextFieldString(xml, &context->currentItem->name), &context->buffer[tokenBegin + 1], tokenEnd - tokenBegin - 1) != 0)
        {
            return kERROR; // mismatched tags
        }

        context->currentItem = context->currentItem->parent;
        *tagType = TAG_CLOSE;
    }
    else // beginning item
    {
        xkXmlItemClass* parentItem;

        parentItem = context->currentItem;
        kCheck(xkXml_AllocItem(xml, &context->currentItem));
        kCheck(xkXml_LinkItemUnder(xml, parentItem, context->currentItem));

        kCheck(xkXml_Substr(xml, context->buffer, tokenBegin, tokenEnd, &context->currentItem->name));
        kCheck(xkXml_ParseAttributes(xml, context));

        // parse until end
        kCheck(xkXml_SkipWhitespace(context));

        if (*context->position == '/') // empty item
        {
            kCheck(xkXml_AdvanceParser(context));
            kCheck(xkXml_TextFieldSetString(xml, &context->currentItem->value, ""));
            context->currentItem = parentItem;
            *tagType = TAG_EMPTY;
        }
        else
        {
            *tagType = TAG_OPEN;
        }
    }

    kCheck(xkXml_SkipWhitespace(context));

    if (*context->position != '>')
    {
        return kERROR;
    }

    return kOK;
}

kStatus xkXml_ParseAttributes(kXml xml, xkXmlParseContext* context)
{
    kSize tokenBegin; // use index because pointer can change during parsing
    kSize tokenEnd;
    xkXmlAttrClass* attr = kNULL;

    while (kTRUE)
    {
        kCheck(xkXml_SkipWhitespace(context));

        if (*context->position == '/' || *context->position == '>')
        {
            break;
        }
        tokenBegin = (kSize) (context->position - context->buffer);
        kCheck(xkXml_SkipToken(context));
        tokenEnd = (kSize) (context->position - context->buffer);
        
        if (!kChar_IsSpace(context->buffer[tokenEnd]) &&
            context->buffer[tokenEnd] != '/' &&
            context->buffer[tokenEnd] != '>' &&
            context->buffer[tokenEnd] != '=')
        {
            return kERROR;
        }

        // allocate and insert
        kCheck(xkXml_AllocAttr(xml, &attr));
        kCheck(xkXml_LinkAttr(xml, context->currentItem, attr));
        kCheck(xkXml_Substr(xml, context->buffer, tokenBegin, tokenEnd, &attr->name));

        // parse value
        kCheck(xkXml_SkipWhitespace(context));

        if (*context->position == '=')
        {
            kChar quote;

            kCheck(xkXml_AdvanceParser(context));
            kCheck(xkXml_SkipWhitespace(context));

            quote = *context->position;

            if (quote != '\'' && quote != '\"')
            {
                return kERROR;
            }

            kCheck(xkXml_AdvanceParser(context));
            tokenBegin = (kSize) (context->position - context->buffer);
            
            while (*context->position != '\0' && *context->position != quote)
            {
                kCheck(xkXml_AdvanceParser(context));
            }

            tokenEnd = (kSize) (context->position - context->buffer);
            
            if (context->buffer[tokenEnd] != quote)
            {
                return kERROR;
            }

            kCheck(xkXml_Substr(xml, context->buffer, tokenBegin, tokenEnd, &attr->value));
            kCheck(xkXml_AdvanceParser(context));
        }
        else
        {
            kCheck(xkXml_TextFieldSetString(xml, &attr->value, ""));
        }
    }

    return kOK;
}

kStatus xkXml_ParseComment(kXml xml, xkXmlParseContext* context)
{
    k32u state = 0;

    kCheck(xkXml_AdvanceParser(context));

    if (*context->position != '-')
    {
        return kERROR;
    }

    kCheck(xkXml_AdvanceParser(context));
    
    if (*context->position != '-')
    {
        return kERROR;
    }

    kCheck(xkXml_AdvanceParser(context));

    while (*context->position != '\0')
    {
        kChar c = *context->position;

        switch(state)
        {
        case 0:
            if (c == '-')
                state = 1;
            break;
        case 1:
            if (c == '-')
                state = 2;
            else
                state = 0;
            break;
        case 2:
            if (c == '>')
            {
                kCheck(xkXml_AdvanceParser(context));
                return kOK;
            }
            else if (c != '-')
            {
                state = 0;
            }
            break;
        }

        kCheck(xkXml_AdvanceParser(context));
    }
    return kERROR;
}

kStatus xkXml_ParseDeclaration(kXml xml, xkXmlParseContext* context)
{
    k32u state = 0;

    kCheck(xkXml_AdvanceParser(context));

    while (*context->position != '\0')
    {
        switch(state)
        {
        case 0:
            if (*context->position == '?')
            {
                state = 1;
            }
            break;
        case 1:
            if (*context->position == '>')
            {
                kCheck(xkXml_AdvanceParser(context));
                return kOK;
            }
            else if (*context->position != '?')
            {
                state = 0;
            }
            break;
        }

        kCheck(xkXml_AdvanceParser(context));
    }

    return kERROR;
}
