base10 9

Регулярные выражения

Функции сравнения строк и поиска подстрок, рассмотренные в предыдущей статье, позволяют успешно справляться с теми задачами, для которых они предназначены, но имеют существенные ограничения в том, что основаны на использовании литеральных значений. В качестве иллюстрации того, в чем состоят ограничения этих функций, рассмотрим следующий пример. Предположим, требуется выполнить проверку строк для определения того, относятся ли эти строки к конкретному типу имени хоста веб-сайта. Адреса, представленные этим именем, должны начинаться с подстроки www., оканчиваться подстрокой .com и иметь в середине одно слово, состоящее из прописных букв. В качестве примера ниже приведены строки, имеющие требуемый формат:

'www.google.com'
'www.zend.com'

А эти строки не соответствуют указанным требованиям:

'java.sun.com'
'www.java.sun.com'
'www.php.net'

Даже краткие размышления показывают, что в нашем распоряжении пока что нет удобного способа, позволяющего воспользоваться средствами сравнения строк и подстрок для проведения требуемой проверки. Мы можем проверить наличие подстрок www. и .com, но гораздо сложнее проверить соответствие заданным требованиям той подстроки, которая находится между ними. Однако с этой задачей позволяют легко справиться регулярные выражения.

Для сокращенного обозначения регулярных выражений в английском языке применяется термин regex. Регулярные выражения представляют собой шаблоны для согласования со строками, содержащие специальные подстановочные символы, которые могут быть согласованы с целыми фрагментами целевой строки. Язык PHP позволяет применять два широких класса регулярных выражений: регулярные выражения, совместимые со стандартом POSIX (называемые расширенными), и обычные регулярные выражения, совместимые с языком Perl. Различия между этими классами в основном касаются синтаксиса, но имеются также некоторые функциональные различия.

В конечном итоге регулярные выражения в стиле POSIX явились итогом развития средств сопоставления с шаблонами регулярных выражений, используемых в интерпретаторах Unix с интерфейсом командной строки, а регулярные выражения, совместимые с языком Perl, в большей степени напоминают соответствующие средства указанного языка. Любой разработчик, которому предстоит выполнить хоть сколько-нибудь существенный объем программирования в среде веб, рано или поздно будет вынужден воспользоваться регулярными выражениями.

Начиная с версии PHP 5.3.0 функции для работы с регулярными выражениями в стиле POSIX являются устаревшими, поэтому далее мы будем рассматривать регулярные выражения в стиле Perl.

Регулярные выражения, совместимые с языком Perl

Для обработки регулярных выражений, совместимых с языком Perl, в языке PHP применяется полностью отдельный набор функций, а формирование шаблонов осуществляется следующим образом:

  • Шаблоны регулярных выражений, совместимых с языком Perl, всегда начинаются и оканчиваются одним конкретным символом, который должен быть одинаковым и в начале и в конце. Этот символ отмечает начало и конец шаблона. В соответствии с общепринятым соглашением для этой цели чаще всего используется символ /, но при желании может также применяться другой символ. Следующий шаблон, совместимый с языком Perl: /pattern/, согласуется с любой строкой, которая содержит в себе строку (вернее, подстроку) "pattern".

  • Сопоставление символов с такими же символами, которые не являются специальными, осуществляется непосредственно. Например, буква в шаблоне согласуется с той же буквой в целевой строке.

  • Специальный символ ^ согласуется только с началом строки, а специальный символ $ — только с концом строки.

  • Специальный символ . согласуется с любым символом.

  • Специальный символ * обеспечивает согласование заданного перед ним регулярного выражения в количестве от нуля или больше, а символ + указывает, что согласование достигается, если количество обнаруженных в целевой строке экземпляров стоящего перед ним регулярного выражения составляет от одного и больше.

  • Набор символов, заключенный в квадратные скобки, согласуется с любым из символов этого набора. Например, шаблон [ab] согласуется и с буквой a, и с буквой b. В квадратных скобках можно также определить диапазон символов с использованием дефиса. Например, шаблон [a-c] согласуется с буквой a, b или c.

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

  • Если рассматриваемый символ не является специальным, то шаблон в стиле Perl последовательно согласует символы. Например, шаблон /abc/ согласуется с любой строкой, которая содержит подстроку "abc"

  • Любой шаблон, за которым следует символ ? (вопросительный знак), означает: "Выполнить согласование конструкции, предшествующей этому символу, нуль или один раз".

  • Любой символ, который имеет в регулярном выражении специальное значение, может быть преобразован в простой, буквально согласуемый символ путем введения перед ним префикса в виде обратной косой черты (знак переключения на другой режим обработки). Специальные символы, которые требуют применения знака переключения, чтобы можно было выполнять согласование с их буквальными значениями, перечислены ниже.

    . \ + * ? [ ] ^ $ ( ) { } = ! < > | :

  • Круглые скобки, выделяющие любую часть шаблона, означают: "Добавить подстроку, которая согласуется с этим подвыражением, к списку подстрок, согласованных с шаблоном"

