Regex สำหรับการแยกสตริงโดยใช้ช่องว่างเมื่อไม่ล้อมรอบด้วยเครื่องหมายคำพูดเดี่ยวหรือคู่


114

ฉันยังใหม่กับนิพจน์ทั่วไปและขอขอบคุณสำหรับความช่วยเหลือ ฉันพยายามรวบรวมนิพจน์ที่จะแยกสตริงตัวอย่างโดยใช้ช่องว่างทั้งหมดที่ไม่ได้ล้อมรอบด้วยเครื่องหมายคำพูดเดี่ยวหรือคู่ ความพยายามครั้งสุดท้ายของฉันมีลักษณะดังนี้(?!")และไม่ค่อยได้ผล มันแยกออกจากช่องว่างก่อนใบเสนอราคา

ตัวอย่างการป้อนข้อมูล:

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

ผลลัพธ์ที่ต้องการ:

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

สังเกต"will be"และ'regular expression'รักษาช่องว่างระหว่างคำ


คุณใช้เมธอด "แยก" จริง ๆ หรือจะวนลูปด้วยเมธอด "find" บน Matcher ก็เพียงพอแล้ว?
erickson

9
"และตอนนี้เขามีปัญหาสองอย่าง"

คำตอบ:


251

ฉันไม่เข้าใจว่าทำไมคนอื่น ๆ ถึงเสนอนิพจน์ทั่วไปที่ซับซ้อนหรือโค้ดยาว ๆ แบบนี้ โดยพื้นฐานแล้วคุณต้องการจับสิ่งต่างๆสองประเภทจากสตริงของคุณ: ลำดับของอักขระที่ไม่เว้นวรรคหรือเครื่องหมายคำพูดและลำดับของอักขระที่ขึ้นต้นและลงท้ายด้วยเครื่องหมายคำพูดโดยไม่มีเครื่องหมายคำพูดคั่นระหว่างคำพูดสองประเภท คุณสามารถจับคู่สิ่งเหล่านั้นด้วยนิพจน์ทั่วไปนี้ได้อย่างง่ายดาย:

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

ฉันเพิ่มกลุ่มการจับภาพเพราะคุณไม่ต้องการให้มีเครื่องหมายคำพูดในรายการ

โค้ด Java นี้จะสร้างรายการโดยเพิ่มกลุ่มการจับภาพหากจับคู่เพื่อยกเว้นเครื่องหมายคำพูดและเพิ่มการจับคู่ regex โดยรวมหากกลุ่มการจับภาพไม่ตรงกัน (คำที่ไม่ได้ใส่เครื่องหมายถูกจับคู่)

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

หากคุณไม่สนใจที่จะมีคำพูดในรายการที่ส่งคืนคุณสามารถใช้รหัสที่ง่ายกว่านี้ได้

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

1
ม.ค. ขอบคุณสำหรับการตอบกลับ BTW ฉันเป็นแฟนตัวยงของ EditPad
carlsz

จะเกิดอะไรขึ้นถ้าฉันต้องการอนุญาตเครื่องหมายคำพูดที่ใช้ Escape ในสตริง\"?
Monstieur

3
ปัญหาเกี่ยวกับคำตอบนี้คือคำพูดที่ไม่ตรงกัน: John's motherผลลัพธ์ที่แยกออกมา[John, s, mother]
leonbloy

2
เพื่อแก้ไขโครงร่างปัญหา leonbloy "([^"]*)"|'([^']*)'|[^\s]+คุณสามารถสั่งซื้อใหม่ถูกดำเนินการบิตและงดคำพูดจากช่องว่างกลุ่ม:
Ghostkeeper

1
สร้างเมื่อนี้และคำตอบอื่น ๆ regex "([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+ต่อไปนี้จะช่วยให้ตัวละครที่อยู่ในเครื่องหมายหนี: ดูstackoverflow.com/questions/5695240/…
Limnic

15

มีคำถามมากมายใน StackOverflow ที่ครอบคลุมคำถามเดียวกันนี้ในบริบทต่างๆโดยใช้นิพจน์ทั่วไป ตัวอย่างเช่น:

อัปเดต : regex ตัวอย่างเพื่อจัดการสตริงที่ยกมาเดี่ยวและคู่ Ref: ฉันจะแยกสตริงได้อย่างไรยกเว้นเมื่ออยู่ในเครื่องหมายคำพูด?

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

ทดสอบสิ่งนี้ด้วยข้อมูลโค้ด Perl ฉบับย่อและผลลัพธ์เป็นไปตามที่จำลองด้านล่าง นอกจากนี้ยังใช้ได้กับสตริงว่างหรือสตริงเว้นวรรคเท่านั้นหากอยู่ระหว่างเครื่องหมายคำพูด (ไม่แน่ใจว่าต้องการหรือไม่)

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

โปรดทราบว่าสิ่งนี้รวมอักขระอัญประกาศไว้ในค่าที่ตรงกันแม้ว่าคุณจะสามารถลบออกได้โดยใช้สตริงแทนที่หรือแก้ไขนิพจน์ทั่วไปเพื่อไม่รวมอักขระเหล่านั้น ฉันจะปล่อยให้มันเป็นแบบฝึกหัดสำหรับผู้อ่านหรือผู้โพสต์อื่นในตอนนี้เนื่องจาก 2:00 น. สายเกินไปที่จะยุ่งกับนิพจน์ทั่วไปอีกต่อไป;)


ฉันคิดว่านิพจน์ทั่วไปของคุณอนุญาตให้ใช้เครื่องหมายคำพูดที่ไม่ตรงกันเช่น "จะเป็น" และ "นิพจน์ทั่วไป"
Zach Scrivena

@ Zach - คุณพูดถูก ... อัปเดตเพื่อแก้ไขในกรณีนี้
Jay

6

หากคุณต้องการอนุญาตเครื่องหมายคำพูดที่ใช้ Escape ภายในสตริงคุณสามารถใช้สิ่งต่อไปนี้:

