다음과 같이 모호한 문자열이 있습니다.
foo,bar,c;qual="baz,blurb",d;junk="quux,syzygy"
쉼표로 나누고 싶지만 따옴표로 쉼표를 무시해야합니다. 어떻게해야합니까? 정규식 접근 방식이 실패한 것 같습니다. 따옴표를 볼 때 수동으로 스캔하고 다른 모드로 들어갈 수 있다고 가정하지만 기존 라이브러리를 사용하는 것이 좋습니다. ( 편집 : 이미 JDK의 일부이거나 Apache Commons와 같이 일반적으로 사용되는 라이브러리의 일부인 라이브러리를 의미한다고 생각합니다.)
위의 문자열은 다음과 같이 나뉩니다.
foo
bar
c;qual="baz,blurb"
d;junk="quux,syzygy"
참고 : 이것은 CSV 파일이 아니며 전체 구조가 더 큰 파일에 포함 된 단일 문자열입니다
답변
시험:
public class Main {
public static void main(String[] args) {
String line = "foo,bar,c;qual=\"baz,blurb\",d;junk=\"quux,syzygy\"";
String[] tokens = line.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1);
for(String t : tokens) {
System.out.println("> "+t);
}
}
}
산출:
> foo
> bar
> c;qual="baz,blurb"
> d;junk="quux,syzygy"
즉 , 쉼표에 0이 있거나 그 앞에 따옴표가 짝수 인 경우에만 쉼표로 분할하십시오 .
또는 눈에 조금 친숙합니다.
public class Main {
public static void main(String[] args) {
String line = "foo,bar,c;qual=\"baz,blurb\",d;junk=\"quux,syzygy\"";
String otherThanQuote = " [^\"] ";
String quotedString = String.format(" \" %s* \" ", otherThanQuote);
String regex = String.format("(?x) "+ // enable comments, ignore white spaces
", "+ // match a comma
"(?= "+ // start positive look ahead
" (?: "+ // start non-capturing group 1
" %s* "+ // match 'otherThanQuote' zero or more times
" %s "+ // match 'quotedString'
" )* "+ // end group 1 and repeat it zero or more times
" %s* "+ // match 'otherThanQuote'
" $ "+ // match the end of the string
") ", // stop positive look ahead
otherThanQuote, quotedString, otherThanQuote);
String[] tokens = line.split(regex, -1);
for(String t : tokens) {
System.out.println("> "+t);
}
}
}
첫 번째 예제와 동일합니다.
편집하다
의견에서 @MikeFHay가 언급 한 바와 같이 :
나는 기본값이 더 이상 없기 때문에 Guava ‘s Splitter를 선호한다
String#split()
.Splitter.on(Pattern.compile(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"))
답변
나는 일반적인 정규 표현식을 좋아하지만, 이런 종류의 상태 의존적 토큰 화의 경우, 특히 유지 보수와 관련하여 간단한 파서 (이 경우 해당 단어가 소리를 낼 수있는 것보다 훨씬 간단합니다)가 더 깨끗한 솔루션이라고 생각합니다 예 :
String input = "foo,bar,c;qual=\"baz,blurb\",d;junk=\"quux,syzygy\"";
List<String> result = new ArrayList<String>();
int start = 0;
boolean inQuotes = false;
for (int current = 0; current < input.length(); current++) {
if (input.charAt(current) == '\"') inQuotes = !inQuotes; // toggle state
boolean atLastChar = (current == input.length() - 1);
if(atLastChar) result.add(input.substring(start));
else if (input.charAt(current) == ',' && !inQuotes) {
result.add(input.substring(start, current));
start = current + 1;
}
}
따옴표 안에 쉼표를 유지하는 데 신경 쓰지 않는다면 따옴표 로 쉼표를 다른 것으로 바꾸고 쉼표로 나누면이 방법 (시작 색인 처리, 마지막 문자 특수 경우 제외)을 단순화 할 수 있습니다.
String input = "foo,bar,c;qual=\"baz,blurb\",d;junk=\"quux,syzygy\"";
StringBuilder builder = new StringBuilder(input);
boolean inQuotes = false;
for (int currentIndex = 0; currentIndex < builder.length(); currentIndex++) {
char currentChar = builder.charAt(currentIndex);
if (currentChar == '\"') inQuotes = !inQuotes; // toggle state
if (currentChar == ',' && inQuotes) {
builder.setCharAt(currentIndex, ';'); // or '♡', and replace later
}
}
List<String> result = Arrays.asList(builder.toString().split(","));
답변
http://sourceforge.net/projects/javacsv/
https://github.com/pupi1985/JavaCSV-Reloaded
(이전 라이브러리의 포크는 Windows를 \r\n
실행하지 않을 때 생성 된 출력에 Windows 줄 종결자가 있을 수 있도록합니다 )
http://opencsv.sourceforge.net/
CSV 파일 읽기 및 쓰기에 Java 라이브러리를 추천 할 수 있습니까?
CSV를 XML 파일로 변환하는 Java 라이브러리 또는 앱?
답변
Bart의 정규식 답변을 조언하지 않을 것입니다.이 특별한 경우 (Fabian이 제안한 것처럼) 구문 분석 솔루션이 더 좋습니다. 정규식 솔루션과 자체 구문 분석 구현을 시도했지만 다음을 발견했습니다.
- 역 참조를 사용하는 정규 표현식으로 분할하는 것보다 구문 분석이 훨씬 빠릅니다. 짧은 문자열의 경우 ~ 20 배, 긴 문자열의 경우 ~ 40 배 더 빠릅니다.
- 정규식이 마지막 쉼표 뒤에 빈 문자열을 찾지 못했습니다. 그것은 원래의 질문에는 없었지만 그것은 나의 요구 사항이었습니다.
내 솔루션과 테스트는 다음과 같습니다.
String tested = "foo,bar,c;qual=\"baz,blurb\",d;junk=\"quux,syzygy\",";
long start = System.nanoTime();
String[] tokens = tested.split(",(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
long timeWithSplitting = System.nanoTime() - start;
start = System.nanoTime();
List<String> tokensList = new ArrayList<String>();
boolean inQuotes = false;
StringBuilder b = new StringBuilder();
for (char c : tested.toCharArray()) {
switch (c) {
case ',':
if (inQuotes) {
b.append(c);
} else {
tokensList.add(b.toString());
b = new StringBuilder();
}
break;
case '\"':
inQuotes = !inQuotes;
default:
b.append(c);
break;
}
}
tokensList.add(b.toString());
long timeWithParsing = System.nanoTime() - start;
System.out.println(Arrays.toString(tokens));
System.out.println(tokensList.toString());
System.out.printf("Time with splitting:\t%10d\n",timeWithSplitting);
System.out.printf("Time with parsing:\t%10d\n",timeWithParsing);
물론이 스 니펫에서 추악함에 불편 함을 느낀다면 다른 스위치로 자유롭게 바꿀 수 있습니다. 그런 다음 구분 기호를 사용한 스위치 후 끊김이 없습니다. 스레드 안전성과 관련이없는 속도를 높이기 위해 StringBuilder가 StringBuffer 대신에 선택되었습니다.
답변
과 같은 둘러보기를 시도하십시오 (?!\"),(?!\")
. 에 ,
둘러싸이지 않은 일치해야합니다 "
.
답변
정규 표현식이 거의 수행하지 않는 성가신 경계 영역에 있습니다 (Bart가 지적한 것처럼 따옴표를 탈출하면 삶이 어려워 질 것입니다). 그러나 완전한 파서는 과도하게 보입니다.
조만간 더 큰 복잡성이 필요할 경우 파서 라이브러리를 찾아 볼 것입니다. 예를 들어 이것
답변
나는 참을성이 없었고 답을 기다리지 않기로 결정했습니다 … 참조를 위해 이런 식으로하기가 어렵지 않습니다 (응용 프로그램에서 작동하므로 이스케이프 된 따옴표에 대해 걱정할 필요가 없습니다. 제한된 형식으로 제한됨)
final static private Pattern splitSearchPattern = Pattern.compile("[\",]");
private List<String> splitByCommasNotInQuotes(String s) {
if (s == null)
return Collections.emptyList();
List<String> list = new ArrayList<String>();
Matcher m = splitSearchPattern.matcher(s);
int pos = 0;
boolean quoteMode = false;
while (m.find())
{
String sep = m.group();
if ("\"".equals(sep))
{
quoteMode = !quoteMode;
}
else if (!quoteMode && ",".equals(sep))
{
int toPos = m.start();
list.add(s.substring(pos, toPos));
pos = m.end();
}
}
if (pos < s.length())
list.add(s.substring(pos));
return list;
}
(독자 운동 : 백 슬래시도 찾아 이스케이프 된 따옴표 처리까지 확장하십시오.)