 |  |
|
난이도 : 초급 DeParand Tony , Daruger Binary Evolution, Inc.의 공동 창립자, Daruger Binary Evolution, Inc
2000 년 2 월 01 일 2002 년 6 월 27 일 수정 Binary Evolution의 Parand Tony Daruger는 XML 문서들을 조작하고 변형하기 위한 스크립트 언어 사용법에 대한 첫번째 튜토리얼에서, Perl과 함께 이러한 기법들을 사용하는 첫 단계를 소개한다. 이 글에는 XML을 HTML로 변형하는 방식이 소개되어 있으며, 그 뒤를 이어 거래 규칙을 평가하기 위해 Perl, XML 및 데이터베이스를 사용하는 주식거래 애플리케이션이 소개된다. 이 기법들은 Tcl과 Python을 비롯한 다른 스크립트 언어들을 사용해서도 적용할 수 있다.
XML과 스크립트 언어는 XML이 탄생한 초창기부터 자연스러운 관계를 맺어왔다. XML 설계 그룹 원래 목표들 중 하나는
Perl 해커들이 2주만에 XML 파서를 작성하게 하는 것이었다. XML을 처리 및 조작하는 것 또한 XML과 함께 스크립팅을
사용하기 위한 비옥한 토양이 되어왔다. 즉, XML은 사용자가 읽기 쉽게 설계되었는데, 주로 텍스트를 바탕으로 한 것이다. 스크립트
언어들은 역사적으로 텍스트를 조작하는 데에 있어서는 지극히 숙달되어 왔다. 스크립팅의 유연성과 강력함은 XML의 서술적인 능력을
완벽하게 보완해준다.
XML은 정보를 표현하고, 저장하며, 교환하기 위한 매체이다. 여기에 스크립팅을 추가되면 이 정보는 액티브하게 된다. 즉,
어떤 실행에 영향을 미치고, 변형과정을 거치며, 기존 시스템과 접속하는 등 단순히 정보만 표현하는 것이 아닌 어떤 실행을 하게
된다.
이 글에서는 XML을 조작하고 변형하기 위해 Perl을 사용할 것이다. Tcl이나 Python을 비롯한 다른 스크립트 언어들을
사용하여 동일한 기법을 적용할 수도 있다. 우선, XML을 HTML로 변형하는 기법이 소개되며, 그 다음으로 거래 규칙을 평가하기
위한 Perl, XML 및 데이터베이스를 사용하는 간단한 주식 거래 애플리케이션에 대해 다루기로 한다.
XML을 HTML으로 변환
이 글의 목적을 위해, 입력 파일로는 XML로 표현된 주식 호가를 사용할 것이다 :
<stock_quote>
<symbol>IBM</symbol>
<when>
<date>12/16/1999</date>
<time>4:40PM</time>
</when>
<price type="ask" value="109.1875"/>
<price type="open" value="108"/>
<price type="dayhigh" value="109.6875"/>
<price type="daylow" value="105.75"/>
<change>+2.1875</change>
<volume>7050200</volume>
</stock_quote> |
이 단순한 인코딩은 주식 호가(stock quote)에서 일반적으로 발견되는 정보를 캡처한다. 포매팅은 속성과 빈 태그와
같이, 어떤 XML의 특징들을 나타낸다. 이 글의 XML file
에서는 주식 포트폴리오 구성을 위해 여러 개의 stock_quote 엘리먼트들을 포함한다.
이 XML 파일은 finance.yahoo.com 웹사이트에서 제공되는 Spreadsheet Format의 주가를 XML로
변환하기 위한 스크립트를 사용하여 생성되었다. 이러한 변환뿐만 아니라, 실시간 XML 주식 호가 서버를 수행하기 위해 사용되는
스크립트들은 현재 웹사이트(참고자료 참조)에서 구할 수 있다.
간단한 치환
XML 소스를 HTML로 변형하기 위한 간단한 방법은, XML 태그로 치환될 각각의 HTML 요소들을 정의하는 것이다. James
Clark의 Expat 파서를 바탕으로, 널리 쓰이는 Perl용 XML::Parser 모듈(참고자료
참조)을 사용하면, XML 문서를 파싱 할 수 있고, 이러한 치환을 수행하기 위해 콜백 루틴을 정의할 수 있다.
아래에 XML::Parser를 불러내는 간단한 사례를 든다 :
use XML::Parser;
my $parser = new XML::Parser(ErrorContext => 2);
$parser->setHandlers(Start => \&start_handler,
End => \&end_handler,
Char => \&char_handler);
$parser->parsefile($file); |
이것은 주어진 파일을 파싱하여, 태그가 시작될 때마다 start_handler 함수를 불러내고, 태그가 끝날 때마다 end_handler
함수를 불러낸다. 태그의 내용은 char_handler 함수가 처리한다.
이러한 콜백 함수들이 주어지면, 간단한 치환 알고리즘을 만들 수 있다. 우선, 몇 가지 치환을 정의하기로 한다 :
%startsub = (
"stock_quote" => "<hr><p>",
"symbol" => "<h2>",
"price" => "<br><b>Price:</b>"
);
%endsub = (
"stock_quote" => "",
"symbol" => "</h2>",
"price" => ""
); |
이제, 치환을 수행하기 위해 핸들러를 작성한다. :
sub start_handler
{
my $expat = shift; my $element = shift;
# element is the name of the tag
print $startsub{$element};
}
sub char_handler
{
my ($p, $data) = @_;
print $data;
} |
start_handler 함수는 주어진 태그에 치환될 값을 단순히 인쇄만 한다. char_handler
는 자신이 받는 데이터를 출력하며, 이것이 해당 태그의 내용이다(속성을 처리하기 위해 몇 가지 추가사항이 들어있는 full
program 도 참고할 것). 이 프로그램을 XML file에서
실행하면, 다음과 같은 출력을 얻는다.:
<hr><p>
<h2>IBM</h2>
<br><b>Date:</b><i>12/16/1999</i>
<br><b>Time:</b><i>4:40PM</i>
<br><b>Price:</b>type=ask value=109.1875
<br><b>Price:</b>type=open value=108
<br><b>Price:</b>type=dayhigh value=109.6875
<br><b>Price:</b>type=daylow value=105.75
<br><b>Change:</b>+2.1875
<br><b>Volume:</b>7050200 |
전체 출력을 보려면 full output 을 참고하라. 이 방법으로
치환을 정의함으로써 간단한 XML을 HTML로 변형할 수 있다.
함수 기반의 치환
치환 기반(substitution-based)의 변형은 구현하기 쉽고 이해하기도 쉽지만, 우리에게 로직을 구현하는 능력은 주지
않는다. 그러나, 태그의 내용이나 속성을 바탕으로 다른 동작을 취하고 싶을 수도 있으며, 혹은 태그의 내용을 저장된 값과 비교하기
위해 데이터에 접속하고 싶을 수도 있을 것이다. 이때는 간단한 1:1 치환 이상이 필요하다. 즉, 각 태그에 대한 함수를 실행하기
위한 능력이 필요한 것이다.
XML::Parser는 XML 문서에 있는 각 태그에 대한 함수를 불러오는 방법을 제공한다. 각 태그에 대해, 파싱 모듈은
태그 이름이 있는 함수를 호출한다. 따라서, 변형을 수행하고, 데이터베이스에 접속하며, 비즈니스 로직을 실행하는 일련의 함수들을
정의할 수 있다.
태그 이름을 바탕으로 함수를 콜백하기 위해서는, "Subs" 스타일의 파서를 불러내야 한다. 또한, "Pkg" 옵션을 통해
함수 콜백이 상주하는 이름공간(namespace)이 어떤 것인지를 지정해야 한다. :
my $parser = new XML::Parser(Style=>'Subs',
Pkg=>'SubHandlers',
ErrorContext => 2);
$parser->setHandlers(Char => \&char_handler); |
이것은 XML 파일의 태그와 내용에 바탕을 둔 일련의 함수 콜백을 야기시킨다. 태그의 시작은 SubHandlers 이름공간에
있는 해당 태그와 같은 이름을 가진 함수를 불러낸다. 태그의 내용은 char_handler 함수로 처리가 되며, 태그의 끝은
"_"가 추가된 태그(예: symbol 태그의 끝부분에 대해, 함수 SubHandlers::symbol_()이 호출됨)와 동일한
이름을 가진 함수를 불러낸다.
이 XML file 은 다음과 같이 연속된 함수 호출을 발생시킨다.:
SubHandlers::stock_quotes();
SubHandlers::stock_quote();
SubHandlers::symbol();
char_handler();
SubHandlers::symbol_();
SubHandlers::when();
.... |
이제, 변형 함수(transformation function)를 작성하는 것은 간단한 문제이다. 여전히 다음과 같이 간단한
치환기능을 수행할 수 있다. :
sub symbol {
print "<img src=images/";
}
sub symbol_ {
print ".gif>\n";
} |
여기서는 동일한 이름의 이미지를 HTML 결과 페이지에 삽입하기 위해, symbol 태그의 내용에 포함된 주식 기호를 사용한다.
또한 더 복잡한 논리도 구현할 수도 있다. :
sub price {
my $expat = shift; my $element = shift;
# Read the attributes
while (@_) {
my $att = shift;
my $val = shift;
$attr{$att} = $val;
}
my $type = $attr{'type'};
my $price = $attr{'value'};
if ($type eq 'ask') {
$label="Ask Price";
} elsif ($type eq 'open') {
$label="Opening Price";
}
print "<td align=left>\n<b>$label</b></td>\n";
print "<td align=right>$price</td>\n";
} |
함수로 넘겨진 첫번째 파라미터는 파서 그 자체에 대한 핸들이며, 그 다음에는 태그의 이름(element)이 따라 나오고, 옵션으로
attribute_name, attribute_value가 쌍으로 된 태그 속성이 뒤를 잇는다. 위의 경우, price 태그의
type 속성을 바탕으로 하는 다른 라벨을 인쇄한다.
The full program 을 보라. 이 프로그램을 XML file에 실행하면 좀 더 나은 html 출력물을 생성한다.
아래 샘플과 같은 모양이다.:
<table width=100%>
<tr>
<td>
<img src=images/IBM.gif><br><br> 12/16/1999 4:40PM
</td>
<td>
<table width=100%>
<tr>
<td align=left><b>Ask Price</b></td>
<td align=right>109.1875</td>
<td align=left><b>Opening Price</b></td>
<td align=right>108</td>
</tr>
<tr>
<td align=left><b>Today's High</b></td>
<td align=right>109.6875</td>
</tr>
</table>
</td>
</tr>
</table> |
트리 기반(tree-based) 프로세싱
지금까지의 설명은XML 문서를 하나의 스트림으로 처리하는 것에 바탕을 두고 있다. 파일을 파싱하는 과정에서, 핸들러들은 각 태그를
만날 때마다 호출된다. 이러한 기능은 메모리 사용과 프로세스 시간의 관점에서, XML을 처리하는 효과적인 수단을 제공한다. 그러나,
어떤 작업들은 처리하기가 다소 어렵다. 예를 들어, 문서의 일부 세그먼트를 이동하거나 재배치해야 하는 경우나, 문서 내의 항목들을
정렬해야 하는 경우를 상상해보자. 문서를 스트림으로 받기 때문에, 문서를 정렬하거나 재배치하기 전에 그 구성 요소들을 저장해야
할 것이다. 구성 요소들을 자동적으로 저장하는 메커니즘은 이러한 작업들을 훨씬 쉽게 해준다.
XML 문서는 조화로운 균형을 이루어야 하며, 그렇게 함으로써 XML 문서들을 트리로 저장하기가 쉬워진다. XML 문서로
작업하는 보편적인 기법은, 우선 이 문서들을 트리 데이터 구조로 파싱한 후, 트리 상에서 작업하는 것이다. DOM(Document
Object Model)뿐만 아니라, Grove와 Twig(참고자료 검토요망)가
이 모델을 사용한다. 이것은 문서를 다루는 데 있어서 큰 유연성을 제공하며, 해당 문서의 구성 요소들은 랜덤한 순서로 액세스할
수 있고, 재배치, 추가, 또는 제거할 수 있다.
그러나, 트리 기반의 방법들은 분명히 몇 가지 결점들이 있다. 이 방법들은 프로세싱과 비즈니스 로직이 발생하기 전에, 트리
데이터 구조를 생성할 뿐만 아니라, 전체 XML 문서를 파싱해야 한다. 트리 데이터 구조는 일반적으로 메모리에 저장되므로, 이러한
방법들은 스트림 기반의 방법들보다는 훨씬 더 큰 메모리 공간을 차지한다. 이 문제는 메모리에 문서를 트리로 저장하는 것이, 원래
XML 문서가 저장되는 스토리지에 비해 몇 배나 필요하다는 사실 때문에 더욱 악화된다. 더 큰 파일에 대해서는, 이 두 가지
모두가 중요할 수 있는데, 파싱 시간과 트리 생성 시간이 엄청나게 걸리게 되고, 메모리 요구사항은 사용할 수 있는 리소스를 넘어설
수 있다..
XML 문서를 트리 기반으로 처리하는 것은 다음에 쓸 글에서 논하기로 하겠다. 이 글의 나머지 부분은 위에서 설명한대로,
스트림 기반의 프로세싱 방법을 사용하도록 하겠다.
액티브 XML 문서
디스플레이를 위해 XML 문서를 변환하는 것은, 대개 XML로 작업할 때 제일 먼저 하게 되는 일이며, 관련된 도구에 대한 훌륭한
도입부 역할을 한다. 그러나, XML의 진정한 힘은 정보를 전송하는 능력뿐만 아니라, 전송된 정보를 바탕으로 어떤 작동을 시작시키는
능력에 있다. 간단한 주식거래 규칙을 실행하기 위해 이러한 액티브한 문서들을 사용하는 샘플 애플리케이션을 면밀히 검토해보기로
하자.
기본적인 시나리오는 이렇다. 주가 서비스는 우리가 선택한 주식들에 대한 최신 주가와 거래량이 포함된 XML 문서(지금까지 사용해온
XML file 의 포맷임)를 주기적으로 보내게 된다. 여기서 사용하는
애플리케이션은 주가와 데이터베이스에 저장되어 있는 일련의 규칙들을 바탕으로 주식을 살 것인지, 혹은 팔 것인지를 결정한다.
이 간단한 애플리케이션에 있어서, 우리는 매수가격과 거래량을 기준으로 사용하여 사거나 파는 것만 한다. 가격과 거래량은 XML
파일로부터 받으며, 해당 규칙들은 MySQL 데이터베이스에서 가져온다(참고자료 참조).
이 규칙들이 평가된 후, 매수나 매도할 필요가 있으면, 해당 명령어가 발행된다.
태그 내용 저장
앞의 디스플레이 지향의 애플리케이션에서는 단지 태그 내용을 출력하기만 하면 되었다. 여기의 주식 애플리케이션에서는 가격이나 거래량과
같은 어떤 태그들을 매수/매도 기준과 비교하기 위해, 그 내용을 액세스하고 저장하는 것이 필요하다.
스트림 기반의 프로세싱 모델을 사용하여 태그의 내용을 저장하는 전략은 이렇다. 우선, 태그의 시작 부분을 만나게 되면 그
내용을 저장하기 위한 스토리지 장소를 마련해두고, char_handler 함수를 통해 해당 태그의 내용을 저장한다. 문서는 스트림으로
처리되므로, 시작 태그를 먼저 만나게 되고, 이로써 해당 내용의 스토리지를 위한 단계를 설정할 수 있다. 다음, 태그의 내용이
나올 것이고, 이 내용은 미리 준비된 위치에 저장된다. 마지막으로 태그의 끝이 나오고, 해당 스토리지 위치에 대해 필요한 정리를
하고 클로즈업해볼 수 있다.
스토리지 위치는 태그 시작 함수에서 설정될 것이고, 태그 끝 함수에서 닫힌다.:
sub volume {
$::state::store_contents = "volume";
}
sub volume_ {
undef $::state::store_contents;
} |
volume은 store_contents 변수를 정의하여, 태그의 내용을 위한 스토리지 위치를 설정한다. 그 다음에 volume_는
store_contents의 정의를 해제하여, 다른 태그의 내용이 동일한 위치에 저장되지 않도록 한다.
char_handler는 내용을 스토리지에 저장할 수 있도록 변경되어야 한다.:
sub char_handler {
my ($p, $data) = @_;
if ($::state::store_contents) {
$::state::storage{$::state::store_contents} .= $data;
}
} |
이것은 변수 store_contents가 정의되어 있는지를 점검하며, 정의되어 있으면 해당 데이터를 storage 해시에 저장한다.
::state 이름공간은 스토리지와 상태 변수들을 파서와 핸들러 이름공간(namespace)에서 분리하는 데 사용된다.
이러한 기법을 사용하면, 관심이 있는 태그의 내용을 저장할 수 있다. price 태그의 경우, 관심의 대상이 되는 값들은
price 태그의 속성으로 표현된다. 이러한 값들이 나오면 속성을 저장할 수 있다.:
규칙 검색
sub price {
my $expat = shift; my $element = shift;
# Read the attributes
while (@_) {
my $att = shift;
my $val = shift;
$attr{$att} = $val;
}
if ($attr{'type'} eq "ask") {
$::state::storage{'price'} = $attr{'value'};
}
} |
CREATE TABLE rules (
symbol CHAR(5),
field CHAR(8),
value CHAR(16),
action CHAR(5)
); |
symbol은 주식의 심벌이다. field는 해당 기준에서 어떤 필드가 사용될지를 설명해준다. 이 경우에는 주가나 거래량 중
하나이다. value는 어떤 작동을 트리거하는 field의 값이다. action은 선택하는 작동의 유형을 설명해준다. 이 경우에는
매수나 매도 중 하나이다.
따라서, 규칙 테이블에서 다음 행:
INSERT INTO rules VALUES ("IBM", "price", "120.0", "buy"); |
는 IBM의 주가가 120 이상이면 매수 주문을 내라는 의미이다. 그리고,
INSERT INTO rules VALUES ("MSFT", "volume", "65000000", "sell"); |
는 Microsoft 주식의 거래량이 65000000주 이상이면, 매도 주문을 내라는 의미이다.
이 규칙들은 주식 심벌에 의거하여 선택함으로써 검색할 수 있다.:
my $sth = $::state::dbh->prepare("select * from rules where symbol='$symbol'");
$sth->execute();
while (my $ref = $sth->fetchrow_hashref()) {
# Act on the retrieved rules
}
$sth->finish(); |
규칙에 따른 실행
각각의 주가는 stock_quote 태그에 포함되어 있다. stock_quote에 대한 태그의 끝에 왔을 때에는, 필요한 모든
정보들, 즉 주식 심벌, 주가 및 거래량 등이 이미 저장이 된 상태이다. 따라서, stock_quote_ 함수에서 이러한 규칙들에
대한 실행을 할 수 있다.:
sub stock_quote_ {
my $symbol = $::state::storage{'symbol'};
# Grab the rules for the given stock from the rules table
my $sth = $::state::dbh->prepare("select * from rules where symbol='$symbol'");
$sth->execute();
while (my $ref = $sth->fetchrow_hashref()) {
my $field = $ref->{'field'};
my $value = $ref->{'value'};
if ($::state::storage{$field} > $::state::storage{$value}) {
# This rule applies
print "Rule \"$field > $value\" applies for $symbol\n";
take_action($symbol, $ref->{'action'});
}
}
$sth->finish();
} |
주어진 주식 심벌에 대해 적용할 수 있는 규칙들이 검색되고 나면, 비교기능이 수행된다. 만약 해당 규칙이 적용되면, take_action
함수가 호출되며, 이 경우에는 단순히 스텁(stub)일 뿐이다.
별도의 리스트인 complete program 뿐만 아니라, schema
for creating the rules table을 사용할 수 있다. 기존의 XML
file 과 이 프로그램을 함께 실행하면 다음과 같이 출력된다 .:
Rule "price > 120.0" applies for IBM
Taking action "buy" on stock "IBM" .
Rule "volume > 65000000" applies for MSFT
Taking action "sell" on stock "MSFT" .
|
다음 단계
이러한 기법들을 좀더 큰 프로젝트에 적용할 수 있으며, 신속하고 유연성 있는 XML 기반의 시스템을 얻게 된다. 변형 및 명령
언어로서 스크립트 언어를 사용(파싱된 XML 문서를 다루는 데에는 고성능의 C/C++ 기반의 파서를 사용)해 구축된 솔루션들은
동급 최고의 방법을 제공한다. 이러한 접근방법은 저수준 언어의 빠른 속도를 제공함과 동시에 사용하기 쉬운 스크립팅 언어의 기능을
동시에 제공한다..
참고자료
필자소개  | |  | Parand Tony Darugar는 Binary Evolution Inc.의 사장 겸 공동 창업자로서, 1992년 부터 인터넷 관련 벤처 회사를 경영하고 있다. |
기사에 대한 평가
 |
| 이 문서 북마킹 하기
|
|  |