javascript - RegEx för att extrahera alla matchningar från sträng med hjälp av RegExp.exec
Jag försöker analysera följande typ av sträng:
[key:"val" key2:"val2"]
där det finns godtyckliga key:"val"-par inuti. Jag vill hämta nyckelnamnet och värdet. För de som är nyfikna försöker jag analysera databasen-formatet för Task Warrior.
Här är min teststräng:
[description:"aoeu" uuid:"123sth"]
vilket är avsett att framhäva att allt kan vara i en nyckel eller ett värde förutom mellanslag, inga mellanslag runt kolon, och värden är alltid inom citattecken.
I node är detta mitt resultat:
[deuteronomy][gatlin][~]$ node
> var re = /^\[(?:(.+?):"(.+?)"\s*)+\]$/g
> re.exec('[description:"aoeu" uuid:"123sth"]');
[ '[description:"aoeu" uuid:"123sth"]',
'uuid',
'123sth',
index: 0,
input: '[description:"aoeu" uuid:"123sth"]' ]
Men description:"aoeu" matchar också detta mönster. Hur kan jag få tillbaka alla träffar?
Svar #1
Fortsätt att anropa re.exec(s) i en loop för att få alla matchningar:
var re = /\s*([^[:]+):\"([^"]+)"/g;
var s = '[description:"aoeu" uuid:"123sth"]';
var m;
do {
m = re.exec(s);
if (m) {
console.log(m[1], m[2]);
}
} while (m);
Testa det med den här JSFiddle: https://jsfiddle.net/7yS2V/
Svar #2
str.match(pattern), om pattern har globalflaggan g, kommer att returnera alla matchningar som en array.
Till exempel:
const str = 'All of us except @Emran, @Raju and @Noman were there';
console.log(
str.match(/@\w*/g)
);
// Will log ["@Emran", "@Raju", "@Noman"]Svar #3
För att loopa igenom alla matchningar kan du använda replace-funktionen:
var re = /\s*([^[:]+):\"([^"]+)"/g;
var s = '[description:"aoeu" uuid:"123sth"]';
s.replace(re, function(match, g1, g2) { console.log(g1, g2); });
Svar #4
Detta är en lösning
var s = '[description:"aoeu" uuid:"123sth"]';
var re = /\s*([^[:]+):\"([^"]+)"/g;
var m;
while (m = re.exec(s)) {
console.log(m[1], m[2]);
}
Detta är baserat på lawnseas svar, men kortare.
Observera att flaggan `g` måste vara inställd för att flytta den interna pekaren framåt över anrop.
Svar #5
str.match(/regex/g)
returnerar alla matchningar som en lista.
Om du, av någon mystisk anledning, behöver den ytterligare informationen som kommer med exec, som ett alternativ till tidigare svar, kan du göra det med en rekursiv funktion istället för en loop på följande sätt (vilket också ser coolare ut :).
function findMatches(regex, str, matches = []) {
const res = regex.exec(str)
res && matches.push(res) && findMatches(regex, str, matches)
return matches
}
// Usage
const matches = findMatches(/regex/g, str)
Som nämnts i kommentarerna tidigare är det viktigt att ha g i slutet av regex-definitionen för att flytta pekaren framåt vid varje körning.
Svar #6
Vi börjar äntligen se en inbyggd matchAll-funktion, se här för beskrivningen och kompatibilitetstabellen. Det ser ut som att från och med maj 2020 stöds Chrome, Edge, Firefox och Node.js (12+) men inte IE, Safari och Opera. Det verkar som att den skrevs i december 2018, så ge det lite tid att nå alla webbläsare, men jag litar på att det kommer att hända.
Den inbyggda matchAll-funktionen är bra eftersom den returnerar ett itererbart objekt. Den returnerar också fångstgrupper för varje matchning! Så du kan göra saker som
// get the letters before and after "o"
let matches = "stackoverflow".matchAll(/(\w)o(\w)/g);
for (match of matches) {
console.log("letter before:" + match[1]);
console.log("letter after:" + match[2]);
}
arrayOfAllMatches = [...matches]; // you can also turn the iterable into an array
Det verkar också som om varje match-objekt använder samma format som match(). Så varje objekt är en array av matchen och fångstgrupperna, tillsammans med tre ytterligare egenskaper index, input och groups. Så det ser ut som:
[<match>, <group1>, <group2>, ..., index: <match offset>, input: <original string>, groups: <named capture groups>]
För mer information om matchAll finns det också en Google Developers-sida. Det finns också polyfills/shims tillgängliga.
Svar #7
Om du har ES9
(Betyder om ditt system: Chrome, Node.js, Deno, Bun, Firefox, etc stödjer Ecmascript 2019 eller senare)
yourString.matchAll( /your-regex/g ) // dont forget the "g"
MDN-dokumentation
Om du använder NPM
Du kan använda den officiella polyfillen
npm install string.prototype.matchall
const matchAll = require('string.prototype.matchall')
console.log( [... matchAll('blah1 blah2',/blah/g) ] )
//[
// [ 'blah', index: 0, input: 'blah1 blah2', groups: undefined ],
// [ 'blah', index: 6, input: 'blah1 blah2', groups: undefined ]
//]
Annars
Här är några funktionellt liknande kopiera-klistra in-versioner
// returns an array, works on super old javascript (ES3 -- 1999)
function findAll(regexPattern, sourceString) {
var output = []
var match
// auto-add global flag while keeping others as-is
var regexPatternWithGlobal = regexPattern.global ? regexPattern : RegExp(regexPattern, regexPattern.flags+"g")
while (match = regexPatternWithGlobal.exec(sourceString)) {
// store the match data
output.push(match)
// zero-length matches will end up in an infinite loop, so increment by one char after a zero-length match is found
if (match[0].length == 0) {
regexPatternWithGlobal.lastIndex += 1
}
}
return output
}
// this version returns an iterator, which is good for large results
// note: iterators require ES6 - 2015 standard
function* findAll(regexPattern, sourceString) {
var match
// auto-add global flag while keeping others as-is
const regexPatternWithGlobal = regexPattern.global ? regexPattern : RegExp(regexPattern, regexPattern.flags+"g")
while (match = regexPatternWithGlobal.exec(sourceString)) {
// store the match data
yield match
// zero-length matches will end up in an infinite loop, so increment by one char after a zero-length match is found
if (match[0].length == 0) {
regexPatternWithGlobal.lastIndex += 1
}
}
return output
}
exempel på användning:
console.log( findAll(/blah/g,'blah1 blah2') )
utgångar :
[ [ 'blah', index: 0 ], [ 'blah', index: 6 ] ]
Svar #8
Baserat på Agus funktion, men jag föredrar att bara returnera matchvärdena:
var bob = "> bob <";
function matchAll(str, regex) {
var res = [];
var m;
if (regex.global) {
while (m = regex.exec(str)) {
res.push(m[1]);
}
} else {
if (m = regex.exec(str)) {
res.push(m[1]);
}
}
return res;
}
var Amatch = matchAll(bob, /(&.*?;)/g);
console.log(Amatch); // yeilds: [>, <]
