ตั้งชื่อกลุ่มการดักจับใน JavaScript regex หรือไม่


208

เท่าที่ฉันรู้ไม่มีสิ่งเช่นชื่อกลุ่มจับภาพใน JavaScript วิธีอื่นในการรับฟังก์ชั่นที่คล้ายกันคืออะไร


1
กลุ่มการจับภาพในจาวาสคริปต์คือตามจำนวน .. $ 1 เป็นกลุ่มที่ถูกจับเป็นครั้งแรก, $ 2, $ 3 ... ถึง $ 99 แต่ดูเหมือนว่าคุณต้องการสิ่งอื่น - ซึ่งไม่มีอยู่จริง
Erik

24
@Erik คุณกำลังพูดถึงกลุ่มการจับภาพหมายเลขการพูดคุยของ OP เกี่ยวกับกลุ่มการจับกุมที่มีชื่อ มีอยู่ แต่เราต้องการทราบว่ามีการสนับสนุนใน JS หรือไม่
Alba Mendez

4
มีข้อเสนอที่จะนำชื่อ regex มาไว้ใน JavaScriptแต่อาจเป็นเวลาหลายปีก่อนที่เราจะเห็นว่าหากเราเคยทำ
fregante

Firefox ลงโทษฉันที่พยายามใช้กลุ่มการจับกุมที่มีชื่อบนเว็บไซต์ ... ความผิดของตัวเองจริงๆ stackoverflow.com/a/58221254/782034
Nick Grealy

คำตอบ:


134

ECMAScript 2018 แนะนำกลุ่มการจับภาพชื่อไว้ใน JavaScript regexes

ตัวอย่าง:

  const auth = 'Bearer AUTHORIZATION_TOKEN'
  const { groups: { token } } = /Bearer (?<token>[^ $]*)/.exec(auth)
  console.log(token) // "Prints AUTHORIZATION_TOKEN"

หากคุณต้องการสนับสนุนเบราว์เซอร์รุ่นเก่าคุณสามารถทำทุกอย่างด้วยกลุ่มการจับภาพปกติ (ตัวเลข) ที่คุณสามารถทำได้ด้วยกลุ่มการจับกุมที่ระบุชื่อคุณเพียงแค่ต้องติดตามตัวเลข - ซึ่งอาจยุ่งยากหากลำดับการจับกลุ่มใน การเปลี่ยนแปลง regex

มีข้อดีเพียง "โครงสร้าง" เพียงสองอย่างของกลุ่มจับชื่อที่ฉันสามารถนึกได้:

  1. ในบางรสชาติของ regex (. NET และ JGSoft เท่าที่ฉันรู้) คุณสามารถใช้ชื่อเดียวกันสำหรับกลุ่มต่าง ๆ ใน regex ของคุณ ( ดูที่นี่สำหรับตัวอย่างที่มีความสำคัญ ) แต่รสชาติของ regex ส่วนใหญ่ไม่รองรับฟังก์ชั่นนี้อยู่ดี

  2. หากคุณจำเป็นต้องอ้างถึงกลุ่มการจับภาพหมายเลขในสถานการณ์ที่พวกเขาล้อมรอบด้วยตัวเลขคุณจะได้รับปัญหา สมมติว่าคุณต้องการที่จะเพิ่มศูนย์การหลักและดังนั้นจึงต้องการแทนที่ด้วย(\d) $10ใน JavaScript นี้การทำงานจะ (ตราบเท่าที่คุณมีน้อยกว่า 10 จับภาพกลุ่มใน regex ของคุณ) แต่ Perl จะคิดว่าคุณกำลังมองหาจำนวน backreference 10แทนหมายเลขตามด้วย1 0ใน Perl คุณสามารถใช้${1}0ในกรณีนี้

นอกเหนือจากนั้นกลุ่มจับภาพที่มีชื่อเป็นเพียง "น้ำตาลทราย" ช่วยในการใช้กลุ่มที่จับภาพเฉพาะเมื่อคุณต้องการจริงๆและใช้กลุ่มที่ไม่ได้รับการบันทึก(?:...)ในสถานการณ์อื่น ๆ ทั้งหมด

ปัญหาที่ใหญ่กว่า (ในความคิดของฉัน) กับ JavaScript คือมันไม่สนับสนุน regexes verbose ซึ่งจะทำให้การสร้างการแสดงออกปกติที่ซับซ้อนอ่านง่ายขึ้นมากขึ้น

