1010
1111namespace Salaros . Configuration
1212{
13- public class ConfigParser
13+ public partial class ConfigParser
1414 {
1515 private static readonly ILog Logger = LogProvider . GetCurrentClassLogger ( ) ;
1616 protected readonly ConfigSection fileHeader ;
@@ -48,18 +48,19 @@ static ConfigParser()
4848 public ConfigParser ( ConfigParserSettings settings = null )
4949 {
5050 Settings = settings ?? new ConfigParserSettings ( ) ;
51+ NullSection = new NullConfigSection ( this ) ;
5152
5253 fileHeader = new ConfigSection ( ) ;
5354 sections = new Dictionary < string , ConfigSection > ( ) ;
5455 }
5556
5657 /// <inheritdoc />
5758 /// <summary>
58- /// Initializes a new instance of the <see cref="T: Salaros.Configuration.ConfigParser" /> class.
59+ /// Initializes a new instance of the <see cref="Salaros.Configuration.ConfigParser" /> class.
5960 /// </summary>
6061 /// <param name="configFile">The configuration file (may be path or file content).</param>
6162 /// <param name="settings">The settings.</param>
62- /// <exception cref="T: System.ArgumentException">configFilePath</exception>
63+ /// <exception cref="System.ArgumentException">configFilePath</exception>
6364 public ConfigParser ( string configFile , ConfigParserSettings settings = null )
6465 : this ( settings )
6566 {
@@ -111,16 +112,25 @@ public ConfigParser(string configFile, ConfigParserSettings settings = null)
111112 /// <value>
112113 /// The sections.
113114 /// </value>
114- public ReadOnlyCollection < ConfigSection > Sections =>
115- new ReadOnlyCollection < ConfigSection > ( sections . Values . ToArray ( ) ) ;
115+ #if NET40
116+ public ReadOnlyCollection < ConfigSection > Sections => new ReadOnlyCollection < ConfigSection > (
117+ #else
118+ public IReadOnlyCollection < ConfigSection > Sections => new Collection < ConfigSection > (
119+ #endif
120+ sections . Values . ToList ( ) ) ;
116121
117122 /// <summary>
118123 /// Gets configuration file's lines.
119124 /// </summary>
120125 /// <value>The lines.</value>
121- public ReadOnlyCollection < IConfigLine > Lines
122- => new ReadOnlyCollection < IConfigLine > ( fileHeader . Lines . Concat ( sections . Values . SelectMany ( s => s . Lines ) )
123- . ToArray ( ) ) ;
126+ #if NET40
127+ public ReadOnlyCollection < IConfigLine > Lines => new ReadOnlyCollection < IConfigLine > (
128+ #else
129+ public IReadOnlyCollection < IConfigLine > Lines => new Collection < IConfigLine > (
130+ #endif
131+ fileHeader . Lines . Concat ( sections . Values . SelectMany ( s => s . Lines ) ) . ToList ( ) ) ;
132+
133+ public NullConfigSection NullSection { get ; }
124134
125135 #endregion Properties
126136
@@ -143,19 +153,20 @@ public ReadOnlyCollection<IConfigLine> Lines
143153 /// </exception>
144154 internal virtual bool TryGetValue < T > ( string sectionName , string keyName , out T value )
145155 {
146- if ( string . IsNullOrWhiteSpace ( sectionName ) )
156+ if ( sectionName is null )
147157 throw new ArgumentNullException ( nameof ( sectionName ) ) ;
158+
148159 if ( string . IsNullOrWhiteSpace ( keyName ) )
149- throw new ArgumentNullException ( nameof ( keyName ) ) ;
160+ throw new ArgumentException ( "Key name must be a non-empty string." , nameof ( keyName ) ) ;
150161
151162#pragma warning disable IDE0034 // Simplify 'default' expression
152163 value = default ( T ) ;
153164#pragma warning restore IDE0034 // Simplify 'default' expression
154165
155166 if ( ! sections . TryGetValue ( sectionName , out var section ) )
156- return false ;
167+ section = null ;
157168
158- var key = section . Keys . FirstOrDefault ( k => Equals ( keyName , k . Name ) ) ;
169+ var key = ( section ?? fileHeader ? . Section ) . Keys . FirstOrDefault ( k => Equals ( keyName , k . Name ) ) ;
159170 if ( key == null )
160171 return false ;
161172
@@ -173,13 +184,13 @@ internal virtual bool TryGetValue<T>(string sectionName, string keyName, out T v
173184 /// <typeparam name="T">The 1st type parameter.</typeparam>
174185 internal virtual T GetRawValue < T > ( string sectionName , string keyName , T defaultValue )
175186 {
176- if ( string . IsNullOrWhiteSpace ( sectionName ) )
187+ if ( sectionName is null )
177188 throw new ArgumentNullException ( nameof ( sectionName ) ) ;
189+
178190 if ( string . IsNullOrWhiteSpace ( keyName ) )
179- throw new ArgumentNullException ( nameof ( keyName ) ) ;
191+ throw new ArgumentException ( "Key name must be a non-empty string." , nameof ( keyName ) ) ;
180192
181193 var iniKey = new ConfigKeyValue < T > ( keyName , Settings . KeyValueSeparator , defaultValue , - 1 ) ;
182-
183194 if ( ! sections . TryGetValue ( sectionName , out var section ) )
184195 {
185196 section = new ConfigSection ( sectionName , Lines . Any ( ) ? Lines . Max ( l => l . LineNumber ) : 0 ) ;
@@ -188,11 +199,14 @@ internal virtual T GetRawValue<T>(string sectionName, string keyName, T defaultV
188199 sections . Add ( sectionName , section ) ;
189200 }
190201
191- var key = section . Keys . FirstOrDefault ( k => Equals ( keyName , k . Name ) ) ;
202+ var key = ( section ?? fileHeader ? . Section ) . Keys . FirstOrDefault ( k => Equals ( keyName , k . Name ) ) ;
192203 if ( key != null )
193204 return ( T ) key . ValueRaw ;
194205
195- section . AddLine ( iniKey ) ;
206+ if ( section is null && Settings . MultiLineValues . HasFlag ( MultiLineValues . AllowEmptyTopSection ) )
207+ section = fileHeader . Section ;
208+
209+ section ? . AddLine ( iniKey ) ;
196210 return defaultValue ;
197211 }
198212
@@ -421,7 +435,7 @@ public virtual bool ValueIsArray(string sectionName, string keyName)
421435 return values . Any ( ) && string . IsNullOrWhiteSpace ( values . First ( ) ) ;
422436 }
423437
424- #endregion
438+ #endregion
425439
426440 #region SetValue
427441
@@ -540,7 +554,7 @@ public virtual bool SetValue(string sectionName, string keyName, byte[] value)
540554 return SetValue ( sectionName , keyName , EncodeByteArray ( value ) ) ;
541555 }
542556
543- #endregion
557+ #endregion SetValue
544558
545559 #region Indexing
546560
@@ -564,7 +578,7 @@ public ConfigSection this[string sectionName]
564578 }
565579 }
566580
567- #endregion
581+ #endregion Indexing
568582
569583 /// <summary>
570584 /// Save configuration file's content.
@@ -641,7 +655,7 @@ public override string ToString()
641655 ) ;
642656 }
643657
644- #endregion
658+ #endregion Methods
645659
646660 #region Helpers
647661
@@ -819,10 +833,17 @@ private void ReadKeyAndValue(ref ConfigSection currentSection, ref ConfigLine cu
819833 throw new ConfigParserException ( "Unknown key=value situation detected!" , lineNumber ) ;
820834 }
821835
822- if ( append )
823- currentLine . Content = $ "{ currentLine . Content } { Settings . NewLine } { value } ";
824- else
825- currentLine = new ConfigKeyValue < object > ( keyName , separator , value , lineNumber ) ;
836+ try
837+ {
838+ if ( append )
839+ currentLine . Content = $ "{ currentLine . Content } { Settings . NewLine } { value } ";
840+ else
841+ currentLine = new ConfigKeyValue < object > ( keyName , separator , value , lineNumber ) ;
842+ }
843+ catch ( Exception ex )
844+ {
845+ throw new ConfigParserException ( $ "Failed to parse the following line: '{ lineRaw } '", lineNumber , ex ) ;
846+ }
826847 }
827848
828849 /// <summary>
0 commit comments