반응형

이 글에서는 Java 2 Java 2, Enterprise Edition, v 1.4 를 사용합니다. 다운로드



SUN JAVA STREAMING XML PARSER 소개

XML을 사용하는 대부분의 Java 개발자는 SAX (Simple API for XML) 라이브러리와 DOM(Document Object Model) 라이브러리에 익숙할 것이다. SAX는 이벤트 기반의 API이며, 이는 일반적으로 프로그래머가 파서와 몇 개의 리스너를 등록하고, 특정 XML 문법 생성자(예를 들어 요소나 속성)가 도착되면 리스너 메소드가 호출된다는 의미이다. 그 반대로 DOM은 트리 기반의 아케텍처를 가지며, 전체 문서를 스캔하여 마주치는 각각의 문법 생성자의 오브젝트 트리를 구축한다. 프로그래머는 스캔이 완료된 후 오브젝트 트리에 접근하여 수정할 수 있다

이 두 방법 모두 각각의 결점을 가지고 있다: 리스너를 이용하는 이벤트 기반의 API는 일반적으로 다루기 힘들다. 리스너들이 파서에 의해 조종되기 때문이다. 트리 기반의 API는 스캔되는 문서의 양에 비해 과도한 양의 메모리를 소모할 수 있다. 이제 Java 개발자들이 XML 을 스캔하는데 이용할 수 있는 세 번째 API가 등장하였다. StAX (Streaming API for XML parse)가 그것이다.

SJSXP란?

SJSXP(Sun Java Streaming XML Parser)는 StAX를 빠른 속도로 구현한다. 썬마이크로시스템즈와 협력하고 있는 BEA Systems, XML-guru James Clark, Stefan Haustein, Aleksandr Slominski (XmlPull 개발자들), 그리고 JCP의 다른 멤버들은 JSR 173를 구현하는 것으로써 StAX를 개발하였다. StAX은 공유 인터페이스들에 기반하는, 파서(parser) 독립적인 Java API이다.

SJSXP는 Java Web Services Developer Pack v1.5에 포함되어있다. SJSXP에 대해 처음으로 알아차릴만한 것은 이것이 스트림 API에 기반한다는 것이다. 즉, 개발자가 어느 한 노드에 접근하기 위해 전체 문서를 읽을 필요가 없다. 또한, 파서를 시작하여 파서가 데이터를 이벤트 리스너 메소드에 "push"하도록 허용하는 법칙을 따르지 않는다. 대신에 SJSXP는 "pull" 메소드를 구현하며, 이 메소드는 정렬 포인터를 문서에서 현재 스캔되고 있는 지점에 유지한다. 이는 종종 커서(cursor)라고 불린다. 사용자는 단순히 커서가 현재 가리키고 있는 노드에 대한 파서를 요청하면 된다.

XML 문서 파싱에 SJSXP 이용하기

SJSXP를 이용하여 XML 문서를 읽는 것은 아주 쉽다. 대부분의 작업은 javax.xml.stream.XMLStreamReader 인터페이스를 구현하는 오브젝트를 통해 이뤄진다. 이 인터페이스는 XML 문서의 첫 부분에서 마지막까지 이동되는 커서를 나타낸다. 몇 가지 명심해야할 것이 있다. 커서는 항상 하나의 아이템(시작 태그 요소, 진행 명령, DTD 선언 등)만을 가리켜야 한다. 또한 커서는 항상 앞으로 움직여야 하며 (뒤로 움직일 수 없다), 다음에 무엇이 나타나는지 미리 보기를 실행할 수 없다. 다음의 코드 발췌에서 파일로부터 XML을 읽는 XMLStreamReader를 얻을 수 있다.

   URL url = Class.forName("MyClassName").getResource(
           "sample.xml");            
   InputStream in = url.openStream();
   XMLInputFactory factory = XMLInputFactory.newInstance();
   XMLStreamReader parser = factory.createXMLStreamReader(in);

그 후 다음 코드를 이용하여 XML 파일을 반복할 수 있다.

   while(parser.hasNext()) {
             
         eventType = parser.next();
         switch (eventType) {

              case START_ELEMENT:
              //  Do something
              break;
              case END_ELEMENT:
              //  Do something
              break;
              //  And so on ...
         }
     }

XMLStreamReaderhasNext() 메소드는 XML 파일에서 또다른 유효한 아이템이 있는지 확인한다. 만약 있다면, 커서가 다음 아이템으로 넘어가게하기 위해 next() 메소드를 사용할 수 있다. next() 메소드는 만나게되는 문법상 생성자(아이템)의 타입을 가리키는 인티거 코드를 리턴한다.

XMLInputStreamReader에는 몇 개의 get 메소드가 있어서 커서가 가리키는 XML 아이템의 내용을 얻는 데 사용할 수 있다. 첫번째 메소드는 getEventType()이다:

   public int getEventType()

