アットウィキロゴ
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


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.IO

Public NotInheritable Class EncodingParser

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

    Public Shared Function GetCode(ByVal file As String) As System.Text.Encoding
        If String.IsNullOrEmpty(file) Then Throw New ArgumentNullException("file ")
        Dim fileInfo As New IO.FileInfo(file)
        If Not fileInfo.Exists Then
            Throw New IO.FileNotFoundException("file")
        End If

        Return GetCode(My.Computer.FileSystem.ReadAllBytes(file))
    End Function

    ''' <summary>
    ''' 文字コードを判別する
    ''' </summary>
    ''' <remarks>
    ''' Jcode.pmのgetcodeメソッドを移植したものです。
    ''' Jcode.pm(http://openlab.ring.gr.jp/Jcode/index-j.html)
    ''' Jcode.pmのCopyright: Copyright 1999-2005 Dan Kogai
    ''' </remarks>
    ''' <param name="bytes">文字コードを調べるデータ</param>
    ''' <returns>適当と思われるEncodingオブジェクト。
    ''' 判断できなかった時はnull。</returns>
    Private Shared Function GetCode(ByVal bytes As Byte()) As System.Text.Encoding
        Const bEscape As Byte = &H1B
        Const bAt As Byte = &H40
        Const bDollar As Byte = &H24
        Const bAnd As Byte = &H26
        Const bOpen As Byte = &H28 ''('
        Const bB As Byte = &H42
        Const bD As Byte = &H44
        Const bJ As Byte = &H4A
        Const bI As Byte = &H49

        Dim len As Integer = bytes.Length
        Dim b1 As Byte, b2 As Byte, b3 As Byte, b4 As Byte

        'Encode::is_utf8 は無視

        Dim isBinary As Boolean = False
        Dim i As Integer
        For i = 0 To len - 1
            b1 = bytes(i)
            If b1 <= &H6 OrElse b1 = &H7F OrElse b1 = &HFF Then
                ''binary'
                isBinary = True
                If b1 = &H0 AndAlso i < len - 1 AndAlso bytes(i + 1) <= &H7F Then
                    'smells like raw unicode
                    Return System.Text.Encoding.Unicode
                End If
            End If
        Next
        If isBinary Then
            Return Nothing
        End If

        'not Japanese
        Dim notJapanese As Boolean = True
        For i = 0 To len - 1
            b1 = bytes(i)
            If b1 = bEscape OrElse &H80 <= b1 Then
                notJapanese = False
                Exit For
            End If
        Next
        If notJapanese Then
            Return System.Text.Encoding.ASCII
        End If

        For i = 0 To len - 3
            b1 = bytes(i)
            b2 = bytes(i + 1)
            b3 = bytes(i + 2)

            If b1 = bEscape Then
                If b2 = bDollar AndAlso b3 = bAt Then
                    'JIS_0208 1978
                    'JIS
                    Return System.Text.Encoding.GetEncoding(50220)
                ElseIf b2 = bDollar AndAlso b3 = bB Then
                    'JIS_0208 1983
                    'JIS
                    Return System.Text.Encoding.GetEncoding(50220)
                ElseIf b2 = bOpen AndAlso (b3 = bB OrElse b3 = bJ) Then
                    'JIS_ASC
                    'JIS
                    Return System.Text.Encoding.GetEncoding(50220)
                ElseIf b2 = bOpen AndAlso b3 = bI Then
                    'JIS_KANA
                    'JIS
                    Return System.Text.Encoding.GetEncoding(50220)
                End If
                If i < len - 3 Then
                    b4 = bytes(i + 3)
                    If b2 = bDollar AndAlso b3 = bOpen AndAlso b4 = bD Then
                        'JIS_0212
                        'JIS
                        Return System.Text.Encoding.GetEncoding(50220)
                    End If
                    If i < len - 5 AndAlso _
                        b2 = bAnd AndAlso b3 = bAt AndAlso b4 = bEscape AndAlso _
                        bytes(i + 4) = bDollar AndAlso bytes(i + 5) = bB Then
                        'JIS_0208 1990
                        'JIS
                        Return System.Text.Encoding.GetEncoding(50220)
                    End If
                End If
            End If
        Next

        'should be euc|sjis|utf8
        'use of (?:) by Hiroki Ohzaki <[email protected]>
        Dim sjis As Integer = 0
        Dim euc As Integer = 0
        Dim utf8 As Integer = 0
        For i = 0 To len - 2
            b1 = bytes(i)
            b2 = bytes(i + 1)
            If ((&H81 <= b1 AndAlso b1 <= &H9F) OrElse _
                (&HE0 <= b1 AndAlso b1 <= &HFC)) AndAlso _
                ((&H40 <= b2 AndAlso b2 <= &H7E) OrElse _
                 (&H80 <= b2 AndAlso b2 <= &HFC)) Then
                'SJIS_C
                sjis += 2
                i += 1
            End If
        Next
        For i = 0 To len - 2
            b1 = bytes(i)
            b2 = bytes(i + 1)
            If ((&HA1 <= b1 AndAlso b1 <= &HFE) AndAlso _
                (&HA1 <= b2 AndAlso b2 <= &HFE)) OrElse _
                (b1 = &H8E AndAlso (&HA1 <= b2 AndAlso b2 <= &HDF)) Then
                'EUC_C
                'EUC_KANA
                euc += 2
                i += 1
            ElseIf i < len - 2 Then
                b3 = bytes(i + 2)
                If b1 = &H8F AndAlso (&HA1 <= b2 AndAlso b2 <= &HFE) AndAlso _
                    (&HA1 <= b3 AndAlso b3 <= &HFE) Then
                    'EUC_0212
                    euc += 3
                    i += 2
                End If
            End If
        Next
        For i = 0 To len - 2
            b1 = bytes(i)
            b2 = bytes(i + 1)
            If (&HC0 <= b1 AndAlso b1 <= &HDF) AndAlso _
                (&H80 <= b2 AndAlso b2 <= &HBF) Then
                'UTF8
                utf8 += 2
                i += 1
            ElseIf i < len - 2 Then
                b3 = bytes(i + 2)
                If (&HE0 <= b1 AndAlso b1 <= &HEF) AndAlso _
                    (&H80 <= b2 AndAlso b2 <= &HBF) AndAlso _
                    (&H80 <= b3 AndAlso b3 <= &HBF) Then
                    'UTF8
                    utf8 += 3
                    i += 2
                End If
            End If
        Next
        'M. Takahashi's suggestion
        'utf8 += utf8 / 2;

        System.Diagnostics.Debug.WriteLine( _
            String.Format("sjis = {0}, euc = {1}, utf8 = {2}", sjis, euc, utf8))
        If euc > sjis AndAlso euc > utf8 Then
            'EUC
            Return System.Text.Encoding.GetEncoding(51932)
        ElseIf sjis > euc AndAlso sjis > utf8 Then
            'SJIS
            Return System.Text.Encoding.GetEncoding(932)
        ElseIf utf8 > euc AndAlso utf8 > sjis Then
            'UTF8
            Return System.Text.Encoding.UTF8
        End If

        Return Nothing
    End Function

End Class

Imports System.Text

''' <summary>
''' ファイルの要約
''' </summary>
''' <remarks></remarks>
Public Structure FileSummary

#Region "Variables"

    Private _filepath As String
    Private _encode As System.Text.Encoding

#End Region

#Region "Properties"

    Public Property FilePath() As String
        Get
            Return _filepath
        End Get
        Set(ByVal value As String)
            _filepath = value
        End Set
    End Property


    Public Property Encode() As Encoding
        Get
            Return _encode
        End Get
        Set(ByVal value As Encoding)
            _encode = value
        End Set
    End Property

#End Region

End Structure

''' <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
最終更新:2014年06月23日 01:31