/**
 * @file llxmlnode.cpp
 * @author Tom Yedwab
 * @brief LLXMLNode implementation
 *
 * $LicenseInfo:firstyear=2005&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"

#include <iostream>
#include <map>

#include "llxmlnode.h"

#include "v3color.h"
#include "v4color.h"
#include "v4coloru.h"
#include "v3math.h"
#include "v3dmath.h"
#include "v4math.h"
#include "llquaternion.h"
#include "llstring.h"
#include "lluuid.h"
#include "lldir.h"

// static
BOOL LLXMLNode::sStripEscapedStrings = TRUE;
BOOL LLXMLNode::sStripWhitespaceValues = FALSE;

LLXMLNode::LLXMLNode() :
    mID(""),
    mParser(NULL),
    mIsAttribute(FALSE),
    mVersionMajor(0),
    mVersionMinor(0),
    mLength(0),
    mPrecision(64),
    mType(TYPE_CONTAINER),
    mEncoding(ENCODING_DEFAULT),
    mLineNumber(-1),
    mParent(NULL),
    mChildren(NULL),
    mAttributes(),
    mPrev(NULL),
    mNext(NULL),
    mName(NULL),
    mValue(""),
    mDefault(NULL)
{
}

LLXMLNode::LLXMLNode(const char* name, BOOL is_attribute) :
    mID(""),
    mParser(NULL),
    mIsAttribute(is_attribute),
    mVersionMajor(0),
    mVersionMinor(0),
    mLength(0),
    mPrecision(64),
    mType(TYPE_CONTAINER),
    mEncoding(ENCODING_DEFAULT),
    mLineNumber(-1),
    mParent(NULL),
    mChildren(NULL),
    mAttributes(),
    mPrev(NULL),
    mNext(NULL),
    mValue(""),
    mDefault(NULL)
{
    mName = gStringTable.addStringEntry(name);
}

LLXMLNode::LLXMLNode(LLStringTableEntry* name, BOOL is_attribute) :
    mID(""),
    mParser(NULL),
    mIsAttribute(is_attribute),
    mVersionMajor(0),
    mVersionMinor(0),
    mLength(0),
    mPrecision(64),
    mType(TYPE_CONTAINER),
    mEncoding(ENCODING_DEFAULT),
    mLineNumber(-1),
    mParent(NULL),
    mChildren(NULL),
    mAttributes(),
    mPrev(NULL),
    mNext(NULL),
    mName(name),
    mValue(""),
    mDefault(NULL)
{
}

// copy constructor (except for the children)
LLXMLNode::LLXMLNode(const LLXMLNode& rhs) :
    mID(rhs.mID),
    mIsAttribute(rhs.mIsAttribute),
    mVersionMajor(rhs.mVersionMajor),
    mVersionMinor(rhs.mVersionMinor),
    mLength(rhs.mLength),
    mPrecision(rhs.mPrecision),
    mType(rhs.mType),
    mEncoding(rhs.mEncoding),
    mLineNumber(0),
    mParser(NULL),
    mParent(NULL),
    mChildren(NULL),
    mAttributes(),
    mPrev(NULL),
    mNext(NULL),
    mName(rhs.mName),
    mValue(rhs.mValue),
    mDefault(rhs.mDefault)
{
}

// returns a new copy of this node and all its children
LLXMLNodePtr LLXMLNode::deepCopy()
{
    LLXMLNodePtr newnode = LLXMLNodePtr(new LLXMLNode(*this));
    if (mChildren.notNull())
    {
        for (LLXMLChildList::iterator iter = mChildren->map.begin();
             iter != mChildren->map.end(); ++iter)
        {
            LLXMLNodePtr temp_ptr_for_gcc(iter->second->deepCopy());
            newnode->addChild(temp_ptr_for_gcc);
        }
    }
    for (LLXMLAttribList::iterator iter = mAttributes.begin();
         iter != mAttributes.end(); ++iter)
    {
        LLXMLNodePtr temp_ptr_for_gcc(iter->second->deepCopy());
        newnode->addChild(temp_ptr_for_gcc);
    }

    return newnode;
}

// virtual
LLXMLNode::~LLXMLNode()
{
    // Strictly speaking none of this should be required execept 'delete mChildren'...
    // Sadly, that's only true if we hadn't had reference-counted smart pointers linked
    // in three different directions. This entire class is a frightening, hard-to-maintain
    // mess.
    if (mChildren.notNull())
    {
        for (LLXMLChildList::iterator iter = mChildren->map.begin();
             iter != mChildren->map.end(); ++iter)
        {
            LLXMLNodePtr child = iter->second;
            child->mParent = NULL;
            child->mNext = NULL;
            child->mPrev = NULL;
        }
        mChildren->map.clear();
        mChildren->head = NULL;
        mChildren->tail = NULL;
        mChildren = NULL;
    }
    for (LLXMLAttribList::iterator iter = mAttributes.begin();
         iter != mAttributes.end(); ++iter)
    {
        LLXMLNodePtr attr = iter->second;
        attr->mParent = NULL;
        attr->mNext = NULL;
        attr->mPrev = NULL;
    }
    llassert(mParent == NULL);
    mDefault = NULL;
}

BOOL LLXMLNode::isNull()
{
    return (mName == NULL);
}

// protected
BOOL LLXMLNode::removeChild(LLXMLNode *target_child)
{
    if (!target_child)
    {
        return FALSE;
    }
    if (target_child->mIsAttribute)
    {
        LLXMLAttribList::iterator children_itr = mAttributes.find(target_child->mName);
        if (children_itr != mAttributes.end())
        {
            target_child->mParent = NULL;
            mAttributes.erase(children_itr);
            return TRUE;
        }
    }
    else if (mChildren.notNull())
    {
        LLXMLChildList::iterator children_itr = mChildren->map.find(target_child->mName);
        while (children_itr != mChildren->map.end())
        {
            if (target_child == children_itr->second)
            {
                if (target_child == mChildren->head)
                {
                    mChildren->head = target_child->mNext;
                }
                if (target_child == mChildren->tail)
                {
                    mChildren->tail = target_child->mPrev;
                }

                LLXMLNodePtr prev = target_child->mPrev;
                LLXMLNodePtr next = target_child->mNext;
                if (prev.notNull()) prev->mNext = next;
                if (next.notNull()) next->mPrev = prev;

                target_child->mPrev = NULL;
                target_child->mNext = NULL;
                target_child->mParent = NULL;
                mChildren->map.erase(children_itr);
                if (mChildren->map.empty())
                {
                    mChildren = NULL;
                }
                return TRUE;
            }
            else if (children_itr->first != target_child->mName)
            {
                break;
            }
            else
            {
                ++children_itr;
            }
        }
    }
    return FALSE;
}

void LLXMLNode::addChild(LLXMLNodePtr& new_child)
{
    if (new_child->mParent != NULL)
    {
        if (new_child->mParent == this)
        {
            return;
        }
        new_child->mParent->removeChild(new_child);
    }

    new_child->mParent = this;
    if (new_child->mIsAttribute)
    {
        LLXMLAttribList::iterator found_it = mAttributes.find(new_child->mName);
        if (found_it != mAttributes.end())
        {
            removeChild(found_it->second);
        }
        mAttributes.insert(std::make_pair(new_child->mName, new_child));
    }
    else
    {
        if (mChildren.isNull())
        {
            mChildren = new LLXMLChildren();
            mChildren->head = new_child;
            mChildren->tail = new_child;
        }
        mChildren->map.insert(std::make_pair(new_child->mName, new_child));

        if (mChildren->tail != new_child)
        {
            mChildren->tail->mNext = new_child;
            new_child->mPrev = mChildren->tail;
            mChildren->tail = new_child;
        }
    }

    new_child->updateDefault();
}

// virtual
LLXMLNodePtr LLXMLNode::createChild(const char* name, BOOL is_attribute)
{
    return createChild(gStringTable.addStringEntry(name), is_attribute);
}

// virtual
LLXMLNodePtr LLXMLNode::createChild(LLStringTableEntry* name, BOOL is_attribute)
{
    LLXMLNodePtr ret(new LLXMLNode(name, is_attribute));
    ret->mID.clear();

    addChild(ret);
    return ret;
}

BOOL LLXMLNode::deleteChild(LLXMLNode *child)
{
    if (removeChild(child))
    {
        return TRUE;
    }
    return FALSE;
}

void LLXMLNode::setParent(LLXMLNodePtr& new_parent)
{
    if (new_parent.notNull())
    {
        LLXMLNodePtr this_ptr(this);
        new_parent->addChild(this_ptr);
    }
    else
    {
        if (mParent != NULL)
        {
            LLXMLNodePtr old_parent = mParent;
            mParent = NULL;
            old_parent->removeChild(this);
        }
    }
}


void LLXMLNode::updateDefault()
{
    if (mParent != NULL && !mParent->mDefault.isNull())
    {
        mDefault = NULL;

        // Find default value in parent's default tree
        if (!mParent->mDefault.isNull())
        {
            findDefault(mParent->mDefault);
        }
    }

    if (mChildren.notNull())
    {
        LLXMLChildList::const_iterator children_itr;
        LLXMLChildList::const_iterator children_end = mChildren->map.end();
        for (children_itr = mChildren->map.begin(); children_itr != children_end; ++children_itr)
        {
            LLXMLNodePtr child = (*children_itr).second;
            child->updateDefault();
        }
    }
}

void XMLCALL StartXMLNode(void *userData,
                          const XML_Char *name,
                          const XML_Char **atts)
{
    // Create a new node
    LLXMLNode *new_node_ptr = new LLXMLNode(name, FALSE);

    LLXMLNodePtr new_node = new_node_ptr;
    new_node->mID.clear();
    LLXMLNodePtr ptr_new_node = new_node;

    // Set the parent-child relationship with the current active node
    LLXMLNode* parent = (LLXMLNode *)userData;

    if (NULL == parent)
    {
        LL_WARNS() << "parent (userData) is NULL; aborting function" << LL_ENDL;
        return;
    }

    new_node_ptr->mParser = parent->mParser;
    new_node_ptr->setLineNumber(XML_GetCurrentLineNumber(*new_node_ptr->mParser));

    // Set the current active node to the new node
    XML_Parser *parser = parent->mParser;
    XML_SetUserData(*parser, (void *)new_node_ptr);

    // Parse attributes
    U32 pos = 0;
    while (atts[pos] != NULL)
    {
        std::string attr_name = atts[pos];
        std::string attr_value = atts[pos+1];

        // Special cases
        if ('i' == attr_name[0] && "id" == attr_name)
        {
            new_node->mID = attr_value;
        }
        else if ('v' == attr_name[0] && "version" == attr_name)
        {
            U32 version_major = 0;
            U32 version_minor = 0;
            if (sscanf(attr_value.c_str(), "%d.%d", &version_major, &version_minor) > 0)
            {
                new_node->mVersionMajor = version_major;
                new_node->mVersionMinor = version_minor;
            }
        }
        else if (('s' == attr_name[0] && "size" == attr_name) || ('l' == attr_name[0] && "length" == attr_name))
        {
            U32 length;
            if (sscanf(attr_value.c_str(), "%d", &length) > 0)
            {
                new_node->mLength = length;
            }
        }
        else if ('p' == attr_name[0] && "precision" == attr_name)
        {
            U32 precision;
            if (sscanf(attr_value.c_str(), "%d", &precision) > 0)
            {
                new_node->mPrecision = precision;
            }
        }
        else if ('t' == attr_name[0] && "type" == attr_name)
        {
            if ("boolean" == attr_value)
            {
                new_node->mType = LLXMLNode::TYPE_BOOLEAN;
            }
            else if ("integer" == attr_value)
            {
                new_node->mType = LLXMLNode::TYPE_INTEGER;
            }
            else if ("float" == attr_value)
            {
                new_node->mType = LLXMLNode::TYPE_FLOAT;
            }
            else if ("string" == attr_value)
            {
                new_node->mType = LLXMLNode::TYPE_STRING;
            }
            else if ("uuid" == attr_value)
            {
                new_node->mType = LLXMLNode::TYPE_UUID;
            }
            else if ("noderef" == attr_value)
            {
                new_node->mType = LLXMLNode::TYPE_NODEREF;
            }
        }
        else if ('e' == attr_name[0] && "encoding" == attr_name)
        {
            if ("decimal" == attr_value)
            {
                new_node->mEncoding = LLXMLNode::ENCODING_DECIMAL;
            }
            else if ("hex" == attr_value)
            {
                new_node->mEncoding = LLXMLNode::ENCODING_HEX;
            }
            /*else if (attr_value == "base32")
            {
                new_node->mEncoding = LLXMLNode::ENCODING_BASE32;
            }*/
        }

        // only one attribute child per description
        LLXMLNodePtr attr_node;
        if (!new_node->getAttribute(attr_name.c_str(), attr_node, FALSE))
        {
            attr_node = new LLXMLNode(attr_name.c_str(), TRUE);
            attr_node->setLineNumber(XML_GetCurrentLineNumber(*new_node_ptr->mParser));
        }
        attr_node->setValue(attr_value);
        new_node->addChild(attr_node);

        pos += 2;
    }

    if (parent)
    {
        parent->addChild(new_node);
    }
}

