アットウィキロゴ
Module Module1

    Sub Main()

        Dim args() As String = Environment.GetCommandLineArgs()
        If args.Length < 3 Then
            StructureErrorMessage()
            Return
        End If

        Dim cmd As String = args(1).ToLower
        Select Case cmd
            Case "-fix" '-fix inifile.ini section entry value
                If args.Length <> 6 Then
                    StructureErrorMessage()
                    Return
                End If
                Dim iniFile As String = args(2)
                Dim section As New Section(args(3))
                Dim entry As New Entry(args(4), args(5))
                Dim fixData As New IniData()
                fixData.Add(section, entry)

                Dim fixer As New Fixer(iniFile)
                fixer.FixIni(fixData)
                fixer.SaveFile()
                fixer.ShowStatus()

            Case "-fixfile" '-fixfile inifile.ini value.ini
                If args.Length <> 4 Then
                    StructureErrorMessage()
                    Return
                End If
                Dim iniFile As String = args(2)
                Dim valueFile As String = args(3)

                Dim fixData As IniData = IniUtil.ReadIniFromFile(valueFile)
                Dim fixer As New Fixer(iniFile)
                fixer.FixIni(fixData)
                fixer.SaveFile()
                fixer.ShowStatus()

            Case Else
                StructureErrorMessage()
        End Select

    End Sub

    ''' <summary>
    '''
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub StructureErrorMessage()

        Dim builder As New System.Text.StringBuilder()
        builder.AppendLine("構文エラーです")
        MsgBox(builder.ToString)
    End Sub


End Module



Imports System.Xml
Imports System.IO

''' <summary>
'''
''' </summary>
''' <remarks></remarks>
Public Class Fixer

#Region "Variables"
    Private _summary As FileSummary
    Private _allTxt As String
    Private _fixCount As Integer = 0 '修正した箇所
#End Region

#Region "Initialize"

    ''' <summary>
    '''
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New(ByVal file As String)
        If file Is Nothing OrElse file.Trim() = String.Empty Then Throw New IO.FileNotFoundException(file, "file")
        If Not IO.File.Exists(file) Then Throw New IO.FileNotFoundException(file, "file")
        Dim encode As Text.Encoding = EncodingParser.GetCode(file)
        If encode Is Nothing Then Throw New InvalidOperationException("エンコードが特定できませんでした")

        _summary.FilePath = file
        _summary.Encode = encode

        Using reader As New StreamReader(file, encode)
            _allTxt = reader.ReadToEnd()
        End Using
    End Sub
#End Region