ห้องสมุด XRegExp ของ Steve Levithanแก้ปัญหาเหล่านี้ได้


5
หลายรสชาติให้ใช้ชื่อกลุ่มการจับภาพเดียวกันหลาย ๆ ครั้งใน regex แต่มีเพียง. NET และ Perl 5.10+ เท่านั้นที่ทำให้สิ่งนี้มีประโยชน์อย่างยิ่งโดยการเก็บค่าที่ถูกจับโดยกลุ่มสุดท้ายของชื่อที่เข้าร่วมในการแข่งขัน
slevithan

103
ข้อได้เปรียบที่สำคัญคือคุณสามารถเปลี่ยน RegExp ของคุณได้โดยไม่ต้องจับคู่กับตัวเลข กลุ่มที่ไม่ได้จับภาพแก้ปัญหานี้ได้ยกเว้นในกรณีเดียวจะเกิดอะไรขึ้นถ้าลำดับของกลุ่มเปลี่ยนไป? นอกจากนี้ยังเป็นที่น่าเบื่อที่จะนำตัวละครพิเศษนี้ไปใช้กับกลุ่มอื่น ...
Alba Mendez

55
ที่เรียกว่าน้ำตาลประโยค ไม่ช่วยเหลือหวานการอ่านรหัส!
Mrchief

1
ฉันคิดว่ามีอีกเหตุผลที่ทำให้กลุ่มจับชื่อที่มีค่าจริงๆ ตัวอย่างเช่นถ้าคุณต้องการใช้ regex เพื่อแยกวันที่จากสตริงคุณสามารถเขียนฟังก์ชั่นที่มีความยืดหยุ่นที่รับค่าและ regex ตราบใดที่ regex ตั้งชื่อการดักจับสำหรับปีเดือนและวันที่คุณสามารถเรียกใช้อาร์เรย์ของนิพจน์ทั่วไปด้วยรหัสขั้นต่ำได้
Dewey Vozel

4
ตั้งแต่ตุลาคม 2019, Firefox, IE 11 และ Microsoft Edge (pre-Chromium) ไม่รองรับการจับภาพกลุ่มที่มีชื่อ เบราว์เซอร์อื่น ๆ ส่วนใหญ่ (แม้กระทั่ง Opera และ Samsung mobile) ก็ทำได้เช่นกัน caniuse.com/…
JDB ยังคงจดจำโมนิก้า

63

คุณสามารถใช้XRegExpซึ่งเป็นการนำนิพจน์ทั่วไปที่เพิ่มขยายขยายได้ข้ามเบราว์เซอร์รวมถึงการสนับสนุนไวยากรณ์เพิ่มเติมแฟล็กและเมธอด:

  • เพิ่ม regex ใหม่และไวยากรณ์ข้อความทดแทนรวมถึงการสนับสนุนที่ครอบคลุมสำหรับการตั้งชื่อการจับภาพ
  • เพิ่มการตั้งค่าสถานะ regex ใหม่สองรายการ: s, เพื่อให้ dot จับคู่อักขระทั้งหมด (โหมด dotall หรือ singleline), และx, สำหรับระยะห่างและข้อคิดเห็น (โหมดขยาย)
  • นำเสนอชุดฟังก์ชันและวิธีการที่ทำให้การประมวลผล regex ที่ซับซ้อนเป็นเรื่องง่าย
  • แก้ไขความไม่สอดคล้องกันระหว่างเบราว์เซอร์ที่พบมากที่สุดโดยอัตโนมัติในลักษณะการทำงานและไวยากรณ์ของ regex
  • ให้คุณสร้างและใช้ปลั๊กอินที่เพิ่มไวยากรณ์และแฟล็กใหม่ในภาษานิพจน์ปกติของ XRegExp ได้อย่างง่ายดาย

60

อีกวิธีที่เป็นไปได้: สร้างวัตถุที่มีชื่อกลุ่มและดัชนี

var regex = new RegExp("(.*) (.*)");
var regexGroups = { FirstName: 1, LastName: 2 };

จากนั้นใช้ปุ่มวัตถุเพื่ออ้างอิงกลุ่ม:

var m = regex.exec("John Smith");
var f = m[regexGroups.FirstName];