void XMLCALL EndXMLNode(void *userData,
                        const XML_Char *name)
{
    // [FUGLY] Set the current active node to the current node's parent
    LLXMLNode *node = (LLXMLNode *)userData;
    XML_Parser *parser = node->mParser;
    XML_SetUserData(*parser, (void *)node->mParent);
    // SJB: total hack:
    if (LLXMLNode::sStripWhitespaceValues)
    {
        std::string value = node->getValue();
        BOOL is_empty = TRUE;
        for (std::string::size_type s = 0; s < value.length(); s++)
        {
            char c = value[s];
            if (c != ' ' && c != '\t' && c != '\n')
            {
                is_empty = FALSE;
                break;
            }
        }
        if (is_empty)
        {
            value.clear();
            node->setValue(value);
        }
    }
}

void XMLCALL XMLData(void *userData,
                     const XML_Char *s,
                     int len)
{
    LLXMLNode* current_node = (LLXMLNode *)userData;
    std::string value = current_node->getValue();
    if (LLXMLNode::sStripEscapedStrings)
    {
        if (s[0] == '\"' && s[len-1] == '\"')
        {
            // Special-case: Escaped string.
            std::string unescaped_string;
            for (S32 pos=1; pos<len-1; ++pos)
            {
                if (s[pos] == '\\' && s[pos+1] == '\\')
                {
                    unescaped_string.append("\\");
                    ++pos;
                }
                else if (s[pos] == '\\' && s[pos+1] == '\"')
                {
                    unescaped_string.append("\"");
                    ++pos;
                }
                else
                {
                    unescaped_string.append(&s[pos], 1);
                }
            }
            value.append(unescaped_string);
            current_node->setValue(value);
            return;
        }
    }
    value.append(std::string(s, len));
    current_node->setValue(value);
}



// static
bool LLXMLNode::updateNode(
    LLXMLNodePtr& node,
    LLXMLNodePtr& update_node)
{

    if (!node || !update_node)
    {
        LL_WARNS() << "Node invalid" << LL_ENDL;
        return FALSE;
    }

    //update the node value
    node->mValue = update_node->mValue;

    //update all attribute values
    LLXMLAttribList::const_iterator itor;

    for(itor = update_node->mAttributes.begin(); itor != update_node->mAttributes.end(); ++itor)
    {
        const LLStringTableEntry* attribNameEntry = (*itor).first;
        LLXMLNodePtr updateAttribNode = (*itor).second;

        LLXMLNodePtr attribNode;

        node->getAttribute(attribNameEntry, attribNode, 0);

        if (attribNode)
        {
            attribNode->mValue = updateAttribNode->mValue;
        }
    }

    //update all of node's children with updateNodes children that match name
    LLXMLNodePtr child = node->getFirstChild();
    LLXMLNodePtr last_child = child;
    LLXMLNodePtr updateChild;

    for (updateChild = update_node->getFirstChild(); updateChild.notNull();
         updateChild = updateChild->getNextSibling())
    {
        while(child.notNull())
        {
            std::string nodeName;
            std::string updateName;

            updateChild->getAttributeString("name", updateName);
            child->getAttributeString("name", nodeName);


            //if it's a combobox there's no name, but there is a value
            if (updateName.empty())
            {
                updateChild->getAttributeString("value", updateName);
                child->getAttributeString("value", nodeName);
            }

            if ((nodeName != "") && (updateName == nodeName))
            {
                updateNode(child, updateChild);
                last_child = child;
                child = child->getNextSibling();
                if (child.isNull())
                {
                    child = node->getFirstChild();
                }
                break;
            }

            child = child->getNextSibling();
            if (child.isNull())
            {
                child = node->getFirstChild();
            }
            if (child == last_child)
            {
                break;
            }
        }
    }

    return TRUE;
}

// static
bool LLXMLNode::parseFile(const std::string& filename, LLXMLNodePtr& node, LLXMLNode* defaults_tree)
{
    // Read file
    LL_DEBUGS("XMLNode") << "parsing XML file: " << filename << LL_ENDL;
    LLFILE* fp = LLFile::fopen(filename, "rb");     /* Flawfinder: ignore */
    if (fp == NULL)
    {
        node = NULL ;
        return false;
    }
    fseek(fp, 0, SEEK_END);
    U32 length = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    U8* buffer = new U8[length+1];
    size_t nread = fread(buffer, 1, length, fp);
    buffer[nread] = 0;
    fclose(fp);

    bool rv = parseBuffer(buffer, nread, node, defaults_tree);
    delete [] buffer;
    return rv;
}

// static
bool LLXMLNode::parseBuffer(
    U8* buffer,
    U32 length,
    LLXMLNodePtr& node,
    LLXMLNode* defaults)
{
    // Init
    XML_Parser my_parser = XML_ParserCreate(NULL);
    XML_SetElementHandler(my_parser, StartXMLNode, EndXMLNode);
    XML_SetCharacterDataHandler(my_parser, XMLData);

    // Create a root node
    LLXMLNode *file_node_ptr = new LLXMLNode("XML", FALSE);
    LLXMLNodePtr file_node = file_node_ptr;

    file_node->mParser = &my_parser;

    XML_SetUserData(my_parser, (void *)file_node_ptr);

    // Do the parsing
    if (XML_Parse(my_parser, (const char *)buffer, length, TRUE) != XML_STATUS_OK)
    {
        LL_WARNS() << "Error parsing xml error code: "
                << XML_ErrorString(XML_GetErrorCode(my_parser))
                << " on line " << XML_GetCurrentLineNumber(my_parser)
                << LL_ENDL;
    }

    // Deinit
    XML_ParserFree(my_parser);

    if (!file_node->mChildren || file_node->mChildren->map.size() != 1)
    {
        LL_WARNS() << "Parse failure - wrong number of top-level nodes xml."
                << LL_ENDL;
        node = NULL ;
        return false;
    }

    LLXMLNode *return_node = file_node->mChildren->map.begin()->second;

    return_node->setDefault(defaults);
    return_node->updateDefault();

    node = return_node;
    return true;
}

// static
bool LLXMLNode::parseStream(
    std::istream& str,
    LLXMLNodePtr& node,
    LLXMLNode* defaults)
{
    // Init
    XML_Parser my_parser = XML_ParserCreate(NULL);
    XML_SetElementHandler(my_parser, StartXMLNode, EndXMLNode);
    XML_SetCharacterDataHandler(my_parser, XMLData);

    // Create a root node
    LLXMLNode *file_node_ptr = new LLXMLNode("XML", FALSE);
    LLXMLNodePtr file_node = file_node_ptr;

    file_node->mParser = &my_parser;

    XML_SetUserData(my_parser, (void *)file_node_ptr);

    const int BUFSIZE = 1024;
    U8* buffer = new U8[BUFSIZE];

    while(str.good())
    {
        str.read((char*)buffer, BUFSIZE);
        int count = (int)str.gcount();

        if (XML_Parse(my_parser, (const char *)buffer, count, !str.good()) != XML_STATUS_OK)
        {
            LL_WARNS() << "Error parsing xml error code: "
                    << XML_ErrorString(XML_GetErrorCode(my_parser))
                    << " on lne " << XML_GetCurrentLineNumber(my_parser)
                    << LL_ENDL;
            break;
        }
    }

    delete [] buffer;

    // Deinit
    XML_ParserFree(my_parser);

    if (!file_node->mChildren || file_node->mChildren->map.size() != 1)
    {
        LL_WARNS() << "Parse failure - wrong number of top-level nodes xml."
                << LL_ENDL;
        node = NULL;
        return false;
    }

    LLXMLNode *return_node = file_node->mChildren->map.begin()->second;

    return_node->setDefault(defaults);
    return_node->updateDefault();

    node = return_node;
    return true;
}


BOOL LLXMLNode::isFullyDefault()
{
    if (mDefault.isNull())
    {
        return FALSE;
    }
    BOOL has_default_value = (mValue == mDefault->mValue);
    BOOL has_default_attribute = (mIsAttribute == mDefault->mIsAttribute);
    BOOL has_default_type = mIsAttribute || (mType == mDefault->mType);
    BOOL has_default_encoding = mIsAttribute || (mEncoding == mDefault->mEncoding);
    BOOL has_default_precision = mIsAttribute || (mPrecision == mDefault->mPrecision);
    BOOL has_default_length = mIsAttribute || (mLength == mDefault->mLength);

    if (has_default_value
        && has_default_type
        && has_default_encoding
        && has_default_precision
        && has_default_length
        && has_default_attribute)
    {
        if (mChildren.notNull())
        {
            LLXMLChildList::const_iterator children_itr;
            LLXMLChildList::const_iterator children_end = mChildren->map.end();
            for (children_itr = mChildren->map.begin(); children_itr != children_end; ++children_itr)
            {
                LLXMLNodePtr child = (*children_itr).second;
                if (!child->isFullyDefault())
                {
                    return FALSE;
                }
            }
        }
        return TRUE;
    }

    return FALSE;
}

// static
bool LLXMLNode::getLayeredXMLNode(LLXMLNodePtr& root,
                                  const std::vector<std::string>& paths)
{
    if (paths.empty()) return false;

    std::string filename = paths.front();
    if (filename.empty())
    {
        return false;
    }

    if (!LLXMLNode::parseFile(filename, root, NULL))
    {
        LL_WARNS() << "Problem reading UI description file: " << filename << " " << errno << LL_ENDL;
        return false;
    }

    LLXMLNodePtr updateRoot;

    std::vector<std::string>::const_iterator itor;

    // We've already dealt with the first item, skip that one
    for (itor = paths.begin() + 1; itor != paths.end(); ++itor)
    {
        std::string layer_filename = *itor;
        if(layer_filename.empty() || layer_filename == filename)
        {
            // no localized version of this file, that's ok, keep looking
            continue;
        }

        if (!LLXMLNode::parseFile(layer_filename, updateRoot, NULL))
        {
            LL_WARNS() << "Problem reading localized UI description file: " << layer_filename << LL_ENDL;
            return false;
        }

        std::string nodeName;
        std::string updateName;

        updateRoot->getAttributeString("name", updateName);
        root->getAttributeString("name", nodeName);

        if (updateName == nodeName)
        {
            LLXMLNode::updateNode(root, updateRoot);
        }
    }

    return true;
}

// static
void LLXMLNode::writeHeaderToFile(LLFILE *out_file)
{
    fprintf(out_file, "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n");
}

void LLXMLNode::writeToFile(LLFILE *out_file, const std::string& indent, bool use_type_decorations)
{
    if (isFullyDefault())
    {
        // Don't write out nodes that are an exact match to defaults
        return;
    }

    std::ostringstream ostream;
    writeToOstream(ostream, indent, use_type_decorations);
    std::string outstring = ostream.str();
    size_t written = fwrite(outstring.c_str(), 1, outstring.length(), out_file);
    if (written != outstring.length())
    {
        LL_WARNS() << "Short write" << LL_ENDL;
    }
}

void LLXMLNode::writeToOstream(std::ostream& output_stream, const std::string& indent, bool use_type_decorations)
{
    if (isFullyDefault())
    {
        // Don't write out nodes that are an exact match to defaults
        return;
    }

    BOOL has_default_type = mDefault.isNull()?FALSE:(mType == mDefault->mType);
    BOOL has_default_encoding = mDefault.isNull()?FALSE:(mEncoding == mDefault->mEncoding);
    BOOL has_default_precision = mDefault.isNull()?FALSE:(mPrecision == mDefault->mPrecision);
    BOOL has_default_length = mDefault.isNull()?FALSE:(mLength == mDefault->mLength);

    // stream the name
    output_stream << indent << "<" << mName->mString << "\n";

    if (use_type_decorations)
    {
        // ID
        if (mID != "")
        {
            output_stream << indent << " id=\"" << mID << "\"\n";
        }

        // Type
        if (!has_default_type)
        {
            switch (mType)
            {
            case TYPE_BOOLEAN:
                output_stream << indent << " type=\"boolean\"\n";
                break;
            case TYPE_INTEGER:
                output_stream << indent << " type=\"integer\"\n";
                break;
            case TYPE_FLOAT:
                output_stream << indent << " type=\"float\"\n";
                break;
            case TYPE_STRING:
                output_stream << indent << " type=\"string\"\n";
                break;
            case TYPE_UUID:
                output_stream << indent << " type=\"uuid\"\n";
                break;
            case TYPE_NODEREF:
                output_stream << indent << " type=\"noderef\"\n";
                break;
            default:
                // default on switch(enum) eliminates a warning on linux
                break;
            };
        }

        // Encoding
        if (!has_default_encoding)
        {
            switch (mEncoding)
            {
            case ENCODING_DECIMAL:
                output_stream << indent << " encoding=\"decimal\"\n";
                break;
            case ENCODING_HEX:
                output_stream << indent << " encoding=\"hex\"\n";
                break;
            /*case ENCODING_BASE32:
                output_stream << indent << " encoding=\"base32\"\n";
                break;*/
            default:
                // default on switch(enum) eliminates a warning on linux
                break;
            };
        }

        // Precision
        if (!has_default_precision && (mType == TYPE_INTEGER || mType == TYPE_FLOAT))
        {
            output_stream << indent << " precision=\"" << mPrecision << "\"\n";
        }

        // Version
        if (mVersionMajor > 0 || mVersionMinor > 0)
        {
            output_stream << indent << " version=\"" << mVersionMajor << "." << mVersionMinor << "\"\n";
        }

        // Array length
        if (!has_default_length && mLength > 0)
        {
            output_stream << indent << " length=\"" << mLength << "\"\n";
        }
    }

    {
        // Write out attributes
        LLXMLAttribList::const_iterator attr_itr;
        LLXMLAttribList::const_iterator attr_end = mAttributes.end();
        for (attr_itr = mAttributes.begin(); attr_itr != attr_end; ++attr_itr)
        {
            LLXMLNodePtr child = (*attr_itr).second;
            if (child->mDefault.isNull() || child->mDefault->mValue != child->mValue)
            {
                std::string attr = child->mName->mString;
                if (use_type_decorations
                    && (attr == "id" ||
                        attr == "type" ||
                        attr == "encoding" ||
                        attr == "precision" ||
                        attr == "version" ||
                        attr == "length"))
                {
                    continue; // skip built-in attributes
                }

                std::string attr_str = llformat(" %s=\"%s\"",
                                             attr.c_str(),
                                             escapeXML(child->mValue).c_str());
                output_stream << indent << attr_str << "\n";
            }
        }
    }

    // erase last \n before attaching final > or />
    output_stream.seekp(-1, std::ios::cur);

    if (mChildren.isNull() && mValue == "")
    {
        output_stream << " />\n";
        return;
    }
    else
    {
        output_stream << ">\n";
        if (mChildren.notNull())
        {
            // stream non-attributes
            std::string next_indent = indent + "    ";
            for (LLXMLNode* child = getFirstChild(); child; child = child->getNextSibling())
            {
                child->writeToOstream(output_stream, next_indent, use_type_decorations);
            }
        }
        if (!mValue.empty())
        {
            std::string contents = getTextContents();
            output_stream << indent << "    " << escapeXML(contents) << "\n";
        }
        output_stream << indent << "</" << mName->mString << ">\n";
    }
}

void LLXMLNode::findName(const std::string& name, LLXMLNodeList &results)
{
    LLStringTableEntry* name_entry = gStringTable.checkStringEntry(name);
    if (name_entry == mName)
    {
        results.insert(std::make_pair(this->mName->mString, this));
        return;
    }
    if (mChildren.notNull())
    {
        LLXMLChildList::const_iterator children_itr;
        LLXMLChildList::const_iterator children_end = mChildren->map.end();
        for (children_itr = mChildren->map.begin(); children_itr != children_end; ++children_itr)
        {
            LLXMLNodePtr child = (*children_itr).second;
            child->findName(name_entry, results);
        }
    }
}

void LLXMLNode::findName(LLStringTableEntry* name, LLXMLNodeList &results)
{
    if (name == mName)
    {
        results.insert(std::make_pair(this->mName->mString, this));
        return;
    }
    if (mChildren.notNull())
    {
        LLXMLChildList::const_iterator children_itr;
        LLXMLChildList::const_iterator children_end = mChildren->map.end();
        for (children_itr = mChildren->map.begin(); children_itr != children_end; ++children_itr)
        {
            LLXMLNodePtr child = (*children_itr).second;
            child->findName(name, results);
        }
    }
}

void LLXMLNode::findID(const std::string& id, LLXMLNodeList &results)
{
    if (id == mID)
    {
        results.insert(std::make_pair(this->mName->mString, this));
        return;
    }
    if (mChildren.notNull())
    {
        LLXMLChildList::const_iterator children_itr;
        LLXMLChildList::const_iterator children_end = mChildren->map.end();
        for (children_itr = mChildren->map.begin(); children_itr != children_end; ++children_itr)
        {
            LLXMLNodePtr child = (*children_itr).second;
            child->findID(id, results);
        }
    }
}

void LLXMLNode::scrubToTree(LLXMLNode *tree)
{
    if (!tree || tree->mChildren.isNull())
    {
        return;
    }
    if (mChildren.notNull())
    {
        std::vector<LLXMLNodePtr> to_delete_list;
        LLXMLChildList::iterator itor = mChildren->map.begin();
        while (itor != mChildren->map.end())
        {
            LLXMLNodePtr child = itor->second;
            LLXMLNodePtr child_tree = NULL;
            // Look for this child in the default's children
            bool found = false;
            LLXMLChildList::iterator itor2 = tree->mChildren->map.begin();
            while (itor2 != tree->mChildren->map.end())
            {
                if (child->mName == itor2->second->mName)
                {
                    child_tree = itor2->second;
                    found = true;
                }
                ++itor2;
            }
            if (!found)
            {
                to_delete_list.push_back(child);
            }
            else
            {
                child->scrubToTree(child_tree);
            }
            ++itor;
        }
        std::vector<LLXMLNodePtr>::iterator itor3;
        for (itor3=to_delete_list.begin(); itor3!=to_delete_list.end(); ++itor3)
        {
            LLXMLNodePtr ptr;
            (*itor3)->setParent(ptr);
        }
    }
}

bool LLXMLNode::getChild(const char* name, LLXMLNodePtr& node, BOOL use_default_if_missing)
{
    return getChild(gStringTable.checkStringEntry(name), node, use_default_if_missing);
}

bool LLXMLNode::getChild(const LLStringTableEntry* name, LLXMLNodePtr& node, BOOL use_default_if_missing)
{
    if (mChildren.notNull())
    {
        LLXMLChildList::const_iterator child_itr = mChildren->map.find(name);
        if (child_itr != mChildren->map.end())
        {
            node = (*child_itr).second;
            return true;
        }
    }
    if (use_default_if_missing && !mDefault.isNull())
    {
        return mDefault->getChild(name, node, FALSE);
    }
    node = NULL;
    return false;
}

void LLXMLNode::getChildren(const char* name, LLXMLNodeList &children, BOOL use_default_if_missing) const
{
    getChildren(gStringTable.checkStringEntry(name), children, use_default_if_missing);
}

void LLXMLNode::getChildren(const LLStringTableEntry* name, LLXMLNodeList &children, BOOL use_default_if_missing) const
{
    if (mChildren.notNull())
    {
        LLXMLChildList::const_iterator child_itr = mChildren->map.find(name);
        if (child_itr != mChildren->map.end())
        {
            LLXMLChildList::const_iterator children_end = mChildren->map.end();
            while (child_itr != children_end)
            {
                LLXMLNodePtr child = (*child_itr).second;
                if (name != child->mName)
                {
                    break;
                }
                children.insert(std::make_pair(child->mName->mString, child));
                child_itr++;
            }
        }
    }
    if (children.size() == 0 && use_default_if_missing && !mDefault.isNull())
    {
        mDefault->getChildren(name, children, FALSE);
    }
}

// recursively walks the tree and returns all children at all nesting levels matching the name
void LLXMLNode::getDescendants(const LLStringTableEntry* name, LLXMLNodeList &children) const
{
    if (mChildren.notNull())
    {
        for (LLXMLChildList::const_iterator child_itr = mChildren->map.begin();
             child_itr != mChildren->map.end(); ++child_itr)
        {
            LLXMLNodePtr child = (*child_itr).second;
            if (name == child->mName)
            {
                children.insert(std::make_pair(child->mName->mString, child));
            }
            // and check each child as well
            child->getDescendants(name, children);
        }
    }
}

bool LLXMLNode::getAttribute(const char* name, LLXMLNodePtr& node, BOOL use_default_if_missing)
{
    return getAttribute(gStringTable.checkStringEntry(name), node, use_default_if_missing);
}

bool LLXMLNode::getAttribute(const LLStringTableEntry* name, LLXMLNodePtr& node, BOOL use_default_if_missing)
{
    LLXMLAttribList::const_iterator child_itr = mAttributes.find(name);
    if (child_itr != mAttributes.end())
    {
        node = (*child_itr).second;
        return true;
    }
    if (use_default_if_missing && !mDefault.isNull())
    {
        return mDefault->getAttribute(name, node, FALSE);
    }

    return false;
}

bool LLXMLNode::setAttributeString(const char* attr, const std::string& value)
{
    LLStringTableEntry* name = gStringTable.checkStringEntry(attr);
    LLXMLAttribList::const_iterator child_itr = mAttributes.find(name);
    if (child_itr != mAttributes.end())
    {
        LLXMLNodePtr node = (*child_itr).second;
        node->setValue(value);
        return true;
    }
    return false;
}

BOOL LLXMLNode::hasAttribute(const char* name )
{
    LLXMLNodePtr node;
    return getAttribute(name, node);
}

// the structure of these getAttribute_ functions is ugly, but it's because the
// underlying system is based on BOOL and LLString; if we change
// so that they're based on more generic mechanisms, these will be
// simplified.
bool LLXMLNode::getAttribute_bool(const char* name, bool& value )
{
    LLXMLNodePtr node;
    if (!getAttribute(name, node))
    {
        return false;
    }
    BOOL temp;
    bool retval = node->getBoolValue(1, &temp);
    value = temp;
    return retval;
}

BOOL LLXMLNode::getAttributeBOOL(const char* name, BOOL& value )
{
    LLXMLNodePtr node;
    return (getAttribute(name, node) && node->getBoolValue(1, &value));
}

BOOL LLXMLNode::getAttributeU8(const char* name, U8& value )
{
    LLXMLNodePtr node;
    return (getAttribute(name, node) && node->getByteValue(1, &value));
}

BOOL LLXMLNode::getAttributeS8(const char* name, S8& value )
{
    LLXMLNodePtr node;
    S32 val;
    if (!(getAttribute(name, node) && node->getIntValue(1, &val)))
    {
        return false;
    }
    value = val;
    return true;
}

BOOL LLXMLNode::getAttributeU16(const char* name, U16& value )
{
    LLXMLNodePtr node;
    U32 val;
    if (!(getAttribute(name, node) && node->getUnsignedValue(1, &val)))
    {
        return false;
    }
    value = val;
    return true;
}

BOOL LLXMLNode::getAttributeS16(const char* name, S16& value )
{
    LLXMLNodePtr node;
    S32 val;
    if (!(getAttribute(name, node) && node->getIntValue(1, &val)))
    {
        return false;
    }
    value = val;
    return true;
}

BOOL LLXMLNode::getAttributeU32(const char* name, U32& value )
{
    LLXMLNodePtr node;
    return (getAttribute(name, node) && node->getUnsignedValue(1, &value));
}

BOOL LLXMLNode::getAttributeS32(const char* name, S32& value )
{
    LLXMLNodePtr node;
    return (getAttribute(name, node) && node->getIntValue(1, &value));
}

BOOL LLXMLNode::getAttributeF32(const char* name, F32& value )
{
    LLXMLNodePtr node;
    return (getAttribute(name, node) && node->getFloatValue(1, &value));
}

BOOL LLXMLNode::getAttributeF64(const char* name, F64& value )
{
    LLXMLNodePtr node;
    return (getAttribute(name, node) && node->getDoubleValue(1, &value));
}

BOOL LLXMLNode::getAttributeColor(const char* name, LLColor4& value )
{
    LLXMLNodePtr node;
    return (getAttribute(name, node) && node->getFloatValue(4, value.mV));
}

BOOL LLXMLNode::getAttributeColor4(const char* name, LLColor4& value )
{
    LLXMLNodePtr node;
    return (getAttribute(name, node) && node->getFloatValue(4, value.mV));
}

BOOL LLXMLNode::getAttributeColor4U(const char* name, LLColor4U& value )
{
    LLXMLNodePtr node;
    return (getAttribute(name, node) && node->getByteValue(4, value.mV));
}

BOOL LLXMLNode::getAttributeVector3(const char* name, LLVector3& value )
{
    LLXMLNodePtr node;
    return (getAttribute(name, node) && node->getFloatValue(3, value.mV));
}

BOOL LLXMLNode::getAttributeVector3d(const char* name, LLVector3d& value )
{
    LLXMLNodePtr node;
    return (getAttribute(name, node) && node->getDoubleValue(3, value.mdV));
}

BOOL LLXMLNode::getAttributeQuat(const char* name, LLQuaternion& value )
{
    LLXMLNodePtr node;
    return (getAttribute(name, node) && node->getFloatValue(4, value.mQ));
}

BOOL LLXMLNode::getAttributeUUID(const char* name, LLUUID& value )
{
    LLXMLNodePtr node;
    return (getAttribute(name, node) && node->getUUIDValue(1, &value));
}

BOOL LLXMLNode::getAttributeString(const char* name, std::string& value )
{
    LLXMLNodePtr node;
    if (!getAttribute(name, node))
    {
        return false;
    }
    value = node->getValue();
    return true;
}

LLXMLNodePtr LLXMLNode::getRoot()
{
    if (mParent == NULL)
    {
        return this;
    }
    return mParent->getRoot();
}

/*static */
const char *LLXMLNode::skipWhitespace(const char *str)
{
    // skip whitespace characters
    while (str[0] == ' ' || str[0] == '\t' || str[0] == '\n') ++str;
    return str;
}

/*static */
const char *LLXMLNode::skipNonWhitespace(const char *str)
{
    // skip non-whitespace characters
    while (str[0] != ' ' && str[0] != '\t' && str[0] != '\n' && str[0] != 0) ++str;
    return str;
}

/*static */
const char *LLXMLNode::parseInteger(const char *str, U64 *dest, BOOL *is_negative, U32 precision, Encoding encoding)
{
    *dest = 0;
    *is_negative = FALSE;

    str = skipWhitespace(str);

    if (str[0] == 0) return NULL;

    if (encoding == ENCODING_DECIMAL || encoding == ENCODING_DEFAULT)
    {
        if (str[0] == '+')
        {
            ++str;
        }
        if (str[0] == '-')
        {
            *is_negative = TRUE;
            ++str;
        }

        str = skipWhitespace(str);

        U64 ret = 0;
        while (str[0] >= '0' && str[0] <= '9')
        {
            ret *= 10;
            ret += str[0] - '0';
            ++str;
        }

        if (str[0] == '.')
        {
            // If there is a fractional part, skip it
            str = skipNonWhitespace(str);
        }

        *dest = ret;
        return str;
    }
    if (encoding == ENCODING_HEX)
    {
        U64 ret = 0;
        str = skipWhitespace(str);
        for (U32 pos=0; pos<(precision/4); ++pos)
        {
            ret <<= 4;
            str = skipWhitespace(str);
            if (str[0] >= '0' && str[0] <= '9')
            {
                ret += str[0] - '0';
            }
            else if (str[0] >= 'a' && str[0] <= 'f')
            {
                ret += str[0] - 'a' + 10;
            }
            else if (str[0] >= 'A' && str[0] <= 'F')
            {
                ret += str[0] - 'A' + 10;
            }
            else
            {
                return NULL;
            }
            ++str;
        }

        *dest = ret;
        return str;
    }
    return NULL;
}

// 25 elements - decimal expansions of 1/(2^n), multiplied by 10 each iteration
const U64 float_coeff_table[] =
    { 5, 25, 125, 625, 3125,
      15625, 78125, 390625, 1953125, 9765625,
      48828125, 244140625, 1220703125, 6103515625LL, 30517578125LL,
      152587890625LL, 762939453125LL, 3814697265625LL, 19073486328125LL, 95367431640625LL,
      476837158203125LL, 2384185791015625LL, 11920928955078125LL, 59604644775390625LL, 298023223876953125LL };

// 36 elements - decimal expansions of 1/(2^n) after the last 28, truncated, no multiply each iteration
const U64 float_coeff_table_2[] =
    {  149011611938476562LL,74505805969238281LL,
       37252902984619140LL, 18626451492309570LL, 9313225746154785LL, 4656612873077392LL,
       2328306436538696LL,  1164153218269348LL,  582076609134674LL,  291038304567337LL,
       145519152283668LL,   72759576141834LL,    36379788070917LL,   18189894035458LL,
       9094947017729LL,     4547473508864LL,     2273736754432LL,    1136868377216LL,
       568434188608LL,      284217094304LL,      142108547152LL,     71054273576LL,
       35527136788LL,       17763568394LL,       8881784197LL,       4440892098LL,
       2220446049LL,        1110223024LL,        555111512LL,        277555756LL,
       138777878,         69388939,          34694469,         17347234,
       8673617,           4336808,           2168404,          1084202,
       542101,            271050,            135525,           67762,
    };

/*static */
const char *LLXMLNode::parseFloat(const char *str, F64 *dest, U32 precision, Encoding encoding)
{
    str = skipWhitespace(str);

    if (str[0] == 0) return NULL;

    if (encoding == ENCODING_DECIMAL || encoding == ENCODING_DEFAULT)
    {
        str = skipWhitespace(str);

        if (memcmp(str, "inf", 3) == 0)
        {
            *(U64 *)dest = 0x7FF0000000000000ll;
            return str + 3;
        }
        if (memcmp(str, "-inf", 4) == 0)
        {
            *(U64 *)dest = 0xFFF0000000000000ll;
            return str + 4;
        }
        if (memcmp(str, "1.#INF", 6) == 0)
        {
            *(U64 *)dest = 0x7FF0000000000000ll;
            return str + 6;
        }
        if (memcmp(str, "-1.#INF", 7) == 0)
        {
            *(U64 *)dest = 0xFFF0000000000000ll;
            return str + 7;
        }

        F64 negative = 1.0f;
        if (str[0] == '+')
        {
            ++str;
        }
        if (str[0] == '-')
        {
            negative = -1.0f;
            ++str;
        }

        const char* base_str = str;
        str = skipWhitespace(str);

        // Parse the integer part of the expression
        U64 int_part = 0;
        while (str[0] >= '0' && str[0] <= '9')
        {
            int_part *= 10;
            int_part += U64(str[0] - '0');
            ++str;
        }

        U64 f_part = 0;//, f_decimal = 1;
        if (str[0] == '.')
        {
            ++str;
            U64 remainder = 0;
            U32 pos = 0;
            // Parse the decimal part of the expression
            while (str[0] >= '0' && str[0] <= '9' && pos < 25)
            {
                remainder = (remainder*10) + U64(str[0] - '0');
                f_part <<= 1;
                //f_decimal <<= 1;
                // Check the n'th bit
                if (remainder >= float_coeff_table[pos])
                {
                    remainder -= float_coeff_table[pos];
                    f_part |= 1;
                }
                ++pos;
                ++str;
            }
            if (pos == 25)
            {
                // Drop any excessive digits
                while (str[0] >= '0' && str[0] <= '9')
                {
                    ++str;
                }
            }
            else
            {
                while (pos < 25)
                {
                    remainder *= 10;
                    f_part <<= 1;
                    //f_decimal <<= 1;
                    // Check the n'th bit
                    if (remainder >= float_coeff_table[pos])
                    {
                        remainder -= float_coeff_table[pos];
                        f_part |= 1;
                    }
                    ++pos;
                }
            }
            pos = 0;
            while (pos < 36)
            {
                f_part <<= 1;
                //f_decimal <<= 1;
                if (remainder >= float_coeff_table_2[pos])
                {
                    remainder -= float_coeff_table_2[pos];
                    f_part |= 1;
                }
                ++pos;
            }
        }

        F64 ret = F64(int_part) + (F64(f_part)/F64(1LL<<61));

        F64 exponent = 1.f;
        if (str[0] == 'e')
        {
            // Scientific notation!
            ++str;
            U64 exp;
            BOOL is_negative;
            str = parseInteger(str, &exp, &is_negative, 64, ENCODING_DECIMAL);
            if (str == NULL)
            {
                exp = 1;
            }
            F64 exp_d = F64(exp) * (is_negative?-1:1);
            exponent = pow(10.0, exp_d);
        }

        if (str == base_str)
        {
            // no digits parsed
            return NULL;
        }
        else
        {
            *dest = ret*negative*exponent;
            return str;
        }
    }
    if (encoding == ENCODING_HEX)
    {
        U64 bytes_dest;
        BOOL is_negative;
        str = parseInteger(str, (U64 *)&bytes_dest, &is_negative, precision, ENCODING_HEX);
        // Upcast to F64
        switch (precision)
        {
        case 32:
            {
                U32 short_dest = (U32)bytes_dest;
                F32 ret_val = *(F32 *)&short_dest;
                *dest = ret_val;
            }
            break;
        case 64:
            *dest = *(F64 *)&bytes_dest;
            break;
        default:
            return NULL;
        }
        return str;
    }
    return NULL;
}

