LINQ to XMLのNamespaceと書き出し時のEncodingについて

ver 0.0.0.2に更新。アップロードするフォルダが指定出来るようになりました。アップロードツール名(FotolifeUploader)が利用されるようになりました。フォルダ指定は再設定が必要なので、前のバージョンを使っている方はsettings.xmlを削除して、再度設定し直してください。あとは間抜けだったUploadToFotolifeメソッドを手直ししたり。

私自身が、そもそもフォトライフのヘビーユーザーではないので、細かいところに気が利いてないかもですね……。そういうのは、よくない。というわけで、当分はFotolifeをちゃんと利用しようキャンペーンを張ることにします。なので、デジタル一眼を買う。と言いたいのだけど、何か微妙なのよねん。いや、そもそも引き籠って家から出ないので撮影するものがないので。かといって熱帯魚や食虫植物とかフィギュアとか、撮影に適した趣味があるわけでもなく。困った困った。まあ、考えます。食虫植物を育てる方向で(?) 部屋が殺風景なので何かは入れたいのだけど、手間はかけたくない。ううむ、難しい。

LINQ to XML

アップロードにはAtomAPIを利用しているので、XMLです。つまりLINQ to XMLの出番です。出力結果がこんな感じなので、そこから逆に考えると……

<entry xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://purl.org/atom/n
s#">
  <title>タイトル</title>
  <content mode="base64" type="image/jpeg">画像BASE64</content>
  <generator>FotolifeUploader</generator>
  <dc:subject>フォルダ名</dc:subject>
</entry>

XElementは、Namespaceの利用が少しややこしいんですよね。最初引っかかりました。「XNamespace.Xmlns + "接頭辞"」で登録できます。

XNamespace ns = "http://purl.org/atom/ns#";
XNamespace dc = "http://purl.org/dc/elements/1.1/";
var xml =
    new XDocument(new XDeclaration("1.0", "UTF-8", null),
    new XElement(ns + "entry", new XAttribute(XNamespace.Xmlns + "dc", dc),
        new XElement(ns + "title", "タイトル"),
        new XElement(ns + "content", new XAttribute("mode", "base64"), new XAttribute("type", "image/jpeg"), "画像BASE64"),
        new XElement(ns + "generator", "FotolifeUploader"),
        new XElement(dc + "subject", "フォルダ名")
    ));
Console.WriteLine(xml); // 出力確認、DeclarationはToStringでは出力されない

少し独特ですが、ほとんど1:1で対応させられるので慣れるとサクサク書けます。非常に快適。個人的にはXMLリテラル的なものよりも好き。Linqがあってほんと良かった……。で、Declarationを出力したい場合の話に続く。(hatena (diary ’Nobuhisa))にもあるように、ToStringでは出力されないのでSaveを使う、と……

var sb = new StringBuilder();
var sw = new StringWriter(sb);
xml.Save(sw);
Console.WriteLine(sb); // UTF-16になる

これでencodingがUTF-16になるのは、Saveメソッド呼ぶとDeclarationは作りなおしているから。.Save("string fileName")ではXDeclarationのエンコーディングを見て、それで保存するけれど、それ以外の場合はXDeclaration無視で再構築される。XDocumentというかXmlWriterのほうの話でしょうか。実際にファイル出力してみると分かる。

var fs = new FileStream(@"C:\text.xml", FileMode.Create);
var sw = new StreamWriter(fs, Encoding.GetEncoding("x-mac-turkish"));
xml.Save(sw);

出力先のエンコードに合わせてくれる、のを便利と見るか、むしろ気が利かない、Writer部分もC#3.0に合わせて作りなおせ、なのかは不明。まあ、嘘エンコード宣言は許しませんよってことですかね。じゃあどうするか、って言ったら

// これで別に何も問題ないと思います、文字列として吐くんだからToStringでいいと思ふ
var xmlString = string.Format("{0}{1}{2}",
    xml.Declaration, Environment.NewLine, xml);
Console.WriteLine(xmlString);

// ToStringがどうしても嫌ならMemoryStream経由で、とか?
string result;
var encoding = Encoding.UTF8;
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms, encoding))
{
    xml.Save(sw);
    result = encoding.GetString(ms.ToArray());
}
Console.WriteLine(result); // 望み通りのUTF-8で出力されてます

結論は、普通にToStringでいいんじゃないかな、と。ToStringメソッドだけではXmlWriterSettingsで言うところのOmitXmlDeclarationを設定出来ないから、デフォルトでは付加しないようにしてる。削除は無理だけど、追加なら簡単だから。XmlDeclarationを付加したい時は別途、自分でくっつければいい。というだけのお話かなー? ToStringで一発で終わらせられないからStringBuilder使って組み立てるってのは、何でそうなるの?と、とても思った。ついでにもう一つ。

// こんなXElementがあるとして
var xElement = XElement.Parse("<hoge>3</hoge>");
// intとして値を取り出す時は
var num1 = int.Parse(xElement.Value); // これダメ。
var num2 = (int)xElement; // こう書こう。

です。LINQ to XMLは既存のものを上手く使ってシンプルに書けるように作られてる。気がする。このキャストもそうだし、ToStringもそう。Parseは頻繁に行うから汚くなるよね→キャストでよくね? 文字列化はよくやるけどSaveもXmlWriterSettingsも面倒くさいよね→ToStringでよくね? といった感じ。関数型構築もそうだけど、今までのもの(XmlDocument)を踏まえて、よく練り直されているなー、と思います。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive