【.NET类库】ClsoINI 一个简单易用的 .NET INI配置文件读写库
我自己喜欢写点小程序,经常会需要保存一些配置与参数。
如果数据量比较多、比较杂乱,我还是推荐使用 Newtonsoft.Json 或者新版本内置的 System.Text.Json 。
Json格式较容易序列化与反序列化,不需要自己关注太多,使用较为轻松。
然而若是程序比较小,存储的数据量不是很多的话,我个人经常使用的是INI配置文件。
INI这种文件十分古老,格式也非常简单,并且 Windows 系统内部就有读写INI的API,如
GetPrivateProfileString
和 WritePrivateProfileString
。不过
GetPrivateProfileString
和 WritePrivateProfileString
这两个API接口并不支持读取内存中的数据,或者是文件流、网络流,所以在某些时候使用并不方便。因此,我自己就写了一个自用的INI类库,方便自己更容易读写INI配置文件,同时也方便自己控制何时读取、从哪里读取、何时写入数据。
废话不多说,VB.NET 源码:
' ClsoINI Pure Edition
' 0.5.5 将创造函数的 createfile 默认设置为 false
' 0.5.4
' (c)2020-2023 clso.fun
' https://clso.fun/go/?clsoini
'
' Licenses LGPL
' https://www.gnu.org/licenses/lgpl-3.0.html
' http://www.thebigfly.com/gnu/lgpl/lgpl-v3.php
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.IO
#If _COMP Then
<Assembly: System.Reflection.AssemblyVersion("0.5.5.0")>
<Assembly: System.Reflection.AssemblyFileVersion("0.5.5.0")>
<Assembly: System.Reflection.AssemblyTitle("ClsoINI")>
<Assembly: System.Reflection.AssemblyDescription("https://clso.fun")>
<Assembly: System.Reflection.AssemblyCompany("clso.fun")>
<Assembly: System.Reflection.AssemblyProduct("Clso Lib - ClsoINI")>
<Assembly: System.Reflection.AssemblyCopyright("©2020-2023 clso.fun")>
<Assembly: System.Reflection.AssemblyTrademark("clso.fun")>
<Assembly: System.Runtime.InteropServices.ComVisible(False)>
#End If
Public Class ClsoINIPure
Dim sections As New Dictionary(Of String, Dictionary(Of String, String))(StringComparer.OrdinalIgnoreCase)
Sub New()
End Sub
''' <summary></summary>
''' <param name="content">INI内容文本</param>
Sub New(ByVal content As String)
Parser(content)
End Sub
''' <param name="filePath">文件路径</param>
''' <param name="enc">文件编码,传递Null时默认为UTF8</param>
''' <param name="createfile">如果文件不存在,是否尝试创建文件</param>
Sub New(ByVal filePath As String, ByVal enc As Encoding, Optional ByVal createfile As Boolean = False)
If enc Is Nothing Then enc = Me.Encoding Else Me.Encoding = enc
Dim content As String = ""
If Not File.Exists(filePath) Then
If createfile Then File.Create(filePath).Close()
Else
content = File.ReadAllText(filePath, enc)
End If
Parser(content)
End Sub
Sub New(ByVal file As FileInfo, Optional ByVal enc As Encoding = Nothing, Optional ByVal createfile As Boolean = False)
Dim content As String = ""
If file.Exists Then
If enc Is Nothing Then enc = Me.Encoding Else Me.Encoding = enc
Using sr = New StreamReader(file.OpenRead, enc)
content = sr.ReadToEnd
End Using
Else
If createfile Then file.Create.Close()
End If
Parser(content)
End Sub
''' <param name="s">文件流</param>
''' <param name="enc">文件编码,传递Null时默认为UTF8</param>
''' <param name="closeStream">是否自动关闭文件流</param>
Sub New(ByVal s As Stream, Optional ByVal enc As Encoding = Nothing, Optional ByVal closeStream As Boolean = False)
If enc Is Nothing Then enc = Me.Encoding Else Me.Encoding = enc
Dim sr As New StreamReader(s, enc)
s.Position = 0
Dim content As String = sr.ReadToEnd
If closeStream Then
sr.Dispose()
s.Dispose()
End If
Parser(content)
End Sub
''' <summary>解析INI内容</summary>
''' <param name="clearContent">是否清理已存储的内容</param>
Sub Parser(ByVal content As String, Optional ByVal clearContent As Boolean = False)
If clearContent Then Me.Clear()
Dim lines As String() = content.Split(New String() {vbCrLf, vbCr, vbLf}, StringSplitOptions.RemoveEmptyEntries)
Dim m As Match
Dim opt As RegexOptions = RegexOptions.IgnoreCase
Dim p1 As String = "^\s*(;|#)"
Dim p2 As String = "^\s*[\s*(.+?)\s*\]\s*$"
Dim p3 As String = "^\s*(.+?)\s*=\s*(.+)\s*$"
Dim sname As String = Nothing '节点
Dim kv As Dictionary(Of String, String) = Nothing '节点的键值字典
For Each line As String In lines
' 忽略注释
If Regex.IsMatch(line, p1, opt) Then
Continue For
End If
' 节点
m = Regex.Match(line, p2, opt)
If m.Success Then
sname = m.Groups(1).Value '节点名
If Me.sections.ContainsKey(sname) Then '如果存在节点名,则从字典中取出
kv = Me.sections(sname)
Else '如果不存在节点,则创建并加入字典
kv = New Dictionary(Of String, String)(StringComparer.OrdinalIgnoreCase)
Me.sections(sname) = kv
End If
Continue For
End If
' 键值
m = Regex.Match(line, p3, opt)
If m.Success Then
If kv Is Nothing Then '若未定义节点,则默认以 @ 为节点名
sname = "@"
kv = New Dictionary(Of String, String)(StringComparer.OrdinalIgnoreCase)
Me.sections(sname) = kv
End If
kv(m.Groups(1).Value) = m.Groups(2).Value 'kv赋值
End If
Next
End Sub
''' <summary>清空内容</summary>
''' <param name="clearGC">手动清理GC</param>
Sub Clear(Optional ByVal clearGC As Boolean = False)
For Each kv In Me.sections
kv.Value.Clear()
Next
Me.sections.Clear()
If clearGC Then GC.Collect()
End Sub
''' <summary>获取或设置默认节点下的值</summary>
Default Property Value(ByVal key As String) As String
Get
Return GetValue("@", key)
End Get
Set(ByVal value As String)
SetValue("@", key, value)
End Set
End Property
''' <summary>获取或设置指定节点下的值</summary>
Default Property Value(ByVal section As String, ByVal key As String) As String
Get
Return GetValue(section, key)
End Get
Set(ByVal value As String)
SetValue(section, key, value)
End Set
End Property
''' <summary>默认编码</summary>
Property Encoding As Encoding = Encoding.UTF8
''' <summary>获取所有节点字典</summary>
Friend ReadOnly Property SectionsDict() As Dictionary(Of String, Dictionary(Of String, String))
Get
Return Me.sections
End Get
End Property
''' <summary>获取指定节点字典</summary>
Friend ReadOnly Property SectionDict(ByVal section As String) As Dictionary(Of String, String)
Get
Return If(Me.sections.ContainsKey(section), Me.sections(section), Nothing)
End Get
End Property
''' <summary>获取默认节点下的值</summary>
Function GetValue(ByVal key As String) As String
Return GetValue("@", key)
End Function
''' <summary>获取指定节点下的值</summary>
Function GetValue(ByVal section As String, ByVal key As String) As String
If Not Me.sections.ContainsKey(section) Then Return Nothing
If Not Me.sections(section).ContainsKey(key) Then Return Nothing
Return Me.sections(section)(key)
End Function
''' <summary>设置默认节点下的值</summary>
Sub SetValue(ByVal key As String, ByVal value As String)
SetValue("@", key, value)
End Sub
''' <summary>设置指定节点下的值</summary>
Sub SetValue(ByVal section As String, ByVal key As String, ByVal value As String)
Dim kv As Dictionary(Of String, String)
If Me.sections.ContainsKey(section) Then '如果存在节名,则从字典中取出
kv = Me.sections(section)
Else '如果不存在节,则创建并加入字典
kv = New Dictionary(Of String, String)(StringComparer.OrdinalIgnoreCase)
Me.sections(section) = kv
End If
kv(key) = value
End Sub
''' <summary>[节点] 键 = 值 | 键=值</summary>
Sub SetValue(ByVal kvs As String)
Dim m As Match
m = Regex.Match(kvs, "^\s*(?:[(.+?)\]\s*)?(.+?)\s*=\s*(.+)\s*$", RegexOptions.IgnoreCase)
If m.Success Then
Dim s = m.Groups(1).Value
Dim k = m.Groups(2).Value
Dim v = m.Groups(3).Value
If s = Nothing Then
SetValue("@", k, v)
Else
SetValue(s, k, v)
End If
End If
End Sub
''' <summary>移除指定节点</summary>
Sub RemoveSection(ByVal section As String)
If Me.sections.ContainsKey(section) Then Me.sections.Remove(section)
End Sub
''' <summary>移除默认节点下的键</summary>
Sub RemoveKey(ByVal key As String)
RemoveKey("@", key)
End Sub
''' <summary>移除指定节点下的键</summary>
Sub RemoveKey(ByVal section As String, ByVal key As String)
If Me.sections.ContainsKey(section) AndAlso Me.sections(section).ContainsKey(key) Then
Me.sections(section).Remove(key)
End If
End Sub
''' <summary>清理空的节点和元素</summary>
''' <param name="clearEle">是否清理空的元素</param>
Sub ClearContent(Optional ByVal clearEle As Boolean = True)
' 总结点为空,直接跳出
If Me.sections.Count < 1 Then Return
' 取出所有节点名
Dim skeys(Me.sections.Count - 1) As String
Me.sections.Keys.CopyTo(skeys, 0)
For i As Integer = 0 To skeys.Length - 1
' 取出当前节点名
Dim skname As String = skeys(i)
' 如果需要清理空元素
If clearEle Then
' 取出当前节点
Dim skv = Me.sections(skname)
' 当前节点下的所有元素名
Dim ekeys(skv.Count - 1) As String
skv.Keys.CopyTo(ekeys, 0)
' 取出所有元素值并进行比对、删除
For Each ename As String In ekeys
If skv(ename) = Nothing Then skv.Remove(ename) '删除元素
Next
End If
' 清理空节点
If Me.sections(skname).Count < 1 Then
Me.sections.Remove(skname)
End If
Next
End Sub
''' <summary>比较元素并清理</summary>
Sub ClearContent(ByVal eleComp As DEleComp)
ClearContentComp(eleComp)
End Sub
''' <summary>比较节点并清理</summary>
Sub ClearContent(ByVal secComp As DSecComp)
ClearContentComp(Nothing, secComp)
End Sub
''' <summary>比较元素与节点并清理</summary>
Sub ClearContent(ByVal eleComp As DEleComp, ByVal secComp As DSecComp)
ClearContentComp(eleComp, secComp)
End Sub
''' <summary>对元素进行比较,返回 true 删除元素</summary>
Delegate Function DEleComp(ByVal Section As String, ByVal Key As String, ByVal Value As String) As Boolean
''' <summary>对当前节点进行比较,返回 true 删除当前节点</summary>
Delegate Function DSecComp(ByVal Section As String) As Boolean
''' <summary>清理空的节点和元素</summary>
''' <param name="eleComp">对元素进行自定义比较,返回 true 删除元素</param>
''' <param name="secComp">对节点进行自定义比较,返回 true 删除元素</param>
Sub ClearContentComp(Optional ByVal eleComp As DEleComp = Nothing, Optional ByVal secComp As DSecComp = Nothing)
If Me.sections.Count < 1 Then Return
Dim skeys(Me.sections.Count - 1) As String
Me.sections.Keys.CopyTo(skeys, 0)
For i As Integer = 0 To skeys.Length - 1
Dim skname As String = skeys(i)
Dim skv = Me.sections(skname)
Dim ekeys(skv.Count - 1) As String
skv.Keys.CopyTo(ekeys, 0)
' 先执行节点的比较
If secComp IsNot Nothing AndAlso secComp(skname) Then
Me.sections.Remove(skname)
Continue For
End If
' 再执行详细内容的比较
If eleComp IsNot Nothing Then
For Each ename As String In ekeys
If eleComp(skname, ename, skv(ename)) Then skv.Remove(ename) '删除元素
Next
End If
' 清理空节点
If Me.sections(skname).Count < 1 Then
Me.sections.Remove(skname)
End If
Next
End Sub
''' <summary>获取文本内容表示</summary>
Function OutputIni(Optional ByVal noInfo As Boolean = False) As String
Dim sb As New StringBuilder
If noInfo = False Then
sb.AppendLine("# ClsoINI https://clso.fun/go/?clsoini")
sb.AppendLine()
End If
If Me.sections.ContainsKey("@") Then
Dim kv = Me.sections("@")
For Each ks In kv
sb.AppendLine(ks.Key & "=" & ks.Value)
Next
sb.AppendLine()
End If
For Each kv In Me.sections
If kv.Key = "@" Then Continue For
sb.AppendLine("[" & kv.Key & "]")
For Each ks In kv.Value
sb.AppendLine(ks.Key & "=" & ks.Value)
Next
sb.AppendLine()
Next
Return sb.ToString
End Function
''' <summary>保存文本内容到指定文件</summary>
Sub SaveIni(ByVal filePath As String, Optional ByVal enc As Encoding = Nothing, Optional ByVal noInfo As Boolean = False)
If enc Is Nothing Then enc = Me.Encoding
File.WriteAllText(filePath, OutputIni(noInfo), enc)
End Sub
''' <summary>保存文本内容到流</summary>
Sub SaveIni(ByVal s As Stream, Optional ByVal enc As Encoding = Nothing, Optional ByVal noInfo As Boolean = False)
If enc Is Nothing Then enc = Me.Encoding
Dim sw As New StreamWriter(s, enc)
sw.Write(OutputIni(noInfo))
sw.Flush()
End Sub
End Class
此库原本是一个简化版的 INI 类库,但在实际使用时,我发现其功能已完全够用,所以我一直在更新的就是这个 ClsoINIPure,而非原来更加冗余、庞大、复杂的版本。使用方法很简单,直接构造 ClsoINIPure,你可以直接指定读取的文件,或者是INI的文本内容,亦或者新建一个空INI,方便之后直接读写。
Dim ini As New ClsoINIPure("路径或者内容,空数据时新建INI配置文件")
Dim value As String = ini("项目名", "键名") '读取一个参数值
ini("项目名", "键名") = "一个新的参数值" '写入一个参数值
ini.SetValue("[项目名] 键名 = 参数值") '快速写入一个参数值
ini.RemoveSection("项目名") '移除一整个项内的参数
ini.RemoveKey("项目名", "键名") '移除一个键
ini.SaveIni("文件路径或是文件流") '立刻存储INI数据到文件
Dim IniContent As String = ini.OutputIni() '立刻获取INI完整文本数据
其他用法请参阅源码,如果您非VB用户,可以直接使用我编译的 ClsoINIPure.dll
。如果你需要修改部分源码,可以使用里面的 ClsoINIPure.bat 进行动态编译。
请注意:本类库采用 LGPL 协议,您可以商业使用此类库,但任何的修改行为都需要公开此类库的源码。
类库源码及DLL文件打包下载:
https://www.lanzouj.com/iJRn81gfmndg
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。