สิ่งนี้ช่วยปรับปรุงความสามารถในการอ่าน / คุณภาพของรหัสโดยใช้ผลลัพธ์ของ regex แต่ไม่สามารถอ่านได้ของ regex


58

ใน ES6 คุณสามารถใช้การทำลายอาร์เรย์เพื่อจับกลุ่มของคุณ:

let text = '27 months';
let regex = /(\d+)\s*(days?|months?|years?)/;
let [, count, unit] = regex.exec(text) || [];

// count === '27'
// unit === 'months'

หมายเหตุ:

  • เครื่องหมายจุลภาคแรกในสุดท้ายletข้ามค่าแรกของอาร์เรย์ผลลัพธ์ซึ่งเป็นสตริงที่ตรงกันทั้งหมด
  • || []หลังจากที่.exec()จะป้องกันไม่ให้เกิดข้อผิดพลาด destructuring เมื่อไม่มีการแข่งขัน (เพราะ.exec()จะกลับมาnull)

1
เครื่องหมายจุลภาคแรกเป็นเพราะองค์ประกอบแรกของอาร์เรย์ที่คืนค่าโดยจับคู่คือนิพจน์อินพุตใช่ไหม
Emilio Grisolía

1
String.prototype.matchส่งคืนอาร์เรย์ด้วย: สตริงที่ตรงกันทั้งหมดที่ตำแหน่ง 0 จากนั้นกลุ่มใด ๆ หลังจากนั้น เครื่องหมายจุลภาคแรกบอกว่า "ข้ามองค์ประกอบที่ตำแหน่ง 0"
2559

2
คำตอบที่ฉันชอบสำหรับผู้ที่มี transpiling หรือเป้าหมาย ES6 + สิ่งนี้ไม่จำเป็นต้องป้องกันข้อผิดพลาดที่ไม่สอดคล้องกันเช่นเดียวกับดัชนีที่ตั้งชื่อได้หากมีการเปลี่ยนแปลง regex ที่นำกลับมาใช้ใหม่ แต่ฉันคิดว่าความกระชับที่นี่ทำให้ง่ายขึ้นสำหรับสิ่งนั้น ฉันได้เลือกRegExp.prototype.execมากกว่าString.prototype.matchในสถานที่ที่สตริงอาจจะเป็นหรือnull undefined
Mike Hill

22

อัปเดต: ในที่สุดมันก็ทำให้เป็น JavaScript (ECMAScript 2018)!


กลุ่มจับภาพที่มีชื่อสามารถทำให้เป็น JavaScript ได้ในไม่ช้า
ข้อเสนอสำหรับมันอยู่ในขั้นตอนที่ 3 แล้ว

กลุ่มการดักจับสามารถให้ชื่อภายในวงเล็บเหลี่ยมโดยใช้(?<name>...)ไวยากรณ์สำหรับชื่อตัวระบุใด ๆ /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/uการแสดงออกปกติสำหรับวันที่แล้วสามารถเขียนเป็น แต่ละชื่อต้องไม่ซ้ำกันและปฏิบัติตามหลักไวยากรณ์สำหรับ ECMAScript IdentifierName

กลุ่มที่มีชื่อสามารถเข้าถึงได้จากคุณสมบัติของคุณสมบัติกลุ่มของผลลัพธ์นิพจน์ทั่วไป หมายเลขอ้างอิงไปยังกลุ่มจะถูกสร้างขึ้นเช่นเดียวกับกลุ่มที่ไม่มีชื่อ ตัวอย่างเช่น:

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2015-01-02');
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';

// result[0] === '2015-01-02';
// result[1] === '2015';
// result[2] === '01';
// result[3] === '02';

มันเป็นข้อเสนอ 4 ขั้นตอนในขณะนี้
GOTO 0

หากคุณใช้ '18 อาจใช้การทำลายทั้งหมดเช่นกัน let {year, month, day} = ((result) => ((result) ? result.groups : {}))(re.exec('2015-01-02'));
Hashbrown

6

การตั้งชื่อกลุ่มที่ถูกจับภาพนั้นมีสิ่งหนึ่งที่: ความสับสนน้อยลงด้วยการแสดงออกปกติที่ซับซ้อน

มันขึ้นอยู่กับกรณีการใช้งานของคุณ แต่การพิมพ์ regex ของคุณอาจช่วยได้

หรือคุณอาจลองกำหนดค่าคงที่เพื่ออ้างถึงกลุ่มที่ถูกจับ

