JavaScript 정규식에서 캡처 그룹을 지정 했습니까?


208

내가 아는 한 JavaScript에는 캡쳐 그룹이라는 것과 같은 것이 없습니다. 유사한 기능을 얻는 다른 방법은 무엇입니까?


1
자바 스크립트의 캡처 그룹은 숫자로 표시됩니다. $ 1은 첫 번째로 캡처 된 그룹, $ 2, $ 3 ... 최대 $ 99이지만 존재하지 않는 다른 무언가를 원하시는 것 같습니다
Erik

24
@Erik은 번호가 매겨진 캡처 그룹, OP가 명명 된 캡처 그룹 에 대해 이야기 하고 있습니다. 그것들은 존재하지만 JS에서 그것들을 지원하는지 알고 싶습니다.
Alba Mendez

4
거기의 자바 스크립트로 명명 된 정규 표현식을 가지고 제안 하지만, 우리가 이제까지 할 경우 우리가 그것을 참조하기 전에 몇 년 수 있습니다.
fregante

Firefox는 웹 사이트에서 명명 된 캡처 그룹을 사용하려고 시도한 것에 대해 처벌했습니다. stackoverflow.com/a/58221254/782034
Nick Grealy

답변:


134

ECMAScript 2018에서는 명명 된 캡처 그룹 을 JavaScript 정규식에 도입했습니다 .

예:

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

구형 브라우저를 지원 해야하는 경우 명명 된 캡처 그룹으로 할 수있는 일반 (번호 매기기) 캡처 그룹으로 모든 작업을 수행 할 수 있습니다. 번호를 추적하면됩니다-그룹에서 그룹을 캡처하는 순서가 번거 롭다면 번거로울 수 있습니다 정규식 변경.

내가 생각할 수있는 명명 된 캡처 그룹의 두 가지 "구조적"장점은 다음과 같습니다.

  1. 일부 정규 표현식 맛 (내가 아는 한 .NET 및 JGSoft)에서는 정규 표현식의 다른 그룹에 동일한 이름을 사용할 수 있습니다 ( 여기에서 중요한 예는 여기 참조 ). 그러나 대부분의 정규 표현식은이 기능을 지원하지 않습니다.

  2. 숫자로 둘러싸인 상황에서 번호가 매겨진 캡처 그룹을 참조해야하는 경우 문제가 발생할 수 있습니다. 하자 당신이 자리에 0을 추가 할 따라서 교체 할 말 (\d)과 함께 $10. 자바 스크립트,이 의지 (당신이 당신의 정규식에서 10 개 미만의 캡처 그룹을 가지고 같은) 작동하지만 펄은 역 참조 번호를 찾고 있다고 생각합니다 10대신 숫자의 1다음에 0. Perl에서는 ${1}0이 경우에 사용할 수 있습니다 .

그 외에, 명명 된 포획 그룹은 단지 "구문 설탕"이다. 캡처 그룹은 실제로 필요할 때만 사용 (?:...)하고 다른 모든 상황에서는 캡처되지 않은 그룹을 사용하는 데 도움이됩니다 .

JavaScript의 더 큰 문제는 (제 생각에) 읽기 쉬운 복잡한 정규 표현식을 훨씬 쉽게 만들 수있는 자세한 정규 표현식을 지원하지 않는다는 것입니다.

Steve Levithan의 XRegExp 라이브러리 는 이러한 문제를 해결합니다.


5
많은 맛이 정규식에서 동일한 캡처 그룹 이름을 여러 번 사용할 수 있습니다. 그러나 .NET과 Perl 5.10+만이 일치하는 이름의 마지막 그룹이 캡처 한 값을 유지함으로써 특히 유용합니다.
slevithan

103
가장 큰 장점은 다음과 같습니다. RegExp 만 변경하면됩니다. 캡처하지 않은 그룹은 한 가지 경우를 제외하고이 문제를 해결 합니다. 그룹 순서가 변경되면 어떻게됩니까? 또한,이 여분의 문자를 다른 그룹에 추가하는 것은 대단한
Alba Mendez

55
소위 구문 설탕 코드 가독성을 향상시키는 데 도움이됩니다!
Mr.

1
명명 된 캡처 그룹이 정말 가치있는 또 다른 이유가 있다고 생각합니다. 예를 들어 정규식을 사용하여 문자열에서 날짜를 구문 분석하려면 값과 정규식을 취하는 유연한 함수를 작성할 수 있습니다. 정규식에서 연도, 월 및 날짜에 대한 캡처 이름을 지정하는 한 최소한의 코드로 정규 표현식 배열을 실행할 수 있습니다.
듀이 보젤

4
2019 년 10 월 현재 Firefox, IE 11 및 Microsoft Edge (Chromium 이전)는 명명 된 그룹 캡처를 지원하지 않습니다. 대부분의 다른 브라우저 (오페라 및 삼성 모바일 포함) caniuse.com/…
JDB는 여전히 Monica

63

