途中まで書きかけ日記を数日放置していた隠者です。
日曜日にはほとんどできていたのですが、装飾とか色づけとか、小さな手直しとか余計な事をちまちまやっていたら、水曜日になってました。内容は、20日(土)に参加してきた勉強会のお話です。
関東Qt勉強会を改めQtユーザー会による「
Qt勉強会 #1」に参加してきました。今回は発表はなしの「もくもく会」です。
ちなみに、知らない人のために、もくもく会とは、一つ所にあつまりながら、各自黙々とお勉強をするお勉強会でして。ただ一人だけでやってるわけではなく、困った事があったら、プロジェクターを借りて、こんな事になっちゃったのだけど、誰かなにかしりませんか?と聞く事も出来る会です。
隠者のように独り身で、部屋で勉強し放題という人間ばかりではないので、こういう機会が貴重な方もいらっしゃいますし、普段人前で発表なんて無理って人でも、ちょっとこんな事でっていう話をするだけなら、敷居も低いということで、こういう勉強会も面白いものです。
ちなみに、隠者の内職は、名古屋で話してきたQPlanet(仮)の続きです。
Planetというのは、この隠者のブログを、Qtユーザー会のPlanetというページで転載しているPython製のFeed agregatorというアプリケーションです。qt-users.jpでは、これをcronで動作させているそうです。
RSSやAtom等をかき集めて静的なページを作成してくれるツールなのですが、Slide Shareの埋め込み iframe が反映されなかったり、タスクさんのSilk製のBlogも登録されていないようで、もう少し簡単に登録管理や、反映できるようにQtで作れないものかなぁと思ってしまいまして。よし、QPlanetを作ろうとか思ってみています。
QtのXML Patternsモジュールを使えば、XMLを簡単に取得・加工できる事は、名古屋行きの時に調べてありましたが、RSS 1.0, RSS 2.0, Atom 1.0等の3種類を分別して、変換していくという辺りの途中で止まっていたので、続きをやってきました。
詳細は全部完成したら、Qt勉強会でまた発表したいなと思っていますが、ざっくりと。
XQueryによるFeedリストの取得とSimpleなXMLへの変換
xmlpatternsに含まれるQXmlQueryクラスは、XQueryの実行ができます。
そこで、与えられたFeedのURLリストを巡回して、RSSやAtomを取得していき、静的Webページに載せる必要最小限の共通XMLへと変換します。
ベースとなる記事は、IBMにぴったりの物があります。
「
RSS と Atom の情報を XQuery を使って集約する」
ただし、バージョンの違いか、実行エンジンの違いか、IBMの記事そのままでは動作しませんでした。そこで、自分なりに試行錯誤して、Qtで使えるコードに修正しました。
ただし、隠者は、XQueryはこれが初めてで、正直いってどこまで正しいか不安があります。
ツッコミはコメントのこしていただくか(ただしいつコメントに気がつくか不明)、@hermit4 でつぶやいて下されば直す努力はします。
なお、出来上がるXMLのauthor情報は、著者の情報が無いFeedもありまして、その辺りについてまだ調査中のため、ダミーが入ってたりします。ごめんなさい。
declare namespace atom="http://www.w3.org/2005/Atom";
declare namespace rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
declare namespace rss="http://purl.org/rss/1.0/";
declare namespace dc="http://purl.org/dc/elements/1.1/";
declare namespace content="http://purl.org/rss/1.0/modules/content/";
declare variable $months := ('jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec');
declare function local:convDate($origdate as xs:string)
{
let $dateTokens := tokenize($origdate,' ')
let $month := string(index-of($months,lower-case($dateTokens[3])))
let $date := concat($dateTokens[4],'-',
if (string-length($month)=1) then concat('0',$month) else $month,
'-',$dateTokens[2])
let $time := string($dateTokens[5])
return string(concat($date,'T',$time,'Z'))
};
declare function local:formatDate($orgdate as xs:string) as xs:dateTime
{
if (matches($orgdate, "^[0-9]{4}-[0-9]{2}-[0-9]{2}T.*")) then
xs:dateTime($orgdate)
else
xs:dateTime(local:convDate($orgdate))
};
declare function local:simple-feed($title,$pubdate,$content,$link,$name)
{
let $convdate := local:formatDate($pubdate)
return
<item>
<title>{$title}</title>
<pubdate>{$convdate}</pubdate>
<link>{$link}</link>
<content>{$content}</content>
<author>
<name>{$name}</name>
</author>
</item>
};
declare function local:convert-atom($feeddoc)
{
for $i in $feeddoc/atom:feed/atom:entry
return local:simple-feed ($i/atom:title/text(),
$i/atom:published/text(),
$i/atom:content/text(),
$i/atom:link[@rel="" or @rel="alternate"][href],
$i/atom:author/atom:name/text())
};
declare function local:convert-rss10($feeddoc)
{
for $i in $feeddoc/rdf:RDF/rss:item
return local:simple-feed($i/rss:title/text(),
$i/dc:date/text(),
$i/content:encoded/text(),
$i/rss:link/text(), "aaa")
};
declare function local:convert-rss20($feeddoc)
{
for $i in $feeddoc/rss/channel/item
return local:simple-feed($i/title/text(),
$i/pubDate/text(),
if (count($i/content:encoded) > 0 )
then $i/content:encoded/text()
else $i/description/text() ,
$i/link/text(), "bbb")
};
declare function local:convert-feed($url)
{
let $feeddoc := doc($url)
return if (count($feeddoc/atom:feed/atom:entry) > 0) then
local:convert-atom($feeddoc)
else if (count($feeddoc/rdf:RDF/rss:item) > 0) then
local:convert-rss10($feeddoc)
else
local:convert-rss20($feeddoc)
};
let $merged := for $url in tokenize($feedlist,',')
return if (doc-available($url)) then local:convert-feed($url)
else ()
let $merged := for $item in $merged
order by $item/pubdate descending
return $item
return
<planet>
{
for $item in $merged[days-from-duration(current-dateTime() - xs:dateTime(./pubdate)) <= $recentdays or position() <= $minitems]
return $item
}
</planet>
いきなり長い上に、見慣れない長いコードですが、これがXQueryというやつです。
Qt部分から設定する変数を使って動作するため、これだけ見るとXQueryを知っている人もさっぱりかもしれません。
Qt部分では、イカのコードで変数を設定します。
QStringList urls = QStringList() << "http://qt5.jp/rss.qml"
<< "http://relog.xii.jp/atom-qt.xml"
<< "http://blog.hermit4.info/feeds/posts/default/-/Qt"
<< "http://qt-labs.jp/feed";
QXmlQuery query(QXmlQuery::XQuery10);
query.bindVariable("feedlist", QVariant(urls.join(",")));
query.bindVariable("recentdays", QVariant(int(7)));
query.bindVariable("minitems", QVariant(int(6)));
これにより、Qtのプログラム側から、XQueryで利用する変数が定義できるのです。
いずれ、Qt側でファイルから読み込むコードを書く予定ですが、今の所は、以下の変数の事だけ覚えておいて下さい。
- $feedlist = URLの","区切り文字列
- $recentdays = 7
- $minitems = 6
1. namespaceの定義
まずは、読み込むfeedのXMLが利用しているnamespaceを定義します。
declare namespace atom="http://www.w3.org/2005/Atom";
というやつですね。これがないと、思うようにXMLを取得できません。必要な名前空間をすべて記載していっています。Qt側でも出来るようなのですが、まぁ、よしとしましょう。
2. 関数の定義
XQueryでは、処理を関数化する事ができます。隠者は以下のような関数を作成しています。
- local:convDate($origdate as xs:string)
英語表記の日時表現を、YYYY-MM-DDThh:mm:ssの表現に変換する。
タイムゾーンは現状、GMTしか無かったので、常にZを付けてしまっていて修正が必要
- local:formatDate($orgdate as xs:string) as xs:dateTime
文字列の日時表記をxs:DateTime型変数に変換する
- local:simple-feed($title,$pubdate,$content,$link,$name)
共通XMLのフォーマット
- local:convert-atom($feeddoc)
Atom 1.0のデータをパースして、simple-feedに渡す
- local:convert-rss10($feeddoc)
RSS 1.0のデータをパースして(ry
- local:convert-rss20($feeddoc)
RSS 2.0のデータをパースして(ry
- local:convert-feed($url)
feedがどのフォーマットか判別して変換関数を呼び出す
といった関数を用意しています。
3. XQueryの開始部
では、これか関数を使って変換を実装している箇所が何をしているのか順を追ってみていきます。
let $merged := for $url in tokenize($feedlist,',')
return if (doc-available($url)) then local:convert-feed($url)
else ()
$feedlist変数のカンマ区切り文字列を","で文字配列に分けています。その上で、配列の一つずつを取り出して、そのURLが有効なdocを取得できるURLか検証し、有効なものであれば、convert-feedを実行します。実行結果は、$merged変数に格納されます。
まぁ、後はこの中でフォーマットを判別して、記事の情報について必要な所だけ抜き出してsimpleな共通のXML表現<item>リストに変換しているわけです。
let $merged := for $item in $merged
order by $item/pubdate descending
return $item
続いて、変換されたSimpleなXMLの中をループしながら、日付降順で並べ変えて$merged変数に入れ直しています。
return
<planet>
{
for $item in $merged[days-from-duration(current-dateTime() - xs:dateTime(./pubdate)) <= $recentdays or position() <= $minitems]
return $item
}
</planet>
最後に、出来上がったXMLから$recentdays以内の記事か、$minitems数に達するまでのfeedを抜き出して、<planet>タグで覆って完了です。
これを実行すると、以下のようなXMLが出来上がるという寸法です。
<planet>
<item>
<title>Qt 勉強会 #1 @Tokyo 開催しました</title>
<pubdate>2013-07-22T09:50:00Z</pubdate>
<link>http://qt5.jp/qt-meetup-1-tokyo-report.html</link>
<content><p>
2013年7月20日(土曜日)に <a href="http://qt-users.doorkeeper.jp/events/4619" target="_blank">Qt 勉強会 #1 @Tokyo</a> を <a href="http://www.ptp.co.jp/" target="_blank">株式PTP</a> さんのミーティングルームで開催しました。
</p>
<p>
今回からは「関東Qt勉強会」ではなく、「Qt 勉強会」という名前で再スタートしました。
</p>
<blockquote class="twitter-tweet"><p>Qt 勉強会 <a href="http://t.co/1O5ArTQl85">pic.twitter.com/1O5ArTQl85</a></p>&mdash; Tasuku Suzuki (@task_jp) <a href="https://twitter.com/task_jp/statuses/358440009327587328">July 20, 2013</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>
今回はもくもく会ということで、各自好きなことをしながら分からないことがあったら誰かに聞くというゆる〜い感じでした。
</p>
<p>
私は、10月/11月に開催される <a href="http://www.qtdeveloperdays.com/" target="_blank">Qt Developer Days</a> の発表の応募のための文章を必死に考えていました。
</p>
<h3>Qt 勉強会 #2 @Tokyo</h3>
<p>
<a href="http://qt-users.doorkeeper.jp/events/5014" target="_blank">Qt 勉強会 #2 @Tokyo</a> を <a href="http://www.google.com/calendar/event?action=TEMPLATE&text=Qt+%E5%8B%89%E5%BC%B7%E4%BC%9A+%232+%40Tokyo&details=http%3A%2F%2Fqt-users.doorkeeper.jp%2Fevents%2F5014&dates=20130810T040000Z%2F20130810T090000Z&location=&trp=truesprop=website:http%3A%2F%2Fqt-users.doorkeeper.jp%2F&sprop=name:Qt+%E5%8B%89%E5%BC%B7%E4%BC%9A+%28Doorkeeper%29" target="_blank">2013/08/10 (土)</a> に開催します。お気軽にお越しください。
</p>
<p>
<a href="http://qt-users.doorkeeper.jp/events/5014" class="doorkeeper-registration-widget">Qt 勉強会 #2 @Tokyo</a>
<script src="https://d1dqic1fklzs1z.cloudfront.net/assets/widget.js" type="text/javascript"></script>
</p>
</content>
<author>
<name>bbb</name>
</author>
</item>
<item>
<title>Qt Quickはじめませんか?</title>
<pubdate>2013-07-16T13:27:10Z</pubdate>
<link/>
<content>
<p>というわけで、Qt Quickの入門書を書きました。8/1発売です。</p>
<p>「Qt QuickではじめるクロスプラットフォームUIプログラミング」</p>
<p>と、題してQt Quickでの開発からリリースまでを解説しています。<br />
Qt Quickで使用するQMLはCやJavaとは少し雰囲気の違う言語なので、どのように組み上げていくかを解説しています。<br />
Qt Quickのすべての機能を解説しきれませんが、開発に必要な内容はひと通り網羅しています。</p>
<p>Windows/Linux/Macへの対応方法も必要に応じて解説してます。<br />
Androidへの対応も進んでいて、紹介しているサンプルをAndroidで動かすこともできます。</p>
<p>これからますますおもしろくなりそうなQt Quickをはじめませんか?<br />
ぜひ、本書を手にとって頂ければと思います。</p>
<p><iframe src="http://rcm-fe.amazon-adsystem.com/e/cm?lt1=_blank&bc1=000000&IS2=1&bg1=FFFFFF&fc1=000000&lc1=0000FF&t=ioriayane-22&o=9&p=8&l=as4&m=amazon&f=ifr&ref=ss_til&asins=4048915126" style="width:120px;height:240px;" scrolling="no" marginwidth="0" marginheight="0" frameborder="0"></iframe></p>
<p>(出版を無事迎えれそうなのは、お世話になっている皆様のおかげです。特にO氏、S氏、D氏に感謝です。)<br />
</p>
</content>
<author>
<name>IoriAYANE</name>
</author>
</item>
<item>
<title>QtQuick本ついに</title>
<pubdate>2013-07-16T03:45:00Z</pubdate>
<link>http://qt5.jp/the-qtquick-book-coming-soon.html</link>
<content><blockquote class="twitter-tweet" data-partner="tweetdeck"><p>発売日を人に聞いて知ったよ、QtQuick本ついに。 <a href="http://t.co/VIzo4zGQAl">http://t.co/VIzo4zGQAl</a> via @</p>&mdash; 理音伊織 (@IoriAYANE) <a href="https://twitter.com/IoriAYANE/statuses/355961176745705472">July 13, 2013</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>
「<a href="http://ascii.asciimw.jp/books/books/detail/978-4-04-891512-0.shtml" target="_blank">Qt QuickではじめるクロスプラットフォームUIプログラミング</a>」が 8/1 に発売されます。
</p>
<p>
初心者から上級者まで、Qt Quick に興味がある人すべてにおすすめできる本なので、今すぐ <a href="http://amzn.to/15hhZ64" target="_blank">Amazon</a> で予約を!
</p></content>
<author>
<name>bbb</name>
</author>
</item>
<item>
<title>名古屋Qt勉強会#9 に参加しました</title>
<pubdate>2013-07-16T03:30:00Z</pubdate>
<link>http://qt5.jp/qt-meetup-nagoya-9-report.html</link>
<content><p>
7月13日に <a href="http://xmldo.jp/seminarroom" target="_blank">ニューキャスト様セミナールーム</a> で開催された <a href="http://www.zusaar.com/event/826006" target="_blank">名古屋Qt勉強会#9 7/13</a> に参加してきました。
</p>
<p>
<a href="http://www.zusaar.com/event/326005" target="_blank">名古屋Qt勉強会 #5</a> 以来、約1年ぶり(多分)4回目の参加になります。
</p>
<h2><a href="http://www.ustream.tv/recorded/35744325" target="_blank">Qt for Android</a></h2>
<p>
年末にリリース予定の Qt 5.2 で正式対応となる Qt for Android の最新状況についての発表でした。発表は Qt 5.1 RC1 ベースだったのですが、Qt 5.1 の正式版と Qt Creator 2.8.0 の組み合わせでどのくらい状況が改善されているのかが気になるところでした。
</p>
<p>
最近お友達に Android 端末をもらったので、何か作ってみようと思います。
</p>
<blockquote class="twitter-tweet"><p>Qt名古屋勉強会の発表資料です。 <a href="http://t.co/6LEhAzVZke">http://t.co/6LEhAzVZke</a></p>&mdash; 理音伊織 (@IoriAYANE) <a href="https://twitter.com/IoriAYANE/statuses/355930919082786818">July 13, 2013</a></blockquote>
<h2><a href="http://www.ustream.tv/recorded/35745459" target="_blank">RaspberryPiを使ってQtでプレゼン</a></h2>
<blockquote class="twitter-tweet"><p>これからはプレゼンは持ち歩く時代らしい。 Raspberry Piをプロジェクターに直結し電源もUSBから <a href="http://t.co/7VNUPcFp">http://t.co/7VNUPcFp</a> <a href="http://t.co/lOnrfm4I">http://t.co/lOnrfm4I</a> <a href="http://t.co/CmA4PwC8">http://t.co/CmA4PwC8</a>&#10;<a href="https://twitter.com/search?q=%23QtJP&amp;src=hash">#QtJP</a></p>&mdash; nekomatu (@nekomatu) <a href="https://twitter.com/nekomatu/statuses/279822352756383746">December 15, 2012</a></blockquote>
<p>
を実際に自分で試したみたという発表でした。
</p>
<blockquote class="twitter-tweet"><p>Qt名古屋勉強会#9の発表資料です。 <a href="http://t.co/4tYr4OhSVY">http://t.co/4tYr4OhSVY</a>&#10;<a href="https://twitter.com/search?q=%23qtjp&amp;src=hash">#qtjp</a> <a href="https://twitter.com/search?q=%23qtngy&amp;src=hash">#qtngy</a></p>&mdash; sazus (@sazus) <a href="https://twitter.com/sazus/statuses/355943723919622144">July 13, 2013</a></blockquote>
<p>
どうでもいいことなのですが、なるべく短い HDMI ケーブルを用意して、電源もプロジェクターの USB から取って無線のマウスで操作するなどの細かい工夫が必要です。
</p>
<h2><a href="http://www.ustream.tv/recorded/35746715" target="_blank">Qtでウェブサービスを作ろう</a></h2>
<p>
<a href="http://silk.qtquick.me/" target="_blank">QML を使ったシンプルなウェブフレームワーク Silk</a> と、Silk を使って作られた、「あとで」「後で」で始まる自分のつぶやきを、自動で pocket に保存する簡単なサービス <a href="http://tweet2pocket.com/" target="_blank">Tweet 2 Pocket (ベータ)</a> の紹介をしました。
</p>
<p>
<script async class="speakerdeck-embed" data-slide="6" data-id="888edd90cd340130e3ce465a590f0cb7" data-ratio="1.33507170795306" src="//speakerdeck.com/assets/embed.js"></script>
</p>
<p>
クライアントサイドもサーバーサイドも Qt で書けるようになると楽ですね。
</p>
<h2><a href="http://www.ustream.tv/recorded/35747717" target="_blank">Q Planetに挑戦中</a></h2>
<p>
QtXmlPatterns を使って RSS リーダー?を作るという発表でした。QtXmlPatterns はなかなか使わないのでとても勉強になりました。
</p>
<blockquote class="twitter-tweet" data-partner="tweetdeck"><p>QPlanet作成に挑戦中 <a href="http://t.co/A2mDf7JC0x">http://t.co/A2mDf7JC0x</a> <a href="https://twitter.com/search?q=%23qtjp&amp;src=hash">#qtjp</a> <a href="https://twitter.com/search?q=%23qtngy&amp;src=hash">#qtngy</a></p>&mdash; hermit4(がんばりたい) (@hermit4) <a href="https://twitter.com/hermit4/statuses/355930442567925760">July 13, 2013</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></content>
<author>
<name>bbb</name>
</author>
</item>
<item>
<title>Qt 5.1.0 の変更点(補足)</title>
<pubdate>2013-07-16T02:28:51Z</pubdate>
<link>http://qt-labs.jp/2013/07/changes-in-qt-5-1-0-additional-notes.html</link>
<content><p><a href="http://qt-labs.jp/2013/07/qt-5-1-0-released.html">Qt 5.1.0 がリリース</a> されました。5.1.0 の主な変更点は <a href="http://qt-labs.jp/2013/04/qt-5-1-alpha.html">5.1.0 アルファ版のリリース記事</a> に書いていますが、そこにない変更点について補足しておきます。</p>
<p></p></content>
<author>
<name>bbb</name>
</author>
</item>
<item>
<title>名古屋Qt勉強会#9に参加しました</title>
<pubdate>2013-07-14T14:51:00+09:00</pubdate>
<link/>
<content>どうも、さっぱりブログを書かない日々が続いています隠者です。<br /><br />今回、3連休ということもあって、ぶらっと3泊4日で名古屋に出かけてきました。<br />今回の目的は、名古屋Qt勉強会 #9への参加です。<br /><br />今回の内容は<br /><ul><li>IoriAYANEさんによる「Qt for Android」</li><li>sazusさんによる「RaspberryPiを使ってQtでプレゼン」</li><li>taskさんによる「Qtでウェブサービスを作ろう」</li></ul>といった内容のあと、せっかくなので、こっそり飛び入りで、「 QPlanetに挑戦中」ってな内容で発表させていただきました。 <br /><br /><iframe allowfullscreen="" frameborder="0" height="356" marginheight="0" marginwidth="0" mozallowfullscreen="" scrolling="no" src="http://www.slideshare.net/slideshow/embed_code/24195504" style="border-width: 1px 1px 0; border: 1px solid #CCC; margin-bottom: 5px;" webkitallowfullscreen="" width="427"> </iframe><br /><br />内容は、関東Qtユーザー会で話した続きのつもりで、実際こんな物を作ろうとしています。今こんな感じですって発表のつもりでしたけど、元々予定に無かった発表なので、思いっきりはしょって飛ばして話して来ました。<br /><br />本当は、名古屋もQtユーザー会として名前とか色々統一しませんか的な話もしなきゃならなかったのですが、名古屋Qt勉強会というと著名人の「ザウルス先生」とか「帽子猫の王」とか「さるぼぼの王」とかいらっしゃいますし、かなりドキドキしながらの参加でしたので、結局話せずじまい。<br /><br />本当は、たすくさんが話すだろうなと思って、フォローするつもりで参加したのですが、たすくさんは隠者が話す物だとおもっていたようで。<br /><br />なにせ名古屋というと、PF部等で濃いお話を聞かせて下さるまごろく先生とか、隠者自身の職業プログラマとしての最初の師匠も名古屋出身ということで、凄い技術者がごろごろしている所という印象が強かったりします。<br /><br />まぁ、でも、懇親会のピザも美味しかったり、色々食べ歩いたり、見て歩いたりと、普段は出不精で旅行らしい旅行なんて凄い久しぶりだったので、堪能しました。<br /><br /></content>
<author>
<name>hermit4(隠者)</name>
</author>
</item>
</planet>
まぁ、正直ここまで書いておいてなんですが、XQueryの構文はいまいち気持ちが悪くてなじめないのですよねぇ。もっとシンプル簡単簡潔に書けないものなのでしょうか・・・。
ちなみに、上記のXQueryをagregate.xqという名前で保存して、それを使って呼び出すお試しコードは以下の通りです。
#include <QCoreApplication>
#include <QTextStream>
#include <QStringList>
#include <QXmlQuery>
#include <QFile>
#include <QDebug>
int main(int argc, char* argv[])
{
QCoreApplication app(argc, argv);
QFile agregate("agregate.xq");
agregate.open(QIODevice::ReadOnly);
QStringList urls = QStringList() << "http://qt5.jp/rss.qml"
<< "http://relog.xii.jp/atom-qt.xml"
<< "http://blog.hermit4.info/feeds/posts/default/-/Qt"
<< "http://qt-labs.jp/feed";
QXmlQuery query(QXmlQuery::XQuery10);
query.bindVariable("feedlist", QVariant(urls.join(",")));
query.bindVariable("recentdays", QVariant(int(7)));
query.bindVariable("minitems", QVariant(int(6)));
query.setQuery(&agregate);
if(!query.isValid()) {
qDebug() << "invalid query";
return 1;
}
QString result;
if(!query.evaluateTo(&result)) {
qDebug() << "eavaluate error";
}
QTextStream(stdout) << result << endl;
return 0;
}
次回の日記は、上記のXMLをHTMLに変換する辺りを頑張りたいと思います。