SquareTree::SquareTree(const char *data) : root(NULL)
{
    // convert the file content to UTF-8
    if (str::StartsWith(data, UTF8_BOM))
        dataUtf8.Set(str::Dup(data + 3));
    else if (str::StartsWith(data, UTF16_BOM))
        dataUtf8.Set(str::conv::ToUtf8((const WCHAR *)(data + 2)));
    else if (data)
        dataUtf8.Set(str::conv::ToUtf8(ScopedMem<WCHAR>(str::conv::FromAnsi(data))));
    if (!dataUtf8)
        return;

    char *start = dataUtf8.Get();
    root = ParseSquareTreeRec(start, true);
    CrashIf(*start || !root);
}
SquareTree::SquareTree(const char* data) : root(nullptr) {
    // convert the file content to UTF-8
    if (str::StartsWith(data, UTF8_BOM)) {
        dataUtf8.SetCopy(data + 3);
    } else if (str::StartsWith(data, UTF16_BOM)) {
        auto tmp = str::conv::ToUtf8((const WCHAR*)(data + 2));
        dataUtf8.Set(tmp.StealData());
    } else if (data) {
        AutoFreeW tmp(str::conv::FromAnsi(data));
        auto tmp2 = str::conv::ToUtf8(tmp.Get());
        dataUtf8.Set(tmp2.StealData());
    }
    if (!dataUtf8) {
        return;
    }

    char* start = dataUtf8.Get();
    root = ParseSquareTreeRec(start, true);
    CrashIf(*start || !root);
}
static SquareTreeNode *ParseSquareTreeRec(char *& data, bool isTopLevel=false)
{
    SquareTreeNode *node = new SquareTreeNode();

    while (*(data = SkipWsAndComments(data))) {
        // all non-empty non-comment lines contain a key-value pair
        // where the value is either a string (separated by '=' or ':')
        // or a list of child nodes (if the key is followed by '[' alone)
        char *key = data;
        for (data = key; *data && *data != '=' && *data != ':' && *data != '[' && *data != ']' && *data != '\n'; data++);
        if (!*data || '\n' == *data) {
            // use first whitespace as a fallback separator
            for (data = key; *data && !str::IsWs(*data); data++);
        }
        char *separator = data;
        if (*data && *data != '\n') {
            // skip to the first non-whitespace character on the same line (value)
            data = SkipWs(data + 1, true);
        }
        char *value = data;
        // skip to the end of the line
        for (; *data && *data != '\n'; data++);
        if (IsBracketLine(separator) ||
            // also tolerate "key \n [ \n ... \n ]" (else the key
            // gets an empty value and the child node an empty key)
            str::IsWs(*separator) && '\n' == *value && IsBracketLine(SkipWsAndComments(data))) {
            // parse child node(s)
            data = SkipWsAndComments(separator) + 1;
            *SkipWsRev(key, separator) = '\0';
            node->data.Append(SquareTreeNode::DataItem(key, ParseSquareTreeRec(data)));
            // arrays are created by either reusing the same key for a different child
            // or by concatenating multiple children ("[ \n ] [ \n ] [ \n ]")
            while (IsBracketLine((data = SkipWsAndComments(data)))) {
                data++;
                node->data.Append(SquareTreeNode::DataItem(key, ParseSquareTreeRec(data)));
            }
        }
        else if (']' == *key) {
            // finish parsing child node
            data = key + 1;
            if (!isTopLevel)
                return node;
            // ignore superfluous closing square brackets instead of
            // ignoring all content following them
        }
        else if ('[' == *key && ']' == SkipWsRev(value, data)[-1]) {
            // treat INI section headers as top-level node names
            // (else "[Section]" would be ignored)
            if (!isTopLevel) {
                data = key;
                return node;
            }
            // trim whitespace around section name (for consistency with GetPrivateProfileString)
            key = SkipWs(key + 1);
            *SkipWsRev(key, SkipWsRev(value, data) - 1) = '\0';
            node->data.Append(SquareTreeNode::DataItem(key, ParseSquareTreeRec(data)));
        }
        else if ('[' == *separator || ']' == *separator) {
            // invalid line (ignored)
        }
        else {
            // string value (decoding is left to the consumer)
            bool hasMoreLines = '\n' == *data;
            *SkipWsRev(key, separator) = '\0';
            *SkipWsRev(value, data) = '\0';
            node->data.Append(SquareTreeNode::DataItem(key, value));
            if (hasMoreLines)
                data++;
        }
    }

    // assume that all square brackets have been properly balanced
    return node;
}