这个函数能用正则表达式重写吗?

9

我想重新格式化并验证用户是否提供了一个有效的比利时企业号码,因为输入可能是以下几个例子之一:

  • BE 0123.321.123
  • BE0123.321.123
  • BE0123 321 123
  • 0123.321.123
  • 123.321.123
  • 123321123

我已经编写了一个函数,用于验证和重新格式化输入为“显示”版本(BE 0123.123.123)和“代码”版本(123123123)。此函数如下所示。

formatAndValidateEnterpriseNumber = enterpriseNumber => {
    if(enterpriseNumber === undefined || !enterpriseNumber || (enterpriseNumber || '').length < 3) return { isValid: false, error: 'Please fill in your enterprise number' };

        //Remove space, dots, ...
        enterpriseNumber = enterpriseNumber.toUpperCase();
        enterpriseNumber = enterpriseNumber.replace(/[. ,:-]+/g, '');

        //Check for double country code
        const reDouble = /^[a-zA-Z]{4}/;
        if (reDouble.test(enterpriseNumber)) enterpriseNumber = enterpriseNumber.substring(2);

        if (enterpriseNumber.length < 9 || enterpriseNumber.length > 12) return { isValid: false, error: 'The length of the provided number is incorrect' };

        //Check country code
        const reBE = /^[a-zA-Z]{2}/;
        if (reBE.test(enterpriseNumber)) {
            //Check if country code = BE
            if (enterpriseNumber.slice(0, 2) !== 'BE') return { isValid: false, error: 'Please fill in a Belgian enterprise number' };
            // Remove country code
            else enterpriseNumber = enterpriseNumber.substring(2);
        }

        //Check if first digit is 0
        if (enterpriseNumber.length === 10 && enterpriseNumber.startsWith('0')) enterpriseNumber = enterpriseNumber.substring(1);

        //Check if enterpriseNumber is valid with modulo test
        if (parseInt(97 - (enterpriseNumber.slice(0, 7) % 97), 10) !== parseInt(enterpriseNumber.slice(7, 9), 10))
            return { isValid: false, error: 'The provided number is invalid'}

      return {
            isValid: true,
            enterpriseNumber: enterpriseNumber,
            displayEnterpriseNumber: `BE 0${enterpriseNumber.substring(0, 3)}.${enterpriseNumber.substring(3, 6)}.${enterpriseNumber.substring(6, 9)}`
      };
};

我认为这很混乱,想知道是否可以通过一个/两个正则表达式测试来重新格式化和验证用户的输入以改进它?

第二个问题:有时候对于账户或信用卡号码,输入框中已经包含了下划线和横线(-),并在输入时重新格式化数字。这种方法叫什么名字?是否可以针对比利时企业号码这样的特定事物进行操作?


3
在正则表达式中,模数测试不容易实现。 - trincot
3个回答

8

是的,你可以:

^(?:BE)?\s*[0-1]?(\d[. ]*){9}$

这个正则表达式应该可以解决问题!
这个来源(荷兰语)说明了什么是比利时的企业号码:
它带有国家代码:BE,后跟01,然后是9位数字。
链接到https://regex101.com/r/4SRHxi/4 解释:
  • ^:字符串必须以给定的正则表达式开头
  • (?:BE)?:查找包含BE的组,但?表示它匹配零次或一次- ?:表示查找但不捕获
  • \s*:查找与零或无限次匹配的空格
  • [0-1]?:检查是否存在一个零或一个,出现零次或一次
  • ((\d[. ]*){9}):检查9个数字是否跟随剩余的字符串,不管它们被多少个点或空格填充。每次迭代都作为第一个捕获组捕获。这在后面进行替换时变得很重要。
  • $:字符串必须结束
这将检查输入是否有效。
将其编辑为code版本很简单:
«code».replace(/^(?:BE)?\s*[0-1]?((\d[. ]*){9})$/g, function(){
      return arguments[1].replace(/\D/g, "");
});

g或全局修饰符可以确保删除所有不需要的字符。使用一个包含替换所有非数字字符的replace函数的函数来输出我们想要的结果。

