正则表达式:解析 GitHub 用户名(JavaScript)

8
我正在尝试从一段文本中解析GitHub用户名(以@开头),以便将它们链接到其关联的个人资料。GitHub用户名的限制如下:
- 只能包含字母数字和单个连字符(不能连续出现连字符) - 不能以连字符开头或结尾(如果以连字符结尾,只需匹配到该位置) - 最长39个字符
例如,以下文本:
"Example @valid hello @valid-username: @another-valid-username, @-invalid @in--valid @ignore-last-dash- an@email.com @another-valid?"
应匹配:
- @valid - @valid-username - @another-valid-username - @in - @ignore-last-dash - @another-valid
应忽略:
- @-invalid - an@email.com
我正在使用JavaScript接近解决此问题。
/\B@((?!.*(-){2,}.*)[a-z0-9][a-z0-9-]{0,38}[a-z0-9])/ig

但是这并不匹配单个字符的用户名(例如@a)。 以下是我的测试结果:https://regex101.com/r/rZ5eW1/2 当前的正则表达式是否有效?如何匹配一个非连字符的单个字符?

嗯...我想到了一个答案,但有一个问题:在@hfd.com的情况下,您希望它做什么?它应该将@htd作为用户名匹配并忽略.com,还是不匹配任何内容?我的代码执行前者。此外,在@invalid--username的情况下,您希望它将@invalid作为用户名匹配,还是忽略整个字符串? - anon
感谢您回复我。@whatever.com应该与@whatever匹配,因此我想让invalid--username匹配invalid是有道理的。 - Scott
在这种情况下,我的答案应该有效。我不确定是否可能做到像拒绝匹配一样的事情,如果除了一个特定的事情之外,它本来是一个好的匹配; 也许需要更复杂的东西,我不知道。 - anon
2
请注意,GitHub过去的用户名验证标准相对宽松,因此一些账户被保留下来(例如https://github.com/artur-)。支持人员告诉我,连字符允许出现在用户名开头和结尾,下划线也是可以的。因此,为了消除假阴性(但可能会有一些假阳性),您可能需要使用更简单的正则表达式,如`/\B@[a-z0-9_-]{1,39}/gi`。 - Piotr
3个回答

6
/\B@([a-z0-9](?:-(?=[a-z0-9])|[a-z0-9]){0,38}(?<=[a-z0-9]))/gi

注意:当此正则表达式遇到不能出现在用户名中的字符或字符集(例如.--)时,它会从@匹配到停止点。OP表示这样做没问题所以我会继续使用它。因此,如果下划线是匹配区域(而不是捕获区域):
@abc.123
@abc--123
@abc-

这是通过使用大量嵌套组来实现的。Regex101有一个很棒的分解, 但这里还是我的解释:

  1. \B: 这是一个内置的表示“不是单词边界”的方法,似乎能够解决问题,尽管如果像someones.@email.com这样的内容是有效的电子邮件地址,则可能会出现问题。但此时,它与在@引用开头的标点符号后不放空格的人的文本无法区分[1]

感谢Honore Doktorr指出JS中不存在负回顾前瞻

  1. @:只是字面上的@符号。少数几个地方,一个字符表示它本身。
  2. (...):捕获组。放置方式意味着它不会捕获@符号,只会匹配它,所以更容易获取用户名——无需获取子字符串。
  3. [a-z0-9]:匹配任何字母或数字的字符类。由于 i 标志,这也匹配大写字母。因为它是第一个字母,所以必须存在。
  4. (?:...):这是一个非捕获组。它将正则表达式块包装在一个组中,而不捕获它。
  5. ...|...:我们有两种替代方案,分别是...
  6. -(?= [a-z0-9]):连字符,紧随其后的是非连字符有效字符。
  7. [a-z0-9]:有效的非连字符字符。
  8. {0,38}:匹配非捕获组0到38次,包括39个字母最多。超过这个范围的任何内容都将被忽略。
  9. (?<=[a-z0-9]):这是一个正向后顾,JS支持它。它确保最后一个字符不是-——或者说是除连字符以外的有效字符。
这可以通过几种方式进行“优化”,但说实话,我可能会使用一个简单得多的正则表达式,并在此后对其进行一些验证,例如:
// somehow get the prospective username into `user`
if (user.startsWith('-')) { /* reject */ }
if (user.endsWith('-')) { /* reject */ }
if (user.contains('--')) { /* reject */ }

至少要解释你代码中的正则表达式。欢迎使用我的正则表达式,但请注明出处。


非常有帮助。感谢详细的解释。感谢 @honore-doktorr 的提示。 - Scott
很好的部件分解。 - Honore Doktorr

3
这个表达式也会匹配您的单词用户名。
/\B@(?!.*(-){2,}.*)[a-z0-9](?:[a-z0-9-]{0,37}[a-z0-9])?\b/ig

示例。解释:

  1. (?!.*(-){2,}.*):否定预查断言该模式的其余部分不能包含两个或更多相邻的破折号。
  2. [a-z0-9]:在@后面必须有一个字母数字字符。
  3. (?:[a-z0-9-]{0,37}[a-z0-9])?:可以有任意数量的字母数字字符或破折号,长度为0-37个字符,并在第2个模式之后紧跟一个字母数字字符,或者可能没有,以覆盖单字符用户名。(?:…)用于非捕获组。
  4. \b:整个模式必须以单词边界结束(包括-)。

我想我理解了其中的一部分。也许。你能加上一个它是如何工作的解释吗? - anon
2
回到这里已经快一年了,这让我想起这是多么聪明的做法。干得好! - anon
2
将这些属性发送给未来的我非常善良,也很聪明 - 谢谢! - Honore Doktorr

1
我正在使用我创建的简单RegExGoogle表单中获取github用户名,它运行得相当不错(只有一个非常罕见的注意事项):
^@\w(-\w|\w\w|\w){0,19}$

在哪里:

  • ^: 行首
  • @-: 符号 @ 和 - 本身。
  • \w: [A-Za-z0-9_],数字、字母(大小写均可)和下划线
  • $: 行尾
  • {0,19}: 重复括号前面的内容从零到十九次

总结:

  • 匹配的RegEx必须是整行(从^$
  • 它将以字母(大小写都可以),数字或下划线开头,后跟一个@ (@A@1@_)
  • 然后它将遵循重复模式(...){0,19}中的三个选项之一:

    • 短横线和一个\w(第一种选择)
    • 两个\w(第二种选择)
    • 一个单独的\w(第三种选择)

    这将重复并给出以下模式:

  • 零次:单个字母用户名

  • 一次:它可以是两个字母的用户名,或三个字母,或带有中划线的三个字符@w-w
  • 多次:它保证短横线不在开头或结尾,也不重复,在任何其他地方都可以。
  • 19次:如果仅使用第一和第二个选项,则最多可获得19*2=38个字符,加上开头的一个字符等于39个字符总数。如果随时使用第三个选项,则总大小将更小。

注意:

  • 它不能识别带有@ww-w...w的模式(第三个字母中有一个破折号,且长度为39个字符)。
  • 虽然它可以识别长度小于39个字符的@ww-w...w模式。

问题在于,要实现ww-w模式,该模式被分解为第一个w单独站立,后跟作为重复表达式中第三个选项的单个w(这只剩下18个),然后又重复一次作为w-(第一个选项,只剩下17个),而当剩下这17个时,我们只能得到17*2=34个字符。这意味着,最大长度为38个字符(34+2+1+1),而不是39个。

但对于我的目的来说,这真的没关系,所以如果你需要简单性,这里有一个可以给你相当好答案的RegEx。我希望它能帮助你在翻译成javascript时理解它。


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