そんなわけで、11月第一週は、MVP Global Summitに参加すべく、米国ワシントン州・シアトル・レドモンドへと行ってきました。国外に出るのは初めてなので、色々準備が必要だったのですが、特にパスポートの受領とスーツケースの新調が大きかったです。スーツケースは以前に長期出張の時に持っていっていたものがあったのですがすっかり忘れていて、どのみちもう重いのも辛いので、軽いやつを買いました。大きさは中ぐらい?何かお土産が出来た時に入るぐらいの大きさってことで考えました。
勢いで買ってしまった感があったんですが、非常に満足感が高かった。勿論、その日のうちにWindows 10 Mobile Insider Previewに更新。ちなみにSIMロックですが、6か月経つとAT&Tのサイトでアンロックできるようになるらしいです(すぐにアンロック出来るのかと勘違いしてトライしたけど、流石にダメだった)。ほかの方法として、某SIMロック解除サイトを使うと、$15ぐらいでアンロック出来ることも判明。参考までに。
まとめ
MVP Global Summitに参加するのが主題だったんですが、初の海外旅行・シアトル良いところ・サミットセッションももちろん、偶然入手したWindows Phoneや現地SIM調達など、ものすごく盛り沢山の旅行で本当に良かったです。次回MVPを受賞しているかどうかはわかりませんが、また行ってみたいなぁと思っています。
さて、今年私は「Microsoft MVP for .NET(今はカテゴリーが再編されたので、Visual Studio and Development Technology)」を受賞しました。MVPを受賞すると特典の一つとして、一年に一度行われる「MVP Global Summit」への参加資格が得られます。生まれてこの方、一度も日本を出たことがなかったので、この節目に行ってみようかと思いました。この記事ではその話に絡めてWindows Phoneの話をしようかと思いましたが、その前に「Microsoft MVP Award」(長いのでMSMVPと略します)についての紹介もしようかと思います。
「Microsoft Most Valuable Professional (MVP) は、自身のマイクロソフト技術に関する知識や経験を最大限に活かしながら、他のユーザーを積極的にサポートしている、非常に優れたコミュニティのリーダーです。彼らは、技術専門知識に加えて自主性と情熱を兼ね備え、マイクロソフト製品の実用的な活用方法をコミュニティやマイクロソフトと共有しています。」
MVP Global Summitへの参加権
毎年11月ぐらいに催されているようです。期間中、米国本社(ワシントン・シアトル付近)のホテルに無料で宿泊できますが、飛行機代やそれ以外のコストは自腹でねん出する必要があります。また、ここで見聞きする技術情報はNDAベースなので、最新情報を聞いて帰ってきても、喋ることは出来ません。
// ビジネスロジックを含むモデル(コレクション)
public sealed class BusinessLogicModel : List<ResultItem>
{
// コレクションを更新する
public async Task UpdateAsync(string value)
{
// 非同期処理...
using (var stream = await httpClient.GetStreamAsync("...?q=" + value).
ConfigureAwait(false)) // 以降の処理をワーカースレッドで実行
{
var document = SgmlReader.Parse(stream);
// ここではUIを直接操作出来ない(ワーカースレッドなので)
this.Clear();
this.AddRange(
from html in document.Elements("html")
from body in html.Elements("body")
// ...
select new ResultItem { ... });
}
}
}
Windows PhoneもWinRTベースの環境と、続々と出荷されるWindows Phone 8ベースの端末(何故か国内では発売されていない。「何故か」w)のおかげで、すっかりWinRTも浸透してきたようです。
(写真に登場するLumia 1520とプロ生ちゃんはフィクションであり、この記事とは無関係ですw)
以前に作ったスクレイピングのライブラリは、 「SgmlReader」と呼ばれるものをPortable Class Library化したものです。このライブラリは、「SGML」をパースしてXmlReader化するものですが、HTMLのDTDが添付されており、要するに「普通のHTMLパーサー」として使えるところがミソです。
これはNuBuildプロジェクトのダイアログですが、「Add Binaries To SubFolder」だけTrueに変えておきます。今回のパッケージには、Profile1のPCLとWinRT向けのPCLの2つのバージョンを入れ込むため、サブフォルダに配置するように指示します。あとはデフォルトのままです。
<?xml version="1.0" encoding="utf-16"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<!-- Required metadata -->
<id>CenterCLR.SgmlReader</id>
<version>2014.12.7.3</version>
<title>SgmlReader for Portable Class Library</title>
<summary>SgmlReader for Portable Class Library. SgmlReader most popular usage the "HTML" parser. (It's scraper!!)</summary>
<authors>Kouji Matsui</authors>
<description>SgmlReader for Portable Class Library.
SgmlReader is "SGML" markup language parser, and derived from System.Xml.XmlReader in .NET CLR.
But, most popular usage the "HTML" parser. (It's scraper!!)
/* Use SgmlReader in Html parse mode. */
XDocument document = SgmlReader.Parse(stream);
Done!</description>
<releaseNotes>2014.12.7.3:
Add 1 line parse method.
2014.12.7.2:
Direct handling the Stream class.
Initial parameter is set of Html parse mode.
2014.12.7.1:
Namespace changed "CenterCLR.Sgml".
More easy usage, HTML parse is default mode.
Native store app library included.
1.8.11.2014:
Initial release.</releaseNotes>
<projectUrl>https://github.com/kekyo/CenterCLR.SgmlReader.git</projectUrl>
<iconUrl>https://raw.githubusercontent.com/kekyo/CenterCLR.SgmlReader/master/CenterCLR.SgmlReader.100.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<licenseUrl>http://opensource.org/licenses/Apache-2.0</licenseUrl>
<copyright>Copyright (c) 2002, Microsoft Corporation; Copyright (c) 2007-2013, MindTouch; Copyright (c) 2014, Kouji Matsui</copyright>
<tags>SgmlReader Parser Portable HtmlReader Html Scraping</tags>
<!-- Optional metadata
<owners></owners>
<dependencies>
</dependencies>
<references></references>
-->
</metadata>
</package>
/// <summary>
/// 指定されたURLのHTMLを読み取って解析します。
/// </summary>
/// <param name="url">URL</param>
/// <returns>コンテンツ群のURL</returns>
private static async Task<IReadOnlyList<KeyValuePair<Uri, string>>> LoadFromAsync(Uri url)
{
using (var client = new HttpClient())
{
using (var stream = await client.GetStreamAsync(url).ConfigureAwait(false))
{
// SgmlReaderを使う
var sgmlReader = new SgmlReader(stream);
var document = XDocument.Load(sgmlReader);
// 郵便番号データダウンロードのサイトをスクレイピングする
// ターゲットは、html/body/div[id=wrap-inner]/div[id=main-box]/div[class=pad]/table/tbody/tr/td/a
// にあるhrefとなる。
// パースとトラバースまでワーカースレッドで実行しておく。
return
(from html in document.Elements("html")
from body in html.Elements("body")
from divWrapOuter in body.Elements("div")
let wrapOuter = GetAttribute(divWrapOuter, "id")
where wrapOuter == "wrap-outer"
from divWrapInner in divWrapOuter.Elements("div")
let wrapInner = GetAttribute(divWrapInner, "id")
where wrapInner == "wrap-inner"
from divMainBox in divWrapInner.Elements("div")
let mainBox = GetAttribute(divMainBox, "id")
where mainBox == "main-box"
from divPad in divMainBox.Elements("div")
let pad = GetAttribute(divPad, "class")
where pad == "pad"
from table in divPad.Elements("table")
from tbody in table.Elements("tbody")
from tr in tbody.Elements("tr")
from td in tr.Elements("td")
from a in td.Elements("a")
let href = GetAttribute(a, "href")
where href.EndsWith(".zip", StringComparison.OrdinalIgnoreCase) == true
let zipUrl = ParseUrl(url, href)
where zipUrl != null
let text = a.Value
where string.IsNullOrWhiteSpace(text) == false
select new KeyValuePair<Uri, string>(zipUrl, text)).
ToList();
}
}
}