U32 LLXMLNode::getBoolValue(U32 expected_length, BOOL *array)
{
    llassert(array);

    // Check type - accept booleans or strings
    if (mType != TYPE_BOOLEAN && mType != TYPE_STRING && mType != TYPE_UNKNOWN)
    {
        return 0;
    }

    std::string *str_array = new std::string[expected_length];

    U32 length = getStringValue(expected_length, str_array);

    U32 ret_length = 0;
    for (U32 i=0; i<length; ++i)
    {
        LLStringUtil::toLower(str_array[i]);
        if (str_array[i] == "false")
        {
            array[ret_length++] = FALSE;
        }
        else if (str_array[i] == "true")
        {
            array[ret_length++] = TRUE;
        }
    }

    delete[] str_array;

#if LL_DEBUG
    if (ret_length != expected_length)
    {
        LL_DEBUGS() << "LLXMLNode::getBoolValue() failed for node named '"
            << mName->mString << "' -- expected " << expected_length << " but "
            << "only found " << ret_length << LL_ENDL;
    }
#endif
    return ret_length;
}

U32 LLXMLNode::getByteValue(U32 expected_length, U8 *array, Encoding encoding)
{
    llassert(array);

    // Check type - accept bytes or integers (below 256 only)
    if (mType != TYPE_INTEGER
        && mType != TYPE_UNKNOWN)
    {
        return 0;
    }

    if (mLength > 0 && mLength != expected_length)
    {
        LL_WARNS() << "XMLNode::getByteValue asked for " << expected_length
            << " elements, while node has " << mLength << LL_ENDL;
        return 0;
    }

    if (encoding == ENCODING_DEFAULT)
    {
        encoding = mEncoding;
    }

    const char *value_string = mValue.c_str();

    U32 i;
    for (i=0; i<expected_length; ++i)
    {
        U64 value;
        BOOL is_negative;
        value_string = parseInteger(value_string, &value, &is_negative, 8, encoding);
        if (value_string == NULL)
        {
            break;
        }
        if (value > 255 || is_negative)
        {
            LL_WARNS() << "getByteValue: Value outside of valid range." << LL_ENDL;
            break;
        }
        array[i] = U8(value);
    }
#if LL_DEBUG
    if (i != expected_length)
    {
        LL_DEBUGS() << "LLXMLNode::getByteValue() failed for node named '"
            << mName->mString << "' -- expected " << expected_length << " but "
            << "only found " << i << LL_ENDL;
    }
#endif
    return i;
}

U32 LLXMLNode::getIntValue(U32 expected_length, S32 *array, Encoding encoding)
{
    llassert(array);

    // Check type - accept bytes or integers
    if (mType != TYPE_INTEGER && mType != TYPE_UNKNOWN)
    {
        return 0;
    }

    if (mLength > 0 && mLength != expected_length)
    {
        LL_WARNS() << "XMLNode::getIntValue asked for " << expected_length
            << " elements, while node has " << mLength << LL_ENDL;
        return 0;
    }

    if (encoding == ENCODING_DEFAULT)
    {
        encoding = mEncoding;
    }

    const char *value_string = mValue.c_str();

    U32 i = 0;
    for (i=0; i<expected_length; ++i)
    {
        U64 value;
        BOOL is_negative;
        value_string = parseInteger(value_string, &value, &is_negative, 32, encoding);
        if (value_string == NULL)
        {
            break;
        }
        if (value > 0x7fffffff)
        {
            LL_WARNS() << "getIntValue: Value outside of valid range." << LL_ENDL;
            break;
        }
        array[i] = S32(value) * (is_negative?-1:1);
    }

#if LL_DEBUG
    if (i != expected_length)
    {
        LL_DEBUGS() << "LLXMLNode::getIntValue() failed for node named '"
            << mName->mString << "' -- expected " << expected_length << " but "
            << "only found " << i << LL_ENDL;
    }
#endif
    return i;
}

U32 LLXMLNode::getUnsignedValue(U32 expected_length, U32 *array, Encoding encoding)
{
    llassert(array);

    // Check type - accept bytes or integers
    if (mType != TYPE_INTEGER && mType != TYPE_UNKNOWN)
    {
        return 0;
    }

    if (mLength > 0 && mLength != expected_length)
    {
        LL_WARNS() << "XMLNode::getUnsignedValue asked for " << expected_length
            << " elements, while node has " << mLength << LL_ENDL;
        return 0;
    }

    if (encoding == ENCODING_DEFAULT)
    {
        encoding = mEncoding;
    }

    const char *value_string = mValue.c_str();

    U32 i = 0;
    // Int type
    for (i=0; i<expected_length; ++i)
    {
        U64 value;
        BOOL is_negative;
        value_string = parseInteger(value_string, &value, &is_negative, 32, encoding);
        if (value_string == NULL)
        {
            break;
        }
        if (is_negative || value > 0xffffffff)
        {
            LL_WARNS() << "getUnsignedValue: Value outside of valid range." << LL_ENDL;
            break;
        }
        array[i] = U32(value);
    }

#if LL_DEBUG
    if (i != expected_length)
    {
        LL_DEBUGS() << "LLXMLNode::getUnsignedValue() failed for node named '"
            << mName->mString << "' -- expected " << expected_length << " but "
            << "only found " << i << LL_ENDL;
    }
#endif

    return i;
}

U32 LLXMLNode::getLongValue(U32 expected_length, U64 *array, Encoding encoding)
{
    llassert(array);

    // Check type - accept bytes or integers
    if (mType != TYPE_INTEGER && mType != TYPE_UNKNOWN)
    {
        return 0;
    }

    if (mLength > 0 && mLength != expected_length)
    {
        LL_WARNS() << "XMLNode::getLongValue asked for " << expected_length << " elements, while node has " << mLength << LL_ENDL;
        return 0;
    }

    if (encoding == ENCODING_DEFAULT)
    {
        encoding = mEncoding;
    }

    const char *value_string = mValue.c_str();

    U32 i = 0;
    // Int type
    for (i=0; i<expected_length; ++i)
    {
        U64 value;
        BOOL is_negative;
        value_string = parseInteger(value_string, &value, &is_negative, 64, encoding);
        if (value_string == NULL)
        {
            break;
        }
        if (is_negative)
        {
            LL_WARNS() << "getLongValue: Value outside of valid range." << LL_ENDL;
            break;
        }
        array[i] = value;
    }

#if LL_DEBUG
    if (i != expected_length)
    {
        LL_DEBUGS() << "LLXMLNode::getLongValue() failed for node named '"
            << mName->mString << "' -- expected " << expected_length << " but "
            << "only found " << i << LL_ENDL;
    }
#endif

    return i;
}

U32 LLXMLNode::getFloatValue(U32 expected_length, F32 *array, Encoding encoding)
{
    llassert(array);

    // Check type - accept only floats or doubles
    if (mType != TYPE_FLOAT && mType != TYPE_UNKNOWN)
    {
        return 0;
    }

    if (mLength > 0 && mLength != expected_length)
    {
        LL_WARNS() << "XMLNode::getFloatValue asked for " << expected_length << " elements, while node has " << mLength << LL_ENDL;
        return 0;
    }

    if (encoding == ENCODING_DEFAULT)
    {
        encoding = mEncoding;
    }

    const char *value_string = mValue.c_str();

    U32 i;
    for (i=0; i<expected_length; ++i)
    {
        F64 value;
        value_string = parseFloat(value_string, &value, 32, encoding);
        if (value_string == NULL)
        {
            break;
        }
        array[i] = F32(value);
    }
#if LL_DEBUG
    if (i != expected_length)
    {
        LL_DEBUGS() << "LLXMLNode::getFloatValue() failed for node named '"
            << mName->mString << "' -- expected " << expected_length << " but "
            << "only found " << i << LL_ENDL;
    }
#endif
    return i;
}

U32 LLXMLNode::getDoubleValue(U32 expected_length, F64 *array, Encoding encoding)
{
    llassert(array);

    // Check type - accept only floats or doubles
    if (mType != TYPE_FLOAT && mType != TYPE_UNKNOWN)
    {
        return 0;
    }

    if (mLength > 0 && mLength != expected_length)
    {
        LL_WARNS() << "XMLNode::getDoubleValue asked for " << expected_length << " elements, while node has " << mLength << LL_ENDL;
        return 0;
    }

    if (encoding == ENCODING_DEFAULT)
    {
        encoding = mEncoding;
    }

    const char *value_string = mValue.c_str();

    U32 i;
    for (i=0; i<expected_length; ++i)
    {
        F64 value;
        value_string = parseFloat(value_string, &value, 64, encoding);
        if (value_string == NULL)
        {
            break;
        }
        array[i] = value;
    }
#if LL_DEBUG
    if (i != expected_length)
    {
        LL_DEBUGS() << "LLXMLNode::getDoubleValue() failed for node named '"
            << mName->mString << "' -- expected " << expected_length << " but "
            << "only found " << i << LL_ENDL;
    }
#endif
    return i;
}

U32 LLXMLNode::getStringValue(U32 expected_length, std::string *array)
{
    llassert(array);

    // Can always return any value as a string

    if (mLength > 0 && mLength != expected_length)
    {
        LL_WARNS() << "XMLNode::getStringValue asked for " << expected_length << " elements, while node has " << mLength << LL_ENDL;
        return 0;
    }

    U32 num_returned_strings = 0;

    // Array of strings is whitespace-separated
    const std::string sep(" \n\t");

    std::string::size_type n = 0;
    std::string::size_type m = 0;
    while(1)
    {
        if (num_returned_strings >= expected_length)
        {
            break;
        }
        n = mValue.find_first_not_of(sep, m);
        m = mValue.find_first_of(sep, n);
        if (m == std::string::npos)
        {
            break;
        }
        array[num_returned_strings++] = mValue.substr(n,m-n);
    }
    if (n != std::string::npos && num_returned_strings < expected_length)
    {
        array[num_returned_strings++] = mValue.substr(n);
    }
#if LL_DEBUG
    if (num_returned_strings != expected_length)
    {
        LL_DEBUGS() << "LLXMLNode::getStringValue() failed for node named '"
            << mName->mString << "' -- expected " << expected_length << " but "
            << "only found " << num_returned_strings << LL_ENDL;
    }
#endif

    return num_returned_strings;
}

