una texto son separar separado por parte palabra los letras leer extraer delimitadores cuales comas caracter cadena array archivo java regex string

texto - Java: dividir una cadena separada por comas pero ignorar comas entre comillas



separar una palabra en letras java (9)

Tengo una cadena vagamente como esta:

foo,bar,c;qual="baz,blurb",d;junk="quux,syzygy"

que quiero dividir por comas, pero necesito ignorar comas entre comillas. ¿Cómo puedo hacer esto? Parece que falla un enfoque de expresión regular; Supongo que puedo escanear manualmente e ingresar a un modo diferente cuando veo una cita, pero sería bueno usar bibliotecas preexistentes. ( edición : supongo que me refería a las bibliotecas que ya forman parte del JDK o ya forman parte de las bibliotecas de uso común, como Apache Commons).

la cadena anterior debe dividirse en:

foo bar c;qual="baz,blurb" d;junk="quux,syzygy"

nota: este NO es un archivo CSV, es una cadena única contenida en un archivo con una estructura general más grande


En lugar de usar lookahead y otras expresiones regulares, simplemente saque las comillas primero. Es decir, para cada agrupación de cotizaciones, reemplace esa agrupación con __IDENTIFIER_1 o algún otro indicador, y __IDENTIFIER_1 esa agrupación a un mapa de cadena, cadena.

Después de dividir en coma, reemplace todos los identificadores asignados con los valores de cadena originales.


Estás en esa molesta área de límites donde las expresiones regulares casi no funcionan (como ha señalado Bart, escapar de las comillas haría la vida difícil), y sin embargo, un analizador completo parece una exageración.

Si es probable que necesite una mayor complejidad en algún momento, pronto buscaría una biblioteca de análisis. Por ejemplo este


Estaba impaciente y elegí no esperar por las respuestas ... para referencia, no parece tan difícil hacer algo como esto (lo que funciona para mi aplicación, no tengo que preocuparme por las comillas que se escapan, como las cosas entre comillas). se limita a unas pocas formas restringidas):

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; }

(ejercicio para el lector: extienda al manejo de citas escapadas buscando barras invertidas también).


Haría algo como esto:

boolean foundQuote = false; if(charAtIndex(currentStringIndex) == ''"'') { foundQuote = true; } if(foundQuote == true) { //do nothing } else { string[] split = currentString.split('',''); }


Intente un lookaround como (?!/"),(?!/") . Esto debe coincidir , que no están rodeados por " .


No recomendaría una respuesta a expresiones regulares de Bart. Encuentro mejor la solución de análisis en este caso particular (como propuso Fabian). He intentado la solución de expresiones regulares y la propia implementación de análisis que he encontrado que:

  1. El análisis es mucho más rápido que la división con expresiones regulares con referencias inversas: ~ 20 veces más rápido para cadenas cortas, ~ 40 veces más rápido para cadenas largas.
  2. Regex no encuentra una cadena vacía después de la última coma. Eso no estaba en la pregunta original, sin embargo, era un requisito mío.

Mi solución y prueba a continuación.

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);

Por supuesto, usted es libre de cambiar el cambio a else-ifs en este fragmento de código si se siente incómodo con su fealdad. Tenga en cuenta entonces la falta de descanso después de un interruptor con separador. StringBuilder fue elegido en lugar de StringBuffer por diseño para aumentar la velocidad, donde la seguridad del hilo no es relevante.


Si bien me gustan las expresiones regulares en general, para este tipo de tokenización dependiente del estado, creo que un analizador simple (que en este caso es mucho más simple de lo que esa palabra podría hacer que suene) es probablemente una solución más limpia, en particular con respecto a la capacidad de mantenimiento. , p.ej:

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; } }

Si no le importa conservar las comas dentro de las comillas, podría simplificar este enfoque (sin manejar el índice de inicio, sin el caso del último carácter especial) reemplazando las comas entre comillas por otra cosa y luego dividir en comas:

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(","));


Tratar:

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); } } }

Salida:

> foo > bar > c;qual="baz,blurb" > d;junk="quux,syzygy"

En otras palabras: divida en la coma solo si esa coma tiene cero, o un número par de comillas por delante .

O, un poco más amigable para los ojos:

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); } } }

Lo que produce lo mismo que el primer ejemplo.

EDITAR

Como lo menciona @MikeFHay en los comentarios:

Prefiero usar el Splitter de Guava , ya que tiene mejores valores predeterminados (vea la discusión anterior sobre los recortes vacíos que se recortan con String#split() , así que hice:

Splitter.on(Pattern.compile(",(?=(?:[^/"]*/"[^/"]*/")*[^/"]*$)"))