LoginSignup
0
1

More than 3 years have passed since last update.

改行ありのCSVファイルをパースする為のクラス(試作)

Last updated at Posted at 2020-05-08

改行含んだCSVをパースしてString()に収めるやつ

改行ありのCSVとか勘弁してほしい所ではあるが、やってくるものは受け止めなければならないので
ネットで誰か作ってないかなーとか思ったがしっくりくるのが無かったので嫌々作ったやつのメモである

※textfieldparserで対応出来るのを知らずに書いてました。そっち使ったほうが早いので皆さんはそちらを使われるといいと思います。

ざっくり使い方

VB.NET
Using csv = New CSVFile("ここに読込むファイルパス")
    Dim data As String()
    While Not csv.ReadToEnd
        data = csv.ReadLineData()
        '改行は配列におさまっていい感じになってるはず
    End While
End Using

作ったクラス

VB.NET
Imports System.IO
Imports System.Text
Imports System.Text.RegularExpressions

''' <summary>
''' CSVファイルを扱う為のクラス
''' </summary>
''' <remarks>ダブルクォートのみ対応</remarks>
Public Class CSVFile
    Implements IDisposable

    Private sr As StreamReader
    Private fs As FileStream
    Private pos As Long
    Private Const HASEND_PATTERN = "^.*([^""]""|"",""""|,[^""]*)$"
    Private _ReadToEnd As Boolean = False
    Public ReadOnly Property ReadToEnd As Boolean
        Get
            Return _ReadToEnd
        End Get
    End Property

    ''' <summary>
    ''' CSVファイルオブジェクトを作成し、ファイルをオープンする。
    ''' </summary>
    ''' <param name="filepath"></param>
    ''' <param name="encodeing"></param>
    ''' <remarks></remarks>
    Sub New(ByVal filepath As String, Optional ByVal encodeing As String = "Shift_JIS")
        '文字コード指定
        Dim enc As Encoding = Encoding.GetEncoding(encodeing)

        'ファイルストリームを開く
        Me.fs = File.OpenRead(filepath)
        Me.pos = Me.fs.Position
        Me.sr = New StreamReader(fs, enc)
    End Sub

    ''' <summary>
    ''' CSVデータとして改行等を考慮し、1行のデータとして返却します。
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReadLine(Optional simple As Boolean = False) As String
        'ファイルの任意の場所にシーク
        'fs.Seek(pos, SeekOrigin.Begin)

        '行データ読み込み
        Dim line = sr.ReadLine()

        If simple Then
            Return line
        End If

        'ファイルの終端なら何も返さない
        If line Is Nothing Then
            _ReadToEnd = True
            Return Nothing
        End If

        '行末があるバターンならそのまま返却
        If Regex.IsMatch(line, HASEND_PATTERN) Then
            Return line
        End If

        '行末が出るまで文字列を連結して返却
        Dim csvLine = New StringBuilder(line)
        While True
            line = sr.ReadLine()
            If line Is Nothing Then
                Return Nothing
            End If

            csvLine.Append(vbCrLf).Append(line)
            If Regex.IsMatch(line, HASEND_PATTERN) Then
                Exit While
            End If
        End While

        '改行を含むデータを返却
        Return csvLine.ToString()
    End Function

    ''' <summary>
    ''' 行データをデータ配列で返却
    ''' </summary>
    ''' <returns></returns>
    Public Function ReadLineData(Optional simple As Boolean = False) As String()
        Return CSVFile.SplitData(ReadLine(simple))
    End Function

    ''' <summary>
    ''' CSV行データをデータ毎に分割します。
    ''' </summary>
    ''' <param name="csvline"></param>
    ''' <returns></returns>
    ''' <remarks>考慮が甘い為、イレギュラーパターンは別途対応</remarks>
    Public Shared Function SplitData(ByVal csvline As String) As String()
        Dim ret As New List(Of String)
        Dim tmp As String() = {}
        Dim tval As String = ""
        If csvLine Is Nothing Then
            Return {}
        End If

        'とりあえずカンマで分解
        tmp = csvline.Split(",")

        Dim nextflg As Boolean = False
        For Each val As String In tmp
            If nextflg Then
                tval += val
                nextflg = False
            Else
                tval = val
            End If
            'OKかNGか判定
            If Regex.IsMatch(tval, "^(""[^""|""""]*""|[^""]*)$") Then
                '   マッチしていればそのままで終了
                '       ^([^"]*)$ -> \1
                If Regex.IsMatch(tval, "^([^""]*)$") Then
                    ret.Add(tval)
                    Continue For
                End If
                '   マッチしていれば抽出して終了
                '       ^"([^"]*)"$ -> \1
                Dim match As Match = Regex.Match(tval, "^""([^""]*)""$")
                If match.Success Then
                    ret.Add(match.Value)
                    Continue For
                End If

                '   マッチしていれば抽出して
                match = Regex.Match(tval, "^"".*([^""]+|"""")""$")
                If match.Success Then
                    '   両サイドのだぶくるくぉーとを外す
                    match = Regex.Match(tval, "^""(.*)""$")
                    If match.Success Then
                        '   ダブルクォートのエスケープ解除で終了
                        ret.Add(match.Value.Replace("""""", """"))
                        Continue For
                    End If
                End If
            End If

            'NG(ダブルくぉーとありでエンドなし
            '   移行の要素を次の正規表現にマッチするまでカンマ区切りで連結
            If Regex.IsMatch(tval, "^"".*([^""]+|"""")""$") Then
                '   両サイドのだぶくるくぉーとを外す
                Dim match = Regex.Match(tval, "^""(.*)""$")
                If match.Success Then
                    '   ダブルクォートのエスケープ解除で終了
                    ret.Add(match.Value.Replace("""""", """"))
                    Continue For
                End If
            End If
            nextflg = True
        Next

        Return ret.ToArray()
    End Function

#Region "IDisposable Support"
    Private disposedValue As Boolean ' 重複する呼び出しを検出するには

    ' IDisposable
    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                If Not fs Is Nothing Then
                    fs.Dispose()
                End If
                If Not sr Is Nothing Then
                    sr.Dispose()
                End If
            End If
            fs = Nothing
            sr = Nothing
        End If
        Me.disposedValue = True
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class

世の中から改行付CSVだとかダブルクォートやカンマを多用するエンドユーザが居なくなる事をここに願う。

0
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1