Всегда такая же реализация, но настроенная для Swift 4
import UIKit
@IBDesignable
class CircularLabel: UILabel {
// *******************************************************
// DEFINITIONS (Because I'm not brilliant and I'll forget most this tomorrow.)
// Radius: A straight line from the center to the circumference of a circle.
// Circumference: The distance around the edge (outer line) the circle.
// Arc: A part of the circumference of a circle. Like a length or section of the circumference.
// Theta: A label or name that represents an angle.
// Subtend: A letter has a width. If you put the letter on the circumference, the letter's width
// gives you an arc. So now that you have an arc (a length on the circumference) you can
// use that to get an angle. You get an angle when you draw a line from the center of the
// circle to each end point of your arc. So "subtend" means to get an angle from an arc.
// Chord: A line segment connecting two points on a curve. If you have an arc then there is a
// start point and an end point. If you draw a straight line from start point to end point
// then you have a "chord".
// sin: (Super simple/incomplete definition) Or "sine" takes an angle in degrees and gives you a number.
// asin: Or "asine" takes a number and gives you an angle in degrees. Opposite of sine.
// More complete definition: http://www.mathsisfun.com/sine-cosine-tangent.html
// cosine: Also takes an angle in degrees and gives you another number from using the two radiuses (radii).
// *******************************************************
@IBInspectable var angle: CGFloat = 1.6
@IBInspectable var clockwise: Bool = true
override func draw(_ rect: CGRect) {
centreArcPerpendicular()
}
/**
This draws the self.text around an arc of radius r,
with the text centred at polar angle theta
*/
func centreArcPerpendicular() {
guard let context = UIGraphicsGetCurrentContext() else { return }
let string = text ?? ""
let size = bounds.size
context.translateBy(x: size.width / 2, y: size.height / 2)
let radius = getRadiusForLabel()
let l = string.count
let attributes = [NSAttributedStringKey.font : self.font!]
let characters: [String] = string.map { String($0) } // An array of single character strings, each character in str
var arcs: [CGFloat] = [] // This will be the arcs subtended by each character
var totalArc: CGFloat = 0 // ... and the total arc subtended by the string
// Calculate the arc subtended by each letter and their total
for i in 0 ..< l {
arcs += [chordToArc(characters[i].size(withAttributes: attributes).width, radius: radius)]
totalArc += arcs[i]
}
// Are we writing clockwise (right way up at 12 o'clock, upside down at 6 o'clock)
// or anti-clockwise (right way up at 6 o'clock)?
let direction: CGFloat = clockwise ? -1 : 1
let slantCorrection = clockwise ? -CGFloat.pi/2 : CGFloat.pi/2
// The centre of the first character will then be at
// thetaI = theta - totalArc / 2 + arcs[0] / 2
// But we add the last term inside the loop
var thetaI = angle - direction * totalArc / 2
for i in 0 ..< l {
thetaI += direction * arcs[i] / 2
// Call centre with each character in turn.
// Remember to add +/-90º to the slantAngle otherwise
// the characters will "stack" round the arc rather than "text flow"
centre(text: characters[i], context: context, radius: radius, angle: thetaI, slantAngle: thetaI + slantCorrection)
// The centre of the next character will then be at
// thetaI = thetaI + arcs[i] / 2 + arcs[i + 1] / 2
// but again we leave the last term to the start of the next loop...
thetaI += direction * arcs[i] / 2
}
}
func chordToArc(_ chord: CGFloat, radius: CGFloat) -> CGFloat {
// *******************************************************
// Simple geometry
// *******************************************************
return 2 * asin(chord / (2 * radius))
}
/**
This draws the String str centred at the position
specified by the polar coordinates (r, theta)
i.e. the x= r * cos(theta) y= r * sin(theta)
and rotated by the angle slantAngle
*/
func centre(text str: String, context: CGContext, radius r:CGFloat, angle theta: CGFloat, slantAngle: CGFloat) {
// Set the text attributes
let attributes : [NSAttributedStringKey : Any] = [
NSAttributedStringKey.foregroundColor: textColor!,
NSAttributedStringKey.font: font!
]
// Save the context
context.saveGState()
// Move the origin to the centre of the text (negating the y-axis manually)
context.translateBy(x: r * cos(theta), y: -(r * sin(theta)))
// Rotate the coordinate system
context.rotate(by: -slantAngle)
// Calculate the width of the text
let offset = str.size(withAttributes: attributes)
// Move the origin by half the size of the text
context.translateBy(x: -offset.width / 2, y: -offset.height / 2) // Move the origin to the centre of the text (negating the y-axis manually)
// Draw the text
str.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
// Restore the context
context.restoreGState()
}
func getRadiusForLabel() -> CGFloat {
// Imagine the bounds of this label will have a circle inside it.
// The circle will be as big as the smallest width or height of this label.
// But we need to fit the size of the font on the circle so make the circle a little
// smaller so the text does not get drawn outside the bounds of the circle.
let smallestWidthOrHeight = min(bounds.size.height, bounds.size.width)
let heightOfFont = text?.size(withAttributes: [NSAttributedStringKey.font: self.font]).height ?? 0
// Dividing the smallestWidthOrHeight by 2 gives us the radius for the circle.
return (smallestWidthOrHeight/2) - heightOfFont + 5
}
}
2014-12-01 Обновление: нижеприведенный ответ работает только для одного особого формата CSV. Как правильно указал DG в комментариях, это решение НЕ соответствует определению CSV RFC 4180, а также не соответствует формату MS Excel. Это решение просто демонстрирует, как можно разобрать одну (нестандартную) линию ввода CSV, содержащую сочетание типов строк, где строки могут содержать экранированные кавычки и запятые.
Как правильно указывает austincheney, вам действительно нужно разобрать строку от начала до конца, если вы хотите правильно обрабатывать строки с кавычками, которые могут содержать экранированные символы. Кроме того, ОП четко не определяет, что такое «строка CSV». Сначала мы должны определить, что представляет собой действительную строку CSV и ее отдельные значения.
Для целей этого обсуждения строка «CSV» состоит из ноль или более значений, где несколько значений разделяются запятой. Каждое значение может состоять из:
Правила / Примечания:
'that\'s cool'
. \'
в одинарных кавычках. \"
в двойных кавычках. Функция JavaScript, которая преобразует допустимая строка CSV (как определено выше) в массив строковых значений.
Регулярные выражения, используемые этим решением, являются сложными. И (IMHO) все нетривиальные регулярные выражения должны быть представлены в режиме свободного пробела с большим количеством комментариев и отступов. К сожалению, JavaScript не разрешает режим свободного пробела. Таким образом, регулярные выражения, реализованные этим решением, сначала представлены в собственном синтаксисе regex (выражаемом с помощью удобного Python: r'''...'''
синтаксиса raw-multi-line-string).
Сначала это регулярное выражение, которое подтверждает, что строка CVS удовлетворяет вышеуказанным требованиям:
re_valid = r"""
# Validate a CSV string having single, double or un-quoted values.
^ # Anchor to start of string.
\s* # Allow whitespace before value.
(?: # Group for value alternatives.
'[^'\\]*(?:\\[\S\s][^'\\]*)*' # Either Single quoted string,
| "[^"\\]*(?:\\[\S\s][^"\\]*)*" # or Double quoted string,
| [^,'"\s\\]*(?:\s+[^,'"\s\\]+)* # or Non-comma, non-quote stuff.
) # End group of value alternatives.
\s* # Allow whitespace after value.
(?: # Zero or more additional values
, # Values separated by a comma.
\s* # Allow whitespace before value.
(?: # Group for value alternatives.
'[^'\\]*(?:\\[\S\s][^'\\]*)*' # Either Single quoted string,
| "[^"\\]*(?:\\[\S\s][^"\\]*)*" # or Double quoted string,
| [^,'"\s\\]*(?:\s+[^,'"\s\\]+)* # or Non-comma, non-quote stuff.
) # End group of value alternatives.
\s* # Allow whitespace after value.
)* # Zero or more additional values
$ # Anchor to end of string.
"""
Если строка соответствует указанному выше выражению, то эта строка является допустимой строкой CSV (в соответствии с ранее описанными правилами) и может быть проанализирована с использованием следующего регулярного выражения. Следующее regex затем используется для сопоставления одного значения из строки CSV.
re_value = r"""
# Match one value in valid CSV string.
(?!\s*$) # Don't match empty last value.
\s* # Strip whitespace before value.
(?: # Group for value alternatives.
'([^'\\]*(?:\\[\S\s][^'\\]*)*)' # Either $1: Single quoted string,
| "([^"\\]*(?:\\[\S\s][^"\\]*)*)" # or $2: Double quoted string,
| ([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*) # or $3: Non-comma, non-quote stuff.
) # End group of value alternatives.
\s* # Strip whitespace after value.
(?:,|$) # Field ends on comma or EOS.
"""
Обратите внимание, что существует одно специальное значение, которое не соответствует этому регулярному выражению - последнее значение, когда это значение пусто. Этот специальный случай «пустое последнее значение» проверяется и обрабатывается следующей js-функцией.
// Return array of string values, or NULL if CSV string not well formed.
function CSVtoArray(text) {
var re_valid = /^\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*(?:,\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*)*$/;
var re_value = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;
// Return NULL if input string is not well formed CSV string.
if (!re_valid.test(text)) return null;
var a = []; // Initialize array to receive values.
text.replace(re_value, // "Walk" the string using replace with callback.
function(m0, m1, m2, m3) {
// Remove backslash from \' in single quoted values.
if (m1 !== undefined) a.push(m1.replace(/\\'/g, "'"));
// Remove backslash from \" in double quoted values.
else if (m2 !== undefined) a.push(m2.replace(/\\"/g, '"'));
else if (m3 !== undefined) a.push(m3);
return ''; // Return empty string.
});
// Handle special case of empty last value.
if (/,\s*$/.test(text)) a.push('');
return a;
};
В следующих примерах фигурные скобки используются для разграничения {result strings}
. (Это поможет визуализировать ведущие / конечные пробелы и строки нулевой длины.)
// Test 1: Test string from original question.
var test = "'string, duppi, du', 23, lala";
var a = CSVtoArray(test);
/* Array hes 3 elements:
a[0] = {string, duppi, du}
a[1] = {23}
a[2] = {lala} */
// Test 2: Empty CSV string.
var test = "";
var a = CSVtoArray(test);
/* Array hes 0 elements: */
// Test 3: CSV string with two empty values.
var test = ",";
var a = CSVtoArray(test);
/* Array hes 2 elements:
a[0] = {}
a[1] = {} */
// Test 4: Double quoted CSV string having single quoted values.
var test = "'one','two with escaped \' single quote', 'three, with, commas'";
var a = CSVtoArray(test);
/* Array hes 3 elements:
a[0] = {one}
a[1] = {two with escaped ' single quote}
a[2] = {three, with, commas} */
// Test 5: Single quoted CSV string having double quoted values.
var test = '"one","two with escaped \" double quote", "three, with, commas"';
var a = CSVtoArray(test);
/* Array hes 3 elements:
a[0] = {one}
a[1] = {two with escaped " double quote}
a[2] = {three, with, commas} */
// Test 6: CSV string with whitespace in and around empty and non-empty values.
var test = " one , 'two' , , ' four' ,, 'six ', ' seven ' , ";
var a = CSVtoArray(test);
/* Array hes 8 elements:
a[0] = {one}
a[1] = {two}
a[2] = {}
a[3] = { four}
a[4] = {}
a[5] = {six }
a[6] = { seven }
a[7] = {} */
Это решение требует, чтобы строка CSV была «действительной». Например, некотируемые значения могут не содержать обратных косых черт или кавычек, например. следующая строка CSV НЕ является допустимой:
var invalid1 = "one, that's me!, escaped \, comma"
Это не является действительно ограничением, потому что любая подстрока может быть представлена как одно или двойное кавычное значение. Обратите внимание также, что это решение представляет собой только одно возможное определение для: «Comma Separated Values».
Редактировать: 2014-05-19: Добавлен отказ от ответственности. Изменить: 2014-12-01: Перемещено заявление об отказе.
Мой ответ предполагает, что ваш ввод является отражением кода / контента из веб-источников, где символы одиночной и двойной кавычки полностью взаимозаменяемы, если они встречаются как неэкспериментированный набор соответствия.
Вы не можете использовать регулярное выражение для это. Фактически вам нужно написать микроанализатор для анализа строки, которую вы хотите разделить. Я, ради этого ответа, вызову цитированные части ваших строк в виде подстрок. Вам нужно специально пройти через строку. Рассмотрим следующий случай:
var a = "some sample string with \"double quotes\" and 'single quotes' and some craziness like this: \\\" or \\'",
b = "sample of code from JavaScript with a regex containing a comma /\,/ that should probably be ignored.";
В этом случае вы совершенно не знаете, где подстрока начинается или заканчивается простым анализом ввода для шаблона символа. Вместо этого вы должны написать логику, чтобы принимать решения о том, используется ли символ кавычки символом кавычки, сам по себе не кавычек и что символ кавычки не выполняет escape.
Я не буду писать этот уровень сложности кода для вас, но вы можете посмотреть на то, что я недавно написал, который имеет шаблон, который вам нужен. Этот код не имеет ничего общего с запятыми, но в остальном это достаточно правильный микропарсер, чтобы вы могли писать свой собственный код. Посмотрите на функцию asifix следующего приложения:
https://github.com/austincheney/Pretty-Diff/blob/master/fulljsmin.js
У меня был очень специфический вариант использования, когда я хотел скопировать ячейки из Google Таблиц в мое веб-приложение. Клетки могут включать двойные кавычки и символы новой строки. Используя копирование и вставку, ячейки разделяются символами табуляции, а ячейки с нечетными данными дублируются. Я пробовал это основное решение, связанную статью с помощью regexp, Jquery-CSV и CSVToArray. http://papaparse.com/ Является единственным, который работал из коробки. Копирование и вставка осуществляется с помощью Google Таблиц с параметрами автоматического обнаружения по умолчанию.
Согласно этой записи в блоге , эта функция должна это сделать:
String.prototype.splitCSV = function(sep) {
for (var foo = this.split(sep = sep || ","), x = foo.length - 1, tl; x >= 0; x--) {
if (foo[x].replace(/'\s+$/, "'").charAt(foo[x].length - 1) == "'") {
if ((tl = foo[x].replace(/^\s+'/, "'")).length > 1 && tl.charAt(0) == "'") {
foo[x] = foo[x].replace(/^\s*'|'\s*$/g, '').replace(/''/g, "'");
} else if (x) {
foo.splice(x - 1, 2, [foo[x - 1], foo[x]].join(sep));
} else foo = foo.shift().split(sep).concat(foo);
} else foo[x].replace(/''/g, "'");
} return foo;
};
Вы бы назвали ее так:
var string = "'string, duppi, du', 23, lala";
var parsed = string.splitCSV();
alert(parsed.join("|"));
Этот вид jsfiddle работает, но похоже, что некоторые элементы имеют перед ними пробелы.
"'string, duppi, du', 23, lala"
, эта функция возвращает: ["'string"," duppi"," du'"," 23"," lala"]
– ridgerunner
14 December 2011 в 01:09
"'"
на '"'
и наоборот.
– CanSpice
14 December 2011 в 01:16
'"string, duppi, du", 23, lala'
приводит к: ['"string',' duppi'.' du"',' 23',' lala']
– ridgerunner
14 December 2011 в 01:37
Чтобы дополнить этот ответ
Если вам нужно разобрать цитаты, экранированные другой цитатой, например:
"some ""value"" that is on xlsx file",123
Вы можете использовать
function parse(text) {
const csvExp = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|"([^""]*(?:"[\S\s][^""]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;
const values = [];
text.replace(csvExp, (m0, m1, m2, m3, m4) => {
if (m1 !== undefined) {
values.push(m1.replace(/\\'/g, "'"));
}
else if (m2 !== undefined) {
values.push(m2.replace(/\\"/g, '"'));
}
else if (m3 !== undefined) {
values.push(m3.replace(/""/g, '"'));
}
else if (m4 !== undefined) {
values.push(m4);
}
return '';
});
if (/,\s*$/.test(text)) {
values.push('');
}
return values;
}
"jjj "" kkk""","123"
– niry
10 January 2017 в 15:02
Люди, похоже, против RegEx для этого. Почему?
(\s*'[^']+'|\s*[^,]+)(?=,|$)
Вот код. Я также сделал скрипт .
String.prototype.splitCSV = function(sep) {
var regex = /(\s*'[^']+'|\s*[^,]+)(?=,|$)/g;
return matches = this.match(regex);
}
var string = "'string, duppi, du', 23, 'string, duppi, du', lala";
var parsed = string.splitCSV();
alert(parsed.join('|'));
Мне понравился ответ FakeRainBrigand, однако он содержит несколько проблем: он не может обрабатывать пробелы между цитатой и запятой и не поддерживает две последовательные запятые. Я попытался отредактировать его ответ, но мое редактирование было отклонено рецензентами, которые, по-видимому, не поняли мой код. Вот моя версия кода FakeRainBrigand. Существует также скрипка: http://jsfiddle.net/xTezm/46/
String.prototype.splitCSV = function() {
var matches = this.match(/(\s*"[^"]+"\s*|\s*[^,]+|,)(?=,|$)/g);
for (var n = 0; n < matches.length; ++n) {
matches[n] = matches[n].trim();
if (matches[n] == ',') matches[n] = '';
}
if (this[0] == ',') matches.unshift("");
return matches;
}
var string = ',"string, duppi, du" , 23 ,,, "string, duppi, du",dup,"", , lala';
var parsed = string.splitCSV();
alert(parsed.join('|'));
Это не решает строку в вопросе, так как ее формат не соответствует RFC 4180; допустимая кодировка - это двойная кавычка с двойной кавычкой. Решение ниже корректно работает с CSV-файлами d / l из электронных таблиц google.
Разбор одной строки был бы неправильным. Согласно RFC 4180 поля могут содержать CRLF, что приведет к тому, что любой считыватель строк разорвет файл CSV. Ниже приведена обновленная версия, которая анализирует строку CSV:
'use strict';
function csvToArray(text) {
let p = '', row = [''], ret = [row], i = 0, r = 0, s = !0, l;
for (l of text) {
if ('"' === l) {
if (s && l === p) row[i] += l;
s = !s;
} else if (',' === l && s) l = row[++i] = '';
else if ('\n' === l && s) {
if ('\r' === p) row[i] = row[i].slice(0, -1);
row = ret[++r] = [l = '']; i = 0;
} else row[i] += l;
p = l;
}
return ret;
};
let test = '"one","two with escaped """" double quotes""","three, with, commas",four with no quotes,"five with CRLF\r\n"\r\n"2nd line one","two with escaped """" double quotes""","three, with, commas",four with no quotes,"five with CRLF\r\n"';
console.log(csvToArray(test));
(однолинейное решение)
function CSVtoArray(text) {
let ret = [''], i = 0, p = '', s = true;
for (let l in text) {
l = text[l];
if ('"' === l) {
s = !s;
if ('"' === p) {
ret[i] += '"';
l = '-';
} else if ('' === p)
l = '-';
} else if (s && ',' === l)
l = ret[++i] = '';
else
ret[i] += l;
p = l;
}
return ret;
}
let test = '"one","two with escaped """" double quotes""","three, with, commas",four with no quotes,five for fun';
console.log(CSVtoArray(test));
И для удовольствия, вот как вы создаете CSV из массива:
function arrayToCSV(row) {
for (let i in row) {
row[i] = row[i].replace(/"/g, '""');
}
return '"' + row.join('","') + '"';
}
let row = [
"one",
"two with escaped \" double quote",
"three, with, commas",
"four with no quotes (now has)",
"five for fun"
];
let text = arrayToCSV(row);
console.log(text);
Помимо отличного и полного ответа от ridgerunner, я подумал о очень простом обходном пути, когда ваш backend запускает php.
Добавьте этот php-файл в бэкэнд вашего домена (скажем: csv.php
)
<?php
session_start(); //optional
header("content-type: text/xml");
header("charset=UTF-8");
//set the delimiter and the End of Line character of your csv content:
echo json_encode(array_map('str_getcsv',str_getcsv($_POST["csv"],"\n")));
?>
Теперь добавьте эту функцию в свой инструментарий javascript (нужно немного переделать, чтобы сделать кроссбраузер, на который я верю.)
function csvToArray(csv) {
var oXhr = new XMLHttpRequest;
oXhr.addEventListener("readystatechange",
function () {
if (this.readyState == 4 && this.status == 200) {
console.log(this.responseText);
console.log(JSON.parse(this.responseText));
}
}
);
oXhr.open("POST","path/to/csv.php",true);
oXhr.setRequestHeader("Content-type","application/x-www-form-urlencoded; charset=utf-8");
oXhr.send("csv=" + encodeURIComponent(csv));
}
Будет стоить вам 1 ajax-вызов, но не менее вы не будете дублировать код и не включать какую-либо внешнюю библиотеку.
При чтении csv в строку он содержит нулевое значение между строкой, поэтому попробуйте его \ 0 Строка за строкой работает.
stringLine = stringLine.replace( /\0/g, "" );
Я также сталкивался с тем же типом проблемы, когда мне приходится разбирать CSV-файл. Файл содержит адрес столбца, который содержит «,». После разбора этого CSV на JSON я получаю несогласованное отображение ключей при преобразовании его в файл JSON. Я использовал узел для разбора файла и библиотеки как baby parse и csvtojson Пример файла -
address,pincode
foo,baar , 123456
Пока я разбирался напрямую, не используя ребенка parse in JSON Я получал
[{
address: 'foo',
pincode: 'baar',
'field3': '123456'
}]
Итак, я написал код, который удаляет запятую (,) любым другим разделителем с каждым полем
/*
csvString(input) = "address, pincode\\nfoo, bar, 123456\\n"
output = "address, pincode\\nfoo {YOUR DELIMITER} bar, 123455\\n"
*/
const removeComma = function(csvString){
let delimiter = '|'
let Baby = require('babyparse')
let arrRow = Baby.parse(csvString).data;
/*
arrRow = [
[ 'address', 'pincode' ],
[ 'foo, bar', '123456']
]
*/
return arrRow.map((singleRow, index) => {
//the data will include
/*
singleRow = [ 'address', 'pincode' ]
*/
return singleRow.map(singleField => {
//for removing the comma in the feild
return singleField.split(',').join(delimiter)
})
}).reduce((acc, value, key) => {
acc = acc +(Array.isArray(value) ?
value.reduce((acc1, val)=> {
acc1 = acc1+ val + ','
return acc1
}, '') : '') + '\n';
return acc;
},'')
}
Возвращенная функция может быть передана в библиотеку csvtojson, и, следовательно, результат может быть использован.
const csv = require('csvtojson')
let csvString = "address, pincode\\nfoo, bar, 123456\\n"
let jsonArray = []
modifiedCsvString = removeComma(csvString)
csv()
.fromString(modifiedCsvString)
.on('json', json => jsonArray.push(json))
.on('end', () => {
/* do any thing with the json Array */
})
[{
address: 'foo, bar',
pincode: 123456
}]
грамматика PEG (.js), которая обрабатывает примеры RFC 4180 в http://en.wikipedia.org/wiki/Comma-separated_values :
start
= [\n\r]* first:line rest:([\n\r]+ data:line { return data; })* [\n\r]* { rest.unshift(first); return rest; }
line
= first:field rest:("," text:field { return text; })*
& { return !!first || rest.length; } // ignore blank lines
{ rest.unshift(first); return rest; }
field
= '"' text:char* '"' { return text.join(''); }
/ text:[^\n\r,]* { return text.join(''); }
char
= '"' '"' { return '"'; }
/ [^"]
Тест на http://jsfiddle.net/knvzk/10 или https://pegjs.org/online .
Загрузите сгенерированный парсер в ] https://gist.github.com/3362830 .
"field one", "field two", "a ""final"" field containing two double quote marks"
Я не тестировал ответ Тревора Диксона на этой странице, но это ответ, который посвящен определению CSV RFC 4180. – DG. 18 March 2014 в 08:11