Regex per dividere una stringa utilizzando lo spazio quando non è racchiuso tra virgolette singole o doppie


114

Sono nuovo alle espressioni regolari e apprezzerei il tuo aiuto. Sto cercando di mettere insieme un'espressione che dividerà la stringa di esempio utilizzando tutti gli spazi che non sono racchiusi tra virgolette singole o doppie. Il mio ultimo tentativo è simile a questo: (?!")e non funziona del tutto. Si divide nello spazio prima della citazione.

Input di esempio:

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

Uscita desiderata:

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

Notalo "will be"e 'regular expression'mantieni lo spazio tra le parole.


Stai effettivamente utilizzando il metodo "split" o il looping con il metodo "find" su Matcher sarebbe sufficiente?
erickson

9
"e ora ha due problemi"

Risposte:


251

Non capisco perché tutti gli altri propongano espressioni regolari così complesse o codice così lungo. Essenzialmente, vuoi prendere due tipi di cose dalla tua stringa: sequenze di caratteri che non sono spazi o virgolette e sequenze di caratteri che iniziano e finiscono con una citazione, senza virgolette in mezzo, per due tipi di virgolette. Puoi facilmente abbinare queste cose con questa espressione regolare:

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

Ho aggiunto i gruppi di acquisizione perché non vuoi le virgolette nell'elenco.

Questo codice Java crea l'elenco, aggiungendo il gruppo di acquisizione se corrisponde per escludere le virgolette e aggiungendo la corrispondenza generale dell'espressione regolare se il gruppo di acquisizione non corrisponde (è stata trovata una parola non quotata).

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

Se non ti dispiace avere le virgolette nell'elenco restituito, puoi utilizzare un codice molto più semplice:

List<String> matchList = new ArrayList<String>();
Pattern regex = Pattern.compile("[^\\s\"']+|\"[^\"]*\"|'[^']*'");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
    matchList.add(regexMatcher.group());
} 

1
Jan, grazie per la tua risposta. A proposito, sono un grande fan di EditPad.
carlsz

E se volessi consentire le virgolette con escape nelle stringhe \"?
Monstieur

3
Il problema con questa risposta è con una citazione impareggiabile: John's motherrisultati divisi in[John, s, mother]
Leonbloy

2
Per risolvere i contorni problema leonbloy, è possibile ri-ordinare gli operandi un po 'e omettere le citazioni del spazi-gruppo: "([^"]*)"|'([^']*)'|[^\s]+.
Ghostkeeper

1
Basandosi su questa e altre risposte, la seguente espressione regolare permette sfuggire caratteri all'interno delle virgolette: "([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+. Vedere stackoverflow.com/questions/5695240/...
limnici

15

Ci sono diverse domande su StackOverflow che coprono la stessa domanda in vari contesti utilizzando espressioni regolari. Per esempio:

AGGIORNAMENTO : regex di esempio per gestire stringhe con virgolette singole e doppie. Rif: come posso dividere su una stringa tranne quando sono racchiuso tra virgolette?

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

Testato con un rapido frammento di Perl e l'output è stato riprodotto di seguito. Funziona anche per stringhe vuote o stringhe di soli spazi se sono tra virgolette (non sono sicuro se lo desideri o no).

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

Nota che questo include le virgolette stesse nei valori corrispondenti, anche se puoi rimuoverlo con una stringa di sostituzione o modificare la regex per non includerli. Lo lascio come esercizio per il lettore o per un altro poster per ora, poiché le 2 del mattino sono troppo tardi per fare più confusione con le espressioni regolari;)


Penso che la tua regex consenta virgolette non corrispondenti, ad esempio "sarà" e "espressioni regolari".
Zach Scrivena

@ Zach - hai ragione, lo fa ... aggiornato per risolverlo per ogni evenienza
Jay


3

L'espressione regolare di Jan Goyvaerts è la migliore soluzione che ho trovato finora, ma crea anche corrispondenze vuote (nulle), che esclude nel suo programma. Queste corrispondenze vuote vengono visualizzate anche dai tester delle espressioni regolari (ad esempio rubular.com). Se giri le ricerche intorno (prima cerca le parti citate e poi le parole separate da spazi) allora potresti farlo in una volta con:

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

2
(?<!\G".{0,99999})\s|(?<=\G".{0,99999}")\s

Ciò corrisponderà agli spazi non racchiusi tra virgolette doppie. Devo usare min, max {0,99999} perché Java non supporta * e + in lookbehind.


1

Probabilmente sarà più facile cercare la stringa, afferrare ogni parte, anziché dividerla.

La ragione è che puoi dividerlo negli spazi prima e dopo "will be". Ma non riesco a pensare a un modo per specificare ignorando lo spazio tra all'interno di una divisione.

(non Java effettivo)

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

Inoltre, l'acquisizione di virgolette singole potrebbe causare problemi:

"Foo's Bar 'n Grill"

//=>

"Foo"
"s Bar "
"n"
"Grill"

La tua soluzione non gestisce stringhe con virgolette singole, che fanno parte dell'esempio di Carl.
Jan Goyvaerts,

1

String.split()non è utile qui perché non c'è modo di distinguere tra gli spazi tra virgolette (non dividere) e quelli all'esterno (dividere). Matcher.lookingAt()è probabilmente quello che ti serve:

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

che produce il seguente output:

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."

1

Mi è piaciuto l'approccio di Marcus, tuttavia, l'ho modificato in modo da consentire il testo vicino alle virgolette e supportare entrambi i caratteri "e" virgolette. Ad esempio, avevo bisogno di un = "valore" per non dividerlo in [a =, " qualche valore "].

(?<!\\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"

1

L'approccio di Jan è fantastico, ma eccone un altro per la cronaca.

Se in realtà volessi dividere come menzionato nel titolo, mantenendo le virgolette tra "will be"e 'regular expression', allora potresti usare questo metodo che è direttamente fuori da Abbina (o sostituisci) un modello tranne nelle situazioni s1, s2, s3 ecc.

La regex:

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

Le due alternanze a sinistra corrispondono complete 'quoted strings'e "double-quoted strings". Ignoreremo queste partite. Il lato destro corrisponde e cattura gli spazi del Gruppo 1, e sappiamo che sono gli spazi giusti perché non sono stati abbinati dalle espressioni a sinistra. Li sostituiamo con quelli SplitHerepoi divisi SplitHere. Ancora una volta, questo è per un vero caso diviso in cui vuoi "will be", no will be.

Ecco un'implementazione funzionante completa (vedere i risultati nella demo online ).

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

1

Se stai usando c #, puoi usare

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

Ho aggiunto specificatamente " | <(? [\ W \ s] *)> " per evidenziare che puoi specificare qualsiasi carattere per raggruppare le frasi. (In questo caso sto usando <> per raggruppare.

L'output è:

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

0

Sono ragionevolmente certo che ciò non sia possibile utilizzando solo le espressioni regolari. Controllare se qualcosa è contenuto all'interno di qualche altro tag è un'operazione di analisi. Sembra lo stesso problema del tentativo di analizzare XML con una regex: non può essere fatto correttamente. Potresti essere in grado di ottenere il risultato desiderato applicando ripetutamente un'espressione regolare non avida e non globale che corrisponda alle stringhe citate, quindi una volta che non riesci a trovare nient'altro, dividerla negli spazi ... che ha un numero di problemi, incluso tenere traccia dell'ordine originale di tutte le sottostringhe. La soluzione migliore è scrivere solo una funzione molto semplice che itera sulla stringa ed estrae i gettoni che desideri.


È possibile con una regex, guarda alcuni dei campioni a cui ho collegato. Ci sono alcune variazioni su questo e ho visto diverse domande simili su SO che affrontano questo problema tramite espressioni regolari.
Jay

1
Sapere quando non usare regex è una conoscenza più utile quindi essere in grado di creare un (?: (['"]) (. *?) (? <! \) (?> \\\) * \ 1 | ([ ^ \ s] +))
Rene

0

Un paio di modifiche si spera utili alla risposta accettata di Jan:

(['"])((?:\\\1|.)+?)\1|([^\s"']+)
  • Consente le virgolette con escape all'interno di stringhe tra virgolette
  • Evita di ripetere lo schema per virgolette singole e doppie; questo semplifica anche l'aggiunta di più simboli di citazione se necessario (a scapito di un altro gruppo di acquisizione)

Questo rompe le parole con apostrofi, comeyou're
Design di Adrian,

0

Puoi anche provare questo:

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

Dovresti davvero aggiungere alcune spiegazioni sul motivo per cui dovrebbe funzionare - puoi anche aggiungere codice e commenti nel codice stesso - nella sua forma attuale, non fornisce alcuna spiegazione che possa aiutare il resto della comunità a capire cosa hai fatto per risolvere / rispondere alla domanda. Ciò è particolarmente importante per le domande che hanno già risposte.
ishmaelMakitla

0

Quanto segue restituisce una matrice di argomenti. Gli argomenti sono la variabile "comando" suddivisa in spazi, a meno che non siano inclusi tra virgolette singole o doppie. Le corrispondenze vengono quindi modificate per rimuovere le virgolette singole e doppie.

using System.Text.RegularExpressions;

var args = Regex.Matches(command, "[^\\s\"']+|\"([^\"]*)\"|'([^']*)'").Cast<Match>
().Select(iMatch => iMatch.Value.Replace("\"", "").Replace("'", "")).ToArray();

2
Puoi aggiungere un po 'di spiegazione alla tua risposta in modo che gli altri possano capirla più facilmente? Idealmente, vogliamo evitare risposte di solo codice.
Jaquez

0

1 ° battuta 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.]

non dividere lo spazio vuoto, se lo spazio è circondato da virgolette singole o doppie
dividere lo spazio vuoto quando i 255 caratteri a sinistra e tutti i caratteri a destra dello spazio non sono né virgolette singole né doppie

adattato dal post originale (gestisce solo virgolette doppie)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.