Рассмотрим в качестве примера следующий шаблон:

/тел.\s+(\d\d\d\d\d\d\d)/

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

В следующей таблице представлены все данные по использованию специальных символов в регулярных выражениях:

Специальные символы регулярных выражений PHP
Классы символов
[...] Любой из символов, указанных в скобках
[^...] Любой из символов, не указанных в скобках
. Любой символ, кроме перевода строки или другого разделителя Unicode-строки
\w Любой символ, образующий "слово"
\W Любой символ, не являющийся текстовым символом
\s Любой пробельный символ из набора Unicode
\S Любой непробельный символ из набора Unicode. Обратите внимание, что символы \w и \S - это не одно и то же
\d Любые ASCII-цифры. Эквивалентно [0-9]
\D Любой символ, отличный от ASCII-цифр. Эквивалентно [^0-9]
Символы повторения
{n,m} Соответствует предшествующему шаблону, повторенному не менее n и не более m раз
{n,} Соответствует предшествующему шаблону, повторенному n или более раз
{n} Соответствует в точности n экземплярам предшествующего шаблона
? Соответствует нулю или одному экземпляру предшествующего шаблона; предшествующий шаблон является необязательным. Эквивалентно {0,1}
+ Соответствует одному или более экземплярам предшествующего шаблона. Эквивалентно {1,}
* Соответствует нулю или более экземплярам предшествующего шаблона. Эквивалентно {0,}
Символы регулярных выражений выбора
| Соответствует либо подвыражению слева, либо подвыражению справа (аналог логической операции ИЛИ).
(...) Группировка. Группирует элементы в единое целое, которое может использоваться с символами *, +, ?, | и т.п. Также запоминает символы, соответствующие этой группе для использования в последующих ссылках.
(?:...) Только группировка. Группирует элементы в единое целое, но не запоминает символы, соответствующие этой группе.
Якорные символы регулярных выражений
^ Соответствует началу строкового выражения или началу строки при многострочном поиске.
$ Соответствует концу строкового выражения или концу строки при многострочном поиске.
\b Соответствует границе слова, т.е. соответствует позиции между символом \w и символом \W или между символом \w и началом или концом строки.
\B Соответствует позиции, не являющейся границей слов.
(?=p) Позитивная опережающая проверка на последующие символы. Требует, чтобы последующие символы соответствовали шаблону p, но не включает эти символы в найденную строку.
(?!p) Негативная опережающая проверка на последующие символы. Требует, чтобы следующие символы не соответствовали шаблону p.
\A начало данных (независимо от многострочного режима)
\Z конец данных либо позиция перед последним переводом строки (независимо от многострочного режима)
Флаги (указываются в конце рег. выражения)
i Указывает, что поиск по шаблону должен быть нечувствителен к регистру символов.
m Многострочный режим поиска (multiline).
s Если данный модификатор используется, метасимвол "точка" в шаблоне соответствует всем символам, включая перевод строк.
x Если используется данный модификатор, неэкранированные пробелы, символы табуляции и пустой строки будут проигнорированы в шаблоне, если они не являются частью символьного класса.