ความคิดเห็นอาจช่วยให้ผู้อื่นที่อ่านรหัสของคุณทราบว่าคุณได้ทำอะไรไปแล้ว

สำหรับส่วนที่เหลือฉันต้องเห็นด้วยกับคำตอบของ Tims


5

มีไลบรารี node.js ชื่อ named-regexpที่คุณสามารถใช้ในโปรเจ็กต์ node.js ของคุณ (เปิดในเบราว์เซอร์โดยการบรรจุไลบรารีด้วยเบราว์เซอร์หรือสคริปต์บรรจุภัณฑ์อื่น ๆ ) อย่างไรก็ตามไลบรารีไม่สามารถใช้กับนิพจน์ทั่วไปที่มีกลุ่มการดักจับที่ไม่มีชื่อ

หากคุณนับวงเล็บปีกกาเปิดในนิพจน์ปกติของคุณคุณสามารถสร้างการแมประหว่างกลุ่มจับภาพที่มีชื่อกับกลุ่มจับภาพหมายเลขใน regex ของคุณและสามารถผสมและจับคู่ได้อย่างอิสระ คุณต้องลบชื่อกลุ่มก่อนใช้ regex ฉันเขียนสามฟังก์ชั่นที่แสดงว่า ดูส่วนสำคัญนี้: https://gist.github.com/gbirke/2cc2370135b665eee3ef


นั่นคือมีน้ำหนักเบาน่าแปลกใจฉันจะลอง
fregante

ใช้ได้กับกลุ่มที่มีชื่อซ้อนกันภายในกลุ่มปกติในนิพจน์ปกติที่ซับซ้อนหรือไม่
ElSajko

มันไม่สมบูรณ์แบบ ข้อผิดพลาดเมื่อ: getMap ("((a | b (: <foo> c)))"); foo ควรเป็นกลุ่มที่สามไม่ใช่ที่สอง /((a|b(c)))/g.exec("bc "); ["bc", "bc", "bc", "c"]
ElSajko

3

ดังที่Tim Pietzckerกล่าวว่า ECMAScript 2018 แนะนำกลุ่มการจับภาพชื่อใน JavaScript regexes แต่สิ่งที่ฉันไม่พบในคำตอบข้างต้นคือวิธีใช้กลุ่มที่มีชื่อใน regex นั้นเอง

\k<name>คุณสามารถใช้กลุ่มจับชื่อกับรูปแบบนี้: ตัวอย่างเช่น

var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/

และดังที่Forivinกล่าวว่าคุณสามารถใช้กลุ่มที่ถูกจับในผลลัพธ์วัตถุดังนี้:

let result = regexObj.exec('2019-28-06 year is 2019');
// result.groups.year === '2019';
// result.groups.month === '06';
// result.groups.day === '28';

  var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/mgi;

function check(){
    var inp = document.getElementById("tinput").value;
    let result = regexObj.exec(inp);
    document.getElementById("year").innerHTML = result.groups.year;
    document.getElementById("month").innerHTML = result.groups.month;
    document.getElementById("day").innerHTML = result.groups.day;
}
td, th{
  border: solid 2px #ccc;
}
<input id="tinput" type="text" value="2019-28-06 year is 2019"/>
<br/>
<br/>
<span>Pattern: "(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>";
<br/>
<br/>
<button onclick="check()">Check!</button>
<br/>
<br/>
<table>
  <thead>
    <tr>
      <th>
        <span>Year</span>
      </th>
      <th>
        <span>Month</span>
      </th>
      <th>
        <span>Day</span>
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        <span id="year"></span>
      </td>
      <td>
        <span id="month"></span>
      </td>
      <td>
        <span id="day"></span>
      </td>
    </tr>
  </tbody>
</table>


2

ในขณะที่คุณไม่สามารถทำเช่นนี้กับวานิลลา JavaScript, บางทีคุณอาจจะใช้บางArray.prototypeฟังก์ชั่นเช่นArray.prototype.reduceการเปิดการแข่งขันการจัดทำดัชนีเป็นคนตั้งชื่อโดยใช้บางมายากล

เห็นได้ชัดว่าการแก้ปัญหาต่อไปนี้จะต้องเกิดขึ้นในการแข่งขัน:

