JavaScript regex'te yakalama grupları adlandırıldı mı?


208

Bildiğim kadarıyla JavaScript'te yakalama grupları olarak adlandırılan bir şey yok. Benzer işlevselliği elde etmenin alternatif yolu nedir?


1
Javascript'teki yakalama grupları sayıya göredir. $ 1 ilk yakalanan gruptur, $ 2, $ 3 ... $ 99'a kadar ama başka bir şey istiyormuşsunuz gibi geliyor - ki bu yok
Erik

24
@Erik , numaralandırılmış yakalama gruplarından, OP'nin adlandırılmış yakalama gruplarından bahsediyor . Varlar, ancak JS'de onlar için destek olup olmadığını bilmek istiyoruz.
Alba Mendez

4
Adlandırılmış regex'i JavaScript'e getirmek için bir teklif var , ancak bunu yaparsak, bunu görmemiz yıllar alabilir.
fregante

Firefox, bir web sitesinde adlandırılmış yakalama gruplarını kullanmaya çalıştığım için beni cezalandırdı ... gerçekten benim hatamdı. stackoverflow.com/a/58221254/782034
Nick Grealy

Yanıtlar:


134

ECMAScript 2018 , JavaScript regexes adlı adlandırılmış yakalama gruplarını tanıttı .

Misal:

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

Daha eski tarayıcıları desteklemeniz gerekiyorsa, adlandırılmış yakalama gruplarıyla yapabileceğiniz normal (numaralandırılmış) yakalama gruplarıyla her şeyi yapabilirsiniz, sadece sayıları takip etmeniz gerekir; normal ifade değişiklikleri.

Düşünebildiğim yakalama gruplarının yalnızca iki "yapısal" avantajı vardır:

  1. Bazı normal ifade tatlarında (bildiğim kadarıyla .NET ve JGSoft), normal ifadenizdeki farklı gruplar için aynı adı kullanabilirsiniz ( bunun önemli olduğu bir örnek için buraya bakın ). Ancak çoğu normal ifade aroması bu işlevselliği yine de desteklemez.

  2. Rakamlarla çevrili bir durumda numaralandırılmış yakalama gruplarına başvurmanız gerekiyorsa, bir sorunla karşılaşabilirsiniz. Let Diyelim ki bir rakam olarak bir sıfır eklemek istediğiniz ve bu nedenle değiştirmek istiyor demek (\d)olan $10. JavaScript'te bu işe yarayacaktır (normal ifadenizde 10'dan az yakalama grubuna sahip olduğunuz sürece), ancak Perl sayı 10yerine geri başvuru numarası aradığınızı düşünecektir 1ve ardından a 0. Perl'de ${1}0bu durumda kullanabilirsiniz .

Bunun dışında, adı geçen yakalama grupları sadece "sözdizimsel şeker" dir. Yakalama gruplarını yalnızca gerçekten ihtiyacınız olduğunda kullanmanıza ve yakalamayan grupları (?:...)diğer tüm koşullarda kullanmanıza yardımcı olur .

JavaScript ile ilgili daha büyük sorun, okunabilir, karmaşık düzenli ifadelerin oluşturulmasını çok daha kolay hale getirecek ayrıntılı regex'leri desteklememesidir.

Steve Levithan'ın XRegExp kütüphanesi bu sorunları çözer.


5
Birçok aroma aynı yakalama grubu adını bir normal ifadede birden çok kez kullanmaya izin verir. Ancak, yalnızca .NET ve Perl 5.10+, maça katılan bir adın son grubunun yakaladığı değeri koruyarak özellikle yararlı hale getirir.
slevithan

103
En büyük avantajı: RegExp'inizi, sayı-değişken eşlemesini değiştirmeden değiştirebilirsiniz. Yakalamayan gruplar bir sorunu hariç bu sorunu çözer: grupların sırası değişirse ne olur? Ayrıca, bu ekstra karakterleri diğer gruplara koymak ...
Alba Mendez

55
Sözde sözdizimsel şeker yapar kod okunabilirliği tatlandırmak yardım!
Mrchief

1
Bence grupların yakalanması için gerçekten değerli başka bir neden daha var. Örneğin, bir dizeden bir tarihi ayrıştırmak için normal ifade kullanmak isterseniz, değeri ve normal ifadeyi alan esnek bir işlev yazabilirsiniz. Normal ifade, yıl, ay ve tarih için yakalamalar adlandırdığı sürece, minimum kodla düzenli ifadeler dizisini çalıştırabilirsiniz.
Dewey Vozel

4
Ekim 2019'dan itibaren Firefox, IE 11 ve Microsoft Edge (Chromium öncesi) adlandırılmış grup yakalamalarını desteklemiyor. Diğer tarayıcıların çoğu (Opera ve Samsung mobile bile) yapar. caniuse.com/…
JDB hala Monica

63

Ek sözdizimi, bayraklar ve yöntemler için destek de dahil olmak üzere düzenli ifadelerin artırılmış, genişletilebilir, tarayıcılar arası uygulaması olan XRegExp'yi kullanabilirsiniz :

  • Adlandırılmış yakalama için kapsamlı destek dahil olmak üzere yeni normal ifade ve yedek metin sözdizimi ekler .
  • İki yeni normal ifade bayrağı ekler:, snoktanın tüm karakterlerle eşleşmesini sağlamak için (diğer adıyla dotall veya tek satır modu) ve xserbest aralık ve yorumlar için (genişletilmiş mod olarak da bilinir).
  • Karmaşık normal regex işlemeyi bir esinti haline getiren bir dizi işlev ve yöntem sunar.
  • Normal ifade davranışı ve sözdiziminde en sık karşılaşılan tarayıcılar arası tutarsızlıkları otomatik olarak giderir.
  • XRegExp'in normal ifade diline yeni sözdizimi ve bayraklar ekleyen eklentileri kolayca oluşturmanızı ve kullanmanızı sağlar.

60

Başka bir olası çözüm: grup adlarını ve dizinleri içeren bir nesne oluşturun.

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

Ardından, gruplara başvuruda bulunmak için nesne anahtarlarını kullanın:

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

Bu, normal ifadenin sonuçlarını kullanarak kodun okunabilirliğini / kalitesini geliştirir, ancak normal ifadenin kendisinin okunabilirliğini geliştirmez.


58

ES6'da gruplarınızı yakalamak için dizi yok etmeyi kullanabilirsiniz:

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

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

Farkına varmak:

  • sondaki ilk virgül, leteşleşen dizinin ilk değerini atlar; bu, eşleşen dizenin tamamıdır
  • || []sonra .exec()eşleşme (çünkü varken bir kurucuların hatayı engelleyecektir .exec()dönecektir null)

1
İlk virgül, eşleşmeyle döndürülen dizinin ilk öğesinin girdi ifadesi olması, değil mi?
Emilio Grisolía

1
String.prototype.matchile bir dizi döndürür: 0 konumundaki tüm eşleşen dize, ardından bundan sonraki gruplar. İlk virgül "0 konumunda elemanı atla" diyor
fregante

2
Aktarma veya ES6 + hedefleri olanlar için en sevdiğim cevap. Bu, örneğin yeniden kullanılmış bir normal ifade değiştiğinde adlandırılmış endekslerin yanı sıra tutarsızlık hatalarını önleyemez, ancak bence buradaki kısalık kolayca bunu telafi eder. Ben tercih ettik RegExp.prototype.execover String.prototype.matchdize olabilir yerlerde nullveya undefined.
Mike Hill

22

Güncelleme: Sonunda JavaScript'e dönüştürdü (ECMAScript 2018)!


Adlandırılmış yakalama grupları çok yakında JavaScript'e girebilir.
Bunun için teklif zaten 3. aşamada.

Bir yakalama grubuna, (?<name>...)herhangi bir tanımlayıcı adı için sözdizimi kullanılarak köşeli parantezler içinde bir ad verilebilir . Bir tarihin normal ifadesi olarak yazılabilir /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u. Her ad benzersiz olmalı ve ECMAScript IdentifierName dilbilgisini izlemelidir .

Adlandırılmış gruplara, normal ifade sonucunun groups özelliğinin özelliklerinden erişilebilir. Aynı adlandırılmamış gruplarda olduğu gibi gruplara numaralandırılmış başvurular da oluşturulur. Örneğin:

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

Bu, şu anda bir 4. aşama önerisidir.
GOTO 0

Eğer '18'i kullanıyorsanız, yıkımla da sonuçlanabilir; let {year, month, day} = ((result) => ((result) ? result.groups : {}))(re.exec('2015-01-02'));
Hashbrown

6

Yakalanan grupları adlandırmak bir şey sağlar: karmaşık düzenli ifadelerle daha az karışıklık.

Bu gerçekten sizin kullanım durumunuza bağlıdır, ancak belki de regex'inizin güzel yazdırılması yardımcı olabilir.

