Урок 4. Практика

Автор Redline


Урок 4. Практика


Выбор инструмента


Для решения любой задачи с использованием RegExp необходимо определить что нужно получить в итоге, и выбрать инструмент.
Задачи, которые решаются с помощью RegExp (из первого урока):

Практические примеры


Не буду приводить примеров для проверки соответствия текста шаблону, т.к. такие RegExp ничем не отличаются от других, кроме информативности - она крайне скудна.

Извлечение информации


Пример 1
Задача:
С сайта гисметео получить текущую температуру за окном для города Ялта.

Решение:
Заходим на этот сайт любым браузером, ищем ссылку для города Ялта и переходим по ней
Далее можно найти решение через библиотеку _IE с использованием коллекций и прочих финтифлюшек. Но мы будем решать с использованием сурового RegExp!
Для этого нам необходимо получить каркас этой html-страницы в скрипт:
Способ 1:

#include <IE.au3>
$sUrl = 'http://www.gismeteo.ru/city/daily/5002/'
$oIE = _IECreate($sUrl)
$sHTML = _IEBodyReadHTML($oIE)
ConsoleWrite($sHTML & @CRLF)


Способ 2:

$sUrl = 'http://www.gismeteo.ru/city/daily/5002/'
$oHTTP = ObjCreate('WinHttp.WinHttpRequest.5.1')
$oHTTP.Open('GET', $sUrl, False)
$oHTTP.Send('')
$oHTTP.WaitForResponse
$sHTML = $oHTTP.ResponseText
ConsoleWrite($sHTML & @CRLF)


Способ 3:

$sUrl = 'http://www.gismeteo.ru/city/daily/5002/'
$sHTML = BinaryToString(InetRead($sUrl), 4)
ConsoleWrite($sHTML & @CRLF)


Все способы имеют право жить и вполне рабочие, критических различий между результатами нет, кроме знака "градус" - первый способ выводит его одним символом, а второй и третий в виде "&deg;", кроме того третий способ имеет проблемы с кодировкой (для перевода в кодировку UTF-8 в функции BinaryToString нужно указать параметр 4). Я обычно пользуюсь вторым способом он и будет использоваться в примерах далее.
Теперь нам нужно проанализировать полученный текст. Копируем полученный текст из консоли в любой редактор (желательно с подсветкой html-тегов для удобства) можно даже в сам SciTE, и ищем искомую температуру "+27". Итог поисков приводит нас к фрагменту:

<div class="rframe" id="weather"><div class="fcontent">
<h2>Погода за окном</h2>
<div class="section">
<h3 class="typeM">Ялта</h3>
<div class="scity"><a href="/catalog/ukraine/316/">Крым АР</a>&nbsp;/&nbsp;<a href="/catalog/ukraine/">Украина</a></div>
<dl class="cloudness">
<dt class="png" style="background-image: url(http://i.gismeteo.com/static/images/icons/new/d.sun.png)"><br></dt>
<dd>Ясно</dd>
</dl>
<div class="temp">+27&deg;C</div>
<div class="wicon barp">754<span class="unit">мм рт.ст.</span></div>
<div class="wicon wind"><dl title="Южный" class="wicon wind5"><dt>Ю</dt><dd>2<span class="unit">м/с</span></dd></dl></div>
<div class="wicon hum">21<span class="unit">%</span></div>
<div class="wrap f_link">

<span class="icon date">04.06.2011 9:00:00</span>
<a class="icon fcast" href="/city/daily/5002/">Прогноз</a>
</div>
</div><!--block expire GMT+0: 2011-06-04 8:43:10--> </div></div>


В этом фрагменте нас интересует только

<div class="temp">+27&deg;C</div>

По идее все - можно вставлять обрамляющие теги в RegExp и мы получим то, что хотим. Но если внимательно просмотреть весь текст, то можно найти несколько таких тегов (<div class="temp">):

<div class="wtabs wrap">
<span class="prev"></span>

<a href="/city/daily/5002/2/" class="next"></a>

<div class="wtab swtab" id="tab_wdaily1">
<img class="cloudness png" src="http://i.gismeteo.com/static/images/icons/new/d.sun.c2.r1.png" alt="">
<dl class="date wrap weekend"><dt>СБ</dt><dd>04.06</dd></dl>
<div class="temp">+20&deg;..<em>+27</em>&deg;</div>
</div>