#Region "Methods"

    ''' <summary>
    '''
    ''' </summary>
    ''' <param name="fixData">修正する箇所と値</param>
    ''' <remarks></remarks>
    Public Sub FixIni(ByVal fixData As IniData)
        If fixData Is Nothing Then Throw New ArgumentNullException("fixPosition")
        For Each section As Section In fixData.Keys
            Dim entryList As List(Of Entry) = fixData(section)
            For Each entry As Entry In entryList
                FixIni(section, entry)
            Next
        Next
    End Sub

    ''' <summary>
    '''
    ''' </summary>
    ''' <param name="targetSection"></param>
    ''' <param name="targetEntry"></param>
    ''' <remarks></remarks>
    Public Sub FixIni(ByVal targetSection As Section, ByVal targetEntry As Entry)
        If targetSection Is Nothing Then Throw New ArgumentNullException("targetSection")
        If targetEntry Is Nothing Then Throw New ArgumentNullException("targetEntry")

        Dim output As New System.Text.StringBuilder()
        Dim foundFlag As Boolean = False '修正箇所が見つかったかどうかのフラグ
        Dim preSection As Section = Nothing '1つ前のセクション
        Dim preLine As String = Nothing '1つ前の行

        '1.テキストを上から順番に呼んでいって、outputに書き出していく
        '2.変更したいセクションを見つけた時だけoutputに書き出す内容を変える
        '3.最終的に元のテキストの内容をoutputの内容に書き換える
        Using reader As New IniReader(_allTxt)
            Dim line As String = reader.ReadLine()

            While Not line Is Nothing
                'まだ変更箇所を発見していない、なおかつ無効な行でないとき
                If Not foundFlag AndAlso reader.IsValidLine() Then
                    'Sectionに所属していれば
                    If Not reader.CurrentSection Is Nothing Then
                        '探していたセクションであれば
                        If reader.CurrentSection.Equals(targetSection) Then
                            '探しているセクションが見つかったとき
                            '探しているエントリが見つかったとき
                            If Not reader.CurrentEntry Is Nothing AndAlso reader.CurrentEntry.SameEntryName(targetEntry) Then
                                line = targetEntry.ToString
                                _fixCount += 1
                                foundFlag = True
                            End If
                        Else '探してたセクションでない場合
                            '1つ前のセクションが探してるセクションなら
                            '(すなわちセクションは見つけたが、該当するエントリがなかった時)
                            If Not preSection Is Nothing AndAlso preSection.Equals(targetSection) Then
                                If Not preLine Is Nothing AndAlso Not preLine.Trim = String.Empty Then line = Environment.NewLine & line '1つ前の行が空行でないなら改行を入れておく
                                line = targetEntry.ToString & Environment.NewLine _
                                        & Environment.NewLine _
                                        & line
                                _fixCount += 1
                                foundFlag = True
                            End If
                            End If
                    End If
                End If
                preLine = reader.CurrentLine
                preSection = reader.CurrentSection
                output.AppendLine(line)
                line = reader.ReadLine()
            End While
        End Using

        '見つかって無い時
        If Not foundFlag Then
            If Not preLine Is Nothing AndAlso Not preLine.Trim = String.Empty Then output.AppendLine() '1つ前の行が空行でないなら改行を入れておく
            '1つ前のセクションが探してるセクションなら
            '(すなわちセクションは見つけたが、該当するエントリがなかった時)
            If Not preSection Is Nothing AndAlso preSection.Equals(targetSection) Then
                output.AppendLine(targetEntry.ToString)
            Else 'セクションもエントリも見つからなかった時
                output.AppendLine(targetSection.ToString)
                output.AppendLine(targetEntry.ToString)
            End If
            _fixCount += 1
        End If

        Me._allTxt = output.ToString
    End Sub

    Public Sub SaveFile()
        If _fixCount = 0 Then

        Else
            Using writer As New StreamWriter(_summary.FilePath, False, _summary.Encode)
                writer.Write(_allTxt)
            End Using
        End If

    End Sub

    Public Sub ShowStatus()
        MsgBox(_fixCount & "箇所修正しました。")
    End Sub

#End Region



End Class

''' <summary>
'''
''' </summary>
''' <remarks></remarks>
Public NotInheritable Class IniUtil

    ''' <summary>
    '''
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub New()
    End Sub

    ''' <summary>
    ''' 有効な行かどうか
    ''' </summary>
    ''' <param name="line"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function IsValid(ByVal line As String) As Boolean
        Return Not line.StartsWith(";") And Not line.Trim = String.Empty
    End Function

    ''' <summary>
    ''' セクションかどうか
    ''' </summary>
    ''' <param name="line"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function IsSection(ByVal line As String) As Boolean
        Return line Like "[[]?*[]]"
    End Function

    ''' <summary>
    ''' 行をSectionオブジェクトに変換する
    ''' </summary>
    ''' <returns>セクションじゃなければNothingを返す</returns>
    ''' <remarks></remarks>
    Public Shared Function ToSection(ByVal line As String) As Section
        If IsSection(line) Then
            line = line.Replace("[", "")
            line = line.Replace("]", "")
            Return New Section(line)
        End If
        Return Nothing
    End Function

    ''' <summary>
    ''' エントリーとその値が対になったペアを返す
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function ToEntry(ByVal line As String) As Entry
        line = line.Trim
        Dim sepalate() As String = line.Split(New Char() {"="c})
        If sepalate.Length = 2 Then
            Dim tmpEntry = sepalate(0).Trim()
            Dim tmpValue = sepalate(1).Trim()
            If tmpEntry <> "" And tmpValue <> "" Then
                Return New Entry(tmpEntry, tmpValue)
            End If
        End If
        Return Nothing
    End Function

    Public Shared Function ReadIniFromFile(ByVal file As String) As IniData
        If file Is Nothing OrElse file.Trim() = String.Empty Then Throw New ArgumentException("文字列がNullまたは空、またはスペースのみです", "file")
        Dim encode As Text.Encoding = EncodingParser.GetCode(file)
        Dim allTxt As String = Nothing
        Using reader As New IO.StreamReader(file, encode)
            allTxt = reader.ReadToEnd
        End Using
        Return ReadIniData(allTxt)
    End Function

    Public Shared Function ReadIniData(ByVal allText As String) As IniData
        Dim inidata As New IniData()
        Using reader As New IniReader(allText)
            reader.ReadLine()
            While Not reader.CurrentLine Is Nothing
                If Not reader.CurrentSection Is Nothing And Not reader.CurrentEntry Is Nothing Then
                    inidata.Add(reader.CurrentSection, reader.CurrentEntry)
                End If
                reader.ReadLine()
            End While
        End Using
        Return inidata
    End Function
