เทียบเท่า Unicode สำหรับ \ w และ \ b ​​ในนิพจน์ทั่วไปของ Java?


126

การใช้ regex สมัยใหม่จำนวนมากตีความการ\wชวเลขคลาสอักขระเป็น "ตัวอักษรตัวเลขหรือเครื่องหมายวรรคตอนเชื่อมต่อ" (โดยปกติ: ขีดล่าง) วิธีการที่เป็นเช่น regex \w+ตรงกับคำที่ชอบhello, élève, หรือGOÄ_432gefräßig

น่าเสียดายที่ Java ไม่มี ใน Java \wจำกัด ไว้ที่[A-Za-z0-9_]. ทำให้การจับคู่คำเหมือนที่กล่าวมาข้างต้นเป็นเรื่องยากท่ามกลางปัญหาอื่น ๆ

นอกจากนี้ยังปรากฏว่า\bตัวคั่นคำตรงกับที่ที่ไม่ควร

อะไรคือสิ่งที่ถูกต้องเทียบเท่ากับ. NET-like, Unicode-Aware \wหรือ\bใน Java ทางลัดอื่นใดที่ต้อง "เขียนใหม่" เพื่อให้ Unicode-ตระหนัก


3
เรื่องสั้นทิมคือพวกเขาทุกคนต้องเขียนเพื่อให้สอดคล้องกับ Unicode ฉันยังไม่เห็นสัญญาณว่า Java 1.7 จะทำอะไรกับคุณสมบัติ Unicode ได้มากไปกว่าการเพิ่มการรองรับสคริปต์ในที่สุดแต่ก็นั่นแหละ มีบางสิ่งที่คุณไม่สามารถทำได้หากไม่มีการเข้าถึงคุณสมบัติ Unicode ที่สมบูรณ์ หากคุณยังไม่มีสคริปต์unipropsและunicharsของฉัน(และuninames ) มันเป็นเครื่องเปิดหูเปิดตาที่น่าทึ่งในทั้งหมดนี้
tchrist

หนึ่งอาจพิจารณาเพิ่มเครื่องหมายในชั้นคำ ตั้งแต่ตัวอย่าง & auml; สามารถแสดงใน Unicode ไม่ว่าจะเป็น \ u0061 \ u0308 หรือ \ u00E4
Mostowski ยุบ

3
เฮ้ทิมลองดูการอัปเดตของฉัน พวกเขาได้เพิ่มธงเพื่อให้ใช้งานได้ทั้งหมด เย่!
tchrist

คำตอบ:


240

รหัสแหล่งที่มา

รหัสแหล่งที่มาสำหรับฟังก์ชั่นการเขียนที่ผมปรึกษาด้านล่างสามารถใช้ได้ที่นี่

อัปเดตใน Java 7

Patternคลาสที่อัปเดตของ Sun สำหรับ JDK7 มีแฟล็กใหม่ที่ยอดUNICODE_CHARACTER_CLASSเยี่ยมซึ่งทำให้ทุกอย่างกลับมาใช้ได้อีกครั้ง สามารถใช้เป็นแบบฝังได้(?U)สำหรับภายในรูปแบบดังนั้นคุณสามารถใช้กับStringWrapper ของชั้นเรียนได้เช่นกัน นอกจากนี้ยังมีการแก้ไขคำจำกัดความสำหรับคุณสมบัติอื่น ๆ อีกมากมายด้วย ตอนนี้จะติดตามมาตรฐาน Unicode ทั้งในRL1.2และRL1.2aจากUTS # 18: Unicode นิพจน์ปกติ นี่เป็นการปรับปรุงที่น่าตื่นเต้นและน่าทึ่งและทีมพัฒนาต้องได้รับการยกย่องสำหรับความพยายามครั้งสำคัญนี้


ปัญหา Regex Unicode ของ Java