(?:(['"])(.*?)(?<!\\)(?>\\\\)*\1|([^\s]+))

สตริงที่ยกมาจะเป็นกลุ่มที่ 2 คำที่ไม่ได้ใส่เครื่องหมายคำเดียวจะเป็นกลุ่ม 3

คุณสามารถลองใช้สตริงต่างๆได้ที่นี่: http://www.fileformat.info/tool/regex.htmหรือhttp://gskinner.com/RegExr/


3

regex จาก Jan Goyvaerts เป็นทางออกที่ดีที่สุดที่ฉันพบ แต่สร้างการจับคู่ว่าง (ว่าง) ซึ่งเขาไม่รวมไว้ในโปรแกรมของเขา การจับคู่ว่างเหล่านี้ยังปรากฏจากผู้ทดสอบ regex (เช่น rubular.com) หากคุณเปลี่ยนการค้นหาโดยรอบ (ก่อนอื่นให้มองหาส่วนที่ยกมาและเว้นวรรคคำที่แยกออกจากกัน) คุณอาจทำได้ในครั้งเดียวด้วย:

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

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

สิ่งนี้จะตรงกับช่องว่างที่ไม่ล้อมรอบด้วยเครื่องหมายคำพูดคู่ ฉันต้องใช้ขั้นต่ำสูงสุด {0,99999} เนื่องจาก Java ไม่รองรับ * และ + ใน lookbehind


1

อาจจะง่ายกว่าในการค้นหาสตริงโดยการจับแต่ละส่วนเทียบกับแยกมัน

เหตุผลคือคุณสามารถแบ่งช่องว่างก่อนและหลัง"will be"ได้ แต่ฉันคิดไม่ออกว่าจะระบุวิธีใดโดยไม่สนใจช่องว่างระหว่างภายในตัวแยก

(ไม่ใช่ 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"
    }
}

นอกจากนี้การจับคำพูดเดี่ยวอาจทำให้เกิดปัญหา:

"Foo's Bar 'n Grill"

//=>

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

โซลูชันของคุณไม่จัดการสตริงที่ยกมาเดี่ยวซึ่งเป็นส่วนหนึ่งของตัวอย่างของ Carl
ม.ค. Goyvaerts

1

String.split()ที่นี่ไม่เป็นประโยชน์เนื่องจากไม่มีวิธีแยกความแตกต่างระหว่างช่องว่างภายในเครื่องหมายคำพูด (อย่าแยก) และช่องว่างภายนอก (แบ่ง) Matcher.lookingAt()อาจเป็นสิ่งที่คุณต้องการ:

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

ซึ่งสร้างผลลัพธ์ต่อไปนี้:

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

อย่างไรก็ตามฉันชอบแนวทางของ Marcus แต่ฉันได้แก้ไขเพื่อให้สามารถอนุญาตข้อความที่อยู่ใกล้กับเครื่องหมายคำพูดและรองรับทั้งอักขระ "และ" เครื่องหมายคำพูดตัวอย่างเช่นฉันต้องการ a = "some value" เพื่อไม่ให้แบ่งเป็น [a =, " ค่าบางอย่าง "]

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

แนวทางของแจนนั้นยอดเยี่ยม แต่นี่เป็นอีกวิธีหนึ่งสำหรับการบันทึก

หากคุณต้องการแยกตามที่กล่าวไว้ในชื่อเรื่องโดยเก็บเครื่องหมายคำพูดไว้"will be"และ'regular expression'คุณสามารถใช้วิธีนี้ซึ่งตรงจากMatch (หรือแทนที่) รูปแบบยกเว้นในสถานการณ์ s1, s2, s3 เป็นต้น

นิพจน์ทั่วไป:

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

ทั้งสอง alternations ซ้ายตรงกับที่สมบูรณ์และ'quoted strings' "double-quoted strings"เราจะไม่สนใจการแข่งขันเหล่านี้ ด้านขวาจับคู่และจับช่องว่างกับกลุ่ม 1 และเรารู้ว่าช่องว่างเหล่านี้เป็นช่องว่างที่ถูกต้องเพราะไม่ตรงกับนิพจน์ทางด้านซ้าย เราแทนที่ผู้ที่มีแล้วแยกSplitHere SplitHereอีกครั้งนี้เป็นกรณีที่แยกความจริงที่คุณต้องการไม่ได้"will be"will be

นี่คือการใช้งานเต็มรูปแบบ (ดูผลลัพธ์ในการสาธิตออนไลน์ )

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

หากคุณใช้ c # คุณสามารถใช้

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

ฉันได้เพิ่ม " | <(? [\ w \ s] *)> โดยเฉพาะเพื่อไฮไลต์ว่าคุณสามารถระบุอักขระใด ๆ ให้กับวลีกลุ่มได้ (ในกรณีนี้ฉันใช้<>เพื่อจัดกลุ่ม

ผลลัพธ์คือ:

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

0

ฉันมั่นใจพอสมควรว่าสิ่งนี้ไม่สามารถทำได้โดยใช้นิพจน์ทั่วไปเพียงอย่างเดียว การตรวจสอบว่ามีบางอย่างอยู่ในแท็กอื่นหรือไม่เป็นการดำเนินการแยกวิเคราะห์ ดูเหมือนว่าจะเป็นปัญหาเดียวกับการพยายามแยกวิเคราะห์ XML ด้วย regex แต่ไม่สามารถทำได้อย่างถูกต้อง คุณอาจจะได้ผลลัพธ์ที่คุณต้องการโดยใช้ regex ที่ไม่โลภซ้ำ ๆ และไม่ใช่ global ที่ตรงกับสตริงที่ยกมาจากนั้นเมื่อคุณไม่พบสิ่งอื่นใดให้แบ่งออกที่ช่องว่าง ... ซึ่งมีจำนวน ปัญหารวมถึงการติดตามลำดับดั้งเดิมของสตริงย่อยทั้งหมด ทางออกที่ดีที่สุดของคุณคือเขียนฟังก์ชันง่ายๆที่วนซ้ำบนสตริงและดึงโทเค็นที่คุณต้องการออกมา


เป็นไปได้ด้วย regex ดูตัวอย่างที่ฉันเชื่อมโยง มีรูปแบบที่แตกต่างกันเล็กน้อยเกี่ยวกับเรื่องนี้และฉันเคยเห็นคำถามที่คล้ายกันหลายข้อเกี่ยวกับ SO ซึ่งกล่าวถึงเรื่องนี้ผ่านนิพจน์ทั่วไป
เจ

1
รู้ว่าเมื่อใดที่ไม่ควรใช้ regex เป็นความรู้ที่มีประโยชน์มากขึ้นเพื่อให้สามารถสร้าง (?: (['"]) (. *?) (? <! \) (?> \\\) * \ 1 | ([ ^ \ s] +))
Rene

0

คู่รักหวังว่าการปรับแต่งที่เป็นประโยชน์สำหรับคำตอบที่ยอมรับของแจน:

(['"])((?:\\\1|.)+?)\1|([^\s"']+)
  • อนุญาตให้ใช้เครื่องหมายคำพูดที่ใช้ Escape ภายในสตริงที่ยกมา
  • หลีกเลี่ยงการทำซ้ำรูปแบบสำหรับอัญประกาศเดี่ยวและคู่ นอกจากนี้ยังช่วยลดความยุ่งยากในการเพิ่มสัญลักษณ์การอ้างอิงเพิ่มเติมหากจำเป็น (โดยเสียค่าใช้จ่ายในการจับภาพอีกหนึ่งกลุ่ม)

สิ่งนี้จะแบ่งคำที่มีเครื่องหมายอะพอสทรอฟีอยู่ในคำเช่นyou're
ออกแบบโดย Adrian

0

คุณยังสามารถลองสิ่งนี้:

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

คุณควรเพิ่มคำอธิบายว่าเหตุใดจึงควรใช้งานได้จริง ๆ - คุณยังสามารถเพิ่มโค้ดและความคิดเห็นในโค้ดได้ - ในรูปแบบปัจจุบันจะไม่ให้คำอธิบายใด ๆ ที่สามารถช่วยให้ชุมชนที่เหลือเข้าใจสิ่งที่ คุณได้แก้ / ตอบคำถาม นี่เป็นสิ่งสำคัญอย่างยิ่งสำหรับคำถามที่มีคำตอบอยู่แล้ว
ishmaelMakitla

0

ต่อไปนี้ส่งคืนอาร์เรย์ของอาร์กิวเมนต์ อาร์กิวเมนต์คือตัวแปร "คำสั่ง" ที่แบ่งออกเป็นช่องว่างเว้นแต่จะรวมอยู่ในเครื่องหมายคำพูดเดี่ยวหรือคู่ จากนั้นการจับคู่จะถูกแก้ไขเพื่อลบเครื่องหมายคำพูดเดี่ยวและคู่

using System.Text.RegularExpressions;

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

2
คุณสามารถเพิ่มคำอธิบายเล็กน้อยในคำตอบของคุณเพื่อให้คนอื่นเข้าใจได้ง่ายขึ้นหรือไม่ ตามหลักการแล้วเราต้องการหลีกเลี่ยงคำตอบแบบใช้รหัสเท่านั้น
Jaquez

0

ซับแรกโดยใช้ 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.]

อย่าแบ่งช่องว่างถ้าช่องว่างล้อมรอบด้วยเครื่องหมายคำพูดเดี่ยวหรือคู่
แบ่งที่ว่างเมื่อ 255 อักขระทางซ้ายและอักขระทั้งหมดทางด้านขวาของช่องว่างจะไม่มีเครื่องหมายคำพูดเดี่ยวหรือคู่

ดัดแปลงจากโพสต์ต้นฉบับ (จัดการเฉพาะเครื่องหมายคำพูดคู่)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.