End Class



''' <summary>
'''
''' </summary>
''' <remarks></remarks>
Public Class IniData
    Inherits Dictionary(Of Section, List(Of Entry))

    ''' <summary>
    '''
    ''' </summary>
    ''' <param name="Section"></param>
    ''' <param name="entry"></param>
    ''' <param name="ignoreDuplicate">同じセクションで同じ名前を持つエントリの登録を許可するか</param>
    ''' <remarks></remarks>
    Public Overloads Sub Add(ByVal Section As Section, ByVal entry As Entry, Optional ByVal ignoreDuplicate As Boolean = False)
        If Me.ContainsKey(Section) Then
            Dim entryList As List(Of Entry) = Me(Section)
            If Not ignoreDuplicate AndAlso ContainsSameEntry(entryList, entry) Then
                Throw New ArgumentException("重複するエントリーを利用しようとしています")
            End If
            entryList.Add(entry)
        Else
            Dim entryList As List(Of Entry) = New List(Of Entry)
            entryList.Add(entry)
            Me.Add(Section, entryList)
        End If
    End Sub


    ''' <summary>
    '''
    ''' </summary>
    ''' <param name="entryList"></param>
    ''' <param name="entry"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function ContainsSameEntry(ByVal entryList As List(Of Entry), ByVal entry As Entry) As Boolean
        For Each currentEntry As Entry In entryList
            If currentEntry.EntryName = entry.EntryName Then Return True
        Next
        Return False
    End Function

End Class

''' <summary>
''' セクション名
''' </summary>
''' <remarks></remarks>
Public Class Section

    Private _sectionName As String
    Public ReadOnly Property SectionName() As String
        Get
            Return _sectionName
        End Get
    End Property

    Public Sub New(ByVal sectionName As String)
        If String.IsNullOrEmpty(sectionName) Then Throw New ArgumentException("文字列がNullまたは空です", "sectionName")
        Me._sectionName = sectionName.Trim
        If Me._sectionName = "" Then
            Throw New ArgumentException("空文字が指定されています")
        End If
    End Sub

    Public Overrides Function Equals(ByVal obj As Object) As Boolean
        If obj Is Nothing Then Return False
        If Me Is obj Then Return True
        If Not TypeOf obj Is Section Then Return False
        'プロパティが等しいか
        If Me.SectionName = DirectCast(obj, Section).SectionName Then Return True
        Return False
    End Function

    Public Overrides Function GetHashCode() As Integer
        Return _sectionName.GetHashCode()
    End Function

    Public Overrides Function ToString() As String
        Return "[" & Me.SectionName & "]"
    End Function
End Class

