Regex para dividir una cadena usando espacio cuando no está rodeada por comillas simples o dobles

114

Soy nuevo en las expresiones regulares y agradecería su ayuda. Estoy tratando de armar una expresión que divida la cadena de ejemplo usando todos los espacios que no están rodeados por comillas simples o dobles. Mi último intento se ve así: (?!")y no funciona del todo. Se divide en el espacio antes de la cita.

Entrada de ejemplo:

This is a string that "will be" highlighted when your 'regular expression' matches something.

Salida deseada:

This
is
a
string
that
will be
highlighted
when
your
regular expression
matches
something.

Tenga en cuenta eso "will be"y 'regular expression'mantenga el espacio entre las palabras.

carlsz
fuente
¿Realmente está utilizando el método "dividir" o sería suficiente realizar un bucle con el método "buscar" en Matcher?
erickson
9
"y ahora tiene dos problemas"

Respuestas:

251

No entiendo por qué todos los demás proponen expresiones regulares tan complejas o un código tan largo. Esencialmente, desea tomar dos tipos de cosas de su cadena: secuencias de caracteres que no son espacios ni comillas, y secuencias de caracteres que comienzan y terminan con una cita, sin comillas en medio, para dos tipos de comillas. Puede hacer coincidir fácilmente esas cosas con esta expresión regular:

[^\s"']+|"([^"]*)"|'([^']*)'

Agregué los grupos de captura porque no desea las citas en la lista.

Este código Java crea la lista, agregando el grupo de captura si coincidió para excluir las comillas y agregando la coincidencia general de expresiones regulares si el grupo de captura no coincidió (una palabra sin comillas coincidió).

List<String> matchList = new ArrayList<String>();
Pattern regex = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
    if (regexMatcher.group(1) != null) {
        // Add double-quoted string without the quotes
        matchList.add(regexMatcher.group(1));
    } else if (regexMatcher.group(2) != null) {
        // Add single-quoted string without the quotes
        matchList.add(regexMatcher.group(2));
    } else {
        // Add unquoted word
        matchList.add(regexMatcher.group());
    }
} 

Si no le importa tener las comillas en la lista devuelta, puede usar un código mucho más simple:

List<String> matchList = new ArrayList<String>();
Pattern regex = Pattern.compile("[^\\s\"']+|\"[^\"]*\"|'[^']*'");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
    matchList.add(regexMatcher.group());
} 
Jan Goyvaerts
fuente
1
Jan, gracias por tu respuesta. Por cierto, soy un gran fan de EditPad.
carlsz
¿Qué sucede si quiero permitir comillas de escape en las cadenas \"?
Monstieur
3
El problema con esta respuesta es con una cita inigualable: John's motherresultados divididos en[John, s, mother]
leonbloy
2
Para fijar los contornos tema leonbloy, puede volver a ordenar los operandos de un bit y omitir las citas del espacio en blanco del grupo: "([^"]*)"|'([^']*)'|[^\s]+.
Ghostkeeper
1
Sobre la base de esta y otras respuestas, la siguiente expresión regular permite escapar de caracteres dentro de las comillas: "([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+. Ver stackoverflow.com/questions/5695240/…
Limnic
15

Hay varias preguntas en StackOverflow que cubren esta misma pregunta en varios contextos usando expresiones regulares. Por ejemplo:

ACTUALIZACIÓN : Ejemplo de expresión regular para manejar cadenas entre comillas simples y dobles. Ref: ¿Cómo puedo dividir en una cadena excepto cuando está entre comillas?

m/('.*?'|".*?"|\S+)/g 

Probé esto con un breve fragmento de Perl y el resultado fue como se reproduce a continuación. También funciona para cadenas vacías o cadenas de solo espacios en blanco si están entre comillas (no estoy seguro de si se desea o no).

This
is
a
string
that
"will be"
highlighted
when
your
'regular expression'
matches
something.

Tenga en cuenta que esto incluye los caracteres de comillas en los valores coincidentes, aunque puede eliminarlos con un reemplazo de cadena o modificar la expresión regular para no incluirlos. Dejaré eso como un ejercicio para el lector u otro póster por ahora, ya que las 2 am es demasiado tarde para seguir jugando con las expresiones regulares;)

Arrendajo
fuente
Creo que su expresión regular permite comillas no coincidentes, por ejemplo, "será" y "expresiones regulares".
Zach Scrivena
@Zach - tienes razón, lo hace ... lo actualizó para solucionarlo por si acaso
Jay
3

La expresión regular de Jan Goyvaerts es la mejor solución que encontré hasta ahora, pero también crea coincidencias vacías (nulas), que él excluye en su programa. Estas coincidencias vacías también aparecen en los probadores de expresiones regulares (por ejemplo, rubular.com). Si cambia las búsquedas (primero busque las partes citadas y luego las palabras separadas por espacios), entonces puede hacerlo una vez con:

("[^"]*"|'[^']*'|[\S]+)+
hierro
fuente
2
(?<!\G".{0,99999})\s|(?<=\G".{0,99999}")\s

Esto coincidirá con los espacios que no estén entre comillas dobles. Tengo que usar min, max {0,99999} porque Java no admite * y + en lookbehind.

Marcus Andrómeda
fuente
1

Probablemente será más fácil buscar la cadena, agarrar cada parte, en lugar de dividirla.

La razón es que puede dividirlo en los espacios antes y después "will be". Pero, no puedo pensar en ninguna forma de especificar ignorando el espacio entre el interior de una división.

(no Java real)

string = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";

regex = "\"(\\\"|(?!\\\").)+\"|[^ ]+"; // search for a quoted or non-spaced group
final = new Array();

while (string.length > 0) {
    string = string.trim();
    if (Regex(regex).test(string)) {
        final.push(Regex(regex).match(string)[0]);
        string = string.replace(regex, ""); // progress to next "word"
    }
}

Además, la captura de comillas simples podría generar problemas:

"Foo's Bar 'n Grill"

//=>

"Foo"
"s Bar "
"n"
"Grill"
Jonathan Lonowski
fuente
Su solución no admite cadenas entre comillas simples, que son parte del ejemplo de Carl.
Jan Goyvaerts
1

String.split()no es útil aquí porque no hay forma de distinguir entre los espacios entre comillas (no dividir) y los que están fuera (dividir). Matcher.lookingAt()es probablemente lo que necesitas:

String str = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
str = str + " "; // add trailing space
int len = str.length();
Matcher m = Pattern.compile("((\"[^\"]+?\")|('[^']+?')|([^\\s]+?))\\s++").matcher(str);

for (int i = 0; i < len; i++)
{
    m.region(i, len);

    if (m.lookingAt())
    {
        String s = m.group(1);

        if ((s.startsWith("\"") && s.endsWith("\"")) ||
            (s.startsWith("'") && s.endsWith("'")))
        {
            s = s.substring(1, s.length() - 1);
        }

        System.out.println(i + ": \"" + s + "\"");
        i += (m.group(0).length() - 1);
    }
}

que produce la siguiente salida:

0: "This"
5: "is"
8: "a"
10: "string"
17: "that"
22: "will be"
32: "highlighted"
44: "when"
49: "your"
54: "regular expression"
75: "matches"
83: "something."
Zach Scrivena
fuente
1

Me gustó el enfoque de Marcus, sin embargo, lo modifiqué para permitir texto cerca de las comillas y admitir caracteres "y". Por ejemplo, necesitaba un = "algún valor" para no dividirlo en [a =, " algún valor "].

(?<!\\G\\S{0,99999}[\"'].{0,99999})\\s|(?<=\\G\\S{0,99999}\".{0,99999}\"\\S{0,99999})\\s|(?<=\\G\\S{0,99999}'.{0,99999}'\\S{0,99999})\\s"
Eric Woodruff
fuente
1

El enfoque de Jan es genial, pero aquí hay otro para que conste.

Si realmente desea dividir como se menciona en el título, manteniendo las comillas en "will be"y 'regular expression', entonces puede usar este método que está directamente fuera de Match (o reemplazar) un patrón, excepto en las situaciones s1, s2, s3, etc.

La expresión regular:

'[^']*'|\"[^\"]*\"|( )

Las dos alternancias izquierdas coinciden completo 'quoted strings'y "double-quoted strings". Ignoraremos estos partidos. El lado derecho hace coincidir y captura espacios con el Grupo 1, y sabemos que son los espacios correctos porque no se corresponden con las expresiones de la izquierda. Los reemplazamos con y SplitHereluego los dividimos SplitHere. Nuevamente, esto es para un verdadero caso dividido en el que lo desea "will be", no will be.

Aquí hay una implementación funcional completa (vea los resultados en la demostración en línea ).

import java.util.*;
import java.io.*;
import java.util.regex.*;
import java.util.List;

class Program {
public static void main (String[] args) throws java.lang.Exception  {

String subject = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
Pattern regex = Pattern.compile("\'[^']*'|\"[^\"]*\"|( )");
Matcher m = regex.matcher(subject);
StringBuffer b= new StringBuffer();
while (m.find()) {
    if(m.group(1) != null) m.appendReplacement(b, "SplitHere");
    else m.appendReplacement(b, m.group(0));
}
m.appendTail(b);
String replaced = b.toString();
String[] splits = replaced.split("SplitHere");
for (String split : splits) System.out.println(split);
} // end main
} // end Program
zx81
fuente
1

Si está usando c #, puede usar

string input= "This is a string that \"will be\" highlighted when your 'regular expression' matches <something random>";

List<string> list1 = 
                Regex.Matches(input, @"(?<match>\w+)|\""(?<match>[\w\s]*)""|'(?<match>[\w\s]*)'|<(?<match>[\w\s]*)>").Cast<Match>().Select(m => m.Groups["match"].Value).ToList();

foreach(var v in list1)
   Console.WriteLine(v);

He agregado específicamente " | <(? [\ W \ s] *)> " para resaltar que puede especificar cualquier carácter para agrupar frases. (En este caso estoy usando <> para agrupar.

La salida es:

This
is
a
string
that
will be
highlighted
when
your
regular expression 
matches
something random
Praveen Singh
fuente
0

Estoy razonablemente seguro de que esto no es posible usando solo expresiones regulares. Verificar si algo está contenido dentro de alguna otra etiqueta es una operación de análisis. Esto parece ser el mismo problema que intentar analizar XML con una expresión regular; no se puede hacer correctamente. Es posible que pueda obtener el resultado deseado aplicando repetidamente una expresión regular no codiciosa y no global que coincida con las cadenas entre comillas, luego, una vez que no pueda encontrar nada más, divídalo en los espacios ... que tiene un número de problemas, incluido el seguimiento del orden original de todas las subcadenas. Su mejor opción es escribir una función realmente simple que repita la cadena y extraiga los tokens que desee.

rmeador
fuente
Es posible con una expresión regular, vea algunas de las muestras a las que he vinculado. Hay algunas variaciones sobre esto, y he visto varias preguntas similares sobre SO que abordan esto a través de expresiones regulares.
Jay
1
Saber cuándo no usar expresiones regulares es un conocimiento más útil que poder crear un (?: (['"]) (. *?) (? <! \) (?> \\\) * \ 1 | ([ ^ \ s] +))
Rene
0

Esperamos que un par de ajustes útiles en la respuesta aceptada de Jan:

(['"])((?:\\\1|.)+?)\1|([^\s"']+)
  • Permite comillas de escape dentro de cadenas entre comillas
  • Evita repetir el patrón para las comillas simples y dobles; esto también simplifica la adición de más símbolos de comillas si es necesario (a expensas de un grupo de captura más)
pascales
fuente
Esto rompe las palabras con apóstrofes, comoyou're
Diseño de Adrian
0

También puedes probar esto:

    String str = "This is a string that \"will be\" highlighted when your 'regular expression' matches something";
    String ss[] = str.split("\"|\'");
    for (int i = 0; i < ss.length; i++) {
        if ((i % 2) == 0) {//even
            String[] part1 = ss[i].split(" ");
            for (String pp1 : part1) {
                System.out.println("" + pp1);
            }
        } else {//odd
            System.out.println("" + ss[i]);
        }
    }
Rakesh Sosa
fuente
Realmente debería agregar alguna explicación de por qué esto debería funcionar; también puede agregar código, así como los comentarios en el código mismo; en su forma actual, no proporciona ninguna explicación que pueda ayudar al resto de la comunidad a comprender qué hiciste para resolver / responder la pregunta. Esto es especialmente importante para las preguntas que ya tienen respuestas.
ishmaelMakitla
0

Lo siguiente devuelve una matriz de argumentos. Los argumentos son la variable 'comando' dividida en espacios, a menos que se incluyan entre comillas simples o dobles. Luego, las coincidencias se modifican para eliminar las comillas simples y dobles.

using System.Text.RegularExpressions;

var args = Regex.Matches(command, "[^\\s\"']+|\"([^\"]*)\"|'([^']*)'").Cast<Match>
().Select(iMatch => iMatch.Value.Replace("\"", "").Replace("'", "")).ToArray();
Rudi Jansen van Vuuren
fuente
2
¿Puede agregar un poco de explicación a su respuesta para que otros puedan entenderla más fácilmente? Idealmente, queremos evitar las respuestas de solo código.
Jaquez
0

1er one-liner usando String.split ()

String s = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
String[] split = s.split( "(?<!(\"|').{0,255}) | (?!.*\\1.*)" );

[This, is, a, string, that, "will be", highlighted, when, your, 'regular expression', matches, something.]

no dividir en el espacio en blanco, si el espacio en blanco está rodeado por comillas simples o dobles
dividir en el espacio en blanco cuando los 255 caracteres a la izquierda y todos los caracteres a la derecha del espacio en blanco no son comillas simples ni dobles

adaptado de la publicación original (maneja solo comillas dobles)

Kaplan
fuente