1

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

public static void main(String[] args) throws FileNotFoundException {
    String tmp = fileReader(fileName); //метод читает весь файл и возвращает строку, в которой мы ищем наши теги
    Pattern p = Pattern.compile("<[^> ]+>");
    Matcher m = p.matcher(tmp);
    while (m.find()) {
        System.out.println(m.group());
    }
}

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

Я пробовал так: ("<[^> ]+>")?. ? означает, что оно будет искать совпадения, которые повторяются 0 или 1 раз. Не работает.

Как это можно сделать?

Regent
  • 19,134

1 Answers1

2

Вы не сможете решить эту задачу чистыми регулярками в Java - в движке регулярок этого языка отсутствуют обратные позиционные проверки непостоянной длины, то бишь:

(?<![a-z].*)

Вы можете посмотреть как примерно могло бы выглядеть такое регулярное выражение:
https://regex101.com/r/iH6gR5/1

<(\w+)>(?=.*?(<\/\1>))(?!.*<\1)

Его проблема в том, что оно совпадает с последним DIV, так как справа от него нет другого DIV.

У меня нет .NET для тестирования регулярки (в нем есть ?<!.*), но рабочий вариант должен выглядеть примерно так:

<(\w+)>(?<!<\1>.*<\1>)(?=.*?(<\/\1>))(?!.*<\1)

P.S. Регулярное выражение не учитывает комментарии, блоки CDATA и прочие прелести синтаксиса HTML, если добавить поддержку этого, то регулярка будет настолько сложной, что не принесет образовательного эффекта.


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

String tmp = "<html><head></head><body><div></div><DIV><pre></pre></div></body></html>";
Pattern p = Pattern.compile( "<([^> ]+)>(?=(?:.*<(\\1)>)?)", Pattern.DOTALL|Pattern.CASE_INSENSITIVE );
Matcher m = p.matcher(tmp);
List<String> exclude = new ArrayList<String>();
while ( m.find() ) {
    if ( m.group(2) != null ) {
        exclude.add( m.group(2).toLowerCase() );
    }
    if ( exclude.indexOf( m.group(1).toLowerCase() ) == -1 ) {
        System.out.println( m.group() );
    }
}

Результат:

<html>
<head>
</head>
<body>
<pre>
</pre>
</body>
</html>

Были исключены div, так как они повторяются.

ReinRaus
  • 17,873
  • 3
  • 47
  • 86
  • В шаблон «распарсить регулярками HTML невозможно» надо добавить «если вы не @ReinRaus» :) – VladD Jul 02 '15 at 16:22
  • @VladD это не так трудно, как кажется :-) – ReinRaus Jul 02 '15 at 16:34
  • Получилось такое выражение - (<[^> /!-]+>)(?![\s\S]*\1) вроде работает и не учитывает коментрарии – Александр Jul 03 '15 at 08:54
  • Лучше \w+, про комментарии и цдата я начал речь, потому что внутри них могут быть тэги. – ReinRaus Jul 03 '15 at 09:01
  • [\s\S] лишняя конструкция. Лучше использовать флаг S_DOT_ALL – ReinRaus Jul 03 '15 at 09:03
  • Вы имеете ввиду, что Ваше выражение Вас устраивает? Оно же ловит последний из повторяющизся тэгов, то есть не удовлетворяет поставленному вопросу. – ReinRaus Jul 03 '15 at 09:07