<div class="wtab" id="tab_wdaily2">
<img class="cloudness png" src="http://i.gismeteo.com/static/images/icons/new/d.sun.c2.r1.png" alt="">
<dl class="date wrap weekend"><dt>ВС</dt><dd>05.06</dd></dl>
<div class="temp">+21&deg;..<em>+25</em>&deg;</div>
</div>

<div class="wtab" id="tab_wdaily3">
<img class="cloudness png" src="http://i.gismeteo.com/static/images/icons/new/d.sun.c2.r1.png" alt="">
<dl class="date wrap"><dt>ПН</dt><dd>06.06</dd></dl>
<div class="temp">+20&deg;..<em>+25</em>&deg;</div>
</div>
</div>


Чем же отличаются эти два фрагмента?
Во-первых, у них разные "родительские" контейнеры (<div class="rframe" id="weather"> и <div class="wtabs wrap">), которые больше нигде не повторяются, а это именно то к чему необходимо "привязываться" при составлении RegExp.
Во-вторых, второй фрагмент внутри <div class="temp"></div> содержит теги "em".
В-третьих, можно заметить, что искомый нам тег идет первым среди <div class="temp"> во всем тексте - это тоже можно использовать.
В-четвертых, формат искомых данных выглядит так - "знак, цифры и "&deg;C", и он не похож на другие контейнера <div class="temp"></div> (ни один из них не заканчивается знаком "C").
Преобразования из "&deg;C" в "°C" будем проделывать через StringReplace, преобразование с помощью самого выражения используем только в четвертом способе, т.к. при такой замене первые три способа сведутся к четвертому ;)
Вы сами при желании можете найти еще множество зацепок, к которым можно "привязаться".
Мы решим эту задачу четырьмя способами!
Способ 1. С учетом родительского контейнера
Составляем шаблон:
1. Следует учесть флаги. Т.к. html-документ может содержать теги вида <div>/<DIV>/<Div>/и т.п., то нам понадобится флаг отключения чувствительности к регистру "(?i)". С текстом мы будем работать как с одной строкой, поэтому нам нужен флаг "(?s)", который дает возможность точке (.) быть любым символом, в том числе символом новой строки.
2. Нам нужно учесть родительский контейнер "<div class="rframe" id="weather">", в итоге получим:
(?si)<div class="rframe" id="weather">
3. Учтем все, что идет до тега "<div class="temp">"
(?si)<div class="rframe" id="weather">.*?
4. Впишем сам тег "<div class="temp">":
(?si)<div class="rframe" id="weather">.*?<div class="temp">
5. Добавим группу с захватом:
(?si)<div class="rframe" id="weather">.*?<div class="temp">(.*?)
6. Впишем символ нового тега "<", для ограничения группы с захватом:
(?si)<div class="rframe" id="weather">.*?<div class="temp">(.*?)<
Собственно, шаблон готов, его можно уже использовать, но давайте его еще подсократим -
поскольку слово ["weather"] встречается единожды во всем тексте - смело удаляем всё, что идет в этом теге кроме него, а так же заменим пробелы на "\s" в результате получаем:
"(?si)"weather".*?<div\sclass="temp">(.*?)<"
Важно! Этот шаблон можно использовать в StringRegExp, а вот для того, чтобы использовать этот шаблон в StringRegExpReplace к нему нужно добавить ".*?" ("?" не даст "съесть" слишком много текста, до того как он встретит сочетание, описанное в шаблоне далее) в начале и ".*" в конце шаблона, т.к. при выдаче результата с использованием обратных ссылок, шаблон должен включать в себя весь текст, а не только его ключевой кусок.
Такой шаблон найдет в тексте слово "weather", затем первый идущий за ним контейнер класса "temp" и выдаст его содержимое.

$sUrl = 'http://www.gismeteo.ru/city/daily/5002/'
$oHTTP = ObjCreate('WinHttp.WinHttpRequest.5.1')
$oHTTP.Open('GET', $sUrl, False)
$oHTTP.Send('')
$oHTTP.WaitForResponse
$sHTML = $oHTTP.ResponseText
$sResult = StringRegExpReplace($sHTML, '(?si).*?"weather".*?<div\sclass="temp">(.*?)<.*', '$1')
$sResult = StringReplace($sResult, '&deg;', '°')
ConsoleWrite($sResult & @CRLF)


Способ 2. С учетом отсутствия дочерних "em" и "/em" тегов
1. Берем флаги из способа №1
2. Дописываем искомый контейнер класса "temp", группу с захватом за ним и знак закрывающегося тега "</":
"(?si)<div\sclass="temp">(.*?)</"
3. Исключаем попадание в группу с захватом любых тегов (в том числе и "em" с "/em"), простой заменой "." на набор типа "ни один символ из набора", куда включим знак открывающегося тега "<":
"(?si)<div\sclass="temp">([^<]*?)</"
Шаблон готов!
Этот шаблон найдет в тексте контейнер класса "temp", после чего начнет "есть" всё кроме знака "<", после группы он должен встретить "</" и такое возможно только в контейнере без вложенных тегов.

$sUrl = 'http://www.gismeteo.ru/city/daily/5002/'
$oHTTP = ObjCreate('WinHttp.WinHttpRequest.5.1')
$oHTTP.Open('GET', $sUrl, False)
$oHTTP.Send('')
$oHTTP.WaitForResponse
$sHTML = $oHTTP.ResponseText
$sResult = StringRegExpReplace($sHTML, '(?si).*?<div\sclass="temp">([^<]*?)</.*', '$1')
$sResult = StringReplace($sResult, '&deg;', '°')
ConsoleWrite($sResult & @CRLF)


Способ 3. С использованием расположения в тексте (первый тег среди всех <div class="temp"> и есть искомый)
1. Берем стандартные флаги.
2. Дописываем искомый контейнер класса "temp", группу с захватом за ним и знак "<":
"(?si)<div\sclass="temp">(.*?)<"
Шаблон готов!
Данный шаблон найдет в тексте контейнер класса "temp" и "съест" все до от ">" до следующего "<".

$sUrl = 'http://www.gismeteo.ru/city/daily/5002/'
$oHTTP = ObjCreate('WinHttp.WinHttpRequest.5.1')
$oHTTP.Open('GET', $sUrl, False)
$oHTTP.Send('')
$oHTTP.WaitForResponse
$sHTML = $oHTTP.ResponseText
$sResult = StringRegExpReplace($sHTML, '(?si).*?<div\sclass="temp">(.*?)<.*', '$1')
;такой шаблон найдет последний контейнер класса "temp":
;$sResult = StringRegExpReplace($sHTML, '(?si).*<div\sclass="temp">(.*?)<.*', '$1')

$sResult = StringReplace($sResult, '&deg;', '°')
ConsoleWrite($sResult & @CRLF)


Способ 4. С описанием формата искомой строки
1. Берем стандартные флаги.
2. Дописываем искомый контейнер класса "temp".
"(?si)<div\sclass="temp">"
3. Дописываем группу с захватом:
"(?si)<div\sclass="temp">((\+|-)?\d+)"
Расшифровка:
"(\+|-)?" - знак плюс или минус, который может быть или нет (при температуре 0 град.)
"\d+" - любой количество цифровых символов, но минимум один
4. Дописываем, то что должно идти после цифр до закрытия контейнера:
"(?si)<div\sclass="temp">((\+|-)?\d+)&deg;C"
Шаблон готов!
Такой шаблон найдет контейнер класса "temp", затем знак плюс или минус, которого может и не быть, после которого идут цифры и строка "&deg;C".

$sUrl = 'http://www.gismeteo.ru/city/daily/5002/'
$oHTTP = ObjCreate('WinHttp.WinHttpRequest.5.1')
$oHTTP.Open('GET', $sUrl, False)
$oHTTP.Send('')
$oHTTP.WaitForResponse
$sHTML = $oHTTP.ResponseText
$sResult = StringRegExpReplace($sHTML, '(?si)<div\sclass="temp">[/b]((\+|-)?\d+)&deg;C', '$1°C')
ConsoleWrite($sResult & @CRLF)


Обратите внимание на "$1°C" в конце выражения - это выдаст соответствие первой обратной ссылке "знак и число" и допишет после них знак градуса и букву "C".
Итог: все способы выдадут одинаковый результат. Способ №4 наиболее рациональный, также отмечу способ №2 - он не зависит от расположения других тегов и от расположения контейнера "<div class="temp">" в тексте.
Пример 2
Задача:
С сайта сбербанка получить значение курса основных валют и драгоценных металлов.
Решение:
Как и в примере №1 находим кусок кода, на котором находятся интересующие нас значения:

...
<td class="img"><img src="/common/img/eggs/strelka_grin2.gif" width="5" height="3" alt="повышение" title="повышение: 0,05"></td>
<td style="font-weight:bold;font-size:14px;color:#339900">
27,5 </td>
<td class="img"><img src="/common/img/eggs/strelka_grin2.gif" width="5" height="3" alt="повышение" title="повышение: 0,05"></td>
<td style="font-weight:bold;font-size:14px;color:#339900">
28,2 </td>
...
<td style="font-weight:bold;font-size:14px">
40,3 </td>
<td class="img"><img src="/common/img/0.gif" width="5" height="3" alt=""></td>
<td style="font-weight:bold;font-size:14px">
41,05 </td>
...
<td style="font-weight:bold;font-size:14px">1357</td>
<td class="img"><img src="/common/img/0.gif" width="5" height="3" alt=""></td>
<td style="font-weight:bold;font-size:14px">
1405 </td>
</tr>
<tr style="height: 21px;">
<td style="text-align:center; width:30px;">
<a href="/moscow/ru/quotes/metals/timeline/index.php?qid190=6">
...
<td style="font-weight:bold;font-size:14px;color:#cc0000">31,84</td>
<td class="img"><img src="/common/img/eggs/strelka_red2.gif" width="5" height="3" alt="падение" title="падение: -0,12"></td>
<td style="font-weight:bold;font-size:14px;color:#cc0000">
33,76 </td>


Конкретно нам нужны значения из этих ячеек:

<td style="font-weight:bold;font-size:14px;color:#339900">
27,5 </td>
<td style="font-weight:bold;font-size:14px;color:#339900">
28,2 </td>
<td style="font-weight:bold;font-size:14px">
40,3 </td>
<td style="font-weight:bold;font-size:14px">
41,05 </td>
<td style="font-weight:bold;font-size:14px">1357</td>
<td style="font-weight:bold;font-size:14px">
1405 </td>
<td style="font-weight:bold;font-size:14px;color:#cc0000">31,84</td>
<td style="font-weight:bold;font-size:14px;color:#cc0000">
33,76 </td>


Нужные нам значения находятся в ячейках таблицы, стиль которых различается только цветом текста (красный, зеленый или черный), текст внутри ячеек состоит из пробелов, цифр и знака ",", других ячеек с такими параметрами на странице нет.
Составляем шаблон:
1. Используем стандартные флаги.
2. Дописываем строку с ячейкой и частью ее стиля, в качестве цвета или его отсутствия используем конструкцию ".*?>":
(?si)<td style="font-weight:bold;font-size:14px.*?>
3. Добавляем группу с захватом в которую попадут только цифровые символы и запятая, перед группой добавим возможные пробелы:
(?si)<td style="font-weight:bold;font-size:14px.*?>\s*([\d,]+)
Шаблон готов!

#include <Array.au3>
$sUrl = 'http://sbrf.ru'
$oHTTP = ObjCreate('WinHttp.WinHttpRequest.5.1')
$oHTTP.Open('GET', $sUrl, False)
$oHTTP.Send('')
$oHTTP.WaitForResponse
$sHTML = $oHTTP.ResponseText
$aResult = StringRegExp($sHTML, '(?si)td style="font-weight:bold;font-size:14px.*?>\s*([\d,]+)', 3)
_ArrayDisplay($aResult)


Пример выдаст массив со всеми значениями курсов валют и металлов, дальше можно делать с ними что угодно.
Пример 1
Дано:
Строка с параметрами и их значениями, параметры могут иметь разные значения или не иметь их, в названии параметров не может быть пробелов, между собой параметры разделены одиночной табуляцией:

id=10 money=0 age= name=John Doe status= City=none nationality=


Задача:
Получить массив параметров и их значений.

Решение:
1. Обойдемся без флагов.
2. Пишем шаблон для параметров - любые символы кроме пробелов, минимум 1 символ:
([^\s]+)
3. Дописываем разделитель "="
([^\s]+)=
4. Дописываем вторую часть для значений параметров - любые символы кроме табуляции (в том числе и 0 символов для случая, когда значение параметра не указано):
([^\s]+)=([^\t]*)
Шаблон готов!

#include <Array.au3>
$sStr = 'id=10'&@TAB&'money=0'&@TAB&'age='&@TAB&'name=John Doe'&@TAB&'status='&@TAB&'City=none'&@TAB&'nationality='
$aResult = StringRegExp($sStr, '([^\s]+)=([^\t]*)', 3)
_ArrayDisplay($aResult)


Примечание: при копировании с форума отступы табуляции теряются или преобразовываются, поэтому в примере табуляция выставлена принудительно, но вы можете расставить табуляцию самостоятельно и проверить работу скрипта при этом.
Усложним задачу - пусть параметры между собой разделяются простыми пробелами:

id=10 money=0 age= name=John Doe status= City=none nationality=


Решение:
1. Первую часть оставим без изменений:
([^\s]+)=
2. Часть со значениями параметров должна включать в себя все что идет перед следующим параметром, т.е. перед словом, за которым стоит знак "=" или, в случае когда это последний параметр в строке - всё до конца строки.
Здесь мы воспользуемся позитивным просмотром вперед.
Дополним шаблон группой с захватом, после которой идет или отсутствует (для последнего параметра в строке)пробел, затем записано условие просмотра вперед, да не простое, а с выбором из двух значений:
([^\s]+)=(.*?)\s?(?=[^\s]+=|$)
Расшифровка условия просмотра:
"?=" - позиция перед...
"[^\s]+=" - ... любым количеством символов, за исключением пробелов, за которым идет знак "="
"|$" - ...или концом строки
Т.е. выражение после того как найдет знак "=" будет искать пробел (который может и отсутствовать), чтобы пробелы не попали в итоговый массив, а затем оно будет искать такую позицию курсора, при которой за ним идет следующий параметр со знаком "=" или конец строки.

#include <Array.au3>
$sStr = 'id=10 money=0 age= name=John Doe status= City=none nationality='
$aResult = StringRegExp($sStr, '([^\s]+)=(.*?)\s?(?=[^\s]+=|$)', 3)
_ArrayDisplay($aResult)



Изменение информации (замена)


Пример 1
Дано:
css-код:

body{
margin: 0;
padding: 0;
background-color: #СССССС;
font-family: Tahoma, Verdana;
font-size: 10pt;
font-weight: bold;
white-space: nowrap;
}

.cont{
border: 10px solid #000000;
background-color: #DDDDDD;
clear: both;
height: 150px;
padding: 10px;
margin: 4px;
}


