1 #Region "Microsoft.VisualBasic::f3c79c0c61f7a108524347607d3a52d3, Microsoft.VisualBasic.Core\ComponentModel\Settings\Inf\INIProfile.vb"
2
3     ' Author:
4     
5     '       asuka (amethyst.asuka@gcmodeller.org)
6     '       xie (genetics@smrucc.org)
7     '       xieguigang (xie.guigang@live.com)
8     
9     ' Copyright (c) 2018 GPL3 Licensed
10     
11     
12     ' GNU GENERAL PUBLIC LICENSE (GPL3)
13     
14     
15     ' This program is free software: you can redistribute it and/or modify
16     ' it under the terms of the GNU General Public License as published by
17     ' the Free Software Foundation, either version 3 of the License, or
18     ' (at your option) any later version.
19     
20     ' This program is distributed in the hope that it will be useful,
21     ' but WITHOUT ANY WARRANTY; without even the implied warranty of
22     ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23     ' GNU General Public License for more details.
24     
25     ' You should have received a copy of the GNU General Public License
26     ' along with this program. If not, see <http://www.gnu.org/licenses/>.
27
28
29
30     ' /********************************************************************************/
31
32     ' Summaries:
33
34     '     Module INIProfile
35     
36     '         Function: (+2 OverloadsGetPrivateProfileString, isCommentsOrBlank, PopulateSections, readDataLines, WritePrivateProfileString
37     
38     '         Sub: WritePrivateProfileString
39     
40     
41     ' /********************************************************************************/
42
43 #End Region
44
45 Imports System.Runtime.CompilerServices
46 Imports Microsoft.VisualBasic.CommandLine.Reflection
47 Imports Microsoft.VisualBasic.Language
48 Imports Microsoft.VisualBasic.Scripting.MetaData
49 Imports Microsoft.VisualBasic.Text.Xml.Models
50 Imports r = System.Text.RegularExpressions.Regex
51
52 Namespace ComponentModel.Settings.Inf
53
54     ''' <summary>
55     ''' Wrapper class for *.ini and *.inf configure file.
56     ''' (可能文件中的注释行会受到影响,所以请尽量使用本类型中的两个静态函数来操作INI文件)
57     ''' </summary>
58     ''' <remarks></remarks>
59     ''' 
60     <Package("Settings.Inf", Description:="Wrapper class for *.ini and *.inf configure file.", Url:="http://gcmodeller.org", Publisher:="xie.guigang@live.com")>
61     Public Module INIProfile
62
63         Const RegexoSectionHeader$ = "^\s*\[[^]]+\]\s*$"
64         Const RegexpKeyValueItem$ = "^\s*[^=]+\s*=\s*.*$"
65
66         ''' <summary>
67         ''' 在读取的时候会将注释行以及空白行给删除掉
68         ''' </summary>
69         ''' <param name="path"></param>
70         ''' <returns></returns>
71         ''' 
72         <Extension>
73         Private Function readDataLines(path As StringAs IEnumerable(Of String)
74             Return From line As String
75                    In path.ReadAllLines
76                    Let strLine As String = line.Trim
77                    Where Not strLine.isCommentsOrBlank
78                    Select strLine
79         End Function
80
81         ''' <summary>
82         ''' Get profile data from the ini file which the data is stores in a specific path like: ``section/key``
83         ''' </summary>
84         ''' <param name="path"></param>
85         ''' <param name="key"></param>
86         ''' <param name="section">
87         ''' 因为这个函数是使用正则表达式进行匹配的,所以section名称不可以有正则表达式之中的特殊符号
88         ''' </param>
89         ''' <returns></returns>
90         ''' <remarks></remarks>
91         <MethodImpl(MethodImplOptions.AggressiveInlining)>
92         <ExportAPI("GetValue"Info:="Get profile data from the ini file which the data is stores in a specific path like: ``section/key``")>
93         Public Function GetPrivateProfileString(section$, key$, path$) As String
94             Return path.readDataLines _
95                 .ToArray _
96                 .GetPrivateProfileString(section, key)
97         End Function
98
99         ''' <summary>
100         ''' 解析ini配置文件数据为通用数据模型
101         ''' </summary>
102         ''' <param name="path"></param>
103         ''' <returns></returns>
104         Public Iterator Function PopulateSections(path As StringAs IEnumerable(Of Section)
105             Dim sectionName$ = Nothing
106             Dim values As New List(Of NamedValue)
107
108             For Each line As String In path.readDataLines
109                 If r.Match(line.Trim, RegexoSectionHeader).Success Then
110                     ' 找到了新的section的起始
111                     ' 则将前面的数据抛出
112                     If Not sectionName.StringEmpty Then
113                         Yield New Section With {
114                             .Name = sectionName,
115                             .Items = values
116                         }
117                     End If
118
119                     values *= 0
120                     sectionName = line.GetStackValue("[""]")
121                 ElseIf r.Match(line, RegexpKeyValueItem, RegexICSng).Success Then
122                     With line.Trim.GetTagValue("=", trim:=True)
123                         values += New NamedValue(.Name, .Value)
124                     End With
125                 End If
126             Next
127
128             ' 抛出剩余的数据
129             If Not sectionName.StringEmpty Then
130                 Yield New Section With {
131                     .Name = sectionName,
132                     .Items = values
133                 }
134             End If
135         End Function
136
137         ''' <summary>
138         ''' Get profile data from the ini file data lines which stores in a specific path like: ``section/key``
139         ''' </summary>
140         ''' <param name="lines$"></param>
141         ''' <param name="section$"></param>
142         ''' <param name="key$"></param>
143         ''' <returns></returns>
144         <Extension>
145         Public Function GetPrivateProfileString(lines$(), section$, key$) As String
146             Dim sectionFind$ = String.Format("^\s*\[{0}\]\s*$", section)
147             Dim keyFind$ = String.Format("^{0}\s*=\s*.*$", key)
148
149             For index As Integer = 0 To lines.Length - 1
150                 If r.Match(lines(index), sectionFind, RegexICSng).Success Then
151
152                     ' 找到了section的起始,则下面的数据到下一个section出现之前都是需要进行查找的数据
153                     For i As Integer = index + 1 To lines.Length - 1
154                         Dim line As String = lines(i)
155
156                         If r.Match(line.Trim, keyFind, RegexICSng).Success Then
157                             Return line.GetTagValue("=", trim:=True).Value
158                         ElseIf r.Match(line.Trim, RegexoSectionHeader).Success Then
159                             ' 已经匹配到了下一个section的起始了
160                             ' 没有找到,则返回空值
161                             Return ""
162                         End If
163                     Next
164                 End If
165             Next
166
167             Return ""
168         End Function
169
170         ''' <summary>
171         ''' 判断当前的行是否是空白或者注释行
172         ''' </summary>
173         ''' <param name="str"></param>
174         ''' <returns></returns>
175         <MethodImpl(MethodImplOptions.AggressiveInlining)>
176         <Extension>
177         Private Function isCommentsOrBlank(str As StringAs Boolean
178             Return String.IsNullOrEmpty(str) OrElse (str.First = ";"OrElse str.First = "#"c)
179         End Function
180
181         <Extension>
182         Public Function WritePrivateProfileString(lines As List(Of String), section$, key$, value$) As String()
183             Dim sectionFind As String = $"^\s*\[{section}\]\s*$"
184             ' 当存在该Section的时候,则从该Index位置处开始进行key的搜索
185             Dim keyFind As String = $"^\s*{key}\s*=\s*.*$"
186             Dim appendSection As Boolean = True
187
188             For index As Integer = 0 To lines.Count - 1
189                 If r.Match(lines(index), sectionFind, RegexICSng).Success Then
190
191                     ' 找到了section的起始,则下面的数据到下一个section出现之前都是需要进行查找的数据
192                     For i As Integer = index + 1 To lines.Count - 1
193                         Dim line As String = lines(i)
194
195                         If r.Match(line.Trim, keyFind, RegexICSng).Success Then
196                             ' 找到了
197                             ' 在这里进行值替换,然后退出循环
198                             lines(i) = $"{key}={value}"
199                             Exit For
200                         ElseIf r.Match(line.Trim, RegexoSectionHeader).Success Then
201                             ' 已经匹配到了下一个section的起始了
202                             ' 没有找到,则进行新建
203                             ' 然后退出循环
204                             lines.Insert(i - 1, $"{key}={value}")
205                             Exit For
206                         End If
207                     Next
208
209                     appendSection = False
210                 End If
211             Next
212
213             If appendSection Then
214                 ' 没有找到section,则需要追加新的数据
215                 lines += $"[{section}]"
216                 lines += $"{key}={value}"
217             End If
218
219             Return lines
220         End Function
221
222         ''' <summary>
223         ''' Setting profile data from the ini file which the data is stores in a specific path like: ``section/key``. 
224         ''' If the path is not exists, the function will create new.
225         ''' </summary>
226         ''' <param name="path"></param>
227         ''' <param name="Section"></param>
228         ''' <param name="key"></param>
229         ''' <param name="value"></param>
230         ''' <remarks>
231         ''' 这个函数会保留下在配置文件之中原来的注释信息
232         ''' </remarks>
233         ''' 
234         <MethodImpl(MethodImplOptions.AggressiveInlining)>
235         <ExportAPI("SetValue"Info:="Setting profile data from the ini file which the data is stores in a specific path like: ``section/key``. If the path is not exists, the function will create new.")>
236         Public Sub WritePrivateProfileString(section$, key$, value$, path$)
237             Call path.ReadAllLines _
238                 .AsList _
239                 .WritePrivateProfileString(section, key, value) _
240                 .SaveTo(path)
241         End Sub
242     End Module
243 End Namespace