UltimateZoneBuilder/Source/Core/IO/Configuration.cs
MaxED bafb8ed511 UDMF: sidedef flags are now defined in game configuration instead of being hardcoded.
Texture scale controls now have "Link" button. 
Thing, Sector (UDMF) and Linedef edit forms now work in realtime mode.
Some more fixes in Edit form realtime update stuff.
Removed some unused variables and functions.
2013-07-19 15:30:58 +00:00

1505 lines
44 KiB
C#

#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* This program is released under GNU General Public License
*
* This program 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 General Public License for more details.
*
*/
#endregion
#region ================== CFG file structure syntax
/*
' ====================================================================================
' CONFIGURATION FILE STRUCTURE SYNTAX
' ====================================================================================
'
' Whitepace is always allowed. This includes spaces, tabs
' linefeeds (10) and carriage returns (13)
'
' Keys may not have spaces or assignment operator = in them.
'
' Comments start with // (unless used within strings)
' and count as comment for the rest of the line. Or use /* and */ /*
' to mark the beginning and end of a comment.
'
' Simple setting:
'
' key = value;
'
' Example: speed = 345;
' cars = 8;
'
' Strings must be in quotes.
'
' Example: nickname = "Gherkin";
' altnick = "Gherk inn";
'
' String Escape Sequences:
' \n New line (10)
' \r Carriage return (13)
' \t Tab (9)
' \" Double-quotation mark
' \\ Backslash
' \000 Any ASCII character (MUST be 3 digits! So for 13 you use \013)
'
' Decimals ALWAYS use a dot, NEVER comma!
'
' Example: pressure = 15.29;
' acceleration = 1.0023;
'
' true, false and null are valid keywords.
' null values can be left out.
'
' In this example, both items are null.
' Example: myitem = null;
' myotheritem;
'
' Structures must use brackets.
'
' Structure Example:
'
' key
' {
' key = value;
' key = value;
'
' key
' {
' key = value;
' key = value;
' key = value;
' }
'
' key = value;
' key = value;
' key = value;
' key = value;
' key = value;
' }
'
' As you can see, structures inside structures are allowed
' and you may go as deep as you want. Note that only the root structures
' can be readed from config using ReadSetting. ReadSetting will return a
' Dictionary object containing everything in that root structure.
'
' Key names must be unique within their scope.
'
' This is NOT allowed, it may not have 'father' more
' than once in the same scope:
'
' mother = 45;
' father = 52;
'
' father
' {
' length = 1.87;
' }
'
' This however is allowed, because father
' now exists in a different scope:
'
' mother = 45;
' father = 52;
'
' parents
' {
' father = 52;
' }
'
' This too is allowed, both 'age' are in a different scope:
'
' mother
' {
' age = 45;
' }
'
' father
' {
' age = 52;
' }
*/
#endregion
#region ================== Namespaces
using System;
using System.IO;
using System.Text;
using System.Globalization;
using System.Collections;
using System.Collections.Specialized;
#endregion
namespace CodeImp.DoomBuilder.IO
{
public sealed class Configuration
{
#region ================== Constants
// Path seperator
public const string DEFAULT_SEPERATOR = ".";
// Error strings
private const string ERROR_KEYMISSING = "Missing key name in assignment or scope.";
private const string ERROR_KEYSPACES = "Spaces not allowed in key names.";
private const string ERROR_ASSIGNINVALID = "Invalid assignment. Missing a previous terminator symbol?";
private const string ERROR_VALUEINVALID = "Invalid value in assignment. Missing a previous terminator symbol?";
private const string ERROR_VALUETOOBIG = "Value too big.";
//private const string ERROR_KEYNOTUNQIUE = "Key is not unique within scope.";
private const string ERROR_KEYWORDUNKNOWN = "Unknown keyword in assignment. Missing a previous terminator symbol?";
private const string ERROR_UNEXPECTED_END = "Unexpected end of data. Missing a previous terminator symbol?";
private const string ERROR_UNKNOWN_FUNCTION = "Unknown function call.";
private const string ERROR_INVALID_ARGS = "Invalid function arguments.";
private const string ERROR_INCLUDE_UNSUPPORTED = "Include function is not supported in data parsed from stream.";
#endregion
#region ================== Variables
// Error result
private bool cpErrorResult = false;
private string cpErrorDescription = "";
private int cpErrorLine = 0;
private string cpErrorFile = "";
// Configuration root
private IDictionary root = null;
#endregion
#region ================== Properties
// Properties
public bool ErrorResult { get { return cpErrorResult; } }
public string ErrorDescription { get { return cpErrorDescription; } }
public int ErrorLine { get { return cpErrorLine; } }
public string ErrorFile { get { return cpErrorFile; } }
public IDictionary Root { get { return root; } set { root = value; } }
public bool Sorted { get { return (root is ListDictionary); } }
#endregion
#region ================== Constructor / Destructor
// Constructor
public Configuration()
{
// Standard new configuration
NewConfiguration();
// We have no destructor
GC.SuppressFinalize(this);
}
// Constructor
public Configuration(bool sorted)
{
// Standard new configuration
NewConfiguration(sorted);
// We have no destructor
GC.SuppressFinalize(this);
}
// Constructor to load a file immediately
public Configuration(string filename)
{
// Load configuration from file
LoadConfiguration(filename);
// We have no destructor
GC.SuppressFinalize(this);
}
// Constructor to load a file immediately
public Configuration(string filename, bool sorted)
{
// Load configuration from file
LoadConfiguration(filename, sorted);
// We have no destructor
GC.SuppressFinalize(this);
}
#endregion
#region ================== Private Methods
// This merges two structures
private static IDictionary Combine(IDictionary d1, IDictionary d2, bool sorted)
{
// Create new dictionary
IDictionary result;
if(sorted) result = new ListDictionary(); else result = new Hashtable();
// Copy all items from d1 to result
IDictionaryEnumerator d1e = d1.GetEnumerator();
while(d1e.MoveNext()) result.Add(d1e.Key, d1e.Value);
// Go for all items in d2
IDictionaryEnumerator d2e = d2.GetEnumerator();
while(d2e.MoveNext())
{
// Check if this is another Hashtable
if(d2e.Value is IDictionary)
{
// Check if already in result
if(result.Contains(d2e.Key) && (result[d2e.Key] is IDictionary))
{
// Modify result
result[d2e.Key] = Combine((IDictionary)result[d2e.Key], (IDictionary)d2e.Value, sorted);
}
else
{
// Copy from d2
if(sorted)
{
// Sorted combine
result[d2e.Key] = Combine(new ListDictionary(), (IDictionary)d2e.Value, sorted);
}
else
{
// Unsorted combine
result[d2e.Key] = Combine(new Hashtable(), (IDictionary)d2e.Value, sorted);
}
}
}
else
{
// Check if also in d1
if(result.Contains(d2e.Key))
{
// Modify result
result[d2e.Key] = d2e.Value;
}
else
{
// Copy
result.Add(d2e.Key, d2e.Value);
}
}
}
// Return result
return result;
}
// This is called by all the ReadSetting overloads to perform the read
private bool CheckSetting(IDictionary dic, string setting, string pathseperator)
{
IDictionary cs = null;
// Split the path in an array
string[] keys = setting.Split(pathseperator.ToCharArray());
// Get the root item
object item = dic;
// Go for each item
for(int i = 0; i < keys.Length; i++)
{
// Check if the current item is of ConfigStruct type
if(item is IDictionary)
{
// Check if the key is valid
if(ValidateKey(null, keys[i].Trim(), "", -1) == true)
{
// Cast to ConfigStruct
cs = (IDictionary)item;
// Check if the requested item exists
if(cs.Contains(keys[i]) == true)
{
// Set the item to the next item
item = cs[keys[i]];
}
else
{
// Key not found
return false;
}
}
else
{
// Invalid key in path
return false;
}
}
else
{
// Unable to go any further
return false;
}
}
// Return result
return true;
}
// This is called by all the ReadSetting overloads to perform the read
private object ReadAnySetting(string setting, object defaultsetting, string pathseperator) { return ReadAnySetting(root, setting, defaultsetting, pathseperator); }
private object ReadAnySetting(IDictionary dic, string setting, object defaultsetting, string pathseperator) { return ReadAnySetting(dic, "", -1, setting, defaultsetting, pathseperator); }
private object ReadAnySetting(IDictionary dic, string file, int line, string setting, object defaultsetting, string pathseperator)
{
IDictionary cs = null;
// Split the path in an array
string[] keys = setting.Split(pathseperator.ToCharArray());
// Get the root item
object item = dic;
// Go for each item
for(int i = 0; i < keys.Length; i++)
{
// Check if the current item is of ConfigStruct type
if(item is IDictionary)
{
// Check if the key is valid
if(ValidateKey(null, keys[i].Trim(), file, line) == true)
{
// Cast to ConfigStruct
cs = (IDictionary)item;
// Check if the requested item exists
if(cs.Contains(keys[i]) == true)
{
// Set the item to the next item
item = cs[keys[i]];
}
else
{
// Key not found
// return default setting
return defaultsetting;
}
}
else
{
// Invalid key in path
// return default setting
return defaultsetting;
}
}
else
{
// Unable to go any further
// return default setting
return defaultsetting;
}
}
// Return the item
return item;
}
// This returns a string added with escape characters
private string EscapedString(string str)
{
// Replace the \ with \\ first!
str = str.Replace("\\", "\\\\");
str = str.Replace("\n", "\\n");
str = str.Replace("\r", "\\r");
str = str.Replace("\t", "\\t");
str = str.Replace("\"", "\\\"");
// Return result
return str;
}
// This raises an error
private void RaiseError(string file, int line, string description)
{
// Raise error
if(!cpErrorResult)
{
cpErrorResult = true;
cpErrorDescription = description;
cpErrorLine = line;
cpErrorFile = file;
}
}
// This validates a given key and sets
// error properties if key is invalid and errorline > -1
private bool ValidateKey(IDictionary container, string key, string file, int errorline)
{
bool validateresult;
// Check if key is an empty string
if(key == "")
{
// ERROR: Missing key name in statement
if(errorline > -1) RaiseError(file, errorline, ERROR_KEYMISSING);
validateresult = false;
}
else
{
// Check if there are spaces in the key
if(key.IndexOfAny(" ".ToCharArray()) > -1)
{
// ERROR: Spaces not allowed in key names
if(errorline > -1) RaiseError(file, errorline, ERROR_KEYSPACES);
validateresult = false;
}
else
{
// Check if we can test existance
if(container != null)
{
/*
// Test if the key exists in this container
if(container.Contains(key) == true)
{
// ERROR: Key is not unique within struct
if(errorline > -1) RaiseError(file, errorline, ERROR_KEYNOTUNQIUE);
validateresult = false;
}
else
*/
{
// Key OK
validateresult = true;
}
}
else
{
// Key OK
validateresult = true;
}
}
}
// Return result
return validateresult;
}
// This validates a given keyword and sets
// error properties if keyword is invalid and errorline > -1
private bool ValidateKeyword(string keyword, string file, int errorline)
{
bool validateresult;
// Check if key is an empty string
if(keyword == "")
{
// ERROR: Missing key name in statement
if(errorline > -1) RaiseError(file, errorline, ERROR_ASSIGNINVALID);
validateresult = false;
}
else
{
// Check if there are spaces in the key
if(keyword.IndexOfAny(" ".ToCharArray()) > -1)
{
// ERROR: Spaces not allowed in key names
if(errorline > -1) RaiseError(file, errorline, ERROR_ASSIGNINVALID);
validateresult = false;
}
else
{
// Key OK
validateresult = true;
}
}
// Return result
return validateresult;
}
#endregion
#region ================== Parsing
// This parses an assignment
private object ParseAssignment(ref string file, ref string data, ref int pos, ref int line)
{
object val = null;
while((pos < data.Length) && !cpErrorResult)
{
// Get current character
char c = data[pos++];
// Check for string opening
if(c == '\"')
{
// Now parsing a string
val = ParseString(ref file, ref data, ref pos, ref line);
if(cpErrorResult) return null;
}
// Check for numeric character
else if("0123456789-.&".IndexOf(c.ToString(CultureInfo.InvariantCulture)) > -1)
{
// Go one byte back, because this
// byte is part of the number!
pos--;
// Now parsing a number
val = ParseNumber(ref file, ref data, ref pos, ref line);
if(cpErrorResult) return null;
}
// Check for new line
else if(c == '\n')
{
// Count the new line
line++;
}
// Check if assignment ends
else if(c == ';')
{
// End of assignment
return val;
}
// Otherwise (if not whitespace) it is a keyword
else if((c != ' ') && (c != '\t'))
{
// Go one byte back, because this
// byte is part of the keyword!
pos--;
// Now parsing a keyword
val = ParseKeyword(ref file, ref data, ref pos, ref line);
if(cpErrorResult) return null;
}
}
RaiseError(file, line, ERROR_UNEXPECTED_END);
return null;
}
// This parses a string
private string ParseString(ref string file, ref string data, ref int pos, ref int line)
{
string val = "";
// In escape sequence?
bool escape = false;
while((pos < data.Length) && !cpErrorResult)
{
// Get current character
char c = data[pos++];
// Check if in an escape sequence
if(escape)
{
// What character?
switch(c)
{
case '\\': val += "\\"; break;
case 'n': val += "\n"; break;
case '\"': val += "\""; break;
case 'r': val += "\r"; break;
case 't': val += "\t"; break;
default:
// Is it a number?
if("0123456789".IndexOf(c.ToString(CultureInfo.InvariantCulture)) > -1)
{
int vv = 0;
char vc = '0';
// Convert the next 3 characters to a number
string v = data.Substring(pos, 3);
try { vv = System.Convert.ToInt32(v.Trim(), CultureInfo.InvariantCulture); }
catch(System.FormatException)
{
// ERROR: Invalid value in assignment
RaiseError(file, line, ERROR_VALUEINVALID);
return null;
}
// Convert the number to a char
try { vc = System.Convert.ToChar(vv, CultureInfo.InvariantCulture); }
catch(System.FormatException)
{
// ERROR: Invalid value in assignment
RaiseError(file, line, ERROR_VALUEINVALID);
return null;
}
// Add the char
val += vc.ToString(CultureInfo.InvariantCulture);
}
else
{
// Add the character as it is
val += c.ToString(CultureInfo.InvariantCulture);
}
// Leave switch
break;
}
// End of escape sequence
escape = false;
}
else
{
// Check for sequence start
if(c == '\\')
{
// Next character is of escape sequence
escape = true;
}
// Check if string ends
else if(c == '\"')
{
return val;
}
// Check for new line
else if(c == '\n')
{
// Count the new line
line++;
}
// Everything else is just part of string
else
{
// Add to value
val += c.ToString(CultureInfo.InvariantCulture);
}
}
}
RaiseError(file, line, ERROR_UNEXPECTED_END);
return null;
}
// Parsing a number
private object ParseNumber(ref string file, ref string data, ref int pos, ref int line)
{
string val = "";
while((pos < data.Length) && !cpErrorResult)
{
// Get current character
char c = data[pos++];
// Check if number ends here
if((c == ';') || (c == ',') || (c == ')'))
{
// One byte back, because this
// is also the end of the assignment
pos--;
// Floating point?
if(val.IndexOf("f") > -1)
{
float fval = 0;
// Convert to float (remove the f first)
try { fval = System.Convert.ToSingle(val.Trim().Replace("f", ""), CultureInfo.InvariantCulture); }
catch(System.FormatException)
{
// ERROR: Invalid value in assignment
RaiseError(file, line, ERROR_VALUEINVALID);
return null;
}
return fval;
}
else
{
int ival = 0;
long lval = 0;
// Convert to int
try
{
// Convert to value
ival = System.Convert.ToInt32(val.Trim(), CultureInfo.InvariantCulture);
return ival;
}
catch(System.OverflowException)
{
// Too large for Int32, try Int64
try
{
// Convert to value
lval = System.Convert.ToInt64(val.Trim(), CultureInfo.InvariantCulture);
return lval;
}
catch(System.OverflowException)
{
// Too large for Int64, return error
RaiseError(file, line, ERROR_VALUETOOBIG);
return null;
}
catch(System.FormatException)
{
// ERROR: Invalid value in assignment
RaiseError(file, line, ERROR_VALUEINVALID);
return null;
}
}
catch(System.FormatException)
{
// ERROR: Invalid value in assignment
RaiseError(file, line, ERROR_VALUEINVALID);
return null;
}
}
}
// Check for new line
else if(c == '\n')
{
// Count the new line
line++;
}
// Everything else is part of the value
else
{
val += c.ToString(CultureInfo.InvariantCulture);
}
}
RaiseError(file, line, ERROR_UNEXPECTED_END);
return null;
}
// Parsing a keyword
private object ParseKeyword(ref string file, ref string data, ref int pos, ref int line)
{
string val = "";
while((pos < data.Length) && !cpErrorResult)
{
// Get current character
char c = data[pos++];
// Check if keyword ends
if((c == ';') || (c == ',') || (c == ')'))
{
// One byte back, because this
// is also the end of the assignment
pos--;
// Validate the keyword
if(ValidateKeyword(val.Trim(), file, line))
{
// Return result depending on the keyword
switch(val.Trim().ToLowerInvariant())
{
case "true": return (bool)true;
case "false": return (bool)false;
case "null": return null;
default: RaiseError(file, line, ERROR_KEYWORDUNKNOWN + "\nUnrecognized token: '" + val.Trim().ToLowerInvariant() + "'"); return null;
}
}
}
// Check for new line
else if(c == '\n')
{
// Count the new line
line++;
}
// Everything else is just part of keyword
else
{
// Add to value
val += c.ToString(CultureInfo.InvariantCulture);
}
}
RaiseError(file, line, ERROR_UNEXPECTED_END);
return null;
}
// This includes another file
private void FunctionInclude(IDictionary cs, ArrayList args, ref string file, int line)
{
string data;
if(string.IsNullOrEmpty(file)) RaiseError(file, line, ERROR_INCLUDE_UNSUPPORTED);
if(args.Count < 1) RaiseError(file, line, ERROR_INVALID_ARGS);
if(!(args[0] is string)) RaiseError(file, line, ERROR_INVALID_ARGS + " Expected a string for argument 1.");
if((args.Count > 1) && !(args[1] is string)) RaiseError(file, line, ERROR_INVALID_ARGS + " Expected a string for argument 2.");
if(cpErrorResult) return;
// Determine the full path of the file to include
string includefile = Path.GetDirectoryName(file) + Path.DirectorySeparatorChar + args[0].ToString();
try
{
// Load the file contents
FileStream fstream = File.OpenRead(includefile);
byte[] fbuffer = new byte[fstream.Length];
fstream.Read(fbuffer, 0, fbuffer.Length);
fstream.Close();
// Convert byte array to string
data = Encoding.UTF8.GetString(fbuffer);
}
catch(Exception e)
{
RaiseError(file, line, "Unable to include file '" + includefile + "'. " + e.GetType().Name + ": " + e.Message);
return;
}
// Remove returns and tabs because the
// parser only uses newline for new lines.
data = data.Replace("\r", "");
data = data.Replace("\t", "");
// Parse the data
IDictionary inc;
if(cs is ListDictionary) inc = new ListDictionary(); else inc = new Hashtable();
int npos = 0, nline = 1;
InputStructure(inc, ref includefile, ref data, ref npos, ref nline);
if(!cpErrorResult)
{
// Check if a path is given
if((args.Count > 1) && !string.IsNullOrEmpty(args[1].ToString()))
{
IDictionary def;
if(cs is ListDictionary) def = new ListDictionary(); else def = new Hashtable();
if(CheckSetting(inc, args[1].ToString(), DEFAULT_SEPERATOR))
{
inc = (IDictionary)ReadAnySetting(inc, file, line, args[1].ToString(), def, DEFAULT_SEPERATOR);
}
else
{
RaiseError(file, line, "Include missing structure '" + args[1].ToString() + "' in file '" + includefile + "'");
return;
}
}
// Recursively merge the structures with the current structure
IDictionary newcs = Combine(cs, inc, (cs is ListDictionary));
cs.Clear();
foreach(DictionaryEntry de in newcs) cs.Add(de.Key, de.Value);
}
}
// This parses a function
private void ParseFunction(IDictionary cs, ref string file, ref string data, ref int pos, ref int line, ref string functionname)
{
// We now parse arguments, separated by commas, until we reach the end of the function
ArrayList args = new ArrayList();
object val = null;
while((pos < data.Length) && !cpErrorResult)
{
// Get current character
char c = data[pos++];
// Check for end of function
if(c == ')')
{
// Check what function to run
switch(functionname.Trim().ToLowerInvariant())
{
// Include another file in here
case "include":
FunctionInclude(cs, args, ref file, line);
return;
default:
RaiseError(file, line, ERROR_UNKNOWN_FUNCTION);
return;
}
}
// Check for string opening
else if(c == '\"')
{
// Now parsing a string
val = ParseString(ref file, ref data, ref pos, ref line);
if(cpErrorResult) return;
args.Add(val);
}
// Check for numeric character
else if("0123456789-.&".IndexOf(c.ToString(CultureInfo.InvariantCulture)) > -1)
{
// Go one byte back, because this
// byte is part of the number!
pos--;
// Now parsing a number
val = ParseNumber(ref file, ref data, ref pos, ref line);
if(cpErrorResult) return;
args.Add(val);
}
// Check for new line
else if(c == '\n')
{
// Count the new line
line++;
}
// Check if argument ends
else if(c == ',')
{
// End of argument
}
// Otherwise (if not whitespace) it is a keyword
else if((c != ' ') && (c != '\t'))
{
// Go one byte back, because this
// byte is part of the keyword!
pos--;
// Now parsing a keyword
val = ParseKeyword(ref file, ref data, ref pos, ref line);
if(cpErrorResult) return;
args.Add(val);
}
}
RaiseError(file, line, ERROR_UNEXPECTED_END);
return;
}
// This parses a structure in the given data starting
// from the given pos and line and updates pos and line.
private void InputStructure(IDictionary cs, ref string file, ref string data, ref int pos, ref int line)
{
string key = "";
// Go through all of the data until
// the end or until the struct closes
// or when an arror occurred
while ((pos < data.Length) && !cpErrorResult)
{
// Get current character
char c = data[pos++];
// Check what character this is
switch(c)
{
case '{': // Begin of new struct
// Validate key
if(ValidateKey(cs, key.Trim(), file, line))
{
// Parse this struct and add it
IDictionary cs2;
if(cs is ListDictionary) cs2 = new ListDictionary(); else cs2 = new Hashtable();
InputStructure(cs2, ref file, ref data, ref pos, ref line);
if(cs.Contains(key.Trim()) && (cs[key.Trim()] is IDictionary))
cs[key.Trim()] = Combine((IDictionary)cs[key.Trim()], cs2, (cs is ListDictionary));
else
cs[key.Trim()] = cs2;
key = "";
}
break;
case '}': // End of this struct
// Stop parsing in this struct
return;
case '(': // Function
ParseFunction(cs, ref file, ref data, ref pos, ref line, ref key);
key = "";
break;
case '=': // Assignment
// Validate key
if(ValidateKey(cs, key.Trim(), file, line))
{
// Now parsing assignment
object val = ParseAssignment(ref file, ref data, ref pos, ref line);
if(!cpErrorResult)
{
cs[key.Trim()] = val;
key = "";
}
}
break;
case ';': // Terminator
// Validate key
if(!string.IsNullOrEmpty(key))
{
if(ValidateKey(cs, key.Trim(), file, line))
{
// Add the key with null as value
cs[key.Trim()] = null;
key = "";
}
}
break;
case '\n': // New line
// Count the line
line++;
// Add this to the key as a space.
// Spaces are not allowed, but it will be trimmed
// when its the first or last character.
key += " ";
break;
case '\\': // Possible comment
case '/':
// Backtrack to use previous character also
pos--;
// Check for the line comment //
if(data.Substring(pos, 2) == "//")
{
// Find the next line
int np = data.IndexOf("\n", pos);
// Next line found?
if(np > -1)
{
// Count the line
line++;
// Skip everything on this line
pos = np + 1;
}
else
{
// No end of line
// Skip everything else
pos = data.Length + 1;
}
}
// Check for the block comment /* */
else if(data.Substring(pos, 2) == "/*")
{
// Find the next closing block comment
int np = data.IndexOf("*/", pos);
// Closing block comment found?
if(np > -1)
{
// Count the lines in the block comment
string blockdata = data.Substring(pos, np - pos + 2);
line += (blockdata.Split("\n".ToCharArray()).Length - 1);
// Skip everything in this block
pos = np + 2;
}
else
{
// No end of line
// Skip everything else
pos = data.Length + 1;
}
}
else
{
// No whitespace
pos++;
}
break;
default: // Everything else
// Add character to key
key += c.ToString(CultureInfo.InvariantCulture);
break;
}
}
}
#endregion
#region ================== Writing
// This will create a data structure from the given object
private string OutputStructure(IDictionary cs, int level, string newline, bool whitespace)
{
string leveltabs = "";
string spacing = "";
StringBuilder db = new StringBuilder("");
// Check if this ConfigStruct is not empty
if(cs.Count > 0)
{
// Create whitespace
if(whitespace)
{
for(int i = 0; i < level; i++) leveltabs += "\t";
spacing = " ";
}
// Get enumerator
IDictionaryEnumerator de = cs.GetEnumerator();
// Go for each item
for(int i = 0; i < cs.Count; i++)
{
// Go to next item
de.MoveNext();
// Check if the value is null
if(de.Value == null)
{
// Output the keyword "null"
//db.Append(leveltabs); db.Append(de.Key.ToString()); db.Append(spacing);
//db.Append("="); db.Append(spacing); db.Append("null;"); db.Append(newline);
// Output key only
db.Append(leveltabs); db.Append(de.Key.ToString()); db.Append(";"); db.Append(newline);
}
// Check if the value if of ConfigStruct type
else if(de.Value is IDictionary)
{
// Output recursive structure
if(whitespace) { db.Append(leveltabs); db.Append(newline); }
db.Append(leveltabs); db.Append(de.Key); db.Append(newline);
db.Append(leveltabs); db.Append("{"); db.Append(newline);
db.Append(OutputStructure((IDictionary)de.Value, level + 1, newline, whitespace));
db.Append(leveltabs); db.Append("}"); db.Append(newline);
if(whitespace) { db.Append(leveltabs); db.Append(newline); }
}
// Check if the value is of boolean type
else if(de.Value is bool)
{
// Check value
if((bool)de.Value == true)
{
// Output the keyword "true"
db.Append(leveltabs); db.Append(de.Key.ToString()); db.Append(spacing);
db.Append("="); db.Append(spacing); db.Append("true;"); db.Append(newline);
}
else
{
// Output the keyword "false"
db.Append(leveltabs); db.Append(de.Key.ToString()); db.Append(spacing);
db.Append("="); db.Append(spacing); db.Append("false;"); db.Append(newline);
}
}
// Check if value is of float type
else if(de.Value is float)
{
// Output the value with a postfixed f
db.Append(leveltabs); db.Append(de.Key); db.Append(spacing); db.Append("=");
db.Append(spacing); db.Append(String.Format(CultureInfo.InvariantCulture, "{0}", de.Value)); db.Append("f;"); db.Append(newline);
}
// Check if value is of other numeric type
else if(de.Value.GetType().IsPrimitive)
{
// Output the value unquoted
db.Append(leveltabs); db.Append(de.Key); db.Append(spacing); db.Append("=");
db.Append(spacing); db.Append(String.Format(CultureInfo.InvariantCulture, "{0}", de.Value)); db.Append(";"); db.Append(newline);
}
else
{
// Output the value with quotes and escape characters
db.Append(leveltabs); db.Append(de.Key); db.Append(spacing); db.Append("=");
db.Append(spacing); db.Append("\""); db.Append(EscapedString(de.Value.ToString())); db.Append("\";"); db.Append(newline);
}
}
}
// Return the structure
return db.ToString();
}
#endregion
#region ================== Public Methods
// This clears the last error
public void ClearError()
{
// Clear error
cpErrorResult = false;
cpErrorDescription = "";
cpErrorLine = 0;
cpErrorFile = "";
}
// This creates a new configuration
public void NewConfiguration() { NewConfiguration(false); }
public void NewConfiguration(bool sorted)
{
// Create new configuration
if(sorted) root = new ListDictionary(); else root = new Hashtable();
}
// This checks if a given setting exists (disregards type)
public bool SettingExists(string setting) { return CheckSetting(root, setting, DEFAULT_SEPERATOR); }
public bool SettingExists(string setting, string pathseperator) { return CheckSetting(root, setting, pathseperator); }
// This can give a value of a key specified in a path form
// also, this does not error when the setting does not exist,
// but instead returns the given default value.
public string ReadSetting(string setting, string defaultsetting) { object r = ReadAnySetting(setting, defaultsetting, DEFAULT_SEPERATOR); if(r != null) return r.ToString(); else return null; }
public string ReadSetting(string setting, string defaultsetting, string pathseperator) { object r = ReadAnySetting(setting, defaultsetting, pathseperator); if(r != null) return r.ToString(); else return null; }
public int ReadSetting(string setting, int defaultsetting) { return Convert.ToInt32(ReadAnySetting(setting, defaultsetting, DEFAULT_SEPERATOR), CultureInfo.InvariantCulture); }
public int ReadSetting(string setting, int defaultsetting, string pathseperator) { return Convert.ToInt32(ReadAnySetting(setting, defaultsetting, pathseperator), CultureInfo.InvariantCulture); }
public float ReadSetting(string setting, float defaultsetting) { return Convert.ToSingle(ReadAnySetting(setting, defaultsetting, DEFAULT_SEPERATOR), CultureInfo.InvariantCulture); }
public float ReadSetting(string setting, float defaultsetting, string pathseperator) { return Convert.ToSingle(ReadAnySetting(setting, defaultsetting, pathseperator), CultureInfo.InvariantCulture); }
public short ReadSetting(string setting, short defaultsetting) { return Convert.ToInt16(ReadAnySetting(setting, defaultsetting, DEFAULT_SEPERATOR), CultureInfo.InvariantCulture); }
public short ReadSetting(string setting, short defaultsetting, string pathseperator) { return Convert.ToInt16(ReadAnySetting(setting, defaultsetting, pathseperator), CultureInfo.InvariantCulture); }
public long ReadSetting(string setting, long defaultsetting) { return Convert.ToInt64(ReadAnySetting(setting, defaultsetting, DEFAULT_SEPERATOR), CultureInfo.InvariantCulture); }
public long ReadSetting(string setting, long defaultsetting, string pathseperator) { return Convert.ToInt64(ReadAnySetting(setting, defaultsetting, pathseperator), CultureInfo.InvariantCulture); }
public bool ReadSetting(string setting, bool defaultsetting) { return Convert.ToBoolean(ReadAnySetting(setting, defaultsetting, DEFAULT_SEPERATOR), CultureInfo.InvariantCulture); }
public bool ReadSetting(string setting, bool defaultsetting, string pathseperator) { return Convert.ToBoolean(ReadAnySetting(setting, defaultsetting, pathseperator), CultureInfo.InvariantCulture); }
public byte ReadSetting(string setting, byte defaultsetting) { return Convert.ToByte(ReadAnySetting(setting, defaultsetting, DEFAULT_SEPERATOR), CultureInfo.InvariantCulture); }
public byte ReadSetting(string setting, byte defaultsetting, string pathseperator) { return Convert.ToByte(ReadAnySetting(setting, defaultsetting, pathseperator), CultureInfo.InvariantCulture); }
public IDictionary ReadSetting(string setting, IDictionary defaultsetting) { return (IDictionary)ReadAnySetting(setting, defaultsetting, DEFAULT_SEPERATOR); }
public IDictionary ReadSetting(string setting, IDictionary defaultsetting, string pathseperator) { return (IDictionary)ReadAnySetting(setting, defaultsetting, pathseperator); }
public object ReadSettingObject(string setting, object defaultsetting) { return ReadAnySetting(setting, defaultsetting, DEFAULT_SEPERATOR); }
// This writes a given setting to the configuration.
// Wont change existing structs, but will add them as needed.
// Returns true when written, false when failed.
public bool WriteSetting(string setting, object settingvalue) { return WriteSetting(setting, settingvalue, DEFAULT_SEPERATOR); }
public bool WriteSetting(string setting, object settingvalue, string pathseperator)
{
IDictionary cs = null;
// Split the path in an array
string[] keys = setting.Split(pathseperator.ToCharArray());
string finalkey = keys[keys.Length - 1];
// Get the root item
object item = root;
// Go for each path item
for(int i = 0; i < (keys.Length - 1); i++)
{
// Check if the key is valid
if(ValidateKey(null, keys[i].Trim(), "", -1) == true)
{
// Cast to ConfigStruct
cs = (IDictionary)item;
// Check if the requested item exists
if(cs.Contains(keys[i]) == true)
{
// Check if the requested item is a ConfigStruct
if(cs[keys[i]] is IDictionary)
{
// Set the item to the next item
item = cs[keys[i]];
}
else
{
// Cant proceed with path
return false;
}
}
else
{
// Key not found
// Create it now
IDictionary ncs;
if(root is ListDictionary) ncs = new ListDictionary(); else ncs = new Hashtable();
cs.Add(keys[i], ncs);
// Set the item to the next item
item = cs[keys[i]];
}
}
else
{
// Invalid key in path
return false;
}
}
// Cast to ConfigStruct
cs = (IDictionary)item;
// Check if the key already exists
if(cs.Contains(finalkey) == true)
{
// Update the value
cs[finalkey] = settingvalue;
}
else
{
// Create the key/value pair
cs.Add(finalkey, settingvalue);
}
// Return success
return true;
}
// This removes a given setting from the configuration.
public bool DeleteSetting(string setting) { return DeleteSetting(setting, DEFAULT_SEPERATOR); }
public bool DeleteSetting(string setting, string pathseperator)
{
IDictionary cs = null;
// Split the path in an array
string[] keys = setting.Split(pathseperator.ToCharArray());
string finalkey = keys[keys.Length - 1];
// Get the root item
object item = root;
// Go for each path item
for(int i = 0; i < (keys.Length - 1); i++)
{
// Check if the key is valid
if(ValidateKey(null, keys[i].Trim(), "", -1) == true)
{
// Cast to ConfigStruct
cs = (IDictionary)item;
// Check if the requested item exists
if(cs.Contains(keys[i]) == true)
{
// Check if the requested item is a ConfigStruct
if(cs[keys[i]] is IDictionary)
{
// Set the item to the next item
item = cs[keys[i]];
}
else
{
// Cant proceed with path
return false;
}
}
else
{
// Key not found
// Create it now
IDictionary ncs;
if(root is ListDictionary) ncs = new ListDictionary(); else ncs = new Hashtable();
cs.Add(keys[i], ncs);
// Set the item to the next item
item = cs[keys[i]];
}
}
else
{
// Invalid key in path
return false;
}
}
// Cast to ConfigStruct
cs = (IDictionary)item;
// Arrived at our destination
// Delete the key if the key exists
if(cs.Contains(finalkey) == true)
{
// Key exists, delete it
cs.Remove(finalkey);
// Return success
return true;
}
else
{
// Key not found, return fail
return false;
}
}
// This will save the current configuration to the specified file
public bool SaveConfiguration(string filename) { return SaveConfiguration(filename, "\r\n", true); }
public bool SaveConfiguration(string filename, string newline) { return SaveConfiguration(filename, newline, true); }
public bool SaveConfiguration(string filename, string newline, bool whitespace)
{
// Kill the file if it exists
if(File.Exists(filename) == true) File.Delete(filename);
// Open file stream for writing
FileStream fstream = File.OpenWrite(filename);
// Create output structure and write to file
string data = OutputConfiguration(newline, whitespace);
byte[] baData= Encoding.UTF8.GetBytes(data);
fstream.Write(baData, 0, baData.Length);
fstream.Flush();
fstream.Close();
// Return true when done, false when errors occurred
return !cpErrorResult;
}
// This will output the current configuration as a string
public string OutputConfiguration() { return OutputConfiguration("\r\n", true); }
public string OutputConfiguration(string newline) { return OutputConfiguration(newline, true); }
public string OutputConfiguration(string newline, bool whitespace)
{
// Simply return the configuration structure as string
return OutputStructure(root, 0, newline, whitespace);
}
// This will load a configuration from file
public bool LoadConfiguration(string filename) { return LoadConfiguration(filename, false); }
public bool LoadConfiguration(string filename, bool sorted)
{
// Check if the file is missing
if(File.Exists(filename) == false)
{
throw(new FileNotFoundException("File not found \"" + filename + "\"", filename));
}
else
{
// Load the file contents
FileStream fstream = File.OpenRead(filename);
byte[] fbuffer = new byte[fstream.Length];
fstream.Read(fbuffer, 0, fbuffer.Length);
fstream.Close();
// Convert byte array to string
string data = Encoding.UTF8.GetString(fbuffer);
// Load the configuration from this data
return InputConfiguration(filename, data, sorted);
}
}
// This will load a configuration from string
public bool InputConfiguration(string data) { return InputConfiguration(data, false); }
public bool InputConfiguration(string data, bool sorted) { return InputConfiguration("", data, sorted); }
private bool InputConfiguration(string file, string data, bool sorted)
{
// Remove returns and tabs because the
// parser only uses newline for new lines.
data = data.Replace("\r", "");
data = data.Replace("\t", "");
// Clear errors
ClearError();
// Parse the data to the root structure
if(sorted) root = new ListDictionary(); else root = new Hashtable();
int pos = 0, line = 1;
InputStructure(root, ref file, ref data, ref pos, ref line);
// Return true when done, false when errors occurred
return !cpErrorResult;
}
#endregion
}
}