ปัญหากับ Java regexes คือว่า Perl 1.0 หนี charclass - ความหมาย\w, \b, \s, \dและการเติมเต็มของพวกเขา - ไม่ได้อยู่ใน Java ขยายไปถึงการทำงานร่วมกับ Unicode คนเดียวในหมู่เหล่านี้\bมีความสุขความหมายขยายบางอย่าง แต่เหล่านี้ไม่ว่าจะเป็นการแผนที่\wหรือเพื่อตัวระบุ Unicodeหรือเพื่อUnicode คุณสมบัติเส้นแบ่ง

นอกจากนี้คุณสมบัติ POSIX ใน Java สามารถเข้าถึงได้ด้วยวิธีนี้:

POSIX syntax    Java syntax

[[:Lower:]]     \p{Lower}
[[:Upper:]]     \p{Upper}
[[:ASCII:]]     \p{ASCII}
[[:Alpha:]]     \p{Alpha}
[[:Digit:]]     \p{Digit}
[[:Alnum:]]     \p{Alnum}
[[:Punct:]]     \p{Punct}
[[:Graph:]]     \p{Graph}
[[:Print:]]     \p{Print}
[[:Blank:]]     \p{Blank}
[[:Cntrl:]]     \p{Cntrl}
[[:XDigit:]]    \p{XDigit}
[[:Space:]]     \p{Space}

นี้เป็นระเบียบจริงเพราะมันหมายถึงว่าสิ่งที่ชอบAlpha, LowerและSpaceทำไม่ได้ในแผนที่ Java กับ Unicode Alphabetic, LowercaseหรือWhitespaceคุณสมบัติ นี่เป็นเรื่องที่น่ารำคาญอย่างยิ่ง การสนับสนุนคุณสมบัติ Unicode ของ Java นั้นถือเป็นการรักษาวัยก่อนวัยอย่างเคร่งครัดซึ่งฉันหมายความว่ามันไม่สนับสนุนคุณสมบัติ Unicode ที่ออกมาในทศวรรษที่ผ่านมา

การไม่สามารถพูดถึงช่องว่างได้อย่างถูกต้องเป็นเรื่องที่น่ารำคาญมาก พิจารณาตารางต่อไปนี้ สำหรับแต่ละจุดโค้ดเหล่านั้นมีทั้งคอลัมน์ J-results สำหรับ Java และคอลัมน์ P-results สำหรับ Perl หรือเอนจิ้น regex ที่ใช้ PCRE อื่น ๆ :

             Regex    001A    0085    00A0    2029
                      J  P    J  P    J  P    J  P
                \s    1  1    0  1    0  1    0  1
               \pZ    0  0    0  0    1  1    1  1
            \p{Zs}    0  0    0  0    1  1    0  0
         \p{Space}    1  1    0  1    0  1    0  1
         \p{Blank}    0  0    0  0    0  1    0  0
    \p{Whitespace}    -  1    -  1    -  1    -  1
\p{javaWhitespace}    1  -    0  -    0  -    1  -
 \p{javaSpaceChar}    0  -    0  -    1  -    1  -

เห็นมั้ย?

ผลลัพธ์ของพื้นที่สีขาว Java เกือบทุกรายการคือ ̲w̲r̲o̲n̲g̲ ตาม Unicode มันเป็นปัญหาใหญ่จริงๆ Java นั้นยุ่งเหยิงโดยให้คำตอบที่“ ผิด” ตามแนวทางปฏิบัติที่มีอยู่และตาม Unicode ด้วย แถม Java ยังไม่ให้คุณเข้าถึงคุณสมบัติ Unicode จริงด้วยซ้ำ! ในความเป็นจริง Java ไม่สนับสนุนคุณสมบัติใด ๆที่สอดคล้องกับช่องว่าง Unicode


ทางออกสำหรับปัญหาเหล่านั้นทั้งหมดและอื่น ๆ

เพื่อจัดการกับปัญหานี้และปัญหาอื่น ๆ ที่เกี่ยวข้องเมื่อวานนี้ฉันได้เขียนฟังก์ชัน Java เพื่อเขียนสตริงรูปแบบใหม่ที่เขียนค่า charclass 14 รายการเหล่านี้ใหม่:

\w \W \s \S \v \V \h \H \d \D \b \B \X \R

โดยแทนที่ด้วยสิ่งที่ใช้งานได้จริงเพื่อให้ตรงกับ Unicode ในรูปแบบที่คาดเดาได้และสอดคล้องกัน มันเป็นเพียงต้นแบบอัลฟาจากเซสชันการแฮ็กเดียว แต่ใช้งานได้อย่างสมบูรณ์

เรื่องสั้นคือรหัสของฉันเขียนใหม่ทั้ง 14 ดังต่อไปนี้:

\s => [\u0009-\u000D\u0020\u0085\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]
\S => [^\u0009-\u000D\u0020\u0085\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]

\v => [\u000A-\u000D\u0085\u2028\u2029]
\V => [^\u000A-\u000D\u0085\u2028\u2029]

\h => [\u0009\u0020\u00A0\u1680\u180E\u2000-\u200A\u202F\u205F\u3000]
\H => [^\u0009\u0020\u00A0\u1680\u180E\u2000\u2001-\u200A\u202F\u205F\u3000]

\w => [\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]
\W => [^\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]

\b => (?:(?<=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])|(?<![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]))
\B => (?:(?<=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])|(?<![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]))

\d => \p{Nd}
\D => \P{Nd}

\R => (?:(?>\u000D\u000A)|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029])

\X => (?>\PM\pM*)

สิ่งที่ต้องพิจารณา ...

  • การใช้งานที่สำหรับ\Xนิยามสิ่งUnicode ตอนนี้หมายถึงการเป็นคลัสเตอร์มรดกอักษรไม่ใช่คลัสเตอร์อักษรขยายเป็นหลังค่อนข้างซับซ้อนมากขึ้น ตอนนี้ Perl เองใช้เวอร์ชันที่น่าสนใจกว่า แต่เวอร์ชันเก่ายังคงใช้งานได้อย่างสมบูรณ์แบบสำหรับสถานการณ์ที่พบบ่อยที่สุด แก้ไข:ดูภาคผนวกที่ด้านล่าง

  • สิ่งที่ต้องทำ\dขึ้นอยู่กับเจตนาของคุณ แต่ค่าเริ่มต้นคือนิยาม Uniode ผมสามารถมองเห็นคนที่ไม่เคยต้องการ\p{Nd}แต่บางครั้งทั้งสองหรือ[0-9]\pN

  • นิยามขอบเขตทั้งสอง\bและ\Bถูกเขียนขึ้นโดยเฉพาะเพื่อใช้\wนิยาม

  • ว่า\wคำนิยามกว้างมากเกินไปเพราะมันคว้าตัวอักษร parenned ไม่ได้เป็นเพียงคนที่วงกลม Other_AlphabeticคุณสมบัติUnicode ไม่พร้อมใช้งานจนกว่า JDK7 นั่นคือสิ่งที่ดีที่สุดที่คุณสามารถทำได้


การสำรวจขอบเขต

เขตแดนมีปัญหานับตั้งแต่ Larry กำแพงประกาศเกียรติคุณแรก\bและ\Bไวยากรณ์สำหรับการพูดคุยเกี่ยวกับพวกเขาสำหรับ Perl 1.0 ย้อนกลับไปในปี 1987 กุญแจสำคัญในการทำความเข้าใจวิธีการ\bและ\Bการทำงานของทั้งสองคือการปัดเป่าสองตำนานแพร่หลายเกี่ยวกับพวกเขา:

  1. พวกเขาจะไม่เคยมองสำหรับ\wตัวอักษรคำไม่เคยสำหรับอักขระที่ไม่ใช่คำพูด
  2. พวกเขาไม่ได้มองหาขอบของสตริงโดยเฉพาะ

\bหมายถึงเขตแดน:

    IF does follow word
        THEN doesn't precede word
    ELSIF doesn't follow word
        THEN does precede word

