IBM Korea Skip to main content
       IBM 홈    |  제품 & 서비스  |  고객지원 & 다운로드  |  회원가입  

자바 프로그래밍으로 온라인 XML 데이터 검색하기
XML 파싱

Level: Introductory

Dan Becker
Software developer, IBM Software Group
2002년 9월

인터넷에 정보를 퍼블리싱하는 것이 보편화 되어 가면서 이러한 정보를 발견하고 요청하는 것 또한 자연스러운 일이 되었다. 이 글은 자바 프로그래밍을 사용하여 웹 기반 XML 데이터를 얻고 데이터를 파싱하여 필요한 엘리먼트와 속성들을 필터링하고 요청된 정보를 이용하여 작업을 수행하는 방법을 설명한다.

이 글은 2002년 8월 IBM developerWorks 저널에 기재되었다.

인터넷에 정보를 내보내는 것이 자연스러운 현상인 만큼 정보를 발견하고 요청하는 것도 자연스러운 일이 되었다. XML은 비즈니스와 소비자가 정보를 더욱 쉽게 공유할 수 있도록 데이터를 설명하는데 사용할 수 있는 기술 중 하나이다. 웹 상의 XML 정보의 예제에는 날씨 정보, 증권 시세표, 선적표, 항공요금표, 경매가격, 오늘의 유머 같은 정보들을 포함하고 있다. 이 데이터에 왜 액세스하려 하는가? 아마도 자기 지역의 날씨 데이터를 저장하고 검색하거나, 선적 과정을 조사 할 작은 유틸리티를 작성해야 할 이유에서 일 것이다. 여러분이 자바 개발자라고 가정한다면 파싱과 조작이 쉬운 XML 정보를 찾을것이다.

물론 HTML 페이지가 있지만 대부분의 데이터는 XML 포맷에서 비롯되고 웹 서버에는 HTML로 변환된다. 많은 웹 사이트들은 두 포맷으로 정보를 제공한다. 구 브라우저와 데이터를 단순히 검색하고자 하는 사람들에게는 HTML로 제공되고, 데이터 모음과 분석을 원하는 넷 정통 프로그래머에게는 XML 포맷으로 제공된다. HTML 페이지에서 데이터를 가져오는 것 보다는 자바 프로그래밍으로 XML 정보를 파싱하고 모으는 것이 훨씬 더 쉽다. 우선 표준 XML 파서가 있으면 다운로드하기가 쉽다. 둘째, 문서 구조는 시간이 흐르면서 변하기 때문에 HTML 태그 보다는 XML 엘리먼트와 속성으로 나타내는 것이 더욱 쉽다.

시작하기
XML 데이터를 파싱하기 위해서는 XML 파서가 있어야 한다. 물론 여러분 정도라면 각자의 파서를 작성할 수 있겠지만 완벽한 기능의 표준 파서들이 무료로 나와있다. Java 2 Platform, version 1.3 Enterprise Edition (J2EE)도 유용할 것이다. 파서는 개발자의 시각에서 볼 때 속도나 신뢰성에서 차이가 나지만 프로그래밍 모델과 인터페이스는 동일하다. 게다가 무료이다.

자바 개발자라면 javax.xml 패키지에 주의를 기울여야 한다. 이 패키지에는 XML 데이터를 파싱하는데 필요한 모든 코드가 있다. XML 문서를 다른 형식으로 변형하는 패키지와 함께 파서 구현 패키지들이 있다.

DOM vs SAX 파서
두 가지 유형의 XML 문서 파서가 있다. 이들은 XML 문서에 액세스하는 방식부터 다르다:

  • Document Object Model (DOM). XML 문서로의 랜덤 액세스(random access)에 사용된다. DOM의 장점은 메모리 안에 문서의 전체 모델을 갖고 있다는 점이다. 이는 모든 XML 엘리먼트에 어떤 순서로든지 액세스가 가능하다는 것을 의미한다. 하지만 큰 문서의 경우 둔해 질 수 있다. 메모리에서 실행시키지 않으면 시스템이 한계에 다다를 때 퍼포먼스가 느려진다.

  • Simple API for XML (SAX). 순차적 액세스(sequential access)에 사용된다. SAX의 장점은 문서의 한 부분이 메모리에서 사용될 수 있기 때문에 보다 큰 문서를 핸들할 수 있다는 점이다. SAX의 단점은 엘리먼트를 순서대로 처리해야 하며 한번에 볼 수 있는 문서 부분도 작다. SAX를 이용하여 문서를 파싱할 때 XML 부분을 저장할 수 있다.

