Разбор InputStream для нескольких шаблонов - PullRequest
5 голосов
/ 14 апреля 2011

Я анализирую InputStream для определенных шаблонов, чтобы извлечь значения из него, например Я хотел бы что-то вроде

<span class="filename"><a href="http://example.com/foo">foo</a>

Я не хочу использовать полноценный html-парсер, так как меня интересует не структура документа, а только некоторые четко определенные фрагменты информации. (Важен только их порядок)
В настоящее время я использую очень простой подход, у меня есть объект для каждого шаблона, который содержит символ [] открывающего и закрывающего «тега» (в этом примере значение открытия было бы <span class="filename"><a href=", а закрытие " для получения URL). и маркер позиции. Для каждого символа, считываемого из InputStream, я перебираю все Patterns и вызываю функцию match(char), которая возвращает true, когда начальный шаблон действительно совпадает, с тех пор я собираю следующие символы в StringBuilder, пока текущий активный шаблон не совпадет ( ) снова. Затем я вызываю функцию с идентификатором Pattern и прочитанным String для дальнейшей обработки.
Хотя в большинстве случаев это работает нормально, я хотел включить в шаблон регулярные выражения, чтобы я мог сопоставить что-то вроде

<span class="filename" id="234217"><a href="http://example.com/foo">foo</a>

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

Ответы [ 3 ]

4 голосов
/ 15 апреля 2011

Вы имеете в виду, что хотите сопоставить любой элемент <span> с данным атрибутом class, независимо от других атрибутов, которые он может иметь?Это достаточно просто:

Scanner sc = new Scanner(new File("test.txt"), "UTF-8");
Pattern p = Pattern.compile(
    "<span[^>]*class=\"filename\"[^>]*>\\s*<a[^>]*href=\"([^\"]+)\""
);
while (sc.findWithinHorizon(p, 0) != null)
{
  MatchResult m = sc.match();
  System.out.println(m.group(1));
}

Файл "test.txt" содержит текст вашего вопроса, и вывод:

http://example.com/foo
and closing
http://example.com/foo
0 голосов
/ 15 апреля 2011

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

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

Грамматика - это упрощенная версия стандартной грамматики JavaCC для HTML . Вы можете добавить больше произведений для соответствия другим образцам.

options {
  JDK_VERSION = "1.5";
  static = false;
}

PARSER_BEGIN(eg1)
import java.util.*;
public class eg1 {
  private String currentTag;
  private String currentSpanClass;
  private String currentHref;

  public static void main(String args []) throws ParseException {
    System.out.println("Starting parse");
    eg1 parser = new eg1(System.in);
    parser.parse();
    System.out.println("Finishing parse");
  }
}

PARSER_END(eg1)

SKIP :
{
    <       ( " " | "\t" | "\n" | "\r" )+   >
|   <       "<!" ( ~[">"] )* ">"            >
}

TOKEN :
{
    <STAGO:     "<"                 >   : TAG
|   <ETAGO:     "</"                >   : TAG
|   <PCDATA:    ( ~["<"] )+         >
}

<TAG> TOKEN [IGNORE_CASE] :
{
    <A:      "a"              >   : ATTLIST
|   <SPAN:   "span"           >   : ATTLIST
|   <DONT_CARE: (["a"-"z"] | ["0"-"9"])+  >   : ATTLIST
}

<ATTLIST> SKIP :
{
    <       " " | "\t" | "\n" | "\r"    >
|   <       "--"                        >   : ATTCOMM
}

<ATTLIST> TOKEN :
{
    <TAGC:      ">"             >   : DEFAULT
|   <A_EQ:      "="             >   : ATTRVAL

|   <#ALPHA:    ["a"-"z","A"-"Z","_","-","."]   >
|   <#NUM:      ["0"-"9"]                       >
|   <#ALPHANUM: <ALPHA> | <NUM>                 >
|   <A_NAME:    <ALPHA> ( <ALPHANUM> )*         >

}

<ATTRVAL> TOKEN :
{
    <CDATA:     "'"  ( ~["'"] )* "'"
        |       "\"" ( ~["\""] )* "\""
        | ( ~[">", "\"", "'", " ", "\t", "\n", "\r"] )+
                            >   : ATTLIST
}

<ATTCOMM> SKIP :
{
    <       ( ~["-"] )+         >
|   <       "-" ( ~["-"] )+         >
|   <       "--"                >   : ATTLIST
}



void attribute(Map<String,String> attrs) :
{
    Token n, v = null;
}
{
    n=<A_NAME> [ <A_EQ> v=<CDATA> ]
    {
        String attval;
        if (v == null) {
            attval = "#DEFAULT";
        } else {
            attval = v.image;
            if( attval.startsWith("\"") && attval.endsWith("\"") ) {
              attval = attval.substring(1,attval.length()-1);
            } else if( attval.startsWith("'") && attval.endsWith("'") ) {
              attval = attval.substring(1,attval.length()-1);
            }
        }
        if( attrs!=null ) attrs.put(n.image.toLowerCase(),attval);
    }
}

void attList(Map<String,String> attrs) : {}
{
    ( attribute(attrs) )+
}


void tagAStart() : {
  Map<String,String> attrs = new HashMap<String,String>();
}
{
    <STAGO> <A> [ attList(attrs) ] <TAGC>
    {
      currentHref=attrs.get("href");    
      if( currentHref != null && "filename".equals(currentSpanClass) )
      {
        System.out.println("Found URL: "+currentHref);
      }
    }
}

void tagAEnd() : {}
{
    <ETAGO> <A> <TAGC>
    {
      currentHref=null;
    }
}

void tagSpanStart() : {
  Map<String,String> attrs = new HashMap<String,String>();
}
{
    <STAGO> <SPAN> [ attList(attrs) ] <TAGC>
    {
      currentSpanClass=attrs.get("class");
    }
}

void tagSpanEnd() : {}
{
    <ETAGO> <SPAN> <TAGC>
    {
      currentSpanClass=null;
    }
}

void tagDontCareStart() : {}
{
   <STAGO> <DONT_CARE> [ attList(null) ] <TAGC>
}

void tagDontCareEnd() : {}
{
   <ETAGO> <DONT_CARE> <TAGC>
}

void parse() : {}
{
    (
      LOOKAHEAD(2) tagAStart() |
      LOOKAHEAD(2) tagAEnd() |
      LOOKAHEAD(2) tagSpanStart() |
      LOOKAHEAD(2) tagSpanEnd() |
      LOOKAHEAD(2) tagDontCareStart() |
      LOOKAHEAD(2) tagDontCareEnd() |
      <PCDATA>
    )*
}
0 голосов
/ 15 апреля 2011

Scanner.useDelimiter (Pattern) API, кажется, то, что вы ищете.Вам придется использовать строку шаблона, разделенную OR (|).

Этот шаблон может действительно очень быстро усложниться.

...