问题很简单。
CSV文件如下所示:
1, "John", "John Joy"
如果我想获得每一栏,我只使用String[] splits = line.split(",");
如果CSV文件如下所示:
1, "John", "Joy, John"
所以我们在双引号对中有一个逗号。上述分裂将不再适用,因为我希望“欢乐,约翰”作为一个完整的部分。
那么有一个优雅/简单的算法来处理这种情况吗?
修改
请不要将其视为正式的CSV解析内容。我只是将CSV用作需要拆分的用例。
我真正想要的不是一个合适的CSV解析器,相反,我只想要一个算法,它可以用逗号来正确地分割一行,考虑到双引号。
答案 0 :(得分:4)
最好将现有的库用于此purpuse,而不是编写自定义实现(如果不这样做,则需要进行学习)。 因为CSV具有一些您可以在自定义实现中遗漏的细节,并且通常库已经过充分测试。
在这里你可以找到一些好的Can you recommend a Java library for reading (and possibly writing) CSV files?
修改强>
我已经创建了解析你的字符串的方法,但是它再次起作用并不完美,因为我还没有很好地测试它。 它可以作为您的起点,您可以进一步改进它。
String inputString = "1, \"John\",\"Joy, John\"";
char quote = '"';
List<String> csvList = new ArrayList<String>();
boolean inQuote = false;
int lastStart = 0;
for (int i = 0; i < inputString.length(); i++) {
if ((i + 1) == inputString.length()) {
//if this is the last character
csvList.add(inputString.substring(lastStart, i + 1));
}
if (inputString.charAt(i) == quote) {
//if the character is quote
if (inQuote) {
inQuote = false;
continue; //escape
}
inQuote = true;
continue;
}
if (inputString.charAt(i) == ',') {
if (inQuote) continue;
csvList.add(inputString.substring(lastStart, i));
lastStart = i + 1;
}
}
System.out.println(csvList);
问题
如果你得到类似1, "John", ""Joy, John""
的字符串怎么办?
(关于“乔伊,约翰”的两个引语)?
答案 1 :(得分:1)
// use regxep with matcher
String string1 = "\"John\", \"John Joy\"";
String string2 = "\"John\", \"Joy, John\"";
Pattern pattern = Pattern.compile("\"[^\"]+\"");
Matcher matcher = pattern.matcher(string1);
System.out.println("string1: " + string1);
int start = 0;
while(matcher.find(start)){
System.out.println(matcher.group());
start = matcher.end() + 1;
if(start > string1.length())
break;
}
matcher = pattern.matcher(string2);
System.out.println("string2: " + string2);
start = 0;
while(matcher.find(start)){
System.out.println(matcher.group());
start = matcher.end() + 1;
if(start > string2.length())
break;
}
答案 2 :(得分:0)
使用正则表达式非常优雅
对不起,我不熟悉Java正则表达式,所以我的例子是Lua:
(这个例子没有考虑到引用文本中可能有换行符,并且原始引用字符在引用文本中会加倍)
--- file.csv
1, "John", "John Joy"
2, "John", "Joy, John"
--- Lua code
for line in io.lines 'file.csv' do
print '==='
for _, s in (line..','):gmatch '%s*("?)(.-)%1%s*,' do
print(s)
end
end
--- Output
===
1
John
John Joy
===
2
John
Joy, John
答案 3 :(得分:0)
您可以从正则表达式开始:
[^",]*|"[^"]*"
匹配不包含逗号或带引号的字符串的非引用字符串。但是,有很多问题,包括:
输入中的逗号之后是否真的有空格?或者,更一般地说,您是否允许不完全位于字段第一个字符的引号?
如何在包含引号的字段周围加上引号?
根据您回答该问题的方式,您最终可能会使用不同的正则表达式。 (实际上,使用CSV解析库的习惯性建议并不是处理极端情况;它不需要考虑它们,因为您假设“标准CSV”处理,无论根据作者的说法。解析库.CSV很乱。)
我已经成功使用的一个正则表达式(尽管不 CSV兼容)是:
(?:[^",]|"[^"]*")*
与第一个非常相似,只是它允许任意数量的连接字段,因此以下两个都被识别为单个字段:
"John"", Mary"
John", "Mary
CSV标准会将第一个视为代表:
John", Mary -- internal quote
并将第二个中的引号视为普通字符,从而产生两个字段。所以YMMV。
无论如何,一旦你决定了一个合适的正则表达式,算法就很简单了。在伪代码中,因为我远非Java专家。
repeat:
match the regex at the current position
and append the result to the result;
if the match fails:
report error
if the match goes to the end of the string:
done
if the next character is a ',':
advance the position by one
otherwise:
report error
根据正则表达式,可能无法报告错误的两个条件。通常,如果引用的字段未终止,则第一个将触发(并且您需要决定是否允许引用字段中的换行 - CSV确实如此)。如果您使用我提供的第一个正则表达式,然后没有立即使用逗号跟随带引号的字符串,则可能会发生第二个。
答案 4 :(得分:-1)
首先将字符串拆分为引号。奇数段会引用内容;即使是那些也必须再用逗号分开。我在日志上使用它,其中引用的文本没有转义引号,就像在这个问题中一样。
boolean quoted = false;
for(String q : str.split("\"")) {
if(quoted)
System.out.println(q.trim());
else
for(String s : q.split(","))
if(!s.trim().isEmpty())
System.out.println(s.trim());
quoted = !quoted;
}