作成中のcsvファイルを処理してからお客様がアップロードし、会社名のように値にカンマが含まれている可能性があるCSVファイルの処理方法に関する提案を探しています。
私たちが見ているアイデアのいくつかは、次のとおりです。引用符で囲まれた識別子(値 "、"値 "、"など)、または|を使用します。コンマの代わりに。最大の問題は、私たちがそれを簡単にしなければならないことです、さもなければ、顧客はそれをしないでしょう。
他の人が言ったように、あなたは引用符を含む値をエスケープする必要があります。これは、埋め込み引用符や改行を含む引用符付きの値をサポートする、C#の小さなCSVリーダーです。
ところで、これは単体テストのコードです。この質問はよく寄せられるように思われるので、今すぐ投稿しています。単純なCSVサポートではライブラリ全体が欲しくない場合もあります。
あなたは次のようにそれを使用することができます:
using System;
public class test
{
public static void Main()
{
using ( CsvReader reader = new CsvReader( "data.csv" ) )
{
foreach( string[] values in reader.RowEnumerator )
{
Console.WriteLine( "Row {0} has {1} values.", reader.RowIndex, values.Length );
}
}
Console.ReadLine();
}
}
これがクラスです。 Csv.Escape
関数を使って有効なCSVを書くこともできます。
using System.IO;
using System.Text.RegularExpressions;
public sealed class CsvReader : System.IDisposable
{
public CsvReader( string fileName ) : this( new FileStream( fileName, FileMode.Open, FileAccess.Read ) )
{
}
public CsvReader( Stream stream )
{
__reader = new StreamReader( stream );
}
public System.Collections.IEnumerable RowEnumerator
{
get {
if ( null == __reader )
throw new System.ApplicationException( "I can't start reading without CSV input." );
__rowno = 0;
string sLine;
string sNextLine;
while ( null != ( sLine = __reader.ReadLine() ) )
{
while ( rexRunOnLine.IsMatch( sLine ) && null != ( sNextLine = __reader.ReadLine() ) )
sLine += "\n" + sNextLine;
__rowno++;
string[] values = rexCsvSplitter.Split( sLine );
for ( int i = 0; i < values.Length; i++ )
values[i] = Csv.Unescape( values[i] );
yield return values;
}
__reader.Close();
}
}
public long RowIndex { get { return __rowno; } }
public void Dispose()
{
if ( null != __reader ) __reader.Dispose();
}
//============================================
private long __rowno = 0;
private TextReader __reader;
private static Regex rexCsvSplitter = new Regex( @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" );
private static Regex rexRunOnLine = new Regex( @"^[^""]*(?:""[^""]*""[^""]*)*""[^""]*$" );
}
public static class Csv
{
public static string Escape( string s )
{
if ( s.Contains( QUOTE ) )
s = s.Replace( QUOTE, ESCAPED_QUOTE );
if ( s.IndexOfAny( CHARACTERS_THAT_MUST_BE_QUOTED ) > -1 )
s = QUOTE + s + QUOTE;
return s;
}
public static string Unescape( string s )
{
if ( s.StartsWith( QUOTE ) && s.EndsWith( QUOTE ) )
{
s = s.Substring( 1, s.Length - 2 );
if ( s.Contains( ESCAPED_QUOTE ) )
s = s.Replace( ESCAPED_QUOTE, QUOTE );
}
return s;
}
private const string QUOTE = "\"";
private const string ESCAPED_QUOTE = "\"\"";
private static char[] CHARACTERS_THAT_MUST_BE_QUOTED = { ',', '"', '\n' };
}
2017の場合、csvは完全に指定されています - RFC 4180。
これは非常に一般的な仕様であり、多くのライブラリで完全にカバーされています( example )。
簡単に入手できるcsvライブラリを使用してください - つまりRFC 4180です。
実際にはCSV形式の仕様と、コンマの処理方法があります。
改行(CRLF)、二重引用符、およびコンマを含むフィールドは、二重引用符で囲む必要があります。
http://tools.ietf.org/html/rfc4180
したがって、値foo
とbar,baz
を持つには、次のようにします。
foo,"bar,baz"
考慮すべきもう1つの重要な要件(これも仕様から):
二重引用符を使用してフィールドを囲む場合は、フィールド内に現れる二重引用符の前に別の二重引用符を付けることによってエスケープする必要があります。例えば:
"aaa","b""bb","ccc"
CSV形式では、値を区切るためにコンマが使用されます。キャリッジリターン、ラインフィード、コンマ、または二重引用符を含む値は、二重引用符で囲まれます。二重引用符を含む値は引用符で囲まれ、各リテラル引用符は直前の引用符でエスケープされます。たとえば、次の3つの値は、
test
list, of, items
"go" he said
次のようにエンコードされます。
test
"list, of, items"
"""go"" he said"
どのフィールドでも引用できますが、カンマ、CR/NL、または引用符{mustを含むフィールドのみ引用できます。
CSVフォーマットには本当の標準はありませんが、ほとんどすべてのアプリケーションは文書化された ここに の規則に従います。他で言及されたRFCはCSVの標準ではありません、それはMIMEの中でCSVを使うことのためのRFCであり、MIMEの外でそれを役に立たなくするいくつかの非伝統的で不必要な制限を含みます。
私が見た多くのCSVモジュールが対応していないのは、1行に複数行をエンコードできるという事実です。つまり、各行を別々のレコードと見なすことはできず、改行を許可しないでください。データを処理するか、これを処理する準備をしてください。
文字列を二重引用符で囲みます。それは一般的です Excelがすること 。
アラエリ、
二重引用符を2つの二重引用符としてエスケープします。例えば。 "test1"、 "foo"、 "bar"、 "test2"
フィールドを二重引用符で囲むことができます。これは別の特殊文字(二重引用符)を追加するため、この方法は好きではありません。エスケープ文字(通常はバックスラッシュ)を定義し、何かをエスケープする必要がある場合はそれを使用するだけです。
データ、より多くのデータ、より多くのデータ、さらにもっと
引用符を一致させようとする必要はなく、解析する例外も少なくなります。これにより、コードも単純化されます。
ほとんどすべての整形式CSV(.net)を扱うためのnugetを通して利用可能なライブラリがあります - CsvHelper
クラスにマップする例:
var csv = new CsvReader( textReader );
var records = csv.GetRecords<MyClass>();
個々のフィールドを読み取る例:
var csv = new CsvReader( textReader );
while( csv.Read() )
{
var intField = csv.GetField<int>( 0 );
var stringField = csv.GetField<string>( 1 );
var boolField = csv.GetField<bool>( "HeaderName" );
}
クライアントにファイルフォーマットを駆動させる: ,
は標準のフィールド区切り文字、"
は区切り文字、引用符、または行末を含むフィールドをエスケープするために使用される標準値です。
(例えば)フィールドに#
を使い、エスケープに'
を使うには:
var csv = new CsvReader( textReader );
csv.Configuration.Delimiter = "#";
csv.Configuration.Quote = ''';
// read the file however meets your needs
* nix-systemを使用している場合は、sed
にアクセスできます。 CSVの特定のフィールドにのみ不要なコンマがあります。次のワンライナーを使用して、それらを"
で RFC4180セクションとして囲むことができます。 2 提案:
sed -r 's/([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*)/\1"\2"\3/' inputfile
不要なコンマが含まれているフィールドに応じて、正規表現のキャプチャグループ(および置換)を変更/拡張する必要があります。
上記の例では、4つ目のフィールド(6つのうち)を引用符で囲みます。
--in-place
- option と組み合わせて、これらの変更をファイルに直接適用できます。
適切な正規表現を「構築」するために、従うべき簡単な原則があります。
[^,]*,
を記述し、それらをすべてキャプチャグループにまとめます。(.*)
と記述します。,.*
を記述し、キャプチャグループにまとめます。特定のフィールドに応じて考えられるさまざまな正規表現/置換の概要を以下に示します。指定しない場合、置換は\1"\2"\3
です。
([^,]*)(,.*) #first field, regex
"\1"\2 #first field, substitution
(.*,)([^,]*) #last field, regex
\1"\2" #last field, substitution
([^,]*,)(.*)(,.*,.*,.*) #second field (out of five fields)
([^,]*,[^,]*,)(.*)(,.*) #third field (out of four fields)
([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*) #fourth field (out of six fields)
不要なコンマを引用符で囲む代わりにsed
で削除する場合は、 this answer を参照してください。
Harpoの答えに対する私のコメントで述べたように、彼の解決策は良く、ほとんどの場合うまくいくが、いくつかのシナリオではコンマが直接隣接しているとコンマに分割できない場合がある。
これはRegex文字列が予期せずにVertabim文字列として振る舞うためです。これを正しく動作させるには、正規表現文字列内のすべての ""文字をVertabimエスケープを使わずに手動でエスケープする必要があります。
すなわち正規表現は手動のエスケープを使ってこれにするべきです:
",(?=(?:[^\"\"]*\"\"[^\"\"]*\"\")*(?![^\"\"]*\"\"))"
これは",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"
に変換されます
Vertabim文字列@",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"
を使うとき、それはあなたが正規表現をデバッグするかどうか見ることができるように以下のように振る舞います:
",(?=(?:[^"]*"[^"]*")*(?![^"]*"))"
要約すると、私はharpoの解決策をお勧めしますが、このちょっとした問題に気をつけてください!
このエラーが発生した場合に通知するためのちょっとしたオプションのフェイルセーフをCsvReaderに含めました(あなたが既知の数の列を持っている場合)。
if (_expectedDataLength > 0 && values.Length != _expectedDataLength)
throw new DataLengthException(string.Format("Expected {0} columns when splitting csv, got {1}", _expectedDataLength, values.Length));
これはコンストラクタを介して挿入できます。
public CsvReader(string fileName, int expectedDataLength = 0) : this(new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
_expectedDataLength = expectedDataLength;
}
Microsoft.VisualBasicへの参照を追加します。
CSVファイルを解析するにはMicrosoft.VisualBasic.FileIO.TextFieldParser
クラスを使用します。サンプルコードは次のとおりです。
Dim parser As TextFieldParser = New TextFieldParser("C:\mar0112.csv")
parser.TextFieldType = FieldType.Delimited
parser.SetDelimiters(",")
While Not parser.EndOfData
'Processing row
Dim fields() As String = parser.ReadFields
For Each field As String In fields
'TODO: Process field
Next
parser.Close()
End While
「;」のような代替の「区切り文字」を使用できます。または "|"しかし最も簡単なのは、ほとんどの(まともな)CSVライブラリとほとんどのまともなスプレッドシートでサポートされている引用です。
CSVデリミタの詳細、およびデリミタの記述と引用のための標準フォーマットの仕様については、このWebページを参照してください
ヨーロッパでは、この問題よりも早くこの問題を抱えています。ヨーロッパでは、小数点にコンマをすべて使用しています。下記の番号を参照してください。
| American | Europe |
| ------------- | ------------- |
| 0.5 | 0,5 |
| 3.14159265359 | 3,14159265359 |
| 17.54 | 17,54 |
| 175,186.15 | 175.186,15 |
そのため、CSVファイルにカンマ区切りを使用することはできません。そのため、ヨーロッパのCSVファイルはセミコロン (;
) で区切られています。
Microsoft Excelのようなプログラムはセミコロンでファイルを読むことができ、それはセパレータから切り替えることが可能です。区切り文字としてタブ(\t
)を使うことさえできます。 Supper Userからのこの回答 を参照してください。
車輪の再発明を望んでいるのなら、次のようにしてください。
public static IEnumerable<string> SplitCSV(string line)
{
var s = new StringBuilder();
bool escaped = false, inQuotes = false;
foreach (char c in line)
{
if (c == ',' && !inQuotes)
{
yield return s.ToString();
s.Clear();
}
else if (c == '\\' && !escaped)
{
escaped = true;
}
else if (c == '"' && !escaped)
{
inQuotes = !inQuotes;
}
else
{
escaped = false;
s.Append(c);
}
}
yield return s.ToString();
}
ファイルを一般的に解析する方法(CSVを例として)についてのもっと教育的な演習に興味があるなら、 この記事 Julian Bucknallによる/をチェックしてください。私はこの記事が気に入っています。それは、物事をはるかに小さな問題に細分化し、それを克服することがはるかに難しいからです。最初に文法を作成します。そして、優れた文法を習得したら、その文法をコードに変換するのは比較的簡単で系統的なプロセスです。
この記事ではC#を使用しており、下部にコードをダウンロードするためのリンクがあります。
私は一般的に任意のコンマまたは任意の特殊文字を含むことができるフィールドをURLエンコードします。そして、それが使用されている/表示されているときにそれをデコードする。
(カンマは%2Cになります)
どの言語にも、文字列をURLエンコードおよびデコードするためのメソッドが必要です。
例えば、Javaでは
URLEncoder.encode(myString,"UTF-8"); //to encode
URLDecoder.decode(myEncodedstring, "UTF-8"); //to decode
私はこれが非常に一般的な解決策であることを知っています、そしてそれはユーザーが手動で、csvファイルの内容を見たいと思う状況にとって理想的でないかもしれません。
これは一般的な慣習ですので、経験則から始めましょう。
CSVを使用せず、代わりにXMLをライブラリと共に使用してxmlファイルを読み書きしてください。
あなたがCSVを使わなければならないならば。それを正しく行い、CSVファイルを解析して保存するために無料のライブラリを使用してください。
1)を正当化するために、あなたがUS-ASCIIを扱っていないのであれば、ほとんどのCSVパーサはエンコーディングを意識していないので、トラブルを求めています。たとえば、Excel 2002はCSVをローカルエンコーディングで保存していますが、エンコーディングについては特に言及していません。 CSV標準は広く採用されていません:(一方、xml標準はよく採用されており、エンコーディングをかなりうまく処理します。
2)を正当化するために、解決策がかなり単純に見えても車輪を再発明する必要がないように、ほとんどすべての言語の周りにたくさんのcsvパーサがあります。
いくつか挙げると:
pythonの場合は csv moduleにビルドしてください。
perlではCPANをチェックし、 Text :: CSV
php用にfgetcsv/fputcsv関数でビルドする
javaチェック用 SuperCVS library
あなたが組み込みデバイスでそれをパースするつもりでないなら、本当に手動でこれを実装する必要はありません。
私が見つけた最も簡単な解決策はLibreOfficeが使うものです:
"
を”
で置き換えるExcelが使用しているものも使用できます。
"
を""
で置き換える他の人々は上記のステップ2のみを行うことを推奨していますが、CSVのように"
という文字列を1列にしたいCSVのように、,
の後にhello",world
が続く行では機能しません:
"hello",world"
これは2つの列を持つ行として解釈されます:hello
とworld"
public static IEnumerable<string> LineSplitter(this string line, char
separator, char skip = '"')
{
var fieldStart = 0;
for (var i = 0; i < line.Length; i++)
{
if (line[i] == separator)
{
yield return line.Substring(fieldStart, i - fieldStart);
fieldStart = i + 1;
}
else if (i == line.Length - 1)
{
yield return line.Substring(fieldStart, i - fieldStart + 1);
fieldStart = i + 1;
}
if (line[i] == '"')
for (i++; i < line.Length && line[i] != skip; i++) { }
}
if (line[line.Length - 1] == separator)
{
yield return string.Empty;
}
}
あなたはこのようにcsvファイルを読むことができます。
これは分割を利用し、スペースを処理します。
ArrayList List = new ArrayList();
static ServerSocket Server;
static Socket socket;
static ArrayList<Object> list = new ArrayList<Object>();
public static void ReadFromXcel() throws FileNotFoundException
{
File f = new File("Book.csv");
Scanner in = new Scanner(f);
int count =0;
String[] date;
String[] name;
String[] Temp = new String[10];
String[] Temp2 = new String[10];
String[] numbers;
ArrayList<String[]> List = new ArrayList<String[]>();
HashMap m = new HashMap();
in.nextLine();
date = in.nextLine().split(",");
name = in.nextLine().split(",");
numbers = in.nextLine().split(",");
while(in.hasNext())
{
String[] one = in.nextLine().split(",");
List.add(one);
}
int xount = 0;
//Making sure the lines don't start with a blank
for(int y = 0; y<= date.length-1; y++)
{
if(!date[y].equals(""))
{
Temp[xount] = date[y];
Temp2[xount] = name[y];
xount++;
}
}
date = Temp;
name =Temp2;
int counter = 0;
while(counter < List.size())
{
String[] list = List.get(counter);
String sNo = list[0];
String Surname = list[1];
String Name = list[2];
for(int x = 3; x < list.length; x++)
{
m.put(numbers[x], list[x]);
}
Object newOne = new newOne(sNo, Name, Surname, m, false);
StudentList.add(s);
System.out.println(s.sNo);
counter++;
}
PapaParseライブラリを使用してCSVファイルを解析し、キーと値のペア(キー/ヘッダー/ CSVファイル値の最初の行)を取得しました。
これが私が使う例です:
https://codesandbox.io/embed/llqmrp96pm
cSV解析のデモがあるため、ダミーの.csvファイルがあります。
それは任意の言語で書かれたアプリで複製するのは簡単で簡単ですが、私はreactJS内でそれを使用しました。
Csvreaderライブラリを使用しましたが、それを使用して列値のカンマ(、)から展開してデータを取得しました。
そのため、ほとんどの列値にカンマ(、)を含むCSVファイルデータを挿入する場合は、以下の関数を使用できます。作者リンク=> https://Gist.github.com/jaywilliams/385876
function csv_to_array($filename='', $delimiter=',')
{
if(!file_exists($filename) || !is_readable($filename))
return FALSE;
$header = NULL;
$data = array();
if (($handle = fopen($filename, 'r')) !== FALSE)
{
while (($row = fgetcsv($handle, 1000, $delimiter)) !== FALSE)
{
if(!$header)
$header = $row;
else
$data[] = array_combine($header, $row);
}
fclose($handle);
}
return $data;
}
まず、「CSVファイルに対してカンマの扱い方を変える必要があるのはなぜだと思いますか」と自問しましょう。
私にとっては、答えは、「データをCSVファイルにエクスポートすると、フィールド内のコンマが消え、元のデータにコンマが表示される複数のフィールドに分割されるためです。」 (それはカンマがCSVフィールド区切り文字であるためです。)
状況に応じて、セミコロンをCSVフィールドの区切り文字として使用することもできます。
私の要件を考慮すると、カンマのように見える文字、例えば単一の低9引用符を使用することができます。
だから、これはあなたがGoでそれを行うことができる方法です:
// Replace special CSV characters with single low-9 quotation mark
func Scrub(a interface{}) string {
s := fmt.Sprint(a)
s = strings.Replace(s, ",", "‚", -1)
s = strings.Replace(s, ";", "‚", -1)
return s
}
Replace関数の2番目のコンマ検索文字は10進数8218です。
ASCIIのみのテキストリーダーを使用している可能性があるクライアントがある場合は、このdecima 8218文字はコンマのようには見えません。このような場合は、RFC 4128に従ってフィールドを二重引用符で囲んでカンマ(またはセミコロン)で囲むことをお勧めします。 https://tools.ietf.org/html/rfc4180
この問題に対する最も簡単な解決策は、顧客にExcelでcsvを開き、次にctrl + rを押してすべてのコンマを任意の識別子に置き換えることです。これは顧客にとって非常に簡単であり、あなたが選んだ区切り文字を読むためにあなたのコードに一つの変更だけを必要とします。
私は通常、CSVファイル解析ルーチンでこれを行います。 'line'変数がCSVファイル内の1行で、すべての列の値が二重引用符で囲まれているとします。以下の2行が実行されると、 'values'コレクションにCSV列が表示されます。
// The below two lines will split the columns as well as trim the DBOULE QUOTES around values but NOT within them
string trimmedLine = line.Trim(new char[] { '\"' });
List<string> values = trimmedLine.Split(new string[] { "\",\"" }, StringSplitOptions.None).ToList();