เท่าที่ฉันรู้กลุ่มการปรับสมดุลเป็นลักษณะเฉพาะของรสชาติ regex ของ. NET
นอกเหนือ: กลุ่มที่ทำซ้ำ
ขั้นแรกคุณต้องรู้ว่า. NET เป็น (อีกครั้งเท่าที่ฉันรู้) รสชาติ regex เดียวที่ช่วยให้คุณเข้าถึงการจับภาพหลายกลุ่มของกลุ่มการจับภาพเดียว (ไม่ใช่ในการอ้างอิงย้อนกลับ แต่หลังจากการจับคู่เสร็จสิ้น)
เพื่อแสดงสิ่งนี้เป็นตัวอย่างให้พิจารณารูปแบบ
(.)+
"abcd"
และสตริง
ในรสชาติ regex อื่น ๆ การจับกลุ่ม1
จะให้ผลลัพธ์เดียว: d
(โปรดทราบว่าการจับคู่ทั้งหมดจะเป็นไปabcd
ตามที่คาดไว้) เนื่องจากการใช้กลุ่มการบันทึกใหม่ทุกครั้งจะเขียนทับการจับภาพก่อนหน้า
ในทางกลับกัน. NET จะจำได้ทั้งหมด และมันก็ทำในกอง หลังจากจับคู่ regex ข้างต้นเช่น
Match m = new Regex(@"(.)+").Match("abcd");
คุณจะพบว่า
m.Groups[1].Captures
เป็นCaptureCollection
องค์ประกอบที่สอดคล้องกับการจับภาพทั้งสี่
0: "a"
1: "b"
2: "c"
3: "d"
โดยที่ตัวเลขเป็นดัชนีในCaptureCollection
. ดังนั้นโดยทั่วไปทุกครั้งที่มีการใช้กลุ่มอีกครั้งการจับภาพใหม่จะถูกผลักเข้าไปในสแต็ก
จะน่าสนใจยิ่งขึ้นหากเราใช้กลุ่มการจับภาพที่ตั้งชื่อ เนื่องจาก. NET อนุญาตให้ใช้ชื่อเดิมซ้ำเราจึงสามารถเขียน regex ได้เช่น
(?<word>\w+)\W+(?<word>\w+)
เพื่อจับคำสองคำให้อยู่ในกลุ่มเดียวกัน อีกครั้งทุกครั้งที่พบกลุ่มที่มีชื่อเฉพาะการจับภาพจะถูกผลักเข้าไปในสแต็ก ดังนั้นการใช้ regex นี้กับอินพุต"foo bar"
และการตรวจสอบ
m.Groups["word"].Captures
เราพบภาพสองภาพ
0: "foo"
1: "bar"
สิ่งนี้ช่วยให้เราสามารถผลักสิ่งต่างๆไปยังกองเดียวจากส่วนต่างๆของนิพจน์ได้ แต่ยังคงเป็นเพียงคุณลักษณะ .NET CaptureCollection
ของความสามารถในการติดตามจับหลายที่มีการระบุไว้ในนี้ แต่ผมบอกว่าคอลเลกชันนี้เป็นสแต็ค ดังนั้นเราจึงสามารถปรากฏ สิ่งจากมันได้หรือไม่
ป้อน: การปรับสมดุลกลุ่ม
ปรากฎว่าเราทำได้ ถ้าเราใช้กลุ่มที่ชอบ(?<-word>...)
การจับภาพสุดท้ายจะถูกดึงออกมาจากสแต็กword
หากนิพจน์ย่อย...
ตรงกัน ดังนั้นถ้าเราเปลี่ยนนิพจน์ก่อนหน้าเป็น
(?<word>\w+)\W+(?<-word>\w+)
จากนั้นกลุ่มที่สองจะปรากฏการจับภาพของกลุ่มแรกและเราจะได้รับช่องว่างCaptureCollection
ในตอนท้าย แน่นอนว่าตัวอย่างนี้ไม่มีประโยชน์เลย
แต่ยังมีรายละเอียดอีกอย่างหนึ่งของไวยากรณ์ลบ: หากสแต็กว่างเปล่าอยู่แล้วกลุ่มจะล้มเหลว (โดยไม่คำนึงถึงรูปแบบย่อย) เราสามารถใช้ประโยชน์จากพฤติกรรมนี้เพื่อนับระดับการซ้อน - และนี่คือที่มาของกลุ่มการปรับสมดุลชื่อ (และจุดที่น่าสนใจ) สมมติว่าเราต้องการจับคู่สตริงที่อยู่ในวงเล็บอย่างถูกต้อง เราดันวงเล็บเปิดแต่ละอันบนสแต็กและใส่หนึ่งการบันทึกสำหรับแต่ละวงเล็บปิด หากเราพบวงเล็บปิดหนึ่งรายการมากเกินไปมันจะพยายามเปิดสแต็กว่างและทำให้รูปแบบล้มเหลว:
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*$
ดังนั้นเราจึงมีสามทางเลือกในการทำซ้ำ ทางเลือกแรกใช้ทุกอย่างที่ไม่ใช่วงเล็บ ทางเลือกที่สองจะจับคู่(
s ในขณะที่ผลักมันลงบนสแต็ก ทางเลือกที่สามตรงกับ)
s ในขณะที่ดึงองค์ประกอบจากสแต็ก (ถ้าเป็นไปได้!)
หมายเหตุ:เพื่อชี้แจงเราเพียงตรวจสอบว่าไม่มีวงเล็บที่ไม่ตรงกัน! ซึ่งหมายความว่าสตริงที่ไม่มีวงเล็บจะตรงกันเนื่องจากยังคงมีความถูกต้องทางไวยากรณ์ (ในบางไวยากรณ์ที่คุณต้องใช้วงเล็บเพื่อให้ตรงกัน) หากคุณต้องการตรวจสอบให้แน่ใจว่ามีวงเล็บอย่างน้อยหนึ่งชุดเพียงแค่เพิ่ม lookahead (?=.*[(])
หลังไฟล์^
.
แม้ว่ารูปแบบนี้จะไม่สมบูรณ์แบบ (หรือถูกต้องทั้งหมด)
ตอนจบ: รูปแบบตามเงื่อนไข
มีการจับอีกหนึ่งรายการ: สิ่งนี้ไม่แน่ใจว่าสแต็กว่างเปล่าที่ส่วนท้ายของสตริง (ดังนั้นจึง(foo(bar)
ใช้ได้) .NET (และรสชาติอื่น ๆ อีกมากมาย) มีอีกหนึ่งโครงสร้างที่ช่วยเราได้นั่นคือรูปแบบเงื่อนไข ไวยากรณ์ทั่วไปคือ
(?(condition)truePattern|falsePattern)
โดยที่falsePattern
เป็นทางเลือก - หากละเว้นกรณีเท็จจะจับคู่กันเสมอ เงื่อนไขอาจเป็นรูปแบบหรือชื่อของกลุ่มการจับภาพ ผมจะเน้นไปที่กรณีหลังตรงนี้ หากเป็นชื่อของกลุ่มการจับภาพระบบtruePattern
จะใช้ก็ต่อเมื่อกองการจับภาพสำหรับกลุ่มนั้นไม่ว่างเปล่า นั่นคือรูปแบบที่มีเงื่อนไขเช่น(?(name)yes|no)
อ่าน "ถ้าname
มีการจับคู่และจับบางสิ่งบางอย่าง (ที่ยังคงเป็นในกอง) รูปแบบการใช้งานyes
อย่างอื่นใช้รูปแบบno
"
ดังนั้นในตอนท้ายของรูปแบบด้านบนของเราเราสามารถเพิ่มบางสิ่ง(?(Open)failPattern)
ที่ทำให้รูปแบบทั้งหมดล้มเหลวได้ถ้าOpen
-stack ไม่ว่างเปล่า สิ่งที่ง่ายที่สุดในการทำให้รูปแบบล้มเหลวโดยไม่มีเงื่อนไขคือ(?!)
(การมองเชิงลบที่ว่างเปล่า) ดังนั้นเราจึงมีรูปแบบสุดท้ายของเรา:
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))$
โปรดทราบว่าไวยากรณ์แบบมีเงื่อนไขนี้ไม่มีส่วนเกี่ยวข้องกับการปรับสมดุลกลุ่ม แต่จำเป็นต้องควบคุมพลังทั้งหมดของพวกเขา
จากที่นี่ท้องฟ้ามีขีด จำกัด การใช้งานที่ซับซ้อนมากเป็นไปได้และมี gotcha บางตัวเมื่อใช้ร่วมกับคุณสมบัติอื่น ๆ . NET-Regex เช่นรูปลักษณ์ที่มีความยาวผันแปรได้ ( ซึ่งฉันต้องเรียนรู้วิธีที่ยากลำบากด้วยตัวเอง ) อย่างไรก็ตามคำถามหลักคือรหัสของคุณยังคงสามารถรักษาได้เมื่อใช้คุณสมบัติเหล่านี้หรือไม่? คุณต้องจัดทำเอกสารเป็นอย่างดีและต้องแน่ใจว่าทุกคนที่ทำงานกับมันรับทราบถึงคุณสมบัติเหล่านี้ด้วย มิฉะนั้นคุณอาจจะดีกว่าเพียงแค่เดินสตริงด้วยตนเองทีละอักขระและนับระดับการซ้อนกันเป็นจำนวนเต็ม
ภาคผนวก: (?<A-B>...)
ไวยากรณ์คืออะไร?
เครดิตสำหรับส่วนนี้ไปที่ Kobi (ดูคำตอบด้านล่างสำหรับรายละเอียดเพิ่มเติม)
จากทั้งหมดข้างต้นเราสามารถตรวจสอบความถูกต้องว่าสตริงได้รับการจัดวงเล็บอย่างถูกต้อง แต่มันจะมีประโยชน์กว่านี้มากถ้าเราสามารถจับภาพ (ซ้อนกัน) สำหรับเนื้อหาทั้งหมดในวงเล็บเหล่านั้นได้ แน่นอนว่าเราจำวงเล็บเปิดและปิดในสแต็กการดักจับแยกต่างหากที่ไม่ได้ว่างเปล่าจากนั้นทำการแยกสตริงย่อยตามตำแหน่งในขั้นตอนแยกต่างหาก
แต่. NET มีคุณสมบัติอำนวยความสะดวกอีกอย่างหนึ่งที่นี่: ถ้าเราใช้(?<A-B>subPattern)
ไม่เพียง แต่การจับภาพที่โผล่ออกมาจากสแต็B
กเท่านั้น แต่ยังรวมถึงทุกอย่างระหว่างการจับภาพที่โผล่ขึ้นมาB
และกลุ่มปัจจุบันนี้จะถูกผลักไปยังสแต็A
ก ดังนั้นหากเราใช้กลุ่มแบบนี้สำหรับวงเล็บปิดในขณะที่สร้างระดับการซ้อนจากสแต็กเราสามารถดันเนื้อหาของทั้งคู่ไปยังสแต็กอื่นได้:
^(?:[^()]|(?<Open>[(])|(?<Content-Open>[)]))*(?(Open)(?!))$
Kobi ให้Live-Demo นี้ในคำตอบของเขา
ดังนั้นการรวบรวมสิ่งเหล่านี้ทั้งหมดเข้าด้วยกันเราสามารถ:
- จำการจับภาพจำนวนมากโดยพลการ
- ตรวจสอบโครงสร้างที่ซ้อนกัน
- จับแต่ละระดับการซ้อน
ทั้งหมดในนิพจน์ทั่วไปเดียว ถ้ามันไม่น่าตื่นเต้น ... ;)
แหล่งข้อมูลบางอย่างที่ฉันพบว่ามีประโยชน์เมื่อได้เรียนรู้ครั้งแรก: