Files
2024-05-18 23:53:26 +08:00

2812 lines
66 KiB
C++

/**
* @file kXml.cpp
*
* @internal
* Copyright (C) 2004-2021 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)
kAddPrivateVersionEx(kXml, "kdat6", "6.0.0.0", "kXml-0", WriteDat6V0, ReadDat6V0)
kAddFrameworkConstructor(kXml, Construct)
kAddPrivateVMethod(kXml, kObject, VRelease)
kAddPrivateVMethod(kXml, kObject, VClone)
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_VClone(kXml xml, kXml source, kAlloc valueAlloc, kObject context)
{
return kXml_Assign(xml, source);
}
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)
{
kString string = kNULL;
kTry
{
kTest(kSerializer_ReadObject(serializer, &string, kObject_Alloc(xml)));
kTestTrue(kObject_Is(string, kTypeOf(kString)), kERROR_FORMAT);
kTest(kXml_FromString(xml, string));
}
kFinally
{
kObject_Destroy(string);
kEndFinally();
}
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;
}