추가 구문, 플래그 및 메소드에 대한 지원을 포함하여 정규 표현식의 확장되고 확장 가능한 크로스 브라우저 구현 인 XRegExp 를 사용할 수 있습니다 .

  • 명명 된 캡처에 대한 포괄적 인 지원을 포함하여 새로운 정규식 및 대체 텍스트 구문을 추가합니다 .
  • 두 개의 새로운 정규식 플래그 추가 : s도트하는 모든 문자와 일치하기를 (일명 DOTALL 또는 만일 Singleline 모드) 및 x자유 공간과 의견 (일명 확장 모드)에 대한.
  • 복잡한 정규식 처리를 간편하게 수행 할 수있는 기능 및 방법 모음을 제공합니다.
  • 정규식 동작 및 구문에서 가장 일반적으로 발생하는 크로스 브라우저 불일치를 자동으로 수정합니다.
  • XRegExp의 정규 표현식 언어에 새로운 구문과 플래그를 추가하는 플러그인을 쉽게 만들고 사용할 수 있습니다.

60

또 다른 가능한 해결책 : 그룹 이름과 색인을 포함하는 객체를 만듭니다.

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

그런 다음 객체 키를 사용하여 그룹을 참조하십시오.

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

이것은 정규 표현식의 결과를 사용하여 코드의 가독성 / 품질을 향상 시키지만 정규 표현식 자체의 가독성은 향상시키지 않습니다.


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
첫 번째 쉼표는 match가 반환하는 배열의 첫 번째 요소가 입력 표현식이기 때문입니다.
Emilio Grisolía 1

1
String.prototype.match위치 0에있는 일치하는 전체 문자열과 그 이후의 그룹이 포함 된 배열을 반환합니다. 첫 번째 쉼표는 "0 위치에서 요소 건너 뛰기"라고 말합니다
fregante

2
transpiling 또는 ES6 + 대상이있는 사용자가 가장 선호하는 답변입니다. 이것은 재사용 된 정규 표현식 변경과 같은 불일치 오류와 명명 된 인덱스가 반드시 방지 할 필요는 없지만 여기서 간결함이이를 쉽게 보완한다고 생각합니다. 나는 선택했다 한 RegExp.prototype.exec이상 String.prototype.match문자열 수있는 곳에 nullundefined.
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

캡처 된 그룹의 이름을 지정하면 복잡한 정규식과의 혼동이 줄어 듭니다.

실제로 사용 사례에 따라 다르지만 정규 표현식을 인쇄하면 도움이 될 수 있습니다.

또는 캡처 된 그룹을 참조하기 위해 상수를 시도하고 정의 할 수 있습니다.

그런 다음 주석은 코드를 읽는 다른 사람, 수행 한 작업을 표시하는 데 도움이 될 수 있습니다.

나머지는 팀의 답변에 동의해야합니다.


5

node.js 프로젝트에서 사용할 수있는 named-regexp라는 node.js 라이브러리가 있습니다 (브라우저에서 browserify 또는 기타 패키징 스크립트로 라이브러리를 패키징하여 브라우저에서). 그러나 명명되지 않은 캡처 그룹이 포함 된 정규식에는 라이브러리를 사용할 수 없습니다.

정규식에서 오프닝 캡처 중괄호를 세면 정규 표현식에서 명명 된 캡처 그룹과 번호가 매겨진 캡처 그룹 사이에 매핑을 만들고 자유롭게 혼합하고 일치시킬 수 있습니다. 정규식을 사용하기 전에 그룹 이름을 제거하면됩니다. 나는 그것을 보여주는 세 가지 기능을 작성했습니다. 이 요지를 참조하십시오 : 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

으로 팀 Pietzcker는 ECMAScript를 2018 소개합니다 자바 스크립트 정규 표현식에로 그룹을 캡처 명명했다. 그러나 위의 답변에서 찾지 못한 것은 정규 표현식 자체에서 명명 된 캡처 된 그룹 을 사용하는 방법이었습니다 .

이 구문으로 명명 된 캡처 그룹을 사용할 수 있습니다 \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

당신은 바닐라 자바 스크립트를 사용하여이 작업을 수행 할 수는 없지만, 아마도 당신은 몇 가지 사용할 수있는 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));


꽤 괜찮은데. 그냥 생각하고 있습니다. 커스텀 정규식을 허용하는 정규식 함수를 만들 수 없습니까? 당신이 그렇게 갈 수 있도록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>)에서는 그룹 내에 배치 하여 동일한 작업을 수행 할 수 있습니다 . 따라서 추가 괄호와 추가 !=입니다. 꽤 비슷해!

모든 것을 문자열 프로토 타입 함수로 감쌌습니다.

풍모

  • 오래된 자바 스크립트와 함께 작동
  • 추가 코드가 없습니다
  • 사용하기 매우 간단
  • 정규식은 여전히 ​​작동합니다
  • 그룹은 정규 표현식 자체에 문서화됩니다
  • 그룹 이름은 공백을 가질 수 있습니다
  • 결과와 함께 객체를 반환

명령

  • (?!={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
}

o의 결과

{
  "house number": "123",
  "street name": "Main",
  "street type": "St"
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.