2009-04-19 18:07:22 +00:00
#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 = = = = = = = = = = = = = = = = = = Namespaces
using System ;
using System.IO ;
using System.Text ;
using System.Globalization ;
using System.Collections.Generic ;
#endregion
namespace CodeImp.DoomBuilder.IO
{
public sealed class UniversalParser
{
#region = = = = = = = = = = = = = = = = = = Constants
// Path seperator
2015-10-01 21:35:52 +00:00
//public const string DEFAULT_SEPERATOR = ".";
2009-04-19 18:07:22 +00:00
// Allowed characters in a key
2015-10-01 21:35:52 +00:00
private const string KEY_CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789_" ;
2009-04-19 18:07:22 +00:00
// Parse mode constants
private const int PM_NOTHING = 0 ;
private const int PM_ASSIGNMENT = 1 ;
private const int PM_NUMBER = 2 ;
private const int PM_STRING = 3 ;
private const int PM_KEYWORD = 4 ;
// Error strings
private const string ERROR_KEYMISSING = "Missing key name in assignment or scope." ;
private const string ERROR_KEYCHARACTERS = "Invalid characters in key name." ;
2013-07-19 15:30:58 +00:00
//private const string ERROR_ASSIGNINVALID = "Invalid assignment. Missing a previous terminator symbol?";
2009-04-19 18:07:22 +00:00
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_KEYWITHOUTVALUE = "Key has no value assigned." ;
private const string ERROR_KEYWORDUNKNOWN = "Unknown keyword in assignment. Missing a previous terminator symbol?" ;
#endregion
#region = = = = = = = = = = = = = = = = = = Variables
// Error result
2014-02-21 14:42:12 +00:00
private int cpErrorResult ;
2009-04-19 18:07:22 +00:00
private string cpErrorDescription = "" ;
2014-02-21 14:42:12 +00:00
private int cpErrorLine ;
2020-10-10 22:31:55 +00:00
// Warnings
private List < string > warnings = new List < string > ( ) ;
2009-04-19 18:07:22 +00:00
// Configuration root
2014-02-21 14:42:12 +00:00
private UniversalCollection root ;
2009-04-19 18:07:22 +00:00
2013-12-20 09:24:43 +00:00
private const string newline = "\n" ;
private StringBuilder key ; //mxd
private StringBuilder val ; //mxd
private Dictionary < string , UniversalEntry > matches ; //mxd
2009-04-19 18:07:22 +00:00
// Settings
private bool strictchecking = true ;
#endregion
#region = = = = = = = = = = = = = = = = = = Properties
// Properties
public int ErrorResult { get { return cpErrorResult ; } }
public string ErrorDescription { get { return cpErrorDescription ; } }
public int ErrorLine { get { return cpErrorLine ; } }
public UniversalCollection Root { get { return root ; } }
public bool StrictChecking { get { return strictchecking ; } set { strictchecking = value ; } }
2020-10-10 22:31:55 +00:00
public bool HasWarnings { get { return warnings . Count ! = 0 ; } }
public List < string > Warnings { get { return warnings ; } }
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Constructor / Destructor
// Constructor
public UniversalParser ( )
{
// Standard new configuration
NewConfiguration ( ) ;
// We have no destructor
GC . SuppressFinalize ( this ) ;
}
// Constructor to load a file immediately
public UniversalParser ( string filename )
{
// Load configuration from file
LoadConfiguration ( filename ) ;
// We have no destructor
GC . SuppressFinalize ( this ) ;
}
#endregion
#region = = = = = = = = = = = = = = = = = = Private Methods
// This returns a string added with escape characters
2014-12-03 23:15:26 +00:00
private static string EscapedString ( string str )
2009-04-19 18:07:22 +00:00
{
// Replace the \ with \\ first!
str = str . Replace ( "\\" , "\\\\" ) ;
2013-12-20 09:24:43 +00:00
str = str . Replace ( newline , "\\n" ) ;
2009-04-19 18:07:22 +00:00
str = str . Replace ( "\r" , "\\r" ) ;
str = str . Replace ( "\t" , "\\t" ) ;
str = str . Replace ( "\"" , "\\\"" ) ;
// Return result
return str ;
}
// This raises an error
private void RaiseError ( int line , string description )
{
// Raise error
cpErrorResult = 1 ;
cpErrorDescription = description ;
2014-08-04 10:02:42 +00:00
cpErrorLine = line + 1 ; //mxd
2009-04-19 18:07:22 +00:00
}
// This validates a given key and sets
// error properties if key is invalid and errorline > -1
private bool ValidateKey ( string key , int errorline )
{
// Check if key is an empty string
if ( key . Length = = 0 )
{
// ERROR: Missing key name in statement
if ( errorline > - 1 ) RaiseError ( errorline , ERROR_KEYMISSING ) ;
2015-12-19 00:05:34 +00:00
return false ;
2009-04-19 18:07:22 +00:00
}
2015-12-19 00:05:34 +00:00
//Only when strict checking
if ( strictchecking )
2009-04-19 18:07:22 +00:00
{
2013-12-20 09:24:43 +00:00
// Check if all characters are valid
2015-12-19 00:05:34 +00:00
string keylc = key . ToLowerInvariant ( ) ; //mxd. UDMF key names are case-insensitive
foreach ( char c in keylc )
2009-04-19 18:07:22 +00:00
{
2013-12-20 09:24:43 +00:00
if ( KEY_CHARACTERS . IndexOf ( c ) = = - 1 )
2009-04-19 18:07:22 +00:00
{
2013-12-20 09:24:43 +00:00
// ERROR: Invalid characters in key name
if ( errorline > - 1 ) RaiseError ( errorline , ERROR_KEYCHARACTERS ) ;
2015-12-19 00:05:34 +00:00
return false ;
2009-04-19 18:07:22 +00:00
}
}
}
2015-12-19 00:05:34 +00:00
// Key is valid
return true ;
2009-04-19 18:07:22 +00:00
}
// This parses a structure in the given data starting
// from the given pos and line and updates pos and line.
2013-12-20 09:24:43 +00:00
private UniversalCollection InputStructure ( ref string [ ] data , ref int pos , ref int line , bool topLevel )
2009-04-19 18:07:22 +00:00
{
int pm = PM_NOTHING ; // current parse mode
2013-12-20 09:24:43 +00:00
key . Remove ( 0 , key . Length ) ;
val . Remove ( 0 , val . Length ) ;
2009-04-19 18:07:22 +00:00
bool escape = false ; // escape sequence?
bool endofstruct = false ; // true as soon as this level struct ends
UniversalCollection cs = new UniversalCollection ( ) ;
// Go through all of the data until
// the end or until the struct closes
// or when an arror occurred
2015-10-01 21:35:52 +00:00
while ( ( cpErrorResult = = 0 ) & & ( endofstruct = = false ) )
2009-04-19 18:07:22 +00:00
{
// Get current character
2021-01-31 17:11:10 +00:00
if ( pos > data [ line ] . Length - 1 )
2014-10-21 13:35:44 +00:00
{
2013-12-20 09:24:43 +00:00
pos = 0 ;
line + + ;
2021-01-31 17:11:10 +00:00
// Stop if we have reached the end of the data
if ( line = = data . Length )
break ;
2015-10-01 21:35:52 +00:00
if ( string . IsNullOrEmpty ( data [ line ] ) ) continue ; //mxd. Skip empty lines here so correct line number is displayed on errors
2013-12-20 09:24:43 +00:00
}
2015-12-28 15:01:53 +00:00
char c = data [ line ] [ pos ] ; // current data character
2009-04-19 18:07:22 +00:00
// ================ What parse mode are we at?
if ( pm = = PM_NOTHING )
{
// Now check what character this is
switch ( c )
{
case '{' : // Begin of new struct
// Validate key
2015-12-28 15:01:53 +00:00
string s = key . ToString ( ) . Trim ( ) ;
2013-12-20 09:24:43 +00:00
if ( ValidateKey ( s , line ) )
2009-04-19 18:07:22 +00:00
{
// Next character
pos + + ;
// Parse this struct and add it
2013-12-20 09:24:43 +00:00
cs . Add ( new UniversalEntry ( s . ToLowerInvariant ( ) , InputStructure ( ref data , ref pos , ref line , false ) ) ) ;
2009-04-19 18:07:22 +00:00
// Check the last character
pos - - ;
// Reset the key
2013-12-20 09:24:43 +00:00
key . Remove ( 0 , key . Length ) ;
2009-04-19 18:07:22 +00:00
}
// Leave switch
break ;
case '}' : // End of this struct
// Stop parsing in this struct
endofstruct = true ;
// Leave the loop
break ;
case '=' : // Assignment
// Validate key
2013-12-20 09:24:43 +00:00
if ( ValidateKey ( key . ToString ( ) . Trim ( ) , line ) )
2009-04-19 18:07:22 +00:00
{
// Now parsing assignment
pm = PM_ASSIGNMENT ;
}
// Leave switch
break ;
case ';' : // Terminator
// Validate key
2013-12-20 09:24:43 +00:00
if ( ValidateKey ( key . ToString ( ) . Trim ( ) , line ) )
2009-04-19 18:07:22 +00:00
{
// Error: No value
RaiseError ( line , ERROR_KEYWITHOUTVALUE ) ;
}
// Leave switch
break ;
case '\n' : // New line
// Count the line
line + + ;
2013-12-20 09:24:43 +00:00
pos = - 1 ;
2009-04-19 18:07:22 +00:00
// Add this to the key as a space.
// Spaces are not allowed, but it will be trimmed
// when its the first or last character.
2013-12-20 09:24:43 +00:00
key . Append ( " " ) ;
2009-04-19 18:07:22 +00:00
// Leave switch
break ;
case '\\' : // Possible comment
case '/' :
// Check for the line comment //
2013-12-20 09:24:43 +00:00
if ( data [ line ] . Substring ( pos , 2 ) = = "//" )
2009-04-19 18:07:22 +00:00
{
2014-10-21 13:35:44 +00:00
// Skip everything on this line
pos = - 1 ;
2013-12-20 09:24:43 +00:00
// Have next line?
2014-10-21 13:35:44 +00:00
if ( line < data . Length - 1 ) line + + ;
2009-04-19 18:07:22 +00:00
}
2014-10-21 13:35:44 +00:00
// Check for the block comment /* */
2013-12-20 09:24:43 +00:00
else if ( data [ line ] . Substring ( pos , 2 ) = = "/*" )
2009-04-19 18:07:22 +00:00
{
2014-10-21 13:35:44 +00:00
// Block comment closes on the same line?.. (mxd)
2013-12-20 09:24:43 +00:00
int np = data [ line ] . IndexOf ( "*/" , pos ) ;
2015-10-01 21:35:52 +00:00
if ( np > - 1 )
2009-04-19 18:07:22 +00:00
{
pos = np + 1 ;
}
else
{
2014-10-21 13:35:44 +00:00
// Find the next closing block comment
line + + ;
while ( ( np = data [ line ] . IndexOf ( "*/" , 0 ) ) = = - 1 )
{
if ( line = = data . Length - 1 ) break ;
line + + ;
}
// Closing block comment found?
if ( np > - 1 )
{
// Skip everything in this block
pos = np + 1 ;
}
2009-04-19 18:07:22 +00:00
}
}
// Leave switch
break ;
default : // Everything else
2015-10-01 21:35:52 +00:00
if ( ! topLevel & & pos = = 0 )
2014-10-21 13:35:44 +00:00
{
while ( matches . ContainsKey ( data [ line ] ) )
{
2013-12-20 09:24:43 +00:00
cs . Add ( matches [ data [ line ] ] . Key , matches [ data [ line ] ] . Value ) ;
line + + ;
pos = - 1 ;
}
}
2009-04-19 18:07:22 +00:00
// Add character to key
2013-12-20 09:24:43 +00:00
if ( pos ! = - 1 ) key . Append ( c ) ;
2009-04-19 18:07:22 +00:00
// Leave switch
break ;
}
}
// ================ Parsing an assignment
else if ( pm = = PM_ASSIGNMENT )
{
// Check for string opening
if ( c = = '\"' )
{
// Now parsing string
2013-12-20 09:24:43 +00:00
pm = PM_STRING ; //numbers
2009-04-19 18:07:22 +00:00
}
2013-12-20 09:24:43 +00:00
// Check for numeric character numbers
2014-02-21 14:42:12 +00:00
else if ( Configuration . NUMBERS2 . IndexOf ( c ) > - 1 )
2009-04-19 18:07:22 +00:00
{
// Now parsing number
pm = PM_NUMBER ;
// Go one byte back, because this
// byte is part of the number!
pos - - ;
}
// Check for new line
else if ( c = = '\n' )
{
// Count the new line
line + + ;
}
// Check if assignment ends
else if ( c = = ';' )
{
// End of assignment
pm = PM_NOTHING ;
// Remove this if it causes problems
2013-12-20 09:24:43 +00:00
key . Remove ( 0 , key . Length ) ;
val . Remove ( 0 , val . Length ) ;
2009-04-19 18:07:22 +00:00
}
// Otherwise (if not whitespace) it will be a keyword
else if ( ( c ! = ' ' ) & & ( c ! = '\t' ) )
{
// Now parsing a keyword
pm = PM_KEYWORD ;
// Go one byte back, because this
// byte is part of the keyword!
pos - - ;
}
}
// ================ Parsing a number
else if ( pm = = PM_NUMBER )
{
// Check if number ends here
if ( c = = ';' )
{
// Hexadecimal?
2015-12-28 15:01:53 +00:00
string s = val . ToString ( ) ;
2016-02-01 22:04:00 +00:00
if ( ( s . Length > 2 ) & & s . StartsWith ( "0x" , StringComparison . OrdinalIgnoreCase ) )
2009-04-19 18:07:22 +00:00
{
// Convert to int
try
{
// Convert to value
2014-02-21 14:42:12 +00:00
int ival = Convert . ToInt32 ( s . Substring ( 2 ) . Trim ( ) , 16 ) ;
2009-04-19 18:07:22 +00:00
// Add it to struct
2013-12-20 09:24:43 +00:00
UniversalEntry entry = new UniversalEntry ( key . ToString ( ) . Trim ( ) . ToLowerInvariant ( ) , ival ) ;
cs . Add ( entry ) ;
2014-11-25 11:52:01 +00:00
if ( ! matches . ContainsKey ( data [ line ] ) ) matches . Add ( data [ line ] , entry ) ;
2009-04-19 18:07:22 +00:00
}
2014-02-21 14:42:12 +00:00
catch ( OverflowException )
2009-04-19 18:07:22 +00:00
{
// Too large for Int32, try Int64
try
{
// Convert to value
2014-02-21 14:42:12 +00:00
long lval = Convert . ToInt64 ( s . Substring ( 2 ) . Trim ( ) , 16 ) ;
2009-04-19 18:07:22 +00:00
// Add it to struct
2013-12-20 09:24:43 +00:00
UniversalEntry entry = new UniversalEntry ( key . ToString ( ) . Trim ( ) . ToLowerInvariant ( ) , lval ) ;
cs . Add ( entry ) ;
2014-11-25 11:52:01 +00:00
if ( ! matches . ContainsKey ( data [ line ] ) ) matches . Add ( data [ line ] , entry ) ;
2009-04-19 18:07:22 +00:00
}
2014-02-21 14:42:12 +00:00
catch ( OverflowException )
2009-04-19 18:07:22 +00:00
{
// Too large for Int64, return error
RaiseError ( line , ERROR_VALUETOOBIG ) ;
}
2014-02-21 14:42:12 +00:00
catch ( FormatException )
2009-04-19 18:07:22 +00:00
{
// ERROR: Invalid value in assignment
2016-03-08 20:41:06 +00:00
RaiseError ( line , ERROR_VALUEINVALID + "\n\nUnrecognized token: \"" + s . Trim ( ) + "\"" ) ;
2009-04-19 18:07:22 +00:00
}
}
2014-02-21 14:42:12 +00:00
catch ( FormatException )
2009-04-19 18:07:22 +00:00
{
// ERROR: Invalid value in assignment
2016-03-08 20:41:06 +00:00
RaiseError ( line , ERROR_VALUEINVALID + "\n\nUnrecognized token: \"" + s . Trim ( ) + "\"" ) ;
2009-04-19 18:07:22 +00:00
}
}
// Floating point?
2015-10-01 21:35:52 +00:00
//mxd. Can be in scientific notation (like "1E-06")
else if ( s . IndexOf ( '.' ) > - 1 | | s . ToLowerInvariant ( ) . Contains ( "e-" ) )
2009-04-19 18:07:22 +00:00
{
2020-05-22 19:39:18 +00:00
double fval = 0 ;
2009-04-19 18:07:22 +00:00
// Convert to float (remove the f first)
2020-05-22 19:39:18 +00:00
try { fval = Convert . ToDouble ( s . Trim ( ) , CultureInfo . InvariantCulture ) ; }
2014-02-21 14:42:12 +00:00
catch ( FormatException )
2009-04-19 18:07:22 +00:00
{
// ERROR: Invalid value in assignment
2016-03-08 20:41:06 +00:00
RaiseError ( line , ERROR_VALUEINVALID + "\n\nUnrecognized token: \"" + s . Trim ( ) + "\"" ) ;
2009-04-19 18:07:22 +00:00
}
// Add it to struct
2013-12-20 09:24:43 +00:00
UniversalEntry entry = new UniversalEntry ( key . ToString ( ) . Trim ( ) . ToLowerInvariant ( ) , fval ) ;
cs . Add ( entry ) ;
2014-11-25 11:52:01 +00:00
if ( ! matches . ContainsKey ( data [ line ] ) ) matches . Add ( data [ line ] , entry ) ;
2009-04-19 18:07:22 +00:00
}
else
{
// Convert to int
try
{
// Convert to value
2014-02-21 14:42:12 +00:00
int ival = Convert . ToInt32 ( s . Trim ( ) , CultureInfo . InvariantCulture ) ;
2009-04-19 18:07:22 +00:00
// Add it to struct
2013-12-20 09:24:43 +00:00
UniversalEntry entry = new UniversalEntry ( key . ToString ( ) . Trim ( ) . ToLowerInvariant ( ) , ival ) ;
cs . Add ( entry ) ;
2014-11-25 11:52:01 +00:00
if ( ! matches . ContainsKey ( data [ line ] ) ) matches . Add ( data [ line ] , entry ) ;
2009-04-19 18:07:22 +00:00
}
2014-02-21 14:42:12 +00:00
catch ( OverflowException )
2009-04-19 18:07:22 +00:00
{
// Too large for Int32, try Int64
try
{
// Convert to value
2014-10-21 13:35:44 +00:00
long lval = Convert . ToInt64 ( s . Trim ( ) , CultureInfo . InvariantCulture ) ;
2009-04-19 18:07:22 +00:00
// Add it to struct
2013-12-20 09:24:43 +00:00
UniversalEntry entry = new UniversalEntry ( key . ToString ( ) . Trim ( ) . ToLowerInvariant ( ) , lval ) ;
cs . Add ( entry ) ;
2014-11-25 11:52:01 +00:00
if ( ! matches . ContainsKey ( data [ line ] ) ) matches . Add ( data [ line ] , entry ) ;
2009-04-19 18:07:22 +00:00
}
2014-02-21 14:42:12 +00:00
catch ( OverflowException )
2009-04-19 18:07:22 +00:00
{
// Too large for Int64, return error
RaiseError ( line , ERROR_VALUETOOBIG ) ;
}
2014-10-21 13:35:44 +00:00
catch ( FormatException )
2009-04-19 18:07:22 +00:00
{
// ERROR: Invalid value in assignment
2016-03-08 20:41:06 +00:00
RaiseError ( line , ERROR_VALUEINVALID + "\n\nUnrecognized token: \"" + s . Trim ( ) + "\"" ) ;
2009-04-19 18:07:22 +00:00
}
}
2014-02-21 14:42:12 +00:00
catch ( FormatException )
2009-04-19 18:07:22 +00:00
{
// ERROR: Invalid value in assignment
2016-03-08 20:41:06 +00:00
RaiseError ( line , ERROR_VALUEINVALID + "\n\nUnrecognized token: \"" + s . Trim ( ) + "\"" ) ;
2009-04-19 18:07:22 +00:00
}
}
// Reset key and value
2013-12-20 09:24:43 +00:00
key . Remove ( 0 , key . Length ) ;
val . Remove ( 0 , val . Length ) ;
2009-04-19 18:07:22 +00:00
// End of assignment
pm = PM_NOTHING ;
}
// Check for new line
else if ( c = = '\n' )
{
// Count the new line
line + + ;
2013-12-20 09:24:43 +00:00
pos = - 1 ;
2009-04-19 18:07:22 +00:00
}
// Everything else is part of the value
else
{
2013-12-20 09:24:43 +00:00
val . Append ( c ) ;
2009-04-19 18:07:22 +00:00
}
}
// ================ Parsing a string
else if ( pm = = PM_STRING )
{
// Check if in an escape sequence
if ( escape )
{
// What character?
switch ( c )
{
2013-12-20 09:24:43 +00:00
case '\\' : val . Append ( '\\' ) ; break ;
case 'n' : val . Append ( newline ) ; break ;
case '\"' : val . Append ( '\"' ) ; break ;
case 'r' : val . Append ( '\r' ) ; break ;
case 't' : val . Append ( '\t' ) ; break ;
2009-04-19 18:07:22 +00:00
default :
// Is it a number?
2014-02-21 14:42:12 +00:00
if ( Configuration . NUMBERS . IndexOf ( c ) > - 1 )
2009-04-19 18:07:22 +00:00
{
int vv = 0 ;
char vc = '0' ;
// Convert the next 3 characters to a number
2013-12-20 09:24:43 +00:00
string v = data [ line ] . Substring ( pos , 3 ) ;
2014-02-21 14:42:12 +00:00
try { vv = Convert . ToInt32 ( v . Trim ( ) , CultureInfo . InvariantCulture ) ; }
catch ( FormatException )
2009-04-19 18:07:22 +00:00
{
// ERROR: Invalid value in assignment
2016-03-08 20:41:06 +00:00
RaiseError ( line , ERROR_VALUEINVALID + "\n\nUnrecognized token: \"" + v . Trim ( ) + "\"" ) ;
2009-04-19 18:07:22 +00:00
}
// Convert the number to a char
2014-02-21 14:42:12 +00:00
try { vc = Convert . ToChar ( vv , CultureInfo . InvariantCulture ) ; }
catch ( FormatException )
2009-04-19 18:07:22 +00:00
{
// ERROR: Invalid value in assignment
2016-03-08 20:41:06 +00:00
RaiseError ( line , ERROR_VALUEINVALID + "\n\nUnrecognized token: \"" + v . Trim ( ) + "\"" ) ;
2009-04-19 18:07:22 +00:00
}
// Add the char
2013-12-20 09:24:43 +00:00
val . Append ( vc ) ;
2009-04-19 18:07:22 +00:00
}
else
{
// Add the character as it is
2013-12-20 09:24:43 +00:00
val . Append ( c ) ;
2009-04-19 18:07:22 +00:00
}
// 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 = = '\"' )
{
// Add string to struct
2013-12-20 09:24:43 +00:00
UniversalEntry entry = new UniversalEntry ( key . ToString ( ) . Trim ( ) . ToLowerInvariant ( ) , val . ToString ( ) ) ;
cs . Add ( entry ) ;
2014-11-25 11:52:01 +00:00
if ( ! matches . ContainsKey ( data [ line ] ) ) matches . Add ( data [ line ] , entry ) ;
2009-04-19 18:07:22 +00:00
// End of assignment
pm = PM_ASSIGNMENT ;
// Reset key and value
2013-12-20 09:24:43 +00:00
key . Remove ( 0 , key . Length ) ;
val . Remove ( 0 , val . Length ) ;
2009-04-19 18:07:22 +00:00
}
// Check for new line
else if ( c = = '\n' )
{
// Count the new line
line + + ;
2013-12-20 09:24:43 +00:00
pos = - 1 ;
2009-04-19 18:07:22 +00:00
}
// Everything else is just part of string
else
{
// Add to value
2013-12-20 09:24:43 +00:00
val . Append ( c ) ;
2009-04-19 18:07:22 +00:00
}
}
}
// ================ Parsing a keyword
else if ( pm = = PM_KEYWORD )
{
// Check if keyword ends
if ( c = = ';' )
{
// Add to the struct depending on the keyword
2013-12-20 09:24:43 +00:00
switch ( val . ToString ( ) . Trim ( ) . ToLowerInvariant ( ) )
2009-04-19 18:07:22 +00:00
{
case "true" :
// Add boolean true
2013-12-20 09:24:43 +00:00
UniversalEntry t = new UniversalEntry ( key . ToString ( ) . Trim ( ) . ToLowerInvariant ( ) , true ) ;
cs . Add ( t ) ;
2014-11-25 11:52:01 +00:00
if ( ! matches . ContainsKey ( data [ line ] ) ) matches . Add ( data [ line ] , t ) ;
2009-04-19 18:07:22 +00:00
break ;
case "false" :
// Add boolean false
2013-12-20 09:24:43 +00:00
UniversalEntry f = new UniversalEntry ( key . ToString ( ) . Trim ( ) . ToLowerInvariant ( ) , false ) ;
cs . Add ( f ) ;
2014-11-25 11:52:01 +00:00
if ( ! matches . ContainsKey ( data [ line ] ) ) matches . Add ( data [ line ] , f ) ;
2009-04-19 18:07:22 +00:00
break ;
2018-03-24 23:40:31 +00:00
case "nan" :
// Add float value
2020-10-10 22:31:55 +00:00
UniversalEntry nan = new UniversalEntry ( key . ToString ( ) . Trim ( ) . ToLowerInvariant ( ) , double . NaN ) ;
// Do not add NaN, just drop it with a warning
// cs.Add(nan);
2020-10-10 23:07:36 +00:00
warnings . Add ( "UDMF map data line " + ( line + 1 ) + ": value of field " + key . ToString ( ) . Trim ( ) . ToLowerInvariant ( ) + " has a value of NaN (not a number). Field is being dropped permanently." ) ;
2018-03-24 23:40:31 +00:00
if ( ! matches . ContainsKey ( data [ line ] ) ) matches . Add ( data [ line ] , nan ) ;
break ;
default :
2009-04-19 18:07:22 +00:00
// Unknown keyword
2016-03-08 20:41:06 +00:00
RaiseError ( line , ERROR_KEYWORDUNKNOWN + "\n\nUnrecognized token: \"" + val . ToString ( ) . Trim ( ) + "\"" ) ;
2009-04-19 18:07:22 +00:00
break ;
}
// End of assignment
pm = PM_NOTHING ;
// Reset key and value
2013-12-20 09:24:43 +00:00
key . Remove ( 0 , key . Length ) ;
val . Remove ( 0 , val . Length ) ;
2009-04-19 18:07:22 +00:00
}
// Check for new line
else if ( c = = '\n' )
{
// Count the new line
line + + ;
2013-12-20 09:24:43 +00:00
pos = - 1 ;
2009-04-19 18:07:22 +00:00
}
// Everything else is just part of keyword
else
{
// Add to value
2013-12-20 09:24:43 +00:00
val . Append ( c ) ;
2009-04-19 18:07:22 +00:00
}
}
// Next character
pos + + ;
}
// Return the parsed result
return cs ;
}
// This will create a data structure from the given object
private string OutputStructure ( UniversalCollection 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 = " " ;
}
// Go for each item
for ( int i = 0 ; i < cs . Count ; i + + )
{
// Check if the value if of collection type
2014-01-11 10:23:42 +00:00
if ( cs [ i ] . Value is UniversalCollection )
2009-04-19 18:07:22 +00:00
{
2014-01-11 10:23:42 +00:00
UniversalCollection c = ( UniversalCollection ) cs [ i ] . Value ;
2009-06-15 22:44:25 +00:00
2009-04-19 18:07:22 +00:00
// Output recursive structure
if ( whitespace ) { db . Append ( leveltabs ) ; db . Append ( newline ) ; }
2014-01-11 10:23:42 +00:00
db . Append ( leveltabs ) ; db . Append ( cs [ i ] . Key ) ;
2009-06-15 22:44:25 +00:00
if ( ! string . IsNullOrEmpty ( c . Comment ) )
2009-06-15 21:58:34 +00:00
{
if ( whitespace ) db . Append ( "\t" ) ;
2009-06-15 22:44:25 +00:00
db . Append ( "// " + c . Comment ) ;
2009-06-15 21:58:34 +00:00
}
db . Append ( newline ) ;
2009-04-19 18:07:22 +00:00
db . Append ( leveltabs ) ; db . Append ( "{" ) ; db . Append ( newline ) ;
2009-06-15 22:44:25 +00:00
db . Append ( OutputStructure ( c , level + 1 , newline , whitespace ) ) ;
2009-04-19 18:07:22 +00:00
db . Append ( leveltabs ) ; db . Append ( "}" ) ; db . Append ( newline ) ;
2015-05-26 21:08:12 +00:00
//if(whitespace) { db.Append(leveltabs); db.Append(newline); } //mxd. Let's save a few Kbs by using single line breaks...
2009-04-19 18:07:22 +00:00
}
// Check if the value is of boolean type
2014-01-11 10:23:42 +00:00
else if ( cs [ i ] . Value is bool )
2009-04-19 18:07:22 +00:00
{
2014-01-11 10:23:42 +00:00
db . Append ( leveltabs ) ; db . Append ( cs [ i ] . Key ) ; db . Append ( spacing ) ; db . Append ( "=" ) ; db . Append ( spacing ) ;
db . Append ( ( bool ) cs [ i ] . Value ? "true;" : "false;" ) ; db . Append ( newline ) ;
2009-04-19 18:07:22 +00:00
}
// Check if value is of float type
2014-01-11 10:23:42 +00:00
else if ( cs [ i ] . Value is float )
2009-04-19 18:07:22 +00:00
{
// Output the value as float (3 decimals)
2014-01-11 10:23:42 +00:00
float f = ( float ) cs [ i ] . Value ;
db . Append ( leveltabs ) ; db . Append ( cs [ i ] . Key ) ; db . Append ( spacing ) ; db . Append ( "=" ) ;
2009-04-19 18:07:22 +00:00
db . Append ( spacing ) ; db . Append ( f . ToString ( "0.000" , CultureInfo . InvariantCulture ) ) ; db . Append ( ";" ) ; db . Append ( newline ) ;
}
2015-10-01 21:35:52 +00:00
//mxd. Check if value is of double type
else if ( cs [ i ] . Value is double )
{
// Output the value as double (7 decimals)
double d = ( double ) cs [ i ] . Value ;
db . Append ( leveltabs ) ; db . Append ( cs [ i ] . Key ) ; db . Append ( spacing ) ; db . Append ( "=" ) ;
2020-05-22 19:39:18 +00:00
db . Append ( spacing ) ; db . Append ( d . ToString ( "0.0##############" , CultureInfo . InvariantCulture ) ) ; /* db.Append(d.ToString("0.0000000", CultureInfo.InvariantCulture)); */ db . Append ( ";" ) ; db . Append ( newline ) ;
2015-10-01 21:35:52 +00:00
}
2009-04-19 18:07:22 +00:00
// Check if value is of other numeric type
2014-01-11 10:23:42 +00:00
else if ( cs [ i ] . Value . GetType ( ) . IsPrimitive )
2009-04-19 18:07:22 +00:00
{
// Output the value unquoted
2014-01-11 10:23:42 +00:00
db . Append ( leveltabs ) ; db . Append ( cs [ i ] . Key ) ; db . Append ( spacing ) ; db . Append ( "=" ) ;
db . Append ( spacing ) ; db . Append ( String . Format ( CultureInfo . InvariantCulture , "{0}" , cs [ i ] . Value ) ) ; db . Append ( ";" ) ; db . Append ( newline ) ;
2009-04-19 18:07:22 +00:00
}
else
{
// Output the value with quotes and escape characters
2014-01-11 10:23:42 +00:00
db . Append ( leveltabs ) ; db . Append ( cs [ i ] . Key ) ; db . Append ( spacing ) ; db . Append ( "=" ) ;
db . Append ( spacing ) ; db . Append ( "\"" ) ; db . Append ( EscapedString ( cs [ i ] . Value . ToString ( ) ) ) ; db . Append ( "\";" ) ; db . Append ( newline ) ;
2009-04-19 18:07:22 +00:00
}
}
}
// Return the structure
return db . ToString ( ) ;
}
#endregion
#region = = = = = = = = = = = = = = = = = = Public Methods
// This clears the last error
public void ClearError ( )
{
// Clear error
cpErrorResult = 0 ;
cpErrorDescription = "" ;
cpErrorLine = 0 ;
}
// This creates a new configuration
public void NewConfiguration ( )
{
// Create new configuration
root = new UniversalCollection ( ) ;
}
// 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
2013-12-20 09:24:43 +00:00
if ( File . Exists ( filename ) ) File . Delete ( filename ) ;
2009-04-19 18:07:22 +00:00
// 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 . ASCII . GetBytes ( data ) ;
fstream . Write ( baData , 0 , baData . Length ) ;
fstream . Flush ( ) ;
fstream . Close ( ) ;
// Return true when done, false when errors occurred
2014-10-21 13:35:44 +00:00
return cpErrorResult = = 0 ;
2009-04-19 18:07:22 +00:00
}
// 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 )
{
// Check if the file is missing
2013-12-20 09:24:43 +00:00
if ( ! File . Exists ( filename ) )
2009-04-19 18:07:22 +00:00
{
throw ( new FileNotFoundException ( "File not found \"" + filename + "\"" , filename ) ) ;
}
else
{
// Load the file contents
2013-12-20 09:24:43 +00:00
List < string > data = new List < string > ( 100 ) ;
2014-10-21 13:35:44 +00:00
using ( FileStream stream = File . OpenRead ( filename ) )
{
2013-12-20 09:24:43 +00:00
StreamReader reader = new StreamReader ( stream , Encoding . ASCII ) ;
2014-10-21 13:35:44 +00:00
while ( ! reader . EndOfStream )
{
2013-12-20 09:24:43 +00:00
string line = reader . ReadLine ( ) ;
if ( string . IsNullOrEmpty ( line ) ) continue ;
data . Add ( line ) ;
}
}
2009-04-19 18:07:22 +00:00
// Load the configuration from this data
2013-12-20 09:24:43 +00:00
return InputConfiguration ( data . ToArray ( ) ) ;
2009-04-19 18:07:22 +00:00
}
}
// This will load a configuration from string
2013-12-20 09:24:43 +00:00
public bool InputConfiguration ( string [ ] data )
2009-04-19 18:07:22 +00:00
{
// Clear errors
ClearError ( ) ;
// Parse the data to the root structure
int pos = 0 ;
2014-08-04 10:02:42 +00:00
int line = 0 ; //mxd
2014-02-26 14:11:06 +00:00
matches = new Dictionary < string , UniversalEntry > ( StringComparer . Ordinal ) ; //mxd
2013-12-20 09:24:43 +00:00
key = new StringBuilder ( 16 ) ; //mxd
val = new StringBuilder ( 16 ) ; //mxd
root = InputStructure ( ref data , ref pos , ref line , true ) ;
2009-04-19 18:07:22 +00:00
// Return true when done, false when errors occurred
2014-02-26 14:11:06 +00:00
return ( cpErrorResult = = 0 ) ;
2009-04-19 18:07:22 +00:00
}
#endregion
}
}