U32 LLXMLNode::getUUIDValue(U32 expected_length, LLUUID *array)
{
    llassert(array);

    // Check type
    if (mType != TYPE_UUID && mType != TYPE_UNKNOWN)
    {
        return 0;
    }

    const char *value_string = mValue.c_str();

    U32 i;
    for (i=0; i<expected_length; ++i)
    {
        LLUUID uuid_value;
        value_string = skipWhitespace(value_string);

        if (strlen(value_string) < (UUID_STR_LENGTH-1))     /* Flawfinder: ignore */
        {
            break;
        }
        char uuid_string[UUID_STR_LENGTH];      /* Flawfinder: ignore */
        memcpy(uuid_string, value_string, (UUID_STR_LENGTH-1));     /* Flawfinder: ignore */
        uuid_string[(UUID_STR_LENGTH-1)] = 0;

        if (!LLUUID::parseUUID(std::string(uuid_string), &uuid_value))
        {
            break;
        }
        value_string = &value_string[(UUID_STR_LENGTH-1)];
        array[i] = uuid_value;
    }
#if LL_DEBUG
    if (i != expected_length)
    {
        LL_DEBUGS() << "LLXMLNode::getUUIDValue() failed for node named '"
            << mName->mString << "' -- expected " << expected_length << " but "
            << "only found " << i << LL_ENDL;
    }
#endif
    return i;
}

U32 LLXMLNode::getNodeRefValue(U32 expected_length, LLXMLNode **array)
{
    llassert(array);

    // Check type
    if (mType != TYPE_NODEREF && mType != TYPE_UNKNOWN)
    {
        return 0;
    }

    std::string *string_array = new std::string[expected_length];

    U32 num_strings = getStringValue(expected_length, string_array);

    U32 num_returned_refs = 0;

    LLXMLNodePtr root = getRoot();
    for (U32 strnum=0; strnum<num_strings; ++strnum)
    {
        LLXMLNodeList node_list;
        root->findID(string_array[strnum], node_list);
        if (node_list.empty())
        {
            LL_WARNS() << "XML: Could not find node ID: " << string_array[strnum] << LL_ENDL;
        }
        else if (node_list.size() > 1)
        {
            LL_WARNS() << "XML: Node ID not unique: " << string_array[strnum] << LL_ENDL;
        }
        else
        {
            LLXMLNodeList::const_iterator list_itr = node_list.begin();
            if (list_itr != node_list.end())
            {
                LLXMLNode* child = (*list_itr).second;

                array[num_returned_refs++] = child;
            }
        }
    }

    delete[] string_array;

    return num_returned_refs;
}

void LLXMLNode::setBoolValue(U32 length, const BOOL *array)
{
    if (length == 0) return;

    std::string new_value;
    for (U32 pos=0; pos<length; ++pos)
    {
        if (pos > 0)
        {
            new_value = llformat("%s %s", new_value.c_str(), array[pos]?"true":"false");
        }
        else
        {
            new_value = array[pos]?"true":"false";
        }
    }

    mValue = new_value;
    mEncoding = ENCODING_DEFAULT;
    mLength = length;
    mType = TYPE_BOOLEAN;
}

void LLXMLNode::setByteValue(U32 length, const U8* const array, Encoding encoding)
{
    if (length == 0) return;

    std::string new_value;
    if (encoding == ENCODING_DEFAULT || encoding == ENCODING_DECIMAL)
    {
        for (U32 pos=0; pos<length; ++pos)
        {
            if (pos > 0)
            {
                new_value.append(llformat(" %u", array[pos]));
            }
            else
            {
                new_value = llformat("%u", array[pos]);
            }
        }
    }
    if (encoding == ENCODING_HEX)
    {
        for (U32 pos=0; pos<length; ++pos)
        {
            if (pos > 0 && pos % 16 == 0)
            {
                new_value.append(llformat(" %02X", array[pos]));
            }
            else
            {
                new_value.append(llformat("%02X", array[pos]));
            }
        }
    }
    // TODO -- Handle Base32

    mValue = new_value;
    mEncoding = encoding;
    mLength = length;
    mType = TYPE_INTEGER;
    mPrecision = 8;
}


void LLXMLNode::setIntValue(U32 length, const S32 *array, Encoding encoding)
{
    if (length == 0) return;

    std::string new_value;
    if (encoding == ENCODING_DEFAULT || encoding == ENCODING_DECIMAL)
    {
        for (U32 pos=0; pos<length; ++pos)
        {
            if (pos > 0)
            {
                new_value.append(llformat(" %d", array[pos]));
            }
            else
            {
                new_value = llformat("%d", array[pos]);
            }
        }
        mValue = new_value;
    }
    else if (encoding == ENCODING_HEX)
    {
        for (U32 pos=0; pos<length; ++pos)
        {
            if (pos > 0 && pos % 16 == 0)
            {
                new_value.append(llformat(" %08X", ((U32 *)array)[pos]));
            }
            else
            {
                new_value.append(llformat("%08X", ((U32 *)array)[pos]));
            }
        }
        mValue = new_value;
    }
    else
    {
        mValue = new_value;
    }
    // TODO -- Handle Base32

    mEncoding = encoding;
    mLength = length;
    mType = TYPE_INTEGER;
    mPrecision = 32;
}

void LLXMLNode::setUnsignedValue(U32 length, const U32* array, Encoding encoding)
{
    if (length == 0) return;

    std::string new_value;
    if (encoding == ENCODING_DEFAULT || encoding == ENCODING_DECIMAL)
    {
        for (U32 pos=0; pos<length; ++pos)
        {
            if (pos > 0)
            {
                new_value.append(llformat(" %u", array[pos]));
            }
            else
            {
                new_value = llformat("%u", array[pos]);
            }
        }
    }
    if (encoding == ENCODING_HEX)
    {
        for (U32 pos=0; pos<length; ++pos)
        {
            if (pos > 0 && pos % 16 == 0)
            {
                new_value.append(llformat(" %08X", array[pos]));
            }
            else
            {
                new_value.append(llformat("%08X", array[pos]));
            }
        }
        mValue = new_value;
    }
    // TODO -- Handle Base32

    mValue = new_value;
    mEncoding = encoding;
    mLength = length;
    mType = TYPE_INTEGER;
    mPrecision = 32;
}

#if LL_WINDOWS
#define PU64 "I64u"
#else
#define PU64 "llu"
#endif

void LLXMLNode::setLongValue(U32 length, const U64* array, Encoding encoding)
{
    if (length == 0) return;

    std::string new_value;
    if (encoding == ENCODING_DEFAULT || encoding == ENCODING_DECIMAL)
    {
        for (U32 pos=0; pos<length; ++pos)
        {
            if (pos > 0)
            {
                new_value.append(llformat(" %" PU64, array[pos]));
            }
            else
            {
                new_value = llformat("%" PU64, array[pos]);
            }
        }
        mValue = new_value;
    }
    if (encoding == ENCODING_HEX)
    {
        for (U32 pos=0; pos<length; ++pos)
        {
            U32 upper_32 = U32(array[pos]>>32);
            U32 lower_32 = U32(array[pos]&0xffffffff);
            if (pos > 0 && pos % 8 == 0)
            {
                new_value.append(llformat(" %08X%08X", upper_32, lower_32));
            }
            else
            {
                new_value.append(llformat("%08X%08X", upper_32, lower_32));
            }
        }
        mValue = new_value;
    }
    else
    {
        mValue = new_value;
    }
    // TODO -- Handle Base32

    mEncoding = encoding;
    mLength = length;
    mType = TYPE_INTEGER;
    mPrecision = 64;
}

void LLXMLNode::setFloatValue(U32 length, const F32 *array, Encoding encoding, U32 precision)
{
    if (length == 0) return;

    std::string new_value;
    if (encoding == ENCODING_DEFAULT || encoding == ENCODING_DECIMAL)
    {
        std::string format_string;
        if (precision > 0)
        {
            if (precision > 25)
            {
                precision = 25;
            }
            format_string = llformat( "%%.%dg", precision);
        }
        else
        {
            format_string = llformat( "%%g");
        }

        for (U32 pos=0; pos<length; ++pos)
        {
            if (pos > 0)
            {
                new_value.append(" ");
                new_value.append(llformat(format_string.c_str(), array[pos]));
            }
            else
            {
                new_value.assign(llformat(format_string.c_str(), array[pos]));
            }
        }
        mValue = new_value;
    }
    else if (encoding == ENCODING_HEX)
    {
        U32 *byte_array = (U32 *)array;
        setUnsignedValue(length, byte_array, ENCODING_HEX);
    }
    else
    {
        mValue = new_value;
    }

    mEncoding = encoding;
    mLength = length;
    mType = TYPE_FLOAT;
    mPrecision = 32;
}

void LLXMLNode::setDoubleValue(U32 length, const F64 *array, Encoding encoding, U32 precision)
{
    if (length == 0) return;

    std::string new_value;
    if (encoding == ENCODING_DEFAULT || encoding == ENCODING_DECIMAL)
    {
        std::string format_string;
        if (precision > 0)
        {
            if (precision > 25)
            {
                precision = 25;
            }
            format_string = llformat( "%%.%dg", precision);
        }
        else
        {
            format_string = llformat( "%%g");
        }
        for (U32 pos=0; pos<length; ++pos)
        {
            if (pos > 0)
            {
                new_value.append(" ");
                new_value.append(llformat(format_string.c_str(), array[pos]));
            }
            else
            {
                new_value.assign(llformat(format_string.c_str(), array[pos]));
            }
        }
        mValue = new_value;
    }
    if (encoding == ENCODING_HEX)
    {
        U64 *byte_array = (U64 *)array;
        setLongValue(length, byte_array, ENCODING_HEX);
    }
    else
    {
        mValue = new_value;
    }
    // TODO -- Handle Base32

    mEncoding = encoding;
    mLength = length;
    mType = TYPE_FLOAT;
    mPrecision = 64;
}

// static
std::string LLXMLNode::escapeXML(const std::string& xml)
{
    std::string out;
    for (std::string::size_type i = 0; i < xml.size(); ++i)
    {
        char c = xml[i];
        switch(c)
        {
        case '"':   out.append("&quot;");   break;
        case '\'':  out.append("&apos;");   break;
        case '&':   out.append("&amp;");    break;
        case '<':   out.append("&lt;");     break;
        case '>':   out.append("&gt;");     break;
        default:    out.push_back(c);       break;
        }
    }
    return out;
}

void LLXMLNode::setStringValue(U32 length, const std::string *strings)
{
    if (length == 0) return;

    std::string new_value;
    for (U32 pos=0; pos<length; ++pos)
    {
        // *NOTE: Do not escape strings here - do it on output
        new_value.append( strings[pos] );
        if (pos < length-1) new_value.append(" ");
    }

    mValue = new_value;
    mEncoding = ENCODING_DEFAULT;
    mLength = length;
    mType = TYPE_STRING;
}

void LLXMLNode::setUUIDValue(U32 length, const LLUUID *array)
{
    if (length == 0) return;

    std::string new_value;
    for (U32 pos=0; pos<length; ++pos)
    {
        new_value.append(array[pos].asString());
        if (pos < length-1) new_value.append(" ");
    }

    mValue = new_value;
    mEncoding = ENCODING_DEFAULT;
    mLength = length;
    mType = TYPE_UUID;
}

