Regex do dzielenia ciągu przy użyciu spacji, gdy nie jest on otoczony pojedynczymi lub podwójnymi cudzysłowami

114

Jestem nowy w wyrażeniach regularnych i byłbym wdzięczny za pomoc. Próbuję połączyć wyrażenie, które podzieli przykładowy ciąg przy użyciu wszystkich spacji, które nie są otoczone pojedynczymi lub podwójnymi cudzysłowami. Moja ostatnia próba wygląda tak: (?!")i nie działa. Dzieli się na przestrzeni przed cytatem.

Przykładowe dane wejściowe:

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

Pożądane wyjście:

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

Zwróć na to uwagę "will be"i 'regular expression'zachowaj odstępy między słowami.

carlsz
źródło
Czy faktycznie używasz metody „split”, czy też wystarczy zapętlenie z metodą „find” w programie Matcher?
erickson
9
„i teraz ma dwa problemy”

Odpowiedzi:

251

Nie rozumiem, dlaczego wszyscy inni proponują tak złożone wyrażenia regularne lub tak długi kod. Zasadniczo chcesz pobrać dwa rodzaje rzeczy ze swojego ciągu: sekwencje znaków, które nie są spacjami ani cudzysłowami, oraz sekwencje znaków, które zaczynają się i kończą cudzysłowem, bez cudzysłowów pomiędzy, dla dwóch rodzajów cudzysłowów. Możesz łatwo dopasować te rzeczy za pomocą tego wyrażenia regularnego:

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

Dodałem grupy przechwytywania, ponieważ nie chcesz cytatów na liście.

Ten kod Java tworzy listę, dodając grupę przechwytywania, jeśli została dopasowana, aby wykluczyć cudzysłowy, i dodając ogólne dopasowanie wyrażenia regularnego, jeśli grupa przechwytywania nie pasuje (dopasowano słowo bez cudzysłowu).

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

Jeśli nie masz nic przeciwko umieszczaniu cudzysłowów na zwracanej liście, możesz użyć znacznie prostszego kodu:

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
źródło
1
Jan, dzięki za twoją odpowiedź. BTW, jestem wielkim fanem EditPada.
carlsz
Co jeśli chcę zezwolić na cudzysłowy w ciągach znaków \"?
Monstieur
3
Problem z tą odpowiedzią polega na niezrównanym cytacie: John's motherwyniki podzielone[John, s, mother]
leonbloy
2
Aby rozwiązać zarysy leonbloy problem, można zmienić kolejność argumentów trochę i pominąć cytaty z białymi znakami grupy: "([^"]*)"|'([^']*)'|[^\s]+.
Ghostkeeper
1
Opierając się na tym i innych odpowiedzi, następujące regex umożliwia ucieczkę znaki wewnątrz cytatów: "([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+. Zobacz stackoverflow.com/questions/5695240/…
Limnic
15

Istnieje kilka pytań dotyczących StackOverflow, które obejmują to samo pytanie w różnych kontekstach przy użyciu wyrażeń regularnych. Na przykład:

AKTUALIZACJA : Przykładowe wyrażenie regularne do obsługi ciągów w pojedynczych i podwójnych cudzysłowach. Ref: Jak mogę podzielić ciąg, chyba że w cudzysłowie?

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

Przetestowałem to za pomocą szybkiego fragmentu kodu Perl i wynik był taki, jak pokazano poniżej. Działa również dla pustych łańcuchów lub ciągów zawierających tylko białe znaki, jeśli znajdują się one w cudzysłowie (nie jestem pewien, czy jest to pożądane, czy nie).

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

Zauważ, że obejmuje to same znaki cudzysłowu w dopasowanych wartościach, chociaż możesz je usunąć, zastępując ciąg znaków, lub zmodyfikować wyrażenie regularne, aby ich nie uwzględniać. Zostawię to na razie jako ćwiczenie dla czytelnika lub innego plakatu, ponieważ 2 nad ranem to już zdecydowanie za późno, aby już majstrować przy wyrażeniach regularnych;)

Sójka
źródło
Myślę, że twoje wyrażenie regularne dopuszcza niedopasowane cudzysłowy, np. „Będzie” i „wyrażenia regularne”.
Zach Scrivena,
@Zach - masz rację, to robi ... zaktualizowałem to, aby to naprawić na wszelki wypadek
Jay
3

Wyrażenie regularne od Jana Goyvaertsa jest najlepszym rozwiązaniem, jakie do tej pory znalazłem, ale tworzy również puste (zerowe) dopasowania, które wyklucza w swoim programie. Te puste dopasowania pojawiają się również w testerach wyrażeń regularnych (np. Rubular.com). Jeśli odwrócisz wyszukiwanie (najpierw poszukaj cytowanych części, a następnie słów oddzielonych spacjami), możesz to zrobić od razu za pomocą:

("[^"]*"|'[^']*'|[\S]+)+
żelazo
źródło
2
(?<!\G".{0,99999})\s|(?<=\G".{0,99999}")\s

Spowoduje to dopasowanie spacji nieuwzględnionych w cudzysłowach. Muszę użyć min, max {0,99999}, ponieważ Java nie obsługuje * i + w lookbehind.

Marcus Andromeda
źródło
1

Prawdopodobnie łatwiej będzie przeszukać ciąg, chwytając każdą część, zamiast ją rozdzielać.

Powodem jest to, że możesz go podzielić na spacje przed i po "will be". Ale nie mogę wymyślić żadnego sposobu, aby określić ignorowanie odstępu między wewnątrz podziału.

(nie rzeczywista Java)

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

Ponadto przechwytywanie pojedynczych cudzysłowów może prowadzić do problemów:

"Foo's Bar 'n Grill"

//=>

"Foo"
"s Bar "
"n"
"Grill"
Jonathan Lonowski
źródło
Twoje rozwiązanie nie obsługuje ciągów w apostrofach, które są częścią przykładu Carla.
Jan Goyvaerts,
1

String.split()nie jest tutaj pomocne, ponieważ nie ma sposobu, aby odróżnić spacje w cudzysłowach (nie dziel) od spacji na zewnątrz (dziel). Matcher.lookingAt()jest prawdopodobnie tym, czego potrzebujesz:

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

co daje następujący wynik:

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
źródło
1

Podobało mi się podejście Marcusa, jednak zmodyfikowałem je, aby umożliwić umieszczanie tekstu w pobliżu cudzysłowów i obsługiwać znaki „i” cytat. Na przykład potrzebowałem a = „jakaś wartość”, aby nie dzielić go na [a =, ” jakąś wartość ”].

(?<!\\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
źródło
1

Podejście Jana jest świetne, ale dla przypomnienia jest jeszcze jedno.

Jeśli faktycznie chciałbyś podzielić, jak wspomniano w tytule, zachowując cudzysłowy w "will be"i 'regular expression', możesz użyć tej metody, która jest prosto z Dopasuj (lub zastąp) wzorzec, z wyjątkiem sytuacji s1, s2, s3 itp.

Wyrażenie regularne:

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

Dwie lewe alternacje są zgodne z zakończeniem 'quoted strings'i "double-quoted strings". Zignorujemy te mecze. Prawa strona dopasowuje i przechwytuje spacje do grupy 1 i wiemy, że są to właściwe spacje, ponieważ nie zostały dopasowane przez wyrażenia po lewej stronie. Zastępujemy te, a SplitHerenastępnie dzielimy SplitHere. Ponownie, dotyczy to prawdziwego podziału przypadku, w którym chcesz "will be", a nie will be.

Oto pełna działająca implementacja (zobacz wyniki w 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
zx81
źródło
1

Jeśli używasz języka C #, możesz użyć

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

Specjalnie dodałem „ | <(? [\ W \ s] *)> ”, aby zaznaczyć, że możesz określić dowolny znak do wyrażenia w grupie. (W tym przypadku używam <> do grupowania.

Wynik to:

This
is
a
string
that
will be
highlighted
when
your
regular expression 
matches
something random
Praveen Singh
źródło
0

Jestem pewien, że nie jest to możliwe przy użyciu samych wyrażeń regularnych. Sprawdzanie, czy coś jest zawarte w jakimś innym tagu, jest operacją analizowania. Wydaje się, że to ten sam problem, co próba przeanalizowania XML za pomocą wyrażenia regularnego - nie można tego zrobić poprawnie. Możesz być w stanie osiągnąć pożądany rezultat, wielokrotnie stosując niechciwe, nieglobalne wyrażenie regularne, które pasuje do cytowanych ciągów, a gdy nie możesz znaleźć niczego innego, podziel je na spacje ... które ma liczbę problemy, w tym śledzenie oryginalnej kolejności wszystkich podciągów. Najlepszym rozwiązaniem jest po prostu napisanie naprawdę prostej funkcji, która iteruje po ciągu i wyciąga żądane tokeny.

rmeador
źródło
Jest to możliwe dzięki wyrażeniu regularnemu, zobacz niektóre próbki, z którymi się łączyłem. Jest kilka odmian tego i widziałem kilka podobnych pytań dotyczących SO, które rozwiązują ten problem za pomocą wyrażeń regularnych.
Jay
1
Wiedz, kiedy nie używać wyrażenia regularnego, jest bardziej pomocna niż możliwość tworzenia (?: (['"]) (. *?) (? <! \) (?> \\\) * \ 1 | ([ ^ \ s] +))
Rene
0

Kilka, miejmy nadzieję, przydatnych poprawek w zaakceptowanej odpowiedzi Jana:

(['"])((?:\\\1|.)+?)\1|([^\s"']+)
  • Zezwala na cudzysłowy w cudzysłowach
  • Unika powtarzania wzorca dla pojedynczego i podwójnego cudzysłowu; upraszcza to również dodawanie większej liczby symboli cytowania w razie potrzeby (kosztem jeszcze jednej grupy przechwytywania)
paskale
źródło
To łamie słowa z apostrofami w nich, jakyou're
Projekt Adriana
0

Możesz też spróbować tego:

    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
źródło
Naprawdę powinieneś dodać jakieś wyjaśnienie, dlaczego to powinno działać - możesz również dodać kod, a także komentarze w samym kodzie - w obecnej formie nie zapewnia żadnego wyjaśnienia, które mogłoby pomóc reszcie społeczności zrozumieć, co zrobiłeś, aby rozwiązać / odpowiedzieć na pytanie. Jest to szczególnie ważne w przypadku pytań, na które już są odpowiedzi.
ishmaelMakitla
0

Poniższe zwraca tablicę argumentów. Argumenty to zmienna „polecenie” podzielona na spacje, chyba że są zawarte w pojedynczych lub podwójnych cudzysłowach. Dopasowania są następnie modyfikowane, aby usunąć pojedyncze i podwójne cudzysłowy.

using System.Text.RegularExpressions;

var args = Regex.Matches(command, "[^\\s\"']+|\"([^\"]*)\"|'([^']*)'").Cast<Match>
().Select(iMatch => iMatch.Value.Replace("\"", "").Replace("'", "")).ToArray();
Rudi Jansen van Vuuren
źródło
2
Czy możesz dodać trochę wyjaśnienia do swojej odpowiedzi, aby inni mogli ją łatwiej zrozumieć? Najlepiej byłoby, gdybyśmy unikali odpowiedzi zawierających tylko kod.
Jaquez
0

Pierwsza jednowierszowa za pomocą 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.]

nie dziel w pustym miejscu, jeśli puste miejsce jest otoczone pojedynczymi lub podwójnymi cudzysłowami,
podziel je w pustym miejscu, gdy 255 znaków po lewej i wszystkie znaki po prawej stronie nie są ani pojedynczymi, ani podwójnymi cudzysłowami

zaadaptowano z oryginalnego postu (obsługuje tylko podwójne cudzysłowy)

Kaplan
źródło