Android で HTML解析

昔、低レベルな仕事をしていた時に、
時々、org.xml.sax.helpers.DefaultHandlerXMLパーサを書いて遊んでいた。

HTML を、これで SAXParser にパースさせていては、当然ダメである。

tagSoup というHTML用のパーサがある。
org.xml.sax.ContentHandler を実装して、org.ccil.cowan.tagsoup.Parser というパーサで解析する。
Android上で実行してみる。


import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
/**
 * HtmlHandler
 */

public abstract class HtmlHandler implements org.xml.sax.ContentHandler{
   private Stack<String> pathStack;
   private Map<String,String> bmap;
   private Map<String,Map<String,String>> attrmap;

   /**
    * タグ読込開始
    * @param qName
    * @param xpath
    * @param attributes
    */

   public abstract void onStartedTag(String qName,String xpath,Map<String,String> attributes);

   /**
    * タグ読込完了
    * @param qName タグ名
    * @param xpath xmlフルパス
    * @param attributes 属性key="value"をMap変換後
    * @param body tag body
    */

   public abstract void onReadedTag(String qName,String xpath,Map<String,String> attributes,String body);

   @Override
   public void startDocument() throws SAXException{
      pathStack = new Stack<String>();
      bmap = new HashMap<String,String>();
      attrmap = new HashMap<String,Map<String,String>>();
   }
   @Override
   public void setDocumentLocator(Locator locator){
   }
   @Override
   public void startPrefixMapping(String prefix, String uri) throws SAXException{
   }
   @Override
   public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException{
      pathStack.push(qName);
      Map<String,String> amap = new HashMap<String,String>();
      for(int i=0;i < atts.getLength();i++){
         amap.put(atts.getQName(i),atts.getValue(i));
      }
      String xpath = getXPath();
      attrmap.put(xpath, amap);
      onStartedTag(qName, xpath, amap);
   }
   @Override
   public void characters(char ch, int start, int length) throws SAXException{
      String path = getXPath();
      if (bmap.containsKey(path)){
         bmap.put(path, bmap.get(path)+(new String(ch,start,length).trim()));
      }else{
         bmap.put(path, (new String(ch,start,length).trim()));
      }
   }
   @Override
   public void endElement(String uri, String localName, String qName) throws SAXException{
      String path = getXPath();
      onReadedTag(qName,path,attrmap.get(path),bmap.get(path));
      attrmap.remove(path);
      bmap.remove(path);
      pathStack.pop();
   }
   @Override
   public void processingInstruction(String target, String data) throws SAXException{
   }
   @Override
   public void ignorableWhitespace(char
 ch, int start, int length) throws SAXException{
   }
   @Override
   public void skippedEntity(String name) throws SAXException{
   }
   @Override
   public void endPrefixMapping(String prefix) throws SAXException{
   }
   @Override
   public void endDocument() throws SAXException{
   }
   private String getXPath(){
      StringBuffer sb = new StringBuffer();
      for(String s : pathStack){
         sb.append("/"+s);
      }
      return sb.toString();
   }

}

使い方は、SAXParser で解析した時と同様のハンドラで、
似たような呼び出し方になる。
以下は、Android での実行のコードサンプル

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.ccil.cowan.tagsoup.Parser;
import org.xml.sax.InputSource;

==============================================

Parser parser = new Parser();
DefaultHttpClient   httpclient = new DefaultHttpClient();
HttpResponse response = httpclient.execute(new HttpGet(url));
int httpstatus = response.getStatusLine().getStatusCode();

if (httpstatus==HttpStatus.SC_OK){

   in = response.getEntity().getContent();

   InputSource source = new InputSource(new InputStreamReader(in, "UTF-8"));

   parser.setContentHandler(new HtmlHandler(){
      @Override
      public void onStartedTag(String qName, String xpath, Map<String,String> attributes){
      }
      @Override
      public void onReadedTag(String qName, String xpath, Map<String,String> attributes, String body){

         // タグ名→ qName
         // XPath → xpath
         // 属性Map → attributes
         // タグBody → body

      }
   }
);
   parser.parse(source);
}