void LLXMLNode::setNodeRefValue(U32 length, const LLXMLNode **array)
{
    if (length == 0) return;

    std::string new_value;
    for (U32 pos=0; pos<length; ++pos)
    {
        if (array[pos]->mID != "")
        {
            new_value.append(array[pos]->mID);
        }
        else
        {
            new_value.append("(null)");
        }
        if (pos < length-1) new_value.append(" ");
    }

    mValue = new_value;
    mEncoding = ENCODING_DEFAULT;
    mLength = length;
    mType = TYPE_NODEREF;
}

void LLXMLNode::setValue(const std::string& value)
{
    if (TYPE_CONTAINER == mType)
    {
        mType = TYPE_UNKNOWN;
    }
    mValue = value;
}

void LLXMLNode::setDefault(LLXMLNode *default_node)
{
    mDefault = default_node;
}

void LLXMLNode::findDefault(LLXMLNode *defaults_list)
{
    if (defaults_list)
    {
        LLXMLNodeList children;
        defaults_list->getChildren(mName->mString, children);

        LLXMLNodeList::const_iterator children_itr;
        LLXMLNodeList::const_iterator children_end = children.end();
        for (children_itr = children.begin(); children_itr != children_end; ++children_itr)
        {
            LLXMLNode* child = (*children_itr).second;
            if (child->mVersionMajor == mVersionMajor &&
                child->mVersionMinor == mVersionMinor)
            {
                mDefault = child;
                return;
            }
        }
    }
    mDefault = NULL;
}

BOOL LLXMLNode::deleteChildren(const std::string& name)
{
    U32 removed_count = 0;
    LLXMLNodeList node_list;
    findName(name, node_list);
    if (!node_list.empty())
    {
        // TODO -- use multimap::find()
        // TODO -- need to watch out for invalid iterators
        LLXMLNodeList::iterator children_itr;
        for (children_itr = node_list.begin(); children_itr != node_list.end(); ++children_itr)
        {
            LLXMLNode* child = (*children_itr).second;
            if (deleteChild(child))
            {
                removed_count++;
            }
        }
    }
    return removed_count > 0 ? TRUE : FALSE;
}

BOOL LLXMLNode::deleteChildren(LLStringTableEntry* name)
{
    U32 removed_count = 0;
    LLXMLNodeList node_list;
    findName(name, node_list);
    if (!node_list.empty())
    {
        // TODO -- use multimap::find()
        // TODO -- need to watch out for invalid iterators
        LLXMLNodeList::iterator children_itr;
        for (children_itr = node_list.begin(); children_itr != node_list.end(); ++children_itr)
        {
            LLXMLNode* child = (*children_itr).second;
            if (deleteChild(child))
            {
                removed_count++;
            }
        }
    }
    return removed_count > 0 ? TRUE : FALSE;
}

void LLXMLNode::setAttributes(LLXMLNode::ValueType type, U32 precision, LLXMLNode::Encoding encoding, U32 length)
{
    mType = type;
    mEncoding = encoding;
    mPrecision = precision;
    mLength = length;
}

void LLXMLNode::setName(const std::string& name)
{
    setName(gStringTable.addStringEntry(name));
}

void LLXMLNode::setName(LLStringTableEntry* name)
{
    LLXMLNode* old_parent = mParent;
    if (mParent)
    {
        // we need to remove and re-add to the parent so that
        // the multimap key agrees with this node's name
        mParent->removeChild(this);
    }
    mName = name;
    if (old_parent)
    {
        LLXMLNodePtr this_ptr(this);
        old_parent->addChild(this_ptr);
    }
}

// Unused
// void LLXMLNode::appendValue(const std::string& value)
// {
//  mValue.append(value);
// }

U32 LLXMLNode::getChildCount() const
{
    if (mChildren.notNull())
    {
        return mChildren->map.size();
    }
    return 0;
}

//***************************************************
//  UNIT TESTING
//***************************************************

U32 get_rand(U32 max_value)
{
    U32 random_num = rand() + ((U32)rand() << 16);
    return (random_num % max_value);
}

LLXMLNode *get_rand_node(LLXMLNode *node)
{
    if (node->mChildren.notNull())
    {
        U32 num_children = node->mChildren->map.size();
        if (get_rand(2) == 0)
        {
            while (true)
            {
                S32 child_num = S32(get_rand(num_children*2)) - num_children;
                LLXMLChildList::iterator itor = node->mChildren->map.begin();
                while (child_num > 0)
                {
                    --child_num;
                    ++itor;
                }
                if (!itor->second->mIsAttribute)
                {
                    return get_rand_node(itor->second);
                }
            }
        }
    }
    return node;
}

void LLXMLNode::createUnitTest(S32 max_num_children)
{
    // Random ID
    std::string rand_id;
    U32 rand_id_len = get_rand(10)+5;
    for (U32 pos = 0; pos<rand_id_len; ++pos)
    {
        char c = 'a' + get_rand(26);
        rand_id.append(1, c);
    }
    mID = rand_id;

    if (max_num_children < 2)
    {
        setStringValue(1, &mID);
        return;
    }

    // Checksums
    U32 integer_checksum = 0;
    U64 long_checksum = 0;
    U32 bool_true_count = 0;
    LLUUID uuid_checksum;
    U32 noderef_checksum = 0;
    U32 float_checksum = 0;

    // Create a random number of children
    U32 num_children = get_rand(max_num_children)+1;
    for (U32 child_num=0; child_num<num_children; ++child_num)
    {
        // Random Name
        std::string child_name;
        U32 child_name_len = get_rand(10)+5;
        for (U32 pos = 0; pos<child_name_len; ++pos)
        {
            char c = 'a' + get_rand(26);
            child_name.append(1, c);
        }

        LLXMLNode *new_child = createChild(child_name.c_str(), FALSE);

        // Random ID
        std::string child_id;
        U32 child_id_len = get_rand(10)+5;
        for (U32 pos=0; pos<child_id_len; ++pos)
        {
            char c = 'a' + get_rand(26);
            child_id.append(1, c);
        }
        new_child->mID = child_id;

        // Random Length
        U32 array_size = get_rand(28)+1;

        // Random Encoding
        Encoding new_encoding = get_rand(2)?ENCODING_DECIMAL:ENCODING_HEX;

        // Random Type
        int type = get_rand(8);
        switch (type)
        {
        case 0: // TYPE_CONTAINER
            new_child->createUnitTest(max_num_children/2);
            break;
        case 1: // TYPE_BOOLEAN
            {
                BOOL random_bool_values[30];
                for (U32 value=0; value<array_size; ++value)
                {
                    random_bool_values[value] = get_rand(2);
                    if (random_bool_values[value])
                    {
                        ++bool_true_count;
                    }
                }
                new_child->setBoolValue(array_size, random_bool_values);
            }
            break;
        case 2: // TYPE_INTEGER (32-bit)
            {
                U32 random_int_values[30];
                for (U32 value=0; value<array_size; ++value)
                {
                    random_int_values[value] = get_rand(0xffffffff);
                    integer_checksum ^= random_int_values[value];
                }
                new_child->setUnsignedValue(array_size, random_int_values, new_encoding);
            }
            break;
        case 3: // TYPE_INTEGER (64-bit)
            {
                U64 random_int_values[30];
                for (U64 value=0; value<array_size; ++value)
                {
                    random_int_values[value] = (U64(get_rand(0xffffffff)) << 32) + get_rand(0xffffffff);
                    long_checksum ^= random_int_values[value];
                }
                new_child->setLongValue(array_size, random_int_values, new_encoding);
            }
            break;
        case 4: // TYPE_FLOAT (32-bit)
            {
                F32 random_float_values[30];
                for (U32 value=0; value<array_size; ++value)
                {
                    S32 exponent = get_rand(256) - 128;
                    S32 fractional_part = get_rand(0xffffffff);
                    S32 sign = get_rand(2) * 2 - 1;
                    random_float_values[value] = F32(fractional_part) / F32(0xffffffff) * exp(F32(exponent)) * F32(sign);

                    U32 *float_bits = &((U32 *)random_float_values)[value];
                    if (*float_bits == 0x80000000)
                    {
                        *float_bits = 0x00000000;
                    }
                    float_checksum ^= (*float_bits & 0xfffff000);
                }
                new_child->setFloatValue(array_size, random_float_values, new_encoding, 12);
            }
            break;
        case 5: // TYPE_FLOAT (64-bit)
            {
                F64 random_float_values[30];
                for (U32 value=0; value<array_size; ++value)
                {
                    S32 exponent = get_rand(2048) - 1024;
                    S32 fractional_part = get_rand(0xffffffff);
                    S32 sign = get_rand(2) * 2 - 1;
                    random_float_values[value] = F64(fractional_part) / F64(0xffffffff) * exp(F64(exponent)) * F64(sign);

                    U64 *float_bits = &((U64 *)random_float_values)[value];
                    if (*float_bits == 0x8000000000000000ll)
                    {
                        *float_bits = 0x0000000000000000ll;
                    }
                    float_checksum ^= ((*float_bits & 0xfffffff000000000ll) >> 32);
                }
                new_child->setDoubleValue(array_size, random_float_values, new_encoding, 12);
            }
            break;
        case 6: // TYPE_UUID
            {
                LLUUID random_uuid_values[30];
                for (U32 value=0; value<array_size; ++value)
                {
                    random_uuid_values[value].generate();
                    for (S32 byte=0; byte<UUID_BYTES; ++byte)
                    {
                        uuid_checksum.mData[byte] ^= random_uuid_values[value].mData[byte];
                    }
                }
                new_child->setUUIDValue(array_size, random_uuid_values);
            }
            break;
        case 7: // TYPE_NODEREF
            {
                LLXMLNode *random_node_array[30];
                LLXMLNode *root = getRoot();
                for (U32 value=0; value<array_size; ++value)
                {
                    random_node_array[value] = get_rand_node(root);
                    const char *node_name = random_node_array[value]->mName->mString;
                    for (U32 pos=0; pos<strlen(node_name); ++pos)       /* Flawfinder: ignore */
                    {
                        U32 hash_contrib = U32(node_name[pos]) << ((pos % 4) * 8);
                        noderef_checksum ^= hash_contrib;
                    }
                }
                new_child->setNodeRefValue(array_size, (const LLXMLNode **)random_node_array);
            }
            break;
        }
    }

    createChild("integer_checksum", TRUE)->setUnsignedValue(1, &integer_checksum, LLXMLNode::ENCODING_HEX);
    createChild("long_checksum", TRUE)->setLongValue(1, &long_checksum, LLXMLNode::ENCODING_HEX);
    createChild("bool_true_count", TRUE)->setUnsignedValue(1, &bool_true_count, LLXMLNode::ENCODING_HEX);
    createChild("uuid_checksum", TRUE)->setUUIDValue(1, &uuid_checksum);
    createChild("noderef_checksum", TRUE)->setUnsignedValue(1, &noderef_checksum, LLXMLNode::ENCODING_HEX);
    createChild("float_checksum", TRUE)->setUnsignedValue(1, &float_checksum, LLXMLNode::ENCODING_HEX);
}

