%include "default.mgp" %%default 1 left, size 6, fore "light yellow", back "blue5", font "standard", ccolor "white", vgap 35 %%default 2 noop %%default 3 fore "white", bar "gray70", vgap 10 %%default 4 noop %%tab 1 noop %%tab 2 noop %%tab 3 noop %default 1 left, size 6, fore "light yellow", back "blue4", font "standard", ccolor "white", vgap 35, bgrad 0 0 128 45 1 "#000080" "#400040" "#800000" %default 2 size 5 %default 3 size 2, fore "white", bar "gray70", vgap 10 %default 4 size 4 %tab 1 size 4 %tab 2 size 3 %tab 3 size 3 %page %fore "white", size 9, vgap 15 %center, font "thick" %ccolor "white" %size 10 Ruby と XML %size 6, fore "orange", font "standard" 吉田正人 %size 5, fore "green" ドコモ・システムズ (株) %font "typewriter", size 4, fore "yellow" yoshidam@yoshidam.net %font "typewriter", size 4, fore "yellow" yoshidam@docomo-sys.co.jp %page 目次 %leftfill XML プログラミングの概要 XMLParser ライブラリの実装について DOM ライブラリの実装について XMLParser ライブラリの使い方 DOM ライブラリの使い方 もう少し実用的なプログラムの例 今後について 付録 %page XML プログラミングの概要 (1) %leftfill XML プログラミングのための API イベント形式 SAX (Simple API for XML) PYX (ESIS) オブジェクト形式 DOM (Document Object Model) %page XML プログラミングの概要 (1-1) %leftfill SAX (http://www.megginson.com/SAX/): イベント (要素開始, 終了, 文字データ, 処理命令など) に対するハンドラを定義する ほとんどの Java の XML パーサで対応している %font "typewriter", size 3, fore "yellow" SAX API の一部 (Java) public interface DocumentHandler { public abstract void startDocument(); public abstract void endDocument(); public abstract void startElement(String name, AttributeList atts); public abstract void endElement(String name); public abstract void characters(char ch[], int start, int length); public abstract void ignorableWhitespace(char ch[], int start, int length); public abstract void processingInstruction(String target, String data); } %page XML プログラミングの概要 (1-2) %leftfill PYX (http://www.xml.com/pub/a/2000/03/15/feature/index.html): イベントを行指向の記法で出力する SGML のプログラミングでよく使われていた %font "typewriter", size 3, fore "yellow" XML ファイル PYX 記法 (test Aattr1 test ほげ → -\n (hoge -\n ほげ\n )hoge -\n )test %page XML プログラミングの概要 (1-3) %leftfill DOM (http://www.w3.org/TR/REC-DOM-Level-1): IDL で定義されたオブジェクトモデル W3C 標準 %font "typewriter", size 3, fore "yellow" DOM のインターフェイスの例 (OMG IDL) interface Node { readonly attribute DOMString nodeName; attribute DOMString nodeValue; readonly attribute unsigned short nodeType; readonly attribute Node parentNode; readonly attribute NodeList childNodes; readonly attribute Node firstChild; readonly attribute Node lastChild; readonly attribute Node previousSibling; readonly attribute Node nextSibling; Node insertBefore(in Node newChild, in Node refChild) raises(DOMException); Node appendChild(in Node newChild) raises(DOMException); }; %page XML プログラミングの概要 (2) %leftfill Ruby の利点 お手軽 コンパイル不要 変数宣言不要 メモリ管理不要 強力な文字列処理能力 Perl と同等の正規表現 日本語,UTF-8 の処理が可能 読みやすい,書きやすい %page XMLParser ライブラリの実装について (1) %leftfill expat とは SP の作者である James Clark 氏が作った C 言語による高速 XML パーサライブラリ (http://www.jclark.com/xml/expat.html) 現在はバージョン 2.0 が expat プロジェクト (http://sourceforge.net/projects/expat/) によって開発中 expat の特徴 高速 validating は行わない イベント方式の API アプリケーションやライブラリへの組込みが容易 (Mozilla, Perl, Python, Tcl, PHP3 / PHP4, w3c-libwww, Apache) %page XMLParser ライブラリの実装について (1-1) %leftfill expat API の例 (C 言語) %font "typewriter", size 3, fore "yellow" XML_Parser XMLPARSEAPI XML_ParserCreate(const XML_Char *encoding); int XMLPARSEAPI XML_Parse(XML_Parser parser, const char *s, int len, int isFinal); void XMLPARSEAPI XML_SetElementHandler(XML_Parser parser, XML_StartElementHandler start, XML_EndElementHandler end); void XMLPARSEAPI XML_SetCharacterDataHandler(XML_Parser parser, XML_CharacterDataHandler handler); void XMLPARSEAPI XML_SetCommentHandler(XML_Parser parser, XML_CommentHandler handler); %page XMLParser ライブラリの実装について (2) %leftfill 基本方針 できるだけ簡略に記述できること expat の高速さを生かすこと イベントハンドラ無しの場合は expat そのもののスピードになるように expat のすべての機能が扱えること ただし,Ruby では不要な機能は除く %page XMLParser ライブラリの実装について (3) %leftfill Parser API について イベントハンドラ・インタフェイス XML::Parser クラスにメソッド定義することによりイベントハンドラを定義する メソッド定義しなかったイベントは処理しない イベント駆動プログラム イテレータ・インターフェイス XMLParser#parse メソッドをイテレータとして使用する ループ処理のように記述できる イベント駆動プログラムに慣れていなくても使える 基本イベント以外はダミーメソッドの定義が必要 SAX インターフェイス %page XMLParser ライブラリの実装について (3-1) %leftfill イベントハンドラ・インターフェイスの実装 %font "typewriter", size 3, fore "yellow" static void myStartElementHandler(void *recv, const XML_Char *name, const XML_Char **atts) { VALUE attrhash; attrhash = rb_hash_new(); while (*atts) { const char* key = *atts++; const char* val = *atts++; rb_hash_aset(attrhash, rb_str_new2((char*)key), rb_str_new2((char*)val)); } rb_funcall((VALUE)recv, id_startElementHandler, 2, rb_str_new2((char*)name), attrhash); } %page XMLParser ライブラリの実装について (3-2) %leftfill イベントハンドラ・インターフェイスの実装 (続き) %font "typewriter", size 3, fore "yellow" id_startElementHandler = rb_intern("startElement"); id_endElementHandler = rb_intern("endElement"); if (rb_method_boundp(CLASS_OF(obj), id_startElementHandler, 0)) start = myStartElementHandler; if (rb_method_boundp(CLASS_OF(obj), id_endElementHandler, 0)) end = myEndElementHandler; if (start || end) XML_SetElementHandler(parser->parser, start, end); %page XMLParser ライブラリの実装について (3-3) %leftfill イベントハンドラのメソッド名 %font "typewriter", size 3, fore "yellow" メソッド名 イベント ------------------------------------------------------- startElement | element start tag endElement | element end tag character | character data processingInstruction | processing instruction unparsedEntityDecl | unparsed entity declaration notationDecl | notation declaration externalEntityRef | external entity reference comment | comment startCdata | CDATA section start endCdata | CDATA section end startNamespaceDecl | Namespace declaration start endNamespaceDecl | Namespace declaration end startDoctypeDecl | DOCTYPE declaration start endDoctypeDecl | DOCTYPE declaration end notStandalone | document is not standalone default | other data defaultExpand | same as default unknownEncoding | unknown character encoding %page XMLParser ライブラリの実装について (3-4) %leftfill イテレータ・インターフェイスの実装 %font "typewriter", size 3, fore "yellow" static void iterStartElementHandler(void *recv, const XML_Char *name, const XML_Char **atts) { VALUE attrhash; attrhash = rb_hash_new(); while (*atts) { const char* key = *atts++; const char* val = *atts++; rb_hash_aset(attrhash, rb_str_new2((char*)key), rb_str_new2((char*)val)); } rb_yield(rb_ary_new3(3, INT2FIX(XML_START_ELEM), rb_str_new2((char*)name), attrhash)); } %page XMLParser ライブラリの実装について (3-5) %leftfill イテレータ・インターフェイスの実装 (続き) %font "typewriter", size 3, fore "yellow" parser->iterator = rb_iterator_p(); if (parser->iterator) { XML_SetElementHandler(parser->parser, iterStartElementHandler, iterEndElementHandler); ... } %page XMLParser ライブラリの実装について (3-6) %leftfill イテレータブロックへの引数 %font "typewriter", size 3, fore "yellow" 第一引数 (イベントタイプ) 第二引数 第三引数 -------------------------------------------------------------- START_ELEM | element name | hash of attributes END_ELEM | element name | nil CDATA | nil | string PI | PI name | string UNPARSED_ENTITY_DECL | entity name | array NOTATION_DECL | notation name | array EXTERNAL_ENTITY_REF | entity names | array COMMENT | nil | string START_CDATA | nil | nil END_CDATA | nil | nil START_NAMESPACE_DECL | prefix | URI END_NAMESPACE_DECL | prefix | nil START_DOCTYPE_DECL | doctype name | nil END_DOCTYPE_DECL | nil | nil DEFAULT | nil | string %page XMLParser ライブラリの実装について (4) %leftfill 文字エンコーディング対応 パース前にエンコーディング変換 ISO-2022-JP はこの方法でのみ対応可能 XML::Encoding クラス expat の UnknownEncoding イベントでパーサにコンバータを登録 Ruby を使ったエンコーディングコンバータも可能 Perl エンコーディングマップの流用 Perl の XML::Parser モジュールの機能を流用 Perl の XML::Encoding モジュールを利用し,マッピングテーブル作成 EUC-JP,Shift_JIS 等が利用可能 %page XMLParser ライブラリの実装について (4-1) %leftfill パース前にエンコーディング変換 %font "typewriter", size 3, fore "yellow" jis = open("sample.xml").read utf8 = Uconv.euctou8(NKF.nkf("-Je", jis)) XML::Parser.new("UTF-8").parse(utf8) %page XMLParser ライブラリの実装について (4-2) %leftfill XML::Encodingクラス %font "typewriter", size 3, fore "yellow" class EUCHandler def map(i) return i if i < 128 return -1 if i < 160 or i == 255 return -2 end def convert(s) Uconv.euctou2(s) end end def unknownEncoding(name) return EUCHandler.new if name =~ /^euc-jp$/i nil end %page XMLParser ライブラリの実装について (4-3) %leftfill Perlエンコーディングマップの流用 %font "typewriter", size 3, fore "yellow" big5.enc euc-kr.enc iso-8859-2.enc iso-8859-3.enc iso-8859-4.enc iso-8859-5.enc iso-8859-7.enc iso-8859-8.enc iso-8859-9.enc windows-1250.enc x-euc-jp-jisx0221.enc x-euc-jp-unicode.enc x-sjis-cp932.enc x-sjis-jdk117.enc x-sjis-jisx0221.enc x-sjis-unicode.enc Shift_JIS と EUC-JP が使えないのは不便なので… shift_jis.enc (x-sjis-cp932.enc と同じ) euc-jp.enc (x-euc-jp-unicode.enc と同じ) %page DOM ライブラリの実装について %leftfill XML::DOM クラス DOM level1 Core API にほぼ準拠 一部未実装 (Document#implementationなど) 文字列型非互換 (wstringではなく,多バイト文字列) Ruby 向けのメソッド,イテレータなどを含む XPointer (WD) 対応 ―― 福嶋正機氏による Visotor モジュール XML::DOM クラスで Visotor パターンを使うためのモジュール Perl 用 XML::Grove::Visitor とほぼ同等 %page XMLParser ライブラリの使い方 (1) %leftfill イベントハンドラの使用例 要素開始タグの数を数えて,各要素の使用回数を調べる。 %font "typewriter", size 3, fore "yellow" require 'xmlparser' class NewParser < XML::Parser attr :elemNames def initialize @elemNames = {} end def startElement(name, attrs) if @elemNames[name].nil? @elemNames[name] = 1 else @elemNames[name] += 1 end end end %page XMLParser ライブラリの使い方 (2) %leftfill イベントハンドラの使用例(続き) %font "typewriter", size 3, fore "yellow" parser = NewParser.new begin parser.parse($<.read) rescue XML::ParserError print "Error: #{$!} in line #{parser.line}\n" end ## print result p parser.elemNames %page XMLParser ライブラリの使い方 (3) %leftfill イテレータの使用例 %font "typewriter", size 3, fore "yellow" require 'xmlparser' parser = XML::Parser.new elemNames = {} begin parser.parse($<.read) do |type, name, data| case (type) when XMLParser::START_ELEM if elemNames[name].nil? elemNames[name] = 1 else elemNames[name] += 1 end end end ## print result p elemNames rescue XML::ParserError print "Error: #{$!} in line #{parser.line}\n" end %page DOM ライブラリの使い方 (1) %leftfill DOM と Visitor の使用例 %font "typewriter", size 3, fore "yellow" require 'xmltreebuilder' require 'xmltreevisitor' class Counter < XML::DOM::Visitor attr :elemNames def initialize @elemNames = {} end def visit_Element(elem) name = elem.nodeName if @elemNames[name].nil? @elemNames[name] = 1 else @elemNames[name] += 1 end super end end %page DOM ライブラリの使い方 (2) %leftfill DOM と Visitor の使用例(続き) %font "typewriter", size 3, fore "yellow" parser = XML::DOM::Builder.new begin tree = parser.parse($<.read) rescue XML::ParserError print "Error: #{$!} in line #{parser.line}\n" end tree.documentElement.accept(c = Counter.new) p c.elemNames %page もう少し実用的なプログラムの例 (1) %leftfill DocBook (もどき) を HTML に変換する。 %font "typewriter", size 3, fore "yellow"
テスト文書 DocBook から HTML に変換するテストです。 セクション1 ほげ セクション2 サブセクション2-1 サブセクション2-1 です。
%page もう少し実用的なプログラムの例 (2) %leftfill こんな感じに… %font "typewriter", size 3, fore "yellow" テスト文書

テスト文書

DocBook から HTML に変換するテストです。

1 セクション1


2 セクション2

サブセクション2-1

サブセクション2-1 です。


%page もう少し実用的なプログラムの例 (3) %leftfill 要素名の置き換え %font "typewriter", size 3, fore "yellow"
  • → <H1> <sect1><title> → <H2> <sect2><title> → <H3> <ulink url="url"> → <A href="url"> %page もう少し実用的なプログラムの例 (3) %leftfill 目次も作ってみる %font "typewriter", size 3, fore "yellow" <sect1 label="label1"> <title>Title1 ... Title2 ...
    1. Title1
    2. Title2
    %page もう少し実用的なプログラムの例 (4) %leftfill プログラムの設計 1-パスでは目次作成が困難 DOM ツリーから DOM ツリーへの変換ではかなり遅い したがって… 1. イベントハンドラで要素名を置き換えつつ HTML DOM ツリー作成 2. HTML DOM ツリーを検索して目次を作成 3. HTML ファイル出力 (EUC-JP に変換できない文字は文字参照に変換) %page もう少し実用的なプログラムの例 (5) %leftfill 1. イベントハンドラで要素名を置き換えつつ HTML DOM ツリー作成 %font "typewriter", size 3, fore "yellow" class DocBook2HTML < XML::Parser def initialzie @current = [] @ret = HTML::HTMLDocument.new end def startElement(name, attrs) case name when "article" html = @ret.createHTMLElement('HTML') html.appendChild(_createHTMLHeader(@ret)) body = @ret.createHTMLElement('BODY') html.appendChild(body) @ret.appendChild(html) @current.push(body) when "emphasis" em = @ret.createHTMLElement("EM") @current[-1].appendChild(em) @current.push(em) ... end end %page もう少し実用的なプログラムの例 (6) %leftfill 1. イベントハンドラで要素名を置き換えつつ HTML DOM ツリー作成 (続き) %font "typewriter", size 3, fore "yellow" def endElement(name) case name when "article" @current[-1].appendChild(_createHTMLFooter(@ret)) when "emphasis" @current.pop ... end end def character(data) @current[-1].appendChild(@ret.createTextNode(data)) end %page もう少し実用的なプログラムの例 (7) %leftfill 2. HTML DOM ツリーを検索して目次を作成 %font "typewriter", size 3, fore "yellow" def _createIndex(indexNode) ol = @ret.createHTMLElement("OL") indexNode.appendChild(ol) section = 1 @ret.getNodesByXPath('/HTML/BODY/DIV[@class="section"]').each do |n| label = n.getElementsByTagName("A")[0].attributes['name'].nodeValue text = n.getElementsByTagName("H2")[0].childNodes[0].nodeValue li = @ret.createHTMLElement("LI") a = @ret.createHTMLElement("A", {'href'=>'#' + label}, text) li.appendChild(a) ol.appendChild(li) section += 1 end end %page もう少し実用的なプログラムの例 (8) %leftfill 3. HTML ファイル出力 %font "typewriter", size 3, fore "yellow" def getHTML case @charset when /ISO-8859-1/i Uconv.u8tolatin1(@ret.to_s) when /EUC-JP/i Uconv.u8toeuc(@ret.to_s) end end end class <