mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-23 20:32:34 +00:00
0366f13c9a
UDMF map parser should work ~35% faster now. Texture browser form: keyboard focus was not updated when switching between textures using Tab key. "Graphics" folder is now checked when searching for texture patches. Various cosmetic changes here and there.
1506 lines
44 KiB
C#
1506 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.";
|
|
|
|
public const string NUMBERS = "0123456789";
|
|
public const string NUMBERS2 = "0123456789-.&";
|
|
|
|
#endregion
|
|
|
|
#region ================== Variables
|
|
|
|
// Error result
|
|
private bool cpErrorResult = false;
|
|
private string cpErrorDescription = "";
|
|
private int cpErrorLine = 0;
|
|
private string cpErrorFile = "";
|
|
private char[] space = new[]{ ' ' }; //mxd
|
|
private char[] newline = new[] { '\n' }; //mxd
|
|
|
|
// 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;
|
|
|
|
// 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))
|
|
{
|
|
// Cast to ConfigStruct
|
|
cs = (IDictionary)item;
|
|
|
|
// Check if the requested item exists
|
|
if(cs.Contains(keys[i]))
|
|
{
|
|
// 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;
|
|
|
|
// 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))
|
|
{
|
|
// Cast to ConfigStruct
|
|
cs = (IDictionary)item;
|
|
|
|
// Check if the requested item exists
|
|
if(cs.Contains(keys[i]))
|
|
{
|
|
// 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(space) > -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(space) > -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(NUMBERS2.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(NUMBERS.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
|
|
{
|
|
switch (c) { //mxd
|
|
// Check for sequence start
|
|
case '\\':
|
|
// Next character is of escape sequence
|
|
escape = true;
|
|
break;
|
|
|
|
// Check if string ends
|
|
case '\"':
|
|
return val;
|
|
|
|
// Check for new line
|
|
case '\n':
|
|
// Count the new line
|
|
line++;
|
|
break;
|
|
|
|
// Everything else is just part of string
|
|
default:
|
|
// Add to value
|
|
val += c.ToString(CultureInfo.InvariantCulture);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
{
|
|
// Convert to int
|
|
try
|
|
{
|
|
// Convert to value
|
|
int ival = System.Convert.ToInt32(val.Trim(), CultureInfo.InvariantCulture);
|
|
return ival;
|
|
}
|
|
catch(System.OverflowException)
|
|
{
|
|
// Too large for Int32, try Int64
|
|
try
|
|
{
|
|
// Convert to value
|
|
long 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 true;
|
|
case "false": return 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] + "' 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(NUMBERS2.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);
|
|
}
|
|
|
|
|
|
// 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(newline).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)
|
|
{
|
|
// 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))
|
|
{
|
|
// Cast to ConfigStruct
|
|
cs = (IDictionary)item;
|
|
|
|
// Check if the requested item exists
|
|
if(cs.Contains(keys[i]))
|
|
{
|
|
// 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))
|
|
{
|
|
// 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))
|
|
{
|
|
// Cast to ConfigStruct
|
|
cs = (IDictionary)item;
|
|
|
|
// Check if the requested item exists
|
|
if(cs.Contains(keys[i]))
|
|
{
|
|
// 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))
|
|
{
|
|
// 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)) 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))
|
|
{
|
|
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
|
|
}
|
|
}
|