และทั้งหมดนี้ถูกกำหนดอย่างตรงไปตรงมาอย่างสมบูรณ์แบบว่า:

  • ต่อไปนี้คำ(?<=\w)มี
  • คำแจ๋ว(?=\w)คือ
  • ไม่ปฏิบัติตามคำ(?<!\w)มี
  • ไม่ได้คำ Precede(?!\w)คือ

ดังนั้นนับตั้งแต่IF-THENมีการเข้ารหัสเป็นand เอ็ดร่วมกันABใน regexes การorเป็นX|Yและเพราะandเป็นที่สูงขึ้นในลำดับความสำคัญกว่าที่เป็นเพียงor AB|CDดังนั้นทุก\bสิ่งที่หมายถึงเขตแดนสามารถถูกแทนที่ได้อย่างปลอดภัยด้วย:

    (?:(?<=\w)(?!\w)|(?<!\w)(?=\w))

ด้วยการ\wกำหนดด้วยวิธีที่เหมาะสม

(คุณอาจคิดว่ามันแปลกที่ส่วนประกอบAและCส่วนตรงข้ามกันในโลกที่สมบูรณ์แบบคุณควรจะเขียนสิ่งAB|Dนั้นได้ แต่ในขณะที่ฉันกำลังไล่ตามความขัดแย้งในการกีดกันซึ่งกันและกันในคุณสมบัติของ Unicode ซึ่งฉันคิดว่าฉันได้ดูแล แต่ฉันทิ้งเงื่อนไขสองชั้นไว้ในขอบเขตในกรณีนี้นอกจากนี้ยังทำให้ขยายได้มากขึ้นหากคุณได้รับแนวคิดเพิ่มเติมในภายหลัง)

สำหรับสิ่งที่\Bไม่มีขอบเขตตรรกะคือ:

    IF does follow word
        THEN does precede word
    ELSIF doesn't follow word
        THEN doesn't precede word

อนุญาตให้\Bแทนที่อินสแตนซ์ทั้งหมดด้วย:

    (?:(?<=\w)(?=\w)|(?<!\w)(?!\w))

นี่คือวิธี\bและ\Bพฤติกรรมจริงๆ รูปแบบที่เทียบเท่ากันสำหรับพวกเขาคือ

  • \bโดยใช้((IF)THEN|ELSE)โครงสร้างคือ(?(?<=\w)(?!\w)|(?=\w))
  • \Bโดยใช้((IF)THEN|ELSE)โครงสร้างคือ(?(?=\w)(?<=\w)|(?<!\w))

แต่เวอร์ชันที่มีก็AB|CDใช้ได้ดีโดยเฉพาะอย่างยิ่งหากคุณไม่มีรูปแบบเงื่อนไขในภาษา regex ของคุณเช่น Java ☹

ฉันได้ตรวจสอบพฤติกรรมของขอบเขตโดยใช้คำจำกัดความที่เท่ากันทั้งสามแล้วกับชุดทดสอบที่ตรวจสอบการจับคู่ 110,385,408 รายการต่อการวิ่งและฉันได้เรียกใช้การกำหนดค่าข้อมูลที่แตกต่างกันหลายสิบรายการตาม:

     0 ..     7F    the ASCII range
    80 ..     FF    the non-ASCII Latin1 range
   100 ..   FFFF    the non-Latin1 BMP (Basic Multilingual Plane) range
 10000 .. 10FFFF    the non-BMP portion of Unicode (the "astral" planes)

อย่างไรก็ตามผู้คนมักต้องการขอบเขตที่แตกต่างกัน พวกเขาต้องการบางสิ่งที่เป็นช่องว่างและตระหนักถึงขอบของสตริง:

  • ขอบซ้าย เป็น(?:(?<=^)|(?<=\s))
  • ขอบขวาเป็น(?=$|\s)

แก้ไข Java ด้วย Java

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

นอกจากนี้ยังช่วยให้คุณสามารถระบุอักขระ Unicode ในจุดโค้ดตรรกะไม่ใช่ในตัวแทน UTF-16 ที่งี่เง่า มันยากที่จะเอาชนะว่ามันสำคัญแค่ไหน! และนั่นเป็นเพียงการขยายสตริง

