메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

XML::LibXML - XML::Parser를 대체할 수 있을까?

한빛미디어

|

2002-02-19

|

by HANBIT

16,083

저자: 킵 햄튼(Kip Hampton), 역 정직한

개요

제임스 클라크 (James Clark)가 만든 expat 파서를 펄에서 사용할 수 있도록 래리 월 (Larry Wall) 과 클라크 쿠퍼 (Clark Cooper)가 만든 펄 인터페이스 XML::Parse 을 기초로 하여 펄에서 사용되는 대부분의 XML 모듈이 만들어졌다. expat과 XML::Parser 콤비만이 펄 세계에서 유일하게 전 기능을 갖춘 XML 파서는 아니다. 이 기사에서는 대니얼 벨리아드(Daniel Velliard)의 libxml2를 펄에서 사용할 수 있게 해주는 펄 인터페이스 XML::LibXML을 살펴보겠다. 참고로 XML::LibXML는 매트 서전트(Matt Sergeant) 와 크리스챤 글란(Christian Glahn)이 만든 것이다.

또다른 XML 파서가 필요한 이유는 도대체 무엇일까?

물론 expat과 XML::Parser가 훌륭하긴 하지만 그렇다고 해서 아주 단점이 없는 것은 아니다. expat은 XML 파서 중 최초의 그룹에 속했고, 그 결과 작성된 그 시점에서 사용자들의 기대를 반영하는 인터페이스를 가지게 되었다. expat과 XML::Parser가 작성된 시기에는 DOM (Document Object Model), SAX, 또는 XPath 등이 존재하지 않았거나 열띤 논의중이어서 "표준"으로 간주되지 않았기 때문에 이들 언어에 대한 인터페이스를 구현하지 않았다. 그 결과 불행히도 대부분의 펄 XML 모듈들은 XML::Parser의 비표준 내지 표준이라고는 하기 힘든 인터페이스에 기반하여 만들어졌기 때문에 입력이 텍스트로 된 XML 문서로 (파일, 파일핸들, 스트링, 소켓 스트림) 처리하기 전에 파싱이 되어야 한다는 가정을 깔고 있다. 이것들은 단순한 경우에는 대개 잘 작동한다. 그러나 최근의 XML 애플리케이션들은 주어진 문서를 가지고 한가지 이상의 작업을 해야 하므로 처리하는 각 단계마다 문서가 스트링으로 직렬화되고 다음 모듈에 의해 다시 파싱되어야 한다는 단점이 있다.

반면 libxml2는 DOM, XPath, 그리고 SAX 인터페이스가 널리 퍼진 후에 작성되어 이 세 가지 표준을 모두 구현하고 있다. 따라서 lixml로 여러분은 파일, 스트링 등에 저장되었거나 일련의 SAX 이벤트들로부터 만들어진 문서를 파싱하여 메모리에 트리를 만들 수 있다. 이 트리들은 W3C DOM과 XPath 인터페이스를 사용하여 조작할 수도 있고 외부 이벤트 핸들러에 넘겨줄 SAX 이벤트를 만드는데 사용될 수도 있다. 이와 같은 유연성은 요즘의 XML 처리에 대한 기대를 반영하는 것으로 XML::Parser가 차지하고 있는 왕좌의 강력한 도전자로 XML::LibXML을 부상시켜 주었다.

XML::LibXML 사용하기

이 달의 컬럼은 작년 초 필자가 쓴 "Perl/XML Quickstart Guide"란 기사의 속편같이 보일지도 모르겠다. 필자가 그 기사를 쓸 당시에는 XML::LibXML이 아직 성숙하지 않은 상태였기 때문에 Quickstart에서 사용했던 것과 동일한 테스트들을 사용해 XML::LibXML을 시험해 보려고 한다. 테스트 케이스에 대한 자세한 내용은 Quickstart에 대한 첫번째 설치와 관련된 기사를 보면 된다. 요약하자면, 두 테스트는 주어진 XML 모듈에서 제공하는 기능들을 이용해 XML 문서에서 자료를 뽑아내 출력하는 방법과 펄 해시에 저장된 자료로부터 XML 문서를 프로그램적으로 만들고 출력하는지 방법을 보여준다.

