
This page is Japanese only.
Last Update 2000/01/02
XMLはeXtensible Makup Languageの略で、ユーザが定義する
タグを利用して文章を構造化する仕様です。最近解説本が沢山
でていますが、本によって訳語が違う場合があるので、訳には
注意しましょう。
それはそれとして、XML文章は以下のような形になります。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE テキスト[ <!ELEMENT テキスト (題名,本文)> <!ELEMENT 題名 (#PCDATA)> <!ELEMENT 本文 (#PCDATA|キーワード)> <!ELEMENT キーワード (#PCDATA)> <!ATTLIST キーワード ジャンル CDATA #REQUIRED> ]> <テキスト> <題名>XMLって何?</題名> <本文> <キーワード ジャンル="特徴">文章を構造化する</キーワード> ことでプログラムも人間もあつかいやすくなった <キーワード ジャンル="言語">マークアップ言語</キーワード> のこと </本文> </テキスト>
XML文章は一般的に<テキスト></テキスト>のタグで囲まれた テキスト(XMLインスタンスと言ったりする)のように思われますが、 実際には文章定義が必要です。 定義がないと文章構造の解析ができないですからね。順を追って先の XML文章の解説をしてみましょう。
<?xml version="1.0" encoding="UTF-8" ?>これはXML宣言です。XMLのバージョンと使用している文字コードの 宣言を行っています。encoding=""を省略するとUTF-8を使っている とみなされます。
This version of XML::Parser and XML::Encoding does not come with map files for
the charset "Shift_JIS" and the charset "euc-jp". Unfortunately, each of these
charsets has more than one mapping. None of these mappings are
considered as authoritative.
Therefore, we have come to believe that it is dangerous to provide map files
for these charsets. Rather, we introduce several private charsets and map
files for these private charsets. If IANA, Unicode Consoritum, and JIS
eventually reach a consensus, we will be able to provide map files for
"Shift_JIS" and "euc-jp".
で結局何が使えるのかというと
次に以下の部分が
<!DOCTYPE テキスト[ <!ELEMENT テキスト (題名,本文)> <!ELEMENT 題名 (#PCDATA)> <!ELEMENT 本文 (#PCDATA|キーワード)> <!ELEMENT キーワード (#PCDATA)> <!ATTLIST キーワード ジャンル CDATA #REQUIRED> ]>文書型定義です。ここでは要素(ELEMENT)、属性(ATTLIST)、 エンティティ(ENTITY)の宣言を行います。詳しくはXMLを解説している ページを参考にしてみてください。ここでは説明を省略します。
文字列置換 <?xml version="1.0"?> <!DOCTYPE 例[ <!ENTITY 魚 "トウアカクマノミ"> ]> <例 パターン="1"> &魚; </例>
ファイルの組み込み <?xml version="1.0"?> <!DOCTYPE 例[ <!ENTITY 1章 SYSTEM "http://hoge.co.jp/chap1.xml"> <!ENTITY 2章 SYSTEM "http://hoge.co.jp/chap2.xml"> ]> <例 パターン="2"> <chapter>&chap1;</chapter> <chapter>&chap2;</chapter> </例>まぁ、こんだけ知っていればとりあえずXML文章を見てなんとか 理解できると思います。それにXML::ParserやXML::DOM, XML::Checker などのモジュールの中でも説明があります。甘いかしら…
XML::Parserはいくつかの関数を設計し、それをXML文書の解析用ハンドラ として登録するというしくみになっています。これだけでは何を言っている のかよくわからないですね。参考になるのはXML::Parserでいくつかデフォルト で入っているスタイルというハンドラのセットです。例えばDebugというスタイル を見てみましょう。
package XML::Parser::Debug;
$XML::Parser::Built_In_Styles{Debug} = 1;
sub Start {
my $expat = shift;
my $tag = shift;
print STDERR "@{$expat->{Context}} \\\\ (@_)\n";
}
sub End {
my $expat = shift;
my $tag = shift;
print STDERR "@{$expat->{Context}} //\n";
}
sub Char {
my $expat = shift;
my $text = shift;
$text =~ s/([\x80-\xff])/sprintf "#x%X;", ord $1/eg;
$text =~ s/([\t\n])/sprintf "#%d;", ord $1/eg;
print STDERR "@{$expat->{Context}} || $text\n";
}
sub Proc {
my $expat = shift;
my $target = shift;
my $text = shift;
print $expat,"\n";
print $expat->{Context}, "\n";
my @foo = @{$expat->{Context}};
print STDERR "@foo $target($text)\n";
}
ハンドラとして登録する関数をこんな感じで作っていくわけです。
ハンドラの登録は以下のように行います。
$p = new XML::Parser(Handlers =>
{Start => \Start,
End => \End,
Char => \Char,
Proc => \Proc})
このようなスタイルを自分で作って解析をしていくのは大変です。
で、誰かが作ってくれたスタイルの一つがDOMになるわけです。
そうそう、XML::ParserはXMLの定義に従っていないXML文章を食わせる とErrorで終了してしまいます。終了させないようにするには 以下のようにしてみてください。
eval{$p->parse($xml)};
print "Caught error: $@\n" if $@;
evalを使えば終了することなくスクリプトを実行させることができます。
$@にエラーメッセージが入っています。
それではDEBUGスタイルで日本語(ここではShift_JIS)を出力できる スクリプトを作ってみましょう。XML::Parserの出力は常にUTF-8なので UTF-8をShift_JISに変換するモジュールと組み合わせるわけです。 変換モジュールは私がでっちあげたもので I18N::cvtという名前です。
#
# jDebugStyle.pl
#
use XML::Parser;
use I18N::cvt;
my $xml =<<'End';
<?xml version="1.0" encoding="x-sjis-unicode"?>
<あいさつ>
<誰 それ="私">こんにちは みなさん</誰>
</あいさつ>
End
my $cvt = new I18N::cvt "Japanese";
my $p = new XML::Parser;
$p->setHandlers(Start => \&Start,
End => \&End,
Char => \&Char);
eval {$p->parse($xml)};
if ($@){
print "Caught error: $@\n";
exit;
}
sub Start
{
my $expat = shift;
my $tag = shift;
map {print STDERR $cvt->u82loc($_)," "}@{$expat->{Context}};
print STDERR "++(";
map {print STDERR $cvt->u82loc($_)," "}@_;
print STDERR ")\n";
}
sub End
{
my $expat = shift;
my $tag = shift;
map {print STDERR $cvt->u82loc($_)," "}@{$expat->{Context}};
print STDERR "--\n";
}
sub Char
{
my $expat = shift;
my $text = shift;
return if ($text =~ /^\s*$/m);
map {print STDERR $cvt->u82loc($_)," "}@{$expat->{Context}};
print STDERR "|| ",$cvt->u82loc($text),"\n";
}
実行結果
++() あいさつ ++(それ 私 ) あいさつ 誰 || こんにちは みなさん あいさつ -- --いずれにせよ、XML::Parserを直接コントロールするのは大変面倒です。 一般的にはXML::DOMを使うことになるでしょう。
DOMというのはDocument Object Modelの省略形です。XMLインスタンスを
オブジェクトモデルを元にアクセスするように考えられた規格です。現在
はLevel 2というのがでていますが、Perl XML::DOMはLevel 1を元に
しています。Level1 とLevel2の違いはまだ調べていなくてわかりません。
XML::DOMは先のXML::Parserのスタイルを記述して作ったものです。ですから
XML::Parserの機能も併せて使うことができます。
さてここでXML文章の構造について考えてみましょう。こんなXMLインスタンス を考えてみたとき、
<?xml version="1.0" encoding="x-sjis-unicode" ?> <メモ 作者="寿下無" ver="1.0"> <はじめに>はじめの文書</はじめに> <本文> <章 番号="1">第1章</章> <章 番号="2">第2章</章> <空タグ例/> </本文> </メモ>このインスタンスをツリー構造で表現してみましょう。すると以下のように なります。
+- 文章全体
|
+- メモ 作者= 寿下無 encoding= "x-sjis-unicode"
|
+- はじめに
| |
| +- はじめの文書
|
+- 本文
|
+- 章 番号=1
| |
| +- 第1章
|
+- 章 番号=2
| |
| +- 第2章
|
+- 空きタグ例
XML::DOMはXMLインスタンスをこのようなツリー状のデータ構造に
変換します。 DOMの仕様というのはこのようなツリー状になったデータ
構造体にアクセスするインタフェースを提供しているのです。
use strict;
use XML::DOM;
use Jcode;
use Tk;
my $xml =<<'END';
<?xml version="1.0" encoding="x-sjis-unicode" ?>
<メモ 作者="寿下無" ver="1.0">
<はじめに>はじめの文書</はじめに>
<本文>
<章 番号="1">第1章</章>
<章 番号="2">第2章</章>
<空タグ例/>
</本文>
</メモ>
END
my $mw = MainWindow->new();
$mw->optionAdd('*font' => '{MS 明朝} 10');
my $label = $mw->Label->pack(-side => 'top');
my $tree = $mw->Scrolled("Tree")->pack(-fill => 'both');
$tree->configure(
-width => 50,
-height => 30,
-itemtype => 'text',
-separator => '/',
-selectmode => 'single',
-scrollbars => 'se',
-browsecmd => sub {
my $data = shift;
$label->configure(-text => $data);
}
);
my $p = new XML::DOM::Parser;
my $doc;
eval {$doc = $p->parse($xml)};
if ($@){
print "XML文章が不整です : $@\n";
exit;
}
check('0',$doc);
MainLoop;
sub check
{
my $pos = shift;
my $ele = shift;
unless ($ele->hasChildNodes){
$tree->add($pos,-text=>Jcode->new(disp($ele),"utf8")->sjis);
$tree->autosetmode($pos);
}else{
$tree->add($pos,-text=>Jcode->new(disp($ele),"utf8")->sjis);
$tree->autosetmode($pos);
my $children = $ele->getChildNodes;
my $n = $children->getLength;
foreach (0 .. $n-1){
check($pos . '/' . $_ ,$children->item($_));
}
}
}
sub disp
{
my $ele = shift;
my $ret;
if ($ele->getNodeType == ELEMENT_NODE){
$ret = $ele->getTagName . " : ";
my $atrs = $ele->getAttributes;
my $n = $atrs->getLength;
for (0 .. $n-1){
$ret .= $atrs->item($_)->getName .
" = " . $atrs->item($_)->getValue .", ";
}
$ret =~ s/(.*), $/$1/ unless ($n == 0);
}elsif($ele->getNodeType == TEXT_NODE){
$ret = $ele->getData;
$ret =~ s/([\t\n])/sprintf "#%d;", ord $1/eg;
}elsif($ele->getNodeType == DOCUMENT_NODE){
my $decl = $ele->getXMLDecl;
my $enc = $decl->getEncoding ? $decl->getEncoding : "UTF8";
$ret = "ver= " . $decl->getVersion .
", encoding= " . $enc;
}else{
$ret = $ele->getNodeTypeName;
}
return $ret;
}
ここでは文字コードの変換にJcode.pmを使っています。前回は自作の
変換スクリプトを使ったのですが、あるものを使ったほうが便利ですね。
そのうち自作のモジュールは消えてなくなります。ちょっと
短い命でちょっとかわいそうでした。Jcode.pmはCPANからダウンロード
することができます。もともとはUnix用ですが、Win32環境でも使用可能
です。添付されたメモを読むとMacでも使えるようです。
JavaScriptを使って カレンダーツールを以前作ってみたことが あります。カレンダーの日付はスクリプトで作って、備考欄を XMLデータで補完しました。使ったXMLインスタンスのDTDは 以下のようになります。JavaScriptはブラウザにすごく依存します。 このスクリプトはIE4以上でないと動きません。
<!DOCTYPE all [ <!ELEMENT all (start,end,aday*)> <!-- カレンダーのスタート日 --> <!ELEMENT start (year,month)> <!-- カレンダーの終了日 --> <!ELEMENT end (year,month)> <-- aday には特定の日のデータを入れる --> <!ELEMENT aday (year,month,date,name,type)> <!-- year には数字か "ANUAL" が入る --> <!ELEMENT year (#PCDATA)> <!ELEMENT month (#PCDATA)> <!ELEMENT date (#PCDATA)> <!-- nameにその日が何の日だかを記述 --> <!ELEMENT name (#PCDATA)> <!-- type には"HORIDAY"か"MEMO"のいずれかが入る --> <!ELEMENT type (#PCDATA)> ]>
今見直すととても変です。(^^;
でもまああるものは利用しないと。
で、このDTDを元に作ったインスタンスは以下のようになります。
<?xml version="1.0" encoding="shift_jis"?> <ALL> <START> <YEAR>1999</YEAR> <MONTH>1</MONTH> </START> <END> <YEAR>1999</YEAR> <MONTH>2</MONTH> </END> <ADAY> <YEAR>ANUAL</YEAR> <MONTH>1</MONTH> <DATE>1</DATE> <NAME>元旦</NAME> <TYPE>HORIDAY</TYPE> </ADAY> <ADAY> <YEAR>ANUAL</YEAR> <MONTH>1</MONTH> <DATE>15</DATE> <NAME>成人の日</NAME> <TYPE>HORIDAY</TYPE> </ADAY> </ALL>
で、このXMLインスタンスを表示するページを以下のように 作りました。ここではフレームを利用します。親のフレーム をframe.html, 子供のフレームをframe1.html, frame2.html としてframe1にJavaScriptとボタン、frame2が出力用となるように しました。それぞれ内容は以下のようになります。
frame.html
<HTML> <FRAMESET rows="20%,*"> <FRAME id="main" src="frame1.html"> <FRAME id="calendar" src="frame2.html"> </FRAME> </HTML>
frame1.html
<HTML>
<TITLE>Calendar</TITLE>
<OBJECT
classid="clsid:CFC399AF-D876-11D0-9C10-00C04FC99C8E"
id="MSXML"
Name="xmlDoc">
</OBJECT>
<SCRIPT language="JavaScript">
<!--
// Global Variables
var startYear,startMonth,endYear,endMonth;
var xmlDoc;
var today = new Date();
var thisYear = today.getYear();
var thisMonth = today.getMonth();
var FLAG = 0;
var READY = 0;
var MSG;
function readXML()
{
var targetXML = "cal.xml";
var rootURL = "" + window.location;
var targetURL = "";
var i,dummy;
window.status = "Creating XML object...";
xmlDoc = MSXML;
window.status = "Loading XML document...";
for( i=rootURL.length-1; i>=0; i-- ) {
if( rootURL.charAt(i) == '\\' || rootURL.charAt(i) == '/' ) {
targetURL = rootURL.substr(0,i) + "/" + targetXML;
break;
}
}
if( targetURL == "" ) {
targetURL = targetXML;
}
xmlDoc.URL = targetURL;
// この行はトラップ。XML読み込みが失敗していると、この行でエラーが起きる
dummy = xmlDoc.version;
window.status="XML document loaded";
//いつからいつまで表示するか
startYear = xmlDoc.root.children.item(0).children.item(0).text - 1900;
startMonth = xmlDoc.root.children.item(0).children.item(1).text - 1;
endYear = xmlDoc.root.children.item(1).children.item(0).text - 1900;
endMonth = xmlDoc.root.children.item(1).children.item(1).text -1;
}
function calendar(oneOfFrame)
{
var Monthdays = new Array (31,28,31,30,31,30,31,31,30,31,30,31);
var Days = new Array("Sun","Mon","Thu","Wed","Thr","Fri","Sat");
var thisMonthDays,i,col,year;
var texts = "";
var date = new Date();
date.setDate(1);
date.setMonth(thisMonth);
date.setYear(thisYear);
if (thisYear < 2000){
year = thisYear + 1900;
}else{
year = thisYear;
}
if(((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)){
Monthdays[1] = 29;
}
thisMonthDays = Monthdays[date.getMonth()];
date.setDate(1);
Startday = date.getDay();
texts = "<HTML><BODY><CENTER>";
texts = texts + "<Table Border>\n";
texts = texts + "<TR><TH Colspan=7>";
texts = texts + year + "年" + (date.getMonth()+1) + "月";
texts = texts + "</TH></TR>\n";
texts = texts + "<TR>";
for(i = 0;i < 7;i++){
texts = texts + "<TH><TT>" + Days[i] + "</TT></TH>";
}
texts = texts + "</TR>\n";
texts = texts + "<TR>";
col = 0;
for(i = 0;i < Startday;i++){
texts = texts + "<TD></TD>";
col++;
}
for(i = 1;i <= thisMonthDays;i++){
texts = texts + "<TD>";
texts = texts + whatsup(thisYear,date.getMonth(),i,col);
texts = texts + "</TD>";
col++;
if(col == 7){
texts = texts + "</TR>\n<TR>";
col = 0;
}
}
texts = texts + "\n</Table>\n";
texts = texts + "<FORM NAME=\"schedule\">\n";
texts = texts + "<INPUT TYPE=\"text\" NAME=\"scroll\" SIZE=\"40\">\n";
texts = texts + "</FORM>\n";
texts = texts + "</CENTER></BODY></HTML>\n";
oneOfFrame.document.write(texts);
oneOfFrame.document.close();
}
function whatsup(year,month,date,days)
{
var txt = "";
var front = "";
var rear = "";
var i,tmp,str;
for(i = 2;i < xmlDoc.root.children.length;i++){
tmp = xmlDoc.root.children.item(i).children;
if((tmp.item(0).text == "ANUAL") || (tmp.item(0).text == (year+1900))){
if((tmp.item(1).text == (month+1)) && (tmp.item(2).text == date)){
txt = txt + " " + tmp.item(3).text;
if (tmp.item(4).text == "HORIDAY"){
front = front + "<B><Font Color = \"#0000ff\">";
rear = "</Font></B>" + rear;
}else if (tmp.item(4).text == "MEMO"){
front = front + "<S>";
rear = "</S>" + rear;
}
front = front + "<A Name= \"" + date +
"\" onMouseOver=\"window.parent.main.showMessage('" + txt +
"')\" onMouseOut=\"window.parent.main.cancelFlag()\">";
rear = "</A>" + rear;
}
}
}
if ((year == today.getYear()) && (month == today.getMonth()) && (date == today.getDate())){
front = front + "<Font Color = \"#ff0000\">";
rear = rear + "</FONT>";
}
if(days == 0){
front = front +"<B>";
rear = "<B>" + rear;
}
str = front + date + rear;
return str;
}
function cancelFlag(){
FLAG = 0;
}
function showMessage(text){
MSG = "";
FLAG = 1;
if(text.length < 40){
for (i = 0; i < 40- text.length;i++){
MSG = " " + MSG;
}
}
MSG = MSG + text;
scroll();
}
function scroll()
{
if (FLAG == 1){
window.parent.calendar.schedule.scroll.value = MSG;
MSG = MSG.substring(1,MSG.length)+MSG.substring(0,1);
setTimeout("scroll()",500);
}else{
window.parent.calendar.schedule.scroll.value = "";
}
}
function prevMonth()
{
if ((thisYear == startYear) && (thisMonth == startMonth)){
thisYear = endYear;
thisMonth = endMonth;
}else if (thisMonth == 0){
thisYear -= 1;
thisMonth = 11;
}else{
thisMonth -= 1;
}
frame = window.parent.calendar;
with (frame){
calendar(frame);
}
}
function nextMonth()
{
if ((thisYear == endYear) && (thisMonth == endMonth)){
thisYear = startYear;
thisMonth = startMonth;
}else if(thisMonth == 11){
thisYear += 1;
thisMonth = 0;
}else{
thisMonth += 1;
}
frame = window.parent.calendar;
with (frame){
calendar(frame);
}
}
function calendarReset()
{
thisYear = today.getYear();
thisMonth = today.getMonth();
frame = window.parent.calendar;
with (frame){
calendar(frame);
}
}
function waitReady()
{
if (READY == 1){
calendarReset();
}else {
setTimeout('waitReady()',10);
}
}
function readyGo()
{
READY = 1;
}
//-->
</SCRIPT>
<BODY>
<SCRIPT language="JavaScript">
<!--
readXML();
// setTimeout('calendarReset()',500);
waitReady();
//-->
</SCRIPT>
<CENTER>
<FORM>
<INPUT type="button" Value="前月" onClick="prevMonth()">
<INPUT type="button" Value="次月" onClick="nextMonth()">
<INPUT type="button" Value="今月" onClick="calendarReset()">
</FORM>
</CENTER>
</BODY>
</HTML>
frame2.html
<HTML> <BODY onLoad = "window.parent.main.readyGo()"> <H3>Now Loading...</H3> </BODY> <SCRIPT> <!-- //--> </SCRIPT> </HTML>
最近見つけたアプリケーションにXML::RSSというのがあります。 モジュール自体はCPANから落とすことが できます。RSSは Rich Site Summary の略で、ニュースヘッドラインを配送 するために利用されています。つまり、
問題になりやすいのは、文字コードです。Perlは日本語の取り扱い
がうまくありません。JPerlを使うことも選択肢の一つですが、
Perl5.6からのUnicodeサポートに期待をつないで、後は自分の
経験則でなんとかするというのが私のやり方です。
そのために妙な苦労をしてしまいますが… (^^;
Jcode.pmを使うとUTF-8, UTF-16, EUC, Shift-JIS, JIS の変換が
できます。例えば、こんな感じ。
use Jcode;
$utf8 = "\xe7\xbe\x8e\xe3\x81\x97\xe3\x81\x84"
. "&#26085;&#26412;"
. "\xe8\xaa\x9e";
$str = Jcode->new($utf8,utf8)->sjis;
for ($str){
s/(\&\#(\d+)\;)/Jcode->new(pack("n",$2),ucs2)->sjis/eg;
print;
}
これを実行すると「美しい日本語」と出力します。&#26085;などは
文字参照を使っている文字の例です。