document.querySelector("pre").textContent.split("\n").forEach(function(element){
  if (element.match(/^(?:BE)?\s*[0-1]?(\d[. ]*){9}$/))
  {
     console.log(element.replace(/^(?:BE)?\s*[0-1]?((\d[. ]*){9})$/g, function(){
      return arguments[1].replace(/\D/g, "");
     }));
  }
  else
  {
    console.log(`REJECTED: ${element}`);
  }

});
<pre>
BE 0123.321.123
BE0123.321.123
BE0123 321 123
BE 0123  321  123
BE 01 23 32 11 23
BE 0123 32 11 23
1123.321.123
123.321.123
123321123
AAA3434343A
BE  1233 445 4545 442
</pre>

现在将字符串重构为正确的用户友好形式变得很容易:

document.querySelector("pre").textContent.split("\n").forEach(function(element) {
  if (element.match(/^(?:BE)?\s*[0-1]?((\d[. ]*){9})$/)) {
    var stripped = element.replace(/^(?:BE)?\s*[0-1]?((\d[. ]*){9})$/g, function(){
          return arguments[1].replace(/\D/g, "");
    });

    //with the modulo check from your code added back in.
    if (97 - (parseInt(stripped.slice(0, 7), 10) % 97) == parseInt(stripped.slice(7, 9), 10)) {
      //use a literal string
      //use substring to put the dots between the sections of three numbers.
      var humanReadable = `BE 0${stripped.substring(0,3)}.${stripped.substring(3,6)}.${stripped.substring(6,9)}`;
      console.log(`CODE: ${stripped}`, `UI: ${humanReadable}`);
    }
  }

});
<pre>
BE 0123.321.123
BE0123.321.123
BE0123 321 123
0123.321.123
123.321.123
123321123
844256524
</pre>


第二个问题 是的,可以这样做,但需要您自己编写代码。

简单版:

document.querySelector("div.enterprisenumber > input").addEventListener("keydown", function(e) {
  let value = this.value;

  //prevent the input from going back to 0
  if ( (value.length == 0 && (e.key == "Backspace" || e.key == "Delete"))) {
    e.preventDefault();
    return false;
  }
}, true);

document.querySelector("div.enterprisenumber > input").addEventListener("keyup", function(e) {
  //reset to a value without dots 
  let value = this.value.replace(/\./g, "");

  //strip the leading zero
  const valueWithout = value;
  //calculate how much iterations we need of a groups of three digits.
  const i = Math.floor(valueWithout.length / 3);
  let newValue = "";
  //check if backspace or delete are used to make sure the dot can be deleted.
  if (valueWithout.length < 9 && !(e.key == "Backspace" || e.key == "Delete")) {
    //only fire when higher than zero
    if (i > 0) {
      let t;
      //t is the index
      for (t = 0; t < i; t++) {
      //slice the correct portion of the string and append a dot, unless we are at the end of the groups
        newValue += valueWithout.slice(t * 3, t * 3 + 3) + (t == 2  ? "" : ".");
      }
      //append the remainder that is not a group of three.
      newValue += valueWithout.slice((t) * 3);
    } else {
      //return the value as is.
      newValue = value;
    }
    //set the new value to the input.
    this.value = newValue;
  }
}, true);

document.querySelector("div.enterprisenumber > input").addEventListener("blur", function(e) {
  let passed = false;
  if (this.value.match(/^(?:BE)?\s*[0-1]?((\d[. ]*){9})$/))
  {
    const value = this.value.replace(/\./g, "");
    //with modulo check
    if (97 - (parseInt(value.slice(0,7), 10) % 97) == value.slice(7, 9))
    {
      passed = true;
    }
  }
  document.querySelector(".enterprisenumber").classList[(passed ? "remove" : "add")]("error");
});

//if focus then focus input
document.querySelector("div.enterprisenumber").addEventListener("click", function(e) {
 if (e.target && e.target.nodeName != "SELECT")
 {
  this.querySelector("input").focus();
 }
});
* {
  box-sizing: border-box;
  font-family: tahoma;
  font-size: 10pt;
}

div.enterprisenumber {
  border: 1px solid #747474;
  width: 300px;
  padding: 0px;
  display: grid;
  grid-template-columns: 25px 40px auto;
  border-radius: 10px;
}

div.enterprisenumber.error {
  border: 1px solid #ff0000;
}

div.enterprisenumber>span {
  grid-column: 1;
  border: 0px;
  padding: 5px;
  background: linear-gradient(to right, rgba(0,0,0, 0.8) 33%, rgba(255,243,54, 0.8) 33%, rgba(255, 243, 54, 0.8) 66%, rgba(255, 15, 33, 0.8) 66%, rgba(255, 15, 33, 0.8) 100%);
  color: #ffffff;
  font-weight: bold;
  text-shadow: 1px 1px #000000;
  border-radius: 10px 10px 10px 10px;
}

