Classes/Class.IniFile.cs
using System; using System.Collections.Generic; using System.IO; using System.Text; namespace IniFileHandler { /*################################################################################ Represents a collection of key-value pairs in an INI section. Properties: KeyValues: Stores key-value pairs in a case-insensitive dictionary. Methods: Add(string key, string value): Adds or updates a key-value pair. ContainsKey(string key): Checks if a key exists. GetValue(string key): Retrieves the value for a key. SetValue(string key, string value): Sets the value for a key. */ /// <summary> /// Represents a collection of key-value pairs in an INI file. /// </summary> public class IniKeyValuePair { /// <summary> /// Gets the dictionary containing the key-value pairs. /// </summary> public Dictionary<string, string> KeyValues { get; private set; } /// <summary> /// Initializes a new instance of the <see cref="IniKeyValuePair"/> class. /// </summary> public IniKeyValuePair() { KeyValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } /// <summary> /// Adds a new key-value pair or updates the value if the key exists. /// </summary> /// <param name="key">The key to add or update.</param> /// <param name="value">The value associated with the key.</param> public void Add(string key, string value) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException("Key cannot be null or empty.", nameof(key)); KeyValues[key] = value; } /// <summary> /// Checks if the specified key exists in the collection. /// </summary> /// <param name="key">The key to check for.</param> /// <returns><c>true</c> if the key exists; otherwise, <c>false</c>.</returns> public bool ContainsKey(string key) { return KeyValues.ContainsKey(key); } /// <summary> /// Gets the value associated with the specified key. /// </summary> /// <param name="key">The key to retrieve the value for.</param> /// <returns>The value associated with the specified key, or <c>null</c> if the key does not exist.</returns> public string GetValue(string key) { return KeyValues.TryGetValue(key, out string value) ? value : null; } /// <summary> /// Sets the value for a specified key. /// </summary> /// <param name="key">The key to set the value for.</param> /// <param name="value">The value to set.</param> public void SetValue(string key, string value) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException("Key cannot be null or empty.", nameof(key)); KeyValues[key] = value; } } //end class IniKeyValuePair /*################################################################################ 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 key-value pairs associated with this section. /// </summary> public IniKeyValuePair KeyValuePair { get; private set; } /// <summary> /// Initializes a new instance of the <see cref="IniSection"/> class with a specified section name. /// </summary> /// <param name="sectionName">The name of the section.</param> public IniSection(string sectionName) { if (string.IsNullOrWhiteSpace(sectionName)) throw new ArgumentException("Section name cannot be null or empty.", nameof(sectionName)); SectionName = sectionName; KeyValuePair = new IniKeyValuePair(); } } //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 <see 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> /// <param 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 name cannot be null or empty.", nameof(section.SectionName)); Sections[section.SectionName] = section; } /// <summary> /// Checks if a section with the specified name exists in the collection. /// </summary> /// <param name="sectionName">The name of the section.</param> /// <returns><c>true</c> 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> /// <param name="sectionName">The name of the section.</param> /// <returns>The <see cref="IniSection"/> with the specified name, or <c>null</c> if the section does not exist.</returns> public IniSection GetSection(string sectionName) { //return Sections.TryGetValue(key, out IniSection section) ? section : null; return Sections.TryGetValue(sectionName, out IniSection section) ? section : null; } /// <summary> /// Tries to get the section with the specified name. /// </summary> /// <param name="sectionName">The name of the section.</param> /// <param name="section">When this method returns, contains the <see cref="IniSection"/> with the specified name, if found; otherwise, <c>null</c>.</param> /// <returns><c>true</c> 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; } /// <summary> /// Gets the key-value pairs outside of any section. /// </summary> public IniKeyValuePair KeyValuePair { get; private set; } private bool _disposed = false; /// <summary> /// Initializes a new instance of the <see cref="IniFile"/> class. /// </summary> public IniFile() { Sections = new IniSections(); KeyValuePair = new IniKeyValuePair(); FilePath = string.Empty; } /// <summary> /// Initializes a new instance of the <see cref="IniFile"/> class with the specified file path. /// </summary> /// <param name="filePath">The path to the INI file.</param> /// <exception 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> /// <param name="filePath">The path to the INI file.</param> /// <exception cref="ArgumentException">Thrown when the file path is null or empty.</exception> /// <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)) { IniKeyValuePair currentSection = KeyValuePair; string line; while ((line = reader.ReadLine()) != null) { if (string.IsNullOrWhiteSpace(line)) continue; if (line.StartsWith("[") && line.EndsWith("]")) { string sectionName = line.Trim('[', ']'); IniSection section = new IniSection(sectionName); currentSection = section.KeyValuePair; Sections.Add(section); } else if (!line.StartsWith(";") && !line.StartsWith("#")) { 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; currentSection.Add(key, value); } else { currentSection.Add(line.Trim(), null); } } } } } /// <summary> /// Saves the current state of the INI file to the associated file path. /// </summary> /// <exception 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> /// <param name="filePath">The file path to save the INI file to.</param> /// <param name="encoding">The encoding to use when saving the file.</param> /// <exception 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)) { foreach (var kvp in KeyValuePair.KeyValues) { writer.WriteLine(kvp.Value == null ? kvp.Key : $"{kvp.Key}={kvp.Value}"); } foreach (var section in Sections.Values) { writer.WriteLine(); writer.WriteLine($"[{section.SectionName}]"); foreach (var kvp in section.KeyValuePair.KeyValues) { writer.WriteLine(kvp.Value == null ? kvp.Key : $"{kvp.Key}={kvp.Value}"); } } } } /// <summary> /// Determines the encoding of the specified INI file based on its name. /// </summary> /// <param name="filePath">The path to the INI file.</param> /// <returns>The 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> /// <param 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> /// <param name="sectionName">The name of the section to check for.</param> /// <returns><c>true</c> if the section exists; otherwise, <c>false</c>.</returns> public bool SectionExists(string sectionName) { return Sections.ContainsKey(sectionName); } /// <summary> /// Sets the value for a specified key in a specified section. /// </summary> /// <param name="sectionName">The name of the section.</param> /// <param name="key">The key to set the value for.</param> /// <param name="value">The value to set.</param> /// <exception 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.KeyValuePair.SetValue(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> /// <param name="sectionName">The name of the section.</param> /// <param name="key">The key to retrieve the value for.</param> /// <returns>The value associated with the specified key, or <c>null</c> if the key does not exist.</returns> public string GetKeyValue(string sectionName, string key) { if (Sections.TryGetValue(sectionName, out IniSection section)) { return section.KeyValuePair.GetValue(key); } return null; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { // Dispose managed resources if any } // Free unmanaged resources if any _disposed = true; } } } //end class IniFile } //end Namespace |