Общие сведения о функциях, совместимых с языком Perl, приведены в таблице ниже:

Функции PHP для работы с регулярными выражениями, совместимые с языком Perl
Функция Описание
preg_match()

Принимает в качестве первого параметра шаблон регулярного выражения, второго параметра — строку, с которой должно быть выполнено согласование, и необязательного третьего параметра — переменную типа "массив" для возвращаемых результатов согласования. Возвращает 0, если согласование не найдено, и 1, если согласование найдено.

В случае успешного согласования переменная типа "массив" содержит следующие элементы: первым элементом является вся согласованная подстрока, а последующие элементы содержат части, соответствующие заключенным в круглые скобки подвыражениям шаблона. Начиная с версии PHP 4.3.0 предусмотрена также возможность задать необязательный флаг PREG_OFFSET_CAPTURE. Этот флаг вынуждает функцию preg_match() возвращать в указанном массиве в расчете на каждое согласование по одному двухэлементному массиву, состоящему из самих результатов согласования и информации о смещении, с которого было начато успешное согласование.

preg_match_all()

Аналогична preg_match(), за исключением того, что осуществляет все возможные подряд идущие согласования шаблона со строкой, а не только первое согласование. Возвращаемым значением является количество успешно выполненных согласований. В этой функции третий параметр, массив согласований, является обязательным (если требуется получение только истинного или ложного значения, которое показывает наличие или отсутствие согласования, используйте функцию preg_match()). Структура возвращаемого массива зависит от необязательного четвертого параметра (в качестве этого параметра служит константа PREG_PATTERN_ORDER или PREG_SET_ORDER, причем по умолчанию применяется первая). При вызове этой функции можно также применять константу PREG_OFFSET_CAPTURE (эти константы описываются далее).

preg_split()

Принимает в качестве первого параметра шаблон регулярного выражения и второго параметра — строку, с которой должно быть выполнено согласование. Возвращает массив, содержащий результаты разбиения строки на подстроки в местах вхождения разграничительных строк, согласующихся с шаблоном. Необязательный третий параметр (с обозначением предельного количества) указывает, на какое количество подстрок должна быть разбита строка, прежде чем будет возвращен полученный список; значение -1 в позиции третьего параметра указывает на то, что предел не устанавливается. В качестве необязательного четвертого параметра может быть задан флаг PREG_SPLIT_NO_EMPTY (вынуждающий функцию возвращать только непустые части), PREG_SPLIT_DELIM_CAPTURE (которое требует возврата всех подстрок, соответствующих заключенным в круглые скобки выражениям в шаблоне разграничителя) или PREG_OFFSET_CAPTURE.

preg_replace() Принимает в качестве параметров шаблон, строку замены и строку, в которой должны быть внесены изменения. Возвращает модифицированную строку, в которой вместо каждой подстроки, согласованной с шаблоном, подставлена строка замены. Необязательный параметр с обозначением предельного количества определяет, сколько должно быть выполнено замен (как в функции preg_split())
preg_replace_callback() Эта функция аналогична функции preg_repiace(), за исключением того, что вторым параметром является имя функции обратного вызова, а не строка замены. Эта функция должна возвращать строки, предназначенные для использования в качестве замены
preg_grep() Принимает в качестве параметров шаблон и массив и возвращает массив, состоящий из элементов входного массива, который сопоставляется с шаблоном. Значения, перешедшие из старого массива в новый массив, имеют такие же ключи, как и соответствующие элементы исходного массива
preg_quote() Функция специального назначения, предназначенная для вставки знаков переключения на другой режим обработки в строки, которые предназначены для использования в качестве шаблонов регулярных выражений. Для этой функции требуется единственный параметр—строка, в которую должны быть вставлены знаки переключения; возвращаемым значением является та же строка, в которой перед каждым специальным символом регулярного выражения вставлена обратная косая черта (знак переключения)