Veya yakalanan gruplarınıza başvurmak için sabitleri tanımlamayı deneyebilirsiniz.

Yorumlar daha sonra kodunuzu okuyan diğer kullanıcılara ne yaptığınızı göstermeye de yardımcı olabilir.

Geri kalanı için Tims cevabını kabul etmeliyim.


5

Node.js projelerinizde kullanabileceğiniz adlı -degegepp adlı bir node.js kütüphanesi vardır (tarayıcıda kütüphaneyi browserify veya diğer paketleme komut dosyalarıyla paketleyerek). Ancak, kütüphane, adlandırılmamış yakalama grupları içeren normal ifadelerle kullanılamaz.

Normal ifadenizdeki açılış yakalama parantezlerini sayarsanız, adlandırılmış yakalama grupları ile normal ifadenizdeki numaralandırılmış yakalama grupları arasında bir eşleme oluşturabilir ve serbestçe karışıp eşleştirebilirsiniz. Normal ifadeyi kullanmadan önce grup adlarını kaldırmanız yeterlidir. Bunu gösteren üç işlev yazdım. Bu özete bakın: https://gist.github.com/gbirke/2cc2370135b665eee3ef


Bu şaşırtıcı hafif, deneyeceğim
fregante

Karmaşık düzenli ifadelerde normal gruplar içindeki iç içe adlandırılmış gruplarla çalışır mı?
ElSajko

Mükemmel değil. Ne zaman hata: getMap ("((a | b (: <foo> c)))"); foo ikinci değil üçüncü grup olmalıdır. /((a|b(c)))/g.exec("bc "); ["bc", "bc", "bc", "c"]
ElSajko

3

As Tim Pietzcker ECMAScript 2018 tanıtır JavaScript regexes içine yakalama gruplarını adında söyledi. Ancak yukarıdaki cevaplarda bulamadığım şey , regex'in kendisinde adlandırılan yakalanan grubun nasıl kullanılacağıydı .

Bu sözdizimi ile adlandırılmış yakalanan grup kullanabilirsiniz: \k<name>. Örneğin

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

ve Forivin'in söylediği gibi, yakalanan grubu nesne sonucunda aşağıdaki gibi kullanabilirsiniz:

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

Bunu vanilya JavaScript ile yapamazken, belki de sihir kullanarak dizinlenmiş eşleşmeleri adlandırılmış eşleşmelere dönüştürmek Array.prototypegibi bir işlev kullanabilirsiniz .Array.prototype.reduce

Açıkçası, aşağıdaki çözüm eşleşmelerin sırayla gerçekleşmesini gerektirecektir:

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


Bu oldukça havalı. Sadece düşünüyorum ... özel bir regex kabul eden bir regex işlevi oluşturmak mümkün olmaz mıydı? Gibi gidebilirsinvar assocArray = Regex("hello alex, I am dennis", "hello ({hisName}.+), I am ({yourName}.+)");
Forivin

@Forivin Açıkça daha ileri gidebilir ve bu özelliği geliştirebilirsiniz.
Çalıştırmak

RegExpPrototipine bir işlev ekleyerek nesneyi genişletebilirsiniz .
Bay TA

@TA TAAIK, yerleşik nesnelerin genişletilmesi önerilmez
Matías Fidemraizer

0

ECMAScript 2018'iniz yok mu?

Amacım, adlandırılmış gruplarla alışkın olduğumuz şeye mümkün olduğunca benzer bir şekilde çalışmasını sağlamaktı. ECMAScript 2018'de ?<groupname>adlandırılmış bir grubu belirtmek için grubun içine yerleştirebilirsiniz, eski javascript çözümümde (?!=<groupname>)ise aynı şeyi yapmak için grubun içine yerleştirebilirsiniz. Yani ekstra bir parantez ve ekstra !=. Oldukça yakın!

Hepsini bir dize prototip işlevi içine sarılmış

Özellikleri

  • eski javascript ile çalışır
  • ekstra kod yok
  • kullanımı oldukça basit
  • Normal ifade hala çalışıyor
  • gruplar normal ifade içinde belgelenir
  • grup adlarında boşluk olabilir
  • sonuç içeren nesneyi döndürür

Talimatlar

  • (?!={groupname})adlandırmak istediğiniz her grubun içine yerleştirin
  • grubun başına ()koyarak yakalamayan grupları yok etmeyi unutmayın ?:. Bunlar adlandırılmayacak.

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

kullanım

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
}

o sonucu

{
  "house number": "123",
  "street name": "Main",
  "street type": "St"
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.