1

有时我有一个文本输入表单,我想在用户输入有效 URL 之前禁用“接受”按钮。在此处或网络上搜索会发现大量正则表达式,但鉴于 URL 规范 (RFC-3986) 的复杂性,几乎不可能为它们编写自己的验证测试套件。一旦我的应用在 App Store 中,我怎么会知道由于正则表达式的缺陷我得到了多少假阴性?

其他时候,我需要从网站或其他文本中提取所有有效的 URL,并且想要获取它们的数组,以便我可以过滤它以仅说出那些指向图像文件的 URL。在这种情况下,错误的正则表达式不太可能成为问题,因为如果我错过了一两张图像,或者得到一个虚假的 URL,这不是一个大问题。无论如何,正则表达式越好,返回的图像列表越正确。

那么,我怎样才能几乎确定地将呈现的字符串验证为正确的 URL?此外,如果能够从任意文本中提取有效 URL,那将是一件好事。

4

2 回答 2

5

网络上有大量声称可以验证 URL 的正则表达式。大多数的问题是,虽然它们可以工作,但它们没有凭据——也就是说,不存在任何方法来证明它们的正确性。

URL 的参考规范是RFC-3986,在长时间搜索最佳正则表达式时,我绊倒了 Jeff Roberson 的正则表达式页面。他所做的是从规范开始,构建小的正则表达式来匹配 RFC 的低级部分,然后逐渐将它们构建成一个完整的表达式。

例如,这是如何获得完整的scheme

# From http://jmrware.com/articles/2009/uri_regexp/URI_regex.html Copyright @ Jeff Roberson
(⌽[A-Za-z][A-Za-z0-9+\-.]*)
# DFH Addition: change ⌽ from "?:" to "" to get capture groups of the various components

第一个 "(" 之后的 unicode 字符更改为 "?:",表示非捕获组,或 "" 将其转换为捕获组。请注意,这匹配包含一个或多个字符的单个字符在第二个“[]”组中,

authority使用以下表达式找到完整的:

# RFC-3986 URI component:  relative-part
(?: //                                                          # ( "//"
  (?: (⌽(?:[A-Za-z0-9\-._~!$&'()*+,;=:]|%[0-9A-Fa-f]{2}☯)* ) @)?     # authority DFH modified to grab the authority without '@'
  (⌽
    \[
    (?:
      (?:
        (?:                                                    (?:[0-9A-Fa-f]{1,4}:){6}
        |                                                   :: (?:[0-9A-Fa-f]{1,4}:){5}
        | (?:                            [0-9A-Fa-f]{1,4})? :: (?:[0-9A-Fa-f]{1,4}:){4}
        | (?: (?:[0-9A-Fa-f]{1,4}:){0,1} [0-9A-Fa-f]{1,4})? :: (?:[0-9A-Fa-f]{1,4}:){3}
        | (?: (?:[0-9A-Fa-f]{1,4}:){0,2} [0-9A-Fa-f]{1,4})? :: (?:[0-9A-Fa-f]{1,4}:){2}
        | (?: (?:[0-9A-Fa-f]{1,4}:){0,3} [0-9A-Fa-f]{1,4})? ::    [0-9A-Fa-f]{1,4}:
        | (?: (?:[0-9A-Fa-f]{1,4}:){0,4} [0-9A-Fa-f]{1,4})? ::
        ) (?:
            [0-9A-Fa-f]{1,4} : [0-9A-Fa-f]{1,4}
          | (?: (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) \.){3}
                (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
          )
      |   (?: (?:[0-9A-Fa-f]{1,4}:){0,5} [0-9A-Fa-f]{1,4})? ::    [0-9A-Fa-f]{1,4}
      |   (?: (?:[0-9A-Fa-f]{1,4}:){0,6} [0-9A-Fa-f]{1,4})? ::
      )
    | [Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&'()*+,;=:]+
    )
    \]
  | (?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}
       (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
  | 
  (?:[A-Za-z0-9\-._~!$&'()*+,;=]|%[0-9A-Fa-f]{2}☯)*
  )
  
  (?: : (⌽[0-9]*) )? # DFH addition to grab just the port
  
 (⌽   # DFH addition to get one capture group
  (⌽ / (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2}☯)* )*    # path-abempty
| /                                                             # / path-absolute
  (⌽:    (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2}☯)+
    (?:/ (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2}☯)* )*
  )?
| (⌽        (?:[A-Za-z0-9\-._~!$&'()*+,;=@] |%[0-9A-Fa-f]{2}☯)+     # / path-noscheme
    (?:/ (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2}☯)* )*
   ) # DFH Wrapper
|                                                            # / path-empty
      (⌽) # DFH addition so constant number of capture groups
 )
)                                                               # )

# DFH Addition: change ☯ to "|[\u0080-\U0010ffff]" to get inline Unicode detection (making this an IRI, not a URI, but you can later hex encode it), or "" for standard behavior
# DFH Addition: change ⌽ from "?:" to "" to get capture groups of the various components

如果您阅读了上面的内容,您会发现这个表达式可以通过在几个地方添加“|[\u0080-\U0010ffff]”来扩展为查找 Unicode 字符。

因为他实际上是从 RFC 开始的,并且他表达的所有部分都完全引用了 ABNF 规范,所以我对它们很有信心。

但是,当我开始测试时,我发现 say 的 URL 验证器http://通过了!事实证明,该规范几乎允许所有内容都是空字符串!有点难以将其用于 UI 表单验证器。

所以我把他的表情,做了一些小的补充。首先,我发现我可以将路径说明符从“*”更改为“?”,这样在表单输入中,用户将被迫在“http://”之后键入至少一个“/”。这使得验证器比它需要的更严格,但更现实。

Jeff 的正则表达式只使用非捕获组,所以我研究了支持捕获组的方法,因此如果需要,可以提取 URL 的所有组件。

另外,想想非美国用户,他们经常需要在 URL 中输入非 ASCII 字符——他们想输入重音字符——但普通验证器会拒绝 Unicode 字符。最好验证一个包含 unicode 字符的字符串,然后在实际使用它之前将 unicode 转换为 '%' 编码的十六进制。|[\\u0080-\\U0010ffff]这需要通过添加到接受 ASCII 的部分来扩展表达式以接受 unicode 字符。

整个问题需要组合一个测试工具,该工具可以构造一个或多个正则表达式以及给定应用程序可能需要的选项,并且可以针对各种测试字符串进行测试;因此承担了 URLFinderAndVerifier

测试工具使用来自 Jeff 页面的扩展表达式字符串,它们的所有空格和注释都完好无损,并带有我所做的附加注释。这些使表达式更易于阅读和理解。测试应用程序读取文本文件并删除所有注释和空格,根据 UI 中选择的选项对它们进行预处理,然后设置它们以供使用或粘贴(以便您可以在应用程序中使用它们)。测试应用程序还允许您在交互模式下使用它,它会在您修改输入文本时进行验证。

选项:

  • 查找 http/https、http/https/ftp 或任何方案

  • 对于表单输入,在“scheme://”之后需要一个“/”,这使得“接受”按钮的切换更加真实(在查询的“?”和框架的“#”之后也需要至少一个字符)

  • 启用捕获组,因此对于每个 URL 提取方案、用户信息、主机、端口、路径,以及可选的查询和/或片段)

  • 在提取模式下,包含或排除查询和/或片段

用法:

  • 克隆项目,并确定你想要的正则表达式,然后将其粘贴到结果窗口并在你的应用程序中使用它(适用于文本文件或代码中的 NSString)

  • 将 URLFinder 接口和实现文件复制到您的项目中

  • 实例化一个 URLFinder 并为它提供第一步中的正则表达式。

于 2013-05-31T19:22:57.273 回答
1

当然,验证 url 的最简单方法是构造一个NSURL对象。

NSURL *url = [NSURL URLWithString:urlString];

根据文档

必须是符合 RFC 2396 的 URL。

如果字符串格式错误,则返回 nil。

最终,您可能NSURL无论如何都希望将 url 转换为对象,因此它可能是决定您的字符串是否有效的最佳位置。

然后要在文本块中查找 url,您可以执行非常简单的正则表达式搜索,只寻找潜在的候选人。例如,像这样:

[^\s]+://[^\s]+

然后使用上述NSURL构造技术来验证这些候选者是否是真正的匹配项。

于 2013-05-31T20:15:30.240 回答