온라인 정보 사이트(날씨 데이터 또는 주식 현황)에 액세스하는 XML 문서는 작고 집중되어 있는 경향이 있다. 일반적으로 특정 사이트나 주식 데이터를 검색하고 정보를 처리하고 다음 쿼리 또는 문서가 고려된다. 이러한 유형의 액세스의 경우 DOM은 선호되는 파싱 방식이다. 반면 SAX 파싱은 이 분야가 아니다.

XML 문서 얻기
파서가 있으므로 웹에서 XML 문서에 액세스 할 수 있다. Listing 1은 인터넷에서 날씨 데이터를 가져오는 예제이다:

Listing 1. XML 날씨 데이터 예제

<forexml site="Austin">
<observation
    city="Austin, Texas"
    temp.F="75"
    rel_hum.percent="31"
    wind_speed.knt="8"
    skies="partly cloudy"
/>
<almanac
    sunrise="7:08 AM"
    sunset="6:21 PM"
/>
<forecast
    type="nearterm"
    source="NWS"
    day="THIS AFTERNOON"
    weather="SU"
    high_temp="77"
    text="HIGHS 75 TO 80. WEST 15 TO 20 MPH."
/>
</forexml>

Unisys Corporation은 National Oceanic and Atmospheric Administration와 수백개의 날씨 관측 사이트와 협력하여 www.weather.unisys.com/forexml.cgi에 XML 포맷으로 기상 관측을 내보내고 있다. URL (예를 들어, www.weather.unisys.com/forexml.cgi?Austin) 다음에 요청 필드에 도시 이름 같은 관측 지역의 이름을 입력하라. 물음표는 요청 필드를 URL에 추가하는데 필요한 경계기호 이다. 읽을 수 있는 관측 결과가 XML 포맷으로 리턴된다. (Listing 1). J2EE에 제공된 XML 파서를 사용해보자.

개념상으로 XML 문서를 DOM으로 볼 수 있다. forexml 노드는 문서의 노드 트리에서 뿌리 노드 (root node)로서 보여진다. 뿌리 노드는 observation, almanac, forecast 같은 자식 노드를 갖고 있다. 이들 노드 각각 observation 이나 0 또는 그 이상의 엘리먼트를 갖고 있다. 예를들어 observation 엘리먼트는 75 값을 가진 temp.F 속성을 갖고 있다. 그림 1은 문서 노드의 계층을 나타낸다:

그림1. 문서 노드 계층(맑은 날!)
Hierarchy of document nodes (A pleasant day!)

주어진 XML 문서에서 관심있는 엘리먼트와 속성을 어떻게 필터링 할까? 우선 파싱하고자 하는 인자를 모아야 한다. Listing 2는 명령행에서 프로그램 인자를 모으고 그들을 processDocument라고 하는 메인 프로세싱 메소드로 전달하는 표준 main 메소드를 보여주고 있다. Java applet과 HTML 매개변수로 비슷한 작동을 수행할 수 있다. Listing 2에서 코드는 표준이고 XML 프로그래밍이 없다. 다음에 온다.

Listing 2. 날씨 애플리케이션 메인 메소드

// Given a list of arguments, parse a city
// name and a list of attributes to filter.
// Alternatively, accept "all" as an indication
// to get all attributes

public static void main( String [] args ) {
   if ( args.length == 0) {
     System.out.println
       ( "Usage: java Weather city"
           +" [all|attributes]" );
     System.out.println
       ( "Example:"
           +" java Weather Austin temp.F" );
     System.exit( 0 );
   }
   // Gather arguments
   String city = args[ 0 ];
   String [] attrNames = null;
   if ( args.length == 1) {
     attrNames = new String [] { "all" };
   } else {
     // args.length > 1
     attrNames =
       new String [ args.length - 1 ]; 
     for ( int i = 1; i < args.length; i++)
       attrNames[ i - 1 ] = args[ i ];
   } // if
	
   // Process document
   processDocument( city, observationTagName,
     attrNames );
} // main

Listing 3은 XML 문서를 얻어 이를 파싱하는데 쓰이는 전형적인 구조이다:

Listing 3. DocumentBuilder 요청하기

// Given a city string, an element name, and
// a list of attributes, print the values of
// the attributes found in the XML document.
public static void processDocument(
   String city,
   String elementName, String [] attrNames ) {
   DocumentBuilderFactory dbf =
      DocumentBuilderFactory.newInstance ( ) ;
      
   try {
         DocumentBuilder db =
            dbf.newDocumentBuilder ( ) ;
            
         if ( city != null ) {
             String urlString =
                 weatherSite + city;
             Document document = getDocument
                ( db, urlString );
             if ( document != null ) {
                Node [] matchNodes =
                   getAttributes( document,
                      elementName, attrNames );
                 if (null != matchNodes ) {
                   if ( matchNodes.length > 6 )
                       printNodes( "City=" + city,
                         matchNodes, true );
                   else  
                       printNodes( "City=" + city,
                         matchNodes, false );
                 } else 
                     System.out.println (  
                        "Element \"" +
                        elementName +
                   "\" not found in document." );
         } else
             System.out.println 
                ( "No XML created from URL="
                   + urlString );
   } // if         
  } catch (  
      ParserConfigurationException e ) { 
      e.printStackTrace ( ) ; 
  }
} // processDocument

먼저 DocumentBuilderFactory의 인스턴스를 얻는다. 일단 팩토리 객체를 얻으면 새로운 Document Builder 메소드를 사용하여 DocumentBuilder를 얻는다. 이것은 XML 인풋 스트림을 파싱할 수 있는 객체이다. 리턴되는 실제 DocumentBuilderFactory 객체는 다음 설정에 의존한다:

  • javax.xml.parsers.DocumentBuilder Factory system property
  • JAVA_HOME/lib/jaxp.properties
  • META-INF/services/javax.xml.parsers.DocumentBuilderFactory 서비스(jar file)
  • platform default

J2EE를 설치할 때 디폴트 설정을 사용한다면 이 예제는 올바르게 작동한다 특별한 파서 기능을 이용하고자 할 때에는 다른 파서를 지정한다. 여기서 얻을 수 있는 교훈은 많은 자바 XML 프로세서를 가지고 작업하라는 것이다.

Listing 3 의 코드 나머지 부분은 속성과 일치하는 XML 문서를 찾아 프린트한다. 이것은 고급 메소드 이다. URL에서 문서를 얻고 필터링하고 속성을 프린트하는 것에 대한 설명은 다음 섹션에 있다.

Listing 4는 웹 에서 XML 문서를 얻고 Listing 3에서 getDocument 메소드를 검색하는 것을 보여주고 있다:

Listing 4. 요청된 URL에서 XML 문서 파싱하기

// Using the given document builder object,
// construct and return an XML DOM
// from the given URL.