메소드는 커서가 있는 곳에서 파서가 찾은 아이템의 타입을 식별하는 integer 코드를 리턴한다. next() 메소드에 의해 리턴되는 것과 같은 코드이다. 아이템은 XMLInputStream 상수 중 하나에 의해 식별된다.

  XMLStreamConstants.START_DOCUMENT 
  XMLStreamConstants.END_DOCUMENT 
  XMLStreamConstants.START_ELEMENT 
  XMLStreamConstants.END_ELEMENT 
  XMLStreamConstants.ATTRIBUTE 
  XMLStreamConstants.CHARACTERS 
  XMLStreamConstants.CDATA 
  XMLStreamConstants.SPACE 
  XMLStreamConstants.COMMENT 
  XMLStreamConstants.DTD 
  XMLStreamConstants.START_ENTITY 
  XMLStreamConstants.END_ENTITY 
  XMLStreamConstants.ENTITY_DECLARATION 
  XMLStreamConstants.ENTITY_REFERENCE 
  XMLStreamConstants.NAMESPACE 
  XMLStreamConstants.NOTATION_DECLARATION 
  XMLStreamConstants.PROCESSING_INSTRUCTION 

만약 아이템에 이름이 있는 경우, getName()getLocalName() 메소드를 사용하여 이름을 얻을 수 있다. 후자는 어떤 다른 정보(예; 확인된 네임스페이스가 없는 요소의 이름) 없이 이름 자체를 산출한다.

   public Qname getName()
   public String getLocalName()

만약 당신이 현재 아이템의 네임스페이스를 식별하고 싶다면 getNamespaceURI() 메소드를 사용 할 수 있다.

   public String getNamespaceURI()

만약 DTD 선언 내의 텍스트나 요소 안의 텍스트 등과 같은 동반되는 텍스트가 있을 때에는 다음 메소드들을 사용하여 얻을 수 있다.(후자는 요소를 위해 단독으로 사용 될 수 있다)

   public String getText()
   public String getElementText()

만약 요소가 그것과 관계 되는 속성을 가진다면 getAttributeCount() 메소드를 사용하여 현재 요소가 가진 속성들의 갯수를 얻을 수 있다. 그 후 getAttributeName()getAttributeValue() 메소드를 사용하여 각각의 정보를 검색 할 수 있다.

   public int getAttributeCount()
   public Qname getAttributeName(int index)
   public String getAttributeValue(int index)

만약 속성의 로컬 이름과 요소의 네임스페이스 URI를 안다면, 또한 다음의 메소드를 이용하여 속성값을 얻을 수 있다.

   public String getAttributeValue(
     String elementNamespaceURI, String localAttributeName) 

추측했겠지만, 모든 접근자 메소드가 특정 상태에 적용가능한 것이 아니다. 예를 들어, 현재 DTD를 프로세싱 중이라면 getElementText()을 호출할 수 없다. 만약 이를 호출한다면, 파서가 충돌하는 이벤트 타입을 식별했다는 XMLStreamException을 얻게 되거나 메소드가 스스로 널(null) 값을 리턴하게 될 것이다.

XMLInputFactory 클래스의 setProperty() 메소드를 사용하여 몇 가지 파서 속성을 시작할 수 있다. 예를 들어, 다음은 파서와 마주치는 엔티티 레퍼런스는 교체될 것이라고 지정한다.

   factory.setProperty(
     XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, 
       Boolean.TRUE);

파서가 외부 엔티티를 지원하는 것을 막기 위해 다음과 같이 설정한다.

   factory.setProperty(
     XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, 
       Boolean.FALSE);

파서가 네임스페이스를 알아차리게하기 위해 다음과 같이 설정한다.

   factory.setProperty(
     XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.TRUE);

SJSXP의 현재 버전에서는 다음의 명령은 허용되지만 파서는 확인되지 않는다는 것을 유의하기 바란다.

   factory.setProperty(XMLInputFactory.IS_VALIDATING, 
     Boolean.TRUE);

만약 이 XMLInputFactory 특성들 중 어느 하나라도 가능하게 된다면 setXMLReporter() 메소드를 사용하여 파서가 만나게되는 오류를 제어할 수 있다. 파서가 만나는 오류의 타입을 가장 빨리 정확하게 결정할 수 있는 방법은 setXMLReporter() 메소드와 상호 작용하는 익명의 이너 클래스를 사용하는 것이다. 이는 다음과 같다.

   factory.setXMLReporter(new XMLReporter() {
     public void report(String message, String errorType,
       Object relatedInformation, Location location) {
         System.err.println("Error in " 
          + location.getLocationURI());
         System.err.println("at line " 
          + location.getLineNumber()
          + ", column " + location.getColumnNumber());
         System.err.println(message);
     }
   });

XML 문서 작성에 SJSXP 사용하기