สำหรับ regex charclass ทดแทนที่ทำให้ charclass ใน Java ของคุณ regexes ที่สุดทำงานบน Unicode, และการทำงานอย่างถูกต้อง คว้าแหล่งที่มาเต็มรูปแบบจากที่นี่ คุณสามารถทำได้ตามที่คุณต้องการแน่นอน หากคุณแก้ไขปัญหานี้ฉันชอบที่จะได้ยินเรื่องนี้ แต่คุณไม่จำเป็นต้องทำ ค่อนข้างสั้น ความกล้าของฟังก์ชันการเขียน regex หลักนั้นง่ายมาก:

switch (code_point) {

    case 'b':  newstr.append(boundary);
               break; /* switch */
    case 'B':  newstr.append(not_boundary);
               break; /* switch */

    case 'd':  newstr.append(digits_charclass);
               break; /* switch */
    case 'D':  newstr.append(not_digits_charclass);
               break; /* switch */

    case 'h':  newstr.append(horizontal_whitespace_charclass);
               break; /* switch */
    case 'H':  newstr.append(not_horizontal_whitespace_charclass);
               break; /* switch */

    case 'v':  newstr.append(vertical_whitespace_charclass);
               break; /* switch */
    case 'V':  newstr.append(not_vertical_whitespace_charclass);
               break; /* switch */

    case 'R':  newstr.append(linebreak);
               break; /* switch */

    case 's':  newstr.append(whitespace_charclass);
               break; /* switch */
    case 'S':  newstr.append(not_whitespace_charclass);
               break; /* switch */

    case 'w':  newstr.append(identifier_charclass);
               break; /* switch */
    case 'W':  newstr.append(not_identifier_charclass);
               break; /* switch */

    case 'X':  newstr.append(legacy_grapheme_cluster);
               break; /* switch */

    default:   newstr.append('\\');
               newstr.append(Character.toChars(code_point));
               break; /* switch */

}
saw_backslash = false;

อย่างไรก็ตามรหัสนั้นเป็นเพียงรุ่นอัลฟ่าสิ่งที่ฉันแฮ็กในช่วงสุดสัปดาห์ มันจะไม่อยู่อย่างนั้น

สำหรับเบต้าฉันตั้งใจจะ:

  • พับการทำสำเนารหัสเข้าด้วยกัน

  • จัดเตรียมอินเทอร์เฟซที่ชัดเจนขึ้นเกี่ยวกับการหลีกเลี่ยงสตริงที่ไม่ใช้ Escape เทียบกับการเพิ่มการหลบหนี regex

  • ให้ความยืดหยุ่นในการ\dขยายตัวและอาจเป็นไฟล์\b

  • จัดหาวิธีการอำนวยความสะดวกที่จัดการการหมุนและเรียก Pattern.compile หรือ String.matches หรืออะไรก็ได้สำหรับคุณ

สำหรับรุ่นที่ใช้งานจริงควรมี javadoc และชุดทดสอบ JUnit ฉันอาจรวม gigatester แต่ไม่ได้เขียนเป็นการทดสอบ JUnit


ภาคผนวก

ฉันมีข่าวดีและข่าวร้าย

ข่าวดีก็คือที่ผมได้ในขณะนี้มีมากประมาณใกล้เคียงกับคลัสเตอร์อักษรขยาย\Xการใช้งานสำหรับการปรับปรุงให้ดีขึ้น

ข่าวร้าย☺คือรูปแบบนั้นคือ:

(?:(?:\u000D\u000A)|(?:[\u0E40\u0E41\u0E42\u0E43\u0E44\u0EC0\u0EC1\u0EC2\u0EC3\u0EC4\uAAB5\uAAB6\uAAB9\uAABB\uAABC]*(?:[\u1100-\u115F\uA960-\uA97C]+|([\u1100-\u115F\uA960-\uA97C]*((?:[[\u1160-\u11A2\uD7B0-\uD7C6][\uAC00\uAC1C\uAC38]][\u1160-\u11A2\uD7B0-\uD7C6]*|[\uAC01\uAC02\uAC03\uAC04])[\u11A8-\u11F9\uD7CB-\uD7FB]*))|[\u11A8-\u11F9\uD7CB-\uD7FB]+|[^[\p{Zl}\p{Zp}\p{Cc}\p{Cf}&&[^\u000D\u000A\u200C\u200D]]\u000D\u000A])[[\p{Mn}\p{Me}\u200C\u200D\u0488\u0489\u20DD\u20DE\u20DF\u20E0\u20E2\u20E3\u20E4\uA670\uA671\uA672\uFF9E\uFF9F][\p{Mc}\u0E30\u0E32\u0E33\u0E45\u0EB0\u0EB2\u0EB3]]*)|(?s:.))

ซึ่งใน Java คุณจะเขียนเป็น:

String extended_grapheme_cluster = "(?:(?:\\u000D\\u000A)|(?:[\\u0E40\\u0E41\\u0E42\\u0E43\\u0E44\\u0EC0\\u0EC1\\u0EC2\\u0EC3\\u0EC4\\uAAB5\\uAAB6\\uAAB9\\uAABB\\uAABC]*(?:[\\u1100-\\u115F\\uA960-\\uA97C]+|([\\u1100-\\u115F\\uA960-\\uA97C]*((?:[[\\u1160-\\u11A2\\uD7B0-\\uD7C6][\\uAC00\\uAC1C\\uAC38]][\\u1160-\\u11A2\\uD7B0-\\uD7C6]*|[\\uAC01\\uAC02\\uAC03\\uAC04])[\\u11A8-\\u11F9\\uD7CB-\\uD7FB]*))|[\\u11A8-\\u11F9\\uD7CB-\\uD7FB]+|[^[\\p{Zl}\\p{Zp}\\p{Cc}\\p{Cf}&&[^\\u000D\\u000A\\u200C\\u200D]]\\u000D\\u000A])[[\\p{Mn}\\p{Me}\\u200C\\u200D\\u0488\\u0489\\u20DD\\u20DE\\u20DF\\u20E0\\u20E2\\u20E3\\u20E4\\uA670\\uA671\\uA672\\uFF9E\\uFF9F][\\p{Mc}\\u0E30\\u0E32\\u0E33\\u0E45\\u0EB0\\u0EB2\\u0EB3]]*)|(?s:.))";

¡Tschüß!


10
สิ่งนี้ช่างมหัศจรรย์. ขอบคุณมาก.
Tim Pietzcker

9
พระคริสต์นั่นคือคำตอบที่กระจ่างแจ้ง ฉันไม่ได้รับข้อมูลอ้างอิงของ Jon Skeet เท่านั้น เขาจะทำอย่างไรกับเรื่องนี้?
BalusC

12
@BalusC: ก่อนหน้านี้อ้างถึงจอนว่าเขาจะให้ฉันตอบคำถาม แต่โปรดอย่าวางtใน @tchrist มันอาจจะไปที่หัวของฉัน :)
tchrist

3
คุณเคยคิดที่จะเพิ่มสิ่งนี้ลงใน OpenJDK หรือไม่?
Martijn Verburg

2
@Martijn: ฉันไม่ได้ไม่; ฉันไม่รู้ว่ามัน "เปิด" :) แต่ฉันมีความคิดที่จะปล่อยมันออกมาในรูปแบบที่เป็นทางการมากกว่านี้ คนอื่น ๆ ในแผนกของฉันต้องการเห็นว่าเสร็จแล้ว (พร้อมใบอนุญาตโอเพนซอร์สบางประเภทอาจเป็น BSD หรือ ASL) ฉันอาจจะเปลี่ยน API จากสิ่งที่อยู่ในต้นแบบอัลฟ่านี้ล้างโค้ด ฯลฯ แต่มันช่วยเราได้อย่างมากและเราคิดว่ามันจะช่วยคนอื่นด้วย ฉันหวังว่า Sun จะทำอะไรบางอย่างเกี่ยวกับห้องสมุดของพวกเขา แต่ Oracle กลับไม่มั่นใจ
tchrist

