String.IndexOf() returns 意外值 - 无法提取两个搜索字符串之间的子字符串

String.IndexOf() returns unexpected value - cannot extract substring between two search strings

用于处理网络故事中某些专有名词的脚本,以帮助我的阅读工具正确发音。

我通过

获取网页内容
$webpage = (Invoke-WebRequest -URI 'https://wanderinginn.com/2018/03/20/4-20-e/').Content

这个 $webpage 应该是字符串类型。

现在

$webpage.IndexOf('<div class="entry-content">')

returns 正确值,但

$webpage.IndexOf("Previous Chapter")

returns 意外值,我需要一些解释为什么或如何自己找到错误。

理论上,它应该通过我要替换的专有名词列表将页面 "body" 剪切,并将其推送到 htm 文件中。 一切正常,但 IndexOf("Prev...") 的值无效。

编辑: invoke-webrequest 之后我可以

Set-Clipboard $webrequest

和post在记事本++中,我可以找到'div class="entry-content"'和'Previous Chapter'。 如果我做类似

Set-Clipboard $webpage.substring(
     $webpage.IndexOf('<div class="entry-content">'),
     $webpage.IndexOf('PreviousChapter')
   )

我希望 Powershell 能够正确确定这些字符串的两个第一个实例并在它们之间进行剪切。 因此我的剪贴板现在应该有我想要的内容,但字符串比第一个更进一步出现。

tl;dr

  • 您对 String.Substring() method 的工作原理有一个 误解 :第二个参数必须是 长度 要提取的子字符串,而不是结尾 index(字符位置)- 见下文。

  • 作为替代,您可以使用更简洁(尽管更复杂)的正则表达式 使用
    -replace
    操作在单个操作中提取感兴趣的子字符串 - 见下文。

  • 总的来说,最好使用 HTML 解析器 来提取所需的信息,因为字符串处理 脆弱 (HTML 允许空格、引用样式等的变化)。


正如 Lee_Dailey 指出的那样,您对 String.Substring() method 的工作原理有一个 误解 :它的论点是:

  • a 起始索引0基于字符位置),
  • 应从中返回给定 length 的子字符串。

相反,您尝试传递另一个 index 作为 length 参数。

要解决这个问题,您必须从较高的索引中减去较低的索引,从而获得您想要的子字符串的长度想要提取:

一个简化的例子:

# Sample input from which to extract the substring 
#   '>>this up to here' 
# or, better,
#   'this up to here'.
$webpage = 'Return from >>this up to here<<'


# WRONG (your attempt): 
# *index* of 2nd substring is mistakenly used as the *length* of the
# substring to extract, which in this even *breaks*, because a length
# that exceeds the bounds of the string is specified.
$webpage.Substring(
  $webpage.IndexOf('>>'),
  $webpage.IndexOf('<<')
)

# OK, extracts '>>this up to here'
# The difference between the two indices is the correct length
# of the substring to extract.
$webpage.Substring(
  ($firstIndex = $webpage.IndexOf('>>')),
  $webpage.IndexOf('<<') - $firstIndex
)

# BETTER, extracts 'this up to here'
$startDelimiter = '>>'
$endDelimiter = '<<'
$webpage.Substring(
  ($firstIndex = $webpage.IndexOf($startDelimiter) + $startDelimiter.Length),
  $webpage.IndexOf($endDelimiter) - $firstIndex
)

关于 .Substring() 的一般注意事项:

在以下情况下,此 .NET 方法会抛出 异常 ,PowerShell 将其显示为 语句 终止错误;也就是说,默认情况下语句 本身 被终止,但执行 继续 :

  • 如果指定的索引超出字符串范围(基于 0 的字符位置小于 0 或大于字符串长度):

      'abc'.Substring(4) # ERROR "startIndex cannot be larger than length of string"
    
  • 如果指定一个长度,其端点将落在字符串的边界之外(如果索引加上长度产生的索引大于字符串的长度)。

      'abc'.Substring(1, 3) # ERROR "Index and length must refer to a location within the string"
    

也就是说,您可以使用单个 regex (regular expression) to extract the substring of interest, via the -replace operator:

$webpage = 'Return from >>this up to here<<'

# Outputs 'this up to here'
$webpage -replace '^.*?>>(.*?)<<.*', ''

关键是让正则表达式匹配整个字符串并通过捕获组提取感兴趣的子字符串((...)) 其值 (</code>) 然后可以用作替换字符串,有效地返回那个。</p> <p><sup>有关 <code>-replace 的更多信息,请参阅

注意:在您的特定情况下需要进行额外的调整,因为您正在处理多行字符串:

$webpage -replace '(?s).*?<div class="entry-content">(.*?)Previous Chapter.*', ''
  • 内联选项 ((?...)) s 确保元字符 . 也匹配 newline 字符(这样 .* 跨行匹配 ),默认情况下不匹配。

  • 请注意,如果搜索字符串恰好包含正则表达式 元字符,您可能必须将 转义 应用于要嵌入到正则表达式中的搜索字符串(在正则表达式上下文中具有特殊含义的字符):

    • 使用嵌入的文字字符串,\-根据需要转义字符;例如,将 .txt 转义为 \.txt

    • 如果要嵌入的字符串来自变量,首先将[regex]::Escape()应用于它的值;例如:

          $var = '.txt'
          # [regex]::Escape() yields '\.txt', which ensures 
          # that '.txt' doesn't also match '_txt"
          'a_txt a.txt' -replace ('a' + [regex]::Escape($var)), 'a.csv'