Задача:
Изменить значение background-color для блока ".cont"
Решение:
Поскольку background-color встречается несколько раз, то придется "цепляться" за сам блок ".cont".
Составляем шаблон:
1. Т.к. текст многострочный, то используем флаг "(?s)"
2. Допишем строку для имени блока:
(?s)\.cont{
3. Добавим строки до background-color заменив на стандартное ".*?" и допишем саму строку без параметра:
(?s)\.cont{.*?background-color:\s#
4. Добавляем группу с захватом, для значения параметра до ";":
(?s)\.cont{.*?background-color:\s#([^;]*)
5. Поскольку StringRegExpReplace при заменах использует весь описанный текст шаблона, то нужно весь текст слева от группы с захватом также добавить в такую группу для дальнейшей выдачи через обратную ссылку:
(?s)(\.cont{.*?background-color:\s#)([^;]*)
Шаблон готов!

$sCSS = 'body{' & @CRLF
$sCSS &= '  margin: 0;' & @CRLF
$sCSS &= '  padding: 0;' & @CRLF
$sCSS &= '  background-color: #СССССС;' & @CRLF
$sCSS &= '  font-family: Tahoma, Verdana;' & @CRLF
$sCSS &= '  font-size: 10pt;' & @CRLF
$sCSS &= '  font-weight: bold;' & @CRLF
$sCSS &= '  white-space: nowrap;' & @CRLF
$sCSS &= '}' & @CRLF
$sCSS &= '' & @CRLF
$sCSS &= '.cont{' & @CRLF
$sCSS &= '  border: 10px solid #000000;' & @CRLF
$sCSS &= '  background-color: #DDDDDD;' & @CRLF
$sCSS &= '  clear: both;' & @CRLF
$sCSS &= '  height: 150px;' & @CRLF
$sCSS &= '  padding: 10px;' & @CRLF
$sCSS &= '  margin: 4px;' & @CRLF
$sCSS &= '}' & @CRLF
$sColor = 'FFFFFF'
ConsoleWrite('Before -------------' & @CRLF)
ConsoleWrite($sCSS & @CRLF)
$sResult = StringRegExpReplace($sCSS, '(?s)(\.cont{.*?background-color:\s#)([^;]*)', '${1}' & $sColor)
ConsoleWrite('After --------------' & @CRLF)
ConsoleWrite($sResult & @CRLF)


Шаблон найдет нужную конструкцию из первой группы с захватом и запишет ее как "$1", а затем найдет соответствие второй группе и заменит ее на новый цвет из переменной "$sColor".
Пример 2
Задача:
Произвести форматирование многострочного текста так, чтобы все знаки запятых между словами стояли сразу за первым словом, но через один пробел от следующего слова ("слово1, слово2"). При этом учесть возможность "висячих" запятых в начале строки и перенести в конец предыдущей.

string,string, string ,string , string string , string,
string string
string ,string
string , string
string,string
,string ,string
, string , string
,string,string
, string ,string


Решение:
В задаче есть два вида запятых - внутри текста и в начале строки. В первом случае нужно заменить запятую со всеми окружающими ее пробелами на "{,}{пробел}", а во втором начало новой строки с запятой и всеми пробелами до и после них на "{,}{пробел}{перенос строки}". Значит, и выражений потребуется два - по одному для каждого случая.
1. Составим шаблон для запятой внутри строки.
В нем ничего сложного - нам нужно учесть пробелы до запятой и после нее (не важно сколько их и есть ли они вообще):
(\h*,\h*)
т.е. любое количество горизонтальных пробельных символов (0 или более штук) одна запятая и снова любое количество горизонтальных пробелов
2. Составим шаблон для запятой в начале новой строки:
Просто допишем к предыдущему шаблону конструкцию:
\h*\v+
т.е. любое количество горизонтальных пробельных символов и один или более "вертикальных пробелов", ими могут быть символы новой строки или возврата каретки (подробнее здесь).
Итоговый шаблон:
(\h*\v+\h*,\h*)
Сами замены смотрите в примере:

$sText = 'string,string, string ,string , string string    ,    string,' & @CRLF
$sText &= 'string string' & @CRLF
$sText &= 'string   ,string' & @CRLF
$sText &= 'string   ,   string' & @CRLF
$sText &= 'string,string' & @CRLF
$sText &= ',string   ,string' & @LF
$sText &= ' ,  string   ,   string' & @LF
$sText &= ' ,string,string' & @LF
$sText &= ', string   ,string' & @CRLF
ConsoleWrite('Before -------------' & @CRLF)
ConsoleWrite($sText & @CRLF)
$sText = StringRegExpReplace($sText, '(\h*,\h*)', ', ')
ConsoleWrite('After1 -------------' & @CRLF)
ConsoleWrite($sText & @CRLF)
$sText = StringRegExpReplace($sText, '(\h*\v+\h*,\h*)', ', ' & @CRLF)
ConsoleWrite('After2 -------------' & @CRLF)
ConsoleWrite($sText & @CRLF)


Пример 3
Задача:
В тексте:

Иванов Николай Иванович 15 июня 1965г. город Волгоград холост; Васильев Иван Васильевич 16 ноября 1960г. город Москва женат; Николаев Василий Николаевич 20 декабря 1962г. деревня Дубовка женат;

сократить имя и отчество до инициала с точкой и поставить их перед фамилией.
Решение:
Можно воспользоваться тем, что ФИО записано после начала строки или ";" и перед цифрами. Но мы "зацепимся" за заглавные буквы в ФИО, ведь в тексте больше нет других трех подряд идущих слов, начинающихся с прописной буквы, кроме ФИО.
1. Описываем фамилию:
([Ё-Я]\S+\s)
Расшифровка:
"[Ё-Я]" - одна заглавная русская буква
"\S+" - 1 или более непробельный символов
"\s" - один пробел
всё заключено в группу с захватом, для формирования обратной ссылки
2. Описываем имя и отчество:
([Ё-Я])\S+\s
Шаблон отличается только расположение группы с захватом - здесь она захватывает только первый символ.
3. Итоговый шаблон:
([Ё-Я]\S+\s)([Ё-Я])\S+\s([Ё-Я])\S+\s
Обратные ссылки будут соответствовать:
1 - фамилия
2 - первая буква имени
3 - первая буква отчества
отсюда получаем нужный нам формат замены:
$2.$3.$1

$sText = 'Иванов Николай Иванович 15 июня 1965г. город Волгоград холост; Васильев Иван Васильевич 16 ноября 1960г. город Москва женат; Николаев Василий Николаевич 20 декабря 1962г. деревня Дубовка женат;'
ConsoleWrite('Before -------------' & @CRLF)
ConsoleWrite($sText & @CRLF)
$sText = StringRegExpReplace($sText, '([Ё-Я]\S+\s)([Ё-Я])\S+\s([Ё-Я])\S+\s', '$2.$3.$1')
ConsoleWrite('After --------------' & @CRLF)
ConsoleWrite($sText & @CRLF)