代码之家  ›  专栏  ›  技术社区  ›  pretzelhammer Paras Bhattrai

从任意正则表达式创建字符串模板?

  •  1
  • pretzelhammer Paras Bhattrai  · 技术社区  · 6 年前

    正则表达式用于分析已格式化的字符串,但我希望使用它们获取原始字符串并格式化它们,例如:

    // phone number
    format("\(\d{3}\) \d{3}-\d{4}", "1234567890");
    // should return "(123) 456-7890"
    
    // date
    format("\d{4}-\d{2}-\d{2}", "20180712");
    // should return "2018-07-12"
    
    // arbitrary
    format("([A-Z]+-\d+ )+", "ABC123DEFGH45IJ6789");
    // should return "ABC-123 DEFGH-45 IJ-6789 "
    

    以上是 只是个例子 ,请 我想要一个对任意正则表达式和任意字符串都有效的通用解决方案 (符合regex)。

    以下是我到目前为止所拥有的,有点不雅,能力有限,但确实满足了上面3个例子中的前2个:

    function consumeCharacters(amount) {
      return (characterArray) => {
        return characterArray.splice(0, amount).join('');
      };
    }
    
    function parseSimpleRegex(regexString) {
      // filter out backslash escapes
      let parsed = regexString.replace(/\\./g, (...args) => {
        return args[0][args[0].length-1];
      });
      
      // get literal characters
      let literals = parsed.split(/d\{\d\}/);
      
      // get variable symbols
      let variables = parsed.match(/d\{\d\}/g);
      let varFunctions = variables.map(variable => consumeCharacters(variable[2]));
      
      let result = [];
      while (literals.length > 0) {
        result.push(literals.shift());
        result.push(varFunctions.shift());
      }
      while (varFunctions.length > 0) {
        result.push(varFunctions.shift());     
      }
      
      // filter out undefineds & empty strings
      result = result.filter(resultPart => !!resultPart);
      return result;
    }
    
    function format(regexString, rawString) {
      let rawCharacters = rawString.split('');
      let formatter = null;
      try {
        formatter = parseSimpleRegex(regexString); 
      } catch (e) {
        return 'failed parsing regex';
      }
      let formattedString = formatter.map((format) => {
        if (typeof format === 'string') {
            return format;
        }
        if (typeof format === 'function') {
            return format(rawCharacters);
        }
      }).join('');
      return formattedString;
    }
    
    const testCases = [
      {
        args: ["\\(\\d{3}\\) \\d{3}-\\d{4}", "1234567890"],
        expected: "(123) 456-7890"
      },
      {
        args: ["\\d{4}-\\d{2}-\\d{2}", "20180712"],
        expected: "2018-07-12"
      },
      {
        args: ["([A-Z]+-\\d+ )+", "ABC123DEFGH45IJ6789"],
        expected: "ABC-123 DEFGH-45 IJ-6789 "
      },
    ];
    
    testCases.forEach((testCase, index) => {
      const result = format(...testCase.args);
      const expected = testCase.expected;
      if (result === expected) {
        console.log(`Test Case #${index+1} passed`);
      } else {
        console.log(`Test Case #${index+1} failed, expected: "${expected}", result: "${result}"`);
      }
    });

    对于更复杂的正则表达式,上面的解决方案是否可以缩放?还是有更好的替代方法?

    2 回复  |  直到 6 年前
        1
  •  2
  •   Poul Bak    6 年前

    一般的答案是:使用创建 groups 然后使用 replace 使用反向引用格式化输出。

    例如,使用第一个示例,使用以下正则表达式:

    /(\d{3})(\d{3})(\d{4})/
    

    它创建三个组,前3个数字,后3个数字和最后4个数字。

    现在格式,使用 string.replace 功能:采用以下更换方式:

    ($1) $2-$3
    

    我将在第一个组周围添加括号,添加空格,然后是第二个组,最后是连字符和最后一个组。

    使用方法:

    您可以这样创建formatphone函数:

    function formatPhone(rawPhone)
    {
        return rawPhone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
    }
    

    你可以用你的其他模式做类似的事情。

    编辑 以下内容:

    完全通用的soultion要求将原始字符串、regex模式和替换模式都传递给函数,如下所示:

    function format(rawString, regex, replacement)
    {
       return rawString.replace(regex, replacement);
    }
    

    其中regex和replacement必须遵循上述规则。

    编辑2 以下内容:

    我想你误解了一些东西。让我们举你的第一个例子:

    format("\(\d{3}\) \d{3}-\d{4}", "1234567890");
    

    这里的正则表达式很简单 不匹配 !!!所以简而言之,你不能做一个采用正则表达式格式的函数。正则表达式是为了 match (而且可能 代替 )如上图所示。

        2
  •  1
  •   Michał Turczyn    6 年前

    你可以用图案 (\d{3})(\d{3})(\d{4}) 换成 (\d{3})(d{3})(d{4}) ,从而产生 123-456-7890 是的。

    对于第三个示例,请使用: (\w{3})(\w{3})(\w{5})(\w{2})(\w{2})(\w{4}) 换成 \1-\2 \3-\4 \5-\6 ,它返回 ABC-123 DEFGH-45 IJ-6789 是的。

    一般使用 (\w{n})...(\w{m}) ,其中 n m 是一些整数,用于捕获p[字符串的一部分到粒子组(您可以使用数组指定这些整数)。您还可以在数组中提供分隔符来形成您的模式。

    Demo

    更新

    如我所说,一般的解决方案是提供块的大小,字符串应该被拆分成分隔符数组。请参见下面的代码:

    var str =  "ABC123DEFGH45IJ6789";
    var blockSizes = [3,3,5,2,2,4];
    var separators = ["-"," ","-"," ","-"];
    var pattern = "(\\w{" + blockSizes[0] + "})";
    var replacementPattern = "$1";
    var i;
    for(i = 1; i < blockSizes.length; i++)
    {
        pattern += "(\\w{" + blockSizes[i] + "})";
        replacementPattern += separators[i - 1] + "$" + (i + 1);
    }
    

    现在,只需使用这些模式来替换,就完成了:

    JS fiddle

    Regex demo