XML 결과를 작성하는 것은 SJSXP를 이용하면 쉽다. 이 경우, XMLStreamReader 인터페이스 대신에 XMLStreamWriter 인터페이스를 사용할 수 있다. XMLStreamWriter 인터페이스는 작성하는 요소, 속성, 코멘트, 텍스트를 비롯해 XML 문서의 모든 부분을 작성하기 위한 직접적인 메소드를 제공한다. 다음의 예제에서는 어떻게 이 인터페이스를 얻어서 XML 문서를 작성하는데 사용하는지 보여준다.

   XMLOutputFactory xof =  XMLOutputFactory.newInstance();
   XMLStreamWriter xtw = 
     xof.createXMLStreamWriter(new FileWriter("myFile"));

   xtw.writeComment(
     "all elements here are in the HTML namespace");
   xtw.writeStartDocument("utf-8","1.0");
   xtw.setPrefix("html", "http://www.w3.org/TR/REC-html40");
   xtw.writeStartElement(
     "http://www.w3.org/TR/REC-html40","html");
   xtw.writeNamespace(
     "html", "http://www.w3.org/TR/REC-html40");
   xtw.writeStartElement(
     "http://www.w3.org/TR/REC-html40","head");
   xtw.writeStartElement(
     "http://www.w3.org/TR/REC-html40","title");
   xtw.writeCharacters("Java Information");
   xtw.writeEndElement();
   xtw.writeEndElement();

   xtw.writeStartElement(
     "http://www.w3.org/TR/REC-html40","body");
   xtw.writeStartElement("http://www.w3.org/TR/REC-html40","p");
   xtw.writeCharacters("Java homepage is ");
   xtw.writeStartElement("http://www.w3.org/TR/REC-html40","a");
   xtw.writeAttribute("href","http://java.sun.com");
   xtw.writeCharacters("here");
   xtw.writeEndElement();
   xtw.writeEndElement();
   xtw.writeEndElement();
   xtw.writeEndElement();
   xtw.writeEndDocument();

   xtw.flush();
   xtw.close();

각 요소를 작성하는 것이 끝나면 사용자는 라이터(writer)를 날려보내고 닫아야한다.

이전의 코드는 다음의 XML로 결과가 나타난다.(여기서는 쉽게 읽을 수 있게 라인 별로 나타내었다.)

   <!--all elements here are explicitly in the HTML namespace-->
   <?xml version="1.0" encoding="utf-8"?>
   <html:html xmlns:html="http://www.w3.org/TR/REC-html40">
   <html:head>
   <html:title>Java Information</html:title>
   </html:head>
   <html:body>
   <html:p>
   Java information is 
   <html:a href="http://frob.com">here</html:a>
   </html:p>
   </html:body>
   </html:html>

XML 문서 필터링

만약 각각의 아이템 타입을 스캔하고 싶지 않다면 들어오는 XML 문서에 대한 필터를 생성할 수 있다. 이를 위해서는 javax.xml.stream.StreamFilter 인터페이스를 구현하는 클래스를 생성한다. 이 인터페이스는 단지 accept() 메소드만으로 구성된다. 이 메소드는 XMLStreamReader 오브젝트를 허용하고 원시 Boolean을 리턴한다. StreamFilter의 일반적인 구현은 다음과 같다.

   public class MyStreamFilter implements StreamFilter {

       public boolean accept(XMLStreamReader reader) {
           if(!reader.isStartElement() && !reader.isEndElement())
               return false;
           else
               return true;
       }
   }

그 다음으로 XMLInputFactorycreateFilteredReader() 메소드를 호출하여 필터링된 리더(reader)를 생성하고 이를 원래 XML 스트림 리더와 StreamFilter 구현에 모두 전달한다. 다음과 같다.

   factory.createFilteredReader(
     factory.createXMLStreamReader(in), new MyStreamFilter());

SJSXP의 더 자세한 정보는 Sun Java Streaming XML Parser release notes를 참고하기 바란다.

Sun Java Streaming XML Parser 예제 코드 구동하기

  1. 이번 테크팁에 대한 샘플 아카이브(ttfeb2005sjsxp.jar)를 다운로드한다.

  2. Java Web Services Developer Pack Downloads page에서 Java WSDP 1.5를 다운로드, 설치한다.

  3. 예제 압축파일을 다운로드 받은 디렉토리를 변경하고, 다음과 같이 샘플 아카이브에 대한 JAR 파일의 압축을 푼다.
         jar xvf ttfeb2005sjsxp.jar
    
  4. 사용자의 classpathttfeb2005sjsxp.jarjsr173_api.jar를 포함하도록 설정한다. 이들은 Java WSDP 1.5 installation의 sjsxp/lib 디렉토리에 위치하고 있다.

  5. SJSXPInput를 컴파일하고 구동한다.

    다음의 각각의 XML 아이템과 유사한 엔트리가 나타날 것이다.
       Event Type (Code=11): DTD
       Without a Name
       With Text: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 
       Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-
       transitional.dtd">
      -----------------------------
    
  6. SJSXPOutput를 컴파일하고 구동한다. 결과는 XMLOutputFile라는 이름의 파일에 보내지며, 위의 결과 예제에 보여지는 요소들을 포함할 것이다.
Posted by 1010