作成され、お客様によってアップロードされた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。
これは非常に一般的な仕様であり、多くのライブラリ(例)で完全にカバーされています。
簡単に入手できるcsvライブラリを使用するだけです。つまり、RFC 4180です。
実際にはCSV形式の仕様とコンマの処理方法があります。
改行(CRLF)、二重引用符、およびコンマを含むフィールドは、二重引用符で囲む必要があります。
http://tools.ietf.org/html/rfc4180
したがって、値foo
とbar,baz
を使用するには、次のようにします。
foo,"bar,baz"
考慮すべきもう1つの重要な要件(これも仕様から):
フィールドを囲むために二重引用符を使用する場合、フィールド内に表示される二重引用符は、その前に別の二重引用符を付けることでエスケープする必要があります。例えば:
"aaa","b""bb","ccc"
System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator
。
CSV形式では、値を区切るためにコンマを使用します。キャリッジリターン、ラインフィード、コンマ、または二重引用符を含む値は、二重引用符で囲まれます。二重引用符を含む値は引用符で囲まれ、各リテラル引用符は直前の引用符でエスケープされます。たとえば、3つの値:
test
list, of, items
"go" he said
次のようにエンコードされます:
test
"list, of, items"
"""go"" he said"
任意のフィールドを引用できますが、引用符で囲む必要があるのは、コンマ、CR / NL、または引用符を含むフィールドのみです。
CSV形式には実際の標準はありませんが、ほとんどすべてのアプリケーションはここに記載されている規則に従います。他の場所で言及されたRFCはCSVの標準ではありません。これはMIME内でCSVを使用するためのRFCであり、MIMEの外では役に立たない、従来とは異なる不要な制限が含まれています。
私が見た多くのCSVモジュールが対応していないことの問題は、複数の行が単一のフィールドにエンコードされる可能性があるという事実です。つまり、各行が個別のレコードであると想定できないため、改行を許可しない必要があります。データまたはこれを処理する準備をしてください。
フィールドを二重引用符で囲むことができます。別の特殊文字(二重引用符)が追加されるため、このアプローチは好きではありません。エスケープ文字(通常はバックスラッシュ)を定義し、何かをエスケープする必要がある場合はどこでも使用できます。
データ、より多くのデータ、より多くのデータ\、さらに、さらに
引用符を一致させる必要はなく、解析する例外も少なくなります。これにより、コードも簡素化されます。
ほぼすべての整形式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
CsvHelper
ライブラリを使用してOPの問題を解決する方法の例を含めた方がよいでしょう。
ハーポの答えに対する私のコメントで述べたように、彼の解決策は優れていてほとんどの場合に機能しますが、いくつかのシナリオでは、カンマが互いに直接隣接しているため、カンマで分割できません。
これは、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への参照を追加します(はい、VisualBasicと表示されていますが、C#でも同様に機能します。最後に、すべてがILであることを思い出してください)。
Microsoft.VisualBasic.FileIO.TextFieldParser
クラスを使用してCSVファイルを解析する以下はサンプルコードです。
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
parser.HasFieldsEnclosedInQuotes = true;
する必要があります。また、CSV仕様に従って、入力ファイルは引用符でコンマを含むフィールドを囲む必要があります。Excelはこれをすでに行っています。
「;」のような代替の「区切り文字」を使用できます。または「|」しかし、最も単純なのは、ほとんどの(まともな)CSVライブラリと最もまともなスプレッドシートでサポートされている単なる引用です。
CSV区切り文字の詳細と、区切り文字を記述して引用するための標準形式の仕様については、このWebページを参照してください。
場合は、あなたがにしているの* nixシステムへのアクセスを持っているsed
と1つの以上存在することができだけで、不要なカンマ特定のフィールドあなたの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
を引用符で囲むのではなく削除したい場合は、この回答を参照してください。
車輪を再発明したい場合は、次の方法が効果的です。
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();
}
ヨーロッパでは、この問題はこの質問よりも早くする必要があります。ヨーロッパでは、小数点にはすべてカンマを使用します。以下のこの番号を参照してください。
| 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からのこの回答を参照してください。
ここにきちんとした小さな回避策があります:
代わりにギリシャ語の小文字の数字記号を使用できます(U + 0375)
こんな感じ͵
この方法を使用すると、多くのリソースも節約できます...
NuGetでSoftCircuits.CsvParserを使用するだけです。これらすべての詳細を処理し、非常に大きなファイルを効率的に処理します。また、必要に応じて、列をオブジェクトのプロパティにマッピングすることで、オブジェクトをインポート/エクスポートすることもできます。さらに、私のテストでは、一般的なCsvHelperの平均より約4倍高速であることを示しました。
これは一般的な慣行に関するものなので、経験則から始めましょう。
CSVを使用せず、ライブラリでXMLを使用して、代わりにxmlファイルを読み書きします。
CSVを使用する必要がある場合。適切に実行し、無料のライブラリを使用してCSVファイルを解析および保存します。
1)を正当化するために、ほとんどのCSVパーサーはエンコードに対応していないため、US-ASCIIを扱っていない場合は問題が発生します。たとえば、Excel 2002ではCSVをローカルエンコーディングで保存していますが、エンコーディングに関する注意はありません。CSV標準は広く採用されていません:(。一方、xml標準はよく採用されており、エンコーディングをかなりうまく処理します。
2)を正当化するために、ほとんどすべての言語に対して多数のcsvパーサーが存在するため、ソリューションが非常に単純に見えても、ホイールを再発明する必要はありません。
いくつか例を挙げると:
Pythonではcsvモジュールでビルドを使用します
perlの場合はCPANとText :: CSVを確認してください
PHPの場合、fgetcsv / fputcsv関数でビルドを使用します
JavaのチェックSuperCVSライブラリ
組み込みデバイスで解析しない場合は、手動で実装する必要はありません。
あなたはこのように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++;
}
まず、「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
私は通常、コンマや特殊文字を含めることができるフィールドをURLエンコードします。そして、それが任意のビジュアルメディアで使用/表示されているときにデコードします。
(カンマは%2Cになります)
すべての言語には、文字列をURLエンコードおよびデコードするメソッドが必要です。
例:Java
URLEncoder.encode(myString,"UTF-8"); //to encode
URLDecoder.decode(myEncodedstring, "UTF-8"); //to decode
これは非常に一般的な解決策であり、ユーザーがcsvファイルのコンテンツを手動で表示したい状況には理想的ではないかもしれません。
私は通常、これをCSVファイル解析ルーチンで行います。「line」変数がCSVファイル内の1行であり、すべての列の値が二重引用符で囲まれていると想定します。以下の2行が実行されると、「値」コレクションに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();
私が見つけた最も簡単なソリューションは、LibreOfficeが使用するものです。
"
を”
Excelが使用するものを使用することもできます。
"
を""
上記のステップ2のみを実行することを推奨している他の人に注意してください。ただし、CSVのように、文字列が1つの列にしたいCSVのように、"
aの後にが続く行では機能しません。,
hello",world
"hello",world"
二つの列と行として解釈される:hello
及びworld"
hello",world
フィールドには、単にとして保存する必要が"hello"",world"
100%正確に解析することができ、。
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;
}
}
私は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;
}
papaParseライブラリーを使用してCSVファイルを解析し、キーと値のペア(キー/ヘッダー/ CSVファイルと値の最初の行)を用意しました。
ここに私が使用する例があります:
https://codesandbox.io/embed/llqmrp96pm
CSV解析デモを行うために、dummy.csvファイルが含まれています。
任意の言語で書かれたアプリで複製するのは簡単でシンプルですが、reactJS内で使用しました。
例は、カンマが.csvファイルでどのように表示されるかを示すのに役立ちます。次のように単純なテキストファイルを作成します。
このテキストファイルを拡張子 ".csv"のテキストファイルとして保存し、Windows 10のExcel 2000で開きます。
aa、bb、cc、d; d「スプレッドシートプレゼンテーションでは、下の行は上の行のように見えるはずですが、下の行はセミコロンの代わりにカンマの代わりに表示されます。」aa、bb、cc、 "d、d"、これはExcelでも機能します
aa、bb、cc、 "d、d"、これはExcel 2000でも機能しますaa、bb、cc、 "d、d"、これはExcel 2000でも機能しますaa、bb、cc、 "d、d"、これは機能しますExcel 2000でも
aa、bb、cc、 "d、d"、最初の引用の前のスペースが原因でExcel 2000で失敗するaa、bb、cc、 "d、d"、最初の引用の前のスペースが原因でExcel 2000で失敗するaa、bb、cc、 "d、d"、最初の引用符の下のスペースが原因でExcel 2000でこれが失敗する
aa、bb、cc、 "d、d"、これは、2番目の引用の前後にスペースがあっても、Excel 2000でも機能します。aa、bb、cc、 "d、d"、これは、2番目の引用の前後にスペースがあっても、Excel 2000でも機能します。aa、bb、cc、 "d、d"、これは、2番目の引用符の前後にスペースがあっても、Excel 2000でも機能します。
ルール:.csvファイルのセル(フィールド)にコンマを表示する場合:「フィールドの開始と終了には二重引用符を使用しますが、最初の引用符の前の空白は避けます」
この問題の最も簡単な解決策は、お客様にcsvをExcelで開いてから、Ctrl + Rを押して、すべてのカンマを任意の識別子に置き換えることです。これは顧客にとって非常に簡単であり、選択した区切り文字を読み取るためにコードを1回変更するだけで済みます。
フィールドを区切るには、タブ文字(\ t)を使用します。