BOOL LLXMLNode::performUnitTest(std::string &error_buffer)
{
    if (mChildren.isNull())
    {
        error_buffer.append(llformat("ERROR Node %s: No children found.\n", mName->mString));
        return FALSE;
    }

    // Checksums
    U32 integer_checksum = 0;
    U32 bool_true_count = 0;
    LLUUID uuid_checksum;
    U32 noderef_checksum = 0;
    U32 float_checksum = 0;
    U64 long_checksum = 0;

    LLXMLChildList::iterator itor;
    for (itor=mChildren->map.begin(); itor!=mChildren->map.end(); ++itor)
    {
        LLXMLNode *node = itor->second;
        if (node->mIsAttribute)
        {
            continue;
        }
        if (node->mType == TYPE_CONTAINER)
        {
            if (!node->performUnitTest(error_buffer))
            {
                error_buffer.append(llformat("Child test failed for %s.\n", mName->mString));
                //return FALSE;
            }
            continue;
        }
        if (node->mLength < 1 || node->mLength > 30)
        {
            error_buffer.append(llformat("ERROR Node %s: Invalid array length %d, child %s.\n", mName->mString, node->mLength, node->mName->mString));
            return FALSE;
        }
        switch (node->mType)
        {
        case TYPE_CONTAINER:
        case TYPE_UNKNOWN:
            break;
        case TYPE_BOOLEAN:
            {
                BOOL bool_array[30];
                if (node->getBoolValue(node->mLength, bool_array) < node->mLength)
                {
                    error_buffer.append(llformat("ERROR Node %s: Could not read boolean array, child %s.\n", mName->mString, node->mName->mString));
                    return FALSE;
                }
                for (U32 pos=0; pos<(U32)node->mLength; ++pos)
                {
                    if (bool_array[pos])
                    {
                        ++bool_true_count;
                    }
                }
            }
            break;
        case TYPE_INTEGER:
            {
                if (node->mPrecision == 32)
                {
                    U32 integer_array[30];
                    if (node->getUnsignedValue(node->mLength, integer_array, node->mEncoding) < node->mLength)
                    {
                        error_buffer.append(llformat("ERROR Node %s: Could not read integer array, child %s.\n", mName->mString, node->mName->mString));
                        return FALSE;
                    }
                    for (U32 pos=0; pos<(U32)node->mLength; ++pos)
                    {
                        integer_checksum ^= integer_array[pos];
                    }
                }
                else
                {
                    U64 integer_array[30];
                    if (node->getLongValue(node->mLength, integer_array, node->mEncoding) < node->mLength)
                    {
                        error_buffer.append(llformat("ERROR Node %s: Could not read long integer array, child %s.\n", mName->mString, node->mName->mString));
                        return FALSE;
                    }
                    for (U32 pos=0; pos<(U32)node->mLength; ++pos)
                    {
                        long_checksum ^= integer_array[pos];
                    }
                }
            }
            break;
        case TYPE_FLOAT:
            {
                if (node->mPrecision == 32)
                {
                    F32 float_array[30];
                    if (node->getFloatValue(node->mLength, float_array, node->mEncoding) < node->mLength)
                    {
                        error_buffer.append(llformat("ERROR Node %s: Could not read float array, child %s.\n", mName->mString, node->mName->mString));
                        return FALSE;
                    }
                    for (U32 pos=0; pos<(U32)node->mLength; ++pos)
                    {
                        U32 float_bits = ((U32 *)float_array)[pos];
                        float_checksum ^= (float_bits & 0xfffff000);
                    }
                }
                else
                {
                    F64 float_array[30];
                    if (node->getDoubleValue(node->mLength, float_array, node->mEncoding) < node->mLength)
                    {
                        error_buffer.append(llformat("ERROR Node %s: Could not read float array, child %s.\n", mName->mString, node->mName->mString));
                        return FALSE;
                    }
                    for (U32 pos=0; pos<(U32)node->mLength; ++pos)
                    {
                        U64 float_bits = ((U64 *)float_array)[pos];
                        float_checksum ^= ((float_bits & 0xfffffff000000000ll) >> 32);
                    }
                }
            }
            break;
        case TYPE_STRING:
            break;
        case TYPE_UUID:
            {
                LLUUID uuid_array[30];
                if (node->getUUIDValue(node->mLength, uuid_array) < node->mLength)
                {
                    error_buffer.append(llformat("ERROR Node %s: Could not read uuid array, child %s.\n", mName->mString, node->mName->mString));
                    return FALSE;
                }
                for (U32 pos=0; pos<(U32)node->mLength; ++pos)
                {
                    for (S32 byte=0; byte<UUID_BYTES; ++byte)
                    {
                        uuid_checksum.mData[byte] ^= uuid_array[pos].mData[byte];
                    }
                }
            }
            break;
        case TYPE_NODEREF:
            {
                LLXMLNode *node_array[30];
                if (node->getNodeRefValue(node->mLength, node_array) < node->mLength)
                {
                    error_buffer.append(llformat("ERROR Node %s: Could not read node ref array, child %s.\n", mName->mString, node->mName->mString));
                    return FALSE;
                }
                for (U32 pos=0; pos<node->mLength; ++pos)
                {
                    const char *node_name = node_array[pos]->mName->mString;
                    for (U32 pos2=0; pos2<strlen(node_name); ++pos2)        /* Flawfinder: ignore */
                    {
                        U32 hash_contrib = U32(node_name[pos2]) << ((pos2 % 4) * 8);
                        noderef_checksum ^= hash_contrib;
                    }
                }
            }
            break;
        }
    }

    LLXMLNodePtr checksum_node;

    // Compare checksums
    {
        U32 node_integer_checksum = 0;
        if (!getAttribute("integer_checksum", checksum_node, FALSE) ||
            checksum_node->getUnsignedValue(1, &node_integer_checksum, ENCODING_HEX) != 1)
        {
            error_buffer.append(llformat("ERROR Node %s: Integer checksum missing.\n", mName->mString));
            return FALSE;
        }
        if (node_integer_checksum != integer_checksum)
        {
            error_buffer.append(llformat("ERROR Node %s: Integer checksum mismatch: read %X / calc %X.\n", mName->mString, node_integer_checksum, integer_checksum));
            return FALSE;
        }
    }

    {
        U64 node_long_checksum = 0;
        if (!getAttribute("long_checksum", checksum_node, FALSE) ||
            checksum_node->getLongValue(1, &node_long_checksum, ENCODING_HEX) != 1)
        {
            error_buffer.append(llformat("ERROR Node %s: Long Integer checksum missing.\n", mName->mString));
            return FALSE;
        }
        if (node_long_checksum != long_checksum)
        {
            U32 *pp1 = (U32 *)&node_long_checksum;
            U32 *pp2 = (U32 *)&long_checksum;
            error_buffer.append(llformat("ERROR Node %s: Long Integer checksum mismatch: read %08X%08X / calc %08X%08X.\n", mName->mString, pp1[1], pp1[0], pp2[1], pp2[0]));
            return FALSE;
        }
    }

    {
        U32 node_bool_true_count = 0;
        if (!getAttribute("bool_true_count", checksum_node, FALSE) ||
            checksum_node->getUnsignedValue(1, &node_bool_true_count, ENCODING_HEX) != 1)
        {
            error_buffer.append(llformat("ERROR Node %s: Boolean checksum missing.\n", mName->mString));
            return FALSE;
        }
        if (node_bool_true_count != bool_true_count)
        {
            error_buffer.append(llformat("ERROR Node %s: Boolean checksum mismatch: read %X / calc %X.\n", mName->mString, node_bool_true_count, bool_true_count));
            return FALSE;
        }
    }

    {
        LLUUID node_uuid_checksum;
        if (!getAttribute("uuid_checksum", checksum_node, FALSE) ||
            checksum_node->getUUIDValue(1, &node_uuid_checksum) != 1)
        {
            error_buffer.append(llformat("ERROR Node %s: UUID checksum missing.\n", mName->mString));
            return FALSE;
        }
        if (node_uuid_checksum != uuid_checksum)
        {
            error_buffer.append(llformat("ERROR Node %s: UUID checksum mismatch: read %s / calc %s.\n", mName->mString, node_uuid_checksum.asString().c_str(), uuid_checksum.asString().c_str()));
            return FALSE;
        }
    }

    {
        U32 node_noderef_checksum = 0;
        if (!getAttribute("noderef_checksum", checksum_node, FALSE) ||
            checksum_node->getUnsignedValue(1, &node_noderef_checksum, ENCODING_HEX) != 1)
        {
            error_buffer.append(llformat("ERROR Node %s: Node Ref checksum missing.\n", mName->mString));
            return FALSE;
        }
        if (node_noderef_checksum != noderef_checksum)
        {
            error_buffer.append(llformat("ERROR Node %s: Node Ref checksum mismatch: read %X / calc %X.\n", mName->mString, node_noderef_checksum, noderef_checksum));
            return FALSE;
        }
    }

    {
        U32 node_float_checksum = 0;
        if (!getAttribute("float_checksum", checksum_node, FALSE) ||
            checksum_node->getUnsignedValue(1, &node_float_checksum, ENCODING_HEX) != 1)
        {
            error_buffer.append(llformat("ERROR Node %s: Float checksum missing.\n", mName->mString));
            return FALSE;
        }
        if (node_float_checksum != float_checksum)
        {
            error_buffer.append(llformat("ERROR Node %s: Float checksum mismatch: read %X / calc %X.\n", mName->mString, node_float_checksum, float_checksum));
            return FALSE;
        }
    }

    return TRUE;
}

LLXMLNodePtr LLXMLNode::getFirstChild() const
{
    if (mChildren.isNull()) return NULL;
    LLXMLNodePtr ret = mChildren->head;
    return ret;
}

LLXMLNodePtr LLXMLNode::getNextSibling() const
{
    LLXMLNodePtr ret = mNext;
    return ret;
}

std::string LLXMLNode::getSanitizedValue() const
{
    if (mIsAttribute)
    {
        return getValue() ;
    }
    else
    {
        return getTextContents();
    }
}


std::string LLXMLNode::getTextContents() const
{
    std::string msg;
    std::string contents = mValue;
    std::string::size_type n = contents.find_first_not_of(" \t\n");
    if (n != std::string::npos && contents[n] == '\"')
    {
        // Case 1: node has quoted text
        S32 num_lines = 0;
        while(1)
        {
            // mContents[n] == '"'
            ++n;
            std::string::size_type t = n;
            std::string::size_type m = 0;
            // fix-up escaped characters
            while(1)
            {
                m = contents.find_first_of("\\\"", t); // find first \ or "
                if ((m == std::string::npos) || (contents[m] == '\"'))
                {
                    break;
                }
                contents.erase(m,1);
                t = m+1;
            }
            if (m == std::string::npos)
            {
                break;
            }
            // mContents[m] == '"'
            num_lines++;
            msg += contents.substr(n,m-n) + "\n";
            n = contents.find_first_of("\"", m+1);
            if (n == std::string::npos)
            {
                if (num_lines == 1)
                {
                    msg.erase(msg.size()-1); // remove "\n" if only one line
                }
                break;
            }
        }
    }
    else
    {
        // Case 2: node has embedded text (beginning and trailing whitespace trimmed)
        std::string::size_type start = mValue.find_first_not_of(" \t\n");
        if (start != mValue.npos)
        {
            std::string::size_type end = mValue.find_last_not_of(" \t\n");
            if (end != mValue.npos)
            {
                msg = mValue.substr(start, end+1-start);
            }
            else
            {
                msg = mValue.substr(start);
            }
        }
        // Convert any internal CR to LF
        msg = utf8str_removeCRLF(msg);
    }
    return msg;
}

void LLXMLNode::setLineNumber(S32 line_number)
{
    mLineNumber = line_number;
}

S32 LLXMLNode::getLineNumber()
{
    return mLineNumber;
}