div.enterprisenumber>select {
  grid-column: 2;
  border: 0px;
  padding: 5px;
}

div.enterprisenumber>input {
  grid-column: 3;
  border: 0px;
  padding: 5px;
  border-radius: 0px 10px 10px 0px;
}
Enter: 844256524
<div class="enterprisenumber">
  <span>BE</span><select><option value="0">0</option><option value="1">1</option><input value="" maxlength="11" />
</div>


哇!感谢您的解释。我喜欢这个解决方案。然而,模数测试已经被删除以检查数字是否有效(前7位数字没有0或1在前面,%97应该是最后两位数字)。 - Thore
2
请查看我回答中发布的解决方案。取模检查应该单独进行。 - Mouser
更新了第一段代码片段中的示例,并且是的,它们返回false! - Mouser
1
已重新编写整个正则表达式。希望能够满足您的期望。Mouser离开了。 - Mouser
1
\s*? 应该只是 \s*。多余的懒惰并没有带给我们任何好处,只会在有效输入上导致更多的回溯。 - amalloy
显示剩余4条评论

2
这是一个BE ____.___.___的输入模式实现。该模式将保持不变,因此输入将始终包含"BE"前缀、空格和两个点。然后可以将验证集中在完整性和模数测试上。
请注意,输入要求第一组有4位数字,其中第一位必须是0或1。
最初的回答:这里是BE ____.___.___输入格式的实现。该格式将被保留,所以输入将包含“BE”前缀、一个空格和两个点。之后验证可以集中于完整性和取余测试。
需要注意的是,输入要求第一组有四个数字,而第一个数字必须是0或1。

const ent = document.getElementById("ent");
const out = document.getElementById("isvalid");

function format() {
    const re = /^\D*[2-9]+|\D+/g;
    const [i, j] = [this.selectionStart, this.selectionEnd].map(i => {
        i = this.value.slice(0, i).replace(re, "").length;
        return i + 3 + (i >= 4 + format.backspace) + (i >= 7 + format.backspace);
    });
    this.value = "BE " + this.value.replace(re, "").padEnd(10, "_")
                                   .replace(/(....)(...)(...).*/, "$1.$2.$3");
    this.setSelectionRange(i, j);
    format.backspace = false;
    out.textContent = validate(this.value) ? "is valid" : "is invalid";
}

function validate(num) {
    return /^BE [01](\d{3}\.){2}\d{3}$/.test(num) 
            && 97 - num.replace(/\D/g, "").slice(0, 8) % 97 === +num.slice(-2);
}

ent.addEventListener("input", format);
ent.addEventListener("keydown", (e) => format.backspace = e.key == "Backspace");
Belgian enterprise number: <input id="ent" value="BE ____.___.___">
<span id="isvalid"></span>


哇,这是一个很好的解决方案针对第二个问题。 - Mouser

2
对于您的示例字符串,您可以匹配以下内容:
^(?:BE\s?)?[01]?(\d{3}([. ])\d{3}\2\d{3}|\d{9})$

这将匹配

  • ^ 字符串的开头
  • (?:BE\s?)? 可选 BE,后跟可选的空格字符
  • [01]? 可选的零或一
  • ( 捕获组
    • \d{3} 匹配 3 个数字
    • ([. ]) 在组中捕获空格或数字以用作反向引用
    • \d{3}\2\d{3} 匹配 3 个数字、点或空格(\2 是反向引用)和 3 个数字
    • |
    • \d{9} 匹配 9 个数字
  • ) 关闭捕获组
  • $ 字符串的结尾

正则表达式演示

然后在替换中使用第一个捕获组,并将空格或点替换为空字符串。

let pattern = /^(?:BE\s?)?[01]?(\d{3}([. ])\d{3}\2\d{3}|\d{9})$/;
let strings = [
  "BE 0123.321.123",
  "BE0123.321.123",
  "BE0123 321 123",
  "0123.321.123",
  "123.321.123",
  "123321123",
];

strings = strings.map(x => x.replace(pattern, function(m, g) {
  let enterpriseNumber = g.replace(/[. ]/g, "");
  return `BE 0${enterpriseNumber.substring(0, 3)}.${enterpriseNumber.substring(3, 6)}.${enterpriseNumber.substring(6, 9)}`
}));

console.log(strings);


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接