public static KeyValue ParseKeyValueFile(string path) { string[] lines = System.IO.File.ReadAllLines("SampleKeyValues/" + path); KeyValue kv = new KeyValue(""); foreach (string str in lines) { if (str.StartsWith("{") || str.StartsWith("//")) { continue; } if (str.Contains("}")) { kv = kv.Parent ?? kv; //we reached the end of a parent, go up one level continue; } string key = null; string value = null; for (int i = 0; i < str.Length; i++) { if (str.ElementAt(i) == '/' && str.ElementAt(i + 1) == '/') { break; } switch (parseState) { case parseEnum.nil: if (str.ElementAt(i) == '"') { parseState = parseEnum.foundFirstKey; key = ""; continue; } break; case parseEnum.foundFirstKey: if (str.ElementAt(i) != '"') { key = key + str.ElementAt(i).ToString(); } else { parseState = parseEnum.foundSecondKey; continue; } break; case parseEnum.foundSecondKey: if (str.ElementAt(i) == '"') { parseState = parseEnum.foundFirstValue; value = ""; continue; } break; case parseEnum.foundFirstValue: if (str.ElementAt(i) != '"') { value = value + str.ElementAt(i).ToString(); } else { parseState = parseEnum.foundSecondvalue; continue; } break; case parseEnum.foundSecondvalue: // break; } } parseState = parseEnum.nil; if (key == null && value == null) { continue; } if (value == null) { if (kv.Key == "") { kv = new KeyValue(key); } else { KeyValue tmpKv = new KeyValue(key); kv.AddChild(tmpKv); kv = tmpKv; //we found a parent, set kv to be the parent we found } } else { KeyValue tmpKv = new KeyValue(key); tmpKv.Value = value; kv.AddChild(tmpKv); } } return(kv); }
/// <summary> /// Grab all of the keyvalues from a string. /// </summary> /// <param name="contents">The string containing keyvalues</param> /// <param name="allowunnamedkeys">Whether or not to allow unnamed blocks (used in bsp entity lump)</param> /// <returns>An array containing all root-level KeyValues in the string</returns> /// <exception cref="KVLib.KeyValues.KeyValueParsingException">Throws one of these if parsing fails</exception> public static KeyValue[] ParseAllKeyValues(string contents, bool allowunnamedkeys = false) { if (contents == null) { throw new KeyValueParsingException("Contents string was null!", new ArgumentNullException()); } try { parseEnum parseState = parseEnum.lookingForKey; KeyValue basekv = new KeyValue("base"); // file contents are interpreted as children of this keyvalue KeyValue curparent = basekv; for (int i = 0; i < contents.Length; i++) { // go until next symbol if (contents[i] == ' ' || contents[i] == '\t' || contents[i] == '\n' || contents[i] == '\r') { continue; } switch (parseState) { case parseEnum.lookingForKey: if (contents[i] == '{') { if (!allowunnamedkeys) { throw new KeyValueParsingException("Hit unnamed key while parsing without unnamed keys enabled.", null); } // This is a special case - some kv files, in particular bsp entity lumps, have unkeyed kvs KeyValue cur = new KeyValue("UNNAMED"); curparent.AddChild(cur); curparent = cur; parseState = parseEnum.lookingForValue; } else if (contents[i] == '"' || contents[i] == '\'') { //quoted key int j = i + 1; if (j >= contents.Length) { throw new KeyValueParsingException("Couldn't find terminating '" + contents[i].ToString() + "' for key started at position " + i.ToString(), null); } while (contents[j] != contents[i]) { // handle escaped quotes if (contents[j] == '\\') { j++; } j++; if (j >= contents.Length) { throw new KeyValueParsingException("Couldn't find terminating '" + contents[i].ToString() + "' for key started at position " + i.ToString(), null); } } //ok, now contents[i] and contents[j] are the same character, on either end of the key KeyValue cur = new KeyValue(contents.Substring(i + 1, j - (i + 1))); //Console.WriteLine("DEBUG: " + contents.Substring(i + 1, j - (i + 1))); curparent.AddChild(cur); curparent = cur; parseState = parseEnum.lookingForValue; i = j; } else if (Char.IsLetter(contents[i])) { //un-quoted key int j = i; while (contents[j] != ' ' && contents[j] != '\t' && contents[j] != '\n' && contents[j] != '\r') { j++; if (j > contents.Length) { throw new KeyValueParsingException("Couldn't find end of key started at position " + i.ToString(), null); } } KeyValue cur = new KeyValue(contents.Substring(i, j - i)); curparent.AddChild(cur); curparent = cur; parseState = parseEnum.lookingForValue; i = j; } else if (contents[i] == '}') { //drop one level curparent = curparent.Parent; } else if (contents[i] == '/') { if (i + 1 < contents.Length && contents[i + 1] == '/') { // we're in a comment! throw stuff away until the next \n while (i < contents.Length && contents[i] != '\n') { i++; } } else { if (BREAK_SPEC) { // Valve (incorrectly?) matches / as a single-line comment while (i < contents.Length && contents[i] != '\n') { i++; } } } } else { throw new KeyValueParsingException("Unexpected '" + contents[i].ToString() + "' at position " + i.ToString(), null); } break; case parseEnum.lookingForValue: if (contents[i] == '{') { // it's a list of children // thankfully, we don't actually need to handle this! parseState = parseEnum.lookingForKey; } else if (contents[i] == '"' || contents[i] == '\'') { //quoted value int j = i + 1; while (contents[j] != contents[i]) { // handle escaped quotes if (contents[j] == '\\') { j++; } j++; if (j > contents.Length) { throw new KeyValueParsingException("Couldn't find terminating '" + contents[i].ToString() + "' for key started at position " + i.ToString(), null); } } //ok, now contents[i] and contents[j] are the same character, on either end of the value curparent.Set(contents.Substring(i + 1, j - (i + 1))); curparent = curparent.Parent; parseState = parseEnum.lookingForKey; i = j; } else if (contents[i] == '/') { if (i + 1 < contents.Length && contents[i + 1] == '/') { // we're in a comment! throw stuff away until the next \n while (i < contents.Length && contents[i] != '\n') { i++; } } } else if (!Char.IsWhiteSpace(contents[i])) { int j = i; while (contents[j] != ' ' && contents[j] != '\t' && contents[j] != '\n' && contents[j] != '\r') { j++; if (j > contents.Length) { // a value ending the file counts as ending the value break; } } curparent.Set(contents.Substring(i, j - i)); curparent = curparent.Parent; parseState = parseEnum.lookingForKey; i = j; } else { throw new KeyValueParsingException("Unexpected '" + contents[i].ToString() + "' at position " + i.ToString(), null); } break; } } // At the end of the file, we should be looking for another key if (parseState != parseEnum.lookingForKey) { throw new KeyNotFoundException("File ended while looking for value", null); } // At the end of the file, all block values should be closed if (curparent != basekv) { throw new KeyNotFoundException("Unterminated child blocks", null); } KeyValue[] ret = basekv.Children.ToArray <KeyValue>(); basekv.ClearChildParents(); return(ret); } catch (KeyValueParsingException e) { throw e; } catch (Exception e) { throw new KeyValueParsingException("Hit an exception while parsing kv data!", e); } }