''' <summary>
''' エントリ名とその値
''' </summary>
''' <remarks></remarks>
Public Class Entry

    Private _entryName As String
    Public ReadOnly Property EntryName() As String
        Get
            Return _entryName
        End Get
    End Property

    Private _entryValue As String
    Public Property EntryValue() As String
        Get
            Return _entryValue
        End Get
        Set(ByVal value As String)
            _entryValue = value
        End Set
    End Property

    Public Sub New(ByVal entryName As String, ByVal entryValue As String)
        If String.IsNullOrEmpty(entryName) Then Throw New ArgumentException("文字列がNullまたは空です", "val")
        Me._entryValue = entryValue.Trim
        Me._entryName = entryName.Trim

        If Me._entryName = "" Or Me._entryValue = "" Then
            Throw New ArgumentException("空文字が指定されています")
        End If
    End Sub

    Public Overrides Function Equals(ByVal obj As Object) As Boolean
        If obj Is Nothing Then Return False
        If Me Is obj Then Return True
        If Not TypeOf obj Is Entry Then Return False
        If Me.EntryName = DirectCast(obj, Entry).EntryName _
           And Me.EntryValue = DirectCast(obj, Entry).EntryValue Then Return True
        Return False
    End Function


    ''' <summary>
    '''
    ''' </summary>
    ''' <param name="targetEntry"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function SameEntryName(ByVal targetEntry As Entry) As Boolean
        Return Me.EntryName = targetEntry.EntryName
    End Function



    Public Overrides Function GetHashCode() As Integer
        Return EntryName.GetHashCode() Xor EntryValue.GetHashCode
    End Function

    Public Overrides Function ToString() As String
        Return Me.EntryName & "=" & Me.EntryValue
    End Function


End Class



''' <summary>
'''
''' </summary>
''' <remarks></remarks>
Public Class IniReader
    Inherits IO.StringReader

#Region "Variables"

    Private _currentLine As String
    Private _currentSection As Section
    Private _currentEntry As Entry

#End Region

#Region "Properties"
    Public ReadOnly Property CurrentLine() As String
        Get
            Return _currentLine
        End Get
    End Property

    Public ReadOnly Property CurrentSection() As Section
        Get
            Return _currentSection
        End Get
    End Property

    Public ReadOnly Property CurrentEntry() As Entry
        Get
            Return _currentEntry
        End Get
    End Property
#End Region

#Region "Initialize"

    ''' <summary>
    '''
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New(ByVal txt As String)
        MyBase.New(txt)
    End Sub

#End Region

#Region "Methods"

    Public Overrides Function ReadLine() As String
        Me._currentLine = MyBase.ReadLine()
        Me._currentEntry = Nothing 'エントリは毎回初期化
        If Not Me._currentLine Is Nothing Then '行があるなら
            'コメントでないなら
            If IsValidLine() Then
                If IsSectionLine() Then 'セクションなら
                    Me._currentSection = ToSection()
                Else 'エントリなら
                    Me._currentEntry = ToEntry()
                End If
            End If
        Else
            '行が読み込めないなら
            Me._currentEntry = Nothing
            Me._currentSection = Nothing
        End If
        Return Me._currentLine
    End Function

    ''' <summary>
    ''' 有効な行かどうか
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function IsValidLine() As Boolean
        Return IniUtil.IsValid(Me.CurrentLine)
    End Function

    '現在の行がSectionならTrueを返す
    Public Function IsSectionLine() As Boolean
        Return IsValidLine() And IniUtil.IsSection(Me.CurrentLine)
    End Function

#End Region

#Region "Private Methods"

    ''' <summary>
    ''' 現在の行をSectionオブジェクトに変換する
    ''' </summary>
    ''' <returns>セクションじゃなければNothingを返す</returns>
    ''' <remarks></remarks>
    Private Function ToSection() As Section
        Return IniUtil.ToSection(Me.CurrentLine)
    End Function

    ''' <summary>
    ''' 現在の行をEntryオブジェクトに変換する
    ''' </summary>
    ''' <returns>変換できなければNothingを返す</returns>
    ''' <remarks></remarks>
    Private Function ToEntry() As Entry
        Return IniUtil.ToEntry(Me.CurrentLine)
    End Function

#End Region

End Class
最終更新:2013年11月10日 20:17