簡體   English   中英

RegEx 中的模板文字

[英]Template literal inside of the RegEx

我試圖在 RegEx 中放置一個模板文字,但它不起作用。 然后我制作了一個變量regex來保存我的regex ,但它仍然沒有給我想要的結果。

但是,如果我單獨console.log(regex) ,我確實收到了所需的 RegEx,例如/.+?(?=location)/i/.+?(?=date)/i等等,但是一旦我將regex放在.replace它似乎不起作用

function validate (data) {
  let testArr = Object.keys(data);
  errorMessages.forEach((elem, i) => {
    const regex = `/.+?(?=${elem.value})/i`;
    const a = testArr[i].replace(regex, '');
    })
  }

您的regex變量是String 要使其成為 RegExp,請使用RegExp構造函數:

const regex = new RegExp(String.raw`pattern_as_in_regex_literal_without_delimiters`)

例如,像/<\\d+>/g這樣的正則表達式文字可以重寫為

const re = RegExp(String.raw`<\d+>`, 'g') // One \ is a literal backslash
const re = RegExp(`<\\d+>`, 'g')       // Two \ are required in a non-raw string literal

要插入變量,您可以使用

const digits = String.raw`\d+`;
const re = RegExp(`<${digits}>`, 'g')

為了解決您的問題,您可以使用

const regex = new RegExp(`.+?(?=${elemvalue.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')})`, "i"); 

此外,最好在正則表達式中轉義變量部分,以便所有特殊的正則表達式元字符都被視為文字。

 const s = "final (location)"; const elemvalue = "(location)"; const regex = new RegExp(`.+?(?=${elemvalue.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&')})`, "i"); // console.log(regex); // /.+?(?=\\(location\\))/i // console.log(typeof(regex)); // object let a = s.replace(regex, ''); console.log(a);

