Выборка цен

Часто возникает проблема по парсингу интересующих программиста данных из HTML, который не всегда хорошего качества, все было бы терпимо, если бы еще не вставки на javascript'е, вот пример такого текста:
<TD>20.02<BR>05:30 <TD class=l>Товар 1<BR>Товар
2 <TD><B>35</B> <TD><A href="http://ссылка/"
id=sfsd32dfs onclick="return m(this)">26.92</A><BR><A
href="http://ссылка/" id=r3_3143svsfd onclick="return
m(this)">27.05</A> <TD><B>270.5</B>
</TR>

Те цифры, которые написаны через точку, являются ценами. Задача состоит в том, чтобы собрать все цены, которые находятся между тегами <a>... </a> Видим, что помимо цен между заданными тегами, есть такие, которые идут сразу после тега <TD>, а также стоят между тегами <B>...</B>. Ясно, что описать достаточно точно содержимое атрибутов тега <A> представляется задачей не самой легкой, поэтому надо ее упростить! Любой тег имеет закрывающий знак '>', наша задача описать, что этот знак идет перед ценой, но так как перед ценой может стоять тег <B> и тег <TD>, но эти цены нам не нужны. Каким образом мы узнаем, что цена стоит между тегами <A>...</A>? По тегу, который идет после цены, если это не тег </B>, то это будет либо тег </A> либо <BR>, а так же по тегу перед ценой если этот тег <TD>. Путем таких размышлений мы пришли к выводу, что должно стоять справа, а что должно стоять слева искомой строки, которая описывается как цифры, разделенные точкой: \d*\.\d* То, что должно совпасть слева, мы описали как символ '>', записываем: (?<=>) - выглядит немного странно, но совпадение справа записывается вот так (?<=), а внтури него после ?<= идет символ '>' То, что должно совпасть справа описывается (?=) внутри мы пишем </A>. Теперь опишем, что не должно стоять перед ценой: (?<!<TD>) перед ценой не должен стоять тег <TD>, это и есть негативная ретроспективная проверка. При помощи негативной опережающей проверки опишем, что не должно стоять справа цены: (?!<\/B>) справа от цены не должен стоять тег </B>. Результирующее регулярное выражение, которое описывает все приведенные условия выглядит вот так:
preg_match_all("/(?<!<TD>)(?<=>)\d*\.\d*(?!<\/B>)(?=<\/A>)/", $string, $matches);
print_r($matches);

После рассмотрения первого примера стоит сделать замечания и пояснения по поводу использования позиционных проверок.

1. Написанные друг за другом проверки применяются независимо друг от друга в одной точке, не меняя ее. Естественно, что совпадение будет найдено, если все проверки совпадут. В нашем примере это были точки перед и после цены. С точки зрения логики применения проверок нет никакой разницы, будет ли стоять проверка на тег <TD> перед проверкой на знак '>'. Правда, с точки зрения оптимизации первой позиционной проверкой должна идти та, которая имеет наибольшую вероятность несовпадения.

2. Совпавшие значения ретроспективных проверок не сохраняются. Т.е. если в нашем примере совпадает опережающая проверка, которая указывает, что после цены идет тег </A>, то сам тег </A>, который заключен в конструкцию (?=) не будет запоминаться в специальных перменных /1,/2 и т.д. Сделано это из-за того, что позиционная проверка совпадает не со строкой, а с местом в строке (она описывает место, где произошло совпадение, а не символы, которые совпали).

3. Нужно указать что PCRE не позволяет делать проверки на совпадение текста произвольной длинны. То есть нельзя делать, например, такую проверку: /(?<=\d+)
Механизм поиска совпадения в ретроспективной проверке реализован так, что при поиске механизму должна подаваться строка фиксированной длины, для того, чтобы в случае несовпадения, механизм мог вернуться назад на фиксированое количество символов и продолжить поиск совпадений в других позиционных проверках. Думаю, что сразу это понять сложно, но представьте себе как происходит поиск совпадения в части (?)(?<=>) вышеописанного регулярного выражения. Берется строка, в которой происходит поиск, отсчитывается от начала столько символов, сколько символов будет в совпадении позиционной проверки, в нашем варианте это 4: <, T, D, > с этого места происходит "заглядывание назад" (ретроспективные проверки на английском языке звучит как lookbhind assertions), т.е. все предыдущие 4 символа проверяются на совпадение со строкой <TD>, если механизм не нашел совпадения, то ему надо вернуться на 4 символа назад, выполнить тоже самое с проверкой (?<=>), т.е. отсчитать один символ, "заглянуть" назад, попробовать найти проверку предыдущего символа с символом '>'. Представьте себе, что условие совпадения состоит из строки нефиксированной длинны: (??) подобная запись должна означать, что перед ценой, не должен стоять тег <TD> в количестве максимимум один экземпляр (либо вообще не стоять). Вот и получается, что после того, как механизм отсчитает 4 символа от начала, он проверит на совпадение с <TD>, но в условии указано, что тега может и не быть вообще, тогда возникает вопрос, на сколько знаков верунться назад, чтобы проверить на совпадение другие проверки. На 4 или вообще не возвращаться? Сразу возникает вопрос, а зачем идти вперед, чтобы потом "заглянуть" назад? Делается это для того, чтобы в случае совпадения всех проверок сразу же начать проверку тех символов, которые идут после позиционных проверок.
Теги:
Выборка цен
Добавлено: 04 Июля 2018 07:48:39 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...