删除嵌入在CSV文件列中的杂散CRLF

时间:2016-01-01 21:00:14

标签: vb.net csv powershell

我需要一些指导如何解决我正在处理的问题。根本问题是我需要在另一个程序中使用CSV文件。创建CSV文件的源系统不会在导出的任何数据字段中删除CRLF(这意味着某些字段具有嵌入的CRLF)。因此,我收到一个CSV文件,其中包含格式错误的行。我的最终目标是实用程序

  • 检查每行的第一列(如果正确,则为GUID,长度为36,或
  • 计算每行中的列数(以下示例)。

在下面的例子中,我正在查看列数。如果正确的计数是18,那么我希望它将该行写入新文件。如果列数不正确,我想从该行中删除CRLF,直到列计数正确。

同样,有两种方法可以解决我所知道的问题:

  1. 检查第一列的长度为36(在第一个逗号之前,不包括第一行,即标题行),或
  2. 对列进行计数并删除任何尾随的CRLF,直到列数等于18(总列数)。
  3. 到目前为止,我对代码的问题是能够将有效行写出到新文件中。目前它写出System.String[]而不是实际的行。

    Public Class Form1
        Private Sub btnFixit_Click(sender As Object, e As EventArgs) Handles btnFixit.Click
            Dim iBadRowNumber As Integer = vbNull
            Dim strFixedFile As System.IO.StreamWriter = My.Computer.FileSystem.OpenTextFileWriter(Me.txtFixedFile.Text, True)
            Using MyReader As New Microsoft.VisualBasic.FileIO.TextFieldParser(Me.txtBaselineFileToProcess.Text)
                MyReader.TextFieldType = FileIO.FieldType.Delimited
                MyReader.SetDelimiters(",")
                Dim currentRow As String()
    
                While Not MyReader.EndOfData
                    Try
                        currentRow = MyReader.ReadFields()
                        If currentRow.Count = 18 Then
                            strFixedFile.WriteLine(currentRow)
                        Else
                            ' Future code here to fix the line
                        End If
                    Catch ex As Microsoft.VisualBasic.FileIO.MalformedLineException
                        MsgBox("Line " & ex.Message &
                        "is not valid and will be skipped.")
                    End Try
                End While
            End Using
           strFixedFile.Close()
        End Sub
    End Class
    

    以下是2个正确行的示例,中间有一行不正确的行。在此示例中,以Sometown开头的行实际上是前一行的一部分。我还看到一个真正的行可能被分成三个或更多的部分行,就像你在Sometown行中看到的那样。

    CustomerId,CustomerName,Status,Type,CustomerNumber,DBA,Address1,Address2,City,State,ZipCode,WebAddr,EMail,SalesCode,ServiceCode,DivisionCode,BranchCode,DepartmentCode
    6d0125cd-70cf-4048-9ee1-8d9682e426a5,"Smith,James",Active,Customer,8,,103 Long Dr,,AnotherTown,NJ,000000,,,!!S,!%9,!!#,!!#,"!""."
    35ed375c-c226-4879-a789-469cae63383c,"Doe, John",Active,Customer,55281,,28 Short Drive,,
    Sometown,CA,12345,,
    email@domain.com,"!$,",!$^,!!#,!!#,!!K
    a5972bce-408f-4def-b77c-4ae0148dd045,"Duck,Donald",Active,Customer,25,,236 North Main St,,Mytown,PA,11111,,,!!2,!%9,!!#,!!#,"!""."
    

    执行特定任务可能有更多优雅的方法。无论是对我上面的逻辑进行更正,还是以完全不同的方式解决VB.net或PowerShell中的问题,我都是开放的。

2 个答案:

答案 0 :(得分:1)

通常,csv可以有多行字段而没有问题。但那些需要被引号括起来。

在您的示例中,这似乎并非如此,但另一方面也没有多行字段,值为Sometown的字段从新行开始。所以我想知道这是不是原始数据。

如果多行字段 用引号括起来,则需要通知解析器。

但即使使用单行,您也会遇到内部带有分隔符的字段引起的问题。幸运的是,这些都是引用的(因为它们应该是),所以你需要设置TextFieldParser.HasFieldsEnclosedInQuotes属性。

现在,如果您的多行字段恰好被引用(应该如此),则上述设置应解决所有问题。

<强>更新

你可以这样做:

currentRow = MyReader.ReadFields()
If currentRow.Count = 18 Then
    strFixedFile.WriteLine(currentRow)
Else
    'Write current row without newline
    'Read next line/row
    'WriteLine this row
End If

但是你必须在内部使用分隔符处理像"Smith,James"这样的字段。确保您的解析器正确处理引用的字段(参见上文)。

答案 1 :(得分:0)

最直接的方法可能是您第一次验证检查的变体:

  • 逐行读取文件,并将当前行和上一行保存在缓冲区中。
  • 检查行的开头是否是正确的GUID(例如,使用正则表达式)。
  • 如果当前行没有以GUID开头,请将其附加到上一行。
  • 否则将上一行写入输出文件,除非它为空,然后将其替换为当前行。

我不了解VB.net,但在PowerShell中看起来有点像这样:

$reader = New-Object IO.StreamReader ('C:\path\to\input.csv')
$writer = New-Object IO.StreamWriter ('C:\path\to\output.csv', $false)

$writer.WriteLine($reader.ReadLine())  # copy CSV header

$output  = ''  # output buffer
$current = ''  # pre-buffered current line from input file
while ($reader.Peek() -ge 0) {
  # read line into pre-buffer
  $current = $reader.ReadLine()

  $hasGUID = $current -match '^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12},'

  # append line to output buffer if it doesn't have a GUID, otherwise
  # write the output buffer to file if it contains data and move the
  # current line to the output buffer
  if (-not $hasGUID) {
    $output += $current
  } else {
    if ($output) { $writer.WriteLine($output) }
    $output = $current
  }
}
# write remaining pre-buffered line (if there is one)
if ($current -and $hasGUID) { $writer.WriteLine($current) }

$reader.Close(); $reader.Dispose()
$writer.Close(); $writer.Dispose()