public static Document getDocument
  ( DocumentBuilder db, String urlString ) {
   try {
     URL url = new URL( urlString );
     
     try {
       URLConnection URLconnection =
          url.openConnection ( ) ;
       HttpURLConnection httpConnection =
          (HttpURLConnection)
         URLconnection;
         
       int responseCode =
        httpConnection.getResponseCode ( ) ;
       if ( responseCode ==
        HttpURLConnection.HTTP_OK) {
           InputStream in =
            httpConnection.getInputStream ( ) ;
            
           try {
             Document doc = db.parse( in );
             return doc;
           } catch(
            org.xml.sax.SAXException e ) {
                e.printStackTrace ( ) ;
           }
        } else {
            System.out.println
               ( "HTTP connection response !=
               HTTP_OK" );
               
        } 
     } catch ( IOException e ) { 
          e.printStackTrace ( ) ;
     } // Catch
  } catch ( MalformedURLException e ) {  
      e.printStackTrace ( ) ;
  } // Catch
  return null;
} // getDocument

우선 URLConnection 은 URL 스트링에 열려있다. 날씨 사이트가 HTTP 요청을 통해서 작동하는 것을 알고 있기 때문에 단순한 URLConnectionHttpURLConnection에 캐스팅할 수 있다. 그런 다음, 서버에서 온 응답 코드를 테스트한다. 요청에 에러가 없다는 것을 서버가 표시하면 커넥션 스트림을 열고 응답을 XML 스트림으로 취급한다. DocumentBuilder 파스 메소드는 파싱하고 데이터 스트림을 javax.xml. 문서로 돌린다. 이 과정 중 어떤 것이라도 실패하면 메소드는 XML 문서 객체를 만들 수 없다는 것을 나타내는 null을 리턴한다.

원하는 엘리먼트를 필터링하기
여러 엘리먼트 태그들이 root 태그인 forexml 다음에 나왔다는 것을 기억하라:

  • observation. 날씨 관측 지역 및 장소, 온도, 습도, 풍속 등의 데이터 포함.
  • almanac. 관측 장소의 일출과 일몰 등의 데이터 포함.
  • forecast. 앞으로의 날씨 예상 포함.

Listing 5는 observation 태그를 사용하고 다른 태그들은 무시된다:

Listing 5. XML 엘리먼트와 속성 맞추기

// Given an XML document,
// return the values of all attributes
// for the given element.

public static Node [] getAttributes
   ( Document document,
    String elementName, String [] attrNames ) {
    
    // Get elements with the given tag name
    // (matches on * too)
    NodeList nodes = document.getElementsByTagName
       ( elementName );
    if ( nodes.getLength() < 1) {
      return null;
    }
    
    Node firstElement = nodes.item( 0 );
    NamedNodeMap nnm =
       firstElement.getAttributes ( ) ;
   
    if (nnm != null) {
      // Test the value of each attribute
      Node [] matchNodes = new Node
         [ attrNames.length ];
        
       for (int i = 0; i < attrNames.length; i++){
          boolean all =
            attrNames[ i ].equalsIgnoreCase("all");
           if (all) {
             // named node map
             int nnmLength = nnm.getLength();
             matchNodes = new Node[ nnmLength ];
             
             for ( int j = 0; j < nnmLength; j++){
                matchNodes[ j ] = nnm.item( j );
             }
             return matchNodes;
           } else {
               matchNodes[ i ] = nnm.getNamedItem
                 ( attrNames[ i ] );
               if ( matchNodes[ i ] == null ) {
                 matchNodes[ i ] =
                     document.createAttribute
                     ( attrNames[ i ] );
                  ((Attr)matchNodes[ i ]).setValue
                    ( unknownAttribute );
               }
           } // if
      } // for
      
      return matchNodes;
   } // if
   
   return null;
} // printDocumentAttrs

Document 클래스가 getElementsByTagName 메소드를 가지고 있는 것에 주목하라. observation 엘리먼트 이름을 사용하여 메소드는 문서의 다른 엘리먼트에서 요청된 태그를 필터링하고 NodeList 객체를 리턴한다.

코드는 첫 번째 엘리먼트를 선택하고 getAttributes을 사용하여 그 엘리먼트용 XML 속성 리스트를 얻는다. 속성은 NamedNodeMap 객체에 리턴된다. 이러한 각각의 노드들은 요청된 속성 이름이 맞는지 또는 와일드 카드가 모두와 맞는지를 확인하기 위해 테스트된다. 이 예제는 노드 객체들의 어레이를 구현하고 이것을 caller에게 리턴한다. 엘리먼트나 속성을 찾지 못하면 메소드는 null을 리턴한다.

자바 프로그래밍으로 온라인 XML 데이터를 조작하는 주요 단계는 다음으로 요약될 수 있다:

  1. DocumentBuilderFactory를 이용하여 XML 파서 요청하기
  2. DocumentBuilder를 이용하여 XML 파서 및 구현하기
  3. 요청된 엘리먼트와 속성 찾기

발견한 데이터를 어떻게 이용할 것인가는 이제 여러분에게 달려있다. 다음 섹션에서는 XML 데이터를 핸들링하는 방법을 설명하겠다.

XML 데이터의 프린팅
Listing 6은 노드들을 통해 반복하고 속성 데이터를 프린트하는 방법이다:

Listing 6. XML 속성 데이터를 프린트하는 메소드 예제

// Given a set of nodes,
// print the values of the nodes.
// The nodes may be given a title.
// The nodes may be printed on one line
// (grouped) or many.

public static void printNodes
  ( String title, Node [] nodes,
       boolean grouped ) {
       
       // Report the name value of each node
       System.out.print( title );
       
       if (grouped){
          System.out.println ( ) ;
       }
       // Walk through the nodes.
       for ( int i = 0; i < nodes.length; i++ ) {
           Node node = nodes[ i ];
           System.out.print
              ( grouped ? " " : ", " );
           System.out.print
              ( node.getNodeName() + "=" +
              node.getNodeValue() );
              
            if ( grouped ) {
               System.out.println ( ) ;
            }  
    } // for
    
    if (!grouped) {
        System.out.println ( ) ;
    }  
} // printAttributeValues

Listing 5 에서는 캡쳐하고자하는 XML 엘리먼트와 속성 데이터를 맞춰 노드 어레이를 만든다. 이러한 특정한 프린트 루틴은 한 라인 또는 여러 라인에 걸쳐 모든 속성들을 프린트할 수 있다.

이제 여행의 끝에 와 있다. Listing 7은 파싱하고 선택한 XML 속성 데이터 아웃풋 예제이다:

Listing 7. 자바 날씨 프로그램 실행에 따른 아웃풋 예제

City=Austin
   city=Austin, Texas
   latitude=30.30
   longitude=-97.70
   time=3 PM CDT ...
   temp.F=84
   temp.C=28
   rel_hum.percent=58
   wind.string=S at 11 knt
   skies=clear    

이 경우 데이터를 System.out 으로 프린트했지만 각자의 애플리케이션에서는 데이터를 데이터베이스에 저장할 수 있고 트랜드 분석이나 그래프 그리기도 가능하다. 이러한 날씨 관측은 매 시간 업데이트 되어 주기적인 점검이 가능하다.

이 프로그램을 사용하여 모든 관측 속성들을 찾거나 온도 또는 상대 습도, 위도와 경도 같은 서브셋도 요청할 수 있다!

결론
자바 프로그래밍을 이용하여 온라인 XML 데이터를 검색하는 방법을 설명했다. 가장 중요한 단계는 관심있는 내용이 수록되어있는 사이트를 찾는 것이다. 일단 사이트를 발견하면 데이터 추출 작업은 모든 XML 문서의 경우 같다. 우선 문서를 요청하고 그런 다음 파싱한다. 마지막으로 원하는 엘리먼트와 속성을 필터링한다. 표준 XML 파서를 이용하면 직접 작성하는 것 보다 강력한 툴을 가질 수 있다.

참고자료

목 차:
시작하기
DOM vs SAX 파서
XML 문서 얻기
원하는 엘리먼트를 필터링하기
XML 데이터 프린트하기
결론
참고 자료
필자 소개
기사에 대한 평가
관련 dW 링크:
Subscribe to the developerWorks newsletter
Understanding JTS, Part 2
Subscribe to the developerWorks newsletter
US 원문 읽기
Also in the Java zone:
Tutorials
Tools and products
Code and components
Articles
Also in the XML zone:
Tutorials
Tools and products
Code and components
Articles
필자소개
Dan Becker는 IBM(Austin, Texas)의 소프트웨어 그룹에서 일하고 있다. 현재는 Interactive Financial Services 프로젝트에서 온라인 웹 뱅킹 시스템 작업을 수행하고 있다.
이 기사에 대하여 어떻게 생각하십니까?

정말 좋다 (5) 좋다 (4) 그저그렇다 (3) 수정보완이 필요하다(2) 형편없다 (1)

  회사소개  |  개인정보 보호정책  |  법률  |  문의