Classes/Class.IniFile.cs
using System; using System.Collections.Generic; using System.IO; using System.Text; namespace IniFileHandler { /// <summary> /// Represents an entry in an INI section that can be either a key-value pair or a simple string. /// </summary> public class IniEntry { /// <summary> /// Gets or sets the key if this is a key-value pair entry. /// </summary> public string Key { get; set; } /// <summary> /// Gets or sets the value. For key-value pairs, this is the value part. /// For simple strings, this contains the entire string. /// </summary> public string Value { get; set; } /// <summary> /// Gets or sets whether this entry is a simple string rather than a key-value pair. /// </summary> public bool IsSimpleString { get; set; } /// <summary> /// Initializes a new instance of the <see cref="IniEntry"/> class as a key-value pair. /// </summary> public IniEntry(string key, string value) { Key = key; Value = value; IsSimpleString = false; } /// <summary> /// Initializes a new instance of the <see cref="IniEntry"/> class as a simple string. /// </summary> public IniEntry(string value) { Value = value; IsSimpleString = true; } } //end class IniEntry /// <summary> /// Represents a collection of entries in an INI section. /// </summary> public class IniEntryCollection { private readonly Dictionary<string, IniEntry> _keyValueEntries; private readonly List<IniEntry> _simpleEntries; /// <summary> /// Gets all entries in this collection. /// </summary> public IEnumerable<IniEntry> AllEntries { get { foreach (var entry in _keyValueEntries.Values) yield return entry; foreach (var entry in _simpleEntries) yield return entry; } } public IniEntryCollection() { _keyValueEntries = new Dictionary<string, IniEntry>(StringComparer.OrdinalIgnoreCase); _simpleEntries = new List<IniEntry>(); } /// <summary> /// Adds a key-value pair entry. /// </summary> public void AddKeyValue(string key, string value) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException("Key cannot be null or empty.", nameof(key)); _keyValueEntries[key] = new IniEntry(key, value); } /// <summary> /// Adds a simple string entry. /// </summary> public void AddSimpleString(string value) { _simpleEntries.Add(new IniEntry(value)); } /// <summary> /// Gets the value for a specific key. /// </summary> public string GetValue(string key) { return _keyValueEntries.TryGetValue(key, out var entry) ? entry.Value : null; } /// <summary> /// Checks if a key exists in the key-value pairs. /// </summary> public bool ContainsKey(string key) { return _keyValueEntries.ContainsKey(key); } /// <summary> /// Gets all simple string entries. /// </summary> public IEnumerable<string> GetSimpleStrings() { return _simpleEntries.ConvertAll(e => e.Value); } } //end class IniEntryCollection /*################################################################################ Represents a section in an INI file containing a name and key-value pairs. Properties: SectionName: The name of the section. KeyValuePair: The key-value pairs within the section. */ /// <summary> /// Represents a section in an INI file. /// </summary> public class IniSection { /// <summary> /// Gets the name of the section. /// </summary> public string SectionName { get; set; } /// <summary> /// Gets the entries associated with this section. /// </summary> public IniEntryCollection Entries { get; private set; } public IniSection(string sectionName) { if (string.IsNullOrWhiteSpace(sectionName)) throw new ArgumentException("Section name cannot be null or empty.", nameof(sectionName)); SectionName = sectionName; Entries = new IniEntryCollection(); } } //end class IniSection /*################################################################################ Represents a collection of INI sections. Manages multiple INI sections. Properties: _sections: The internal dictionary holding section names and their corresponding IniSection objects. Methods: Add(IniSection section): Adds a section to the collection. ContainsKey(string sectionName): Checks if a section exists. GetSection(string sectionName): Retrieves a section. Values: Returns all sections. */ /// <summary> /// Represents a collection of INI sections. /// </summary> public class IniSections { /// <summary> /// Gets the dictionary of sections. /// </summary> public Dictionary<string, IniSection> Sections { get; private set; } /// <summary> /// Initializes a new instance of the cref="IniSections"/> class. /// </summary> public IniSections() { Sections = new Dictionary<string, IniSection>(StringComparer.OrdinalIgnoreCase); } /// <summary> /// Adds a new section to the collection or updates an existing section. /// </summary> /// name="section">The section to add or update.</param> public void Add(IniSection section) { if (section == null || string.IsNullOrWhiteSpace(section.SectionName)) throw new ArgumentException("Section cannot be null and must have a name.", nameof(section)); Sections[section.SectionName] = section; } /// <summary> /// Checks if a section with the specified name exists in the collection. /// </summary> /// name="sectionName">The name of the section.</param> /// if the section exists; otherwise, <c>false</c>.</returns> public bool ContainsKey(string sectionName) { return Sections.ContainsKey(sectionName); } /// <summary> /// Gets the section with the specified name. /// </summary> /// name="sectionName">The name of the section.</param> /// cref="IniSection"/> with the specified name, or if the section does not exist.</returns> public IniSection GetSection(string sectionName) { return Sections.TryGetValue(sectionName, out IniSection section) ? section : null; } /// <summary> /// Tries to get the section with the specified name. /// </summary> /// name="sectionName">The name of the section.</param> /// name="section">When this method returns, contains the cref="IniSection"/> with the specified name, if found; otherwise, <c>null</c>.</param> /// if the section exists; otherwise, <c>false</c>.</returns> public bool TryGetValue(string sectionName, out IniSection section) { return Sections.TryGetValue(sectionName, out section); } /// <summary> /// Gets the collection of all sections. /// </summary> public IEnumerable<IniSection> Values => Sections.Values; } //end class IniSections /*################################################################################ Represents an INI file with sections and key-value pairs. Properties: FilePath: The path to the INI file. Sections: Collection of sections in the file. KeyValuePair: Global key-value pairs not associated with any section. Methods: ReadFile(string filePath): Reads and parses the INI file. SaveFile(): Saves the INI file with the default encoding. SaveFile(string filePath): Saves the INI file with the specified encoding. SaveFile(string filePath, Encoding encoding): Saves the INI file with a specific encoding. DetermineEncoding(string filePath): Determines the encoding based on the file name. AddSection(string sectionName): Adds a section. SectionExists(string sectionName): Checks if a section exists. SetKeyValue(string sectionName, string key, string value): Adds or updates a key-value pair. GetKeyValue(string sectionName, string key): Retrieves the value of a key. */ /// <summary> /// Represents an INI file. /// </summary> public class IniFile : IDisposable { /// <summary> /// Gets or sets the file path of the INI file. /// </summary> public string FilePath { get; private set; } /// <summary> /// Gets the sections in the INI file. /// </summary> public IniSections Sections { get; private set; } public IniEntryCollection GlobalEntries { get; private set; } private bool _disposed = false; /// <summary> /// Initializes a new instance of the cref="IniFile"/> class. /// </summary> public IniFile() { Sections = new IniSections(); GlobalEntries = new IniEntryCollection(); FilePath = string.Empty; } /// <summary> /// Initializes a new instance of the cref="IniFile"/> class with the specified file path. /// </summary> /// name="filePath">The path to the INI file.</param> /// cref="ArgumentException">Thrown when the file path is null or empty.</exception> public IniFile(string filePath) : this() { if (string.IsNullOrWhiteSpace(filePath)) throw new ArgumentException("File path cannot be null or empty.", nameof(filePath)); ReadFile(filePath); } /// <summary> /// Reads the INI file at the specified file path. /// </summary> /// name="filePath">The path to the INI file.</param> /// cref="ArgumentException">Thrown when the file path is null or empty.</exception> /// cref="FileNotFoundException">Thrown when the specified INI file is not found.</exception> public void ReadFile(string filePath) { if (string.IsNullOrWhiteSpace(filePath)) throw new ArgumentException("File path cannot be null or empty.", nameof(filePath)); if (!File.Exists(filePath)) throw new FileNotFoundException("The specified INI file was not found.", filePath); FilePath = filePath; var encoding = DetermineEncoding(filePath); using (var reader = new StreamReader(filePath, encoding)) { IniEntryCollection currentCollection = GlobalEntries; string line; while ((line = reader.ReadLine()) != null) { line = line.Trim(); if (string.IsNullOrWhiteSpace(line)) continue; if (line.StartsWith(";") || line.StartsWith("#")) continue; if (line.StartsWith("[") && line.EndsWith("]")) { string sectionName = line.Trim('[', ']'); IniSection section = new IniSection(sectionName); currentCollection = section.Entries; Sections.Add(section); } else { int separatorIndex = line.IndexOf('='); if (separatorIndex != -1) { string key = line.Substring(0, separatorIndex).Trim(); string value = separatorIndex < line.Length - 1 ? line.Substring(separatorIndex + 1).Trim() : null; currentCollection.AddKeyValue(key, value); } else { currentCollection.AddSimpleString(line); } } } } } /// <summary> /// Saves the current state of the INI file to the associated file path. /// </summary> /// cref="InvalidOperationException">Thrown when there is no associated file path.</exception> public void SaveFile() { if (string.IsNullOrEmpty(FilePath)) throw new InvalidOperationException("This INI record has no associated file."); SaveFile(FilePath, DetermineEncoding(FilePath)); } public void SaveFile(string filePath) { SaveFile(filePath, DetermineEncoding(filePath)); } /// <summary> /// Saves the current state of the INI file to the specified file path. /// </summary> /// name="filePath">The file path to save the INI file to.</param> /// name="encoding">The encoding to use when saving the file.</param> /// cref="ArgumentException">Thrown when the file path is null or empty.</exception> public void SaveFile(string filePath, Encoding encoding) { if (string.IsNullOrWhiteSpace(filePath)) throw new ArgumentException("File path cannot be null or empty.", nameof(filePath)); using (var writer = new StreamWriter(filePath, false, encoding)) { // Write global entries foreach (var entry in GlobalEntries.AllEntries) { WriteEntry(writer, entry); } // Write sections foreach (var section in Sections.Values) { writer.WriteLine(); writer.WriteLine($"[{section.SectionName}]"); foreach (var entry in section.Entries.AllEntries) { WriteEntry(writer, entry); } } } } private void WriteEntry(StreamWriter writer, IniEntry entry) { if (entry.IsSimpleString) { writer.WriteLine(entry.Value); } else { writer.WriteLine(entry.Value == null ? entry.Key : $"{entry.Key}={entry.Value}"); } } /// <summary> /// Determines the encoding of the specified INI file based on its name. /// </summary> /// name="filePath">The path to the INI file.</param> /// encoding of the file.</returns> private Encoding DetermineEncoding(string filePath) { return Path.GetFileName(filePath).Equals("GptTmpl.inf", StringComparison.OrdinalIgnoreCase) ? Encoding.Unicode // UTF-16 LE : Encoding.UTF8; } /// <summary> /// Adds a new section to the INI file. /// </summary> /// name="sectionName">The name of the section to add.</param> public void AddSection(string sectionName) { if (!Sections.ContainsKey(sectionName)) { var section = new IniSection(sectionName); Sections.Add(section); } } /// <summary> /// Checks if a section with the specified name exists in the INI file. /// </summary> /// name="sectionName">The name of the section to check for.</param> /// if the section exists; otherwise, <c>false</c>.</returns> public bool SectionExists(string sectionName) { return Sections.ContainsKey(sectionName); } public void AddSimpleString(string sectionName, string value) { if (Sections.TryGetValue(sectionName, out var section)) { section.Entries.AddSimpleString(value); } else { throw new KeyNotFoundException($"Section '{sectionName}' does not exist."); } } /// <summary> /// Sets the value for a specified key in a specified section. /// </summary> /// name="sectionName">The name of the section.</param> /// name="key">The key to set the value for.</param> /// name="value">The value to set.</param> /// cref="KeyNotFoundException">Thrown when the section does not exist.</exception> public void SetKeyValue(string sectionName, string key, string value) { if (Sections.TryGetValue(sectionName, out var section)) { section.Entries.AddKeyValue(key, value); } else { throw new KeyNotFoundException($"Section '{sectionName}' does not exist."); } } /// <summary> /// Gets the value associated with the specified key in the specified section. /// </summary> /// name="sectionName">The name of the section.</param> /// name="key">The key to retrieve the value for.</param> /// value associated with the specified key, or if the key does not exist.</returns> public string GetKeyValue(string sectionName, string key) { if (Sections.TryGetValue(sectionName, out IniSection section)) { return section.Entries.GetValue(key); } return null; } public IEnumerable<string> GetSimpleStrings(string sectionName) { if (Sections.TryGetValue(sectionName, out IniSection section)) { return section.Entries.GetSimpleStrings(); } return new List<string>(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { // Dispose managed resources if any } _disposed = true; } } } //end class IniFile } //end Namespace IniFileHandler |