├── logo.png ├── tests └── IniFile.Tests │ ├── Data │ ├── EmptySections.ini │ ├── PropertyWithoutSection.ini │ ├── UnrecognizedLine.ini │ ├── EmptyProperties.ini │ ├── MultilinePropertyValue.ini │ ├── Formatted.ini │ ├── Unformatted.ini │ ├── Valid.ini │ └── TypedProperties.ini │ ├── Players.ini │ ├── SaveTests.cs │ ├── IniFile.Tests.csproj │ ├── IniConstructionTests.cs │ ├── IniLoadSettingTests.cs │ └── IniFileTests.cs ├── docs ├── object-model.png └── object-model.gliffy ├── .github └── dependabot.yml ├── GitVersion.yml ├── scripts └── publish.bat ├── src └── IniFile │ ├── IniFile.csproj.DotSettings │ ├── Items │ ├── IniItem.cs │ ├── IPaddedItem.cs │ ├── MinorIniItem.cs │ ├── Padding.cs │ ├── CommentPadding.cs │ ├── PropertyPadding.cs │ ├── SectionPadding.cs │ ├── PaddingValue.cs │ ├── MajorIniItem.cs │ └── IniItemFactory.cs │ ├── GlobalSuppressions.cs │ ├── Config │ ├── TypesConfig.cs │ ├── CommentPaddingConfig.cs │ ├── ItemPaddingConfig.cs │ ├── PropertyPaddingConfig.cs │ ├── SectionPaddingConfig.cs │ ├── PaddingConfig.cs │ ├── HashCommentConfig.cs │ └── IniGlobalConfig.cs │ ├── Properties │ └── GlobalSuppressions.cs │ ├── BlankLine.cs │ ├── IniFormatOptions.cs │ ├── Comment.cs │ ├── IniLoadSettings.cs │ ├── Property.cs │ ├── IniFile.csproj │ ├── Section.cs │ ├── ErrorMessages.Designer.cs │ ├── ErrorMessages.resx │ ├── PropertyValue.cs │ └── Ini.cs ├── appveyor-tmp.yml ├── IniFile.sln.licenseheader ├── .vscode ├── launch.json └── tasks.json ├── DEVELOPER.md ├── IniFile.sln ├── .gitignore ├── .editorconfig ├── LICENSE └── README.md /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeevanJames/IniFile/HEAD/logo.png -------------------------------------------------------------------------------- /tests/IniFile.Tests/Data/EmptySections.ini: -------------------------------------------------------------------------------- 1 | [Section1] 2 | 3 | [Section2] 4 | [Section3] 5 | -------------------------------------------------------------------------------- /docs/object-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeevanJames/IniFile/HEAD/docs/object-model.png -------------------------------------------------------------------------------- /tests/IniFile.Tests/Data/PropertyWithoutSection.ini: -------------------------------------------------------------------------------- 1 | Key1 = Value1 2 | 3 | [Section1] 4 | Key2 = Value2 5 | Key3 = Value3 6 | -------------------------------------------------------------------------------- /tests/IniFile.Tests/Data/UnrecognizedLine.ini: -------------------------------------------------------------------------------- 1 | [ValidSection] 2 | Key1 = Value1 3 | Key2 = Value2 4 | ``Invalid stuff 5 | Key3 = Value3 -------------------------------------------------------------------------------- /tests/IniFile.Tests/Data/EmptyProperties.ini: -------------------------------------------------------------------------------- 1 | [Section1] 2 | Key1 = 3 | Key2= 4 | 5 | [Section2] 6 | Key3 = Value3 7 | Key4 = 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "23:30" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /tests/IniFile.Tests/Data/MultilinePropertyValue.ini: -------------------------------------------------------------------------------- 1 | [Section] 2 | Singleline = This is a single line 3 | Multiline = < 2 | Latest -------------------------------------------------------------------------------- /tests/IniFile.Tests/Data/Valid.ini: -------------------------------------------------------------------------------- 1 | ; This is a valid INI file with a mix of all types of objects 2 | 3 | [LoggingService] 4 | 5 | ; Base URL for the logging service 6 | BaseUri = https://logging-service.local/api 7 | 8 | ; Port number 9 | Port = 8080 10 | 11 | ; Logging details 12 | ; Governs how logs are written 13 | 14 | [Logging] 15 | 16 | ; Minimum level to log 17 | MinimumLevel = Info 18 | 19 | ; File name to write logs to 20 | File = log.txt 21 | 22 | ; Maximum size of log file 23 | ; before a new log file is used 24 | 25 | MaxSize = 16MB 26 | -------------------------------------------------------------------------------- /appveyor-tmp.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - choco install gitversion.portable -pre -y 3 | 4 | assembly_info: 5 | patch: false 6 | 7 | before_build: 8 | - nuget restore 9 | - ps: gitversion /l console /output buildserver /updateAssemblyInfo 10 | 11 | build: 12 | project: 13 | 14 | after_build: 15 | - cmd: ECHO nuget pack \.nuspec -version "%GitVersion_NuGetVersion%" -prop "target=%CONFIGURATION%" 16 | - cmd: nuget pack \.nuspec -version "%GitVersion_NuGetVersion%" -prop "target=%CONFIGURATION%" 17 | - cmd: appveyor PushArtifact ".%GitVersion_NuGetVersion%.nupkg" -------------------------------------------------------------------------------- /tests/IniFile.Tests/Data/TypedProperties.ini: -------------------------------------------------------------------------------- 1 | [TypedData] 2 | String = This is a string 3 | SByte = -10 4 | Byte = 25 5 | Short = -400 6 | UShort = 700 7 | Int = -30000 8 | UInt = 50000 9 | Long = -1000000 10 | ULong = 20000000 11 | Float = 12.54443 12 | Double = 6566123.22 13 | Decimal = 1000.50 14 | DateTime = 2018-12-08 15 | 16 | [False Booleans] 17 | Bool1 = 0 18 | Bool2 = f 19 | Bool3 = n 20 | Bool4 = off 21 | Bool5 = no 22 | Bool6 = disabled 23 | Bool7 = false 24 | 25 | [True Booleans] 26 | Bool1 = 1 27 | Bool2 = t 28 | Bool3 = y 29 | Bool4 = on 30 | Bool5 = yes 31 | Bool6 = enabled 32 | Bool7 = true 33 | 34 | [Enum] 35 | Enum1 = Saturday 36 | -------------------------------------------------------------------------------- /IniFile.sln.licenseheader: -------------------------------------------------------------------------------- 1 | extensions: .cs 2 | #region --- License & Copyright Notice --- 3 | /* 4 | IniFile Library for .NET 5 | Copyright (c) 2018-2021 Jeevan James 6 | All rights reserved. 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | */ 20 | #endregion 21 | -------------------------------------------------------------------------------- /src/IniFile/Items/IniItem.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | namespace IniFile.Items 22 | { 23 | /// 24 | /// Base class for all INI items, such as sections, properties, comments and blank lines. 25 | /// 26 | public abstract class IniItem 27 | { 28 | } 29 | } -------------------------------------------------------------------------------- /src/IniFile/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | // This file is used by Code Analysis to maintain SuppressMessage 22 | // attributes that are applied to this project. 23 | // Project-level suppressions either have no target or are given 24 | // a specific target and scoped to a namespace, type, member, etc. 25 | 26 | -------------------------------------------------------------------------------- /src/IniFile/Items/IPaddedItem.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | namespace IniFile.Items 22 | { 23 | /// 24 | /// Represents a INI item that has padding. 25 | /// 26 | /// The type of padding for the item. 27 | public interface IPaddedItem 28 | where TPadding : Padding 29 | { 30 | TPadding Padding { get; } 31 | } 32 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/tests/IniFile.Tests/bin/Debug/netcoreapp2.1/IniFile.Tests.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/tests/IniFile.Tests", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /src/IniFile/Config/TypesConfig.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System.Globalization; 22 | 23 | namespace IniFile.Config 24 | { 25 | public sealed class TypesConfig 26 | { 27 | public string TrueString { get; set; } = "1"; 28 | 29 | public string FalseString { get; set; } = "0"; 30 | 31 | public string DateFormat { get; set; } = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; 32 | 33 | public string TimeFormat { get; set; } = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern; 34 | } 35 | } -------------------------------------------------------------------------------- /src/IniFile/Config/CommentPaddingConfig.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using IniFile.Items; 22 | 23 | namespace IniFile.Config 24 | { 25 | /// 26 | /// Configuration for padding defaults in comments. 27 | /// 28 | public sealed class CommentPaddingConfig : ItemPaddingConfig 29 | { 30 | /// 31 | /// The default padding between the comment character (; or #) and the start of the 32 | /// comment text. Defaults to 1. 33 | /// 34 | public PaddingValue Inside { get; set; } 35 | } 36 | } -------------------------------------------------------------------------------- /src/IniFile/Properties/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System.Diagnostics.CodeAnalysis; 22 | 23 | [assembly: SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix", Justification = "")] 24 | [assembly: SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "")] 25 | [assembly: SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "", Scope = "type", Target = "~T:IniFile.Property")] 26 | 27 | [assembly: SuppressMessage("Minor Code Smell", "S4136:Method overloads should be grouped together", Justification = "")] 28 | -------------------------------------------------------------------------------- /src/IniFile/Config/ItemPaddingConfig.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using IniFile.Items; 22 | 23 | namespace IniFile.Config 24 | { 25 | /// 26 | /// Base class for the common padding configurations for sections, properties and comments. 27 | /// 28 | public abstract class ItemPaddingConfig 29 | { 30 | /// 31 | /// The default padding on the left margin. Default is 0. 32 | /// 33 | public PaddingValue Left { get; set; } 34 | 35 | /// 36 | /// The default padding on the right margin. Default is 0. 37 | /// 38 | public PaddingValue Right { get; set; } 39 | } 40 | } -------------------------------------------------------------------------------- /tests/IniFile.Tests/SaveTests.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System; 22 | using System.IO; 23 | 24 | using Moq; 25 | 26 | using Shouldly; 27 | 28 | using Xunit; 29 | 30 | namespace IniFile.Tests 31 | { 32 | public sealed class SaveTests 33 | { 34 | private readonly Ini _ini; 35 | 36 | public SaveTests() 37 | { 38 | _ini = new Ini(); 39 | } 40 | 41 | [Fact] 42 | public void SaveTo_Throws_on_non_writable_stream() 43 | { 44 | var stream = new Mock(); 45 | stream.Setup(s => s.CanWrite).Returns(false); 46 | 47 | Should.Throw(() => _ini.SaveTo(stream.Object)); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/tests/IniFile.Tests/IniFile.Tests.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/tests/IniFile.Tests/IniFile.Tests.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/tests/IniFile.Tests/IniFile.Tests.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /src/IniFile/Items/MinorIniItem.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | namespace IniFile.Items 22 | { 23 | /// 24 | /// Base class for the minor INI items, namely the and 25 | /// . 26 | /// 27 | public abstract class MinorIniItem : IniItem 28 | { 29 | /// 30 | /// Initializes a new instance of the class. 31 | /// 32 | protected MinorIniItem() 33 | { 34 | } 35 | 36 | public static implicit operator MinorIniItem(string str) 37 | { 38 | if (string.IsNullOrEmpty(str) || str.Trim().Length == 0) 39 | return new BlankLine(); 40 | return new Comment(str); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/IniFile/Config/PropertyPaddingConfig.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using IniFile.Items; 22 | 23 | namespace IniFile.Config 24 | { 25 | /// 26 | /// Configuration for padding defaults in properties. 27 | /// 28 | public sealed class PropertyPaddingConfig : ItemPaddingConfig 29 | { 30 | /// 31 | /// The default padding between the end of the property name and the equals symbol. 32 | /// Default is 1. 33 | /// 34 | public PaddingValue InsideLeft { get; set; } 35 | 36 | /// 37 | /// The default padding between the equals symbol and the start of the property value. 38 | /// Default is 1. 39 | /// 40 | public PaddingValue InsideRight { get; set; } 41 | } 42 | } -------------------------------------------------------------------------------- /src/IniFile/Config/SectionPaddingConfig.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using IniFile.Items; 22 | 23 | namespace IniFile.Config 24 | { 25 | /// 26 | /// Configuration for padding defaults in sections. 27 | /// 28 | public sealed class SectionPaddingConfig : ItemPaddingConfig 29 | { 30 | /// 31 | /// The default padding between the opening square bracket of a section and the start of 32 | /// the section name. Default is 0. 33 | /// 34 | public PaddingValue InsideLeft { get; set; } 35 | 36 | /// 37 | /// The default padding between the end of the section name and the closing square 38 | /// bracket of a section. Default is 0. 39 | /// 40 | public PaddingValue InsideRight { get; set; } 41 | } 42 | } -------------------------------------------------------------------------------- /src/IniFile/BlankLine.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System.Diagnostics; 22 | 23 | using IniFile.Items; 24 | 25 | namespace IniFile 26 | { 27 | /// 28 | /// Represents a blank line object in an INI. 29 | /// 30 | [DebuggerDisplay("----------")] 31 | public sealed class BlankLine : MinorIniItem, IPaddedItem 32 | { 33 | /// 34 | /// Initializes a new instance of the class. 35 | /// 36 | public BlankLine() 37 | { 38 | } 39 | 40 | /// 41 | /// Padding details of this . 42 | /// 43 | public Padding Padding { get; } = new(); 44 | 45 | /// 46 | public override string ToString() 47 | { 48 | return Padding.Left.ToString(); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/IniFile/Config/PaddingConfig.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | namespace IniFile.Config 22 | { 23 | /// 24 | /// Configuration for padding defaults for sections, properties and comments. 25 | /// 26 | public sealed class PaddingConfig 27 | { 28 | /// 29 | /// Configuration for padding defaults of sections. 30 | /// 31 | public SectionPaddingConfig Section { get; } = new SectionPaddingConfig(); 32 | 33 | /// 34 | /// Configuration for padding defaults of properties. 35 | /// 36 | public PropertyPaddingConfig Property { get; } = new PropertyPaddingConfig(); 37 | 38 | /// 39 | /// Configuration for padding defaults of comments. 40 | /// 41 | public CommentPaddingConfig Comment { get; } = new CommentPaddingConfig(); 42 | } 43 | } -------------------------------------------------------------------------------- /src/IniFile/Items/Padding.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | namespace IniFile.Items 22 | { 23 | /// 24 | /// 25 | /// Represents details of the padding of an INI item, such as a , 26 | /// , and a . 27 | /// 28 | /// 29 | /// Padding is used to represent the exact formatting of the INI item in the object model. 30 | /// 31 | /// 32 | public class Padding 33 | { 34 | /// 35 | /// The amount of space to the left of the INI item. 36 | /// 37 | public PaddingValue Left { get; set; } 38 | 39 | /// 40 | /// Resets the padding values to the defaults. 41 | /// 42 | public virtual void Reset() 43 | { 44 | Left = 0; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/IniFile/Config/HashCommentConfig.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | namespace IniFile.Config 22 | { 23 | /// 24 | /// Configuration on whether the hash symbol (#) can be used for comments. 25 | /// 26 | public sealed class HashCommentConfig 27 | { 28 | /// 29 | /// Gets or sets whether comments prefixed with a hash (#) symbol are allowed, in addition 30 | /// to the standard semi-colon ones. 31 | /// 32 | public bool Allow { get; set; } 33 | 34 | /// 35 | /// 36 | /// Gets or sets whether comments are to be prefixed with a hash (#) symbol by default, 37 | /// instead of the standard semi-colon. 38 | /// 39 | /// Note that semi-colon prefixed comments will still be allowed. 40 | /// 41 | public bool IsDefault { get; set; } 42 | } 43 | } -------------------------------------------------------------------------------- /DEVELOPER.md: -------------------------------------------------------------------------------- 1 | # Developer notes 2 | 3 | ## To-do items 4 | 5 | - [x] Allow defaults for padding to be specified. 6 | - [ ] Add doc comments for all padding configs 7 | - [ ] Implement a INI specification from https://github.com/SemaiCZE/inicpp/wiki/INI-format-specification 8 | - [x] Allow for identifier pattern for sections and property names 9 | - [x] Typed property values for booleans, integral numbers and floating numbers 10 | - [x] Typed property values for enums 11 | - [ ] Typed property values for lists 12 | - [ ] Support space before and after names with `\` 13 | - [ ] Handle cross-platform newline characters when formatting. Enable the `Ensure_format_is_retained` test when done. 14 | - [ ] Document object model with an image in the README.md file. 15 | - [ ] Add more tests. 16 | 17 | - [ ] Add support for strongly-typed INI classes. Perhaps in a different package. 18 | 19 | The following code: 20 | ```cs 21 | public class PersonalDetails 22 | { 23 | [Required] 24 | public string FullName { get; set; } 25 | public int Age { get; set; } 26 | [Date("yyyy-MM-dd")] 27 | public DateTime? Dob { get; set; } 28 | } 29 | 30 | public class Employment 31 | { 32 | [Required] 33 | public string Company { get; set; } 34 | public string Position { get; set; } 35 | } 36 | 37 | public class EmployeeConfig 38 | { 39 | [Name("Personal-Details"), Required] 40 | public PersonalDetails Personal { get; set; } 41 | 42 | public Employment Employment { get; set; } 43 | } 44 | ``` 45 | 46 | maps to the following INI definition: 47 | ```ini 48 | [Personal-Details] 49 | FullName = Barry Allen 50 | Age = 32 51 | Dob = 1986-06-14 52 | 53 | [Employment] 54 | Company = Justice League 55 | Position = Founding member 56 | ``` 57 | -------------------------------------------------------------------------------- /src/IniFile/IniFormatOptions.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | namespace IniFile 22 | { 23 | /// 24 | /// Options for formatting the contents of the INI. 25 | /// 26 | public sealed class IniFormatOptions 27 | { 28 | /// 29 | /// Inserts blank lines between sections, if there isn't any. 30 | /// 31 | public bool EnsureBlankLineBetweenSections { get; set; } 32 | 33 | /// 34 | /// Inserts blank lines between properties, if there isn't any. 35 | /// 36 | public bool EnsureBlankLineBetweenProperties { get; set; } 37 | 38 | /// 39 | /// Removes successive blank lines 40 | /// 41 | public bool RemoveSuccessiveBlankLines { get; set; } 42 | 43 | /// 44 | /// Default formatting options for INI content. 45 | /// 46 | public static readonly IniFormatOptions Default = new(); 47 | } 48 | } -------------------------------------------------------------------------------- /tests/IniFile.Tests/IniFile.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | false 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | all 31 | runtime; build; native; contentfiles; analyzers 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/IniFile/Items/CommentPadding.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | namespace IniFile.Items 22 | { 23 | /// 24 | /// Represents the padding details for an INI comment. 25 | /// 26 | public sealed class CommentPadding : Padding 27 | { 28 | /// 29 | /// Initializes a new instance of the class. 30 | /// 31 | public CommentPadding() 32 | { 33 | SetDefaults(); 34 | } 35 | 36 | /// 37 | /// The amount of space to the right of the comment text. 38 | /// 39 | public PaddingValue Right { get; set; } 40 | 41 | /// 42 | /// Amount of space between the comment ; and the start of the comment text. 43 | /// 44 | public PaddingValue Inside { get; set; } 45 | 46 | /// 47 | public override void Reset() 48 | { 49 | SetDefaults(); 50 | } 51 | 52 | private void SetDefaults() 53 | { 54 | Left = Ini.Config.Padding.Comment.Left; 55 | Inside = Ini.Config.Padding.Comment.Inside; 56 | Right = Ini.Config.Padding.Comment.Right; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/IniFile/Items/PropertyPadding.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | namespace IniFile.Items 22 | { 23 | /// 24 | /// Represents the padding details for a INI property. 25 | /// 26 | public sealed class PropertyPadding : Padding 27 | { 28 | /// 29 | /// Initializes a new instance of the class. 30 | /// 31 | public PropertyPadding() 32 | { 33 | SetDefaults(); 34 | } 35 | 36 | /// 37 | /// The amount of space to the right of the property. 38 | /// 39 | public PaddingValue Right { get; set; } 40 | 41 | /// 42 | /// The amount of space between the property name and the equal symbol. 43 | /// 44 | public PaddingValue InsideLeft { get; set; } 45 | 46 | /// 47 | /// The amount of space between the equal symbol and the property value. 48 | /// 49 | public PaddingValue InsideRight { get; set; } 50 | 51 | /// 52 | public override void Reset() 53 | { 54 | SetDefaults(); 55 | } 56 | 57 | private void SetDefaults() 58 | { 59 | Left = Ini.Config.Padding.Property.Left; 60 | InsideLeft = Ini.Config.Padding.Property.InsideLeft; 61 | InsideRight = Ini.Config.Padding.Property.InsideRight; 62 | Right = Ini.Config.Padding.Property.Right; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/IniFile/Items/SectionPadding.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | namespace IniFile.Items 22 | { 23 | /// 24 | /// Represents the padding details for an INI section. 25 | /// 26 | public sealed class SectionPadding : Padding 27 | { 28 | /// 29 | /// Initializes a new instance of the class. 30 | /// 31 | public SectionPadding() 32 | { 33 | SetDefaults(); 34 | } 35 | 36 | /// 37 | /// The amount of space to the right of the section. 38 | /// 39 | public PaddingValue Right { get; set; } 40 | 41 | /// 42 | /// The amount of space between the left brace of the section and the section text. 43 | /// 44 | public PaddingValue InsideLeft { get; set; } 45 | 46 | /// 47 | /// The amount of space between the right brace of the section and the section text. 48 | /// 49 | public PaddingValue InsideRight { get; set; } 50 | 51 | /// 52 | public override void Reset() 53 | { 54 | SetDefaults(); 55 | } 56 | 57 | private void SetDefaults() 58 | { 59 | Left = Ini.Config.Padding.Section.Left; 60 | InsideLeft = Ini.Config.Padding.Section.InsideLeft; 61 | InsideRight = Ini.Config.Padding.Section.InsideRight; 62 | Right = Ini.Config.Padding.Section.Right; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /tests/IniFile.Tests/IniConstructionTests.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System; 22 | using System.IO; 23 | 24 | using Moq; 25 | 26 | using Shouldly; 27 | 28 | using Xunit; 29 | 30 | namespace IniFile.Tests 31 | { 32 | public sealed class IniConstructionTests 33 | { 34 | [Fact] 35 | public void Ctor_creates_instance() 36 | { 37 | var ini = new Ini(); 38 | 39 | ini.ShouldNotBeNull(); 40 | } 41 | 42 | [Fact] 43 | public void Ctor_with_null_settings() 44 | { 45 | var ini = new Ini(null); 46 | 47 | ini.ShouldNotBeNull(); 48 | } 49 | 50 | [Fact] 51 | public void Ctor_with_non_null_settings() 52 | { 53 | var ini = new Ini(IniLoadSettings.Default); 54 | 55 | ini.ShouldNotBeNull(); 56 | } 57 | 58 | [Fact] 59 | public void Ctor_with_null_stream_will_throw() 60 | { 61 | Should.Throw(() => new Ini((Stream) null)); 62 | } 63 | 64 | [Fact] 65 | public void Ctor_with_non_readable_stream_will_throw() 66 | { 67 | var mock = new Mock(); 68 | mock.Setup(s => s.CanRead).Returns(false); 69 | 70 | Should.Throw(() => new Ini(mock.Object)); 71 | } 72 | 73 | [Fact] 74 | public void Ctor_with_null_textreader_should_throw() 75 | { 76 | Should.Throw(() => new Ini((TextReader)null)); 77 | } 78 | 79 | [Fact] 80 | public void Load_with_null_content_string() 81 | { 82 | Should.Throw(() => Ini.Load(null)); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/IniFile/Comment.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System.Diagnostics; 22 | 23 | using IniFile.Items; 24 | 25 | namespace IniFile 26 | { 27 | /// 28 | /// Represents a comment object in an INI. 29 | /// 30 | public sealed class Comment : MinorIniItem, IPaddedItem 31 | { 32 | [DebuggerBrowsable(DebuggerBrowsableState.Never)] 33 | private string _text; 34 | 35 | /// 36 | /// Initializes a new instance of the class with an empty comment. 37 | /// 38 | public Comment() : this(string.Empty) 39 | { 40 | } 41 | 42 | /// 43 | /// Initializes a new instance of the class with the specified text. 44 | /// 45 | /// The comment text. 46 | public Comment(string text) 47 | { 48 | Text = text; 49 | CommentChar = Ini.Config.HashForComments.Allow && Ini.Config.HashForComments.IsDefault 50 | ? CommentChar.Hash 51 | : CommentChar.Semicolon; 52 | } 53 | 54 | /// 55 | /// The comment text. 56 | /// 57 | public string Text 58 | { 59 | get => _text; 60 | set => _text = value ?? string.Empty; 61 | } 62 | 63 | public CommentChar CommentChar { get; set; } 64 | 65 | /// 66 | /// Padding details of this . 67 | /// 68 | public CommentPadding Padding { get; } = new(); 69 | 70 | /// 71 | public override string ToString() 72 | { 73 | char commentChar = CommentChar == CommentChar.Semicolon ? ';' : '#'; 74 | return $"{Padding.Left}{commentChar}{Padding.Inside}{Text}{Padding.Right}"; 75 | } 76 | } 77 | 78 | public enum CommentChar 79 | { 80 | Semicolon, 81 | Hash 82 | } 83 | } -------------------------------------------------------------------------------- /src/IniFile/Items/PaddingValue.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System; 22 | using System.Diagnostics; 23 | 24 | namespace IniFile.Items 25 | { 26 | /// 27 | /// Represents the size of padding - the number of spaces in the padding. 28 | /// 29 | [DebuggerDisplay("{Value}")] 30 | public readonly struct PaddingValue : IEquatable 31 | { 32 | internal PaddingValue(int value) 33 | { 34 | if (value < 0) 35 | throw new ArgumentOutOfRangeException(nameof(value)); 36 | Value = value; 37 | } 38 | 39 | /// 40 | /// The number of spaces in the padding. 41 | /// 42 | public int Value { get; } 43 | 44 | public override bool Equals(object obj) 45 | { 46 | return obj is PaddingValue && Equals((PaddingValue)obj); 47 | } 48 | 49 | public bool Equals(PaddingValue other) 50 | { 51 | return Value == other.Value; 52 | } 53 | 54 | public override int GetHashCode() 55 | { 56 | return -1937169414 + Value.GetHashCode(); 57 | } 58 | 59 | /// 60 | /// Converts this to a padded string. 61 | /// 62 | /// The padded string containing the equal number of spaces. 63 | public override string ToString() => 64 | Value == 0 ? string.Empty : new string(' ', Value); 65 | 66 | public static bool operator ==(PaddingValue value1, PaddingValue value2) 67 | { 68 | return value1.Equals(value2); 69 | } 70 | 71 | public static bool operator !=(PaddingValue value1, PaddingValue value2) 72 | { 73 | return !(value1 == value2); 74 | } 75 | 76 | public static implicit operator PaddingValue(int value) => 77 | new PaddingValue(value); 78 | 79 | public static implicit operator int(PaddingValue paddingValue) => 80 | paddingValue.Value; 81 | } 82 | } -------------------------------------------------------------------------------- /src/IniFile/IniLoadSettings.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System; 22 | using System.Diagnostics; 23 | using System.Text; 24 | 25 | namespace IniFile 26 | { 27 | /// 28 | /// The settings to use when loading INI data from files, streams and text readers. 29 | /// 30 | [DebuggerDisplay("Encoding: {Encoding.EncodingName}; Detect: {DetectEncoding}; Case sensitive: {CaseSensitive}")] 31 | public sealed class IniLoadSettings 32 | { 33 | [DebuggerBrowsable(DebuggerBrowsableState.Never)] 34 | private Encoding _encoding; 35 | 36 | /// 37 | /// Initializes a new instance of the class. 38 | /// 39 | public IniLoadSettings() 40 | { 41 | Encoding = Encoding.UTF8; 42 | } 43 | 44 | /// 45 | /// Gets or sets the character encoding to use when loading or saving INI data. 46 | /// 47 | public Encoding Encoding 48 | { 49 | get => _encoding; 50 | set => _encoding = value ?? throw new ArgumentNullException(nameof(value)); 51 | } 52 | 53 | /// 54 | /// Gets or sets whether to automatically detect the character encoding when loading 55 | /// INI data. 56 | /// 57 | public bool DetectEncoding { get; set; } 58 | 59 | /// 60 | /// Gets or sets whether to consider section and property key names as case sensitive, 61 | /// when searching for them by name. 62 | /// 63 | public bool CaseSensitive { get; set; } 64 | 65 | /// 66 | /// Gets or sets whether to ignore blank lines when loading the INI data. 67 | /// 68 | public bool IgnoreBlankLines { get; set; } 69 | 70 | /// 71 | /// Gets or sets whether to ignore comments when loading the INI data. 72 | /// 73 | public bool IgnoreComments { get; set; } 74 | 75 | /// 76 | /// Default settings to use to load INI data. 77 | /// 78 | public static IniLoadSettings Default { get; }= new(); 79 | 80 | /// 81 | /// Preconfigured settings to use if you plan to only read from the INI file without making 82 | /// changes and saving. 83 | /// 84 | public static IniLoadSettings ReadOnly { get; } = new() 85 | { 86 | IgnoreBlankLines = true, 87 | IgnoreComments = true 88 | }; 89 | } 90 | } -------------------------------------------------------------------------------- /src/IniFile/Property.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System.Text; 22 | using System.Text.RegularExpressions; 23 | 24 | using IniFile.Items; 25 | 26 | namespace IniFile 27 | { 28 | /// 29 | /// Represents a property object in an INI. 30 | /// 31 | public sealed class Property : MajorIniItem, IPaddedItem 32 | { 33 | /// 34 | /// Initializes a new instance of the class. 35 | /// 36 | /// The name of the property. 37 | /// The value of the property. 38 | /// 39 | /// Collection of strings that represent the comments and blank lines of the property. 40 | /// If the string is null, an empty string or a whitespace string, then a 41 | /// object is created, otherwise a is created. 42 | /// 43 | /// 44 | public Property(string name, PropertyValue value, params string[] items) : base(name, items) 45 | { 46 | Value = value; 47 | } 48 | 49 | /// 50 | /// The value of the property. 51 | /// 52 | public PropertyValue Value { get; set; } 53 | 54 | /// 55 | /// The symbol used to denote the end of a multi-line value. 56 | /// 57 | public string MultiLineEndOfText { get; set; } = "EOT"; 58 | 59 | /// 60 | /// Padding details of this . 61 | /// 62 | public PropertyPadding Padding { get; } = new(); 63 | 64 | /// 65 | public override string ToString() 66 | { 67 | string[] lines = NewLinePattern.Split(Value.ToString()); 68 | if (lines.Length == 1) 69 | return $"{Padding.Left}{Name}{Padding.InsideLeft}={Padding.InsideRight}{Value}{Padding.Right}"; 70 | 71 | string eot = string.IsNullOrEmpty(MultiLineEndOfText) || MultiLineEndOfText.Trim().Length == 0 72 | ? "EOT" : MultiLineEndOfText.Trim(); 73 | var sb = new StringBuilder(); 74 | sb.AppendLine($"{Padding.Left}{Name}{Padding.InsideLeft}={Padding.InsideRight}<<{eot}"); 75 | foreach (string line in lines) 76 | sb.AppendLine(line); 77 | sb.AppendLine(eot); 78 | return sb.ToString(); 79 | } 80 | 81 | private static readonly Regex NewLinePattern = new(@"\r\n|\r|\n"); 82 | } 83 | } -------------------------------------------------------------------------------- /tests/IniFile.Tests/IniLoadSettingTests.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System.Linq; 22 | using Shouldly; 23 | using Xunit; 24 | using Xunit.DataAttributes; 25 | 26 | namespace IniFile.Tests 27 | { 28 | public sealed class IniLoadSettingTests 29 | { 30 | private readonly IniLoadSettings _settings; 31 | 32 | public IniLoadSettingTests() 33 | { 34 | _settings = new IniLoadSettings(); 35 | } 36 | 37 | [Theory] 38 | [EmbeddedResourceContent("IniFile.Tests.Data.Valid.ini")] 39 | public void Load_Should_ignore_blank_lines(string iniContent) 40 | { 41 | _settings.IgnoreBlankLines = true; 42 | 43 | Ini ini = Ini.Load(iniContent, _settings); 44 | 45 | foreach (Section section in ini) 46 | { 47 | section.Items.OfType().Count().ShouldBe(0); 48 | section.Items.OfType().Count().ShouldBeGreaterThan(0); 49 | foreach (Property property in section) 50 | { 51 | property.Items.OfType().Count().ShouldBe(0); 52 | property.Items.OfType().Count().ShouldBeGreaterThan(0); 53 | } 54 | } 55 | } 56 | 57 | [Theory] 58 | [EmbeddedResourceContent("IniFile.Tests.Data.Valid.ini")] 59 | public void Load_Should_ignore_comments(string iniContent) 60 | { 61 | _settings.IgnoreComments = true; 62 | 63 | Ini ini = Ini.Load(iniContent, _settings); 64 | 65 | foreach (Section section in ini) 66 | { 67 | section.Items.OfType().Count().ShouldBe(0); 68 | section.Items.OfType().Count().ShouldBeGreaterThan(0); 69 | foreach (Property property in section) 70 | { 71 | property.Items.OfType().Count().ShouldBe(0); 72 | property.Items.OfType().Count().ShouldBeGreaterThan(0); 73 | } 74 | } 75 | } 76 | 77 | [Theory] 78 | [EmbeddedResourceContent("IniFile.Tests.Data.Valid.ini")] 79 | public void Load_Should_ignore_blank_lines_and_comments(string iniContent) 80 | { 81 | _settings.IgnoreBlankLines = true; 82 | _settings.IgnoreComments = true; 83 | 84 | Ini ini = Ini.Load(iniContent, _settings); 85 | 86 | foreach (Section section in ini) 87 | { 88 | section.Items.Count.ShouldBe(0); 89 | foreach (Property property in section) 90 | property.Items.Count.ShouldBe(0); 91 | } 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/IniFile/IniFile.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;netstandard1.3;net35;net40;net45 5 | 1.6.0 6 | Jeevan James 7 | Jeevan James 8 | .NET library to open, modify and save .INI files 9 | Copyright (c) 2018-2021 Jeevan James 10 | Apache-2.0 11 | https://github.com/JeevanJames/IniFile 12 | https://github.com/JeevanJames/IniFile.git 13 | Git 14 | 15 | Release notes are at https://github.com/JeevanJames/IniFile/releases 16 | IniFile.NET 17 | IniFile.NET 18 | logo.png 19 | README.md 20 | latest 21 | en-US 22 | true 23 | 24 | Properties\IniFile.xml 25 | 1701;1702;1591 26 | 27 | 28 | 29 | NETSTANDARD2_0 30 | 31 | 32 | 33 | NETSTANDARD1_3 34 | 35 | 36 | 37 | 38 | 39 | all 40 | runtime; build; native; contentfiles; analyzers; buildtransitive 41 | 42 | 43 | all 44 | runtime; build; native; contentfiles; analyzers; buildtransitive 45 | 46 | 47 | all 48 | runtime; build; native; contentfiles; analyzers; buildtransitive 49 | 50 | 51 | 52 | 53 | 54 | True 55 | True 56 | ErrorMessages.resx 57 | 58 | 59 | 60 | 61 | 62 | ResXFileCodeGenerator 63 | ErrorMessages.Designer.cs 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /IniFile.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31205.134 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1DC9EAB0-885D-4551-BD75-48C72B52575C}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IniFile", "src\IniFile\IniFile.csproj", "{4ACAD1EF-8AA7-4067-8CFF-B47B8BB3B037}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{16C13081-A386-4B86-8452-BDF388DCC343}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IniFile.Tests", "tests\IniFile.Tests\IniFile.Tests.csproj", "{B762A622-D963-4EB0-A065-710F625617CD}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7113BA74-EBBD-40C8-B6C1-48D73935851C}" 15 | ProjectSection(SolutionItems) = preProject 16 | .editorconfig = .editorconfig 17 | .gitignore = .gitignore 18 | DEVELOPER.md = DEVELOPER.md 19 | IniFile.sln.licenseheader = IniFile.sln.licenseheader 20 | LICENSE = LICENSE 21 | logo.png = logo.png 22 | README.md = README.md 23 | EndProjectSection 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Debug|x64 = Debug|x64 29 | Debug|x86 = Debug|x86 30 | Release|Any CPU = Release|Any CPU 31 | Release|x64 = Release|x64 32 | Release|x86 = Release|x86 33 | EndGlobalSection 34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 35 | {4ACAD1EF-8AA7-4067-8CFF-B47B8BB3B037}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {4ACAD1EF-8AA7-4067-8CFF-B47B8BB3B037}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {4ACAD1EF-8AA7-4067-8CFF-B47B8BB3B037}.Debug|x64.ActiveCfg = Debug|Any CPU 38 | {4ACAD1EF-8AA7-4067-8CFF-B47B8BB3B037}.Debug|x64.Build.0 = Debug|Any CPU 39 | {4ACAD1EF-8AA7-4067-8CFF-B47B8BB3B037}.Debug|x86.ActiveCfg = Debug|Any CPU 40 | {4ACAD1EF-8AA7-4067-8CFF-B47B8BB3B037}.Debug|x86.Build.0 = Debug|Any CPU 41 | {4ACAD1EF-8AA7-4067-8CFF-B47B8BB3B037}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {4ACAD1EF-8AA7-4067-8CFF-B47B8BB3B037}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {4ACAD1EF-8AA7-4067-8CFF-B47B8BB3B037}.Release|x64.ActiveCfg = Release|Any CPU 44 | {4ACAD1EF-8AA7-4067-8CFF-B47B8BB3B037}.Release|x64.Build.0 = Release|Any CPU 45 | {4ACAD1EF-8AA7-4067-8CFF-B47B8BB3B037}.Release|x86.ActiveCfg = Release|Any CPU 46 | {4ACAD1EF-8AA7-4067-8CFF-B47B8BB3B037}.Release|x86.Build.0 = Release|Any CPU 47 | {B762A622-D963-4EB0-A065-710F625617CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {B762A622-D963-4EB0-A065-710F625617CD}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {B762A622-D963-4EB0-A065-710F625617CD}.Debug|x64.ActiveCfg = Debug|Any CPU 50 | {B762A622-D963-4EB0-A065-710F625617CD}.Debug|x64.Build.0 = Debug|Any CPU 51 | {B762A622-D963-4EB0-A065-710F625617CD}.Debug|x86.ActiveCfg = Debug|Any CPU 52 | {B762A622-D963-4EB0-A065-710F625617CD}.Debug|x86.Build.0 = Debug|Any CPU 53 | {B762A622-D963-4EB0-A065-710F625617CD}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {B762A622-D963-4EB0-A065-710F625617CD}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {B762A622-D963-4EB0-A065-710F625617CD}.Release|x64.ActiveCfg = Release|Any CPU 56 | {B762A622-D963-4EB0-A065-710F625617CD}.Release|x64.Build.0 = Release|Any CPU 57 | {B762A622-D963-4EB0-A065-710F625617CD}.Release|x86.ActiveCfg = Release|Any CPU 58 | {B762A622-D963-4EB0-A065-710F625617CD}.Release|x86.Build.0 = Release|Any CPU 59 | EndGlobalSection 60 | GlobalSection(SolutionProperties) = preSolution 61 | HideSolutionNode = FALSE 62 | EndGlobalSection 63 | GlobalSection(NestedProjects) = preSolution 64 | {4ACAD1EF-8AA7-4067-8CFF-B47B8BB3B037} = {1DC9EAB0-885D-4551-BD75-48C72B52575C} 65 | {B762A622-D963-4EB0-A065-710F625617CD} = {16C13081-A386-4B86-8452-BDF388DCC343} 66 | EndGlobalSection 67 | GlobalSection(ExtensibilityGlobals) = postSolution 68 | SolutionGuid = {53F15EC7-29D0-4CB3-AB32-9B073F9BFBE2} 69 | EndGlobalSection 70 | EndGlobal 71 | -------------------------------------------------------------------------------- /src/IniFile/Items/MajorIniItem.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System; 22 | using System.Collections.Generic; 23 | using System.Diagnostics; 24 | using System.Linq; 25 | 26 | namespace IniFile.Items 27 | { 28 | /// 29 | /// Base class for the major INI items, namely the and 30 | /// . 31 | /// 32 | /// Each major INI item can have zero or more comments and blank lines. 33 | /// 34 | public abstract class MajorIniItem : IniItem 35 | { 36 | [DebuggerBrowsable(DebuggerBrowsableState.Never)] 37 | private string _name; 38 | 39 | /// 40 | /// Initializes a new instance of the class. 41 | /// 42 | /// The unique name of the INI item. 43 | /// 44 | /// Collection of strings that represent the comments and blank lines of the INI item. 45 | /// If the string is null, an empty string or a whitespace string, then a 46 | /// object is created, otherwise a is created. 47 | /// 48 | /// Thrown if the specified name is null. 49 | protected MajorIniItem(string name, params string[] items) 50 | { 51 | if (items == null) 52 | throw new ArgumentNullException(nameof(items)); 53 | Name = name; 54 | foreach (string item in items) 55 | { 56 | if (string.IsNullOrEmpty(item) || item.Trim().Length == 0) 57 | Items.Add(new BlankLine {Padding = {Left = (item ?? string.Empty).Length}}); 58 | else 59 | Items.Add(new Comment(item)); 60 | } 61 | } 62 | 63 | /// 64 | /// The unique name of the or the . 65 | /// 66 | public string Name 67 | { 68 | get => _name; 69 | set 70 | { 71 | if (string.IsNullOrEmpty(value) || value.Trim().Length == 0) 72 | throw new ArgumentException(ErrorMessages.InvalidMajorItemName, nameof(value)); 73 | _name = value.Trim(); 74 | } 75 | } 76 | 77 | /// 78 | /// The collection of comments and blank lines that are associated with this INI item. 79 | /// 80 | public IList Items { get; } = new List(); 81 | 82 | /// 83 | /// Returns just the objects belonging to this INI item. 84 | /// 85 | public IEnumerable Comments => Items.OfType(); 86 | 87 | /// 88 | /// Shortcut method to adding a class to the 89 | /// property. 90 | /// 91 | /// The comment text. 92 | /// The newly-added comment. 93 | public Comment AddComment(string text) 94 | { 95 | var comment = new Comment(text); 96 | Items.Add(comment); 97 | return comment; 98 | } 99 | 100 | /// 101 | /// Shortcut method to adding a class to the 102 | /// property. 103 | /// 104 | /// The newly-adding blank line. 105 | public BlankLine AddBlankLine() 106 | { 107 | var blankLine = new BlankLine(); 108 | Items.Add(blankLine); 109 | return blankLine; 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /src/IniFile/Items/IniItemFactory.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System; 22 | using System.Globalization; 23 | using System.Text.RegularExpressions; 24 | 25 | namespace IniFile.Items 26 | { 27 | internal static class IniItemFactory 28 | { 29 | /// 30 | /// Creates the correct object from the given line. If the line 31 | /// does not match any INI item object, an exception is thrown. 32 | /// 33 | /// A line from the INI file. 34 | /// The object that matches the line format. 35 | internal static IniItem CreateItem(string line) 36 | { 37 | IniItem item = TryCreateProperty(line) 38 | ?? TryCreateSection(line) 39 | ?? TryCreateComment(line) 40 | ?? TryCreateBlankLine(line); 41 | 42 | if (item != null) 43 | return item; 44 | 45 | throw new FormatException(string.Format(CultureInfo.CurrentCulture, ErrorMessages.UnrecognizedLine, line)); 46 | } 47 | 48 | private static IniItem TryCreateSection(string line) 49 | { 50 | Match match = SectionPattern.Match(line); 51 | if (!match.Success) 52 | return null; 53 | var section = new Section(match.Groups[3].Value); 54 | section.Padding.Left = match.Groups[1].Length; 55 | section.Padding.InsideLeft = match.Groups[2].Length; 56 | section.Padding.InsideRight = match.Groups[4].Length; 57 | section.Padding.Right = match.Groups[5].Length; 58 | return section; 59 | } 60 | 61 | private static readonly Regex SectionPattern = new Regex(@"^(\s*)\[(\s*)([\w\.\$\:][\w_~\-\.\:\s]*)(\s*)\](\s*)$"); 62 | 63 | private static IniItem TryCreateProperty(string line) 64 | { 65 | Match match = PropertyPattern.Match(line); 66 | if (!match.Success) 67 | return null; 68 | var property = new Property(match.Groups[2].Value, match.Groups[5].Value); 69 | property.Padding.Left = match.Groups[1].Length; 70 | property.Padding.InsideLeft = match.Groups[3].Length; 71 | property.Padding.InsideRight = match.Groups[4].Length; 72 | property.Padding.Right = match.Groups[6].Length; 73 | return property; 74 | } 75 | 76 | private static readonly Regex PropertyPattern = new Regex(@"^(\s*)([\w\.\$\:][\w_~\-\.\:\s]*)(\s*)=(\s*)(.*)(\s*)$"); 77 | 78 | private static IniItem TryCreateComment(string line) 79 | { 80 | Match match = Ini.Config.HashForComments.Allow 81 | ? CommentWithHashPattern.Match(line) : CommentPattern.Match(line); 82 | if (!match.Success) 83 | return null; 84 | var comment = new Comment(match.Groups[4].Value) 85 | { 86 | CommentChar = match.Groups[2].Value == ";" ? CommentChar.Semicolon : CommentChar.Hash 87 | }; 88 | comment.Padding.Left = match.Groups[1].Length; 89 | comment.Padding.Inside = match.Groups[3].Length; 90 | comment.Padding.Right = match.Groups[5].Length; 91 | return comment; 92 | } 93 | 94 | private static readonly Regex CommentPattern = new Regex(@"^(\s*)(;)(\s*)(.+)(\s*)$"); 95 | 96 | private static readonly Regex CommentWithHashPattern = new Regex(@"^(\s*)([;|#])(\s*)(.+)(\s*)$"); 97 | 98 | private static IniItem TryCreateBlankLine(string line) 99 | { 100 | if (line.Trim().Length == 0) 101 | { 102 | var blankLine = new BlankLine(); 103 | blankLine.Padding.Left = line.Length; 104 | return blankLine; 105 | } 106 | return null; 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /src/IniFile/Section.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System.Collections; 22 | using System.Collections.Generic; 23 | using System.Diagnostics; 24 | 25 | using IniFile.Items; 26 | 27 | namespace IniFile 28 | { 29 | /// 30 | /// Represents a section object in an INI. 31 | /// A section is also a collection of instances. 32 | /// 33 | public sealed partial class Section : MajorIniItem, IPaddedItem 34 | { 35 | /// 36 | /// Initializes a new instance of the class. 37 | /// 38 | /// The name of the section. 39 | /// 40 | /// Collection of strings that represent the comments and blank lines of the section. 41 | /// If the string is null, an empty string or a whitespace string, then a 42 | /// object is created, otherwise a is created. 43 | /// 44 | /// 45 | public Section(string name, params string[] items) : base(name, items) 46 | { 47 | } 48 | 49 | /// 50 | /// Padding details of this . 51 | /// 52 | public SectionPadding Padding { get; } = new(); 53 | 54 | /// 55 | public override string ToString() 56 | { 57 | return $"{Padding.Left}[{Padding.InsideLeft}{Name}{Padding.InsideRight}]{Padding.Right}"; 58 | } 59 | } 60 | 61 | public sealed partial class Section : IList 62 | { 63 | [DebuggerBrowsable(DebuggerBrowsableState.Never)] 64 | private readonly List _properties = new(); 65 | 66 | public Property this[int index] 67 | { 68 | get => _properties[index]; 69 | set => _properties[index] = value; 70 | } 71 | 72 | /// 73 | /// Gets or sets the value of the property given its name. 74 | /// 75 | /// The name of the property to get or set. 76 | /// The value of the property. 77 | public PropertyValue this[string name] 78 | { 79 | get 80 | { 81 | Property property = _properties.Find(p => p.Name == name); 82 | return property?.Value ?? PropertyValue.Empty; 83 | } 84 | set 85 | { 86 | Property property = _properties.Find(p => p.Name == name); 87 | if (property == null) 88 | { 89 | property = new Property(name, value); 90 | _properties.Add(property); 91 | } 92 | else 93 | property.Value = value; 94 | } 95 | } 96 | 97 | /// 98 | /// The number of properties in this section. 99 | /// 100 | public int Count => _properties.Count; 101 | 102 | public bool IsReadOnly => false; 103 | 104 | public void Add(Property item) 105 | { 106 | _properties.Add(item); 107 | } 108 | 109 | public void Clear() 110 | { 111 | _properties.Clear(); 112 | } 113 | 114 | public bool Contains(Property item) 115 | { 116 | return _properties.Contains(item); 117 | } 118 | 119 | public void CopyTo(Property[] array, int arrayIndex) 120 | { 121 | _properties.CopyTo(array, arrayIndex); 122 | } 123 | 124 | public IEnumerator GetEnumerator() 125 | { 126 | return _properties.GetEnumerator(); 127 | } 128 | 129 | public int IndexOf(Property item) 130 | { 131 | return _properties.IndexOf(item); 132 | } 133 | 134 | public void Insert(int index, Property item) 135 | { 136 | _properties.Insert(index, item); 137 | } 138 | 139 | public bool Remove(Property item) 140 | { 141 | return _properties.Remove(item); 142 | } 143 | 144 | public void RemoveAt(int index) 145 | { 146 | _properties.RemoveAt(index); 147 | } 148 | 149 | IEnumerator IEnumerable.GetEnumerator() 150 | { 151 | return _properties.GetEnumerator(); 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /src/IniFile/Config/IniGlobalConfig.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System; 22 | using System.Globalization; 23 | 24 | using IniFile.Items; 25 | 26 | namespace IniFile.Config 27 | { 28 | /// 29 | /// Global configuration for reading and writing to INI files. 30 | /// 31 | public sealed class IniGlobalConfig 32 | { 33 | /// 34 | /// Configuration for using hash symbols (#) for comments in INI files. 35 | /// 36 | public HashCommentConfig HashForComments { get; } = new HashCommentConfig(); 37 | 38 | /// 39 | /// Configuration for padding defaults for sections, properties and comments in INI files. 40 | /// 41 | public PaddingConfig Padding { get; } = new PaddingConfig(); 42 | 43 | /// 44 | /// Configuration for handling certain data types when reading and writing INI properties. 45 | /// 46 | public TypesConfig Types { get; } = new TypesConfig(); 47 | 48 | /// 49 | /// Allows the hash symbol (#) to be used to prefix comments. 50 | /// 51 | /// Sets the default comment prefix to the hash symbol (#). 52 | /// 53 | /// The same instance so that multiple calls can be chained. 54 | /// 55 | public IniGlobalConfig AllowHashForComments(bool setAsDefault = false) 56 | { 57 | HashForComments.Allow = true; 58 | HashForComments.IsDefault = setAsDefault; 59 | return this; 60 | } 61 | 62 | public IniGlobalConfig SetSectionPaddingDefaults(PaddingValue? left = null, PaddingValue? insideLeft = null, 63 | PaddingValue? insideRight = null, PaddingValue? right = null) 64 | { 65 | Padding.Section.Left = left ?? 0; 66 | Padding.Section.InsideLeft = insideLeft ?? 0; 67 | Padding.Section.InsideRight = insideRight ?? 0; 68 | Padding.Section.Right = right ?? 0; 69 | return this; 70 | } 71 | 72 | public IniGlobalConfig SetPropertyPaddingDefaults(PaddingValue? left = null, PaddingValue? insideLeft = null, 73 | PaddingValue? insideRight = null, PaddingValue? right = null) 74 | { 75 | Padding.Property.Left = left ?? 0; 76 | Padding.Property.InsideLeft = insideLeft ?? 1; 77 | Padding.Property.InsideRight = insideRight ?? 1; 78 | Padding.Property.Right = right ?? 1; 79 | return this; 80 | } 81 | 82 | public IniGlobalConfig SetCommentPaddingDefaults(PaddingValue? left = null, PaddingValue? inside = null, 83 | PaddingValue? right = null) 84 | { 85 | Padding.Comment.Left = left ?? 0; 86 | Padding.Comment.Inside = inside ?? 1; 87 | Padding.Comment.Right = right ?? 1; 88 | return this; 89 | } 90 | 91 | /// 92 | /// Sets the default string values to represent true and false when writing 93 | /// boolean properties. 94 | /// 95 | /// The string value to represent true. 96 | /// The string value to represent false. 97 | /// 98 | /// The same instance so that multiple calls can be chained. 99 | /// 100 | public IniGlobalConfig SetBooleanStrings(string trueString = "1", string falseString = "0") 101 | { 102 | Types.TrueString = string.IsNullOrEmpty(trueString) || trueString.Trim().Length == 0 ? "1" : trueString; 103 | Types.FalseString = string.IsNullOrEmpty(falseString) || falseString.Trim().Length == 0 ? "0" : falseString; 104 | return this; 105 | } 106 | 107 | /// 108 | /// Sets the default date and time formats when reading and writing 109 | /// and values. 110 | /// 111 | /// 112 | /// The format for dates. If this value is null, then the current culture short 113 | /// date format is used. 114 | /// 115 | /// 116 | /// The same instance so that multiple calls can be chained. 117 | /// 118 | public IniGlobalConfig SetDateFormats(string dateFormat = null) 119 | { 120 | Types.DateFormat = string.IsNullOrEmpty(dateFormat) || dateFormat.Trim().Length == 0 121 | ? CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern 122 | : dateFormat; 123 | return this; 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /src/IniFile/ErrorMessages.Designer.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | //------------------------------------------------------------------------------ 22 | // 23 | // This code was generated by a tool. 24 | // Runtime Version:4.0.30319.42000 25 | // 26 | // Changes to this file may cause incorrect behavior and will be lost if 27 | // the code is regenerated. 28 | // 29 | //------------------------------------------------------------------------------ 30 | 31 | namespace IniFile { 32 | using System; 33 | using System.Reflection; 34 | 35 | 36 | /// 37 | /// A strongly-typed resource class, for looking up localized strings, etc. 38 | /// 39 | // This class was auto-generated by the StronglyTypedResourceBuilder 40 | // class via a tool like ResGen or Visual Studio. 41 | // To add or remove a member, edit your .ResX file then rerun ResGen 42 | // with the /str option, or rebuild your VS project. 43 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 44 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 45 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 46 | internal class ErrorMessages { 47 | 48 | private static global::System.Resources.ResourceManager resourceMan; 49 | 50 | private static global::System.Globalization.CultureInfo resourceCulture; 51 | 52 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 53 | internal ErrorMessages() { 54 | } 55 | 56 | /// 57 | /// Returns the cached ResourceManager instance used by this class. 58 | /// 59 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 60 | internal static global::System.Resources.ResourceManager ResourceManager { 61 | get { 62 | if (object.ReferenceEquals(resourceMan, null)) { 63 | #if NETSTANDARD1_3 64 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("IniFile.ErrorMessages", typeof(ErrorMessages).GetTypeInfo().Assembly); 65 | #else 66 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("IniFile.ErrorMessages", typeof(ErrorMessages).Assembly); 67 | #endif 68 | resourceMan = temp; 69 | } 70 | return resourceMan; 71 | } 72 | } 73 | 74 | /// 75 | /// Overrides the current thread's CurrentUICulture property for all 76 | /// resource lookups using this strongly typed resource class. 77 | /// 78 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 79 | internal static global::System.Globalization.CultureInfo Culture { 80 | get { 81 | return resourceCulture; 82 | } 83 | set { 84 | resourceCulture = value; 85 | } 86 | } 87 | 88 | /// 89 | /// Looks up a localized string similar to Cannot convert the property value to a {0}.. 90 | /// 91 | internal static string CannotCastPropertyValue { 92 | get { 93 | return ResourceManager.GetString("CannotCastPropertyValue", resourceCulture); 94 | } 95 | } 96 | 97 | /// 98 | /// Looks up a localized string similar to Unexpected error: currentSection variable should not be null.. 99 | /// 100 | internal static string CreateObjectModelInvalidCurrentSection { 101 | get { 102 | return ResourceManager.GetString("CreateObjectModelInvalidCurrentSection", resourceCulture); 103 | } 104 | } 105 | 106 | /// 107 | /// Looks up a localized string similar to The INI file '{0}' does not exist.. 108 | /// 109 | internal static string IniFileDoesNotExist { 110 | get { 111 | return ResourceManager.GetString("IniFileDoesNotExist", resourceCulture); 112 | } 113 | } 114 | 115 | /// 116 | /// Looks up a localized string similar to The specified name is not valid. A valid name can start with any alphabetic character, period (.), dollar symbol ($) or semi-colon (:) and can be followed by one or more alpha-numeric characters, underscores (_), tilde (~), dash (-), period (.), semi-colon (:), dollar symbol ($), or space.. 117 | /// 118 | internal static string InvalidMajorItemName { 119 | get { 120 | return ResourceManager.GetString("InvalidMajorItemName", resourceCulture); 121 | } 122 | } 123 | 124 | /// 125 | /// Looks up a localized string similar to The property '{0}' is not in a section. This is not allowed; all properties must be specified under a section.. 126 | /// 127 | internal static string PropertyWithoutSection { 128 | get { 129 | return ResourceManager.GetString("PropertyWithoutSection", resourceCulture); 130 | } 131 | } 132 | 133 | /// 134 | /// Looks up a localized string similar to The specified stream is not readable (CanRead = false). The INI content cannot be loaded from it.. 135 | /// 136 | internal static string StreamNotReadable { 137 | get { 138 | return ResourceManager.GetString("StreamNotReadable", resourceCulture); 139 | } 140 | } 141 | 142 | /// 143 | /// Looks up a localized string similar to The specified stream is not writable (CanWrite = false). The INI object cannot be saved to it.. 144 | /// 145 | internal static string StreamNotWritable { 146 | get { 147 | return ResourceManager.GetString("StreamNotWritable", resourceCulture); 148 | } 149 | } 150 | 151 | /// 152 | /// Looks up a localized string similar to An unrecognized line was encountered in the INI content. 153 | ///{0}. 154 | /// 155 | internal static string UnrecognizedLine { 156 | get { 157 | return ResourceManager.GetString("UnrecognizedLine", resourceCulture); 158 | } 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/IniFile/ErrorMessages.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | The INI file '{0}' does not exist. 122 | 123 | 124 | The property '{0}' is not in a section. This is not allowed; all properties must be specified under a section. 125 | 126 | 127 | The specified stream is not readable (CanRead = false). The INI content cannot be loaded from it. 128 | 129 | 130 | The specified stream is not writable (CanWrite = false). The INI object cannot be saved to it. 131 | 132 | 133 | An unrecognized line was encountered in the INI content. 134 | {0} 135 | 136 | 137 | The specified name is not valid. A valid name can start with any alphabetic character, period (.), dollar symbol ($) or semi-colon (:) and can be followed by one or more alpha-numeric characters, underscores (_), tilde (~), dash (-), period (.), semi-colon (:), dollar symbol ($), or space. 138 | 139 | 140 | Cannot convert the property value to a {0}. 141 | 142 | 143 | Unexpected error: currentSection variable should not be null. 144 | 145 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories 2 | root = true 3 | 4 | # C# files 5 | [*.cs] 6 | 7 | #### Core EditorConfig Options #### 8 | 9 | # Indentation and spacing 10 | indent_size = 4 11 | indent_style = space 12 | tab_width = 4 13 | 14 | # New line preferences 15 | end_of_line = crlf 16 | insert_final_newline = false 17 | 18 | #### .NET Coding Conventions #### 19 | 20 | # Organize usings 21 | dotnet_separate_import_directive_groups = true 22 | dotnet_sort_system_directives_first = true 23 | file_header_template = unset 24 | 25 | # this. and Me. preferences 26 | dotnet_style_qualification_for_event = false:warning 27 | dotnet_style_qualification_for_field = false 28 | dotnet_style_qualification_for_method = false:warning 29 | dotnet_style_qualification_for_property = false:warning 30 | 31 | # Language keywords vs BCL types preferences 32 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 33 | dotnet_style_predefined_type_for_member_access = true:warning 34 | 35 | # Parentheses preferences 36 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion 37 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion 38 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary 39 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion 40 | 41 | # Modifier preferences 42 | dotnet_style_require_accessibility_modifiers = for_non_interface_members 43 | 44 | # Expression-level preferences 45 | dotnet_style_coalesce_expression = true 46 | dotnet_style_collection_initializer = true 47 | dotnet_style_explicit_tuple_names = true:warning 48 | dotnet_style_null_propagation = true 49 | dotnet_style_object_initializer = true 50 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 51 | dotnet_style_prefer_auto_properties = true 52 | dotnet_style_prefer_compound_assignment = true:warning 53 | dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion 54 | dotnet_style_prefer_conditional_expression_over_return = true:suggestion 55 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning 56 | dotnet_style_prefer_inferred_tuple_names = true:warning 57 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning 58 | dotnet_style_prefer_simplified_boolean_expressions = true:warning 59 | dotnet_style_prefer_simplified_interpolation = true 60 | 61 | # Field preferences 62 | dotnet_style_readonly_field = true:warning 63 | 64 | # Parameter preferences 65 | dotnet_code_quality_unused_parameters = all 66 | 67 | # Suppression preferences 68 | dotnet_remove_unnecessary_suppression_exclusions = none 69 | 70 | #### C# Coding Conventions #### 71 | 72 | # var preferences 73 | csharp_style_var_elsewhere = false:warning 74 | csharp_style_var_for_built_in_types = false:warning 75 | csharp_style_var_when_type_is_apparent = true:warning 76 | 77 | # Expression-bodied members 78 | csharp_style_expression_bodied_accessors = true:suggestion 79 | csharp_style_expression_bodied_constructors = false:suggestion 80 | csharp_style_expression_bodied_indexers = true:suggestion 81 | csharp_style_expression_bodied_lambdas = true:suggestion 82 | csharp_style_expression_bodied_local_functions = false:suggestion 83 | csharp_style_expression_bodied_methods = false:suggestion 84 | csharp_style_expression_bodied_operators = false:suggestion 85 | csharp_style_expression_bodied_properties = true:suggestion 86 | 87 | # Pattern matching preferences 88 | csharp_style_pattern_matching_over_as_with_null_check = true 89 | csharp_style_pattern_matching_over_is_with_cast_check = true 90 | csharp_style_prefer_not_pattern = true 91 | csharp_style_prefer_pattern_matching = true 92 | csharp_style_prefer_switch_expression = true 93 | 94 | # Null-checking preferences 95 | csharp_style_conditional_delegate_call = true 96 | 97 | # Modifier preferences 98 | csharp_prefer_static_local_function = true:warning 99 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async 100 | 101 | # Code-block preferences 102 | csharp_prefer_braces = when_multiline 103 | csharp_prefer_simple_using_statement = true 104 | 105 | # Expression-level preferences 106 | csharp_prefer_simple_default_expression = true:warning 107 | csharp_style_deconstructed_variable_declaration = true 108 | csharp_style_implicit_object_creation_when_type_is_apparent = true 109 | csharp_style_inlined_variable_declaration = true:warning 110 | csharp_style_pattern_local_over_anonymous_function = true 111 | csharp_style_prefer_index_operator = true 112 | csharp_style_prefer_range_operator = true 113 | csharp_style_throw_expression = false 114 | csharp_style_unused_value_assignment_preference = discard_variable 115 | csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion 116 | 117 | # 'using' directive preferences 118 | csharp_using_directive_placement = outside_namespace:warning 119 | 120 | #### C# Formatting Rules #### 121 | 122 | # New line preferences 123 | csharp_new_line_before_catch = false 124 | csharp_new_line_before_else = true 125 | csharp_new_line_before_finally = false 126 | csharp_new_line_before_members_in_anonymous_types = true 127 | csharp_new_line_before_members_in_object_initializers = true 128 | csharp_new_line_before_open_brace = all 129 | csharp_new_line_between_query_expression_clauses = true 130 | 131 | # Indentation preferences 132 | csharp_indent_block_contents = true 133 | csharp_indent_braces = false 134 | csharp_indent_case_contents = true 135 | csharp_indent_case_contents_when_block = false 136 | csharp_indent_labels = flush_left 137 | csharp_indent_switch_labels = true 138 | 139 | # Space preferences 140 | csharp_space_after_cast = false 141 | csharp_space_after_colon_in_inheritance_clause = true 142 | csharp_space_after_comma = true 143 | csharp_space_after_dot = false 144 | csharp_space_after_keywords_in_control_flow_statements = true 145 | csharp_space_after_semicolon_in_for_statement = true 146 | csharp_space_around_binary_operators = before_and_after 147 | csharp_space_around_declaration_statements = false 148 | csharp_space_before_colon_in_inheritance_clause = true 149 | csharp_space_before_comma = false 150 | csharp_space_before_dot = false 151 | csharp_space_before_open_square_brackets = false 152 | csharp_space_before_semicolon_in_for_statement = false 153 | csharp_space_between_empty_square_brackets = false 154 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 155 | csharp_space_between_method_call_name_and_opening_parenthesis = false 156 | csharp_space_between_method_call_parameter_list_parentheses = false 157 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 158 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 159 | csharp_space_between_method_declaration_parameter_list_parentheses = false 160 | csharp_space_between_parentheses = false 161 | csharp_space_between_square_brackets = false 162 | 163 | # Wrapping preferences 164 | csharp_preserve_single_line_blocks = true 165 | csharp_preserve_single_line_statements = false 166 | 167 | #### Naming styles #### 168 | 169 | # Naming rules 170 | 171 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 172 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 173 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 174 | 175 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 176 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 177 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 178 | 179 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 180 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 181 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 182 | 183 | dotnet_naming_rule.private_or_internal_field_should_be_underscore.severity = suggestion 184 | dotnet_naming_rule.private_or_internal_field_should_be_underscore.symbols = private_or_internal_field 185 | dotnet_naming_rule.private_or_internal_field_should_be_underscore.style = underscore 186 | 187 | # Symbol specifications 188 | 189 | dotnet_naming_symbols.interface.applicable_kinds = interface 190 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 191 | dotnet_naming_symbols.interface.required_modifiers = 192 | 193 | dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field 194 | dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected 195 | dotnet_naming_symbols.private_or_internal_field.required_modifiers = 196 | 197 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 198 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 199 | dotnet_naming_symbols.types.required_modifiers = 200 | 201 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 202 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 203 | dotnet_naming_symbols.non_field_members.required_modifiers = 204 | 205 | # Naming styles 206 | 207 | dotnet_naming_style.pascal_case.required_prefix = 208 | dotnet_naming_style.pascal_case.required_suffix = 209 | dotnet_naming_style.pascal_case.word_separator = 210 | dotnet_naming_style.pascal_case.capitalization = pascal_case 211 | 212 | dotnet_naming_style.begins_with_i.required_prefix = I 213 | dotnet_naming_style.begins_with_i.required_suffix = 214 | dotnet_naming_style.begins_with_i.word_separator = 215 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 216 | 217 | dotnet_naming_style.underscore.required_prefix = _ 218 | dotnet_naming_style.underscore.required_suffix = 219 | dotnet_naming_style.underscore.word_separator = 220 | dotnet_naming_style.underscore.capitalization = camel_case 221 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /docs/object-model.gliffy: -------------------------------------------------------------------------------- 1 | {"contentType":"application/gliffy+json","version":"1.1","metadata":{"title":"untitled","revision":0,"exportBorder":false},"embeddedResources":{"index":0,"resources":[]},"stage":{"objects":[{"x":10,"y":99,"rotation":0,"id":27,"uid":"com.gliffy.shape.basic.basic_v1.default.text","width":110,"height":42,"lockAspectRatio":false,"lockShape":false,"order":27,"graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Blank line and comment belong to MaxSize property

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null,"linkMap":[]},{"x":164,"y":122,"rotation":0,"id":25,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":25,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#980000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":false,"interpolationType":"linear","cornerRadius":null,"controlPath":[[0,0],[-43.01162633521312,-2.842170943040401e-14]],"lockSegments":{}}},"children":null,"linkMap":[]},{"x":165,"y":109,"rotation":0,"id":24,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":24,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#980000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":false,"interpolationType":"linear","cornerRadius":null,"controlPath":[[0,0],[0,29.154759474226495]],"lockSegments":{}}},"children":null,"linkMap":[]},{"x":10,"y":30,"rotation":0,"id":22,"uid":"com.gliffy.shape.basic.basic_v1.default.text","width":110,"height":42,"lockAspectRatio":false,"lockShape":false,"order":22,"graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Blank line and comment belong to Logging section

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null,"linkMap":[]},{"x":160,"y":49,"rotation":0,"id":21,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":21,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#980000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":false,"interpolationType":"linear","cornerRadius":null,"controlPath":[[5.0434117678316,2.842170943040401e-14],[-41.04875150354758,0]],"lockSegments":{}}},"children":null,"linkMap":[]},{"x":166,"y":34,"rotation":0,"id":20,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":20,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#980000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":false,"interpolationType":"linear","cornerRadius":null,"controlPath":[[0,0],[0,32.01562118716424]],"lockSegments":{}}},"children":null,"linkMap":[]},{"x":604,"y":164,"rotation":0,"id":16,"uid":"com.gliffy.shape.basic.basic_v1.default.text","width":68,"height":14,"lockAspectRatio":false,"lockShape":false,"order":10,"graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Comment

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null,"linkMap":[]},{"x":604,"y":146,"rotation":0,"id":15,"uid":"com.gliffy.shape.basic.basic_v1.default.text","width":70,"height":14,"lockAspectRatio":false,"lockShape":false,"order":9,"graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Blank Line

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null,"linkMap":[]},{"x":603,"y":80,"rotation":0,"id":14,"uid":"com.gliffy.shape.basic.basic_v1.default.text","width":70,"height":14,"lockAspectRatio":false,"lockShape":false,"order":8,"graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Property

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null,"linkMap":[]},{"x":602,"y":63,"rotation":0,"id":13,"uid":"com.gliffy.shape.basic.basic_v1.default.text","width":73,"height":14,"lockAspectRatio":false,"lockShape":false,"order":7,"graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Section

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null,"linkMap":[]},{"x":288,"y":153,"rotation":0,"id":9,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":6,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#980000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":false,"interpolationType":"linear","cornerRadius":null,"controlPath":[[0,0],[312.00160255998685,0]],"lockSegments":{}}},"children":null,"linkMap":[]},{"x":289,"y":171,"rotation":0,"id":8,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":5,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#980000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":false,"interpolationType":"linear","cornerRadius":null,"controlPath":[[0,0],[311.0016077128862,0]],"lockSegments":{}}},"children":null,"linkMap":[]},{"x":299,"y":87,"rotation":0,"id":7,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":4,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#980000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":false,"interpolationType":"linear","cornerRadius":null,"controlPath":[[0,0],[301.0016611249846,0]],"lockSegments":{}}},"children":null,"linkMap":[]},{"x":245,"y":71,"rotation":0,"id":6,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":3,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#980000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":false,"interpolationType":"linear","cornerRadius":null,"controlPath":[[0,0],[354.0014124265608,0]],"lockSegments":{}}},"children":null,"linkMap":[]},{"x":159.99999999999997,"y":20,"rotation":0,"id":3,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":420.00000000000006,"height":230,"lockAspectRatio":false,"lockShape":false,"order":1,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#d0e0e3","gradient":true,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":4.421052631578946,"y":0,"rotation":0,"id":5,"uid":null,"width":411.1578947368426,"height":168,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

; Logging details\n

[Logging]\n

MinLevel = Info\n

LogFile = Logger.txt\n

\n

; Maximum size in MB\n

MaxSize = 16\n

\n

; The services\n

[Services]\n

CustomerService = https://customer.service/api\n

ProductService = https://product.service/api\n

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":160,"y":30,"rotation":0,"id":10,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":170,"height":40,"lockAspectRatio":false,"lockShape":false,"order":0,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#980000","fillColor":"#980000","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":null,"linkMap":[]}],"background":"#FFFFFF","width":752,"height":250,"maxWidth":5000,"maxHeight":5000,"nodeIndex":29,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"none","stroke":"#980000","strokeWidth":1,"gradient":false}},"lineStyles":{"global":{"endArrow":0,"dashStyle":null,"strokeWidth":1,"stroke":"#980000"}},"textStyles":{},"themeData":null}} -------------------------------------------------------------------------------- /tests/IniFile.Tests/IniFileTests.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System; 22 | 23 | using Shouldly; 24 | 25 | using Xunit; 26 | using Xunit.DataAttributes; 27 | 28 | namespace IniFile.Tests 29 | { 30 | public sealed class IniTests 31 | { 32 | [Theory] 33 | [EmbeddedResourceContent("IniFile.Tests.Players.ini")] 34 | public void Basic_tests(string validIni) 35 | { 36 | Ini.Config 37 | .AllowHashForComments() 38 | .SetSectionPaddingDefaults(insideLeft: 1, insideRight: 1) 39 | .SetPropertyPaddingDefaults(left: 4); 40 | 41 | var ini = Ini.Load(validIni); 42 | 43 | ini.Count.ShouldBe(3); 44 | ini[0].Name.ShouldBe("Game State"); 45 | ini[1].Name.ShouldBe("Jeevan"); 46 | ini[2].Name.ShouldBe("Merina"); 47 | 48 | Section section = ini[0]; 49 | section.Count.ShouldBe(2); 50 | section["Player1"].ToString().ShouldBe("Ryan"); 51 | section["Player2"].ToString().ShouldBe("Emma"); 52 | } 53 | 54 | [Fact(DisplayName = "Basic INI creation test")] 55 | public void Basic_create_test() 56 | { 57 | Ini.Config 58 | .AllowHashForComments(true) 59 | .SetSectionPaddingDefaults(insideLeft: 1, insideRight: 1) 60 | .SetPropertyPaddingDefaults(left: 4); 61 | 62 | var ini = new Ini 63 | { 64 | new("Players", "This section defines the players") 65 | { 66 | new Property("Player1", "The Flash"), 67 | new Property("Player2", "Superman") 68 | }, 69 | new("The Flash", string.Empty) 70 | { 71 | ["Level"] = 9, 72 | ["Power"] = "Superspeed", 73 | ["Masked"] = true 74 | }, 75 | new("Superman", string.Empty) 76 | { 77 | ["Level"] = 9, 78 | ["Power"] = "Superstrength,heat vision", 79 | ["Masked"] = false, 80 | ["MultiLine"] = @"This is line one 81 | This is line 2 82 | This is line 3 and the last line" 83 | } 84 | }; 85 | 86 | ini.ShouldNotBeNull(); 87 | ini.Count.ShouldBe(3); 88 | 89 | Section playersSection = ini["Players"]; 90 | playersSection.ShouldNotBeNull(); 91 | playersSection.Items.Count.ShouldBe(1); 92 | playersSection.Items[0].ShouldBeOfType(); 93 | playersSection.Count.ShouldBe(2); 94 | playersSection["Player1"].ToString().ShouldBe("The Flash"); 95 | playersSection["Player2"].ToString().ShouldBe("Superman"); 96 | 97 | Section flashSection = ini["The Flash"]; 98 | flashSection.ShouldNotBeNull(); 99 | flashSection.Items.Count.ShouldBe(1); 100 | flashSection.Items[0].ShouldBeOfType(); 101 | flashSection.Count.ShouldBe(3); 102 | ((int)flashSection["Level"]).ShouldBe(9); 103 | flashSection["Power"].ToString().ShouldBe("Superspeed"); 104 | 105 | Section supermanSection = ini["Superman"]; 106 | supermanSection.ShouldNotBeNull(); 107 | supermanSection.Items.Count.ShouldBe(1); 108 | supermanSection.Items[0].ShouldBeOfType(); 109 | supermanSection.Count.ShouldBe(4); 110 | ((int)supermanSection["Level"]).ShouldBe(9); 111 | supermanSection["Power"].ToString().ShouldBe("Superstrength,heat vision"); 112 | 113 | flashSection["Level"] = 10; 114 | int level = flashSection["Level"]; 115 | level.ShouldBe(10); 116 | 117 | flashSection["Masked"] = "yes"; 118 | bool masked = flashSection["Masked"]; 119 | masked.ShouldBeTrue(); 120 | } 121 | 122 | //[Theory(Skip = "Need to handle cross-platform new line characters.")] 123 | //[EmbeddedResourceContent("IniFile.Tests.Players.ini")] 124 | //public void Ensure_format_is_retained(string validIni) 125 | //{ 126 | // var ini = Ini.Load(validIni); 127 | // string iniContent = ini.ToString(); 128 | // iniContent.ShouldBe(validIni); 129 | //} 130 | 131 | [Theory(DisplayName = "Throws on unrecognized line")] 132 | [EmbeddedResourceContent("IniFile.Tests.Data.UnrecognizedLine.ini")] 133 | public void Unrecognized_line_throws(string iniContent) 134 | { 135 | Should.Throw(() => Ini.Load(iniContent)); 136 | } 137 | 138 | [Theory(DisplayName = "Throwns on a property without a section")] 139 | [EmbeddedResourceContent("IniFile.Tests.Data.PropertyWithoutSection.ini")] 140 | public void Property_without_section_throws(string iniContent) 141 | { 142 | Should.Throw(() => Ini.Load(iniContent)); 143 | } 144 | 145 | [Theory(DisplayName = "Allows empty sections")] 146 | [EmbeddedResourceContent("IniFile.Tests.Data.EmptySections.ini")] 147 | public void Empty_sections_are_allowed(string iniContent) 148 | { 149 | Ini ini = Ini.Load(iniContent); 150 | 151 | ini.ShouldNotBeNull(); 152 | ini.Count.ShouldBe(3); 153 | ini.ShouldAllBe(section => section.Count == 0); 154 | } 155 | 156 | [Theory(DisplayName = "Properties without values are allowed")] 157 | [EmbeddedResourceContent("IniFile.Tests.Data.EmptyProperties.ini")] 158 | public void Empty_properties_are_allowed(string iniContent) 159 | { 160 | Ini ini = Ini.Load(iniContent); 161 | 162 | ini.ShouldNotBeNull(); 163 | ini["Section1"]["Key1"].ToString().ShouldBe(string.Empty); 164 | ini["Section1"]["Key2"].ToString().ShouldBe(string.Empty); 165 | ini["Section2"]["Key4"].ToString().ShouldBe(string.Empty); 166 | } 167 | 168 | [Theory] 169 | [EmbeddedResourceContent("IniFile.Tests.Data.Unformatted.ini")] 170 | public void Format_ini(string iniContent) 171 | { 172 | Ini ini = Ini.Load(iniContent); 173 | 174 | ini.Format(new IniFormatOptions { RemoveSuccessiveBlankLines = true }); 175 | string formatted = ini.ToString(); 176 | 177 | formatted.ShouldNotBeNull(); 178 | } 179 | 180 | [Theory(DisplayName = "Can read typed properties")] 181 | [EmbeddedResourceContent("IniFile.Tests.Data.TypedProperties.ini")] 182 | public void Can_read_typed_properties(string iniContent) 183 | { 184 | Ini.Config.Types.DateFormat = "yyyy-MM-dd"; 185 | 186 | Ini ini = Ini.Load(iniContent); 187 | 188 | Section dataSection = ini["TypedData"]; 189 | string stringValue = dataSection["String"]; 190 | sbyte sbyteValue = dataSection["SByte"]; 191 | byte byteValue = dataSection["Byte"]; 192 | short shortValue = dataSection["Short"]; 193 | ushort ushortValue = dataSection["UShort"]; 194 | int intValue = dataSection["Int"]; 195 | uint uintValue = dataSection["UInt"]; 196 | long longValue = dataSection["Long"]; 197 | ulong ulongValue = dataSection["ULong"]; 198 | float floatValue = dataSection["Float"]; 199 | double doubleValue = dataSection["Double"]; 200 | decimal decimalValue = dataSection["Decimal"]; 201 | DateTime dateTimeValue = dataSection["DateTime"]; 202 | 203 | Section falseBoolSection = ini["False Booleans"]; 204 | bool falseBool1 = falseBoolSection["Bool1"]; 205 | bool falseBool2 = falseBoolSection["Bool2"]; 206 | bool falseBool3 = falseBoolSection["Bool3"]; 207 | bool falseBool4 = falseBoolSection["Bool4"]; 208 | bool falseBool5 = falseBoolSection["Bool5"]; 209 | bool falseBool6 = falseBoolSection["Bool6"]; 210 | bool falseBool7 = falseBoolSection["Bool7"]; 211 | 212 | Section trueBoolSection = ini["True Booleans"]; 213 | bool trueBool1 = trueBoolSection["Bool1"]; 214 | bool trueBool2 = trueBoolSection["Bool2"]; 215 | bool trueBool3 = trueBoolSection["Bool3"]; 216 | bool trueBool4 = trueBoolSection["Bool4"]; 217 | bool trueBool5 = trueBoolSection["Bool5"]; 218 | bool trueBool6 = trueBoolSection["Bool6"]; 219 | bool trueBool7 = trueBoolSection["Bool7"]; 220 | 221 | Section enumSection = ini["Enum"]; 222 | DayOfWeek dow = enumSection["Enum1"].AsEnum(); 223 | 224 | dateTimeValue.ShouldBe(new DateTime(2018, 12, 08)); 225 | 226 | falseBool1.ShouldBeFalse(); 227 | falseBool2.ShouldBeFalse(); 228 | falseBool3.ShouldBeFalse(); 229 | falseBool4.ShouldBeFalse(); 230 | falseBool5.ShouldBeFalse(); 231 | falseBool6.ShouldBeFalse(); 232 | falseBool7.ShouldBeFalse(); 233 | 234 | trueBool1.ShouldBeTrue(); 235 | trueBool2.ShouldBeTrue(); 236 | trueBool3.ShouldBeTrue(); 237 | trueBool4.ShouldBeTrue(); 238 | trueBool5.ShouldBeTrue(); 239 | trueBool6.ShouldBeTrue(); 240 | trueBool7.ShouldBeTrue(); 241 | 242 | dow.ShouldBe(DayOfWeek.Saturday); 243 | } 244 | 245 | [Fact(DisplayName = "Can write typed properties")] 246 | public void Can_write_typed_properties() 247 | { 248 | var ini = new Ini 249 | { 250 | new Section("TypedData") 251 | { 252 | ["String"] = "This is a string", 253 | ["SByte"] = (sbyte) -10, 254 | ["Byte"] = (byte) 25, 255 | ["Short"] = (short) -400, 256 | ["UShort"] = (ushort) 700, 257 | ["Int"] = -30000, 258 | ["UInt"] = 50000U, 259 | ["Long"] = -1000000L, 260 | ["ULong"] = 20000000UL, 261 | ["Float"] = 12.54443F, 262 | ["Double"] = 6566123.22D, 263 | ["Decimal"] = 1000.50m, 264 | ["Boolean"] = true, 265 | ["DateTime"] = DateTime.Now 266 | } 267 | }; 268 | 269 | ini.ShouldNotBeNull(); 270 | } 271 | 272 | [Theory(DisplayName = "Can read a multiline property value")] 273 | [EmbeddedResourceContent("IniFile.Tests.Data.MultilinePropertyValue.ini")] 274 | public void Can_read_multiline_property_value(string iniContent) 275 | { 276 | Ini ini = Ini.Load(iniContent); 277 | 278 | string expectedValue = "This is a " + Environment.NewLine + 279 | "multiline" + Environment.NewLine + 280 | " value."; 281 | ini["Section"]["Multiline"].ToString().ShouldBe(expectedValue); 282 | } 283 | 284 | //[Fact] 285 | //public void Can_load_from_file() 286 | //{ 287 | // var ini = new Ini(@"D:\Temp\Data.ini"); 288 | // string ip = ini["tcp"]?["ip"]; 289 | // string missing = ini["tcp2"]?["missing"]; 290 | 291 | // ip.ShouldNotBeNull(); 292 | // missing.ShouldBeNull(); 293 | //} 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IniFile.NET [![Build status](https://img.shields.io/appveyor/ci/JeevanJames/inifile.svg)](https://ci.appveyor.com/project/JeevanJames/inifile/branch/master) [![Test status](https://img.shields.io/appveyor/tests/JeevanJames/inifile.svg)](https://ci.appveyor.com/project/JeevanJames/inifile/branch/master) [![NuGet Version](http://img.shields.io/nuget/v/IniFile.NET.svg?style=flat)](https://www.nuget.org/packages/IniFile.NET/) [![NuGet Downloads](https://img.shields.io/nuget/dt/IniFile.NET.svg)](https://www.nuget.org/packages/IniFile.NET/) 2 | 3 | IniFile.NET is a .NET library to open, modify and save .INI files. 4 | 5 | 1. [Nuget Installation](#nuget-installation) 6 | 1. [Ini object model](#ini-object-model) 7 | 1. [Loading an existing .INI](#loading-an-existing-ini) 8 | 1. [IniLoadSettings](#iniloadsettings) 9 | 1. [Creating an INI file](#creating-a-ini-file) 10 | 1. [Comments and blank lines](#comments-and-blank-lines) 11 | 1. [Using properties](#using-properties) 12 | 1. [Gotcha when using implicitly-typed variables to read property values](#gotcha-when-using-implicitly-typed-variables-to-read-property-values) 13 | 1. [Boolean properties](#boolean-properties) 14 | 1. [Date/time properties](#datetime-properties) 15 | 1. [Enum properties](#enum-properties) 16 | 1. [Saving the INI content](#saving-the-ini-content) 17 | 1. [Global configuration](#global-configuration) 18 | 1. [Formatting the INI content](#formatting-the-ini-content) 19 | 20 | ## Nuget installation 21 | Install using package manager: 22 | ```ps 23 | PM> Install-Package IniFile.NET 24 | ``` 25 | 26 | Install using `dotnet` CLI: 27 | ```sh 28 | > dotnet add package IniFile.NET 29 | ``` 30 | 31 | ## Ini object model 32 | ![Ini object model](/docs/object-model.png) 33 | 34 | The primary class in this library is the `IniFile.Ini` class and it maintains the structure of an .INI file as an in-memory object model, with objects for sections, properties (key-value pairs), comments and blank lines, allowing it to model the exact structure of a .INI file. 35 | 36 | The `IniFile.Ini` class is a collection of `Section` objects (`IList
`). Each `Section` is additionally a collection of `Property` objects (`IList`). 37 | 38 | Both `Section` and `Property` objects contain a collection of minor objects, namely `Comment` and `BlankLine` objects, which are the comments and blank lines that appear before the respective sections and properties. 39 | 40 | ## Loading an existing .INI 41 | The `Ini` class provides several constructor overloads to load .INI data from streams, text readers and files. 42 | ```cs 43 | // Load INI data from a file 44 | var ini = new Ini("Settings.ini"); 45 | ``` 46 | 47 | The class also provides a static factory method `Load` to create an `Ini` object from a string. 48 | ```cs 49 | const iniStr = File.ReadAllText("Settings.ini"); 50 | Ini ini = Ini.Load(iniStr); 51 | ``` 52 | 53 | ### `IniLoadSettings` 54 | All `Ini` constructors and the `Load` static factory method accept an optional `IniLoadSettings` object to control how the .INI data is loaded and handled. 55 | 56 | |Property|Description|Default| 57 | |--------|-----------|-------| 58 | |`CaseSensitive`|A `bool` indicating whether the section names and property key names in the INI file are case sensitive.|`false`| 59 | |`DetectEncoding`|A `bool` indicating whether the character encoding should be automatically detected when the INI file is loaded.|`false`| 60 | |`Encoding`|The character encoding to use when reading or writing data to the INI file.|`UTF-8`| 61 | |`IgnoreBlankLines`|A `bool` indicating whether to ignore blank lines when loading the INI file content. Useful if you just want to read from the INI file, but not make changes.|`false`| 62 | |`IgnoreComments`|A `bool` indicating whether to ignore comments when loading the INI file content. Useful if you just want to read from the INI file, but not make changes.|`false`| 63 | 64 | ```cs 65 | var loadSettings = new IniLoadSettings 66 | { 67 | Encoding = Encoding.Unicode, 68 | DetectEncoding = true, 69 | CaseSensitive = true 70 | }; 71 | var ini = new Ini(stream, loadSettings); 72 | ``` 73 | 74 | ## Creating a INI file 75 | Since the `Ini` class is a collection of `Section` objects (`IList
`) and each `Section` object is a collection of `Property` objects (`IList`), so you can use regular `IList` mechanisms to add, remove and manage sections and properties. 76 | 77 | So, for example, you can create an INI from scratch, using collection initializers, as shown here: 78 | ```cs 79 | var ini = new Ini 80 | { 81 | new Section("Section Name") 82 | { 83 | new Property("Property1 Name", "A string value"), 84 | new Property("Property2 Name", 10) 85 | } 86 | }; 87 | ``` 88 | 89 | Properties are also name-value pairs, so you can also use the dictionary initializer syntax to create them. So the code above can also look like this: 90 | ```cs 91 | var ini = new Ini 92 | { 93 | new Section("Section Name") 94 | { 95 | ["Property1 Name"] = "A string value", 96 | ["Property2 Name"] = 10 97 | } 98 | }; 99 | ``` 100 | 101 | Since they are just regular lists, you can use the regular methods and properties on `IList` to manage sections and properties: 102 | ```cs 103 | // Get number of sections 104 | int sectionCount = ini.Count; 105 | 106 | // Add a new section 107 | var section = new Section("New section"); 108 | ini.Add(section); 109 | 110 | // foreach over the properties of a section 111 | foreach (Property property in section) 112 | { 113 | // Your code goes here 114 | } 115 | 116 | // You can also use regular LINQ operations 117 | 118 | // Check if there are any properties in a section 119 | if (section.Any()) 120 | { 121 | // Your code goes here 122 | } 123 | ``` 124 | 125 | ### Comments and blank lines 126 | Any comments and blank lines appearing before a section or property belong to the respective `Section` or `Property` instance, and are stored in an `Items` property. 127 | ```ini 128 | ; This comment and the following blank line 129 | ; belong to the Connections section. 130 | 131 | [Connections] 132 | 133 | ; The blank line directly above, this comment 134 | ; and this comment belong to the SqlDb property 135 | SqlDb = Db=Server;User=admin;Pwd=passw0rd1 136 | ``` 137 | 138 | Comments and blank lines are represented by the `Comment` and `BlankLine` classes, respectively. 139 | 140 | The `Items` property is a regular `IList<>` collection, and can contain a mix of `Comment` and `BlankLine` instances. 141 | ```cs 142 | // Two ways to add a comment to a section. 143 | section.AddComment("This is a comment"); 144 | section.Items.Add(new Comment("This is a comment.")); 145 | 146 | // Adding a comment to a property. 147 | property.Items.Add(new Comment("No need to specify the ; prefix. It is added automatically")); 148 | 149 | // Two ways to add a blank line to a section 150 | section.AddBlankLine(); 151 | section.Items.Add(new BlankLine()); 152 | 153 | // Find all comments for a property 154 | IEnumerable comments = property.Comments; 155 | IEnumerable comments = property.Items.OfType(); 156 | ``` 157 | 158 | `Section` and `Property` constructors also accept a range of strings to denote comments or blank lines. If the string is `null`, empty or just whitespace, then it is considered a blank line, otherwise it is considered a comment. This code: 159 | ```cs 160 | var section = new Section("SectionName", null, "This is a comment surrounded by blank lines", null); 161 | ``` 162 | will generate: 163 | ```ini 164 | 165 | ; This is a comment surrounded by blank lines 166 | 167 | [SectionName] 168 | ``` 169 | 170 | ## Using properties 171 | INI properties are represented by the `Property` class and are name-value pairs, where the name is a `string` and the value can be a `string`, `bool`, any integral number type (`int`, `byte`, `long`, `ushort`, etc.), any floating-point number type (`float`, `double` and `decimal`) and `DateTime`. 172 | 173 | ```cs 174 | // Write a double value to a property 175 | section["Pi"] = 3.14d; 176 | 177 | // Write a boolean value to a property 178 | var property = new Property("SendMail", true); 179 | section.Add(property); 180 | 181 | // Read a string value from a property 182 | string name = section["Name"]; 183 | 184 | // Read a decimal value from a property 185 | decimal price = section["Price"]; 186 | ``` 187 | 188 | ### Gotcha when using implicitly-typed variables to read property values 189 | When reading property values, if you use an implicitly-typed variable (using `var`), then you will notice that the variable is of type `PropertyValue`. This is the underlying type used by the framework to allow property values to support multiple types like `string`, `int`, `bool`, etc. It does this by allowing implicit conversions between the `PropertyValue` value and all the allowed types. 190 | 191 | However, when using `var` to declare the variable, the C# compiler will not know the actual type you intend the property value to be. 192 | ```cs 193 | var value = section["property-name"]; // value will be of type PropertyValue 194 | ``` 195 | 196 | To get the value as the actual type, explicitly specify the variable type: 197 | ```cs 198 | int value = section["property-name"]; // value will be of type int 199 | ``` 200 | 201 | Alternatively, you can explicitly cast the `PropertyValue` value to the expected type: 202 | ```cs 203 | var value = (int)section["property-value"]; // value will be of type int 204 | ``` 205 | 206 | ### Boolean properties 207 | The IniFile framework can recognize the following string values when reading boolean properties: 208 | 209 | |Boolean value|Allowed string values| 210 | |-------------|---------------------| 211 | |`true`|`1`, `t`, `y`, `on`, `yes`, `enabled`, `true`| 212 | |`false`|`0`, `f`, `n`, `off`, `no`, `disabled`, `false`| 213 | 214 | When writing boolean values, the IniFile framework will use the string values configured in the `Ini.Config.Types.TrueString` and `Ini.Config.Types.FalseString` to write the `true` and `false` values respectively to the output INI file. 215 | 216 | By default, `Ini.Config.Types.TrueString` is configured to `1` and `Ini.Config.Types.FalseString` is configured to `0`. So, the following code: 217 | ```cs 218 | section["HasDiscount"] = true; 219 | section["ValidateParking"] = false; 220 | ``` 221 | will generate the following properties in the INI file: 222 | ```ini 223 | HasDiscount = 1 224 | ValidateParking = 0 225 | ``` 226 | 227 | You can assign custom strings to the `Ini.Config.Types.TrueString` and `Ini.Config.Types.FalseString` config properties. 228 | ```cs 229 | Ini.Config.SetBooleanStrings("Oui", "Non"); 230 | 231 | // Or the long way 232 | Ini.Config.Types.TrueString = "Oui"; 233 | Ini.Config.Types.FalseString = "Non"; 234 | ``` 235 | 236 | ### Date/time properties 237 | Property values can be written as `DateTime` values: 238 | ```cs 239 | section["Today"] = DateTime.Now; 240 | ``` 241 | 242 | The IniFile framework uses the `Ini.Config.Types.DateFormat` property to control how date values are represented as strings in the INI file. By default, this is defaulted to the system's short date format (`CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern`). 243 | ```cs 244 | // Set date format to US style 245 | Ini.Config.SetDateFormats(dateFormat: "M/dd/yyyy"); 246 | 247 | // Or the long way 248 | Ini.Config.Types.DateFormat = "M/dd/yyyy"; 249 | 250 | // Reset the date format to system default 251 | Ini.Config.SetDateFormats(); 252 | 253 | // Or the long way 254 | Ini.Config.Types.DateFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; 255 | ``` 256 | 257 | When reading date values from a property, the framework will try to parse the property string value according to the format specified by the `Ini.Config.Types.DateFormat` config value. 258 | ```cs 259 | DateTime createdDate = section["CreatedDate"]; 260 | ``` 261 | 262 | If the date string value in the INI file is a different format from the config, you can use the property's `AsDateTime` method to explicitly specify the format: 263 | ```cs 264 | DateTime createdDate = section["CreatedDate"].AsDateTime("yyyy/MM/dd"); 265 | ``` 266 | 267 | ### Enum properties 268 | Property values cannot be directly read or written as enum values. 269 | 270 | To write an enum value to a property, use the enum's `ToString` method to write a string representation of the value. 271 | ```cs 272 | section["StartDay"] = DayOfWeek.Monday.ToString(); 273 | ``` 274 | 275 | To read an enum value, the property provides an `AsEnum` method: 276 | ```cs 277 | DayOfWeek startDay = section["StartDay"].AsEnum(); 278 | ``` 279 | 280 | Enum values are not case-sensitive. 281 | 282 | ## Saving the INI content 283 | The `Ini` class provides several overloads to save the INI content to streams, text writers and files. All these overloads have synchronous and async versions. 284 | ```cs 285 | // Synchronous call 286 | ini.SaveTo(@"Setting.ini"); 287 | 288 | // Asynchronous call 289 | await ini.SaveToAsync(stream); 290 | ``` 291 | 292 | ## Global configuration 293 | Certain aspects of the IniFile framework can be configured using the static `Ini.Config` property. This has properties to configure behaviors such as: 294 | * Whether to allow hash symbols (`#`) to represent comments in addition to the default semi-colon (`;`). 295 | * The default spacings for various types of INI objects such as sections, properties and comments. 296 | * How to handle reading and writing of certain types of property values, such as booleans and date/times. 297 | 298 | While, you can set the configuration properties manually, the `Ini.Config` property also provides a fluent API to configure related sets of configurations: 299 | ```cs 300 | Ini.Config 301 | .AllowHashForComments(setAsDefault: true) 302 | .SetSectionPaddingDefaults(insideLeft: 1, insideRight: 1) 303 | .SetPropertyPaddingDefaults(left: 2) 304 | .SetBooleanStrings(trueString: "YES", falseString: "NO") 305 | .SetDateFormats(dateFormat: "M/dd/yy"); 306 | ``` 307 | 308 | ## Formatting the INI content 309 | The `Ini` class retains the exact formatting from the source .INI file content. It provides a `Format` method to correctly format the contents. 310 | 311 | By default, the `Format` method resets the padding for all lines in the INI file. 312 | ```cs 313 | var ini = new Ini(iniFilePath); 314 | ini.Format(); 315 | ini.SaveTo(iniFilePath); 316 | ``` 317 | 318 | In addition, the `Format` method takes an optional `IniFormatOptions` parameter that can specify additional formatting options: 319 | 320 | |Option|Description|Default| 321 | |------|-----------|-------| 322 | |`EnsureBlankLinesBetweenSections`|If true, a blank line is inserted between each section.|`false`| 323 | |`EnsureBlankLinesBetweenProperties`|If true, a blank line is inserted between each property.|`false`| 324 | |`RemoveSuccessiveBlankLines`|If true, any successive blank lines are removed.|`false`| 325 | 326 | ```cs 327 | var ini = new Ini(iniFilePath); 328 | ini.Format(new IniFormatOptions 329 | { 330 | EnsureBlankLinesBetweenSections = true, 331 | RemoveSuccessiveBlankLines = true 332 | }); 333 | ini.SaveTo(iniFilePath); 334 | ``` 335 | -------------------------------------------------------------------------------- /src/IniFile/PropertyValue.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System; 22 | using System.Collections.Generic; 23 | using System.Diagnostics; 24 | using System.Globalization; 25 | 26 | #if NETSTANDARD1_3 27 | using System.Reflection; 28 | #endif 29 | 30 | namespace IniFile 31 | { 32 | /// 33 | /// Represents the value of a property 34 | /// Property values can be strings, numbers (both floats and integers), date/times and booleans. 35 | /// 36 | public readonly struct PropertyValue : IEquatable 37 | { 38 | [DebuggerBrowsable(DebuggerBrowsableState.Never)] 39 | private readonly Type _type; 40 | 41 | /// 42 | /// The value of the property, internally using the actual type. 43 | /// 44 | [DebuggerBrowsable(DebuggerBrowsableState.Never)] 45 | private readonly object _value; 46 | 47 | /// 48 | /// 49 | /// The string representation of the value. This is used if the string representation 50 | /// is different from the string returned by calling _value.ToString(). 51 | /// 52 | /// 53 | /// Boolean values use this, as there can be multiple string representations of a 54 | /// boolean value such as 0, on, etc. 55 | /// 56 | /// 57 | [DebuggerBrowsable(DebuggerBrowsableState.Never)] 58 | private readonly string _stringValue; 59 | 60 | /// 61 | /// Initializes an instance of the structure. 62 | /// 63 | /// 64 | /// The value to initialize the property with. If null, the type is considered a 65 | /// string. 66 | /// 67 | internal PropertyValue(object value) : this(value, null) 68 | { 69 | } 70 | 71 | /// 72 | /// Initializes an instance of the structure where the string 73 | /// representation of the underlying object is different from the string representation 74 | /// of the value in the INI file. 75 | /// 76 | /// 77 | /// The value to initialize the property with. If null, the type is considered a 78 | /// string. 79 | /// 80 | /// 81 | /// The different string representation of the value in the INI file. 82 | /// 83 | internal PropertyValue(object value, string stringValue) 84 | { 85 | _value = value; 86 | _type = _value != null ? _value.GetType() : typeof(string); 87 | _stringValue = stringValue; 88 | } 89 | 90 | /// 91 | /// Returns the string value, if it exists, otherwise returns the 92 | /// result of the object value. If the object value is null, then the returned 93 | /// value is also null. 94 | /// 95 | /// String that represents the current property value. 96 | public override string ToString() 97 | { 98 | return _stringValue ?? _value?.ToString(); 99 | } 100 | 101 | /// 102 | /// Returns the property value as a . 103 | /// 104 | /// 105 | /// A format specifier that defines the required format of the property string value. 106 | /// 107 | /// 108 | /// An object that specifies the culture-specific formatting information about the 109 | /// property string value. 110 | /// 111 | /// The property value as a . 112 | public DateTime AsDateTime(string format, IFormatProvider provider = null) 113 | { 114 | provider ??= CultureInfo.CurrentCulture; 115 | return ConvertTo(this, s => DateTime.TryParseExact( 116 | s, format, provider, DateTimeStyles.None, out DateTime dt) 117 | ? new ConversionResult(dt) : default); 118 | } 119 | 120 | /// 121 | /// Returns the property value as the specified enum. 122 | /// 123 | /// The type of enum to convert the property value to. 124 | /// Indicates whether the property value is case-sensitive. 125 | /// The property value as the specified enum. 126 | public TEnum AsEnum(bool caseSensitive = false) 127 | where TEnum : struct, IConvertible 128 | { 129 | #if NETSTANDARD1_3 130 | if (!typeof(TEnum).GetTypeInfo().IsEnum) 131 | #else 132 | if (!typeof(TEnum).IsEnum) 133 | #endif 134 | throw new InvalidOperationException(); 135 | #if !NET35 136 | if (!Enum.TryParse(ToString(), !caseSensitive, out TEnum value)) 137 | throw new InvalidCastException(string.Format(CultureInfo.CurrentCulture, ErrorMessages.CannotCastPropertyValue, typeof(TEnum).FullName)); 138 | return value; 139 | #else 140 | try 141 | { 142 | var value = (TEnum) Enum.Parse(typeof(TEnum), ToString(), !caseSensitive); 143 | return value; 144 | } 145 | catch (ArgumentException ex) 146 | { 147 | throw new InvalidCastException(string.Format(CultureInfo.CurrentCulture, ErrorMessages.CannotCastPropertyValue, typeof(TEnum).FullName), ex); 148 | } 149 | #endif 150 | } 151 | 152 | public static implicit operator PropertyValue(string value) => new(value); 153 | public static implicit operator string(PropertyValue pvalue) => pvalue.ToString(); 154 | 155 | public static implicit operator PropertyValue(sbyte value) => new(value); 156 | public static implicit operator sbyte(PropertyValue pvalue) => ConvertTo(pvalue, s => 157 | sbyte.TryParse(s, out sbyte value) ? new ConversionResult(value) : default 158 | ); 159 | 160 | public static implicit operator PropertyValue(byte value) => new(value); 161 | public static implicit operator byte(PropertyValue pvalue) => ConvertTo(pvalue, s => 162 | byte.TryParse(s, out byte value) ? new ConversionResult(value) : default 163 | ); 164 | 165 | public static implicit operator PropertyValue(short value) => new(value); 166 | public static implicit operator short(PropertyValue pvalue) => ConvertTo(pvalue, s => 167 | short.TryParse(s, out short value) ? new ConversionResult(value) : default 168 | ); 169 | 170 | public static implicit operator PropertyValue(ushort value) => new(value); 171 | public static implicit operator ushort(PropertyValue pvalue) => ConvertTo(pvalue, s => 172 | ushort.TryParse(s, out ushort value) ? new ConversionResult(value) : default 173 | ); 174 | 175 | public static implicit operator PropertyValue(int value) => new(value); 176 | public static implicit operator int(PropertyValue pvalue) => ConvertTo(pvalue, s => 177 | int.TryParse(s, out int value) ? new ConversionResult(value) : default 178 | ); 179 | 180 | public static implicit operator PropertyValue(uint value) => new(value); 181 | public static implicit operator uint(PropertyValue pvalue) => ConvertTo(pvalue, s => 182 | uint.TryParse(s, out uint value) ? new ConversionResult(value) : default 183 | ); 184 | 185 | public static implicit operator PropertyValue(long value) => new(value); 186 | public static implicit operator long(PropertyValue pvalue) => ConvertTo(pvalue, s => 187 | long.TryParse(s, out long value) ? new ConversionResult(value) : default 188 | ); 189 | 190 | public static implicit operator PropertyValue(ulong value) => new(value); 191 | public static implicit operator ulong(PropertyValue pvalue) => ConvertTo(pvalue, s => 192 | ulong.TryParse(s, out ulong value) ? new ConversionResult(value) : default 193 | ); 194 | 195 | public static implicit operator PropertyValue(float value) => new(value); 196 | public static implicit operator float(PropertyValue pvalue) => ConvertTo(pvalue, s => 197 | float.TryParse(s, out float value) ? new ConversionResult(value) : default 198 | ); 199 | 200 | public static implicit operator PropertyValue(double value) => new(value); 201 | public static implicit operator double(PropertyValue pvalue) => ConvertTo(pvalue, s => 202 | double.TryParse(s, out double value) ? new ConversionResult(value) : default 203 | ); 204 | 205 | public static implicit operator PropertyValue(decimal value) => new(value); 206 | public static implicit operator decimal(PropertyValue pvalue) => ConvertTo(pvalue, s => 207 | decimal.TryParse(s, out decimal value) ? new ConversionResult(value) : default 208 | ); 209 | 210 | public static implicit operator PropertyValue(DateTime value) => 211 | new(value, value.ToString(Ini.Config.Types.DateFormat, CultureInfo.InvariantCulture)); 212 | public static implicit operator DateTime(PropertyValue pvalue) => ConvertTo(pvalue, s => 213 | DateTime.TryParseExact(s, Ini.Config.Types.DateFormat, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime value) 214 | ? new ConversionResult(value) : default 215 | ); 216 | 217 | private static T ConvertTo(PropertyValue pvalue, Func> converter) 218 | where T : struct 219 | { 220 | if (pvalue._value is T typedValue) 221 | return typedValue; 222 | 223 | string stringValue = pvalue.ToString(); 224 | if (stringValue == null) 225 | throw new InvalidCastException(string.Format(CultureInfo.CurrentCulture, ErrorMessages.CannotCastPropertyValue, typeof(T).Name)); 226 | 227 | ConversionResult result = converter(stringValue); 228 | 229 | if (!result.CanConvert) 230 | throw new InvalidCastException(string.Format(CultureInfo.CurrentCulture, ErrorMessages.CannotCastPropertyValue, typeof(T).Name)); 231 | return result.Value; 232 | } 233 | 234 | public static implicit operator PropertyValue(bool value) => 235 | new(value, value ? Ini.Config.Types.TrueString : Ini.Config.Types.FalseString); 236 | 237 | public static implicit operator bool(PropertyValue pvalue) 238 | { 239 | if (pvalue._value is bool boolValue) 240 | return boolValue; 241 | 242 | string stringValue = pvalue.ToString()?.ToUpperInvariant() ?? string.Empty; 243 | 244 | switch (stringValue) 245 | { 246 | case "0": 247 | case "F": 248 | case "N": 249 | case "OFF": 250 | case "NO": 251 | case "DISABLED": 252 | case "FALSE": 253 | return false; 254 | case "1": 255 | case "T": 256 | case "Y": 257 | case "ON": 258 | case "YES": 259 | case "ENABLED": 260 | case "TRUE": 261 | return true; 262 | } 263 | 264 | if (stringValue == Ini.Config.Types.TrueString.ToUpperInvariant()) 265 | return true; 266 | if (stringValue == Ini.Config.Types.FalseString.ToUpperInvariant()) 267 | return false; 268 | 269 | #pragma warning disable S3877 // Exceptions should not be thrown from unexpected methods 270 | #pragma warning disable CA1065 // Do not raise exceptions in unexpected locations 271 | throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ErrorMessages.CannotCastPropertyValue, "boolean"), nameof(pvalue)); 272 | #pragma warning restore CA1065 // Do not raise exceptions in unexpected locations 273 | #pragma warning restore S3877 // Exceptions should not be thrown from unexpected methods 274 | } 275 | 276 | public static bool operator ==(PropertyValue value1, PropertyValue value2) 277 | { 278 | return value1.Equals(value2); 279 | } 280 | 281 | public static bool operator !=(PropertyValue value1, PropertyValue value2) 282 | { 283 | return !(value1 == value2); 284 | } 285 | 286 | /// 287 | public override bool Equals(object obj) 288 | { 289 | return obj is PropertyValue value && Equals(value); 290 | } 291 | 292 | /// 293 | public bool Equals(PropertyValue other) 294 | { 295 | return EqualityComparer.Default.Equals(_type, other._type); 296 | } 297 | 298 | /// 299 | public override int GetHashCode() 300 | { 301 | return -331038658 + EqualityComparer.Default.GetHashCode(_type); 302 | } 303 | 304 | /// 305 | /// Returns whether the property has no value. 306 | /// 307 | /// True, if the value is empty; otherwise false. 308 | public bool IsEmpty() 309 | { 310 | return _value == null && _stringValue == null; 311 | } 312 | 313 | /// 314 | /// Represents an empty property value. 315 | /// 316 | public static readonly PropertyValue Empty = new(null); 317 | } 318 | 319 | internal readonly struct ConversionResult 320 | where T : struct 321 | { 322 | internal ConversionResult(T value) 323 | { 324 | CanConvert = true; 325 | Value = value; 326 | } 327 | 328 | internal bool CanConvert { get; } 329 | 330 | internal T Value { get; } 331 | } 332 | } -------------------------------------------------------------------------------- /src/IniFile/Ini.cs: -------------------------------------------------------------------------------- 1 | #region --- License & Copyright Notice --- 2 | /* 3 | IniFile Library for .NET 4 | Copyright (c) 2018-2021 Jeevan James 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | #endregion 20 | 21 | using System; 22 | using System.Collections.Generic; 23 | using System.Collections.ObjectModel; 24 | using System.Diagnostics; 25 | using System.Diagnostics.CodeAnalysis; 26 | using System.Globalization; 27 | using System.IO; 28 | using System.Linq; 29 | using System.Text; 30 | using System.Text.RegularExpressions; 31 | #if NETSTANDARD 32 | using System.Threading.Tasks; 33 | #endif 34 | 35 | using IniFile.Config; 36 | using IniFile.Items; 37 | 38 | namespace IniFile 39 | { 40 | /// 41 | /// In-memory object representation of an INI file. 42 | /// This class is a read-only collection of objects. 43 | /// 44 | [DebuggerDisplay("INI file - {Count} sections")] 45 | public sealed partial class Ini 46 | { 47 | [DebuggerBrowsable(DebuggerBrowsableState.Never)] 48 | private readonly IniLoadSettings _settings; 49 | 50 | /// 51 | /// Initializes a new empty instance of the class with the default 52 | /// settings. 53 | /// 54 | public Ini() 55 | : this(null) 56 | { 57 | } 58 | 59 | /// 60 | /// Initializes a new empty instance of the class with the specified 61 | /// settings. 62 | /// 63 | /// The Ini file settings. 64 | public Ini(IniLoadSettings settings) 65 | : base(GetEqualityComparer(settings)) 66 | { 67 | } 68 | 69 | 70 | /// 71 | /// Initializes a new instance of the class and loads the data from 72 | /// the specified file. 73 | /// 74 | /// The .ini file to load from. 75 | /// Optional Ini file settings. 76 | /// Thrown if the iniFile is null. 77 | /// Thrown if the specified file does not exist. 78 | public Ini(FileInfo iniFile, IniLoadSettings settings = null) 79 | : base(GetEqualityComparer(settings)) 80 | { 81 | if (iniFile is null) 82 | throw new ArgumentNullException(nameof(iniFile)); 83 | if (!iniFile.Exists) 84 | throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, ErrorMessages.IniFileDoesNotExist, iniFile.FullName), iniFile.FullName); 85 | 86 | _settings = settings ?? IniLoadSettings.Default; 87 | 88 | using var stream = new FileStream(iniFile.FullName, FileMode.Open, FileAccess.Read, FileShare.Read); 89 | using var reader = new StreamReader(stream, _settings.Encoding ?? Encoding.UTF8, _settings.DetectEncoding); 90 | ParseIniFile(reader); 91 | } 92 | 93 | 94 | /// 95 | /// Initializes a new instance of the class and loads the data from 96 | /// the specified file. 97 | /// 98 | /// The path to the .ini file. 99 | /// Optional Ini file settings. 100 | /// Thrown if the iniFilePath is null. 101 | /// Thrown if the specified file does not exist. 102 | public Ini(string iniFilePath, IniLoadSettings settings = null) 103 | : base(GetEqualityComparer(settings)) 104 | { 105 | if (iniFilePath is null) 106 | throw new ArgumentNullException(nameof(iniFilePath)); 107 | if (!File.Exists(iniFilePath)) 108 | throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, ErrorMessages.IniFileDoesNotExist, iniFilePath), iniFilePath); 109 | 110 | _settings = settings ?? IniLoadSettings.Default; 111 | 112 | using var stream = new FileStream(iniFilePath, FileMode.Open, FileAccess.Read, FileShare.Read); 113 | using var reader = new StreamReader(stream, _settings.Encoding ?? Encoding.UTF8, _settings.DetectEncoding); 114 | ParseIniFile(reader); 115 | } 116 | 117 | /// 118 | /// Initializes a new instance of the class and loads the data from 119 | /// the specified stream. 120 | /// 121 | /// The stream to load from. 122 | /// Optional Ini file settings. 123 | /// Thrown if the specified stream is null. 124 | /// Thrown if the stream cannot be read. 125 | public Ini(Stream stream, IniLoadSettings settings = null) 126 | : base(GetEqualityComparer(settings)) 127 | { 128 | if (stream is null) 129 | throw new ArgumentNullException(nameof(stream)); 130 | if (!stream.CanRead) 131 | throw new ArgumentException(ErrorMessages.StreamNotReadable, nameof(stream)); 132 | 133 | _settings = settings ?? IniLoadSettings.Default; 134 | 135 | using var reader = new StreamReader(stream, _settings.Encoding ?? Encoding.UTF8, _settings.DetectEncoding); 136 | ParseIniFile(reader); 137 | } 138 | 139 | /// 140 | /// Initializes a new instance of the class and loads the data from 141 | /// the specified . 142 | /// 143 | /// The to load from. 144 | /// Optional Ini file settings. 145 | /// Thrown if the specified text reader is null. 146 | public Ini(TextReader reader, IniLoadSettings settings = null) 147 | : base(GetEqualityComparer(settings)) 148 | { 149 | if (reader is null) 150 | throw new ArgumentNullException(nameof(reader)); 151 | 152 | _settings = settings ?? IniLoadSettings.Default; 153 | 154 | ParseIniFile(reader); 155 | } 156 | 157 | /// 158 | /// Initializes a new instance of the class and loads the data from 159 | /// the specified string. 160 | /// 161 | /// The string representing the Ini content. 162 | /// Optional Ini file settings. 163 | /// An instance of the class. 164 | public static Ini Load(string content, IniLoadSettings settings = null) 165 | { 166 | using var reader = new StringReader(content); 167 | return new Ini(reader, settings); 168 | } 169 | 170 | /// 171 | /// Transforms the INI content into an in-memory object model. 172 | /// 173 | /// The INI content. 174 | private void ParseIniFile(TextReader reader) 175 | { 176 | // Go through all the lines and build a flat collection of INI objects. 177 | IList lineItems = ParseLines(reader).ToList(); 178 | 179 | // Go through the line objects and construct an object model. 180 | CreateObjectModel(_settings, lineItems); 181 | } 182 | 183 | /// 184 | /// Read all lines from the given text reader, parse and generate INI objects such as 185 | /// section, properties, comments and blank lines. 186 | /// 187 | /// The instance to read the INI lines from. 188 | /// 189 | private static IEnumerable ParseLines(TextReader reader) 190 | { 191 | // These variables track multiline values 192 | Property mlProperty = null; 193 | var mlValue = new StringBuilder(); 194 | string mlEot = null; 195 | 196 | for (string line = reader.ReadLine(); line != null; line = reader.ReadLine()) 197 | { 198 | // Are we in the middle of a multi-line property value? 199 | // If so, continue adding the line strings to the mlValue variable until the EOT 200 | // string is found. 201 | if (mlProperty != null) 202 | { 203 | // Have we reached the end of the multi-line value (end-of-text)? 204 | if (line.Trim() == mlEot) // EOT is case-sensitive 205 | { 206 | mlProperty.Value = mlValue.ToString(); 207 | mlProperty = null; 208 | } 209 | else 210 | { 211 | if (mlValue.Length > 0) 212 | mlValue.AppendLine(); 213 | mlValue.Append(line); 214 | } 215 | } 216 | else 217 | { 218 | IniItem iniItem = IniItemFactory.CreateItem(line); 219 | 220 | if (iniItem is Property property) 221 | { 222 | // If the property value matches the start of a multiline value, enable multiline 223 | // mode (mlProperty != null) and add all subsequent lines until the end-of-text 224 | // marker is found. 225 | Match match = MultilineStartPattern.Match(property.Value); 226 | if (match.Success) 227 | { 228 | // Start tracking the multiline value until we encounter an end-of-text (EOT) line 229 | mlProperty = property; 230 | #if NET35 231 | mlValue = new StringBuilder(); 232 | #else 233 | mlValue.Clear(); 234 | #endif 235 | mlEot = match.Groups[1].Value; 236 | } 237 | } 238 | 239 | yield return iniItem; 240 | } 241 | } 242 | } 243 | 244 | private static readonly Regex MultilineStartPattern = new(@"^<<(\w+)$"); 245 | 246 | /// 247 | /// Go through each line object and construct a hierarchical object model, with properties 248 | /// under sections and comments/blank lines under respective sections and properties. 249 | /// 250 | /// The INI load settings that control the load behavior. 251 | /// 252 | private void CreateObjectModel(IniLoadSettings settings, IList lineItems) 253 | { 254 | Section currentSection = null; 255 | var minorItems = new List(); 256 | foreach (IniItem item in lineItems) 257 | { 258 | switch (item) 259 | { 260 | case BlankLine blankLine when !settings.IgnoreBlankLines: 261 | minorItems.Add(blankLine); 262 | break; 263 | case Comment comment when !settings.IgnoreComments: 264 | minorItems.Add(comment); 265 | break; 266 | case Section section: 267 | currentSection = section; 268 | AddRangeAndClear(currentSection.Items, minorItems); 269 | Add(currentSection); 270 | break; 271 | case Property property when currentSection is null: 272 | throw new FormatException(string.Format(CultureInfo.CurrentCulture, ErrorMessages.PropertyWithoutSection, property.Name)); 273 | case Property property: 274 | AddRangeAndClear(property.Items, minorItems); 275 | if (currentSection is null) 276 | throw new InvalidOperationException(ErrorMessages.CreateObjectModelInvalidCurrentSection); 277 | currentSection.Add(property); 278 | break; 279 | } 280 | } 281 | 282 | // If there are comments or blank lines at the end of the INI, which cannot be assigned 283 | // to any section or property, assign them to the TrailingItems collection. 284 | if (minorItems.Count > 0) 285 | AddRangeAndClear(TrailingItems, minorItems); 286 | } 287 | 288 | private static void AddRangeAndClear(IList source, IList minorItems) 289 | { 290 | foreach (MinorIniItem item in minorItems) 291 | source.Add(item); 292 | minorItems.Clear(); 293 | } 294 | 295 | /// 296 | /// Saves the instance to a file at the specified file path. 297 | /// 298 | /// The path of the file to save to. 299 | public void SaveTo(string filePath) 300 | { 301 | using StreamWriter writer = File.CreateText(filePath); 302 | SaveTo(writer); 303 | } 304 | 305 | /// 306 | /// Saves the instance to the specified file. 307 | /// 308 | /// The object that represents the file to save to. 309 | /// Thrown if the specified file is null. 310 | public void SaveTo(FileInfo file) 311 | { 312 | if (file is null) 313 | throw new ArgumentNullException(nameof(file)); 314 | using StreamWriter writer = File.CreateText(file.FullName); 315 | SaveTo(writer); 316 | } 317 | 318 | /// 319 | /// Saves the instance to the specified stream. 320 | /// 321 | /// The stream to save to. 322 | /// Thrown if the specified stream is null. 323 | /// Thrown if the stream cannot be written to. 324 | public void SaveTo(Stream stream) 325 | { 326 | if (stream is null) 327 | throw new ArgumentNullException(nameof(stream)); 328 | if (!stream.CanWrite) 329 | throw new ArgumentException(ErrorMessages.StreamNotWritable, nameof(stream)); 330 | using var writer = new StreamWriter(stream); 331 | SaveTo(writer); 332 | } 333 | 334 | /// 335 | /// Saves the instance to the specified text writer. 336 | /// 337 | /// The text writer to save to. 338 | /// Thrown if the specified text writer is null. 339 | public void SaveTo(TextWriter writer) 340 | { 341 | if (writer is null) 342 | throw new ArgumentNullException(nameof(writer)); 343 | InternalSave(writer); 344 | writer.Flush(); 345 | } 346 | 347 | /// 348 | /// Constructs a string representing the instance data. 349 | /// 350 | /// A string representing the instance data. 351 | public override string ToString() 352 | { 353 | var sb = new StringBuilder(); 354 | using var writer = new StringWriter(sb); 355 | InternalSave(writer); 356 | return sb.ToString(); 357 | } 358 | 359 | /// 360 | /// Common method called to save the instance data to various destinations 361 | /// such as streams, strings, files and text writers. 362 | /// 363 | /// The text writer to write the data to. 364 | private void InternalSave(TextWriter writer) 365 | { 366 | foreach (Section section in this) 367 | { 368 | foreach (MinorIniItem minorItem in section.Items) 369 | writer.WriteLine(minorItem.ToString()); 370 | writer.WriteLine(section.ToString()); 371 | foreach (Property property in section) 372 | { 373 | foreach (MinorIniItem minorItem in property.Items) 374 | writer.WriteLine(minorItem.ToString()); 375 | writer.WriteLine(property.ToString()); 376 | } 377 | } 378 | 379 | foreach (MinorIniItem trailingItem in TrailingItems) 380 | writer.WriteLine(trailingItem.ToString()); 381 | } 382 | 383 | #if NETSTANDARD 384 | /// 385 | /// Saves the instance to the specified stream asynchronously. 386 | /// 387 | /// The stream to save to. 388 | /// A task representing the asynchronous save operation. 389 | /// Thrown if the specified stream is null. 390 | /// Thrown if the stream cannot be written to. 391 | public Task SaveToAsync(Stream stream) 392 | { 393 | if (stream is null) 394 | throw new ArgumentNullException(nameof(stream)); 395 | if (!stream.CanWrite) 396 | throw new ArgumentException(ErrorMessages.StreamNotWritable, nameof(stream)); 397 | return SaveToAsyncInternal(stream); 398 | } 399 | 400 | private async Task SaveToAsyncInternal(Stream stream) 401 | { 402 | using var writer = new StreamWriter(stream); 403 | await SaveToAsync(writer).ConfigureAwait(false); 404 | } 405 | 406 | /// 407 | /// Saves the instance to the specified text writer asynchronously. 408 | /// 409 | /// The text writer to save to. 410 | /// A task representing the asynchronous save operation. 411 | /// Thrown if the specified text writer is null. 412 | public Task SaveToAsync(TextWriter writer) 413 | { 414 | if (writer is null) 415 | throw new ArgumentNullException(nameof(writer)); 416 | return SaveToAsyncInternal(writer); 417 | } 418 | 419 | private async Task SaveToAsyncInternal(TextWriter writer) 420 | { 421 | await InternalSaveAsync(writer).ConfigureAwait(false); 422 | await writer.FlushAsync().ConfigureAwait(false); 423 | } 424 | 425 | /// 426 | /// Common method called to asynchronously save the instance data to 427 | /// various destinations such as streams, strings, files and text writers. 428 | /// 429 | /// The text writer to write the data to. 430 | /// A task representing the asynchronous save operation. 431 | private async Task InternalSaveAsync(TextWriter writer) 432 | { 433 | foreach (Section section in this) 434 | { 435 | foreach (MinorIniItem minorItem in section.Items) 436 | await writer.WriteLineAsync(minorItem.ToString()).ConfigureAwait(false); 437 | await writer.WriteLineAsync(section.ToString()).ConfigureAwait(false); 438 | foreach (Property property in section) 439 | { 440 | foreach (MinorIniItem minorItem in property.Items) 441 | await writer.WriteLineAsync(minorItem.ToString()).ConfigureAwait(false); 442 | await writer.WriteLineAsync(property.ToString()).ConfigureAwait(false); 443 | } 444 | } 445 | 446 | foreach (MinorIniItem trailingItem in TrailingItems) 447 | await writer.WriteLineAsync(trailingItem.ToString()).ConfigureAwait(false); 448 | } 449 | #endif 450 | 451 | /// 452 | /// Any trailing comments and blank lines at the end of the INI. 453 | /// 454 | public IList TrailingItems { get; } = new List(); 455 | 456 | 457 | /// 458 | /// Formats the INI content by resetting all padding and applying any formatting rules 459 | /// as per the options parameter. 460 | /// 461 | /// 462 | /// Optional rules for formatting the INI content. Uses default rules 463 | /// () if not specified. 464 | /// 465 | [SuppressMessage("Major Code Smell", "S1066:Collapsible \"if\" statements should be merged", Justification = "")] 466 | public void Format(IniFormatOptions options = null) 467 | { 468 | options ??= IniFormatOptions.Default; 469 | 470 | for (int s = 0; s < Count; s++) 471 | { 472 | Section section = this[s]; 473 | 474 | // Reset padding for each minor item in the section 475 | FormatMinorItems(section.Items); 476 | 477 | // Insert blank line between sections, if specified by options 478 | if (options.EnsureBlankLineBetweenSections) 479 | { 480 | if (s > 0 && (section.Items.Count == 0 || !(section.Items[0] is BlankLine))) 481 | section.Items.Insert(0, new BlankLine()); 482 | } 483 | 484 | // Reset padding for the section itself 485 | section.Padding.Reset(); 486 | 487 | for (int p = 0; p < section.Count; p++) 488 | { 489 | Property property = section[p]; 490 | 491 | // Reset padding for each minor item in the property 492 | FormatMinorItems(property.Items); 493 | 494 | // Insert blank line between properties, if specified 495 | if (options.EnsureBlankLineBetweenProperties) 496 | { 497 | if (p > 0 && (property.Items.Count == 0 || !(property.Items[0] is BlankLine))) 498 | property.Items.Insert(0, new BlankLine()); 499 | } 500 | 501 | // Reset padding for the property itself 502 | property.Padding.Reset(); 503 | } 504 | } 505 | 506 | // Remove any trailing blank lines 507 | for (int i = TrailingItems.Count - 1; i >= 0; i--) 508 | { 509 | // Non blank lines are fine, so when we encounter the first non blank line, exit 510 | // this loop. 511 | if (!(TrailingItems[i] is BlankLine)) 512 | break; 513 | if (TrailingItems[i] is BlankLine) 514 | TrailingItems.RemoveAt(i); 515 | } 516 | 517 | // Format any remaining trailing items 518 | FormatMinorItems(TrailingItems); 519 | } 520 | 521 | 522 | /// 523 | /// Formats a collection of minor INI items (i.e. and 524 | /// ). By default, the paddings for these items are reset, but the 525 | /// method can also optionally remove consecutive blank lines. 526 | /// 527 | /// The collection of items to format. 528 | private static void FormatMinorItems(IList minorItems) 529 | { 530 | foreach (MinorIniItem minorItem in minorItems) 531 | { 532 | if (minorItem is BlankLine blankLine) 533 | blankLine.Padding.Reset(); 534 | else if (minorItem is Comment comment) 535 | comment.Padding.Reset(); 536 | } 537 | 538 | for (int i = minorItems.Count - 1; i > 0; i--) 539 | { 540 | if (minorItems[i] is BlankLine && minorItems[i - 1] is BlankLine) 541 | minorItems.RemoveAt(i); 542 | } 543 | } 544 | 545 | /// 546 | /// Global configuration for the framework. 547 | /// 548 | public static readonly IniGlobalConfig Config = new(); 549 | } 550 | 551 | public sealed partial class Ini : KeyedCollection 552 | { 553 | public new Section this[string key] 554 | { 555 | get 556 | { 557 | if (key is null) 558 | throw new ArgumentNullException(nameof(key)); 559 | 560 | IEqualityComparer comparer = GetEqualityComparer(_settings); 561 | foreach (Section section in this) 562 | { 563 | if (comparer.Equals(key, section.Name)) 564 | return section; 565 | } 566 | return null; 567 | } 568 | } 569 | 570 | protected override string GetKeyForItem(Section item) 571 | { 572 | if (item is null) 573 | throw new ArgumentNullException(nameof(item)); 574 | return item.Name; 575 | } 576 | 577 | private static IEqualityComparer GetEqualityComparer(IniLoadSettings settings) 578 | { 579 | settings ??= IniLoadSettings.Default; 580 | return settings.CaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; 581 | } 582 | } 583 | } --------------------------------------------------------------------------------