String.IndexOf () возвращает неожиданное значение - не может извлечь подстроку между двумя строками поиска - PullRequest
1 голос
/ 14 марта 2019

Скрипт для манипулирования некоторыми собственными именами в веб-истории, чтобы мой инструмент чтения мог правильно их произносить.

Я получаю содержимое веб-страницы через

$webpage = (Invoke-WebRequest -URI 'https://wanderinginn.com/2018/03/20/4-20-e/').Content

Эта веб-страница $ должна иметь тип String.

Now

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

возвращает правильное значение, но

$webpage.IndexOf("Previous Chapter")

возвращает неожиданное значение, и мне нужно некоторое объяснение, почему иликак я могу найти ошибку самостоятельно.

Теоретически она должна вырезать "тело" страницы, пропустить ее через список имен собственных, которые я хочу заменить, и вставить это в htm-файл.Это все работает, но значение IndexOf («Prev ...») - нет.

Редактировать: после invoke-webrequest я могу

Set-Clipboard $webrequest

и опубликовать это вNotepad ++, там я могу найти и «div class =« entry-content »» и «Предыдущая глава».Если я сделаю что-то вроде

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

, я бы ожидал, что Powershell правильно определит оба первых экземпляра этих строк и разделится между ними. Поэтому в моем буфере обмена теперь должно быть желаемое содержимое, но строкаидет дальше, чем первое вхождение.

1 Ответ

1 голос
/ 14 марта 2019

ТЛ; др

  • У вас было неправильное представление о том, как String.Substring() метод работает : вторым аргументом должна быть длина подстроки для извлечения, а не конец индекс (позиция символа) - см. ниже.

  • В качестве альтернативы вы можете использовать более краткую (хотя и более сложную) regex операцию с
    -replace
    для извлечения интересующая подстрока в одной операции - см. ниже.

  • В целом, лучше использовать HTML-парсер для извлечения желаемой информации, потому что обработка строк хрупкая (HTML допускает изменения в пустом пространстве , стиль цитирования, ...).


Как указывает Lee_Dailey , у вас было неправильное представление о том, как String.Substring() метод работает : его аргументы:

  • a начальный индекс (позиция символа на основе 0),
  • , из которого должна быть возвращена подстрока заданной длины .

Вместо этого вы попытались передать другой 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"
    

Тем не менее, вы можете использовать одно регулярное выражение ( регулярное выражение ) для извлечения интересующей подстроки через оператор -replace

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

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

Ключ заключается в том, чтобы регулярное выражение соответствовало всей строке и извлекало интересующую подстроку через группу захвата ((...)), значение которой ($1) может затем использоваться в качестве строки замены, эффективно возвращая только это.

Для получения дополнительной информации о -replace см. этот ответ .

Примечание: В вашем конкретном случае требуется дополнительная настройка, потому что вы имеете дело с mutiline строкой:

$webpage -replace '(?s).*?<div class="entry-content">(.*?)Previous Chapter.*', '$1'
  • Встроенный параметр ((?...)) s гарантирует, что метасимвол . также соответствует символ новой строки символов (так что .* соответствует в строках ), что по умолчанию это не так.

  • Обратите внимание, что вам, возможно, придется применить экранирование к строкам поиска для встраивания в регулярное выражение, если они содержат регулярные выражения метасимволы (символы со специальным значением в контекст регулярного выражения):

    • Со встроенными литеральными строками, \ - экранировать символы по мере необходимости; например, бежать .txt как \.txt

    • Если строка для вставки происходит из переменной , сначала примените [regex]::Escape() к ее значению; e.g.:

      $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'
      
...