2016-05-17 5 views
1

Ich habe eine CSV-Datei mit ziemlich unregelmäßigen Einträgen. Der erste Eintrag einer Zeile hat keine umschließenden Anführungszeichen, wird die ganze Zeile zitiert, und jedes Feld doppelt so hoch ist wie folgt zitiert:Ruby: CSV mit irregulären Feldern bereinigen

# my_file.csv, opened with sublime text : 

# Headers 
"first_name,""last_name"",""username"",""phone_number"",""address"",""email_address"",""email_address_confirmed"",""joined_at"",""status"",""is_admin"",""accept_emails_from_admin"",""language"",""can_post_listings""" 

# Sample entry 
"Mr X,""Mr X"",""mrxxx"","""","""",""[email protected]"",""true"",""2015-09-21 09:08:51 UTC"",""accepted"",""true"",""true"",""fr"",""true""" 

ich konnte Vorprozess die Datei etwas anderes als Rubin (Excel, ein einfach regex/ersetzen, oder alles, was Sie denken könnten), aber da ich wahrscheinlich mehrere Male tun muss, wäre eine Ruby-Lösung großartig.

Derzeit bin ich mit

csv = File.open(csv_file_path) 
CSV.parse(csv, :headers => true) 

nur Und ich sehe nicht wirklich, wie ich diese Differenz leicht jede Reihe nur für den ersten Eintrag beheben könnte ...

Das Problem ist die CSV wird nicht korrekt analysiert und betrachtet stattdessen jede Zeile als eine einzelne Zeichenfolge (anstelle eines Arrays mit so vielen Elementen wie Spalten).

# csv.headers : note this is an array with a single string 
["first_name,\"last_name\",\"username\",\"phone_number\",\"address\",\"email_address\",\"email_address_confirmed\",\"joined_at\",\"status\",\"is_admin\",\"accept_emails_from_admin\",\"language\",\"can_post_listings\""] 

# csv.to_a.last 
["xxx,\"xxxx\",\"martin\",\"\",\"\",\"[email protected]\",\"false\",\"2016-05-12 13:06:53 UTC\",\"pending_email_confirmation\",\"false\",\"true\",\"fr\",\"false\""] 

EDIT: ich versucht haben folgendes

processed = File.readlines(path).map do |row| 
    row.strip     # strip newlines 
     .gsub(/^\"|\"$/, '') # remove outer quotes 
     .gsub(/\"\"/, '"')  # fix double quotes 
end 
CSV.parse(processed.join('\n')) 

I laufen in eine CSV::MalformedCSVError: Missing or stray quote in line 1

Musterausgänge

# File.readlines(path).first 
# => "\"first_name,\"\"last_name\"\",\"\"username\"\",\"\"phone_number\"\",\"\"address\"\",\"\"email_address\"\",\"\"email_address_confirmed\"\",\"\"joined_at\"\",\"\"status\"\",\"\"is_admin\"\",\"\"accept_emails_from_admin\"\",\"\"language\"\",\"\"can_post_listings\"\"\"\n" 

# processed.first 
# => "first_name,\"last_name\",\"username\",\"phone_number\",\"address\",\"email_address\",\"email_address_confirmed\",\"joined_at\",\"status\",\"is_admin\",\"accept_emails_from_admin\",\"language\",\"can_post_listings\"" 

EDIT 2

Ouch, habe ich einige nein manchmal Komm-Kommas, und Daves Antwort scheint in diesen Fällen zu versagen. Es ist dieses Feld

"" 45, street_addr - Place ""

, die ein Komma enthält, die kein Trennzeichen ist. Der vollständige Eintrag

"Mr x,""Mr xx"",""bbernelin"","""",""45, street_addr - Place"",""[email protected]"",""true"",""2016-04-13 11:14:08 UTC"",""accepted"",""false"",""true"",""fr"",""true""" 
+0

gibt es mehr Zitat s in Ihrem Beispiel, das Sie in Ihrer Beschreibung angeben. Es gibt ein Zitat am Anfang jeder Zeile und zwei Anführungszeichen auf jeder Seite jedes Feldes nach dem ersten. Ich denke, die Antwort, die ich gepostet habe, wird für dich funktionieren, aber wenn du dein Beispiel mit deiner Beschreibung übereinstimmst, könnten wir vielleicht die Lösung vereinfachen. –

Antwort

1

Nun, landete ich mit:

processed = File.readlines(path).map do |row| 
    row.strip.gsub('""', '"')[1..-2] 
end.join("\n") 
CSV.parse(processed) 

Die [1..-2] entfernt nur die extra " am Anfang/Ende der Zeile, die Dinge verpfuscht

1

Es sieht aus wie es

  • 0 oder mehr Anführungszeichen um jeden Eintrag
  • genau 1 Komma zwischen jedem Eintrag
  • keine Kommas oder Anführungszeichen in einem Eintrag

So können Sie alle Anführungszeichen um jeden Eintrag durch 1 Zitat ersetzen:

csv = gsub(/(?<=^|,)"*([^,"\n]*)"*(?=,|$)/, %Q("\\1")) 

kommentiert regexp:

/ 
    (?<=^|,) # pattern is preceded by the beginning of the string or a comma 
    "*   # any number of " 
    ([^,"\n]*) # any number of characters, not , " or newline 
    "*   # any number of " 
    (?=,|$)  # pattern is followed by the end of the string or a comma 
/

Es scheint, korrekte Ergebnisse auf Ihrem Beispiel zu produzieren:

csv = %Q("first_name,""last_name"",""username"",""phone_number"",""address"",""email_address"",""email_address_confirmed"",""joined_at"",""status"",""is_admin"",""accept_emails_from_admin"",""language"",""can_post_listings"""\n) + 
     %Q("Mr X,""Mr X"",""mrxxx"","""","""",""[email protected]"",""true"",""2015-09-21 09:08:51 UTC"",""accepted"",""true"",""true"",""fr"",""true""") 
CSV.parse(csv.gsub(/(?<=^|,)"*([^,"\n]*)"*(?=,|$)/, %Q("\\1")), headers: true).to_a 
=> [ 
    ["first_name", "last_name", "username", "phone_number", "address", "email_address", "email_address_confirmed", "joined_at", "status", "is_admin", "accept_emails_from_admin", "language", "can_post_listings"], 
    ["Mr X", "Mr X", "mrxxx", "", "", "[email protected]", "true", "2015-09-21 09:08:51 UTC", "accepted", "true", "true", "fr", "true"] 
    ] 
+0

Vielen Dank, Ihre Antwort funktioniert, aber ich (realisierte ich) habe einige Felder mit verschachtelten Kommas (Straßenadressen wie 'Paris, Frankreich'). Denkst du, dass du eine bessere Regex finden kannst? Ich habe meine Frage bearbeitet, um ein Beispiel für ein solches Feld zu geben. –

2

Von dem, was ich die ganze Linie sagen kann, hat umschließenden Anführungszeichen, und dann einige Felder sind doppelt zitiert.

require 'csv' 

processed = DATA.map do |row| 
    row.strip     # strip newlines 
    .gsub(/^\"|\"$/, '') # remove outer quotes 
    .gsub(/\"\"/, '"')  # fix double quotes 
end 

CSV.parse(processed.join('\n'), headers: true) do |row| 
    p row 
end 

__END__ 
"first_name,""last_name"",""username"",""phone_number"",""address"",""email_address"",""email_address_confirmed"",""joined_at"",""status"",""is_admin"",""accept_emails_from_admin"",""language"",""can_post_listings""" 
"Mr X,""Mr X"",""mrxxx"","""","""",""[email protected]"",""true"",""2015-09-21 09:08:51 UTC"",""accepted"",""true"",""true"",""fr"",""true""" 

Ergebnisse: Fixing dass der CSV-Parser glücklich macht, so dass dies scheint zu funktionieren

#<CSV::Row "first_name":"Mr X" "last_name":"Mr X" "username":"mdxxx" 
"phone_number":"" "address":"" "email_address":"[email protected]" 
"email_address_confirmed":"true" "joined_at":"2015-09-21 09:08:51 UTC" 
"status":"accepted" "is_admin":"true" "accept_emails_from_admin":"true" 
"language":"fr" "can_post_listings":"true"> 
+0

Hey danke für die Antwort, aber ich kann es immer noch nicht zur Arbeit bringen. Vielleicht lese ich die Datei schlecht? Ich habe meine Frage mit zusätzlichen Informationen bearbeitet. –

+0

Mein Ansatz ist übergeneralisiert und behandelt keine Kommas; Das ist der richtige Ansatz. Ich denke, es funktioniert nicht für Cyril, da er die CSV in einer einzigen Zeichenfolge hat. Dies sollte für Cyril entweder funktionieren, wenn die Regexp im ersten 'gsub' zu'/(? <=^| \ N) \ "| \" (? = $ | \ N)/'wird oder wenn Cyril die Datei in ein Array von Strings mit z 'Datei # readlines'. –

Verwandte Themen