// @text Contains the text to match
// @regex A regular expression object (f.e. /.+/)
// @matchNames An array of literal strings where each item
//             is the name of each group
function namedRegexMatch(text, regex, matchNames) {
  var matches = regex.exec(text);

  return matches.reduce(function(result, match, index) {
    if (index > 0)
      // This substraction is required because we count 
      // match indexes from 1, because 0 is the entire matched string
      result[matchNames[index - 1]] = match;

    return result;
  }, {});
}

var myString = "Hello Alex, I am John";

var namedMatches = namedRegexMatch(
  myString,
  /Hello ([a-z]+), I am ([a-z]+)/i, 
  ["firstPersonName", "secondPersonName"]
);

alert(JSON.stringify(namedMatches));


มันเท่ห์มาก ฉันแค่คิด .. จะไม่สามารถสร้างฟังก์ชัน regex ที่ยอมรับ regex แบบกำหนดเองได้หรือไม่ เพื่อที่คุณจะได้ไปvar assocArray = Regex("hello alex, I am dennis", "hello ({hisName}.+), I am ({yourName}.+)");
Forivin

@Forivin ชัดเจนว่าคุณสามารถไปต่อและพัฒนาคุณสมบัตินี้ การทำงานให้เป็นเรื่องยาก: D
Matías Fidemraizer

คุณสามารถขยายRegExpวัตถุโดยการเพิ่มฟังก์ชั่นต้นแบบของมัน
Mr. TA

@ Mr.TA AFAIK ไม่แนะนำให้ขยายวัตถุในตัว
Matías Fidemraizer

0

ไม่มี ECMAScript 2018 ใช่ไหม

เป้าหมายของฉันคือการทำให้มันทำงานคล้ายกับสิ่งที่เราคุ้นเคยกับกลุ่มที่มีชื่อ ในขณะที่ ECMAScript 2018 คุณสามารถวางไว้?<groupname>ภายในกลุ่มเพื่อระบุกลุ่มที่มีชื่อในโซลูชันของฉันสำหรับจาวาสคริปต์รุ่นเก่าคุณสามารถวางไว้(?!=<groupname>)ภายในกลุ่มเพื่อทำสิ่งเดียวกัน ดังนั้นมันจึงเป็นวงเล็บพิเศษและอีกอัน!=หนึ่ง ค่อนข้างใกล้!

ฉันห่อมันทั้งหมดไว้ในฟังก์ชั่นต้นแบบสตริง

คุณสมบัติ

  • ทำงานกับจาวาสคริปต์ที่เก่ากว่า
  • ไม่มีรหัสพิเศษ
  • สวยใช้งานง่าย
  • Regex ยังคงใช้งานได้
  • มีการจัดทำเอกสารกลุ่มภายใน regex เอง
  • ชื่อกลุ่มสามารถมีช่องว่าง
  • ส่งคืนวัตถุที่มีผลลัพธ์

คำแนะนำ

  • วาง(?!={groupname})ในแต่ละกลุ่มที่คุณต้องการตั้งชื่อ
  • อย่าลืมกำจัดกลุ่มที่ไม่ได้ดักจับใด ๆ()โดยใส่?:จุดเริ่มต้นของกลุ่มนั้น สิ่งเหล่านี้จะไม่ถูกตั้งชื่อ

arrays.js

// @@pattern - includes injections of (?!={groupname}) for each group
// @@returns - an object with a property for each group having the group's match as the value 
String.prototype.matchWithGroups = function (pattern) {
  var matches = this.match(pattern);
  return pattern
  // get the pattern as a string
  .toString()
  // suss out the groups
  .match(/<(.+?)>/g)
  // remove the braces
  .map(function(group) {
    return group.match(/<(.+)>/)[1];
  })
  // create an object with a property for each group having the group's match as the value 
  .reduce(function(acc, curr, index, arr) {
    acc[curr] = matches[index + 1];
    return acc;
  }, {});
};    

การใช้

function testRegGroups() {
  var s = '123 Main St';
  var pattern = /((?!=<house number>)\d+)\s((?!=<street name>)\w+)\s((?!=<street type>)\w+)/;
  var o = s.matchWithGroups(pattern); // {'house number':"123", 'street name':"Main", 'street type':"St"}
  var j = JSON.stringify(o);
  var housenum = o['house number']; // 123
}

ผลของการ

{
  "house number": "123",
  "street name": "Main",
  "street type": "St"
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.