По-видимому, наиболее широко используемыми из этих функций являются preg_match() и preg_match_all(). Первая функция представляет собой наилучшее средство получения ответа на простой вопрос о том, сопоставляется ли некоторый шаблон с некоторой строкой, а последняя функция является удобным средством подсчета количества согласований или получения сразу всех согласующихся подстрок.

Необязательный четвертый параметр функции preg_match_all() требует дополнительных пояснений. Как правило, массив, который содержит возвращаемые результаты согласования, имеет двухуровневую структуру. На первом уровне показана очередность сопоставлений (первое сопоставление, второе и т.д.), а на втором показана позиция сопоставления в шаблоне. (Все сопоставление всегда находится на первом месте, а за ним следуют по порядку все субшаблоны, обозначенные круглыми скобками.) Вопрос заключается в следующем: "Какой уровень является самым верхним? Будет ли возвращаемый массив представлять собой список элементов с указанием позиций, каждый из которых содержит список номеров последовательных согласований, или данный массив имеет обратную структуру?"

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

Давайте рассмотрим сначала простой пример, в котором извлекаются все телефонные номера из подстроки и выбирается код страны, код города и номер:

Код PHP
$str = "Телефоны нашей компании: <br>    8 (495) 123-45-67 - офис в Москве;<br>    1-212-555-75-75 - офис в Нью-Йорке.";

echo "Исходная строка: <br><pre>$str</pre>";

// Шаблон регулярного выражения
$pattern = '/(\d+)[\s|\(|-]*([\d]{3,})[\s|\)|-]*([\d|-]+)/';
preg_match_all($pattern, $str, $arr, PREG_SET_ORDER);

foreach ($arr as $entry)
{
	echo "<pre><br>Найденное совпадение: <b>".$entry[0]."</b><br>    "
	     ."Код страны: <em>".$entry[1]."</em><br>    "
		 ."Код города: <em>".$entry[2]."</em><br>    "
		 ."Номер: <em>".$entry[3]."</em><br>";
}

Этот код даст следующий результат:

Использование регулярных выражений для поиска телефонных номеров

Я могу долго объяснять принцип работы представленного в примере регулярного выражения, но я лучше покажу:

Схема использования регулярного выражения

Пример: программа извлечения ссылок

В качестве более сложного примера того, какие действия могут быть выполнены с помощью регулярных выражений, рассмотрим функцию, позволяющую извлекать ссылки из текста произвольной веб-страницы и выводить полученные результаты на экран. Входными данными служит URL страницы, которая должна быть проанализирована, а выходными данными являются выведенный на экран список ссылок, представленных на соответствующей странице. Этот список состоит из целевого URL для данной ссылки и описательного текста, присутствующего в этой ссылке (который принято называть анкором (или якорем ссылки)). Для решения указанной задачи используются функции для работы с регулярными выражениями, совместимыми с языком Perl.

Разработка такой функции может стать самым первым шагом при создании веб-навигатора для поисковой машины. Поисковики (Google, Yandex и т.п.) загружают содержимое веб-страниц для их анализа и индексации. В ходе работы возникает необходимость распознавать ссылки на другие страницы, если новое информационное наполнение можно будет найти только на этих страницах.

Основное регулярное выражение

Основой разрабатываемой небольшой функции должно служить само регулярное выражение. Необходимо прежде всего разработать такое регулярное выражение, которое сопоставляется только со ссылками HTML (и ничем иным) и может применяться для извлечения фрагментов таких ссылок. Ссылки HTML, как правило, выглядят таким образом:

<a href="http://mysite.com">Это мой сайт</a>

