본문 바로가기

Dev/Android

[Android]Sax Parser를 이용한 XML 파싱!


[Android] SAX Parser를 이용한 XML 파싱! 

android 개발을 하다보면, 웹에있는 데이터를 긁어?! 와야할 때가 생기는데요, 이럴때 사용되는 기술이 파싱기술 입니다. 안드로이드와 같은 모바일 앱의 경우 XML이나 HTML등과 같은 특정 마크업랭귀지로 이루어진 문서에서 데이터를 파싱해 오는 것은 아주 중요한 목표라고 생각됩니다. 이번 포스팅은 기존에 SAX Parser를 사용하여 XML 데이터를  읽어오는 방법을 알아보도록 하겠습니다.

전산학에서 파싱((syntactic) parsing)은 일련의 문자열을 의미있는 토큰(token)으로 분해하고 이들로 이루어진 파스 트리(parse tree)를 만드는 과정을 말한다.

기본적으로 해당글은 아래 원문에 기초하여 작성했다는걸 말씀드리면서 본격적으로 포스팅을 시작해 보도록 하겠습니다.  그럼 본격적으로 SAX Parser에 대해서 알아보도록 할까요?! 








# SAX Parser 의 동작과 구성

SAX Parser의 가장 큰 장점은 메모리를 사용하는(차지하는) 공간(footprint)이 적다는 것입니다. SAX Parser(줄여서 SP)는  라인단위로 마킹혹은 체크를 하면서 분석하기 때문에 XML 데이터 모두를 메모리에 올려 놓지 않아도 되기 때문이겠죠. 이것은 대용량의 XML데이터를 파싱하면서 그 성능을 확연히 느낄수 있는데요, 빠르단 얘기입니다...

SP의 약점으로는 반드시 특정 Element에 대해서 어떻게 동작할지 사용자가 직접 정의해 줘야 한다는 점입니다. 뭐 이건 개발자로써 너무 당연하기에 약점이라 볼수는 없지 아니하지 아니할수 없지 않죠?!

# XML에 대한 이해!!

해석에 따르자면 XML의 의미는 저장과 수송을 위한 데이터라고 하네요.. 마크업 랭귀지인데 데이터라니!!! 라는 생각을 하실수도 있겠지만, 지금 우리가 처한 현실에서 XML은 데이터 맞습니다. 근데 난 HTML을 파싱하고 싶은데 XML밖에 못해요? 라고 질문을 하시는 분이 있으실수 있습니다. 네, HTML과 XML은 분명 비슷한(Extend:확장한) 언어가 맞습니다. 하지만 XML 이란 언어는 HTML의 언어보다 조금더 확실하고 정확한 규약에 의해 작성된 언어라고 보시면 됩니다. XML코드와 HTML코드를 한번 비교해 보세요. 보시면 XML코드는 라인이 이~뻐~. 포멀해~ 따라서 파싱을 하는데 더욱 편리합니다. 

그렇다면 간단하게 XML의 문서구조에 대해서 알아봅시다. XML은 HTML과 비슷하게 기본적으로 여러개의 태그(또는 Element)쌍으로 이뤄지는데요<example>라 쓰이는 opening tag와 </example>라 쓰이는 closing tag가 이러한 태그 쌍을 말하는 거죠, 이러한 태그쌍에 의해 둘려싸임을 당한 -_-.. 녀석이 value 입니다. 또한 opening tag에서 속성을 가질수도 있습니다. 물론 이러한 속성도 SP가 다룰수(handle) 있습니다요. <example attr='value'> 요기서 attr이 속성, 'attrValue'이것이 속성 값이겠죠?

<Element attr = 'attrValue'> value </example> 

일단 요정도만 아시면 파싱을 하기위한 XML 구조 분석 이해가 끝났습니다. (자체 마무리...)



# 1 단계 : 어플 세팅하기?!!

이제 실제로 이클립스를 열고 프로젝트를 하나 만듭니다. 가장 먼저 해야할 일은 퍼미션 추가 입니다! 아래와 같이 uses permission에 추가해 줍니다!


그러면 아래와 같이 AndroidManifest.xml 파일에 구문이 추가 됩니다!
 

 
셋팅끝!



# 2 단계 : 클래스와 UI 만들기~

첫째로 여러분이 보여주고 싶은 뷰(엑티비티)를 만드는 것입니다! 자기에 입맛에 따라 파싱해온 데이터를 어떻게 보여줄지 안드로이드 레이아웃을 만들어 주는과정이죠. 저는 아래와 같은 코드를 사용하였습니다. 모든 코드는 원문과 다르게 커스터마이징 하였습니다.

@ main.xml  - (실제로 보여질 화면..아이템은 'listview_item.xml'에서 커스터마이징 했습니다.)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
		<ListView android:id="@+id/main_lv_content"
		    android:layout_width="fill_parent"
		    android:layout_height="fill_parent"/>
</LinearLayout>


 
@ listview_item.xml  - (ListView의 아이템을 커스터마이징 하기위해 만들었습니다.)  
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/main_activity_tv_title"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
        android:text="No Title"
        android:textSize="15sp" />

    <LinearLayout android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="10sp"
            android:text="Artist : "/>
        <TextView android:id="@+id/main_activity_tv_artist"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="No Artist"
            android:textSize="10sp" />
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="10sp"
            android:text="Country : "
            android:layout_marginLeft="10sp"/>
        <TextView android:id="@+id/main_activity_tv_country"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="No Country"
            android:textSize="10sp" />
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="10sp"
            android:text="Company : "
            android:layout_marginLeft="10sp"/>
        <TextView android:id="@+id/main_activity_tv_company"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="No Company"
            android:textSize="10sp" />
    </LinearLayout>
    <LinearLayout android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="10sp"
            android:text="Price : "/>
        <TextView android:id="@+id/main_activity_tv_price"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="No Price"
            android:textSize="10sp" />
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="10sp"
            android:text="Year : "
            android:layout_marginLeft="10sp"/>
        <TextView android:id="@+id/main_activity_tv_year"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="No Year"
            android:textSize="10sp" />
    </LinearLayout>
</LinearLayout>


# 3 단계 : 자료 처리기(Content Handler) 만들기.

Content handler 는 실제로 XML을 파싱하면서 생기는 이벤트를 처리해주는, 그래서 우리가 원하는 결과를 얻도록 도와주는 녀석입니다. 여기서 어떤 Element의 value를 가져올지, 설정하는 부분이기도 합니다. <example> 과 같은 opening tag를 만나면, 아래 클래스의 메소드중 startElement가 호출됩니다. 또한 </example>과 같은 closing tag를 만나면, endElement 메소드가 호출됩니다. 특히나 SP(SAX parser)의 경우 closion tag들을 만나면, characters 라는 메소드가 호출되어 opening tag와 closing tag 사이의 value 값을 얻을수 있습니다. 이러한 개념을 바탕으로 아래와 같이 DataGetterSetters라는 객체들을 저장할수 있는 배열리스트에 차곡차곡 쌓아논 후에, 보여주면 되겠죠?

@ DataHandler.java 
package wecon.test.parse;

import java.util.ArrayList;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import android.util.Log;

public class DataHandler extends DefaultHandler {
	private String elementValue = null;
	private Boolean elementOn = false;
	private ArrayList<DataGetterSetters> dataList = new ArrayList<DataGetterSetters>();
	private DataGetterSetters data = null;
	
	public ArrayList<DataGetterSetters> getData()
	{
		return dataList;
	}
		
	public void startElement(String uri, String localName, String qName,
            Attributes attributes) throws SAXException {
        elementOn = true; 
        if(localName.equals("CD")){
        	data = new DataGetterSetters();
        }       
    }
 
    /**
     * This will be called when the tags of the XML end.
     **/
    @Override
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
 
        elementOn = false;
        
        if(localName.equalsIgnoreCase("title")){
        	data.setTitle(elementValue);
        } else if(localName.equalsIgnoreCase("artist")){
        	data.setArtist(elementValue);
        } else if(localName.equalsIgnoreCase("country")){
        	data.setCountry(elementValue);
        } else if(localName.equalsIgnoreCase("company")){
        	data.setCompany(elementValue);
        } else if(localName.equalsIgnoreCase("price")){
        	data.setPrice(Double.parseDouble(elementValue));
        	Log.i("debuging", "price : " + data.getPrice());
        } else if(localName.equalsIgnoreCase("year")){
        	data.setYear(Integer.parseInt(elementValue));
        	data.setPeriod(Integer.parseInt(elementValue));
        } else if(localName.equalsIgnoreCase("cd")){
        	dataList.add(data);
        	data = null;
        }
    }
 
    /**
     * This is called to get the tags value
     **/
    @Override
    public void characters(char[] ch, int start, int length)
            throws SAXException {
        if (elementOn) {
            elementValue = new String(ch, start, length);
            elementOn = false;
        }
 
    }
	
}


# 4 단계 : Getters, Setters 만들기.


라인수는 굉장히 길어보이지만 정말 심플한 클래스입니다. 실제적으로 우리가 얻어야 할 값들에 대해서 정의를 하고, getter, setter 메소드만 정의해준 격이죠.. 일종의 우리가 얻어야할 자료들을 담기에 적당한 Box를 만든다고 보시면 됩니다. 아래의 코드를 보시면 Box안에 title, artist, country 등등 여러 물건들이 들어가는 것을 볼수 있습니다. 이러한 물건이 들어가는 Box가 한개가 아니므로, 이러한 이유에서 위의 DataHandler.java에서 DataGetterSetters객체를 ArrayList로 만든것 입니다.

