Чтение CSV файла с отсутствующими столбцами

Я пытаюсь прочитать файл CSV в своем приложении VB.net, используя следующий код:

While Not EOF(1)
    Input(1, dummy)
    Input(1, phone_number)
    Input(1, username)
    Input(1, product_name)
    Input(1, wholesale_cost)
    Input(1, dummy)
    Input(1, dummy)
End While

Мой CSV файл (как текст) выглядит следующим образом:

Customer Name,Phone Number,Username,Product,Wholesale Cost,Sales Price,Gross Profit, Customer Reference
  ,00000000000,00000000000,Product Name,25.00,35.00,10.00,
  ,00000000000,00000000000,Product Name,1.00,1.40,0.40,

Как вы можете видеть, не все поля всегда включены, и поэтому при чтении файла отображается ошибка, потому что она не может попасть в конец строки.

Как я могу обрабатывать этот тип файла?

Иногда поля будут на некоторых строках, а другие - нет.

UPDATE

Я пробовал ответить, что Zenacity предоставлен, но при попытке прочитать с помощью sArray(1) внутри цикла он возвращает Index was outside the bounds of the array

Ответ 1

Одна вещь, с которой вам следует столкнуться, заключается в том, что те методы Filexxxx официально официально и официально не рекомендуются. При использовании их Intellisense появляется с помощью:

... Функция My позволяет повысить производительность и производительность операций ввода-вывода файлов, чем FileOpen. Для получения дополнительной информации см. Microsoft.VisualBasic.FileIO.FileSystem.

Они говорят о My.Computer.FileSystem, но есть еще более полезные методы NET.

Сообщение не показывает, как данные будут сохранены, но если это массив любого вида и/или структуры, они по крайней мере субоптимальны, если не устаревают. Это сохранит его в классе, так что числовые данные могут быть сохранены в виде чисел, а вместо массива будет использоваться List.

Я сделал быстрый файл, похожий на ваш, с некоторыми случайными данными: {"CustName", "Phone", "UserName", "Product", "Cost", "Price", "Profit", "SaleDate", "RefCode"}:

  • CustName присутствует в 70% случаев
  • Имя пользователя никогда не отображается
  • RefCode присутствует в 30% случаев
  • Я добавил SaleDate, чтобы проиллюстрировать это преобразование данных

Ziggy Aurantium, 132-5562, Food Food, 8,26,9,95,1,69,08/04/2016,
Catrina Caison, 899-8599, Точилка ножа, 4,95,6,68,1,73,10/12/2016, X-873-W3
, 784-4182, Паровой компрессор, 11,02, 12,53,1,51,09/12/2016,

Код для анализа CSV

Примечание: это плохой способ анализа CSV. Есть много проблем, которые могут возникнуть, делая это таким образом; плюс требуется больше кода. Он представлен потому, что это простой способ не иметь дело с недостающими полями. См. Правильный путь

' form/class level var:
Private SalesItems As List(Of SaleItem)

SaleItem - это простой класс для хранения интересующих вас элементов. SalesItems - это коллекция, которая может хранить только объекты SaleItem. Свойства этого класса позволяют сохранить стоимость и стоимость как Decimal и дату как DateTime.

' temp var
Dim item As SaleItem
' create the collection
SalesItems = New List(Of SaleItem)

' load the data....all of it
Dim data = File.ReadAllLines("C:\Temp\custdata.csv")

' parse data lines 
' Start at 1 to skip a Header
For n As Int32 = 0 To data.Length - 1
    Dim split = data(n).Split(","c)

    ' check if it is a good line
    If split.Length = 9 Then
        ' create a new item
        item = New SaleItem
        ' store SOME data to it
        item.CustName = split(0)
        item.Phone = split(1)
        ' dont care anout user name (2)
        item.Product = split(3)
        ' convert numbers
        item.Price = Convert.ToDecimal(split(4))
        item.Cost = Convert.ToDecimal(split(5))
        ' dont use the PROFIT, calculate it in the class (6)

        ' convert date
        item.SaleDate = Convert.ToDateTime(split(7))

        ' ignore nonexistant RefCode (8)

        ' add new item to collection
        ' a List sizes itself as needed!
        SalesItems.Add(item)
    Else
        ' To Do: make note of a bad line format
    End If
Next

' show in DGV for approval/debugging
dgvMem.DataSource = SalesItems

Результат: введите описание изображения здесь

Примечания
Как правило, плохая идея хранить что-то, что можно просто вычислить. Таким образом, свойство Profit:

Public ReadOnly Property Profit As Decimal
    Get
        Return (Cost - Price)
    End Get
End Property

Он не может быть "устаревшим", если цена или цена будут обновлены.

Как показано, использование результирующей коллекции может быть легко отображено пользователю. Учитывая DataSource, DataGridView создаст столбцы и заполнит строки.

Правильный путь

String.Split(c) очень плохая идея, потому что если продукт: "Hose, Small Green", он будет рубить это и обрабатывать его как 2 поля. Существует ряд инструментов, которые будут выполнять почти всю работу за вас:

  • Прочитайте файл
  • Разбор строк
  • Сопоставьте данные CSV с классом
  • преобразует текст в соответствующий тип данных
  • создать экономичный коллектив

Помимо класса все вышеизложенное может быть выполнено всего несколькими строками, используя CSVHelper:

Private CustData As List(Of SaleItem)
...
Using sr As New StreamReader("C:\Temp\custdata.csv", False),
     csv = New CsvReader(sr)
    csv.Configuration.HasHeaderRecord = True

    CustData = csv.GetRecords(Of SaleItem)().ToList()
End Using

Две или три строки кода для чтения, разбора и создания коллекции из 250 элементов.

Даже если вы хотите сделать это вручную по какой-либо причине, CSVHelper может помочь. Вместо того, чтобы создавать для вас List(Of SaleItem), вы можете использовать его для чтения и анализа данных:

... like above
csv.Configuration.HasHeaderRecord = True
Do Until csv.Read() = False
    For n As Int32 = 0 To csv.Parser.FieldCount - 1
        DoSomethingWith(csv.GetField(n))
    Next
Loop

Это возвращает поля вам по одному. Он не конвертирует ни даты, ни цены, но он не будет задыхаться от отсутствующих элементов данных.

Ресурсы

Ответ 2

Предупреждение:, если значения CustomerName или ProductName могут содержать запятую (.i.e. CustomerName = "Callaway , Mark"), вы не можете использовать метод String.Split(). и лучше искать сторонний csv-парсер, или вы можете использовать TextFieldParser Class → статья MSDN

Мой ответ предполагает, что отсутствующие поля всегда находятся с правой стороны строки и что значение полей не содержит запятую (else @Plutonix ответ - это то, что вы ищете)

Используя этот код, вы сможете импортировать строки с отсутствующими полями.

Вы должны прочитать каждую строку из файла csv, подсчитать "," в этой строке, используя следующий код

Line.Count(Function(c As Char) c = ",")

если число меньше 7 (8 столбцов), вы добавите отсутствующий ","

 String.PadRight((7 - intCommaCount), ",")

Примечание:, если отсутствующая запятая находится слева, вы можете использовать String.PadLeft((7 - intCommaCount), ",")

И разделим строку на свойства Item

Я создал следующий Item класс

Public Class MyItem


Public Property CustomerName As String
Public Property PhoneNumber As String
Public Property Username As String
Public Property Product As String
Public Property WholesaleCost As String
Public Property SalesPrice As String
Public Property GrossProfit As String
Public Property CustomerReference As String

Public Shared Function CreateObjectFromLine(ByVal Line As String) As MyItem

    'Count Comma occurence in Line
    Dim intCommaCount As Integer = Line.Count(Function(c As Char) c = CChar(","))
    Dim strTemp = Line

    'Add missing comma's
    If intCommaCount < 7 Then

        strTemp = strTemp.PadRight((7 - intCommaCount), ",")

    End If

    'Split Line and return MyItem Class
    Dim str() As String = strTemp.Split(",")

    Return New MyItem With {.CustomerName = str(0),
        .PhoneNumber = str(1),
        .Username = str(2),
        .Product = str(3),
        .WholesaleCost = str(4),
        .SalesPrice = str(5),
        .GrossProfit = str(6),
        .CustomerReference = str(7)}




End Function



End Class

И я использовал следующий код для импорта данных из файла CSV

    Dim SalesItems As New List(Of MyItem)
    Dim csvFile As String = "C:\1.csv"


    Using csvStreamReader As New IO.StreamReader(csvFile)

        While Not csvStreamReader.EndOfStream

       Dim strLine as string = csvStreamReader.ReadLine

       ' Skip Header
       If strLine.StartsWith("Customer Name") Then Continue While

            Dim item As MyItem = MyItem.CreateObjectFromLine(strLine)

            SalesItems.Add(item)



        End While


    End Using

    'Showing Result in a DataGridView
    dgvItems.DataSource = SalesItems

Примечание.. Это простой пример, который требует добавления ошибки. Обработка Try... Catch, Null Проверка

Ответ 3

Используя следующую функцию, вы можете оценить содержимое файла по строкам и предпринять соответствующие действия.

Imports System.IO    
Private Sub ParseCSVFile(psFile As String)
    Dim sArray() As String
    Dim Customer_Name As String = String.Empty
    Dim Phone_Number As String = String.Empty
    Dim Username As String = String.Empty
    Dim Product As String = String.Empty
    Dim Wholesale_Cost As String = String.Empty
    Dim Sales_Price As String = String.Empty
    Dim Gross_Profit As String = String.Empty
    Dim Customer_Reference As String = String.Empty

    Try
        Using objStreamReader As StreamReader = New StreamReader(psFile) 'should be full path
            Dim sLine As String = String.Empty
            Do
                sLine = objStreamReader.ReadLine()
                If sLine <> Nothing Then
                    sArray = Split(sLine, ",")
                    Customer_Name = sArray(0)
                    Phone_Number = sArray(1)
                    Username = sArray(2)
                    Product = sArray(3)
                    Wholesale_Cost = sArray(4)
                    Sales_Price = sArray(5)
                    Gross_Profit = sArray(6)
                    Customer_Reference = sArray(7)
                    Debug.Print(Customer_Name & "," & Phone_Number & "," & Username & "," & Product & "," & Wholesale_Cost & "," & Sales_Price & "," & Gross_Profit & "," & Customer_Reference)
                End If
            Loop Until sLine Is Nothing
        End Using
    Catch
        'log error
    End Try
End Sub