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; }