diff --git a/PCAxis.Core.UnitTest/IniFileTest.vb b/PCAxis.Core.UnitTest/IniFileTest.vb new file mode 100644 index 0000000..38b96c9 --- /dev/null +++ b/PCAxis.Core.UnitTest/IniFileTest.vb @@ -0,0 +1,33 @@ +Imports System.IO +Imports System.Text +Imports Microsoft.VisualStudio.TestTools.UnitTesting + + +Public NotInheritable Class IniFileTests + + Private Const TestIniContent As String = + "[section 1]" & vbCrLf & + "key1=value1" & vbCrLf & + "key2=value2 ; this is a comment" & vbCrLf & + "" & vbCrLf & + "[section 2]" & vbCrLf & + "keyA=valueA \" & vbCrLf & + "continued valueA" & vbCrLf & + "keyB=valueB" & vbCrLf + + + + + + + Public Sub LoadedIniFile_Ignore_Casing(section As String, key As String, expected As String) + ' Arrange + Dim iniFile = New IniFile(New MemoryStream(Encoding.UTF8.GetBytes(TestIniContent))) + iniFile.Load() + ' Act + Dim value = iniFile.GetValue(section, key) + ' Assert + Assert.AreEqual(expected, value) + End Sub + +End Class \ No newline at end of file diff --git a/PCAxis.Core/Grouping/GroupRegistry.vb b/PCAxis.Core/Grouping/GroupRegistry.vb index b2749be..3b2349c 100644 --- a/PCAxis.Core/Grouping/GroupRegistry.vb +++ b/PCAxis.Core/Grouping/GroupRegistry.vb @@ -6,90 +6,6 @@ Namespace PCAxis.Paxiom ''' Public Class GroupRegistry -#Region "IniReader Class" - ''' - ''' Ini-file reader. Used by the groupregistry when reading from the .vs and .agg files - ''' - ''' - Private Class IniReader - Private Const BUFFER_SIZE As Integer = 1024 -#Region "API Calls" - Private Declare Unicode Function GetPrivateProfileString Lib "kernel32" _ - Alias "GetPrivateProfileStringW" (ByVal lpApplicationName As String, _ - ByVal lpKeyName As String, ByVal lpDefault As String, _ - ByVal lpReturnedString As String, ByVal nSize As Int32, _ - ByVal lpFileName As String) As Int32 -#End Region -#Region "Read overloads" - Public Overloads Function Read(ByVal path As String, _ - ByVal sectionName As String, _ - ByVal keyName As String) As String - ' overload 1 assumes zero-length default - Return Read(path, sectionName, keyName, "") - End Function - - Public Overloads Function Read(ByVal path As String, _ - ByVal sectionName As String) As String - ' overload 2 returns all keys in a given section of the given file - Return Read(path, sectionName, Nothing, "") - End Function - - Public Overloads Function Read(ByVal path As String) As String - ' overload 3 returns all section names given just path - Return Read(path, Nothing, Nothing, "") - End Function -#End Region - -#Region "Public methods" - - Public Overloads Function Read(ByVal path As String, _ - ByVal sectionName As String, _ - ByVal keyName As String, _ - ByVal defaultValue As String) As String - Dim n As Integer - Dim bufferSize As Integer = BUFFER_SIZE - Dim data As String - - data = New String(" "c, bufferSize) - n = GetPrivateProfileString(sectionName, keyName, defaultValue, data, data.Length, path) - - If n > 0 Then - - While n >= (bufferSize - 2) - 'Buffer was to small. Grow buffer and read again - bufferSize += BUFFER_SIZE - data = New String(" "c, bufferSize) - n = GetPrivateProfileString(sectionName, keyName, defaultValue, data, data.Length, path) - End While - - Return data.Substring(0, n) - Else - Return "" - End If - End Function - - Public Function GetAllKeysInSection(ByVal path As String, ByVal section As String) As System.Collections.Specialized.StringCollection - Dim col As New System.Collections.Specialized.StringCollection - Dim value As String - Dim keys() As String - - value = Read(path, section) ' get all keys in section - value = value.Replace(ControlChars.NullChar, "|"c) ' change embedded NULLs to pipe chars - keys = value.Split("|"c) - - For i As Integer = 0 To keys.Length - 1 - If Not String.IsNullOrEmpty(keys(i)) Then - col.Add(keys(i)) - End If - Next - - Return col - End Function -#End Region - End Class -#End Region - - #Region "GroupingInfoMeta class" ''' ''' Holds metadata about a specific GroupingInfo-object @@ -400,14 +316,15 @@ Namespace PCAxis.Paxiom ''' Dictionary that tells which GroupingInfoMeta that belongs to a specific GroupingInfo object ''' Tells if the .vs file is in the (default) grouping directory or not ''' - Private Sub ReadValuesetFile(ByVal f As FileInfo, _ - ByVal valuesetDict As Dictionary(Of String, Valueset), _ - ByVal domainsDict As Dictionary(Of String, Dictionary(Of String, Valueset)), _ - ByVal groupingInfoMetaDict As Dictionary(Of GroupingInfo, GroupingInfoMeta), _ + Private Sub ReadValuesetFile(ByVal f As FileInfo, + ByVal valuesetDict As Dictionary(Of String, Valueset), + ByVal domainsDict As Dictionary(Of String, Dictionary(Of String, Valueset)), + ByVal groupingInfoMetaDict As Dictionary(Of GroupingInfo, GroupingInfoMeta), ByVal defaultDirectory As Boolean) Dim vs As Valueset - Dim ini As New IniReader - Dim col1, col2 As System.Collections.Specialized.StringCollection + Dim ini As New IniFile(f.FullName) + ini.Load() + Dim col1, col2 As IEnumerable(Of String) Dim code As String Dim value As String Dim domain As String @@ -432,26 +349,26 @@ Namespace PCAxis.Paxiom '2. Add values to valueset '-------------------------- vs = New Valueset With { - .Name = ini.Read(f.FullName, "descr", "name").ToLower(), - .Prestext = ini.Read(f.FullName, "descr", "prestext") + .Name = ini.GetValue("descr", "name").ToLower(), + .Prestext = ini.GetValue("descr", "prestext") } - col1 = ini.GetAllKeysInSection(f.FullName, "valuecode") - col2 = ini.GetAllKeysInSection(f.FullName, "valuetext") + col1 = ini.GetAllKeysInSection("valuecode") + col2 = ini.GetAllKeysInSection("valuetext") If col1.Count <> col2.Count Then Throw New PXException("Valuecode and valuetext section must contain the same number of entries") End If For i As Integer = 0 To col1.Count - 1 - code = ini.Read(f.FullName, "valuecode", col1(i)) + code = ini.GetValue("valuecode", col1(i)) If (i.Equals(999) And code.Equals("****")) Then 'Handle .vsc and .vsn files ReadLargeValueset(f.FullName, vs) Exit For Else - value = ini.Read(f.FullName, "valuetext", col2(i)) + value = ini.GetValue("valuetext", col2(i)) If Not vs.Values.ContainsKey(code) Then vs.Values.Add(code, value) End If @@ -461,17 +378,19 @@ Namespace PCAxis.Paxiom '3. Add groupings to valueset '--------------------------- 'Get all keys in aggreg section - col1 = ini.GetAllKeysInSection(f.FullName, "aggreg") + col1 = ini.GetAllKeysInSection("aggreg") For i As Integer = 0 To col1.Count - 1 - grouping = ini.Read(f.FullName, "aggreg", col1(i)) + grouping = ini.GetValue("aggreg", col1(i)) groupingPath = Path.Combine(f.DirectoryName, grouping) If System.IO.File.Exists(groupingPath) Then - valuesetName = ini.Read(groupingPath, "aggreg", "valueset").ToLower() + Dim iniGrp = New IniFile(groupingPath) + iniGrp.Load() + valuesetName = iniGrp.GetValue("aggreg", "valueset").ToLower() 'Verify that the aggregation belongs to the valueset (or if not check shall be done...) If (_strict = False) Or (valuesetName.Equals(vs.Name)) Then 'Get name from the .agg file - groupingName = ini.Read(groupingPath, "aggreg", "name") + groupingName = iniGrp.GetValue("aggreg", "name") 'Always use aggregated values for PX-file groupings gi = New GroupingInfo(grouping) With { .Name = groupingName, @@ -498,10 +417,10 @@ Namespace PCAxis.Paxiom '5. Add the domain(s) to the domain dictionary '------------------------------------------- 'Get all keys in domain section - col1 = ini.GetAllKeysInSection(f.FullName, "domain") + col1 = ini.GetAllKeysInSection("domain") For i As Integer = 0 To col1.Count - 1 - domain = ini.Read(f.FullName, "domain", col1(i)).ToLower() + domain = ini.GetValue("domain", col1(i)).ToLower() If defaultDirectory Then vsPath = "" @@ -590,7 +509,8 @@ Namespace PCAxis.Paxiom ''' True if the valueset belongs to the specified domain, else false ''' Private Function CheckDomain(ByVal f As FileInfo, ByVal domain As String) As Boolean - Dim ini As New IniReader + Dim ini As New IniFile(f.FullName) + ini.Load() Dim domains As System.Collections.Specialized.StringCollection Dim name As String @@ -599,10 +519,10 @@ Namespace PCAxis.Paxiom End If 'Get all keys in domain section - domains = ini.GetAllKeysInSection(f.FullName, "domain") + domains = ini.GetAllKeysInSection("domain") For i As Integer = 0 To domains.Count - 1 - name = ini.Read(f.FullName, "domain", domains(i)) + name = ini.GetValue("domain", domains(i)) If domain.ToLower() = name.ToLower() Then Return True End If @@ -676,10 +596,10 @@ Namespace PCAxis.Paxiom ''' Public Function GetGrouping(ByVal gi As GroupingInfo) As Grouping Dim grouping As New Grouping - Dim ini As New IniReader + Dim ini As IniFile Dim path As String - Dim groupCodeKeys As System.Collections.Specialized.StringCollection - Dim childCodeKeys As System.Collections.Specialized.StringCollection + Dim groupCodeKeys As IEnumerable(Of String) + Dim childCodeKeys As IEnumerable(Of String) Dim group As Group Dim giMeta As GroupingInfoMeta @@ -707,23 +627,26 @@ Namespace PCAxis.Paxiom Throw New PCAxis.Paxiom.PXException("Could not load grouping") End If + ini = New IniFile(path) + ini.Load() + grouping.ID = gi.ID - grouping.Name = ini.Read(path, "aggreg", "name") - grouping.Map = ini.Read(path, "aggreg", "map") + grouping.Name = ini.GetValue("aggreg", "name") + grouping.Map = ini.GetValue("aggreg", "map") 'Add groups to the grouping - groupCodeKeys = ini.GetAllKeysInSection(path, "aggtext") + groupCodeKeys = ini.GetAllKeysInSection("aggtext") For Each groupCodeKey As String In groupCodeKeys group = New Group With { - .GroupCode = ini.Read(path, "aggreg", groupCodeKey), - .Name = ini.Read(path, "aggtext", groupCodeKey) + .GroupCode = ini.GetValue("aggreg", groupCodeKey), + .Name = ini.GetValue("aggtext", groupCodeKey) } 'Add childcodes to the group - childCodeKeys = ini.GetAllKeysInSection(path, group.GroupCode) + childCodeKeys = ini.GetAllKeysInSection(group.GroupCode) For Each childCodeKey As String In childCodeKeys Dim child As New GroupChildValue With { - .Code = ini.Read(path, group.GroupCode, childCodeKey) + .Code = ini.GetValue(group.GroupCode, childCodeKey) } If giMeta.Valueset.Values.TryGetValue(child.Code, child.Name) Then group.ChildCodes.Add(child) diff --git a/PCAxis.Core/Grouping/IniFile.vb b/PCAxis.Core/Grouping/IniFile.vb new file mode 100644 index 0000000..558e082 --- /dev/null +++ b/PCAxis.Core/Grouping/IniFile.vb @@ -0,0 +1,132 @@ +Imports System.IO + +Public Class IniFile + + Private Class Section + Public ReadOnly Property Name As String + Public ReadOnly Property Data As Dictionary(Of String, KeyValuePair(Of String, String)) + + Public Sub New(name As String) + Me.Name = name + Me.Data = New Dictionary(Of String, KeyValuePair(Of String, String))() + End Sub + End Class + + Private ReadOnly _stream As Stream + Private ReadOnly _data As New Dictionary(Of String, Section)() + Private ReadOnly _encoding As System.Text.Encoding + + Private Shared Function GetEncodeing(path As String) As System.Text.Encoding + Dim cs As String + Dim BUFFER_SIZE As Integer = 1024 + Dim buffer(BUFFER_SIZE - 1) As Byte + Dim size As Integer + Using fs As System.IO.FileStream = System.IO.File.OpenRead(path) + Dim det As Ude.ICharsetDetector + det = New Ude.CharsetDetector + Dim fi As New System.IO.FileInfo(path) + size = Math.Min(BUFFER_SIZE, Convert.ToInt32(fi.Length)) + size = fs.Read(buffer, 0, size) + det.Feed(buffer, 0, size) + det.DataEnd() + + cs = det.Charset + End Using + + If cs Is Nothing Then + Return System.Text.Encoding.Default + End If + + If String.Compare(cs, "ASCII", True) = 0 Then + Return System.Text.Encoding.Default + End If + + Return System.Text.Encoding.GetEncoding(cs) + End Function + + + Public Sub New(path As String) + If String.IsNullOrWhiteSpace(path) Then + Throw New ArgumentException("Path must not be null or whitespace.", NameOf(path)) + End If + _encoding = GetEncodeing(path) + _stream = New FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read) + End Sub + + Public Sub New(stream As Stream) + If stream Is Nothing Then + Throw New ArgumentNullException(NameOf(stream)) + End If + _encoding = System.Text.Encoding.Default + _stream = stream + End Sub + + Protected Iterator Function ReadLines() As IEnumerable(Of String) + Using reader As New StreamReader(_stream, _encoding) + Dim line As String = Nothing + Do + line = reader.ReadLine() + If line Is Nothing Then Exit Do + Yield line.Trim() + Loop + End Using + End Function + + Public Sub Load() + Dim currentSection As String = String.Empty + For Each line In ReadLines() + If line.StartsWith("[") AndAlso line.EndsWith("]") Then + currentSection = line.Substring(1, line.Length - 2).Trim() + Continue For + End If + + If line.StartsWith(";") OrElse String.IsNullOrWhiteSpace(line) Then + Continue For + End If + + Dim equalsIndex As Integer = line.IndexOf("="c) + If equalsIndex > 0 Then + Dim key As String = line.Substring(0, equalsIndex).Trim() + Dim value As String = line.Substring(equalsIndex + 1).Trim() + Dim section = GetSection(currentSection) + section.Data(key.ToUpper()) = New KeyValuePair(Of String, String)(key, value) + End If + Next + + _stream.Close() + End Sub + + Private Function GetSection(section As String) As Section + Dim sectionKey = section.ToUpper() + Dim sectionData As Section = Nothing + If Not _data.TryGetValue(sectionKey, sectionData) Then + sectionData = New Section(section) + _data(sectionKey) = sectionData + End If + Return sectionData + End Function + + Public Function GetAllSections() As IEnumerable(Of String) + Return _data.Keys + End Function + + Public Function GetAllKeysInSection(section As String) As IEnumerable(Of String) + Dim sectionKey = section.ToUpper() + Dim sectionData As Section = Nothing + If _data.TryGetValue(sectionKey, sectionData) Then + Return sectionData.Data.Values.Select(Of String)(Function(kv) kv.Key) + End If + Return Array.Empty(Of String)() + End Function + + Public Function GetValue(section As String, key As String, Optional defaultValue As String = "") As String + Dim sectionKey = section.ToUpper() + Dim sectionData As Section = Nothing + Dim value As KeyValuePair(Of String, String) + If _data.TryGetValue(sectionKey, sectionData) AndAlso sectionData.Data.TryGetValue(key.ToUpper(), value) Then + Return value.Value + End If + Return defaultValue + End Function + +End Class \ No newline at end of file