Ответ 1
Простое регулярное выражение для синтаксического анализа строки CSV, если не считать кавычек внутри указанных полей, равно:
"[^"]*"|[^,]*
Каждое совпадение возвращает поле.
У меня есть приложение VBA, которое работает каждый день. Он проверяет папку, в которую загружаются файлы CSV, и добавляет их содержимое в базу данных. Когда они разобрали их, я понял, что некоторые ценности имеют запятую как часть их имени. Эти значения содержались в строковых литералах.
Итак, я пытаюсь понять, как разбирать этот CSV и игнорировать запятые, которые содержатся в строковых литералах. Например...
1,2,3,"This should,be one part",5,6,7 Should return
1
2
3
"This should,be one part"
5
6
7
Я использую функцию VBA split(), потому что я не хочу изобретать колесо, но если мне нужно догадаться, что я сделаю что-то еще.
Любые предложения будут оценены.
Простое регулярное выражение для синтаксического анализа строки CSV, если не считать кавычек внутри указанных полей, равно:
"[^"]*"|[^,]*
Каждое совпадение возвращает поле.
Первый способ решить эту проблему - посмотреть на структуру строки из файла csv (int, int, "String literal, будет иметь не более одной запятой" и т.д.). Наивное решение было бы (если предположить, что линия не имеет точек с запятой)
Function splitLine1(line As String) As String()
Dim temp() As String
'Splits the line in three. The string delimited by " will be at temp(1)
temp = Split(line, Chr(34)) 'chr(34) = "
'Replaces the commas in the numeric fields by semicolons
temp(0) = Replace(temp(0), ",", ";")
temp(2) = Replace(temp(2), ",", ";")
'Joins the temp array with quotes and then splits the result using the semicolons
splitLine1 = Split(Join(temp, Chr(34)), ";")
End Function
Эта функция решает только эту проблему. Другой способ выполнения задания - использовать объект регулярных выражений из VBScript.
Function splitLine2(line As String) As String()
Dim regex As Object
Set regex = CreateObject("vbscript.regexp")
regex.IgnoreCase = True
regex.Global = True
'This pattern matches only commas outside quotes
'Pattern = ",(?=([^"]*"[^"]*")*(?![^"]*"))"
regex.Pattern = ",(?=([^" & Chr(34) & "]*" & Chr(34) & "[^" & Chr(34) & "]*" & Chr(34) & ")*(?![^" & Chr(34) & "]*" & Chr(34) & "))"
'regex.replaces will replace the commas outside quotes with semicolons and then the
'Split function will split the result based on the semicollons
splitLine2 = Split(regex.Replace(line, ";"), ";")
End Function
Этот метод кажется гораздо более загадочным, но не зависит от структуры строки
Вы можете больше узнать о шаблонах регулярных выражений в VBScript Здесь
@Gimp сказал...
Текущие ответы не содержат достаточно деталей.
Я столкнулся с той же проблемой. Ищите более подробную информацию в этом Ответ.
Выяснить ответ @MRAB:
Function ParseCSV(FileName)
Dim Regex 'As VBScript_RegExp_55.RegExp
Dim MatchColl 'As VBScript_RegExp_55.MatchCollection
Dim Match 'As VBScript_RegExp_55.Match
Dim FS 'As Scripting.FileSystemObject
Dim Txt 'As Scripting.TextStream
Dim CSVLine
ReDim ToInsert(0)
Set FS = CreateObject("Scripting.FileSystemObject")
Set Txt = FS.OpenTextFile(FileName, 1, False, -2)
Set Regex = CreateObject("VBScript.RegExp")
Regex.Pattern = """[^""]*""|[^,]*" '<- MRAB answer
Regex.Global = True
Do While Not Txt.AtEndOfStream
ReDim ToInsert(0)
CSVLine = Txt.ReadLine
For Each Match In Regex.Execute(CSVLine)
If Match.Length > 0 Then
ReDim Preserve ToInsert(UBound(ToInsert) + 1)
ToInsert(UBound(ToInsert) - 1) = Match.Value
End If
Next
InsertArrayIntoDatabase ToInsert
Loop
Txt.Close
End Function
Вам нужно настроить вкладку InsertArrayIntoDatabase для собственной таблицы. Mine имеет несколько текстовых полей с именем f00, f01 и т.д.
Sub InsertArrayIntoDatabase(a())
Dim rs As DAO.Recordset
Dim i, n
Set rs = CurrentDb().TableDefs("tbl").OpenRecordset()
rs.AddNew
For i = LBound(a) To UBound(a)
n = "f" & Format(i, "00") 'fields in table are f00, f01, f02, etc..
rs.Fields(n) = a(i)
Next
rs.Update
End Sub
Обратите внимание, что вместо использования CurrentDb()
в InsertArrayIntoDatabase()
вы должны действительно использовать глобальную переменную, которая получает значение CurrentDb()
до ParseCSV()
, потому что выполняется CurrentDb()
в цикле очень медленно, особенно в очень большом файле.
Если вы работаете с таблицами MS Access, есть преимущества в простом импорте текста с диска. Например:
''If you have a reference to the Windows Script Host Object Model
Dim fs As New FileSystemObject
Dim ts As TextStream
''For late binding
''Dim fs As Object
''Dim ts As Object
''Set fs=CreateObject("Scripting.FileSystemObject")
Set ts = fs.CreateTextFile("z:\docs\import.csv", True)
sData = "1,2,3,""This should,be one part"",5,6,7"
ts.Write sData
ts.Close
''Just for testing, your table will already exist
''sSQL = "Create table Imports (f1 int, f2 int, f3 int, f4 text, " _
'' & "f5 int, f6 int, f7 int)"
''CurrentDb.Execute sSQL
''The fields will be called F1,F2 ... Fn in the text file
sSQL = "INSERT INTO Imports SELECT * FROM " _
& "[text;fmt=delimited;hdr=no;database=z:\docs\].[import.csv]"
CurrentDb.Execute sSQL
Я знаю, что это старый пост, но думал, что это может помочь другим. Это было плагиат/пересмотрено из http://n3wt0n.com/blog/comma-separated-values-and-quoted-commas-in-vbscript/, но работает очень хорошо и устанавливается как функция, с которой вы можете передать свою входную строку.
Function SplitCSVLineToArray(Line, RemoveQuotes) 'Pass it a line and whether or not to remove the quotes
ReplacementString = "#!#!#" 'Random String that we should never see in our file
LineLength = Len(Line)
InQuotes = False
NewLine = ""
For x = 1 to LineLength
CurrentCharacter = Mid(Line,x,1)
If CurrentCharacter = Chr(34) then
If InQuotes then
InQuotes = False
Else
InQuotes = True
End If
End If
If InQuotes Then
CurrentCharacter = Replace(CurrentCharacter, ",", ReplacementString)
End If
NewLine = NewLine & CurrentCharacter
Next
LineArray = split(NewLine,",")
For x = 0 to UBound(LineArray)
LineArray(x) = Replace(LineArray(x), ReplacementString, ",")
If RemoveQuotes = True then
LineArray(x) = Replace(LineArray(x), Chr(34), "")
End If
Next
SplitCSVLineToArray = LineArray
End Function
Я понимаю, что это старый пост, но я просто столкнулся с ним, ища решение той же проблемы, что и у OP, поэтому поток по-прежнему имеет значение.
Чтобы импортировать данные из CSV, я добавляю запрос на рабочий лист
wksTarget.Querytables.add(Connection:=strConn, Destination:=wksTarget.Range("A1"))
затем установите соответствующие параметры запроса (например, Name, FieldNames, RefreshOnOpen
и т.д.)
Querytables могут обрабатывать различные разделители через TextFileCommaDelimiter
, TextFileSemiColonDelimiter
и другие. И есть ряд других параметров (TextfilePlatform, TextFileTrailingMinusNumbers, TextFileColumnTypes, TextFileDecimalSeparator, TextFileStartRow, TextFileThousandsSeparator
), которые обрабатывают особенности исходного файла.
В соответствии с OP, QueryTables также имеет параметр, предназначенный для обработки запятых, находящихся в двойных кавычках - TextFileQualifier = xlTextQualifierDoubleQuote
.
Я нахожу QueryTables намного проще, чем писать код, чтобы импортировать файл, разделять/анализировать строки или использовать выражения REGEX.
Все вместе пример фрагмента кода будет выглядеть примерно так:
strConn = "TEXT;" & "C:\Desktop\SourceFile.CSV"
varDataTypes = Array(5, 1, 1, 1, 1, 1, 5, 5)
With wksTarget.QueryTables.Add(Connection:=strConn, _
Destination:=wksTarget.Range("A1"))
.Name = "ImportCSV"
.FieldNames = True
.RefreshOnFileOpen = False
.SaveData = True
.TextFilePlatform = xlMSDOS
.TextFileStartRow = 1
.TextFileParseType = xlDelimited
.TextFileCommaDelimiter = True
.TextFileTextQualifier = xlTextQualifierDoubleQuote
.TextFileColumnDataTypes = varDataTypes
.Refresh BackgroundQuery:=False
End With
Я предпочитаю удалить QueryTable после импорта данных (wksTarget.QueryTable("ImportCSV").Delete
), но я полагаю, что он может быть создан только один раз, а затем просто обновлен, если источник и адресаты для данных не изменяются.
Я сделал еще один вариант решения для разбора файлов CSV с "заключенными в кавычки" текстовыми строками с возможными разделителями, такими как запятая внутри двойных кавычек. Этот метод не требует выражений регулярных выражений или каких-либо других дополнений. Кроме того, этот код имеет дело с несколькими запятыми между кавычками. Вот подпрограмма для тестирования:
Sub SubstituteBetweenQuotesSub()
'In-string character replacement function by Maryan Hutsul 1/29/2019
Dim quote, quoteTwo As Integer
Dim oddEven As Integer
Dim i, counter As Integer
Dim byteArray() As Byte
'LineItems are lines of text read from CSV file, or any other text string
LineItems = ",,,2019NoApocalypse.ditamap,[email protected],Approver,""JC, ,Son"",Reviewer,[email protected],""God, All-Mighty,"",2019-01-29T08:47:29.290-05:00"
quote = 1
oddEven = 0
Do Until quote = 0
quote = InStr(quote, LineItems, Chr(34))
quoteTwo = InStr(quote + 1, LineItems, Chr(34))
oddEven = oddEven + 1
If oddEven Mod 2 = 1 And quote <> 0 Then
counter = 0
For i = quote To quoteTwo
byteArray = StrConv(LineItems, vbFromUnicode)
If i <> 0 Then
If byteArray(i - 1) = 44 Then '44 represents comma, can also do Chr(44)
counter = counter + 1
End If
End If
Next i
LineItems = Left(LineItems, quote - 1) & Replace(LineItems, ",", ";", quote, counter)
quote = quote + 1
ElseIf quote <> 0 Then
quote = quote + 1
End If
Loop
End Sub
Вот функция, в которую вы можете передавать строки из .csv,.txt или любых других текстовых файлов:
Function SubstituteBetweenQuotes(LineItems)
'In-string character replacement function by Maryan Hutsul 1/29/2019
'LineItems are lines of text read from CSV file, or any other text string
Dim quote, quoteTwo As Integer
Dim oddEven As Integer
Dim i, counter As Integer
Dim byteArray() As Byte
quote = 1
oddEven = 0
Do Until quote = 0
quote = InStr(quote, LineItems, Chr(34))
quoteTwo = InStr(quote + 1, LineItems, Chr(34))
oddEven = oddEven + 1
If oddEven Mod 2 = 1 And quote <> 0 Then
counter = 0
For i = quote To quoteTwo
byteArray = StrConv(LineItems, vbFromUnicode)
If i <> 0 Then
If byteArray(i - 1) = 44 Then '44 represents "," comma, can also do Chr(44)
counter = counter + 1
End If
End If
Next i
LineItems = Left(LineItems, quote - 1) & Replace(LineItems, ",", ";", quote, counter)
quote = quote + 1
ElseIf quote <> 0 Then
quote = quote + 1
End If
Loop
SubstituteBetweenQuotes = LineItems
End Function
А ниже приведен код для чтения CSV файла с используемой функцией:
Dim fullFilePath As String
Dim i As Integer
'fullFilePath - full link to your input CSV file
Open fullFilePath For Input As #1
row_number = 0
column_number = 0
'EOF - End Of File (1) - file #1
Do Until EOF(1)
Line Input #1, LineFromFile
LineItems = Split(SubstituteBetweenQuotes(LineFromFile), ",")
For i = LBound(LineItems) To UBound(LineItems)
ActiveCell.Offset(row_number, i).Value = LineItems(i)
Next i
row_number = row_number + 1
Loop
Close #1
Все разделители и символы замены могут быть изменены в соответствии с вашими потребностями. Надеюсь, что это полезно, так как у меня было много пути, чтобы решить некоторые проблемы с импортом CSV
Недавно у нас была похожая проблема с анализом CSV в Excel, и мы внедрили решение, адаптированное из кода Javascript для анализа данных CSV:
Function SplitCSV(csvText As String, delimiter As String) As String()
' Create a regular expression to parse the CSV values
Dim RegEx As New RegExp
' Create pattern which will match each column in the CSV, wih submatches for each of the groups in the regex
' Match Groups: Delimiter Quoted fields Standard fields
RegEx.Pattern = "(" + delimiter + "|^)(?:\""([^\""]*(?:\""\""[^\""]*)*)\""|([^\""\""" + delimiter + """]*))"
RegEx.Global = True
RegEx.IgnoreCase = True
' Create an array to hold all pattern matches (i.e. columns)
Dim Matches As MatchCollection
Set Matches = RegEx.Execute(csvText)
' Create an array to hold output data
Dim Output() As String
' Create int to track array location when iterating
Dim i As Integer
i = 0
' Manually add blank if first column is blank, since VBA regex misses this
If csvText Like ",*" Then
ReDim Preserve Output(i)
Output(i) = ""
i = i + 1
End If
' Iterate over all pattern matches and get values into output array
Dim Match As Match
Dim MatchedValue As String
For Each Match In Matches
' Check to see which kind of value we captured (quoted or unquoted)
If (Len(Match.SubMatches(1)) > 0) Then
' We found a quoted value. When we capture this value, unescape any double quotes
MatchedValue = Replace(Match.SubMatches(1), """""", """")
Else
' We found a non-quoted value
MatchedValue = Match.SubMatches(2)
End If
' Now that we have our value string, let add it to the data array
ReDim Preserve Output(i)
Output(i) = MatchedValue
i = i + 1
Next Match
' Return the parsed data
SplitCSV = Output
End Function
Принимая во внимание ваши комментарии, вы можете легко найти здесь
Попробуй это! Убедитесь, что "Регулярные выражения Microsoft VBScript 5.5" отмечены галочкой в разделе "Ссылки" в разделе "Инструменты".
Function Splitter(line As String, n As Integer)
Dim s() As String
Dim regex As Object
Set regex = CreateObject("vbscript.regexp")
regex.IgnoreCase = True
regex.Global = True
regex.Pattern = ",(?=([^\""]*\""[^\""]*\"")*[^\""]*$)"
s = split(regex.Replace(line, "|/||\|"), "|/||\|")
Splitter = s(n - 1)
End Function