Это означает, что ссылка задана дескриптором анкора, который имеет атрибут href, а между начальным дескриптором (<a>) и конечным дескриптором (</a>) заключен текст анкора. В данном разделе будет создан шаблон, предназначенный для сопоставления со ссылкой такого упрощенного типа. (Разрабатываемый шаблон не позволяет обрабатывать все данные, которые согласно спецификации языка HTML разрешено использовать в качестве допустимых анкорных ссылок. В частности, в анкорах разрешены и другие атрибуты, кроме href, но при решении рассматриваемой задачи такая возможность игнорируется.)

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

Вначале рассмотрим самое несложное выражение, которое предназначено для поиска начала дескриптора анкора. Первый черновой вариант может выглядеть следующим образом:

Код PHP
// Первый черновой вариант шаблона для согласования со ссылками 
// дескриптора анкора
$pattern = '/<a href="[^"]+">/';

Обратите внимание на то, что разрабатываемый шаблон еще не предназначен для использования в работоспособном коде PHP; в данном случае создается черновой вариант выражения, которое должно быть вставлено в код PHP позднее. На обычном языке первое черновое определение дескриптора анкора можно описать как состоящее из левой угловой скобки, за которой следует буква "a", затем — пробел, вслед за ним — строка "href=", знак двойной кавычки, произвольное количество символов, отличных от знаков кавычки, заключительный знак кавычки и, наконец, правая угловая скоба. После его составления все выражение заключается в пару символов косой черты, которые служат для машины обработки регулярных выражений указанием на то, где начинается и оканчивается выражение.

