簡體   English   中英

在 JavaScript 正則表達式中命名捕獲組?

[英]Named capturing groups in JavaScript regex?

據我所知,JavaScript 中沒有命名捕獲組這樣的東西。 獲得類似功能的替代方法是什么?

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. 如果您需要在被數字包圍的情況下引用編號的捕獲組,您可能會遇到問題。 假設您想在數字上添加一個零,因此想用$10替換(\\d) 在 JavaScript 中,這會起作用(只要您的正則表達式中的捕獲組少於 10 個),但 Perl 會認為您正在尋找反向引用編號10而不是編號1 ,然后是0 在 Perl 中,您可以在這種情況下使用${1}0

除此之外,命名的捕獲組只是“語法糖”。 只有在您真正需要它們時才使用捕獲組,而在所有其他情況下使用非捕獲組(?:...)有所幫助。

JavaScript 的更大問題(在我看來)是它不支持冗長的正則表達式,這會使創建可讀、復雜的正則表達式變得容易得多。

Steve Levithan 的 XRegExp 庫解決了這些問題。

另一種可能的解決方案:創建一個包含組名和索引的對象。

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

然后,使用對象鍵來引用組:

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

這使用正則表達式的結果提高了代碼的可讀性/質量,但不是正則表達式本身的可讀性。

您可以使用XRegExp ,這是一種增強的、可擴展的、跨瀏覽器的正則表達式實現,包括對附加語法、標志和方法的支持:

  • 添加新的正則表達式和替換文本語法,包括對命名捕獲的全面支持。
  • 添加兩個新的正則表達式標志: s ,使點匹配所有字符(又名 dotall 或單行模式),和x ,用於自由間距和注釋(又名擴展模式)。
  • 提供一套函數和方法,使復雜的正則表達式處理變得輕而易舉。
  • 自動修復正則表達式行為和語法中最常見的跨瀏覽器不一致問題。
  • 讓您輕松創建和使用插件,為 XRegExp 的正則表達式語言添加新的語法和標志。

在 ES6 中,您可以使用數組解構來捕獲您的組:

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

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

注意:

  • 最后一個let的第一個逗號跳過結果數組的第一個值,它是整個匹配的字符串
  • || [] || [] after .exec()將在沒有匹配項時防止解構錯誤(因為.exec()將返回null

更新:它終於變成了 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';

命名捕獲的組提供了一件事:減少與復雜正則表達式的混淆。

這真的取決於您的用例,但也許漂亮地打印您的正則表達式可能會有所幫助。

或者您可以嘗試定義常量來引用您捕獲的組。

評論也可能有助於向閱讀您代碼的其他人展示您做了什么。

其余的我必須同意蒂姆斯的回答。

正如Tim Pietzcker所說,ECMAScript 2018 將命名捕獲組引入 JavaScript 正則表達式。 但是我在上面的答案中沒有找到的是如何在正則表達式本身中使用命名的捕獲組

您可以使用具有以下語法的命名捕獲組: \\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>

有一個名為named-regexp的 node.js 庫,您可以在您的 node.js 項目中使用它(在瀏覽器中通過使用 browserify 或其他打包腳本打包庫)。 但是,該庫不能與包含未命名捕獲組的正則表達式一起使用。

如果您計算正則表達式中的左捕獲括號,您可以在正則表達式中的命名捕獲組和編號捕獲組之間創建映射,並且可以自由混合和匹配。 您只需要在使用正則表達式之前刪除組名。 我已經編寫了三個函數來證明這一點。 請參閱此要點: https : //gist.github.com/gbirke/2cc2370135b665eee3ef

雖然你不能用普通的 JavaScript 做到這一點,但也許你可以使用一些Array.prototype函數,比如Array.prototype.reduce使用一些魔法將索引匹配轉換為命名匹配。

顯然,以下解決方案需要按順序進行匹配:

 // @text Contains the text to match // @regex A regular expression object (fe /.+/) // @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 ([az]+), I am ([az]+)/i, ["firstPersonName", "secondPersonName"] ); alert(JSON.stringify(namedMatches));

沒有 ECMAScript 2018?

我的目標是讓它的工作方式盡可能類似於我們習慣於命名組的方式。 而在 ECMAScript 2018 中,您可以將?<groupname>放置在組內以指示命名組,而在我的舊版 javascript 解決方案中,您可以將(?!=<groupname>)放置在組內以執行相同的操作。 所以它是一組額外的括號和一個額外的!= 很接近了!

我把它全部包裝成一個字符串原型函數

特征

  • 適用於較舊的 javascript
  • 沒有額外的代碼
  • 使用起來非常簡單
  • 正則表達式仍然有效
  • 組記錄在正則表達式本身中
  • 組名可以有空格
  • 返回帶有結果的對象

指示

  • (?!={groupname})放在您要命名的每個組中
  • 記住通過將?:放在該組的開頭來消除任何非捕獲組() 這些不會被命名。

數組.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"
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM