[c#]iTuens の api を使って App Store のアプリ情報の取得

iTunesより早い「高速AppStore検索」作りました。 – ku-sukeのはてなダイアリー
http://d.hatena.ne.jp/ku-suke/20100228
iPhoneアプリの値下げ情報をつぶやくBotを作りました。あと、App storeのxmlの取得の仕方。 ≪ KORESS: ジャパニーズ・モダン・ドンブラコ
http://koress.jp/2009/10/iphonebotapp_storexml.html

あたりを見て、iTunes の web api を叩けば ok ってことで、簡単に。

private void button1_Click(object sender, EventArgs e)
{

	// url を取得
	string id = "6015";
	string url = "http://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/browse?path=%2F36%2F" + id + "%2F1";
	WebClientEx client = new WebClientEx();
	// cookie を設定
	client.Cookie = new CookieContainer();
	client.Headers.Add("User-Agent", "iTunes/10.6.3 (Windows; Microsoft Windows 7 Ultimate Edition Service Pack 1 (Build 7601)) AppleWebKit/534.57.2");
	client.Headers.Add("X-Apple-Store-Front", "143462-9");
	// client.Headers.Add("Accept-Encoding", "gzip");
	StreamReader sr = new StreamReader(client.OpenRead(url));
	string content = sr.ReadToEnd();
	sr.Close();

	Debug.Print("length:{0}", content.Length);

	// ファイルに出力
	StreamWriter sw = new StreamWriter("result.xml", false);
	sw.Write(content);
	sw.Close();
	textBox1.Text = string.Format("success: {0}", content.Length);
}

受信する時に gzip のほうが小さくなるのですが、zip 解凍が面倒なので付けていません。つけないと普通の text で取得できます。
ただし、カテゴリによって非常にでかい(ファイナンシャルだと10MBぐらい)なので、注意が必要です。

取得したデータは XML 形式で、objective-c を書いた方であれば、ああなるほどなプロパティリストな構造になっています。
なので、dict タグの下に、key-value という形式なのですが…これがプロパティリストのアクセス手段がない c# では面倒で orz

<dict>
  <key>artistId</key><integer>284946266</integer>
  <key>artistName</key><string>e-Agent</string>
  <key>buy-only</key><true/>
  <key>buyParams</key><string>productType=C&amp;salableAdamId=286058381&amp;pricingParameters=STDQ&amp;price=0&amp;ct-id=14</string>
  <key>genre</key><string>ファイナンス</string>
  <key>genreId</key><integer>6015</integer>
  <key>itemId</key><integer>286058381</integer>
  <key>itemName</key><string>3in1 Mortgage Calc</string>
  <key>kind</key><string>software</string>
  <key>playlistName</key><string>3in1 Mortgage Calc</string>
  <key>popularity</key><string>0.0</string>
  <key>price</key><integer>0</integer>
  <key>priceDisplay</key><string>無料</string>
  <key>rating</key>

自前の ExDoc を使ってこんなことをしています(ExDocは、そのうち、XmlDom, HtmlDom になる予定です)。

private void button2_Click(object sender, EventArgs e)
{
	var doc = new EXDocument();
	doc.Load(&quot;result.xml&quot;);

	var items = doc * &quot;key&quot; == &quot;itemId&quot;;
	var lst = new List<App>();
	foreach ( var it in items )
	{
		var pa = it.Parent;
		lst.Add(
			new App
			{
				itemId = pa.ChildNodes[13].Value,
				itemName = pa.ChildNodes[15].Value
			});
	}
	dataGridView1.DataSource = lst;
}

key の値が「itemId」のタグを拾ってきて、親要素を取得。
その子ノードを ChildNodes でピンポイントで拾うという荒業です。まあ、これで用途は足りるかと。

実行結果はこんな感じ。

このデータをローカルファイルに保存しておけば、iTunes を使わなくても高速検索ができますよ、ってな具合ですね。
Web 上に乗せる場合はちょっと工夫が要りますが基本は同じ。一定時間で取得するようにすれば、最新アプリ情報とかも取れるかも。

カテゴリー: C# | [c#]iTuens の api を使って App Store のアプリ情報の取得 はコメントを受け付けていません

[C#] XElement とは違う LINQ できる XmlNode を作成する

昨日の続きで、XmlNode に navigator の機能を付けてみます。
xml に関するナビゲータは、既に XPathNavigator クラスがあって、XPATH の記法を使って XML のツリー構造を探索できるのですが…当たり前なのですが「XPATHを使わないといけない」という制約があります。

XPathNavigator クラス (System.Xml.XPath)
http://msdn.microsoft.com/ja-jp/library/system.xml.xpath.xpathnavigator(v=vs.110).aspx

まあ、xpath で細々と書ればいいんだけど、もっと大雑把に C# の文法に近く、というか LINQ 近い形で探索がしたいなぁと思った…というのは嘘で、成り行き上こうなりましたって感じです。HTML の探索方法を色々模索していったら、LINQ の Where メソッドを使う(IEnumable<> を使う)ほうが一番妥当かというパターンなのです。

■IEnumable<>部分を HtmlDoc からコピペ

以前作ったところから、そのままコピーして手直し。
IEnumerator<XmlNode> のところは、内部クラスにして XmlNavigator.Enumerator にします。

public class XmlNavigator : IEnumerable<XmlNode>
{
	XmlNode _root;

	public XmlNavigator(XmlNode root)
	{
		_root = root;
	}

	public IEnumerator<XmlNode> GetEnumerator()
	{
		return new Enumerator(_root);
	}

	System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
	{
		return new Enumerator(_root);
	}

	protected class Enumerator : IEnumerator<XmlNode>
	{
		XmlNode _root;
		XmlNode _cur;

		public Enumerator(XmlNode root)
		{
			_root = root;
			_cur = null;
		}


		public XmlNode Current
		{
			get { return _cur; }
		}

		public void Dispose()
		{
		}

		object System.Collections.IEnumerator.Current
		{
			get { return _cur; }
		}

		public bool MoveNext()
		{
			if (_cur == null)
			{
				_cur = _root;
				return true;
			}
			if (_cur.Children.Count > 0)
			{
				_cur = _cur.Children[0];
				return true;
			}
			if (_cur.NextSubling != null)
			{
				_cur = _cur.NextSubling;
				return true;
			}
			XmlNode cur = _cur;
			while (true)
			{
				XmlNode pa1 = cur.Parent;
				if (pa1 == null)
				{
					_cur = null;
					return false;
				}
				if (pa1.NextSubling != null)
				{
					_cur = pa1.NextSubling;
					return true;
				}
				cur = pa1;
			}
		}

		public void Reset()
		{
			_cur = null;
		}
	}
}

MoveNext メソッドが若干ややこしくて、XML のツリー構造を親子関係を伝って探索していきます。バイナリサーチの要領ですね。
次の兄弟要素を拾うことが必要なので、XmlNode.NextSubling メソッドを追加しています。

		public XmlNode NextSubling
		{
			get
			{
				if (this.Parent == null)
				{
					return null;
				}
#if true
				var it = this.Parent.Children.GetEnumerator();
				while ( it.MoveNext() ) {
					if ( it.Current == this ) {
						if ( it.MoveNext() ) {
							return it.Current;
						} else {
							return null;
						}
					}
				}
				return null;
#else
				for (int i = 0; i < this.Parent.Children.Count; i++)
				{
					XmlNode nd = this.Parent.Children[i];
					if (this.Equals(nd))
					{
						if (i < this.Parent.Children.Count - 1)
						{
							return this.Parent.Children[i + 1];
						}
						else
						{
							return null;
						}
					}
				}
				return null;
#endif
			}
		}

こんな感じでいちいち探索しているのでスピード的には遅いのですが、用途は足りるのでOKとします。
最初は for 文で書いておいて、後から GetEnumerator で書き直してありますが…多分スピードは変わらないでしょう。

■テストコードを書く

例によって、テストコードを書きます。昨日書いた XmlNode の構築メソッドチェーンを使って、ぽちぽちと移植。

	[TestClass]
	public class TestXmlNavi
	{
		[TestMethod]
		public void TestNormal()
		{
			var root = new XmlNode(&quot;root&quot;)
				.Node(&quot;person&quot;)
				.Node(&quot;name&quot;, &quot;masuda&quot;)
				.Root;

			// タグが検索できた場合
			var q = new XmlNavigator(root)
				.Where(n => n.TagName == &quot;name&quot;)
				.FirstOrDefault();
			Assert.AreEqual(&quot;masuda&quot;, q.Value);

			// タグが見つからなかった場合
			q = new XmlNavigator(root)
				.Where(n => n.TagName == &quot;error&quot;)
				.FirstOrDefault();
			Assert.AreEqual(null, q);

		}

		[TestMethod]
		public void TestNormal2()
		{
			var root = new XmlNode(&quot;root&quot;)
				.Node(&quot;person&quot;)
				.AddNode(&quot;name&quot;, &quot;masuda&quot;)
				.AddNode(&quot;name&quot;, &quot;yamada&quot;)
				.AddNode(&quot;name&quot;, &quot;yamasaki&quot;)
				.Root;

			// タグが検索できた場合
			var q = new XmlNavigator(root)
				.Where(n => n.TagName == &quot;name&quot;)
				;
			Assert.AreEqual(3, q.Count());
			Assert.AreEqual(&quot;masuda&quot;, q.First().Value);
		}

		[TestMethod]
		public void TestNormal3()
		{
			var root = new XmlNode(&quot;root&quot;)
				.Node(&quot;person&quot;).AddAttr(&quot;id&quot;, &quot;1&quot;)
					.AddNode(&quot;name&quot;, &quot;masuda&quot;)
					.AddNode(&quot;age&quot;, &quot;44&quot;)
				.Parent
				.Node(&quot;person&quot;).AddAttr(&quot;id&quot;, &quot;2&quot;)
					.AddNode(&quot;name&quot;, &quot;yamada&quot;)
					.AddNode(&quot;age&quot;, &quot;20&quot;)
				.Parent
				.Node(&quot;person&quot;).AddAttr(&quot;id&quot;, &quot;3&quot;)
					.AddNode(&quot;name&quot;, &quot;tanaka&quot;)
					.AddNode(&quot;age&quot;, &quot;10&quot;)
				.Root;

			// タグが検索できた場合
			var q = new XmlNavigator(root)
				.Where(n => n.Attrs[&quot;id&quot;] == &quot;2&quot;)
				.FirstOrDefault();
			Assert.AreEqual(&quot;person&quot;, q.TagName);
			// ExDoc記述
			Assert.AreEqual(&quot;yamada&quot;, q / &quot;name&quot;);
			Assert.AreEqual(&quot;20&quot;, q / &quot;age&quot;);
		}

		[TestMethod]
		public void TestNormal4()
		{
			var root = new XmlNode(&quot;root&quot;)
				.Node(&quot;person&quot;).AddAttr(&quot;id&quot;, &quot;1&quot;)
					.AddNode(&quot;name&quot;, &quot;masuda&quot;)
					.AddNode(&quot;age&quot;, &quot;44&quot;)
				.Parent
				.Node(&quot;person&quot;).AddAttr(&quot;id&quot;, &quot;2&quot;)
					.AddNode(&quot;name&quot;, &quot;yamada&quot;)
					.AddNode(&quot;age&quot;, &quot;20&quot;)
				.Parent
				.Node(&quot;person&quot;).AddAttr(&quot;id&quot;, &quot;3&quot;)
					.AddNode(&quot;name&quot;, &quot;tanaka&quot;)
					.AddNode(&quot;age&quot;, &quot;10&quot;)
				.Root;

			// クエリ文にしてみる
			var q = from n in new XmlNavigator(root)
					where n.Attrs[&quot;id&quot;] == &quot;2&quot;
					select n;

			Assert.AreEqual(&quot;person&quot;, q.First());
			// ExDoc記述
			Assert.AreEqual(&quot;yamada&quot;, q.First() / &quot;name&quot;);
			Assert.AreEqual(&quot;20&quot;, q.First() / &quot;age&quot;);
		}

		[TestMethod]
		public void TestNormal5()
		{
			var root = new XmlNode(&quot;root&quot;)
				.Node(&quot;person&quot;).AddAttr(&quot;id&quot;, &quot;1&quot;)
					.AddNode(&quot;name&quot;, &quot;masuda&quot;)
					.AddNode(&quot;age&quot;, &quot;44&quot;)
				.Parent
				.Node(&quot;person&quot;).AddAttr(&quot;id&quot;, &quot;2&quot;)
					.AddNode(&quot;name&quot;, &quot;yamada&quot;)
					.AddNode(&quot;age&quot;, &quot;20&quot;)
				.Parent
				.Node(&quot;person&quot;).AddAttr(&quot;id&quot;, &quot;3&quot;)
					.AddNode(&quot;name&quot;, &quot;tanaka&quot;)
					.AddNode(&quot;age&quot;, &quot;10&quot;)
				.Root;

			// クエリ文にしてみる
			var nd = from n in new XmlNavigator(root)
					 where n % &quot;id&quot; == &quot;2&quot;
					 select n;
			XmlNode xn = nd.FirstOrDefault();

			// 本来は、以下のようにしたのだが暗黙キャストが作れない
			/*
			XmlNode xn = from n in new XmlNavigator(root)
						 where n % &quot;id&quot; == &quot;2&quot;
						 select n;
			*/

			Assert.AreEqual(&quot;person&quot;, xn);
			// ExDoc記述
			Assert.AreEqual(&quot;yamada&quot;, xn / &quot;name&quot;);
			Assert.AreEqual(&quot;20&quot;, xn / &quot;age&quot;);
		}
	}

TestNormal3 あたりからある Assert.AreEqual(“yamada”, q / “name”); は、子要素から「name」タグを取得するという ExDoc の文法です…つーか、operator の多重定義。
XmlNode クラスに以下の operator を追加しておきます。

	#region sweet operator 
	public static XmlNode operator / ( XmlNode node, string tag ) 
	{
		var nd = node.Children.Find( n => n.TagName == tag ) ;
		return nd ?? XmlNode.Empty ;
	}
	public static implicit operator string(XmlNode node)
	{
		return node.Value;
	}
	public static string operator %(XmlNode node, string key)
	{
		return node.Attrs[key];
	}

ExDoc を作ったときは等価オペレータ(==演算子)も多重定義していましたが、今回は LINQ に任せるので、/演算子と%演算子だけを多重定義します。
TestNormal5 メソッドの暗黙のキャストはうまく定義できないので、断念してます。何か逃れることはできないかと、後でチェック。

■できあがったコード

XmlNavigator クラスは最初に示した通りです。XmlNode クラスは多少手を加えてテストはOKということで。
IEnumerator<XmlNode>.MoveNext さえきっちりと実装していけば、LINQ 文法で動くのでかなりコーディングが楽です。

■IEnumerable<XmlNode> からの暗黙のキャストが作れない

ExDoc で作ったように、LINQ の結果を XmlNode で直接受けたい(暗黙のキャストを受けたい)のですが、これが実装できません。

	XmlNode xn = from n in new XmlNavigator(root)
				 where n % "id" == "2"
				 select n;

下記のように作るのですが、

class XmlNode {
...
	public static implicit operator XmlNode(IEnumerable<XmlNode> lst)
	{
		return lst.FirstOrDefault() ?? XmlNode.Empty;
	}
}

コンパイルするとこんなエラーが。

エラー	1	'Moonmile.XmlDom.XmlNode.implicit operator Moonmile.XmlDom.XmlNode(System.Collections.Generic.IEnumerable<Moonmile.XmlDom.XmlNode>)': インターフェイスとの間におけるユーザー定義の変換は許可されていません。

ってな具合で、インターフェースからキャストはできないそうです。うーむ残念。
なんかいい方法はないですかねぇ。多分、ToList メソッドのように拡張メソッドをつければいいんでしょうが

	XmlNode xn = (from n in new XmlNavigator(root)
				 where n % "id" == "2"
				 select n ).ToXmlNode();

のように折角のクエリ文法に括弧を入れないと駄目なのでいまいち過ぎ。
まあ、メソッド的に

	var xn = new XmlNavigator(root)
		.Where(n => n % &quot;id&quot; == &quot;2&quot;)
		.FirstOrDefault();

と書くと、ひとつの XmlNode が取れるんので十分なんですけどね。後で考えますか。

カテゴリー: C# | [C#] XElement とは違う LINQ できる XmlNode を作成する はコメントを受け付けていません

[C#] XElement 風に XML を構築するクラスを作ってみる

困ったときにそのまま使える LINQ to XML コードサンプル 18 パターン [1/2] – Web/DB プログラミング徹底解説
http://keicode.com/dotnet/linq-to-xml-101.php
XElement クラス (System.Xml.Linq)
http://msdn.microsoft.com/ja-jp/library/system.xml.linq.xelement.aspx

を参考にしながら、XML をメソッドチェーンを使って構築するクラスを作ってみます。まぁ、HtmlDom を作るときの副産物…と言うか、練習問題的に。

■使い方サンプルを作る

TDD と UIDD のハイブリッドプロセス(という命名をしておく)、ということで使い方のサンプルを先に作ります。
これを先に作っておくと、ある程度のマニュアルがなくても使える「感覚的に正しい」プログラミングコードができあがります。D.A.ノーマンの言う「ユーザーインターフェース」ってやつです。

public void test1()
{
	var root = new XmlNode("root")
		.Node("person")
		.Node("name", "masuda")
		.Root;

	var root2 = new XmlNode("root")
		.Node("person")
		.AddNode("name", "masuda")
		.AddNode("age", "44")
		.AddNode("address", "itabashi-ku")
		.Root;

	var root3 = new XmlNode("root")
		.Node("person")
		.AddAttr("name", "masuda")
		.AddAttr("age", "44")
		.SetValue("masuda tomoaki")
		.Root;


	var root4 = new XmlNode("persons")
		.Node("person").AddAttr("id", "1")
			.AddNode("name", "masuda tomoaki")
			.AddNode("age", "44")
		.Parent
		.Node("person").AddAttr("id", "2")
			.AddNode("name", "yamada taro")
			.AddNode("age", "20")
		.Root;
}

実は、.Parent と .Root は後付、SetValue はやむなくって感じですね。
最初に作ったのはこんな感じ。

	var root = new XmlNode("root")
		.Node("person")
		.Attr("name", "masuda")
		.Attr("age", "44")
		.Value("masuda tomoaki");

「動詞」を使わずに「名詞」だけで、繋げてみたかったのですが、Value プロパティと、Value メソッドが曖昧になってしまうのでパス。
あと、子ノードを連続追加する時に Node だけだと駄目で、Node.Parent.Node(“tag”) と変なことをしないといけないので、AddNode に変更。それに伴って、Attr も AddAttr に変更しています。

それと、最後に Root プロパティを入れたのは、テストコードを書いたときに、

	var root = new XmlNode("root");
	root.Node("person")
		.Attr("name", "masuda")
		.Attr("age", "44")
		.Value("masuda tomoaki");
    Assert.AreEqual("...", root.Xml );

のように「わざわざ」ルート要素を保存しないといけないという面倒があるので、ルートに戻すプロパティを用意します。

	var root = new XmlNode("root")
	    .Node("person")
		.Attr("name", "masuda")
		.Attr("age", "44")
		.Value("masuda tomoaki")
        .Root ;
    Assert.AreEqual("...", root.Xml );

こっちのほうがすっきりするし、後から意味が分かり易いのです。

■インターフェースに従って、コンパイルが通るようにコーディング

テストコードを先に書いてもいいのですが、Visual Studio のインテリセンスを利用したいので、コンパイルが通るまでのコードを書きます。
素のエディタで書くときは、逆にテストコードから書いたりします。と言うのも、素のエディタの場合は、テストコードのコンパイルエラーからメソッド/プロパティを追加することになるので、テストコードが先のほうがやり易いのです。
この違いは(私にとって)重要です。

■テストコードを書く

下記では、一気に追加していますが、実際にはひとつひとつ追加しています。
簡単なテストから複雑なテストへというのはテストの定番です。
実は Xml プロパティは、テストコードを書くときに追加しています。こうするとテストがやりやすいですからね。

	[TestMethod]
	public void TestNormal1()
	{
		var root = new XmlNode(&quot;root&quot;)
			.Node(&quot;person&quot;)
			.Node(&quot;name&quot;, &quot;masuda&quot;)
			.Root;

		Assert.AreEqual(&quot;<root><person><name>masuda</name></person></root>&quot;, root.Xml);
	}

	[TestMethod]
	public void TestNormal2()
	{
		var root = new XmlNode(&quot;root&quot;)
			.Node(&quot;person&quot;)
			.AddNode(&quot;name&quot;, &quot;masuda&quot;)
			.AddNode(&quot;age&quot;, &quot;44&quot;)
			.AddNode(&quot;address&quot;, &quot;itabashi-ku&quot;)
			.Root;

		Assert.AreEqual(&quot;<root><person><name>masuda</name><age>44</age><address>itabashi-ku</address></person></root>&quot;, root.Xml);
	}

	[TestMethod]
	public void TestNormal3()
	{
		var root = new XmlNode(&quot;root&quot;)
			.Node(&quot;person&quot;)
			.AddAttr(&quot;name&quot;, &quot;masuda&quot;)
			.AddAttr(&quot;age&quot;, &quot;44&quot;)
			.SetValue(&quot;masuda tomoaki&quot;)
			.Root;

		Assert.AreEqual(&quot;<root><person name=\&quot;masuda\&quot; age=\&quot;44\&quot;>masuda tomoaki</person></root>&quot;, root.Xml);
	}

	[TestMethod]
	public void TestNormal4()
	{
		var root = new XmlNode(&quot;persons&quot;)
			.Node(&quot;person&quot;).AddAttr(&quot;id&quot;, &quot;1&quot;)
				.AddNode(&quot;name&quot;, &quot;masuda tomoaki&quot;)
				.AddNode(&quot;age&quot;, &quot;44&quot;)
			.Parent
			.Node(&quot;person&quot;).AddAttr(&quot;id&quot;, &quot;2&quot;)
				.AddNode(&quot;name&quot;, &quot;yamada taro&quot;)
				.AddNode(&quot;age&quot;, &quot;20&quot;)
			.Root;
			;

		Assert.AreEqual(&quot;<person>&quot; + 
			&quot;<person id=\&quot;1\&quot;><name>masuda tomoaki</name><age>44</age></person>&quot; +
			&quot;<person id=\&quot;2\&quot;><name>tamada taro</name><age>20</age></person>&quot; +
			&quot;</persons>&quot;,
			root.Xml);
	}

■コードが完成品

でもって、テストを通るようにしたのが以下、150行程度のコードです。
Xml プロパティは、HtmlDom からコピペしているのでちょっと無駄なコードが入っていますが、まぁこの程度で書けるのだから C# って便利やなぁ、と。
これを HtmlDom に組み入れるかどうか思案中。

/// <summary>
/// XML node class
/// </summary>
public class XmlNode
{
	#region property
	public string TagName { get; set; }
	public string Value { get; set; }
	public XmlNodeCollection Children { get; set; }
	public XmlAttrCollection Attrs { get; set; }
	public XmlNode Parent { get; set; }
	#endregion

	#region constractor 
	public XmlNode(string tag, string value = &quot;&quot;)
	{
		this.TagName = tag;
		this.Value = value;
		this.Children = new XmlNodeCollection();
		this.Children.Parent = this;
		this.Attrs = new XmlAttrCollection();
	}
	#endregion

	#region creator for method chain
	public XmlNode Node(string tag, string value = &quot;&quot;)
	{
		var nd = new XmlNode(tag, value);
		this.Children.Add(nd);
		return nd;
	}
	public XmlNode AddNode(string tag, string value = &quot;&quot;)
	{
		this.Node(tag, value);
		return this;
	}

	public XmlNode AddAttr(string key, string value)
	{
		this.Attrs.Add(new XmlAttr(key, value));
		return this;
	}
	public XmlNode SetValue(string value)
	{
		this.Value = value;
		return this;
	}
	#endregion

	#region xml string serialize
	public string Xml
	{
		get
		{
                string s = &quot;&quot;;
                if (this.TagName == &quot;&quot; || this.TagName == &quot;#text&quot; || this.TagName == &quot;#comment&quot;)
                {
                    s = this.Value.Trim();
                }
                else
                {
                    s += string.Format(&quot;<{0}&quot;, this.TagName);
                    foreach (var at in this.Attrs)
                    {
                        s += string.Format(&quot; {0}=\&quot;{1}\&quot;&quot;, at.Key, at.Value);
                    }
                    if (this.Children.Count > 0)
                    {
                        s += &quot;>&quot;;
					s += this.Value;
                        foreach (var el in this.Children)
                        {
                            s += el.Xml;
                        }
                        s += string.Format(&quot;</{0}>&quot;, this.TagName);
                    }
				else if (this.Value != &quot;&quot;)
				{
					s += &quot;>&quot;;
					s += this.Value;
					s += string.Format(&quot;</{0}>&quot;, this.TagName);
				} 
				else 
                    {
                        s += &quot;/>&quot;;
                    }
                }
                return s;
		}
	}
	#endregion

	public XmlNode Root
	{
		get
		{
			XmlNode nd = this;
			while (nd.Parent != null)
			{
				nd = nd.Parent;
			}
			return nd;
		}
	}
}

/// <summary>
/// XML node collection
/// </summary>
public class XmlNodeCollection : List<XmlNode> {
	public XmlNode Parent { get; set; }
	public new XmlNode Add(XmlNode nd) 
	{
		base.Add(nd);
		nd.Parent = this.Parent;
		return nd;
	}

}

/// <summary>
/// XML attribute class
/// </summary>
public class XmlAttr
{
	public string Key { get; set; }
	public string Value { get; set; }

	public XmlAttr(string key, string value)
	{
		this.Key = key;
		this.Value = value;
	}
}
/// <summary>
/// XML Attribute collection
/// </summary>
public class XmlAttrCollection : List<XmlAttr> { }

カテゴリー: C# | [C#] XElement 風に XML を構築するクラスを作ってみる はコメントを受け付けていません

[C#] IEnumerable<> と IEnumerator<> を使って独自のリストを作ってみる

HtmlDom のイテレーターを作る前に、List<> で利用される LINQ がどう動くのかをチェック。
手始めには、IEnumerable と IEnumerator を使えばよいとのことなので、自前で作ってみます。

IEnumerable(T) インターフェイス (System.Collections.Generic)
http://msdn.microsoft.com/ja-jp/library/9eekhta0.aspx
IEnumerable.GetEnumerator メソッド (System.Collections)
http://msdn.microsoft.com/ja-jp/library/system.collections.ienumerable.getenumerator.aspx

MSDN 自体に既にサンプルコードが載っているのでそれを使っていきます。

■ランダムな値を返す RandomNum リストを作る

ランダムな値を返すならば、Random を使えばいいだけなのですが、まぁ、これを LINQ で使えるようにします。

foreach ( var i in rnd ) {
  Debug.Print( i );
}

なんてことをやると無限に繰り返してしまうようなモノですね(苦笑)。

	class RandomNum : IEnumerable<int>
	{
		int _max ;
		public RandomNum(int max)
		{
			_max = max;
		}

		public IEnumerator<int> GetEnumerator()
		{
			return new RandomNumEnum(_max);
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return new RandomNumEnum(_max);
		}
	}
	class RandomNumEnum : IEnumerator<int>
	{
		int _max;
		Random _rnd;
		int _cur;
		public RandomNumEnum(int max)
		{
			_max = max;
			_rnd = new Random();
		}
		public int Current
		{
			get { return _cur; }
		}

		public void Dispose()
		{
		}

		object System.Collections.IEnumerator.Current
		{
			get { return _cur; }
		}

		public bool MoveNext()
		{
			_cur = _rnd.Next(_max);
			return true;
		}

		public void Reset()
		{
			_rnd = new Random();
		}
	}

RandomNum が、List<int> にあたるコレクションで、RandomNumEnum がイテレーターです。ちょうど C++ の list<int> と list<int>::iterator の関係…でいいのかな? IEnumerable, IEnumerator で宣言されているメソッドは、右クリック「インターフェース実装」→「インターフェースの実装」を選択すると Visual Studio がひな形を作ってくれます。

これを利用する場合は、

private void button1_Click(object sender, EventArgs e)
{
	// 初期化
	RandomNum rnd = new RandomNum(100);
	// 最初の50件を取得
	listBox1.DataSource = rnd.Take(50).ToList();
}

private void button2_Click(object sender, EventArgs e)
{
	// 初期化
	RandomNum rnd = new RandomNum(100);
	// 10から20までの値を取得
	listBox1.DataSource = rnd.Where(x => 10 <= x && x <= 20)
		.Take(30).ToList();
}

上記のように、Take メソッドを使って最初の50件を取得します。foreach を使うと無限に取得できるので暴走するハズです。
また、where メソッドを使って条件を付けることもできます。あまり意味がありませんが。

実行するとこんな感じ。

普通にランダムに表示したのと変わりませんが、一気にランダムな値を作る時に便利かと(便利なのか?)

■サイクリックなコレクションを作る

なんとなくコレクションの使い方が分かったので、今度はサイクリックな配列を作ってみます。最後の要素の次が先頭になってぐるぐるとまわるだけのリストです。

ランダム値と同じように、IEnumerable<string> を実装するリストと、IEnumerator<string> を実装するイテレーターを作ります。

	class CyclicArray : IEnumerable<string>
	{
		List<string> _lst;
		public CyclicArray(List<string> lst)
		{
			_lst = lst;
		}

		public IEnumerator<string> GetEnumerator()
		{
			return new CyclicArrayEnum(_lst);
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return new CyclicArrayEnum(_lst);
		}

	}
	class CyclicArrayEnum : IEnumerator<string>
	{
		List<string> _lst;
		int _pos;
		public CyclicArrayEnum(List<string> lst)
		{
			_lst = lst;
			_pos = -1;
		}

		public string Current
		{
			get { return _lst[_pos] ; }
		}

		public void Dispose()
		{
		}

		object System.Collections.IEnumerator.Current
		{
			get { return _lst[_pos]; }
		}

		public bool MoveNext()
		{
			_pos++;
			if (_pos >= _lst.Count)
				_pos = 0;
			return true;
			throw new NotImplementedException();
		}

		public void Reset()
		{
			_pos = -1;
		}
	}

サイクリックなので、CyclicArrayEnum::MoveNext メソッドは必ず true を返します。これも foreach をすると無限大のパターンになるのですが、まあ良しとします。内部的には _pos で現在位置を保存しておいて、末尾に来ると先頭に戻すという処理を入れます。

private void button3_Click(object sender, EventArgs e)
{
	var lst = new List<string>() {
		&quot;1:masuda&quot;,
		&quot;2:tomoaki&quot;,
		&quot;3:yamasaki&quot;,
		&quot;4:yumi&quot;,
		&quot;5:kaho&quot;,
	};
	// 初期化
	var ca = new CyclicArray(lst);
	// 13件取得する
	listBox1.DataSource =
		ca.Take(13).ToList();
}

 

使う場合は、こんな風に List<string> を渡して、Take メソッドで一定数を取るというスタイルですね。
配列としては5つの要素しかないのですが、take で 13個を要求するとサイクリックに返してくれます。
簡単ですが、これでコレクションとイテレーターは作れそうなのが分かりました、と。

■ツリー構造を子孫まで巡って探すイテレーターはどのように作るのか?

普通のコレクションの場合は一段階しか要素を探しに行かないのですが、XML 構造のようにツリー構造になっていると再帰的に子要素を探す必要があるのです。

なので、これを擬似的に書いてみると…なところで詰まってしまったので後日。

カテゴリー: C# | [C#] IEnumerable<> と IEnumerator<> を使って独自のリストを作ってみる はコメントを受け付けていません

iPhone前とiPhone後、かつiPhone以外の「後」を考察する

常々「Windows Phoneには電話機能はいらない、WiFi に特化して軽量/バッテリーの持ちを考えて win アプリが導入できれば即売れる」と話したりしているのですが、サムスンと apple の訴訟で提示した apple がそれを示しているという意味で、

iPhone前とiPhone後・・・ | Blog!NOBON
http://blog.nobon.boo.jp/?eid=904179

私の場合、はじめての apple 製品が ipod で、その後が初代 iPad という、iPhone を無視した経緯があるので、かつての iPhone がどれほどいまいちだったのかは分かりませんが…少なくとも、iPhone の形状(全面に静電式タッチディスプレイ)という方式を貫いて、おらくそのまま消え去るであろう(十年後位にはなると思うのですが)というスタイルは、iPhone というスマートフォンの形状は「誕生と同時に完成形」であり「それ以上は進化しない」という特徴をもっています。

実は、使ってみると分かるのですが、

  • 電話としては非常に使いにくいです。
  • 従来のボタン式のゲームは使いづらいです。
  • 電子書籍を読むには画面が小さすぎます。

という不便さはありますが、それを上回るほどの

  • メモ帳などのアプリが、簡単に app store から入れられます。
  • タッチタイプのスマートフォン型のゲームがたくさんあります。
  • 書籍は「読む」のではなく「ブラウジング(流し読み)」します。
  • ツイッターやフェイスブックが何処でも使えます。
  • Apple というブランド性

という利便性(あるいは中毒性)があります。
私の場合、2年前にツイッターにハマった直後、iPhone に買い替え(実は、飛行機の中で携帯電話を落としてしまったので、やむなく iPhone 4 に買い替え。iPad は既に持っていた)という経緯もあるのです。携帯電話でツイッターをやるよりは、iPhone でツイッターを閲覧する方が確実に便利です。これはタッチタイプというペンがいらない方式かつ、ブラウジングではなく専用アプリによる動作のなめらかさが長所になるのですが、ボタン動作に関しては、docomo の java アプリで作れば同等の操作性を実現できるので(ボタンを押すという、従来?方式でもありますが)特に iPhone が良いという訳でもありません。

しかし、市場原理として(あるいは経営的な判断として)、「最初に売れているものを追う」というサムスン方式は、かつて日本の電機会社が行った手法でもあり、現在でも行われている経済的な手法(ランチェスターの法則)、あたり前の方法です。既に「高速道路」が引かれているわけですから、それを使って追えばいいわけです。
なので、サムスンが apple の市場を喰い、あるいはスマートフォンという市場を拡げてパイを apple と分割する、かつ apple がスマートフォンという市場(apple にとって、スマートフォンという市場は、iPhone 専用の市場です)を荒らされるあるいは市場が無法化するのを嫌って、サムスンを訴えるのも当然なことです。

まあ、そんな泥臭い経営的な判断はさておき、apple とサムスンがお互いに体力を消耗している間、私達は何をすれば良いでしょうか?ってことです。

■ガラケーの末路と将来性

日本のガラケー(フューチャーフォン)が iPhone に一掃されつつあるように見えますが、他国の多機能電話も一掃されつつあります。OS 的に、iOS, Android が席巻してくると symbian とか brew とか docomo の java とか、panasoinc の 独自 itron が一掃されつつあり、欧州の gms 系なんて何処いったんでしょう、っていう状態です。フィンランドの経済(福祉社会)を支えていた nokia が凋落すると国も危ういという現象は、サムスンが凋落すると韓国が危ういという状態と同じだったりします。まあ、サムスンの重役が国策に関与しているという点では、初期の原子力発電所建設に経済界が強くかかわっていた(今でも関わっている)という点と同じですね…まあ、それはともかく。

ワタクシとしては、スマートフォンの電話機能には辟易していて、それほど電話が掛けない私であっても時々普通の携帯電話が恋しくなります。これは、iPhone であれ、sumsong の galaxy であれ同じことです。その他の android 型スマートフォンも同じ。元々、電話機は「受話器」と本体の電送部分が分かれていて「受話器」には、聞く部分と話す部分が別々に「顔の形状」に合わせて作られていました。これは、現状のスマートフォンでは完全に無視されています。平たい液晶だから、という点もありますが、従来の携帯電話では、音声を聞くところと話すところがある程度マッチングするように作られています。折り畳み携帯の場合は液晶を守るという役目もありますが、ある程度曲がることができるので、マイクの位置を変更しやすいという利点があります。

が、この電話機能としてのデザインが無視されているのは、スマートフォンがそもそも電話としての機能を優先させていないという第一の理由があります。Apple としては、iPhone で電話をされたって一銭(1セント)も入ってきません。そこで思いついたのが、app store という方式です。これは mac os 自体にもかつてあった?のかどうか調べていないのですが、自社の store で直接「買わせる」ことによってマージンを取れるようにします。商品自体は、apple でも作りますが他の品ぞろえも多くしておきます。いわゆる「流通業」に参入した訳です。
実は、流通に関しては docomo も au もやってはいたのですが、いまいち盲点/乗り気ではないような気がしますね。アプリを作るために docomo の java を公開したものの、公式や野良アプリが混在してしまって「流通業」には参入していません。docomo の場合は、アプリ販売からのマージンよりも、電話することよる課金が多いので、そちらに流れていたという現状もあります。今後の docomo の戦略としても「電話を掛けさせる、データを流れさせる」という方向になるはずです。

まあ、そんな会社の思惑とは別に、スマートフォンではない携帯、主に電話機能に特化かつ iPad などのタブレットとうまく融合するものを考えてみると、

  • 黒井白子が使っていた細長いペンシル型の携帯電話でもOK
  • au が出していた骨伝導を利用したイアフォン型の電話機のミニ版でもOK

な具合にタッチタイプの液晶部分を外してしまいます。iPod nano がミニサイズして行ったと同じように「電話機能」のデザインを重視して、それだけを残して他を排除します。

  • タブレットPC、WiFi専用スマートフォン?、に Wifi 機能を提供するディザリング機能(ルータ機能)

確かにタブレットPCに3GSなどの通信機能が付いていれば、便利には違いないのですが、「定期的な」通信料が掛かりすぎます。ユーザーにとっては、携帯電話のみに通信機能があって、それ以外は wifi/無線lan/bluetooth で ok です。現在のように機能が重なってしまった、スマートフォンと携帯電話を2台持つぐらいならば、

  • ルータ機能付きのミニ携帯電話
  • 大きな画面を持つ軽いタブレットPC

の組み合わせのほうが良いでしょう。あるいは、ジャックを付けて、タブレットPC に従来式のモデム機能を提供してもよいのです。

ただし、これは docomo のような回線を持っている会社は売り出しませんし、Apple や sumsong のように既にスマートフォンを持っている会社は出さないでしょう。なので、活路としては、windows phone なんですけどね…という訳で、最初の「windows phone に phone 機能を外せば」に戻るのです。

■タブレットPC の将来

私は、iPhone とは別に wifi-pockcet を持っています。wifi のみの ipad, ノートPC を活用するためには、やっぱり無線LAN 専用の機器があったほうがよいのです。また、何人か集まったときには無線LANの貸し出しもできるので、営業的にスムースです。

さて、電子書籍とタブレットPCという二つの市場がありますが、重量の問題が解決されれば、この市場はもうちょっとスタイルが変わってきます。

  • ほぼ使い捨てにできる、軽量な e-ink な電子書籍
  • ゲームアプリを導入が簡単なタブレットPC

という二分割ですね。どちらも、重量は200g程度が望ましいのですが、普通の文庫本や漫画本の代用にするならば、電子書籍は 180g よりも軽くしてほしいが、ゲーム中心となるタブレットPC は、PSP 程度(280g)程度で良いのです。e-ink の場合はダウンロードと画面の切り替え時以外、電気を喰わないのでバッテリー容積を少なくすることと、先の通信機能を wifi 化してしまうことにより更に軽量化あるいは「使い捨て」化が可能です。使い捨てにしなくても「リサイクル」化ができれば ok です。
「使い捨て」の e-ink を希望するのは、落として壊してしまって惜しくない価格帯であってほしいのです。プレゼント的に配布できるとか、
街中で100冊位の入れ込みで売れるとか、そういう従来の「本」感覚で売れる/読める感覚が欲しいのです。

amazon や kobo 社(楽天ではないよ)、はこのあたりを目指してほしいですよね。通信機能なんかは街の本屋さんで wifi 購入でもよいので、3gs の機能は不要なんですよ、本来は。

タブレットPC の場合は、ノートパソコンに代用するパターンとゲーム機に代用のパターンがあります。ノートパソコンの代用のパターンは、microsoft の surface のようにキーボードを別に用意します。iPad でもそうなのですが、ソフトウェアキーボードを出して執筆というのはほぼ難しいです。入力が面倒だから結局ノートパソコンを持ち歩いてしまいます。
しかし、ゲーム機の代用(あるいは後継)であったり、業務用のタブレットPCの後継(コンビニで使っている商品照会だとか、デパートでおいてある商品紹介の iPad だとか)はかなり様相が違います。

ペンタッチ式の業務アプリは windows xp のタブレット時代からあり、今も使われています。ちなみに、居酒屋のチェーン店では palm os がまだ使われていますよね。ペンタッチはそれなりに便利なのと、一度業務で作る/使うとなかなか次の段階には進みづらいのが、業界用アプリの特色です。
なので、タブレット市場にいくつかのセグメントがあるのです。

  • ゲーム機の後継/代替としてのタブレットPC
  • ノートパソコンの代替としてのタブレットPC
  • 業務タブレットの後継としてのタブレットPC

先に書いた通り、実はノートパソコンの大体にタブレットPCは難しいのです、確かにソフトウェアキーボードで打てないこともないのですが、タブレットの画面を半分以上覆ってしまうのは、ちょっと文章が見えなくて書ききれません。こうなると専用のキーボードが欲しいところで、そうなると単なるタブレット機能がある画面になってしまいます(それでも使い方はいろいろなんですが)。

ゲーム機の後継としては、iPad も microsoft の sufrace も同じです。iPad の場合は OpenGL で画面を作ることになりますが、windows 8 の場合は directX ですね。このあたりの「作り易さ/作り難さ」はよくわかりません。デバッガ関係では、directX のほうが圧勝な気がするのですが。ここは、psp などの専用機も交えないと駄目ですからね。

ですが、私の本来の狙い所は「業務タブレットの後継」です。実は、この分野を見ていくとレガシーなものをレガシーなままに使っているところもあり、新規技術を導入しているところもあり(顔認証とか)と様々です。レガシーなところは、windows xp タブレットの便利さです。vb6 がそのまま動くために、かつての vb6 アプリをタブレットPC でも動かせば、そのまま使えてしまうという利点があります。全画面で作ってしまえば、vb6 であろうと、.net であろうと変わりませんからね。画面操作のコンポーネントは旧文化オリエントのものを使ったりする訳です。

一方、新規技術のほうは、タブレット機能を使いたいがために、現在ではちょっと複雑になってしまっています。当時、液晶のタッチパネルが高かったので効果的に配置ができなかったのですが、安価なタッチタイプのタブレットPCが出てくれば、自動アプリ配信、リモートセッティングなどがやり易くなります。バックグラウンドに大きなPCを隠す必要もない(大抵は柱に隠してあります)。タッチタイプの液晶が壊れれば、機器ごと変えることもできるし、無線LAN が標準で付いているので有線LANケーブルの設定も必要ありません。これはかなりメンテナンスフリーな配置ができるわけです。

そんな訳で、私としは、

  • 単機能かつミニサイズの携帯電話(ルータ機能あり)
  • 使い捨てに近い軽量 e-ink タブレット
  • wifi 機能のみの軽量タブレットPC

を強く望むし将来的にはそうありたいなと。まぁ、通信市場としての docomo, au、スマートフォン市場としての apple, sumsong の攻防/思惑があるわけですんなりとはいかないでしょうが、なんども言いますが、windows phone に phone 機能が無ければ買うのにッ!!!

カテゴリー: 雑談 | iPhone前とiPhone後、かつiPhone以外の「後」を考察する はコメントを受け付けていません

[C#] 型推論と暗黙のキャスト(implicit)を組み合わせる

Microsoft、「Metroスタイル」改称へ――米メディア報道 – ITmedia ニュース
http://www.itmedia.co.jp/news/articles/1208/03/news053.html

Metro Style という用語が消えるそうだけど「メトロスタイル」の可能性が低くなるのであれば、それはそれで歓迎かと。某メトロ本を書く前の文章練習として、少し書きつけます。

C# の var は、型推論という形で C# の型をコンパイラが判定するという機能で、宣言部のコードが短くなるということと、LINQ のクエリを受けるときに小難しい…というか記述不可能な書き方をしなくてよいのが大きな利点です。

CPoint pt = new CPoint();

のかわりに

var pt = new CPoint();

と書くわけです。これ、VB で書くと、

Dim pt As New CPoint()

なので、宣言部だけだとあまり意味がないのですが、

var query = from t in Table where t.name = “masuda” select t ;

のように LINQ のクエリを受ける場合は「ほぼ必須」ですよね。「ほぼ」っていうのは、IEmutable か IQuerable を使えばなんとか可能なのですが、インテリセンスの助けがないと確実に無理ッ!!! 素のテキストエディタを使う場合は「var」が必須になります。

C# は C++ の型の書き方を継承しているので、宣言をするときに、あたかも型を2つ書かないといけないという不便さが際立つのですが(VB だと1回だけでいいですよね)、もともと C++ の場合は、

CPoint pt ;

CPoint *pt = new CPoint();

といった異なる書き方をするので、必ずしも型が重複しているという訳ではありません。先のほうは内部変数でスコープを抜ける時にデストラクタが呼ばれる(自動的に解放される)ので、普通は上記の書き方を多用します。C# の場合は、using を使って

using ( CPoint pt = new CPoint() ) { … }

のようにスコープを決めることもできるのですが、なんかいまいちです。コーディングの経済性からいえばタイピングの少ない方に流れがちなので、私の場合は using はあまり使っていません。明示的に画像関係(Bitmapなど)を扱う時ぐらいです。

■暗黙のキャスト(implicit)

型推論と反対の発想(と私は思っている)ものに、暗黙のキャストがあります。型推論は、コンパイラが構文(関数の戻り値や別の型)から一意の型を決めますが、キャストはコードを書いている人=プログラマが型を決定します。余談ですが、F# の場合は型を推論すると同時に、’a という anonymous な型が用意されています。これはテンプレート/generic の T と同じように働くのですが、表面上は型がその場所で規定されているのか、呼び出し側で既定されているのかを区別しません。C++ や C# の場合は CPoint() のように「<>」を使って「不定の型」であることを示すところが大きな違いと感じています。ちなみに、VB の場合は「CPoint(Of Integer)()」となって実に奇妙な感じなのです。

LINQ や XML を扱っていると、ツリー構造なりテーブル構造が単数と複数の組み合わせであることがわかってきます。LINQ は、プログラムコードの中から for/while 文を排除していく故に複数行のコードがワンライナーで記述できる(数式に近くなる)という利点があるのですが、単数と複数の要素を暗黙のキャストを使って使い分けると、.FirstOrDefault や [0] とか != null を減らすことができます。

一例を示すと HtmlDom では、HTML タグに関して、HtmlElement という単数の要素と、HtmlElementCollection という複数の要素≒リストを用意しています。

class HtmlElementCollection : List<HtmlElement> { … }

ツリー構造なので、HtmlElement は、子要素として HtmlElementCollection Children というプロパティを持っています。

さて、とある要素に div という子要素があるかどうかを調べるのには、LINQ を使うと次のように書けるように作ります。

# @neuecc さんより
# LINQ to random thoughts – NyaRuRuの日記
# http://d.hatena.ne.jp/NyaRuRu/20080205/p1
# Any を使えという指摘が。そりゃそうだ。 Count すると全件検索してしまうがな orz

if ( root.Children.Any( n => n.TagName == “div” ) ) {

要素があるかどうかを Anyメソッドで調べるのですが、大抵の場合は「子要素があれば最初の要素がほしい」というのが普通です。そうなると、.First メソッドを使って、

var el = root.Children.First( n => n.TagName == “div” );

ってことになるのですが、子要素がない場合は、el の値は null になってしまいます。こうなると != null が必要になってちょっと面倒です。なので、.FirstOrDefault というメソッドを使って次のように書き換えます。

var el = root.Children.FirstOrDefault( n => n.TagName == “div” );

さて、ここで div は最初のひとつだけということにしてましたが、最後のひとつを取りたい時はどうするのでしょうか? うまい具合に Last メソッドや LastOrDefault メソッドがあるのでこれを活用します…ってことにヘルプ的にはなるのですが、こうやっていくと組み合わせ的にメソッド数が増えてしまいます。

ならば、

var el = root.Children.Where( n => n.TagName == “div” ).First() ;

のようにどうせ LINQ は遅延評価なのだから、Where で検索した後に First だけを持ってくるのが素直では?と思ってしまうわけです。まあ、実際にこういう書き方もできるわけで。そこで、暗黙のキャストを使って、First 自体も消してしまおうというのが、HtmlDom の発想(元ネタは ExDoc)です。

HtmlElement el = root.Children.Where( n => n.TagName == “div” ); // 最初の要素を取得

HtmlElementCollection els = root.Children.Where( n => n.TagName == “div” ); // 複数の要素を取得

のように、あえて「型」を記述させて「最初の要素(単数の要素)」なのか「リスト(複数の要素)」なのかをプログラマが決めます。Where メソッド自体はリストを返すのですが、HtmlElementCollection から HtmlElement への自動的なキャストを許すことで、こんな書き方ができます。

public class HtmlElementCollection :  List<HtmlElement>
{
    public static implicit operator HtmlElement(HtmlElementCollection col)
    {
        if (col.Count > 0)
        {
            return col[0];
        }
        else
        {
            return HtmlElement.EmptyElement;
        }
    }

遅延実行ではありませんが、上記の形で簡単に暗黙のキャストが実装できます。HtmlElementCollection から HtmlElement にキャストした時に、要素があれば先頭の要素を、要素がなければ EmptyElement という空の要素を返します。これは、.FirstOrDefault というメソッドと同じ動作をします。空であるかどうかは、null と比較するのではなく、IsEmpty というプロパティを使うわけです。

こうすることで、先の el という変数は null になりません。null にはならないので、el.IsEmpty や、el.Children.Where という書き方を続けてできます。Where メソッドを直列にする書き方は、LINQ でも同じで常に IEmutable というコレクションもどき(コレクションに見えるが、遅延実行されるので、内部的には実行メソッドの順序だけを保持しているという点で「もどき」)を使うことで実現されています。もう少し文法的に煮詰めたら HtmlDom もそうする予定です。

■要素を探すというコードが大半であるという現実

XML や HTML を扱う場合には「指定した id を持つ要素を探す」というコードが頻繁に発生します。ElementById というメソッドで、要素を探して値を取得したり設定したりします。for/while の場合は、2つの動作があって「要素に対して同じ操作を繰り返す」というのと「要素を探し出して、何か操作して終わる」というのが頻繁に発生します。

サンプルを書いて考えてみると、

□複数の要素を一気に更新

foreach ( var el in root.Children ) {
  if ( el.TagName == &quot;div&quot; ) {
    el.Value = &quot;xxx&quot;;
  }
}

var q = root.Children.Where( n => n.TagName == &quot;div&quot; );
// LINQ の場合、連続して更新ってどうやるんだっけ?
foreach ( var el in q ) {
  el.Value = &quot;xxx&quot;;
}

// HtmlDom の場合
root.Children.Where( n => n.TagName == &quot;div&quot; ).Value = “xxx”;

□ひとつの要素を見つけて更新

foreach ( var el in root.Children ) {
  if ( el.Attribute(&quot;id&quot;) == &quot;m1&quot; ) {
    el.Value = &quot;xxx&quot;;
    break;
  }
}

// LINQ の場合
root.Children.Where( n => n.Attribute(&quot;id&quot;) == &quot;m1&quot; ).First().Value = &quot;xxx&quot;;

// HtmlDom の場合
root.Children.Where( n => n.Attribute(&quot;id&quot;) == &quot;m1&quot; ).Value = &quot;xxx&quot;;

上記のように HtmlDom では使って単数/複数を同じように扱うことができます…と云いますか、LINQ 構文内では、HtmlElementCollection として扱い、同じことが HtmlElement にも適用できる、という「見かけ上」の問題を解決しています。単数のほうは、正確に書くと HtmlElement にキャストすることになるので、

((HtmlElement)root.Children.Where( n => n.Attribute(&quot;id&quot;) == &quot;m1&quot; )).Value = &quot;xxx&quot;;

ということになるのですが、動作としては「id=”m1”」である要素は、「全体の1つしかない」という「仕様」を前提にしてコーディングをするので、複数あるときはイリーガルな訳で無駄なのです。逆に、データの中にひとつもないというのもイリーガルなわけで、これらの処理は正確には「例外」として処理する必要があるのですが、コーディングとしては面倒だし「忘れ」そうですよね。そうそう、この「忘れる/漏れる」というヒューマンエラーを回避するためにも、単数/複数/null を一括して扱う implicit な訳です。安全側に倒れるという安全管理の手法です。データが多少イリーガルでもプログラムは落ちずになんとなく正常に動くというパターンです。

そんな訳で、LINQ の文法である from/where/select を活用して、同時に暗黙のキャストと組み合わせている HtmlDom ですが、公開はもうちょっと先かなぁと。遅延実行の実装がよくわからいのが本音で、もうちょっと LINQ の実装をながめなくては。

neuecc

カテゴリー: C# | 1件のコメント

Raspbarry PI が届いたよ

船便でやっとこさ届いた Raspbarry PI です。

IMG_0370

Raspberry Pi(ラズベリーパイ)
http://jp.rs-online.com/web/generalDisplay.html?id=raspberrypi

いやいや、船便で時間が掛かっているうちに日本でも手に入れるのが楽になってしまいました orz 値段的には、ドルでも円でも同じみたいです。$35 プラス船便代だったので、日本で買ったほうが安いかと。いや、今は品切れなので、それなりには早いのかな?

IMG_0371

電源は、microUSB というちょっと特殊なものなのですが、通常のUSBからの電源供給は上のような100円ショップであるもので使えます(動作確認済み)。2種類あるので、薄い方を買えばOKです。まぁ、間違っても100円なのでよいかと。

私の場合、Raspberry PI の本体よりも、HDMI 出力の方が問題だったりして、液晶モニタが古いタイプだと HDMI のコネクタがなかったりします。Raspberry PI のコネクタは「タイプA」なので、通常のオーディオ用の「タイプA」を買ってきました。

早速、microUSB を普通のPC から繋げて、

  • 無線LANのマウス
  • 有線LANケーブル(raspberry pi は wifi が入っていないので)
  • USB キーボード

を入れると、おなじみの linux のブートログが表示されたのち、

IMG_0372

ログインしてから、startx でデスクトップが表示できます。ここでマウスも使えるのでOK.

IMG_0373

キーボードは、ASCII キーボードしか対応していない(多分、設定を切り替えればOKかと)ので記号が打ちにくいですが、こんな風にブラウザも表示できます。

IMG_0374

GUI はかなりもっさりと動きますが、これはメモリが少ないしGPUのパワー不足かと。そもそもが高機能linuxを目指している訳ではないので、これで十分です。

ディスプレイは要らないので、terminal で入れるようにポートを空けて、telnet で入れるようにすればokかなと。基本はコマンドラインで何かやるので。

シャットダウンは普通にshutdownコマンドを使えばOK。まぁ、microUSB を抜いてしまっても、SDメモリなので壊れる心配は少ないかと。

カテゴリー: 雑談 | Raspbarry PI が届いたよ はコメントを受け付けていません

Visual Studio 2012 の発売は 9/12、Windows 8 は 10/26

Microsoft、「Visual Studio 2012」と「.NET 4.5」の正式版は9月12日と発表 – ITmedia ニュース
http://www.itmedia.co.jp/news/articles/1208/02/news022.html
Windows 8がRTMに 開発者は8月15日から入手可能 – ITmedia ニュース
http://www.itmedia.co.jp/news/articles/1208/02/news019.html
「Windows 8」の発売は10月26日 「Surface for Windows RT」も同日発売 – ITmedia ニュース
http://www.itmedia.co.jp/news/articles/1207/19/news019.html

Windows 8 RTM, Visual Studio 2012 RTM が 8/15 の「敗戦記念日」(米国にしたら戦勝記念日か?)というのはさておき、Winodws 8 本を書くにあたっては、正式版を待っての画面キャプチャとなるので、大まかな画面キャプチャは RTM で済ませていおいて 10/26 を待って画面を確認、というスケジュールですかね? > 某編集さま。

Visual Studio 2012 自体は、Windows 7 にもインストール可能なのですが、今回の書籍のメインターゲットとなる metro style application は Windows 8 での開発のみとなるので(シミュレータが自分へのリモートデスクトップという事実もあるし)、となると Windows 8 + Visual Studio 2012 の組み合わせで画面操作をチェック、ということになりますね。

あわせて、Windows Store への登録準備、と言いますか実験的な登録だけがあるので(例によって、Windows Phone Store への登録はしませんw)、審査が通過するサンプルアプリを作らないといけないという微妙な感じなのですが…まぁ、徐々に準備をしてきましょう > 某勉強会のメンバさま

で、私は RTM の頃は何をしているかというと、、、MFC+Fortran と格闘して、傍らに mac mini だったりする訳ですよ。とほほ?

カテゴリー: 雑談 | Visual Studio 2012 の発売は 9/12、Windows 8 は 10/26 はコメントを受け付けていません

さよならもいわずに

このブログには日常の事を書かないことにしているのだが、本屋で上野顕太郎「さよならもいわずに」を見つけて記録を残しておくのも供養かと思い、1時間ほど書き下してみることにする。

24日に父(享年75歳)が死んだのは、2年前から始まっていた病院生活の終わりであった。7月頭に既に死を覚悟する旨を医者より聞かされ、先行きがないことにより直前の酸素吸入や輸血をしないことを決めたものの、直前に死の前に孫の顔を見せようかと札幌に行った時のことでもあり、1日前に苦しみながら息をする姿に今からでも輸血をすればとも思ったものの、時は遅く、いや実際には輸血をしたところで死が2週間ほど延びるにすぎず、最後は急性肺炎のため息ができなくなり、24日の夕方に臨終ということになる。
孫の顔を見せるという、という名目であるものの、実は2年前の交通事故から意識がなく軸索損傷にて反応も少なく、医者には「反射」と呼ばれるようなモノを追う目をしていた父ではあったが、声を掛けるとこちらを向いたような気もし、誰かが来ると瞼を開けて負うような気もし、硬直しつつある腕や手を拡げようとすると痛がり、顎が割れてしまった口をあくびのために開け閉めするたびに痛い顔をし、意識に関係なく伸びる髭を電動剃刀で剃ろうとすると鼻の下を伸ばし少し剃りやすいように仕向けるような気もし、兎も角も「意識がない状態」が続いていて、全く喋らず、トイレにも立てず、手足も動かせず、少しだけ瞳を動かし、疲れると瞼を下げる動作をする父という状態は、「老いた父」でもなく「呆けてしまった父」でもなく、ただそこに目の前のモノに反応する赤ん坊のような父の姿になったとも言えた。
そのような期間が2年間続いた。仕事場が東京にあるという場所がら、札幌に頻繁に帰ることはままならなかったが、下の子が生まれ、その1か月後に交通事故があり、幼い子を妻に任せて(妻自身の産後という大変な時期であった)2週間ほど北大の病院に母と通い詰めた。
タクシーの中で「ひょっとしたら、即死のほうが良かったかもしれない」と恨み言を言うかもしれないと、弟を怒鳴ってしまったしことは、実は楽観的な事実でしかなく、実際はモノ言わぬ状態だけが残された。
病状は重く、いや、足の骨折と内臓破裂と顎の骨折、あばら骨の骨折と、即死に近いものがあったものの、幸いにして足を切断することもなく、内蔵破裂で大量出血死することもなく深夜の長い手術と救急病棟の1週間があったものの「死」は免れた。しかし、意識は戻らなかった。いや、当時は意識が戻るものと思い、1ヶ月、孫の運動会のテープや声掛け、日に15分の面会を続けたのだが、意識は戻らなかった。
頭をひどく回転させると、脳と体を繋ぐ神経が切れる現象が発生し、脳が孤立状態になる。これが軸索損傷で、外部の世界から完全に遮断されてしまうとのこと。痛みも音も何も感じない世界に置かれ、全く眠った状態と医者には説明を受けたものの、瞼をうっすらと代えてこちらをきょろきょろと見る姿は、何かを探しているような感じがして、意識がないとは思えなかった。いや、脳神経が少しずつ回復する、回復しないにせよ別の回路が代用するという生命力があればこそ、事故直後のうつろな目の動きと、2年後の少しはっきりとした目の動きは、何か見ているような感じがした。手も指も顔も動かすことは叶わなかったが、俗な言葉を使えば「心」は残っていたのであろう。

だから、2年間の危篤状態の末に、最期に急性肺炎で亡くなったという言い方が正しい。意識がない状態では、嚥下もままならず流動食は口の管から、鼻の管から、最後には太腿の血管にそそぐことになった。喉を切開する時も、意識が戻った時に声が出ないのではないかと心配したものの無用であったし、2か月程の指や腕、全身のリハビリや針治療を繰り返したものの結論だけ見れば全ては無用だったと言える。ただ、父にとって死を引き延ばしてしまったのか、最期の自らの体が動かない状態を無用に長く続けさせてしまったのかと疑問に思うこともたびたびあったが、気管切開をして酸素吸入をやめてもなお自力で息をし、生来の健康性から(常備薬はなかった)心臓は強く打ち、切り開いたお腹の傷も治り、骨折した足の骨がついた生命力をみると、なんらかの「意志」があったのだから、意志に沿うだけと思ったものだ。だからこそ、延命処置としかならない輸血は断ることにした。実は2月に輸血を行っている。1回だけは延ばしてあげたいという想いと、後悔なく試しておきたいという思いがあったものの、それから1週間ほどは高熱が続いたそうである。治るのであればそれでよいものの、治らないとなれば、意識がないとすれば「延命」に何の意味があるのかと疑問になったが、それは「私達の時間」を確保するためだったと言える。交通事故の直後、即死という形で父を失っていた場合はどうなっただろうか。いや、交通事故で即死する人も多いのだが(実際北大には交通事故で担ぎ込まれる患者も多く、それなりの数に方が亡くなっていた)、私の場合はそれは結果的に、何かを納得するための「時間」となった。

「さよならもいわずに」を読むと、階下で即死をしていた(心臓発作とうことになるのだろう)キホさんの姿が描かれる。配偶者を亡くしたときの愕然さと、通夜、葬儀の慌ただしさ、親戚の出入り、そしてそれでも仕事/生活があるという姿が描かれる。これを、父の死の前に読んだらどうなったのかとも思ったのだが、いや、いま本屋に入って手に取り買ってファミレスでパンケーキを食べながら読むという行為は「タイミング」と「感情移入」に尽きると思う。

癌で死ぬ、老衰で死ぬ、体調が悪くてそれがもとで死ぬ、という姿のひとつに、交通事故の後「モノ」を言うこと無く長く続き死ぬというパターンが私には加わった。私にとって父の死という現象は、実は大学に行った頃に既にあったものの、奇しくも私の失態により4年程も前に幾度となく父に迷惑を掛ける機会ができてしまい、その後、なんとなく家族に戻った。家を出るという(大学で一人暮らしをするとか、就職で一人暮らしを始めるとか)時には、既に自分の中の父は死に面している。だから、父がどのように呆けようと、札幌で年老いていようと、実家で床に臥せることがあろうと、気にしないつもりでもありそういう態度を貫き通していたものの、現実は私の楽観的な予想を裏切り、突然の交通事故の後、意識不明の2年間、そして死という経緯に至ることになった。

現実というものは、かくも厳しいものかと思われるかもしれないが、いや、急性肺炎で息を引き取る臨終の場所に居合わせ、通夜、葬儀という慌ただしい現実が過ぎるなか、父の遺体の横に居ても、病院のベットの横にいる感覚しかなかった。良くも悪くも、意識がなく、喋らない父が其処にいるだけだった。ベットは棺に変わり、顔の周りには暑い熱気を避けるためにドライアイスを置いていたものの、何も変わらない。仮通夜、通夜、葬儀の中でお坊さんがお経をあげていても、気持ちはあまり変わらなかった。
が、火葬場に父を運び「焼」かれる1時間を待ち、鍵で開けて骨だけとなった姿を見ると、もう戻りはしないという納得だけが残された。後付けであるが、お経も葬儀もなんらかの儀式は、その時間の経過と納得の時間を引き延ばすための手段なのだろう。少なくとも私の場合は信仰よりも、引き延ばされた現実の時間だけが残っている。

通夜から初七日までの間、いくつかのプログラムと原稿用紙15枚程度の文章を書いて「仕事」をしてみた。死よりも生を優先させるのが良いのは、それは日常に立ち返らなくてはいけないからだ。
ひとつ、幸いなのは2年前の交通事故の後は、日常は「危篤付き」の電話とともにあったのだが、今後はそれがないということだ。母にとっては2年間ほぼ休みなしの見舞い(看護自体は病院がやってくれるので、行く必要はないのだが)から解放されるということと同時に、その日常が消えてしまうということだ。だから、父が消えたというぽっかりと抜けた穴は、実は2年前に起こり、そして2年間で埋められつつあった穴がもういちど無くなったのかもしれない。いや、覚悟をする時間があったから、その穴は比較的浅かったと思う。

1時間ほど書いたの終わりにしよう。
月並みながら自分が生まれたことに感謝し、目の前の仕事に取り組む。

カテゴリー: 雑談 | さよならもいわずに はコメントを受け付けていません

[C#] HTMLをLINQで扱えるようにする(前哨戦)

諸事情で作る必要はなくなったのだけど、ExDoc の延長戦上にあるし、ということでぼちぼちと。
主旨としては、

Html Agility Pack
http://htmlagilitypack.codeplex.com/

と似たようなものです。Html Agility Pack を使ったことがないので(あとで観察するけど)正しい違いはどうか分からないのですが、今作っているものは、

・内部的には XML の整形式を使う。
・子ノードも含めて、LINQ(where)を簡単に実行できる。
・ノードの更新(Update/Remove/Insert)が簡単にできる。

を目標に作成しています。使い方の想定としては、既存のホームページを HTML 形式で抜き取った後に、HTML の整形、id や class などの無駄な属性の削除、javascript や comment などの削除、がさくっとできるツールをつくための、内部機関といったところです。

■目的

HTML 形式は XML 整形式ではないので、タグの入れ子などがややこしいのですが、最終的に PHP などで扱う場合には整形式にしておくと parse が楽なのです。XML 系のツールも使いやすいですからね。なので、ExDoc を少し改造した形で、内部を XML として扱います。

XML や HTML の子孫ノードは「//h1/div」のように XPath 形式が一番楽なのです。ですが、これは C#/VB では扱いにくい。コンパイルが通らないからね。なので、これに似た形で構文を書けるようにします。最初は、自作の ExDoc 形式「doc * “h1” / “div”」を考えていたのですが、更新作業を考えるとかなり面倒なので、LINQ 方式で Where メソッドのチェーンで書けるようにします。

doc.Where( x => x.TagName == &quot;div&quot; && x.Id == &quot;m1&quot; ).Vallue = &quot;new message&quot;;

のような感じで、「<div id=”m1″></div>」の中身を「new message」に書き換えるパターンを、ワンライナーで書けるようにします。
無駄なタグを消したり、無駄な属性を消したりすることが多いことを考えて、HTML のタグを子孫ノードから直接見つけるようにします。何処にあるかわからないけど、ひとまず id を使って見つけられるという感じですね。

■手段

基本は、LINQ の where, select を使います。ただし、ツリー構造を追って探索するのは面倒なので、where メソッドをオーバーライドして子孫ノードまで探索するようにします。大抵は、class か id を使うのでこれで十分でしょう。

通常 where メソッドの戻り値は、リストか null になるのですが、プログラムが複雑になるので null は返さないようにします。そして、単数/複数を区別するのも嫌なので、HtmlElement あるいは HtmlElementCollection を返します。このあたりの制御は、ExDoc と同じように、暗黙のキャスト(implicit)を駆使します。

■仮実装

MSTest を使って、仮実装をします。基本的なメソッド名をだけを決めてテストコードを書いて実装、というテスト起動です。
ただし、最初の HtmlDocument を作るところだけは、実験を繰り返しながらブレークスルーを目指します。

namespace TestHtmlDom
{
    [TestClass]
    public class TestHtmlLinq
    {
        [TestMethod]
        public void TestTagName()
        {
            string html = @&quot;<body><h1>title</h1>message</body>&quot;;
            HtmlDocument doc = new HtmlDocument(html);

            // var q = doc.documentElement.Children.Where(n => n.TagName == &quot;h1&quot;);
            var q = doc.Where(n => n.TagName == &quot;h1&quot;);
            Assert.AreEqual(1, q.Count);
            Assert.AreEqual(&quot;title&quot;, q[0].Value);
        }

        [TestMethod]
        public void TestTagName2()
        {
            string html = @&quot;<body><h2>title1</h2>message<h2>title2</h2></body>&quot;;
            HtmlDocument doc = new HtmlDocument(html);

            var q = doc.Where(n => n.TagName == &quot;h2&quot;);
            Assert.AreEqual(2, q.Count);
            Assert.AreEqual(&quot;title1&quot;, q[0].Value);
            Assert.AreEqual(&quot;title2&quot;, q[1].Value);
        }

        [TestMethod]
        public void TestTagName3()
        {
            string html = @&quot;
<body>
    <h2>title1</h2>
        <span>message</span>
    <h2>title2</h2>
        <span>message2</span>
</body>
&quot;;
            HtmlDocument doc = new HtmlDocument(html);

            var q = doc.Where(n => n.TagName == &quot;span&quot;);
            Assert.AreEqual(2, q.Count);
            Assert.AreEqual(&quot;message&quot;, q[0].Value);
            Assert.AreEqual(&quot;message2&quot;, q[1].Value);
        }

        [TestMethod]
        public void TestUpdate1()
        {
            string html = @&quot;
<body>
    <h2>title1</h2>
        <span id='m1'>message</span>
    <h2>title2</h2>
        <span id='m2'>message2</span>
</body>
&quot;;
            HtmlDocument doc = new HtmlDocument(html);

            doc.Where(n => n.Attrs[&quot;id&quot;] == &quot;m2&quot;)
                .Update(n => n.Value = &quot;new message&quot;);

            var q = doc.Where(n => n.TagName == &quot;span&quot;);
            Assert.AreEqual(2, q.Count);
            Assert.AreEqual(&quot;message&quot;, q[0].Value);
            Assert.AreEqual(&quot;new message&quot;, q[1].Value);
        }

        [TestMethod]
        public void TestRemove1()
        {
            string html = @&quot;
<body>
    <h2>title1</h2>
    <span id='m1'>message</span>
    <h2>title2</h2>
    <span id='m2'>message2</span>
</body>
&quot;;
            HtmlDocument doc = new HtmlDocument(html);

            doc.Where(n => n.Attrs[&quot;id&quot;] == &quot;m2&quot;).Remove();

            var q = doc.Where(n => n.TagName == &quot;span&quot;);
            Assert.AreEqual(1, q.Count);
            Assert.AreEqual(&quot;message&quot;, q[0].Value);
        }

        [TestMethod]
        public void TestRemove2()
        {
            string html = @&quot;
<body>
    <h2>title1</h2>
    <span id='m1'>message</span>
    <h2>title2</h2>
    <span id='m2'>message2</span>
</body>
&quot;;
            HtmlDocument doc = new HtmlDocument(html);
            var el = doc.Where(n => n.TagName == &quot;span&quot;);
            doc.Remove( el );
            Assert.AreEqual(@&quot;<body><h2>title1</h2><h2>title2</h2></body>&quot;, doc.Html );
        }

        [TestMethod]
        public void TestInsert1()
        {
            string html = @&quot;
<body>
    <h2>title1</h2>
    <div id='m1'></div>
    <h2>title2</h2>
    <div id='m2'></div>
</body>
&quot;;
            HtmlDocument doc = new HtmlDocument(html);

            HtmlElement target = doc.Where( n => n.Attrs[&quot;id&quot;] == &quot;m1&quot; );
            var el = target.AppendChild( new HtmlElement( &quot;p&quot;, &quot;new message&quot; ));

            Assert.AreEqual(&quot;<body><h2>title1</h2><div id=\&quot;m1\&quot;><p>new message</p></div><h2>title2</h2><div id=\&quot;m2\&quot;/></body>&quot;, doc.Html );
        }
    }
}

まだ、検索系の where と更新系の update/remove/append を軽く実装しただけですが、結構いい感じに動いています。
テストを実行するために、Html プロパティを実装して、Assert.AreEqual をやりやすくしています。まだ Html プロパティを ReadOnly にしていますが、後々は書き込めるようにするということで。

■最初の HTML Document をどう作るか?

HTML が整形式ではない(タグの対応が揃っていない)ので、自前でパースしないといけないのか、とも思っていたのですが、mshtml.IHTMLDocument2 を使うことで比較手軽に HTML の DOM を作れます。

public HtmlDocument LoadHtml( string html )
{
#if false
    WebBrowser br = new WebBrowser();
    br.Navigate("about:blank");
    br.Document.Write(html);
    IHTMLDocument2 doc = (IHTMLDocument2)br.Document.DomDocument;
#else
    var doc = new HTMLDocument() as IHTMLDocument2;
    doc.write(new object[] { html });
#endif
    Load(doc);
    return this;
}

最初は、WebBrowser コントロールから DomDocument を取得しようと思ったのですが、mshtml.HTMLDocument クラスを使うことで HTML 文字列を直接扱えます。ノード自体を DOM で扱う場合には、IHTMLDocument2 を使うのでこれにキャストをします。
このあたりのノウハウはおいおいと公開しています。

カテゴリー: C# | [C#] HTMLをLINQで扱えるようにする(前哨戦) はコメントを受け付けていません