읽기

XML 문서에 저장되어 있는 자료에 접근하기 위해 XML::LibXML은 표준 W3C DOM 인터페이스를 제공한다. 문서들은 노드를 가지는 트리로 취급되며 노드가 가지고 있는 자료는 노드 객체에 메소드를 호출하여 접근하게 된다.

use strict;
use XML::LibXML;

my $file = "files/camelids.xml";
my $parser = XML::LibXML->new();
my $tree = $parser->parse_file($file);
my $root = $tree->getDocumentElement;
my @species = $root->getElementsByTagName("species");

foreach my $camelid (@species) {
    my $latin_name = $camelid->getAttribute("name");
    my @name_node  = $camelid->getElementsByTagName("common-name");
    my $common_name = $name_node[0]->getFirstChild->getData;
    my @c_node  = $camelid->getElementsByTagName("conservation");
    my $status =  $c_node[0]->getAttribute("status");
    print "$common_name ($latin_name) $status \n";
}
XML::LibXML 의 가장 흥미로운 기능 중 하나는 DOM 인터페이스는 물론이고 추가로 XPath 언어를 이용해 노드를 선택할 수 있다는 점이다. 아래의 코드에서는 XPath를 사용하여 원하는 노드를 선택함으로써 앞의 예제와 동일한 결과를 얻을 수 있음을 보여준다.

use strict;
use XML::LibXML;

my $file = "files/camelids.xml";
my $parser = XML::LibXML->new();
my $tree = $parser->parse_file($file);
my $root = $tree->getDocumentElement;

foreach my $camelid ($root->findnodes("species")) {
    my $latin_name = $camelid->findvalue("@name");
    my $common_name = $camelid->findvalue("common-name");
    my $status =  $camelid->findvalue("conservation/@status");
    print "$common_name ($latin_name) $status \n";
}
위의 코드에서 흥미로운 점은 동일한 노드들의 트리에 DOM 과 XPath 인터페이스의 메소드 중에서 애플리케이션의 필요에 가장 잘 맞는 메소드를 섞어 쓸 수 있다는 것이다.

쓰기

XML::LibXML을 이용해 XML 문서를 만들려면 제공되는 DOM 인터페이스를 사용하기만 하면 된다.

use strict;
use XML::LibXML;

my $doc = XML::LibXML::Document->new();
my $root = $doc->createElement("html");
$doc->setDocumentElement($root);
my $body = $doc->createElement("body");
$root->appendChild($body);

foreach my $item (keys (%camelid_links)) {
   my $link = $doc->createElement("a");
   $link->setAttribute("href", $camelid_links{$item}->{url});
   my $text = XML::LibXML::Text->new($camelid_links{$item}->{description});
   $link->appendChild($text);
   $body->appendChild($link);
}
print $doc->toString;
XML::LibXML과 XML::DOM을 구분하는 중요한 차이점은 libxml2의 객체 모델이 W3C DOM 레벨 2 인터페이스에 맞기 때문에 이것이 XML 네임스페이스를 가지고 있는 문서를 더 잘 다룰 수 있다는 점이다. 따라서 XML::DOM은 아래같이 제한적이다.

@nodeset = getElementsByTagName($element_name);
그리고

$node = $doc->createElement($element_name);
XML::LibXML 은 아래와 같이 할 수도 있다.

@nodeset = getElementsByTagNameNS($namespace_uri, $element_name);
그리고

$node = $doc->createElementNS($namespace_uri, $element_name);
SAX가 제공하는 즐거움

이상으로 우리는 XML::LibXML이 제공하는 DOM과 XPath의 장점을 살펴보았다. 그러나 여기서 이야기가 끝나는 것은 아니다. libxml2 라이브러리는 SAX 인터페이스도 제공하여 SAX 이벤트에서부터 DOM 트리를 만들거나 DOM 트리에서 SAX 이벤트를 만들 수도 있다.

아래의 코드는 XML::SAX::Base에 기반을 둔 SAX 드라이버에서 프로그램을 통해 DOM 트리를 만들어낸다. 이 예제에서 초기 SAX 이벤트들은 CamelDriver 클래스에 구현된 커스텀 드라이버로부터 발생되며 CamelDriver 클래스는 XML::LibXML::SAX::Builder 클래스를 호출하여 DOM 트리를 만든다.


use XML::LibXML;
use XML::LibXML::SAX::Builder;

my $builder = XML::LibXML::SAX::Builder->new();
my $driver = CamelDriver->new(Handler => $builder);
my $doc = $driver->parse(%camelid_links);

# doc is an XML::LibXML::Document object
print $doc->toString;

package CamelDriver;
use base qw(XML::SAX::Base);

sub parse {
  my $self = shift;
  my %links = @_;
  $self->SUPER::start_document;
  $self->SUPER::start_element({Name => "html"});
  $self->SUPER::start_element({Name => "body"});

  foreach my $item (keys (%camelid_links)) {
    $self->SUPER::start_element({Name => "a",
                                   Attributes => {
                                     "href" => $links{$item}->{url}
                                               }
                                });
    $self->SUPER::characters({Data => $links{$item}->{description}});
    $self->SUPER::end_element({Name => "a"});
  }

  $self->SUPER::end_element({Name => "body"});
  $self->SUPER::end_element({Name => "html"});
  $self->SUPER::end_document;

}
1;
XML::LibXML::SAX::Generator를 이용해 기존의 DOM 트리에서 SAX 이벤트를 발생시킬 수도 있다. 아래의 코드에서 camelids.xml 파일을 파싱하여 만들어진 DOM 트리가 XML::LibXML::SAX::Generator의 generate() 메소드에 넘겨지면 XML::Handler::XMLWriter 에 있는 이벤트 핸들러가 호출되어 문서를 STDOUT으로 출력하게 된다.

use strict;
use XML::LibXML;
use XML::LibXML::SAX::Generator;
use XML::Handler::XMLWriter;

my $file = "files/camelids.xml";
my $parser = XML::LibXML->new();
my $doc = $parser->parse_file($file);
my $handler = XML::Handler::XMLWriter->new();
my $driver = XML::LibXML::SAX::Generator->new(Handler => $handler);

# generate SAX events that are captured
# by a SAX Handler or Filter.
$driver->generate($doc);
SAX 이벤트를 받아들이고 내보낼 수 있는 기능은 최근 이 컬럼에서 논의되었던 비XML 자료에서 SAX 이벤트를 발생시키고 SAX 필터 체인을 작성하는 것과 연관지어 생각해보면 아주 유용하다고 볼 수 있다. 예를 들면 펄로 쓰여진 SAX 드라이버를 이용해 데이터베이스 질의에서 가져온 자료에 기반을 두고 이벤트를 발생시켜 DOM 객체를 만들 수 있다. 이 DOM 객체는 C-space에서 디스플레이를 위해 XSLT와 굉장히 빠른 libxslt 라이브러리 (이것은 libxml2 DOM 객체를 기대한다)를 사용해 변환되고, 변환된 DOM 트리에서 커스텀 SAX 필터를 이용한 추가 처리를 위해 SAX 이벤트를 발생시켜 최종 마무리를 한다. 즉 이 모든 것을 하면서 스트링을 다시 파싱하기 위해 문서를 직렬화할 필요가 한 번도 없다는 이야기다. 정말 놀랍지 않은가!

결론

이상 우리가 살펴본 것처럼 XML::LibXML은 XML 처리에 대해 빠르면서도 최신식의 접근방식을 제공한다. 이것은 제 1세대라 할 수 있는 XML::Parser와 비교해 볼 때 보다 여러 가지 방면에서 뛰어나다고 볼 수 있다. 그러나 필자의 말을 오해하지 말길 바란다. XML::Parser와 그에 의존하는 모듈은 여전히 유용하고, 잘 지원되고 있기 때문에 짧은 시간 안에 사라질 위험이 있을 것 같지는 않다. 하지만 그것이 유일한 방법은 아니며 XML::LibXML이 제공하는 유연성을 생각한다면 다음 번 Perl/XML 프로젝트를 시작하기 전에 다시 한 번 XML::LibXML을 자세히 들여다 보라고 강력히 권하고 싶다.
TAG :
댓글 입력
자료실

최근 본 상품0