更高級的模板文字形式是Tagged templates -MDN

 const escape = s => `${s}`.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, "\\\\$&"); const regex = ({ // (1) raw: [part, ...parts] }, ...subs) => new RegExp( subs.reduce( // (2) (result, sub, i) => `${result}${escape(sub)}${parts[i]}`, part ) ); const t1 = `d`; const r1 = regex `^ab(c${t1}e)`; // (3) // /^ab(cde)/ console.log('r2.test(`abc${t1}e`); ➜', r1.test(`abc${t1}e`)); // true // Check for proper escaped special chars const t2 = `(:?bar)\\d{2}`; const r2 = regex `foo${t2}`; // /foo\\(:\\?bar\\)d\\{2\\}/ ➜ t2 is escaped! console.log('r2.test(`foo${t2}`); ➜', r2.test(`foo${t2}`)); // true console.log('r2.test(`foo\\(:\\?bar\\)d\\{2\\}`); ➜', r2.test(`foo\\(:\\?bar\\)d\\{2\\}`)); // true console.log('r2.test(`foobar11`); ➜', r2.test(`foobar11`)); // false console.log(r2);

怎么運行的:

  1. 定義一個“標簽函數”(名稱無關緊要),例如regex() ) 從模板文字字符串構建我們的正則表達式( return new RegExp() )。

    提示:標簽函數甚至不需要返回字符串!

    • 第一個參數parts是一個string[] (注入變量之間的段)。

      a${..}bc${..}de['a', 'bc', 'de'] = parts

      提示: tag 函數的第一個參數上可用的特殊raw屬性允許您在輸入原始字符串時訪問它們,而無需處理轉義序列。

      這就是我們需要的! 我們可以解構第一個參數,並拆分出parts的第一part 這是我們的第一個 regExp result段。

    • subs...是來自注入變量的替換值:

      ..${'x'}..${'y'}..['x', 'y']

  2. 循環...subs並連接正則表達式字符串

    1. escape()當前stubs[i]特殊字符(這會破壞最終表達式)。
    2. 附加轉義stubs[i]並在其間添加parts[i]
  3. 模板文字字符串添加前綴,使用regex `...`並返回一個新的RegExp

    const r1 = regex `^ab(c${t1}e)`/^ab(cde)/


條件轉義

到目前為止, regex()所有模板變量..${..}..${..}..作為字符串處理。 (調用.toString()

regex `X${new Date()}X`          // /X7\.12\.2020X/
regex `X${{}}X`                  // /X\[object Object\]X/
regex `X${[1, 'b', () => 'c']}X` // /X1,b,\(\) => 'c'X/

即使這是預期的行為,我們也不能在所有地方都使用它。

如何在“標記模板”中連接正則表達式?

用例:您想要一個“RegExp 工廠類”從較小的部分構建更復雜的表達式。 例如,用於解析/驗證類似 javascript MIME 類型的Content-Type標頭值的 RegExp。 語法: media-type = type "/" subtype 這就是我們想要找到的:

  • */*
  • application/*application/javascriptapplication/ecmascript
  • text/* , text/javascript , text/ecmascript
const group = (name, regExp) => regex`(?<${name}>${regExp})`;

const rTypeGroup = group(`type`, `(text|application|\*)+?`);  
// /(?<type>\(text\|application\|\*\)\+\?)/ 
const rSubGroup = group(`sub`, `((java|ecma)+?script|\*)+?`); 
// /(?<sub>\(\(java\|ecma\)\+\?script\|\*\)\+\?)/
// .. and test
rTypeGroup.test(`application`);                   // false !!!
rTypeGroup.test(`\(text\|application\|\*\)\+\?`); // true !!!

由於regex()轉義了所有替換,因此我們的捕獲組的主體在字面上匹配。 對於某些類型,我們可以修改regex()並跳過escape() 現在我們可以傳遞 RegEx 實例並跳過escape()

 const escape = s => `${s}`.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, "\\\\$&"); const regex = ({ raw: [part, ...parts] }, ...subs) => new RegExp( // skip escape() subs.reduce( // ┏━━━━━━━━━┻━━━━━━━━━┓ (result, sub, i) => `${result}${sub instanceof RegExp ? sub.source : escape(sub)}${parts[i]}`, part ) ); const group = (name, regExp) => regex `(?<${name}>${regExp})`; // RegEx // ┏━━━━━━━━━━━┻━━━━━━━━━━━┓ const rTypeGroup = group(`type`, /(text|application|\\*)+?/); // /(?<type>(text|application|\\*)+?)/ const rSubGroup = group(`sub`, /((java|ecma)+?script|\\*)+?/); // /(?<sub>((java|ecma)+?script|\\*)+?)/ // Type console.log('rTypeGroup.test(`*`); ➜', rTypeGroup.test(`*`)); // true console.log('rTypeGroup.test(`text`); ➜', rTypeGroup.test(`text`)); // true console.log('rTypeGroup.exec(`*`).groups.type; ➜', rTypeGroup.exec(`*`).groups.type); // '*' console.log('rTypeGroup.exec(`text`).groups.type; ➜', rTypeGroup.exec(`text`).groups.type); // 'text' // SubType console.log('rSubGroup.test(`*`); ➜', rSubGroup.test(`*`)); // true console.log('rSubGroup.test(`javascript`); ➜', rSubGroup.test(`javascript`)); // true console.log('rSubGroup.test(`ecmascript`); ➜', rSubGroup.test(`ecmascript`)); // true console.log('rSubGroup.exec(`*`).groups.sub; ➜', rSubGroup.exec(`*`).groups.sub); // '*' console.log('rSubGroup.exec(`javascript`).groups.sub; ➜', rSubGroup.exec(`javascript`).groups.sub); // 'javascript'

我們現在可以決定是否應該轉義 var。 由於我們忽略了任何量詞和標志,我們的小組還驗證了ABCtextDEF12texttext 、...。

現在我們可以捆綁多個 RegEx:

//                                                 '/' would be escaped! RegEx needed..            
//                                                               ┏━┻━┓
const rMediaTypeGroup = group(`mediaType`, regex `${rTypeGroup}${/\//}${rSubGroup}`);
// /(?<mediaType>(?<type>(text|application|\*)+?)\/(?<sub>((java|ecma)+?script|\*)+?))/
rMediaTypeGroup.test(`text/javascript`)); // true

 const escape = s => `${s}`.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, "\\\\$&"); const regex = ({ raw: [part, ...parts] }, ...subs) => new RegExp( subs.reduce( (result, sub, i) => `${result}${sub instanceof RegExp ? sub.source : escape(sub)}${parts[i]}`, part ) ); const group = (name, regExp) => regex `(?<${name}>${regExp})`; const rTypeGroup = group(`type`, /(text|application|\\*)+?/); const rSubGroup = group(`sub`, /((java|ecma)+?script|\\*)+?/); // '/' would be escaped! RegEx needed.. // ┏━┻━┓ const rMediaTypeGroup = group(`mediaType`, regex `${rTypeGroup}${/\\//}${rSubGroup}`); // /(?<mediaType>(?<type>(text|application|\\*)+?)\\/(?<sub>((java|ecma)+?script|\\*)+?))/ console.log('rMediaTypeGroup.test(`*/*`); ➜', rMediaTypeGroup.test(`*/*`)); // true console.log('rMediaTypeGroup.test(`**/**`); ➜', rMediaTypeGroup.test(`**/**`)); // true console.log('rMediaTypeGroup.test(`text/javascript`); ➜', rMediaTypeGroup.test(`text/javascript`)); // true console.log('rMediaTypeGroup.test(`1text/javascriptX`); ➜', rMediaTypeGroup.test(`1text/javascriptX`)); // true console.log('rMediaTypeGroup.test(`*/java`); ➜', rMediaTypeGroup.test(`*/java`)); // true console.log('rMediaTypeGroup.test(`text/X`); ➜', rMediaTypeGroup.test(`text/X`)); // false console.log('rMediaTypeGroup.test(`/*`); ➜', rMediaTypeGroup.test(`/*`)); // false


標志

在初始化之前必須知道所有標志。 您可以閱讀它們( /xx/gm.flags/xx/gm.multiline/xx/i.ignoreCase ,..),但沒有設置器。

返回new Regex()的標記模板函數(例如regex() new Regex()需要知道所有標志。

本節演示了如何處理標志的 3 種替代方法。

  • 選項 A:將標志傳遞為${templateVariable} ➜ 不推薦!
  • 選項 B:擴展class RegExp
  • 選項 C:使用Proxy()作為標志

選項 A:將標志作為${templateVariable}傳遞。 ➜ 不推薦!

像其他替換變量一樣的線程標志。 我們需要檢查最后一個變量是否類似於標志gmi ,..)並將其與subs分開......

 const escape = s => `${s}`.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, "\\\\$&"); const regex = ({ raw: [part, ...parts], // super verbose... splitFlags = ({ // destruct subs[] Array (arrays are objects!) length: l, // destruct subs[].length to l iEnd = l - 1, // last array index to iEnd [iEnd]: sub, // subs[subs.length - i] to sub ...subs // subs[0...n-1] to subs }) => [ // returns RegEx() constr. params: [flags, concat regex string] // ┏━━━━━━━━ all chars of sub flag-like? ━━━━━━━━━┓ ┏━ flags ┏━ re-add last sub and set flags: undefined [...sub].every(f => ['s', 'g', 'i', 'm', 'y', 'u'].includes(f)) ? sub : !(subs[iEnd] = sub) || undefined, Object.values(subs).reduce( // concat regex string (result, sub, i) => `${result}${escape(sub)}${parts[i]}`, part ) ] }, ...subs) => new RegExp(...splitFlags(subs).reverse()); const r1 = regex `^foo(${`bar`})${'i'}`; console.log('r1:', r1, 'flags:', r1.flags); // /^foo(bar)/i ['i' flag] const r2 = regex `^foo(${`bar`})${'mgi'}`; console.log('r2:', r2, 'flags:', r2.flags); // /^foo(bar)/gim ['gim' flag] // invalid flag 'x' ━━━━┓ const r3 = regex `^foo(${`bar`})${'x'}`; console.log('r3:', r3, 'flags:', r3.flags); // /^foo(bar)x/ [no flags] // invalid flag 'z' ━━━━┓ const r4 = regex `^foo(${`bar`})${'gyzu'}`; console.log('r4:', r4, 'flags:', r4.flags); // /^foo(bar)gyzu/ [no flags]

代碼看起來超級冗長,並且從外部混淆標志邏輯替換邏輯並不明顯。 如果最后一個變量被錯誤地確定為flag-like ,它也會破壞最終的正則表達式。

我們尋找電話類型,例如i-phonea-phone 、...

const rPhoneA = regex `${`a`}-phone`; // 
console.log('rPhoneA:', rPhoneA, 'flags:', rPhoneA.flags); // /a-phone/     [no flags]
const rPhoneI = regex `${`i`}-phone`; 
console.log('rPhoneI:', rPhoneI, 'flags:', rPhoneI.flags); // /(?:)/i       ['i' flag]

console.log('rPhoneA.test(`a-phone`); ➜', rPhoneA.test(`a-phone`)); // true
console.log('rPhoneA.test(`i-phone`); ➜', rPhoneA.test(`i-phone`)); // false

console.log('rPhoneI.test(`a-phone`); ➜', rPhoneI.test(`a-phone`)); // true
console.log('rPhoneI.test(`i-phone`); ➜', rPhoneI.test(`i-phone`)); // true

...所有手機都是i-phones 因為i是一個類似標志的替代品,並且已從subs...數組中刪除,該數組現在為空[] reduce()返回一個空字符串''並且new RegExp('', 'i')添加一個空的非捕獲組: (?:)

選項 B:擴展class RegExp

我們可以從RegExp擴展並添加getter方法來設置標志。 讓它們自動返回,這樣我們就可以將它們鏈接起來。 我們甚至可以添加一個_ “清除標志”方法。

這很好用,但也有效果,每個添加/刪除的標志都會導致TRegExp的新克隆。 如果我們構建“靜態”(可緩存)表達式,這對您來說可能沒問題。

  • A:在循環內的構造函數中添加getter 或者..
  • B:添加類吸氣劑

 const escape = s => `${s}`.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, "\\\\$&"); class TRegExp extends RegExp { constructor(...args) { super(...args); // Clear all flags Object.defineProperty(this, '_', { get() { return this.flags.length ? new TRegExp(this.source) : this; }, enumerable: false }); // A: define getters for all flags ['g', 'i', 'm', 'u', 'y'].reduce((my, flag) => Object.defineProperty(my, flag, { get() { // clone this on flags change ━━┓ return my.flags.includes(flag) ? my : new TRegExp(my.source, `${my.flags}${flag}`); }, // return this ━━┛ enumerable: false }), this); } // B: Define getters for each flag individually // get g() { // return this.flags.includes('g') ? this : new TRegExp(this.source, `${this.flags}g`); // } } const regex = ({raw: [part, ...parts]}, ...subs) => new TRegExp( subs.reduce( // ┣━━ TRegExp() (result, sub, i) => `${result}${subs instanceof TRegExp ? sub.source : escape(sub)}${parts[i]}`, part ) ); console.log('TRegExp +flags:', regex `foo(bar)`.g); // /foo(bar)/g console.log('TRegExp +flags:', regex `foo(bar)`.im); // /foo(bar)/im console.log('TRegExp +flags:', regex `foo(bar)`.gi_.y); // /foo(bar)/y // ┗━━━━━┻━━━━ ( + 'g', + 'i', - 'gi', + 'y') const group = regex `(?<foo>(:?bar)\\d{2})`.gi; const t = `a bar12 bar0 bar13 bar-99 xyz BaR14 bar15 abc`; console.log([...t.matchAll(group)].map(m => m.groups.foo)); // ["bar12", "bar13", "BaR14", "bar15"]

選項 C:使用Proxy()作為標志

您可以代理“標記模板函數”,例如regex()並攔截其上的任何get() 代理可以解決很多問題,但也會導致很大的混亂。 您可以重新編寫整個函數並完全改變初始行為。

 const escape = s => `${s}`.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, "\\\\$&"); const _regex = (flags, {raw: [part, ...parts]}, ...subs) => new RegExp( subs.reduce( (result, sub, i) => `${result}${sub instanceof RegExp ? sub.source : escape(sub)}${parts[i]}`, part ), flags ); // ┗━━━ flags const regex = new Proxy(_regex.bind(undefined, ''), { get: (target, property) => _regex.bind(undefined, property) }); console.log('Proxy +flags:', regex.gi `foo(bar)`); // /foo(bar)/gi const r = /(:?bar)\\d{2}/; // matches: 'bar' + digit + digit ➜ 'bar12', 'abar123',.. const t = `(:?bar)\\d{2}`; // template literal with regExp special chars console.log('Proxy +flags:', regex.gi `(foo${r})`); // /(foo(:?bar)\\d{2})/gi console.log('Proxy +flags:', regex.gi `(foo${t})`); // /(foo\\(:\\?bar\\)d\\{2\\})/gi // flags ━┻━┛


更多閱讀

暫無
暫無

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

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