15

มันโชคร้ายจริงๆที่\wไม่ได้ผล วิธีแก้ปัญหาที่เสนอ\p{Alpha}ก็ไม่ได้ผลสำหรับฉันเช่นกัน

ดูเหมือนว่าจะ[\p{L}]จับตัวอักษร Unicode ทั้งหมด ดังนั้น Unicode เทียบเท่าของที่ควรจะเป็น\w[\p{L}\p{Digit}_]


แต่\wยังตรงกับตัวเลขและอื่น ๆ ฉันคิดว่าสำหรับตัวอักษร\p{L}ก็น่าจะใช้ได้
Tim Pietzcker

คุณถูก. \p{L}ก็เพียงพอแล้ว ฉันคิดว่าตัวอักษรเท่านั้นที่เป็นปัญหา [\p{L}\p{Digit}_]ควรจับอักขระที่เป็นตัวเลขและตัวอักษรทั้งหมดรวมทั้งขีดล่าง
musiKk

@MusicKk: ดูคำตอบของฉันสำหรับโซลูชันที่สมบูรณ์ที่ช่วยให้คุณสามารถเขียนรูปแบบของคุณได้ตามปกติ แต่จากนั้นส่งผ่านฟังก์ชันที่แก้ไข lacunae ที่อ้าปากค้างของ Java เพื่อให้ทำงานได้อย่างถูกต้องบน Unicode
tchrist

ไม่\wถูกกำหนดโดย Unicode ว่ากว้างกว่าแค่\pLตัวเลขและเลข ASCII ซึ่งเป็นสิ่งที่โง่เขลาทั้งหมด คุณต้องเขียน[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]ถ้าคุณต้องการทราบ Unicode \wสำหรับ Java - หรือคุณก็สามารถใช้ของฉันunicode_charclassฟังก์ชั่นจากที่นี่ ขออภัย!
tchrist

1
@ ทิมใช่สำหรับจดหมาย\pLใช้งานได้ (คุณไม่จำเป็นต้องใช้อุปกรณ์ประกอบฉากตัวอักษรเดียว) อย่างไรก็ตามคุณไม่ต้องการเช่นนั้นเนื่องจากคุณต้องค่อนข้างระมัดระวังว่าการจับคู่ของคุณจะไม่ได้รับคำตอบที่แตกต่างกันเพียงเพราะข้อมูลของคุณอยู่ใน Unicode Normalization Form D (aka NFD หมายถึงการสลายตัวแบบบัญญัติ) เมื่อเทียบกับการใช้ NFC (NFD ตามด้วย canonical องค์ประกอบ ). ตัวอย่างคือว่าจุดรหัส U + E9 ( "é") เป็น\pLในรูปแบบ NFC แต่รูปแบบของมันจะกลายเป็น NFD U + 65.301 \pL\pMดังนั้นการแข่งขัน คุณสามารถบอกรับรอบนี้ด้วย\X: (?:(?=\pL)\X)แต่คุณจะต้องรุ่นของฉันว่าสำหรับ Java :(
tchrist

7

ใน Java \wและ\dไม่รู้จัก Unicode จับคู่เฉพาะอักขระ ASCII [A-Za-z0-9_]และ[0-9]. เช่นเดียวกันกับ\p{Alpha}เพื่อน ๆ ("คลาสอักขระ" ของ POSIX ที่อิงตามควรจะไวต่อโลแคล แต่ใน Java พวกเขาเคยจับคู่อักขระ ASCII เท่านั้น) หากคุณต้องการจับคู่ "อักขระคำ" ของ Unicode คุณจะต้องสะกดออกเช่น[\pL\p{Mn}\p{Nd}\p{Pc}]สำหรับตัวอักษรตัวแก้ไขแบบไม่เว้นวรรค (เน้นเสียง) ตัวเลขทศนิยมและเครื่องหมายวรรคตอนเชื่อมต่อ

อย่างไรก็ตาม Java ของ\b เป็น Unicode ที่เข้าใจได้ มันใช้Character.isLetterOrDigit(ch)และตรวจสอบตัวอักษรเน้นเสียงเช่นกัน แต่อักขระ "เชื่อมต่อเครื่องหมายวรรคตอน" เดียวที่จำได้คือขีดล่าง แก้ไข:เมื่อฉันลองโค้ดตัวอย่างของคุณมันจะพิมพ์ออกมา""และélève"ตามที่ควร ( ดูใน ideone.com )


ฉันขอโทษ Alan แต่คุณไม่สามารถพูดได้จริง ๆ ว่า Java \bเป็น Unicode ที่เข้าใจได้ มันทำให้เกิดความผิดพลาดมากมาย "\u2163=", "\u24e7="และ"\u0301="ทุกรูปแบบล้มเหลวในการจับคู่"\\b="ใน Java แต่ควรไป - ตามที่perl -le 'print /\b=/ || 0 for "\x{2163}=", "\x{24e7}=", "\x{301}="'เผยให้เห็น อย่างไรก็ตามหาก (และเฉพาะในกรณีที่) คุณสลับขอบเขตคำในเวอร์ชันของฉันแทนเนทีฟ\bใน Java สิ่งเหล่านั้นก็ทำงานใน Java ได้เช่นกัน
tchrist

@tchrist: ฉันไม่ได้แสดงความคิดเห็นเกี่ยวกับ\bความถูกต้องของมันเพียงแค่ชี้ให้เห็นว่ามันทำงานบนอักขระ Unicode (ที่ใช้งานใน Java) ไม่ใช่แค่ ASCII เหมือน\wและเพื่อน ๆ แต่มันไม่ทำงานอย่างถูกต้องด้วยความเคารพต่อเมื่อตัวละครที่ถูกจับคู่กับตัวละครฐานเช่นเดียวกับใน\u0301 e\u0301=และฉันไม่มั่นใจว่า Java ผิดในกรณีนี้ เครื่องหมายรวมจะถือว่าเป็นอักขระคำได้อย่างไรเว้นแต่ว่าจะเป็นส่วนหนึ่งของคลัสเตอร์ grapheme ที่มีตัวอักษร
Alan Moore

3
@Alan นี่คือสิ่งที่ถูกล้างเมื่อ Unicode ชี้แจงคลัสเตอร์ grapheme โดยการพูดคุยเกี่ยวกับคลัสเตอร์ grapheme แบบขยายและแบบเดิม คำจำกัดความเดิมของคลัสเตอร์ grapheme ซึ่ง\Xย่อมาจาก non-mark ตามด้วยจำนวนเครื่องหมายใด ๆ เป็นปัญหาเพราะคุณควรจะสามารถอธิบายไฟล์ทั้งหมดว่าตรงกัน/^(\X*\R)*\R?$/แต่คุณทำไม่ได้ถ้าคุณมี\pMจุดเริ่มต้นของ ไฟล์หรือแม้แต่บรรทัด ดังนั้นพวกเขาจึงพยายามให้มันจับคู่อักขระอย่างน้อยหนึ่งตัวเสมอ มันทำมาตลอด แต่ตอนนี้มันทำให้รูปแบบข้างต้นใช้ได้ผล […ต่อ…]
tchrist

2
@Alan มันทำอันตรายมากกว่าดีที่เนทีฟของ Java \bนั้นรับรู้ Unicode บางส่วน พิจารณาการจับคู่สตริงกับรูปแบบ"élève" \b(\w+)\bเห็นปัญหาไหม
tchrist

1
@tchrist: ใช่โดยไม่มีคำว่าขอบเขต\w+พบสองรายการที่ตรงกันlและveซึ่งแย่พอ แต่ด้วยคำว่าขอบเขตมันไม่พบอะไรเลยเพราะ\bจดจำéและèเป็นอักขระคำ อย่างน้อยที่สุด\bและ\wควรยอมรับว่าอะไรเป็นตัวอักษรและอะไรที่ไม่ใช่
Alan Moore
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.