Конструкция [^"]+ в середине этого выражения означает примерно следующее: квадратные скобки указывают применяемый набор символов, а знак вставки (^), который непосредственно следует за левой скобкой, указывает, что используется отрицание этого множества символов. Таковым является множество, содержащее все символы, не находящиеся в последующем списке. Наконец, знак + после обозначения класса символов, заключенного в квадратные скобки, показывает, что подразумевается наличие в шаблоне по меньшей мере одного символа, отличного от кавычки.

Как уже было сказано, мы не ставили перед собой задачу точно отразить синтаксис, предписанный спецификацией HTML. Однако существует целый ряд способов, с помощью которых можно немного ослабить ограничения, налагаемые этим выражением. Прежде всего, насколько известно, количество пробелов между начальным символом < и дескриптором a может быть больше единицы. Кроме того, допускается наличие произвольного количества пробелов между словами a и href или между заключительной двойной кавычкой и правой угловой скобой. После добавления конструкций, описывающих эти условия, все выражение принимает такой вид:

Код PHP
// Второй черновой вариант, в котором допускаются 
// дополнительные пробелы
$pattern = '/<a\s+href="[^"]+"\s*>/';

Здесь конструкция \s+ обозначает количество пробелов от одного и больше. Теперь введем конструкции для обработки самого текста анкора и заключительного дескриптора </a>:

Код PHP
// Третий черновой вариант, в котором предусматривается обработка 
// текста и закрывающего дескриптора
$pattern = '/<a\s+href="[^"]+"\s*>[^>]*<\/a>/';

В качестве текста анкора допускается использовать любой текст, который занимает на странице место вплоть до заключительного дескриптора анкора, поэтому создается класс символов, который включает любые символы, кроме правой угловой скобой ([^>]*), и указывает, что данные символы могут присутствовать в количестве от нуля и больше. Наконец, вводится субшаблон для сопоставления с закрывающим дескриптором анкора (<\/a>).

Применение данного шаблона вполне обеспечивает обработку дескрипторов анкора в том виде, который используется в качестве исходного определения, но обеспечивает сопоставление только с такими анкорами, в которых имя дескриптора (а) и атрибут (href) заданы строчными буквами. Однако допускается с тем же успехом использовать дескрипторы с прописными буквами, поэтому добавим модификатор i после всего выражения, чтобы указать на режим сопоставления без учета регистра:

Код PHP
// Четвертый черновой вариант, обеспечивающий согласование 
// без учета регистра
$pattern = '/<a\s+href="[^"]+"\s*>[^>]*<\/a>/i';

Этот черновой вариант является почти полностью законченным и может использоваться для получения положительного или отрицательного ответа на вопрос о том, содержит ли страница ссылки такого типа, который нам требуется. Но мы обязаны провести дополнительную работу и извлечь определенные фрагменты любой строки, сопоставляемой с шаблоном. Для указания на это вводятся круглые скобки, заключающие в себе те подстроки, которые нас интересуют (в этом выражении добавлен модификатор x, чтобы экранировать пробелы в рег. выражении, которые вводятся для его удобочитаемости):

Код PHP
// Окончательный черновой вариант, обеспечивающий извлечение подстрок
$pattern = '/<a \s+ href=" ([^"]+) " \s*> ([^>]*) <\/a>/ix';

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

Использование данного выражения в функции

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

  • Принять в качестве параметра заданный URL.

  • Открыть соединение HTTP с помощью указанного URL и получить информационное наполнение страницы в виде строки.

  • Выполнить обработку строки в цикле, применяя рассматриваемый шаблон регулярного выражения и сохраняя результаты согласования.

  • Вывести на экран все пары извлеченных подстрок (состоящие из целевого URL и текста анкора).

В примере ниже показана полная структура целевой страницы включая функцию print_links():

Простая программа поиска ссылок на PHP
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Основы PHP</title>
<style>
* { font-family:Calibri }
input { padding:8px; margin-bottom:8px }
</style>
</head>

<body>
<form method="post">
    <input type="text" name="url" placeholder="Введите целевой URL"><br>
    <input type="submit" value="Получить ссылки">
</form>

<?php
	
function print_links($url)
{
	$fp = fopen($url, "r") or die("Невозможно открыть файл $url");
	$page_contents = "";
	$pattern = '/<a \s+ href=" ([^"]+) " \s*> ([^>]*) <\/a>/ix';
	
	while ($new_text = fread($fp, 100)) 
	{
		$page_contents .= $new_text;
	}
	
	$match_result = preg_match_all($pattern, $page_contents, $match_array, PREG_SET_ORDER);
	
	foreach ($match_array as $entry)
	{
		$href = $entry[1];
		$anchor = $entry[2]; 
		print("<br><b>href</b>: $href;<br><b>анкор</b>: $anchor<br>");
	}
}

// Обработка веб-формы (обсуждается позже)
if (isset($_POST['url']))
    print_links($_POST['url']);

?>
</body>
</html>

Задача разработки этой функции проще, чем кажется на первый взгляд, поскольку в языке PHP уже предусмотрены многие ее составляющие. В частности, не требуется предусматривать каких-либо особых действий по созданию соединения HTTP для загрузки веб-страницы, поскольку функция fopen() принимает URL в качестве параметра и выполняет все необходимое. Поэтому достаточно после вызова функции fopen() с указанным в ней URL считывать символы до тех пор, пока поступление этих символов не прекратится, добавляя полученные данные к создаваемой строке.

Обработка в цикле содержимого HTML-страницы обеспечивается с помощью функции preg_match_all(). В этой функции шаблон регулярного выражения применяется максимально возможное количество раз, после каждого использования обработка строки начинается непосредственно вслед за тем участком, где перед этим было обнаружено совпадение с шаблоном, и результаты поиска сохраняются в массиве $match_array. Здесь мы решили применить структуру массива, упорядоченную в соответствии с флажком PREG_SET_ORDER. Это означает, что каждая запись в массиве верхнего уровня представляет собой часть, соответствующую результатам конкретного согласования с шаблоном в текущей итерации, а не часть, полученную в результате всех согласований.

На рисунке ниже показан результат запуска этого скрипта для сайта professorweb.ru:

Поиск ссылок
Обработка строк
Массивы

Комментарии (0)

Результаты поиска по запросу

Система Orphus