Нужно ли когда-нибудь вкладывать регулярные выражения? - PullRequest
1 голос
/ 07 декабря 2010

Я хочу вытащить два числа 10 и 11 из HTML, который выглядит примерно так: только в нем даже больше шума, чем показано здесь:

<div a>
<noise=53>
<item=10>
<item=11>
</div>
<div b>
<item=20>
<noise=52>
<item=21>
</div>

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

(?s)(?<=<div a>).*?(?=</div>)

, чтобы получить данные в разделе "div a", затем используйте

(?s)(?<=<item=)[0-9]*

в результате, чтобы получить нужные мне числа.Но я не могу понять, как сделать это только в одном регулярном выражении.У меня есть предположение о том, как я мог бы, если бы только Java позволяла мне ставить * s в задних взглядах, но Java этого не делает (и я смутно понимаю, почему нет).Можно ли сделать это только с одним регулярным выражением или я должен согласиться на два?

Ответы [ 6 ]

1 голос
/ 08 декабря 2010
import java.util.regex.*;

public class Test
{
  public static void main(String[] args)
  {
    String s = "<div x><item=02><noise=99><item=05></div>\n" + 
        "<div a><noise=53><item=10><item=11><noise=55><item=12></div>\n" + 
        "<item=99>\n" + 
        "<div b><item=20><noise=52><item=21></div>";
    System.out.println(s);
    System.out.println();
    Pattern p = Pattern.compile(
        "(?:<div a>|\\G)(?:[^<]++|<(?!(?:item|/?div)\\b))*+<item=(\\d+)");
    Matcher m = p.matcher(s);
    while (m.find())
    {
      System.out.println(m.group(1));
    }
  }
}

выход:

<div x><item=02><noise=99><item=05></div>
<div a><noise=53><item=10><item=11><noise=55><item=12></div>
<item=99>
<div b><item=20><noise=52><item=21></div>

10
11
12

Разбивая это, мы имеем:

  • (?:<div a>|\\G): \G соответствует везде, где прервано предыдущее совпадение, или в начале текста, если предыдущего совпадения не было. Предварительный поиск следующей части запрещает сопоставление в начале, поэтому первое совпадение начинается с <div a>.

  • (?:[^<]++|<(?!(?:item|/?div)\\b))*+: эта часть потребляет все, что находится между текущей позицией совпадения и следующим тегом <item=N>. Он сожирает все символы, кроме < и <, если это не начало последовательности <item, <div или </div. (Последние два гарантируют, что все совпадения <item=N> содержатся в одном и том же элементе div; кроме того, <div препятствует сопоставлению \G в начале текста, а </div предотвращает совпадения между div элементами, например <item=99> в примере.)

  • Наконец, <item=(\\d+) соответствует тегу item и фиксирует номер, который вы после.

1 голос
/ 07 декабря 2010

Я не совсем уверен, что вы подразумеваете под вложенными регулярными выражениями. Способ, к которому обычно прибегают к подобным вещам, состоит в том, чтобы осторожно выполнять все понемногу, как лексер. Таким образом, вам не нужно пытаться встроить все в один шаблон.

Вместо использования Matcher.matches(), вы можете пойти на это с помощью Matcher.lookingat(), который ищет что-то из текущей начальной точки. Таким образом, вы можете проверить их на одну и ту же позицию.

Подобная тактика включает использование формы с одним аргументом Matcher.find(), где вы указываете начальную позицию символа в качестве аргумента.

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

Комбинируя разумное использование методов find(N) и lookingat() (плюс start()), возможно, с утверждением \G, вы можете создать себе более гибкий и сложный алгоритм обработки, чем это возможно при использовании единственного регулярного выражение одно.

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

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

1 голос
/ 07 декабря 2010

Я не думаю, что вы можете перейти к одному. Но обратите внимание, что разделение HTML лучше всего выполнять с помощью синтаксического анализатора XML или HTML. Вы можете использовать синтаксический анализатор XML, если HTML является правильно сформированным XHTML; в противном случае посмотрите на http://java -source.net / open-source / html-parsers .

0 голосов
/ 07 декабря 2010

Даже не пытайтесь, вам нужен парсер, многие доступны.

0 голосов
/ 07 декабря 2010

Если это настоящий HTML, его можно преобразовать в XML, например, с помощью HTMLTidy или NekoHTML, и тогда вам следует использовать выражение XPath для него.

0 голосов
/ 07 декабря 2010

Я думаю, что утилита Sed будет более полезной, чем программирование с регулярным выражением для извлечения текстовых данных.Попробуйте следующий скрипт в Sed (с опцией -n ).

/<div \w>/,/<\/div>/ {
    s/.*item=\([0-9]\+\).*/\1/p
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...