Swift 2正则表达式的意外行为

时间:2015-10-14 00:29:30

标签: regex swift swift2

我正在尝试从HTML字符串中提取信息,但我得到了意想不到的结果。我正在使用的代码如下:

let html: NSString? = "<tbody><tr><td sortkey=\"20151003\">03 Oct 2015</td><td>8,852.61</td><td>1,383.68</td><td>Text</td></tr><tr><td sortkey=\"20151004\">04 Oct 2015</td><td>2,577.14</td><td>282.49</td><td>Text</td></tr></tbody>"

let rowPattern = "<tr>\\s*<td s.*?<\\/tr>"
let rowRegex = try! NSRegularExpression(pattern: rowPattern, options: [])
let rowMatches = rowRegex.matchesInString(String(html), options: [], range: NSMakeRange(0, html!.length))

for rowMatch in rowMatches {
    let rowString: NSString = html!.substringWithRange(rowMatch.resultByAdjustingRangesWithOffset(-9).range)

    print(rowString)

    let valPattern = "<td>(.*?)<\\/td>|<td.*?\"(.*?)\">.*?<\\/td>"
    let valRegex = try! NSRegularExpression(pattern: valPattern, options: [])
    let valMatches = valRegex.matchesInString(String(rowString), options: [], range: NSMakeRange(0, rowString.length))

    for valMatch in valMatches {
        print(valMatch.rangeAtIndex(1))
        // let value = rowString.substringWithRange(valMatch.rangeAtIndex(1))
        // print(value)
    }
}

输出是:

<tr><td sortkey="20151003">03 Oct 2015</td><td>8,852.61</td><td>1,383.68</td><td>Text</td></tr>
(9223372036854775807,0)
(47,8)
(64,8)
(81,4)

首先,请注意我需要将rowMatch的范围偏移-9才能获得正确的结果。我不知道为什么会这样。

其次,第一场比赛返回的范围是(9223372036854775807,0),显然不正确并引发错误。同样,我不明白这里出了什么问题,但我怀疑这可能是我的正则表达式模式的问题。其他范围是正确的。

有关信息,print(value)的预期输出为:

20151003
8,852.61
1,383.68
Text

修改

经过进一步的实验,我发现了以下内容:

valMatches[0].rangeAtIndex(2)为第一场比赛提供了正确的范围,但其余的则需要valMatches[0].rangeAtIndex(1)。我不确定这是否是正确的行为,或者是否是@ t4nhpt在下面的回答中提出的错误。无论哪种方式,如果有人能够解释发生了什么,那将是件好事。

2 个答案:

答案 0 :(得分:2)

第一个问题let html: NSString? = "..."可选, 因此String(html)评估为

Optional(...)

神秘偏移9是“Optional(”:)

的长度

要解决此问题,您可以解开String(html!)或声明html 作为非选择性的。无论哪种情况,都不需要resultByAdjustingRangesWithOffset(-9)

第二个问题是你的两个捕获组 图案:

let valPattern = "<td>(.*?)<\\/td>|<td.*?\"(.*?)\">.*?<\\/td>"

<td>8,852.61</td>匹配第一个选项,因此 第一个捕获组匹配8,852.61,所以 rangeAtIndex(1)设置为该字符串的范围,并且 rangeAtIndex(2)设置为(NSNotFound, 0)

<td sortkey="20151003">03 Oct 2015</td>匹配第二个 替代方案,因此rangeAtIndex(2)设置为 20151003rangeAtIndex(1)的范围是(NSNotFound, 0)

NSNotFound在64位平台上定义为Int.max,即2^63 - 1 = 9223372036854775807

总而言之,这给出了预期的结果:

let html: NSString = "<tbody><tr><td sortkey=\"20151003\">03 Oct 2015</td><td>8,852.61</td><td>1,383.68</td><td>Text</td></tr><tr><td sortkey=\"20151004\">04 Oct 2015</td><td>2,577.14</td><td>282.49</td><td>Text</td></tr></tbody>"

let rowPattern = "<tr>\\s*<td s.*?<\\/tr>"
let rowRegex = try! NSRegularExpression(pattern: rowPattern, options: [])
let rowMatches = rowRegex.matchesInString(String(html), options: [], range: NSMakeRange(0, html.length))

for rowMatch in rowMatches {
    let rowString: NSString = html.substringWithRange(rowMatch.range)

    print("rowString=\(rowString)")

    let valPattern = "<td>(.*?)<\\/td>|<td.*?\"(.*?)\">.*?<\\/td>"
    let valRegex = try! NSRegularExpression(pattern: valPattern, options: [])
    let valMatches = valRegex.matchesInString(String(rowString), options: [], range: NSMakeRange(0, rowString.length))

    for valMatch in valMatches {
        if valMatch.rangeAtIndex(1).location != NSNotFound {
            let value = rowString.substringWithRange(valMatch.rangeAtIndex(1))
            print(value)
        }
        if valMatch.rangeAtIndex(2).location != NSNotFound {
            let value = rowString.substringWithRange(valMatch.rangeAtIndex(2))
            print(value)
        }
    }
}

输出:

rowString=<tr><td sortkey="20151003">03 Oct 2015</td><td>8,852.61</td><td>1,383.68</td><td>Text</td></tr>
20151003
8,852.61
1,383.68
Text
rowString=<tr><td sortkey="20151004">04 Oct 2015</td><td>2,577.14</td><td>282.49</td><td>Text</td></tr>
20151004
2,577.14
282.49
Text

答案 1 :(得分:1)

加入两种模式似乎是一个错误。您可以将模式拆分为两部分,找到两个[NSTextCheckingResult]然后将它们连接在一起。作弊,哈哈。

  // let valPattern = "<td>(.*?)<\\/td>|<td.*?\"(.*?)\">.*?<\\/td>"
  let valPattern1 = "<td.*?\"(.*?)\">.*?<\\/td>"
  let valPattern2 = "<td>(.*?)<\\/td>"
  var valRegex = try! NSRegularExpression(pattern: valPattern1, options: [])
  var valMatches1 = valRegex.matchesInString(String(rowString), options: [], range: NSMakeRange(0, rowString.length))
  valRegex = try! NSRegularExpression(pattern: valPattern2, options: [])
  let valMatches2 = valRegex.matchesInString(String(rowString), options: [], range: NSMakeRange(0, rowString.length))

  valMatches1 += valMatches2

  for valMatch in valMatches1 {
       ...