/* ANY-LICENSE V1.0 ---------------- You can use these files under any license approved by the Open Source Initiative, preferrably one of the popular licenses, as long as the license you choose is compatible to the dependencies of these files. See http://www.opensource.org/licenses/ for a list of approved licenses. Author: Martin Furter Project: borgnet, borg.ch C# classes and examples. Modified: 2023 A simple config file. */ using System; using System.Drawing; using System.Globalization; using System.IO; using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using BorgCh; using BorgCh.Hacks; namespace BorgCh { public class ConfigurationException : Exception { public ConfigurationException( string message ) : base( message ) { } } public class ConfigFile { public static string COMMON = "common"; public static string INCLUDE = "include"; private static Logger logger = Logger.get_logger( "BorgCh.ConfigFile" ); private string filename; private SortedList data; private int index; public ConfigFile() { filename = ""; index = -1; data = new SortedList(); } public ConfigFile( string configfilename, bool load_includes=true ) { filename = configfilename; index = -1; data = new SortedList(); load_file( filename, false, load_includes ); } private void load_file( string configfilename, bool overwrite, bool load_includes ) { Regex pattern = new Regex( @"^[a-z0-9_-]+$" ); HashSet incsread = new HashSet(); List inclist = new List(); if( !IsPathFullyQualified( configfilename ) ) { configfilename = Path.GetFullPath( configfilename ); } inclist.Add( configfilename ); while( inclist.Count > 0 ) { if( incsread.Contains( inclist[0] ) ) { logger.log( LogLevel.DEBUG3, "Config file '{0}' has already been read.", inclist[0] ); inclist.RemoveAt( 0 ); continue; } String? configpath = Path.GetDirectoryName( inclist[0] ); if( configpath == null ) { configpath = "."; } logger.log( LogLevel.DEBUG3, "Reading config file '{0}'.", inclist[0] ); incsread.Add( inclist[0] ); StreamReader reader = new StreamReader( inclist[0] ); inclist.RemoveAt( 0 ); int incpos = 0; string section = ""; int line_nr = 0; while( true ) { string? line = reader.ReadLine(); if( line == null ) { break; } line = line.Trim(); line_nr++; if( line.Length == 0 || line.StartsWith( "#" ) ) { // line is empty or commented out } else if( line.StartsWith( "[" ) && line.EndsWith( "]" ) ) { section = line.Substring( 1, line.Length-2 ).Trim(); if( !pattern.IsMatch( section ) ) { throw new ConfigurationException( string.Format( "Invalid section name '{0}' " + "(only a-z 0-9 _ and - are allowed)", section ) ); } add_pair( section, null, overwrite ); } else { int i = line.IndexOf( "=" ); if( i < 0 ) { throw new ConfigurationException( string.Format( "Line {0} is invalid", line_nr ) ); } string entry = line.Substring( 0, i ).Trim(); if( !pattern.IsMatch( entry ) ) { throw new ConfigurationException( string.Format( "Invalid section name '{0}' " + "(only a-z 0-9 _ and - are allowed)", section ) ); } string value = line.Substring( i+1 ).Trim(); if( section != COMMON || entry != INCLUDE ) { string key = section + "\r" + entry; add_pair( key, value, overwrite ); } else if( load_includes ) { if( !IsPathFullyQualified( value ) ) { value = Path.Combine( configpath, value ); } inclist.Insert( incpos++, value ); } else { logger.log( LogLevel.DEBUG3, "Ignoring config file '{0}'.", value ); } } } reader.Close(); overwrite = true; } } private bool IsPathFullyQualified( String path ) { /* try { return Path.IsPathFullyQualified( path ); } catch( MissingMethodException ) */ { if( !Path.IsPathRooted( path ) ) { return false; } String? root = Path.GetPathRoot( path ); if( root == null ) { return false; } if( root.StartsWith( "\\" ) ) { return true; } if( root.EndsWith( "\\" ) ) { return true; } return false; } } private void add_pair( string key, string? value, bool overwrite ) { if( overwrite ) { data[key] = value; } else { data.Add( key, value ); } } public string Filename { get { return filename; } } public void merge( string configfilename, bool load_includes=true ) { load_file( configfilename, true, load_includes ); } public bool save( string configfilename ) { try { if( configfilename == null || configfilename == "" ) { configfilename = filename; } else if( !IsPathFullyQualified( configfilename ) ) { configfilename = Path.GetFullPath( configfilename ); } String tmpfilename = configfilename + ".tmp"; StreamWriter writer = new StreamWriter( tmpfilename ); for( int index=0; index= 0 ) { String name = key.Substring( r+1 ); String? value = (string?)data.GetByIndex( index ); if( value == null ) value = ""; writer.WriteLine( name + " = " + value ); } else { writer.WriteLine( "" ); writer.WriteLine( "[" + key + "]" ); } } writer.WriteLine( "" ); writer.Close(); Replace.replace( tmpfilename, configfilename ); // all done filename = configfilename; return true; } catch( Exception ) { return false; } } public bool has_section( string section ) { return data.IndexOfKey( section ) >= 0; } public bool select_section( string section ) { index = data.IndexOfKey( section ); return index >= 0; } public bool next( out string name, out string value ) { index++; if( index < data.Count ) { string key = (string)data.GetKey( index ); int i = key.IndexOf( '\r' ); if( i >= 0 ) { name = key.Substring( i+1 ); //value = (string)data.GetByIndex( index ); Object? obj = data.GetByIndex( index ); if( obj != null ) { value = (string)obj; } else { value = ""; } return true; } } index = -1; name = ""; value = ""; return false; } public string get( string section, string name, string? dflt=null ) { string key = section + "\r" + name; int i = data.IndexOfKey( key ); if( i >= 0 ) { Object? obj = data.GetByIndex( i ); if( obj != null ) { return (string)obj; } } if( dflt != null ) { return dflt; } return ""; } public string get( string section, string name, bool do_throw ) { string value = get( section, name, null ); if( value != null ) { return value; } if( do_throw ) { throw new ConfigurationException( string.Format( "Missing entry '{0}' in section '{1}' of config file '{2}'", name, section, filename ) ); } return ""; } public bool get_bool( string section, string name ) { string s = get( section, name, true ); if( s == "true" ) { return true; } else if( s == "false" ) { return false; } else { throw new ConfigurationException( string.Format( "Invalid value '{3}' (must be 'true' or 'false') " + "for entry '{0}' in section '{1}' of config file '{2}'", name, section, filename, s ) ); } } public bool get_bool( string section, string name, bool dfl ) { string s = get( section, name ); if( s == null ) { return dfl; } else if( s == "true" ) { return true; } else if( s == "false" ) { return false; } else { throw new ConfigurationException( string.Format( "Invalid value '{3}' (must be 'true' or 'false') " + "for entry '{0}' in section '{1}' of config file '{2}'", name, section, filename, s ) ); } } public int get_int( string section, string name, int min, int max ) { string s = get( section, name, true ); int n; if( !Int32.TryParse( get( section, name, true ), out n ) ) { throw new ConfigurationException( string.Format( "Invalid number '{4}' " + "for entry '{0}' in section '{1}' of config file '{2}'", name, section, filename, s ) ); } if( n < min || n > max ) { throw new ConfigurationException( string.Format( "Invalid value '{3}' (valid is between {4:0} and {5:0}) " + "for entry '{0}' in section '{1}' of config file '{2}'", name, section, filename, s, min, max ) ); } return n; } public int get_int_default( string section, string name, int min, int max, int dfl ) { try { dfl = get_int( section, name, min, max ); } catch( Exception ) { } return dfl; } public double get_double( string section, string name, double min, double max ) { string s = get( section, name, true ); double n; if( !Double.TryParse( get( section, name, true ), out n ) ) { throw new ConfigurationException( string.Format( "Invalid number '{3}' " + "for entry '{0}' in section '{1}' of config file '{2}'", name, section, filename, s ) ); } if( n < min || n > max ) { throw new ConfigurationException( string.Format( "Invalid value '{3}' (valid is between {4} and {5}) " + "for entry '{0}' in section '{1}' of config file '{2}'", name, section, filename, s, min, max ) ); } return n; } public double get_double_default( string section, string name, double min, double max, double dfl ) { try { dfl = get_double( section, name, min, max ); } catch( Exception ) { } return dfl; } public Color get_color( string section, string name ) { string s0 = get( section, name, true ); string s1 = s0; int n; if( s1.StartsWith( "0x", StringComparison.CurrentCultureIgnoreCase ) ) { s1 = s1.Substring( 2 ); } if( s1.Length != 6 || !int.TryParse( s1, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out n ) ) { throw new ConfigurationException( string.Format( "Invalid color value '{3}' " + "for entry '{0}' in section '{1}' of config file '{2}'", name, section, filename, s0 ) ); } int r = (n >> 16) & 0xFF; int g = (n >> 8) & 0xFF; int b = (n >> 0) & 0xFF; return Color.FromArgb( r, g, b ); } public void set( String section, String entry, String value ) { if( !has_section( section ) ) { add_pair( section, null, true ); } add_pair( section + "\r" + entry, value, true ); } public void set( String section, String entry, int value ) { set( section, entry, String.Format( "{0}", value ) ); } public void set( String section, String entry, bool value ) { set( section, entry, value ? "true" : "false" ); } } } // namespace BorgCh