@ DataGetterSetters.java
 
package wecon.test.parse;

import java.util.ArrayList;

import android.util.Log;

public class DataGetterSetters {
	
	private static int mPeriod;
	private String mTitle;
	private String mArtist;
	private String mCountry;
	private String mCompany;
	private double mPrice;
	private int mYear;
	
	public void setPeriod(int period)
	{
		if(period<1980){
			this.mPeriod = 7;
		}else if(period<1990){
			this.mPeriod = 8;
		}else if(period<2000){
			this.mPeriod = 9;
		}
	}
	
	public int getperiod()
	{
		return mPeriod;
	}
	
	public void setTitle(String title)
	{
		this.mTitle = title;
	}
	
	public String getTitle()
	{
		return mTitle;
	}
	
	public void setArtist(String artist){
		this.mArtist = artist;
	}
	
	public String getArtist(){
		return mArtist;
	}
	
	public void setCountry(String country)
	{
		this.mCountry = country;
	}
	
	public String getcountry()
	{
		return mCountry;
	}
	
	public void setCompany(String company)
	{
		this.mCompany = company;
	}
	
	public String getCompany()
	{
		return mCompany;
	}
	
	public void setPrice(double price)
	{
		this.mPrice = price;
	}
	
	public double getPrice()
	{
		return mPrice;
	}
	
	public void setYear(int year)
	{
		this.mYear = year;
	}
	
	public int getYear()
	{
		return mYear;
	}
}


# 5 단계 : 실행코드 만들기.


이제 만들어놓은 클래스들로 파싱만하면 됩니다. SAX parser 인스턴트를 만들고, handler도 만듭니다. URL은 우리가 파싱해오고 싶은 주소를 말합니다. 우리가 파싱할 대상은 아래 주소로 하겠습니다.

http://www.xmlfiles.com/examples/cd_catalog.xml


@ ParseTestActivity.java

package wecon.test.parse;

import java.net.URL;
import java.util.ArrayList;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.htmlcleaner.TagNode;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;

/**
 * @subject android xml parse example
 * @author HakSung.Kim (ddongEee3486@gmail.com)
 * @blog http://twoday.tistory.com
 * @since 2012.01.09
 */

public class ParseTestActivity extends Activity {
	private ArrayList<datagettersetters> dataList;
	private DataListAdapter adapter;
	private ListView listView;
	
	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        listView = (ListView)findViewById(R.id.main_lv_content);
        
        try {
            //------ Main parse section start ------//
            SAXParserFactory saxPF = SAXParserFactory.newInstance(); 
            SAXParser saxP = saxPF.newSAXParser();
            XMLReader xmlR = saxP.getXMLReader();
            URL url = new URL("http://www.xmlfiles.com/examples/cd_catalog.xml"); // URL of the XML
            DataHandler myDataHandler = new DataHandler();
            xmlR.setContentHandler(myDataHandler);
            xmlR.parse(new InputSource(url.openStream()));
            //------ Main parse section end ------//
            
            // load parsing data & View
            dataList = myDataHandler.getData();
            adapter = new DataListAdapter(this, dataList);
            listView.setAdapter(adapter);
            
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}


# 별첨 : ListView 커스터마이징!!

파싱해온 값들을 이쁘게 보여주기 위해 커스터마이징 한 xml을 item으로 연결시켜주는 Adapter class 입니다.

@ DataListAdapter.java
 
package wecon.test.parse;

import java.util.ArrayList;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

public class DataListAdapter extends BaseAdapter {
	private ArrayList<DataGetterSetters> dataList;
	private Context context;
	
	public DataListAdapter(Context context, ArrayList<DataGetterSetters> dataList)
	{
		this.context = context;
		this.dataList = dataList;
	}
	@Override
	public int getCount() {
		return dataList.size();
	}

	@Override
	public Object getItem(int position) {
		return dataList.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		View v = convertView;
DataGetterSetters data = dataList.get(position); LayoutInflater inflater; if(v == null){ inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = inflater.inflate(R.layout.listview_item,null); } if(data!=null){ TextView title = (TextView)v.findViewById(R.id.main_activity_tv_title); TextView artist = (TextView)v.findViewById(R.id.main_activity_tv_artist); TextView country = (TextView)v.findViewById(R.id.main_activity_tv_country); TextView company = (TextView)v.findViewById(R.id.main_activity_tv_company); TextView price = (TextView)v.findViewById(R.id.main_activity_tv_price); TextView year = (TextView)v.findViewById(R.id.main_activity_tv_year); title.setText(data.getTitle()); artist.setText(data.getArtist()); country.setText(data.getcountry()); company.setText(data.getCompany()); price.setText(""+data.getPrice()); year.setText(""+data.getYear()); } return v; } }


# 결과