Я пытаюсь извлечь атрибуты тега привязки (<a>). Пока у меня есть это выражение:
(?<name>\b\w+\b)\s*=\s*("(?<value>[^"]*)"|'(?<value>[^']*)'|(?<value>[^"'<> \s]+)\s*)+
который работает для таких строк, как
<a href = "test.html" class = "xyz">
и (одинарные кавычки)
<a href='test.html' class = "xyz">
но не для строки без кавычек:
<a href=test.html class=xyz>
Как я могу изменить свое регулярное выражение, чтобы оно работало с атрибутами без кавычек? Или есть лучший способ сделать это?
Обновлять:Спасибо за все хорошие комментарии и советы. Есть одна вещь, о которой я не упомянул: мне, к сожалению, приходится исправлять / модифицировать код, написанный не мной. И нет времени / денег, чтобы все это переписывать снизу вверх.






Я предлагаю вам использовать HTML Tidy для преобразования HTML в XHTML, а затем использовать подходящее выражение XPath для извлечения атрибутов.
Если вы хотите быть общим, вы должны посмотреть на точную спецификацию тега a, например здесь. Но даже с этим, если вы сделаете свое идеальное регулярное выражение, что, если у вас есть искаженный html?
Я бы посоветовал обратиться к библиотеке для анализа html, в зависимости от языка, с которым вы работаете: например. например, Beautiful Soup из Python.
Ответ Token Mantra: вы не должны настраивать / изменять / собирать / или иным образом создавать html / xml с использованием регулярного выражения.
также могут быть условные выражения в крайнем регистре, такие как \ 'и \ ", которые необходимо учитывать. Вместо этого вам будет лучше использовать правильный DOM Parser, XML Parser или один из многих других десятков проверенных и проверенных инструментов. изобретать свои собственные.
Мне все равно, какой из них вы используете, если он признан, протестирован и вы его используете.
my $foo = Someclass->parse( $xmlstring );
my @links = $foo->getChildrenByTagName("a");
my @srcs = map { $_->getAttribute("src") } @links;
# @srcs now contains an array of src attributes extracted from the page.
«условные выражения углового регистра, такие как \ 'и \», которые необходимо учитывать »... вы не можете избежать кавычек в атрибуте HTML. Единственный способ включить их - это закодировать их как объект & quot;
Да, в спецификации HTML указано, что вы должны кодировать их, но, тем не менее, из-за того, что люди, использующие обратную косую черту с использованием, адаптируются, чтобы заставить его работать, и все больше людей используют его, поэтому ваш синтаксический анализатор должен уметь обрабатывать его, когда они это делают :)
Это не так с 2020 года в Chrome 79; обратная косая черта-кавычка - это нет, распознаваемая как экранированная кавычка, скорее, косая черта удаляется, а кавычка либо распознается как разделитель значения, либо как часть значения, в зависимости от ее положения / окружения.
Если вы работаете в .NET, я рекомендую пакет гибкости HTML, очень надежный даже с искаженным HTML.
Затем вы можете использовать XPath.
Обновление (2020), Гюм Фокс предлагает https://regex101.com/r/U9Yqqg/2 (обратите внимание, что regex101.com не существовал, когда я изначально написал этот ответ)
(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|\s*/?[>"']))+.)["']?
Применительно к:
<a href=test.html class=xyz>
<a href = "test.html" class = "xyz">
<a href='test.html' class = "xyz">
<script type = "text/javascript" defer async id = "something" onload = "alert('hello');"></script>
<img src = "test.png">
<img src = "a test.png">
<img src=test.png />
<img src=a test.png />
<img src=test.png >
<img src=a test.png >
<img src=test.png alt=crap >
<img src=a test.png alt=crap >
Оригинальный ответ (2008 г.): Если у вас есть такой элемент, как
<name attribute=value attribute = "value" attribute='value'>
это регулярное выражение можно использовать для последовательного поиска каждого имени и значения атрибута
(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?
Применяется на:
<a href=test.html class=xyz>
<a href = "test.html" class = "xyz">
<a href='test.html' class = "xyz">
это даст:
'href' => 'test.html'
'class' => 'xyz'
Note: This does not work with numeric attribute values e.g.
<div id = "1">won't work.
Edited: Improved regex for getting attributes with no value and values with " ' " inside.
([^\r\n\t\f\v= '"]+)(?:=(["'])?((?:.(?!\2?\s+(?:\S+)=|\2))+.)\2?)?
Применяется на:
<script type = "text/javascript" defer async id = "something" onload = "alert('hello');"></script>
это даст:
'type' => 'text/javascript'
'defer' => ''
'async' => ''
'id' => 'something'
'onload' => 'alert(\'hello\');'
А как насчет «foo = " bar 'bar =' bla "»?
@Gumbo: это регулярное выражение должно учитывать одинарные или двойные кавычки, поскольку оно использует класс символов ['"]
Конечно, он не мог управлять кавычками в пределах значения атрибута.
Я знаю. Но значение foo будет «bar 'bar =' bla», а не просто «bar».
Фон, это частично работает, но когда в одном теге есть сочетание нецитированных и цитируемых слов, это больше не работает. См. Мой ответ для другой версии регулярного выражения
Было бы здорово, если бы он мог ловить пустые атрибуты, такие как «выбранный» и «проверен».
Также я узнал, что с единственным символом в качестве значения регулярное выражение также вернет кавычки.
@AndreaSilvestri, можете ли вы отредактировать этот ответ с фиксированным регулярным выражением, которое бы этого избежало?
@PauloCosta, вы можете увидеть регулярное выражение в regex101.com/r/bY3kM1/1: оно распакует регулярное выражение для вас.
@VonC, похоже, не работает, если у вас есть атрибут типа data-test = "1", выбрано значение "1, а не просто 1.
@SlavikMeltser Да, 4 года назад я выделил более надежное решение: stackoverflow.com/a/13618472/6309
Я добавил решение, которое позволяет использовать кавычки без кавычек / кавычек, одинарные / двойные кавычки, экранированные кавычки внутри атрибутов, пробелы вокруг знаков равенства, различное количество атрибутов, проверять только атрибуты внутри тегов и управлять разными кавычками внутри значения атрибута: stackoverflow.com/a/38305337/1204332
@IvanChaer Отлично сделано. Но я проголосовал за вашу первую итерацию еще в июле;)
Да, VonC, но совсем недавно @SlavikMeltser пролил свет на ретроспективы, которые позволили мне улучшить свой ответ. Спасибо! :)
А как насчет <glyph unicode = "« "horizon-adv-x = " 450 "d = " M45,532C45,478,71 />?
@ user1932634 Это точно не совпадет с моим регулярным выражением 9-летней давности ...
Возникла проблема с этим образцом: <img src=test.png />. Это дает src => test.png /> со вторым регулярным выражением и src => test.png / с первым.
@GyumFox Действительно. Я обновил регулярное выражение в regex101.com/r/k5qzMI/2, но оно не работает для значений, которые не заключены в кавычки (одинарные или двойные)
У меня лучший результат при изменении вашего первого регулярного выражения (хотя я тестировал только те случаи, которые меня интересовали ...): regex101.com/r/U9Yqqg/1
@GyumFox Спасибо. Я включил ваш комментарий в ответ для большей наглядности. Как проиллюстрировано regex101.com/r/U9Yqqg/2, это не охватывает случаи все, но близко.
Я бы пересмотрел стратегию использования только одного регулярного выражения. Конечно, неплохо было бы придумать одно-единственное регулярное выражение, которое все это делает. Но с точки зрения ремонтопригодности вы собираетесь выстрелить себе в обе ноги.
Просто чтобы согласиться со всеми: не анализируйте HTML с помощью регулярного выражения.
Невозможно создать выражение, которое выбирает атрибуты даже для правильного фрагмента HTML, не говоря уже о всех возможных искаженных вариантах. Ваше регулярное выражение уже практически нечитаемо, даже если не пытаться справиться с недопустимым отсутствием кавычек; Погоняйтесь дальше в ужас реального HTML, и вы сведете себя с ума от неподъемного куска ненадежных выражений.
Существуют библиотеки для чтения испорченного HTML или исправления его в действительный XHTML, который затем можно легко проглотить с помощью анализатора XML. Используй их.
Хотя совет не анализировать HTML с помощью регулярного выражения действителен, вот выражение, которое делает в значительной степени то, что вы просили:
/
\G # start where the last match left off
(?> # begin non-backtracking expression
.*? # *anything* until...
<[Aa]\b # an anchor tag
)?? # but look ahead to see that the rest of the expression
# does not match.
\s+ # at least one space
( \p{Alpha} # Our first capture, starting with one alpha
\p{Alnum}* # followed by any number of alphanumeric characters
) # end capture #1
(?: \s* = \s* # a group starting with a '=', possibly surrounded by spaces.
(?: (['"]) # capture a single quote character
(.*?) # anything else
\2 # which ever quote character we captured before
| ( [^>\s'"]+ ) # any number of non-( '>', space, quote ) chars
) # end group
)? # attribute value was optional
/msx;
«Но подождите», - скажете вы. "А как насчет * комментариев?!?!" Хорошо, тогда вы можете заменить . в разделе без возврата на: (Он также обрабатывает разделы CDATA.)
(?:[^<]|<[^!]|<![^-\[]|<!\[(?!CDATA)|<!\[CDATA\[.*?\]\]>|<!--(?:[^-]|-[^-])*-->)
\K прямо перед именем атрибута и не беспокоиться о захвате всего материала, который вы хотите пропустить.Значение без кавычек, "([^> \ s '"] +) ", не будет выполнено для <tag attr = value />, включая' / 'в значении. Вероятно, это должно быть что-то вроде (непроверено)" ( . *?) (?: \ s |> | /> | '| ") # символы до первого пробела,>, />, цитата"
Именно то, что я искал - мне нужно было удалить кучу атрибутов (один и тот же атрибут, разные значения "и attribute = ". *? "Помогло в моем случае ... очень удобная ссылка, спасибо @mheyman
Вы не можете использовать одно и то же имя для нескольких захватов. Таким образом, вы не можете использовать квантификатор в выражениях с именованными захватами.
Так что либо не используйте именованные захваты:
(?:(\b\w+\b)\s*=\s*("[^"]*"|'[^']*'|[^"'<>\s]+)\s+)+
Или не используйте квантификатор в этом выражении:
(?<name>\b\w+\b)\s*=\s*(?<value>"[^"]*"|'[^']*'|[^"'<>\s]+)
Это также позволяет использовать такие значения атрибутов, как bar=' baz='quux:
foo = "bar=' baz='quux"
Что ж, недостатком будет то, что вам придется впоследствии удалить начальные и конечные кавычки.
Гораздо точнее, чем мое регулярное выражение. +1. Обратите внимание, почему [^ \ s], а [^ \ s] будет достаточно?
Я просто скопировал регулярное выражение из вопроса. :)
что-то вроде этого может быть полезно
'(\S+)\s*?=\s*([\'"])(.*?|)\2
Извлеките элемент:
var buttonMatcherRegExp=/<a[\s\S]*?>[\s\S]*?</a>/;
htmlStr=string.match( buttonMatcherRegExp )[0]
Затем используйте jQuery для анализа и извлечения нужного вам бита:
$(htmlStr).attr('style')
Мне это тоже понадобилось, и я написал функцию для разбора атрибутов, получить ее можно отсюда:
https://gist.github.com/4153580
(Примечание: он не использует регулярное выражение)
Привет, Фуркан, решение с регулярным выражением может быть лучшим для этой ситуации, так как оно быстрее :) см. Мой ответ
Я также думаю, что регулярное выражение должно быть лучше, но я не хотел иметь дело с деталями с регулярным выражением, например с атрибутом вроде this value = "Tester's Device", этот единственный qoute будет путать вещи с простыми шаблонами регулярных выражений, или даже иногда нет цитаты вокруг ценностей. Я сделал это надежным способом. Если бы это было на C, это было бы быстрее, чем регулярное выражение, но я не могу сказать то же самое для php.
Сплаттне
Решение @VonC частично работает, но есть некоторая проблема, если в теге есть смешанные нецитированные и цитируемые
Этот работает со смешанными атрибутами
$pat_attributes = "(\S+)=(\"|'| |)(.*)(\"|'| |>)"
проверить это
<?php
$pat_attributes = "(\S+)=(\"|'| |)(.*)(\"|'| |>)"
$code = ' <IMG title=09.jpg alt=09.jpg src = "http://example.com.jpg?v=185579" border=0 mce_src = "example.com.jpg?v=185579"
';
preg_match_all( "@$pat_attributes@isU", $code, $ms);
var_dump( $ms );
$code = '
<a href=test.html class=xyz>
<a href = "test.html" class = "xyz">
<a href=\'test.html\' class = "xyz">
<img src = "http://"/> ';
preg_match_all( "@$pat_attributes@isU", $code, $ms);
var_dump( $ms );
Тогда $ ms будет содержать ключи и значения во 2-м и 3-м элементах.
$keys = $ms[1];
$values = $ms[2];
Это действительно кажется более надежным решением. +1
Хороший. Однако я попытался присоединиться следующим образом: array_combine ($ ms [1], $ ms [3]) (3 вместо 2)
Я создал Функция PHP, который может извлекать атрибуты любых тегов HTML. Он также может обрабатывать атрибуты, такие как disabled, которые не имеют значения, а также может определять, является ли тег автономным тегом (не имеет закрывающего тега) или нет (имеет закрывающий тег), проверяя результат content:
/*! Based on <https://github.com/mecha-cms/cms/blob/master/system/kernel/converter.php> */
function extract_html_attributes($input) {
if ( ! preg_match('#^(<)([a-z0-9\-._:]+)((\s)+(.*?))?((>)([\s\S]*?)((<)/\2(>))|(\s)*/?(>))$#im', $input, $matches)) return false;
$matches[5] = preg_replace('#(^|(\s)+)([a-z0-9\-]+)(=)(")(")#i', '$1$2$3$4$5<attr:value>$6', $matches[5]);
$results = array(
'element' => $matches[2],
'attributes' => null,
'content' => isset($matches[8]) && $matches[9] == '</' . $matches[2] . '>' ? $matches[8] : null
);
if (preg_match_all('#([a-z0-9\-]+)((=)(")(.*?)("))?(?:(\s)|$)#i', $matches[5], $attrs)) {
$results['attributes'] = array();
foreach($attrs[1] as $i => $attr) {
$results['attributes'][$attr] = isset($attrs[5][$i]) && ! empty($attrs[5][$i]) ? ($attrs[5][$i] != '<attr:value>' ? $attrs[5][$i] : "") : $attr;
}
}
return $results;
}
$test = array(
'<div class = "foo" id = "bar" data-test = "1000">',
'<div>',
'<div class = "foo" id = "bar" data-test = "1000">test content</div>',
'<div>test content</div>',
'<div>test content</span>',
'<div>test content',
'<div></div>',
'<div class = "foo" id = "bar" data-test = "1000"/>',
'<div class = "foo" id = "bar" data-test = "1000" />',
'< div class = "foo" id = "bar" data-test = "1000" />',
'<div class id data-test>',
'<id = "foo" data-test = "1000">',
'<id data-test>',
'<select name = "foo" id = "bar" empty-value-test = "" selected disabled><option value = "1">Option 1</option></select>'
);
foreach($test as $t) {
var_dump($t, extract_html_attributes($t));
echo '<hr>';
}
Не определяет тег doctype
Посмотри на это Regex и PHP - изолировать атрибут src от тега img
возможно, вы сможете пройтись по DOM и получить желаемые атрибуты. Он отлично работает для меня, получая атрибуты из тега тела
Простое извлечение атрибутов (Смотрите, как это работает):
((?:(?!\s|=).)*)\s*?=\s*?["']?((?:(?< = ")(?:(?<=\\)"|[^"])*|(?<=')(?:(?<=\\)'|[^'])*)|(?:(?!"|')(?:(?!/>|>|\s).)+))
Или с проверкой открытия / закрытия тега, извлечением имени тега и экранированием комментария. Это выражение предусматривает отсутствие кавычек / кавычек, одинарные / двойные кавычки, экранированные кавычки внутри атрибутов, пробелы вокруг знаков равенства, различное количество атрибутов, проверку только атрибутов внутри тегов и управление разными кавычками внутри значения атрибута. (Смотрите, как это работает):
(?:\<\!\-\-(?:(?!\-\-\>)\r\n?|\n|.)*?-\-\>)|(?:<(\S+)\s+(?=.*>)|(?<=[=\s])\G)(?:((?:(?!\s|=).)*)\s*?=\s*?[\"']?((?:(?<=\")(?:(?<=\\)\"|[^\"])*|(?<=')(?:(?<=\\)'|[^'])*)|(?:(?!\"|')(?:(?!/>|>|\s).)+))[\"']?\s*)
(Лучше работает с флагами gisx.)
Поскольку регулярные выражения Javascript не поддерживают ретроспективный анализ, они не будут поддерживать большинство функций предыдущих выражений, которые я предлагаю. Но если это может кому-то подойти, вы можете попробовать эту версию. (Смотрите, как это работает).
(\S+)=[\'"]?((?:(?!/>|>|"|\'|\s).)+)
Ваше извлечение RegEx не является точным. См. Обновленный пример из форка вашего тестового примера: regex101.com/r/y3DOf5/1
Да, невозможно выполнить условный поиск, основанный на проверке наличия предыдущего символа (в данном случае одинарные / двойные кавычки). Не с одним регулярным выражением. Вот почему парсер - лучший вариант. К сожалению, это регулярное выражение является приблизительным.
на самом деле условный поиск можно создать только с помощью RegEx, используя группы просмотра вперед и назад. Я опубликую правильный ответ, который сделает это скоро, когда у меня будет больше времени.
Спасибо за подсказки, @SlavikMeltser! Я обновил свой ответ, оглянувшись назад.
К сожалению, это не работает с javascript, поскольку он не поддерживает ретроспективный просмотр.
@choise Верно! Я добавил упрощенное выражение для JS.
У меня это работает. Он также принимает во внимание некоторые конечные случаи, с которыми я столкнулся.
Я использую это регулярное выражение для синтаксического анализатора XML
(?<=\s)[^><:\s]*=*(?=[>,\s])
Это мой лучший RegEx для извлечения свойств в теге HTML:
# Обрезать совпадение внутри кавычек (одинарных или двойных)
(\S+)\s*=\s*([']|["])\s*([\W\w]*?)\s*\2
# Без обрезки
(\S+)\s*=\s*([']|["])([\W\w]*?)\2
Плюсы:
Минусы:
<div title = "You're"> результат: Группа 1: заголовок, Группа 2:", Группа 3: Вы.Это онлайн-пример RegEx: https://regex101.com/r/aVz4uG/13
Обычно я использую этот RegEx для извлечения тегов HTML:
Я рекомендую это, если вы не используете такие теги, как <div, <span и т. д.
<[^/]+?(?:\".*?\"|'.*?'|.*?)*?>
Например:
<div title = "a>b=c<d" data-type='a>b=c<d'>Hello</div>
<span style = "color: >=<red">Nothing</span>
# Returns
# <div title = "a>b=c<d" data-type='a>b=c<d'>
# <span style = "color: >=<red">
Это онлайн-пример RegEx: https://regex101.com/r/aVz4uG/15
Ошибка в этом RegEx:
<div[^/]+?(?:\".*?\"|'.*?'|.*?)*?>
В этом теге:
<article title = "a>b=c<d" data-type='a>b=c<div '>Hello</article>
Возвращает <div '>, но не должно возвращать совпадений:
Match: <div '>
Чтобы "решить" эту проблему, удалите шаблон [^/]+?:
<div(?:\".*?\"|'.*?'|.*?)*?>
Ответ # 317081 хорош, но он не подходит должным образом в следующих случаях:
<div id = "a"> # It returns "a instead of a
<div style = ""> # It doesn't match instead of return only an empty property
<div title = "c"> # It not recognize the space between the equal (=)
Это улучшение:
(\S+)\s*=\s*["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))?[^"']*)["']?
против
(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?
Избегайте пробелов между равным сигналом: (\ S +) \ s * = \ s * ((?: ...
Поменять последние + и. для: | [> "'])) ? [^ "'] *) ["']?
Это онлайн-пример RegEx: https://regex101.com/r/aVz4uG/8
Теги и атрибуты в HTML имеют вид
<tag
attrnovalue
attrnoquote=bli
attrdoublequote = "blah 'blah'"
attrsinglequote='bloob "bloob"' >
Для сопоставления атрибутов вам понадобится регулярное выражение attr, которое находит одну из четырех форм. Затем вам нужно убедиться, что в тегах HTML указываются только совпадения. Предполагая, что у вас правильное регулярное выражение, общее регулярное выражение будет следующим:
attr(?=(attr)*\s*/?\s*>)
Предварительный просмотр гарантирует, что за атрибутом следуют только другие атрибуты и закрывающий тег. Я использую следующее регулярное выражение для attr:
\s+(\w+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^><"'\s]+)))?
Неважные группы не захватываются. Первая совпадающая группа $1 дает вам имя атрибута, значение - одно из $2 или $3 или $4. Я использую $2$3$4 для извлечения значения.
Последнее регулярное выражение
\s+(\w+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^><"'\s]+)))?(?=(?:\s+\w+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^><"'\s]+))?)*\s*/?\s*>)
Примечание. Я удалил все ненужные группы в предварительном просмотре и сделал все оставшиеся группы незахваченными.
Для C# я выбрал AngleSharp и его класс
HtmlParser.