スッキリした変換のほうがうれしい感じはします。しかし、メタ情報の参照というのはたいていは不吉なことで、破綻の入り口だったりしますので、どうすべきかの判断は難しいところです。
XML側のメタ情報を使うのはやっぱりイヤなので、XMLインスタンスだけを見てメタ情報なしで出来る簡約化を考えることにします。[追記]正規形の選び方がどうも間違ってます、後で考えなおします。[/追記] [追記の追記]修正案を「拡張JSONで表現したXMLの正規形」に書きました。[/追記の追記]
簡約ルール
次のデータを例題に使います。
@book { "" : [ @title { "" : ["Webを支える技術"] }, @author { "" : ["山本陽平"] }, @isbn-10 {"" : ["4774142042"] } ] }
まず、 @title { "" : ["Webを支える技術"] } を @title { "" : "Webを支える技術" } にすることはメタ情報なしで機械的に出来ます。そのルールを明示的に書けば次のとおり。
- 簡約ルール-1: 1つの項目だけからなるリストは、その項目にする。
さらに次のルールも採用しましょう。
- 簡約ルール-2: 無名プロパティ(XMLの要素内容に相当)だけからなるオブジェクトはそのプロパティ値に置き換える。
簡約ルール-2を適用すると、@title { "" : "Webを支える技術" } は、@title "Webを支える技術" となります。
最初の例に、2つの簡約ルールをどんどん適用すると次のようになります。
@book [ @title "Webを支える技術", @author "山本陽平", @isbn-10 "4774142042" ]
これなら許せる感じ。
拡張JSONのなかでXMLデータを定義する
念のために、「もとのXMLデータ」と「変換後に簡約化された拡張JSONデータ」との関係も書いておきます。
- 属性を持たず、内容がテキストだけの要素: @tag "文字列"
- 属性を持たず、内容に子要素を含む要素: @tag [ 子ノードの列 ]
- それ以外の一般的な要素: @tag {"属性名" : "属性値", ..., "" : 内容}
これをもとに、拡張JSONデータの内部で、要素、内容、属性などを定義することもできます。
- 文字列は基本内容である。
- 要素は基本内容である。
- 基本内容は内容である。
- 基本内容のリストは内容である。
- 内容にタグを付けたデータは要素である。
- 無名プロパティ値が内容で、その他に文字列値のプロパティだけを持つオブジェクトは属性付き内容である。
- 属性付き内容にタグを付けたデータは要素である。
BNFで書けば:
基本内容 ::= 文字列 | 要素 内容 ::= 基本内容 | list<基本内容> 属性付き内容 ::= {文字列値属性並び, "" : 内容} 要素 ::= タグ 内容 | タグ 属性付き内容
冗長性を避けるために表現をできるだけ短くしたいなら、次の処理をします。
- リスト内で、連続する文字列は連結する。
- リスト内で、空文字列は削除する。
- @tag "" は @tag [] に置き換える。(短くはならないが、冗長性を排除)
- 長さ1のリストは、その項目に置き換える。
- "" : [] という形のプロパティは削除する。
RSSデータの一部を拡張JSON形式で書いてみると次のようです。名前空間接頭辞はそのまま使ってますが、名前空間URIは考慮されてません。
@channel { "rdf:about" : "http://d.hatena.ne.jp/m-hiyama/rss", "" : [ @title "檜山正幸のキマイラ飼育記", @link "http://d.hatena.ne.jp/m-hiyama/", @description "檜山正幸のキマイラ飼育記", @dc:creator "m-hiyama", @dc:date "2010-04-09T18:18:18+09:00", @items @rdf:Seq [ @rdf:li { "rdf:resource" : "http://d.hatena.ne.jp/m-hiyama/20100409/1270774835" }, @rdf:li { "rdf:resource" : "http://d.hatena.ne.jp/m-hiyama/20100409/1270778764", } ] ] }
可読性は悪くないですね。いや、けっこう読みやすい。これはいいかも。
[追記]
拡張JSONでエンコードされたXMLの特徴付けをもう少し詳しく書いておきます。
- 属性を持たず、内容が空である要素: @tag []
- 属性を持たず、内容が非空テキストだけの要素: @tag "文字列"
- 属性を持たず、内容が単一要素である要素: @tag @tag1 属性と内容
- 属性を持たず、内容に子要素を含む要素: @tag [ 子ノードの列 ]
- 属性を持ち、内容が空である要素: @tag {"属性名" : "属性値"}
- 属性を持ち、内容が非空である要素: @tag {"属性名" : "属性値", "" : 非空内容}
Catyスキーマで書けるかな?
// eXtendedJSON-encoded XML // 再帰的な定義 type NEString = string(minLength=1); // 非空 (NonEmpty) 文字列 type BasicContent = NEString | Element; type Content = BasicContent | [BasicContent*]; type AttrCon = {"" : Content?, * : string?}; // 属性が付くかも知れない内容 type Element = @* AttrCon;
@* は実装されてませんが、ワイルドカードタグで、任意の名前(ただし予約語以外)のタグにマッチします。この定義で十分だと思いますが、次の冗長性が残ります。
- 単一要素または文字列 x とシングルトンのリスト [x] のどちらも使える。
- "" : [] というプロパティが許される。("" : "" は許されない)
- 子ノードのリストに、文字列が連続して出現する。
- 空内容要素を @tag {} でも @tag [] でも表すことができる。
これらの点に関しては、バリデーションでハネるのではなくて、許容したほうがいいでしょう。その代わり、同値性の条件と正規化(表現の簡略化)の手順を定義しておきます。[追記]正規形の選び方がどうも間違ってます、後で考えなおします。[/追記] [追記の追記]修正案を「拡張JSONで表現したXMLの正規形」に書きました。[/追記の追記]
- x と [x] はどちらでもよいが同値である。x が正規形。
- "" : [] というプロパティがあってもよい。削除した形が正規形。
- リスト内で連続して出現する文字列があってもよい。連結した形が正規形。
- @tag [] を正規形と決める(別にどっちでもいいのだが)。
実際、非正規形のほうが見やすい/使いやすいこともあります。次は非正規形の例。
- @greeting [@smile []]
- @script {"src" : "js/lib.js", "" :[]}
- @greeting ["hello, ", "world.\n"]