YQL JavaScript メモ その2 restオブジェクト

requestオブジェクトは基本的にrestオブジェクトの作成方法でしかない。restオブジェクトへのアクセスはrequest、y.rest、y.queryのいずれかを使うしかない。
そのrestオブジェクトはプロパティ及びメソッドを持つ。以下はその一覧。

プロパティ

プロパティ 概要
headers headerの連想配列を返す
url URLを返す
queryParams queryの連想配列を返す
matrixParams matrixの連想配列を返す。matrixについてはmethod部分で後述

メソッド

accept(content-type)

受け取るデータ形式を指定する

var ret = request.accept("application/json").contentType("application/xml").post(content).response;
contentType(content-type)

送るデータ形式を指定する

var content = <employee>
  <name>John</name>
  <id>21</id>
</employee>;
var ret = request.contentType("application/xml").post(content).response;
del()

HTTP DELETEを実行する。DELETE文で使う。

<delete itemPath="" produces="JSON">
  <urls>
    <url>http://api.mixi-platform.com/2/voice/statuses/{post_id}</url>
  </urls>
  <inputs>
    <key id="oauth_token" type="xs:string" paramType="query" required="true" />
    <key id="post_id" type="xs:string" paramType="path" required="true" />
  </inputs><execute><![CDATA[response.object = request.del().response;]]></execute>
</delete>
fallbackCharset(charset_list)

responseのdecodeの為に「utf-8」「iso-8859-1」の2つがdefaultで設定されているが、これを任意の文字コードで上書きする。

response.object = y.rest(url).fallbackCharset("shift_jis, iso-8859-8, UTF-8, iso-8859-1").get().response;
filterChars()

不正な文字列を削除する。不正な文字列の定義は下記

  • Web Serviceの文字コードに一致しない文字列
  • XMLの仕様に一致しない文字列
response.object = y.rest(url).filterChars().get().response;
forceCharset(charset_list)

指定した文字コードでのデコードを強制する。これはresponseやfallbackCharsetメソッドで指定された文字コードより優先される。

y.rest("www.uol.com.br").forceCharset("iso-8859-1").get().response;
get()

指定されたURLに対してGETメソッドを実行する。これはSELECT文でのみ有効。

r = y.rest("yahoo.com").contentType("application/json").get();
response.object = r;
head()

指定されたURLに対してHEADメソッドを実行する。実行結果はheadersプロパティに格納される。

<table xmlns="http://query.yahooapis.com/v1/schema/table.xsd">
  <meta>
    <sampleQuery>select * from {table} where url_path="www.example.com" and header="true";</sampleQuery>
  </meta>
  <bindings>
    <select itemPath="" produces="XML">
      <inputs>
        <key id="url_path" type="xs:string" paramType="variable" required="true" />
        <key id="header" type="xs:boolean" paramType="variable"/>
      </inputs>
      <execute><![CDATA[
        var res = {};
        if (header) {
          res.body = y.rest(url_path).head().headers;
        } else {
          res.body = y.rest(url_path).get().response;
        }
        response.object = res.body;
      ]]></execute>
    </select>
  </bindings>
</table>
header(name, value)

HTTP requestにheaderを追加する

y.include("http://yqlblog.net/samples/base64.js");
var authheader = "Basic " + Base64.encode(username + ":" + password);
response.object = request.header("Authorization", authheader).get().response;
jsonCompat(mode)

詳しくはここ。現在引数modeで使えるのは「new」のみ。これでlossless JSONを扱えるようになる。
YQLはweb serviceから得られるデータをxmlで内部処理する。入力と出力が共にJSONであっても同様。この場合JSONXMLJSONという変換がなされる。このときlossyなデータとなる場合がある。このような事態を避ける時にこのmethodを使用する。

<?xml version="1.0" encoding="UTF-8"?>
<table xmlns="http://query.yahooapis.com/v1/schema/table.xsd" securityLevel="any">
  <bindings>
    <select produces="JSON">
      <execute><![CDATA[
        var api_key = "http://upcoming.yahoo.com/services/api/keygen.php";
        var url = "http://upcoming.yahooapis.com/services/rest/?api_key=" + api_key + "&method=event.search&search_text=concert&metro_id=1&format=json";
        var a = y.rest(url).get().response;
        var b = y.rest(url).jsonCompat("new").get().response;
        y.log(y.jsToString(y.xmlToJson(a)));
        y.log(y.jsToString(y.xmlToJson(b)));
        response.object = b;
        ]]></execute>
    </select>
  </bindings>
</table>
matrix(name,value)

URLに投げるmatrixを指定すると書いてあるが、matrixってなんぞやと思ったらろくなソースが出てこない。日本語ソースに至っては出てきもしない。どうもかなりマイナーな規格のようだ。
What is MATRIX URL PARAMETER?
URL matrix parameters vs. request parameters]
まあ要するにURLへのparameterの与え方が通常とは違うらしい。

y.rest("http://flower_order_service.com").matrix("flower", "roses").matrix("color", "red").post();
path(path_segment)

指定されたURLにPATHを追加する。

var myData = y.rest("http://blah.com").path("one").path("two").query("a", "b").header("X-TEST","value").get().response;
post(content)

指定されたURLに対してPOSTメソッドを実行する。INSERT、UPDATE、DELETE文で使うと書いてあるが、SELECT文でpostを使う事もあるような気がするんだがどうするんだろう。

var content = {"employee": { "name":"John", "id":21}};
var ret = request.contentType("application/json").post(content).response;
put(content)

指定されたURLに対してPUTメソッドを実行する。INSERT、UPDATE、DELETE文で使う。

url = blogurl + "/xmlrpc.php";
myRequest = y.rest(url);
myRequest.contentType("text/xml");
results = myRequest.put('<?xml version="1.0" encoding="UTF-8"?>' + postData.toString()).response;
query(key, value)

URLに投げるqueryを指定する。

var formUrl = "http://example.com/form";
var resp = y.rest(formUrl).query("name", "Tom").post().response;
query(hashmap)

URLに投げるqueryを連想配列で指定する。

var formUrl = "http://example.com/form";
var name = {"name": "John"};
var resp = y.rest(formUrl).query(name).post().response;
timeout(milli_seconds)

ミリ秒でタイムアウトを指定する

y.rest("http://....").timeout(500).get();

YQL JavaScript メモ その1 yオブジェクト

大本の文書はここ

y.cache

y.cacheはcache操作のプロパティ及びメソッドを持つ。
※Sampleを見てもいまいちよく分からない、実際に動かしてみてもエラー吐くので後日追記。

CacheOpResultオブジェクト

下記メソッド群の返り値となる。opとresultという2つのプロパティを持つ。opは実行されたメソッド名、resultは実行結果を返す。

y.cache.decr(key, step, init, timeOut)
y.cache.get(key)
y.cache.get(key,timeOut)
y.cache.incr(key, step, init, timeOut)
y.cache.put(key, value, timeOut)
y.cache.remove(key)

y.context

現在の所tableプロパティのみでありこれはtable名を返す。

y.crypto

MD5SHA1等のハッシュ関数を持つ。

var md5string = y.crypto.encodeMd5("encode this string to md5");   

y.crypto.encodeHmacSHA256(String secret, String plaintext)
y.crypto.encodeHmacSHA1(String secret, String plaintext)
y.crypto.encodeMd5(String plaintext)
y.crypto.encodeSha(String plaintext)
y.crypto.encodeBase64(String plaintext)
y.crypto.decodeBase64(String plaintext)
y.crypto.uuid()

y.date

現在getOffsetFromEpochInMillisメソッドのみらしい。

y.date.getOffsetFromEpochInMillis(time_format)

RFC3339で定義された日付文字列を与えるとUNIX timeを返す。下記のように普通にnew Date()じゃ出来ない事が簡単に出来るようになる。nowやyesterdayなんかDateオブジェクトは認識してくれない。

var start_time = parseInt(y.date.getOffsetFromEpochInMillis("now"));
var start_date = new Date(start_time);
var yesterday_time =  parseInt(y.date.getOffsetFromEpochInMillis("yesterday"));
var yesterday_date =  new Date(yesterday_time);
var month_from_now =  new Date(parseInt(y.date.getOffsetFromEpochInMillis("now + 1 month")));
var ali_liston_fight = new Date(parseInt(y.date.getOffsetFromEpochInMillis("1964-02-25")));

y.decompress(base64_compressed_string)

字面だけ見るとy.crypto.decodeBase64との違いが分からない。原文には「Allows you to decompress query parameters and the HTTP POST request body. The decompress method decodes a base64 string and then uses gunzip to decompress the string.」とある。どうもbase64でデコードした後更にgzipで解凍するらしい。HTTP POSTのrequest bodyをdecompressする事が出来ると書いてあるのでpostのエンコード処理ってのはこうなってるんだろうきっと。

y.deflate(string, level)

Deflateアルゴリズムで文字列を圧縮後base64エンコードする。

var data = "{ customer: { name: 'John Lucas' }}"; 
var compressed_data = y.deflate(data, 1); 
var ret = y.rest("http://example-webservice.com").post(compressed_data).response; 
response.object = ret;

y.diagnostics

YQL Consoleでのクエリー実行結果のdiagnosticsタグを返す。

y.env(environment file)

environment fileをIncludeする。environment fileの説明はここの辺を参照。

y.exit(status_code, msg)

scriptの実行を中止する。任意のstatus_codeとmessageを指定する事も出来る。

y.include(url)

JavaScript fileをIncludeする。

y.getTenantContextsAvailable()

※なんだかよく分からないので後で書く。

y.inflate(base64_deflated_string)

y.deflateとは逆のメソッド。

y.jsonToXml(object)

jsonxmlに変換する。戻り値はE4Xオブジェクト。

y.log(message)

diagnosticsタグ内にlogを残す。

y.query(statement, params, timeout, callback)

YQLクエリーを実行する。statementがYQL文でparamsがkey。timeoutはミリ秒で指定。callbackを指定する事も出来る。statement以外は省略可。

var q = y.query("select * from answers.search where query=@query and category_id=@cat_id and type=@type", {query:"cars",cat_id:"2115500137",type:"resolved"});
var results = q.results;  

戻り値はオブジェクトで「results」「diagnostics」「query」「timeout」の計4つのプロパティを持つ。resultsはクエリーの実行結果、diagnosticsはy.diagnosticsと同様、queryはy.queryに引数として与えられたstatementを返す。

y.rest(url, callback)

指定されたURLに対してGETメソッドを実行する。戻り値はrestオブジェクト。callbackは省略可。

var myRequest = y.rest('http://example.com');
var data = myRequest.get().response;

y.sync(flag)

y.restとy.queryを複数同時に実行した時の非同期処理に関するメソッド。引数flagがtrueの場合、全てのrequest処理終了時trueを返す。要するに同期処理。flagがfalseの場合、いずれかのrequest処理が1つ終了した時点でtrueを返す。要するに非同期処理。
という事らしいんだけど、非同期処理時に2番目以降のrequest処理完了をどうやって取得するんだろうかこれ。

y.tidy(html)

HTML TIDYでHTMLを整形したHTMLを返す。

y.use(url, namespace)

URLで指定されたopen data tableをimportする。

y.xparseJson(json_str)

json文字列をjsonオブジェクトにparseする。与えられた文字列がjsonの仕様に適合しない場合例外を投げる。

y.xpath(object, xpath)

E4XオブジェクトにXPathを適用する。戻り値もやはりE4Xオブジェクト。

y.xmlToJson(object)

E4Xオブジェクトをjsonオブジェクトに変換する。

YQL Open Data Table Reference Sample メモ その2

YouTube Video Search。これが分かれば基本的な物は作れるはず。

<table xmlns="http://query.yahooapis.com/v1/schema/table.xsd">
  <meta>
    <author>Guilherme Chapiewski <guilherme.chapiewski@gmail.com></author>
    <documentationURL>http://code.google.com/apis/youtube/2.0/developers_guide_protocol_api_query_parameters.html</documentationURL>
    <sampleQuery>select * from {table} where query="louis ck"</sampleQuery>
    <sampleQuery>select * from {table} where query="basketball" and start_index=11 and  max_results=10</sampleQuery>
    <sampleQuery>select * from {table} where query="apple" and order_by='relevance'</sampleQuery>
  </meta>
  <bindings>
    <select itemPath="results.video" produces="XML">
      <urls>
        <url>http://gdata.youtube.com/feeds/api/videos</url>
      </urls>
      <inputs>
        <key id="q" as="query" type="xs:string" paramType="query" required="true"/>
        <key id="start-index" as="start_index" type="xs:integer" paramType="query" required="false"/>
        <key id="max-results" as="max_results" type="xs:integer" paramType="query" required="false"/>
        <key id="orderby" as="order_by" type="xs:string" paramType="query" required="false"/>
        <key id="key" as="key" type="xs:string" paramType="query" required="false"/>
        <key private="true" id="v" type="xs:integer" default="2" paramType="query"/>
      </inputs>
      <execute>
      (詳細はexecuteの項目で)
      </execute>
    </select>
  </bindings>
</table>

url

よく見るとqueryの指定がない。Sampleが間違ってるんだろうかと思い色々やってみた結果、別に間違ってないらしい。つまり、keyのparamTypeにqueryが指定されている場合urlに自動で補完されるようだ。じゃあpathはどうなのかというと自動補完なんかしてくれない。何処に補完すれば良いのか分からないので当然と言えば当然。

key

key要素は前回説明したけど、今回は前回にはなく何に使うのか一目で分からない属性がついてる。

as


という定義の仕方をすると当然where句においてもwhere q="hoge"という指定の仕方となる。これでも別に構わないと言えばその通りだがいまいち分かりにくい。出来ればwhere query="hoge"としたい。しかしidはURLのquery文字列となるので変更出来ない。そこでasでkeyの別名を指定する。as="query"とした場合、where query="hoge"と出来る。

url要素内での使い方
key id="q" as="query" paramType="query"とした場合
http://hoge.com?{q}
http://hoge.com?{query}
key id="q" as="query" paramType="path"とした場合
http://hoge.com/{q}
× http://hoge.com/{query}
pathとasの組み合わせにおいてのみNGというよく分からない結果となる。

execute要素内での使い方
paramTypeに関係なくasで指定した文字列を使う。asが指定されてる状態でidの方を使うと、そんなものは定義されてないと怒られる。

上記のよく分からない結果はおそらくバグなんだろう。まあpathなんか使う事はあまりないが。

execute

JavaScriptっぽい言語を使ってdataを加工する。JavaScriptっぽい何かであってJavaScriptではない。事実色々存在しないメソッドがあるし。その辺はやりながら覚えるしかない。

if (order_by) {
  if (order_by.length<=0) {
    order_by="relevance";
  }
} else {
  order_by="relevance";
}
var atom = Namespace("http://www.w3.org/2005/Atom");
var media = Namespace("http://search.yahoo.com/mrss/");
var gd = Namespace("http://schemas.google.com/g/2005");
var yt = Namespace("http://gdata.youtube.com/schemas/2007");
var xml = request.get().response,
results = <results></results>;
if (xml) {
  for each (video_entry in xml.atom::entry) {
    var video = <video></video>,
    categories = <categories></categories>,
    tags = <tags></tags>,
    thumbnails = <thumbnails></thumbnails>,
    files = <files></files>;
    video.appendChild(<id>{video_entry.media::group.yt::videoid.toString()}</id>);
    video.appendChild(<url>{video_entry.media::group.media::player.@url}</url>);
    video.appendChild(<title>{video_entry.atom::title.toString()}</title>);
    video.appendChild(<content>{video_entry.media::group.media::description.toString()}</content>);
    video.appendChild(<author>{video_entry.atom::author.atom::name.toString()}</author>);
    video.appendChild(<duration>{video_entry.media::group.yt::duration.@seconds}</duration>);
    video.appendChild(<comment_count>{video_entry.gd::comments.gd::feedLink.@countHint}</comment_count>);
    for each (category in video_entry.media::group.media::category) {
      categories.appendChild(<category>{category.@label}</category>);
    }
    video.appendChild(categories);
    for each(tag in video_entry.media::group.media::keywords.split(',')) {
      tags.appendChild(<tag>{tag}</tag>);
    }
    video.appendChild(tags);
    for each (thumbnail in video_entry.media::group.media::thumbnail) {
      var t = <thumbnail>{thumbnail.@url}</thumbnail>;
      t.@height = thumbnail.@height;
      t.@width = thumbnail.@width;
      t.@time = thumbnail.@time;
      thumbnails.appendChild(t);
    }
    video.appendChild(thumbnails);
    for each (file in video_entry.media::group.media::content) {
      var f = <file>{file.@url}</file>;
      f.@type = file.@type;
      files.appendChild(f);
    }
    video.appendChild(files);
    results.appendChild(video);
  }
}
response.object = results;

そして最初のSampleQueryを実行するとこんなXMLが返ってくる。

<?xml version='1.0' encoding='UTF-8'?>
  <feed xmlns='http://www.w3.org/2005/Atom' xmlns:media='http://search.yahoo.com/mrss/' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:gd='http://schemas.google.com/g/2005' xmlns:gml='http://www.opengis.net/gml' xmlns:yt='http://gdata.youtube.com/schemas/2007' xmlns:georss='http://www.georss.org/georss'>
  <id>http://gdata.youtube.com/feeds/api/videos</id>
  <updated>2013-04-08T02:48:04.454Z</updated>
  <category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/>
  <title type='text'>Videos matching: louis ck</title>
  <logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo>
  <link rel='alternate' type='text/html' href='http://www.youtube.com'/>
  <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos'/>
  <link rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/batch'/>
  <link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos?q=louis+ck&amp;start-index=1&amp;max-results=25'/>
  <link rel='next' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos?q=louis+ck&amp;start-index=26&amp;max-results=25'/>
  <author>
    <name>YouTube</name>
    <uri>http://www.youtube.com/</uri>
  </author>
  <generator version='2.1' uri='http://gdata.youtube.com'>YouTube data API</generator>
  <openSearch:totalResults>1000000</openSearch:totalResults>
  <openSearch:startIndex>1</openSearch:startIndex>
  <openSearch:itemsPerPage>25</openSearch:itemsPerPage>
  <entry>
    <id>http://gdata.youtube.com/feeds/api/videos/1kVqL6X5jXk</id>
    <published>2013-03-12T18:57:48.000Z</published>
    <updated>2013-04-08T01:54:28.000Z</updated>
    <category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/>
    <category scheme='http://gdata.youtube.com/schemas/2007/categories.cat' term='People' label='ブログ'/>
    <title type='text'>Louis C.K. | Live at the Beacon Theater (2011)</title>
    <content type='text'>If you really like his shows head on over to https://buy.louisck.net, to support his future shows. Louis C.K. | Live at the Beacon Theater (2011) Copyright O...</content>
    <link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=1kVqL6X5jXk&amp;feature=youtube_gdata'/>
    <link rel='http://gdata.youtube.com/schemas/2007#video.responses' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/1kVqL6X5jXk/responses'/>
    <link rel='http://gdata.youtube.com/schemas/2007#video.related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/1kVqL6X5jXk/related'/>
    <link rel='http://gdata.youtube.com/schemas/2007#mobile' type='text/html' href='http://m.youtube.com/details?v=1kVqL6X5jXk'/>
    <link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/1kVqL6X5jXk'/>
    <author>
      <name>Sarviin Ageelen</name>
      <uri>http://gdata.youtube.com/feeds/api/users/Xcess96</uri>
    </author>
    <gd:comments>
      <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#comments' href='http://gdata.youtube.com/feeds/api/videos/1kVqL6X5jXk/comments' countHint='775'/>
    </gd:comments>
    <georss:where>
      <gml:Point>
        <gml:pos>5.391829967498779 100.38143157958984</gml:pos>
      </gml:Point>
    </georss:where>
    <yt:hd/>
    <media:group>
      <media:category label='ブログ' scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>People</media:category>
      <media:content url='http://www.youtube.com/v/1kVqL6X5jXk?version=3&amp;f=videos&amp;app=youtube_gdata' type='application/x-shockwave-flash' medium='video' isDefault='true' expression='full' duration='3764' yt:format='5'/>
      <media:content url='rtsp://v6.cache1.c.youtube.com/CiILENy73wIaGQl5jfmlL2pF1hMYDSANFEgGUgZ2aWRlb3MM/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='3764' yt:format='1'/>
      <media:content url='rtsp://v6.cache1.c.youtube.com/CiILENy73wIaGQl5jfmlL2pF1hMYESARFEgGUgZ2aWRlb3MM/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='3764' yt:format='6'/>
      <media:description type='plain'>If you really like his shows head on over to https://buy.louisck.net, to support his future shows. Louis C.K. | Live at the Beacon Theater (2011) Copyright O...</media:description>
      <media:keywords/>
      <media:player url='http://www.youtube.com/watch?v=1kVqL6X5jXk&amp;feature=youtube_gdata_player'/>
      <media:thumbnail url='http://i.ytimg.com/vi/1kVqL6X5jXk/0.jpg' height='360' width='480' time='00:31:22'/>
      <media:thumbnail url='http://i.ytimg.com/vi/1kVqL6X5jXk/1.jpg' height='90' width='120' time='00:15:41'/>
      <media:thumbnail url='http://i.ytimg.com/vi/1kVqL6X5jXk/2.jpg' height='90' width='120' time='00:31:22'/>
      <media:thumbnail url='http://i.ytimg.com/vi/1kVqL6X5jXk/3.jpg' height='90' width='120' time='00:47:03'/>
      <media:title type='plain'>Louis C.K. | Live at the Beacon Theater (2011)</media:title>
      <yt:duration seconds='3764'/>
    </media:group>
    <gd:rating average='4.918944' max='5' min='1' numRaters='3257' rel='http://schemas.google.com/g/2005#overall'/>
    <yt:statistics favoriteCount='0' viewCount='269442'/>
  </entry>
  ・・・・
</feed>

script部分を上から見ていく。

var atom = Namespace("http://www.w3.org/2005/Atom");
var media = Namespace("http://search.yahoo.com/mrss/");
var gd = Namespace("http://schemas.google.com/g/2005");
var yt = Namespace("http://gdata.youtube.com/schemas/2007");

Namespaceを複数定義している。レスポンスのXMLを見ると確かに名前空間が複数ある。流石にXMLを扱うだけあって独自実装で名前空間を扱えるようにしてるようだ。

var xml = request.get().response

今はとりあえずこれでurl要素で定義したurlのレスポンスが返ってくると覚えとけば良い。headerメソッドやpostメソッドもある。

for each (video_entry in xml.atom::entry)

JavaScriptでforeachか。まあ要はfor in loopだろうと思ったら挙動がちょっと違う。for in loopの場合指定されたオブジェクトのプロパティを列挙するが、このforeachは指定されたオブジェクト自身を列挙する。この場合はentryの子要素ではなくentry自身を順番に返す。foreachってそんな挙動だったっけ。
あと「xml.atom::entry」という指定の方法。暫く眺めてれば分かると思うけど、名前空間の指定はこうやるらしい。このSampleの様に名前空間が複数でなく1つであろうとも必ず名前空間の指定は必要。1つしかないしわかり切ってるので省略可なんて事にはならない。

video.appendChild(<id>{video_entry.media::group.yt::videoid.toString()}</id>);
video.appendChild(<url>{video_entry.media::group.media::player.@url}</url>);
video.appendChild(<title>{video_entry.atom::title.toString()}</title>);
video.appendChild(<content>{video_entry.media::group.media::description.toString()}</content>);
video.appendChild(<author>{video_entry.atom::author.atom::name.toString()}</author>);
video.appendChild(<duration>{video_entry.media::group.yt::duration.@seconds}</duration>);
video.appendChild(<comment_count>{video_entry.gd::comments.gd::feedLink.@countHint}</comment_count>);

さて、この余り見覚えのない記法はECMAScript for XML(E4X)という奴らしい。こんな物始めて知った。全てをDOMオブジェクトで扱うという世の中の流れの中で違和感がある。具体的な実装の説明はここら辺
{}は変数の代わりに使うって書いてあるが、要するに文字列や数値のベタ打ち以外は全部{}で括る。@urlはXPATHで出てくるのと一緒でattributeを表す。この{}はE4Xという記法の中で使うのであっていつもいつも{}で括るわけではない。例えば

video.appendChild(video_entry.atom::title.toString());

とする場合はXMLタグで囲まれてないので{}も必要ない。
ちなみに、「video_entry.atom::title」は「<a class="keyword" href="http://d.hatena.ne.jp/keyword/value">value</a>」というE4X文字列を返すが、「video_entry.atom::title.toString()」とすると「value」のみを返す。

YQL Open Data Table Reference Sample メモ その1

YQLは便利だ。データ整形はこれが一番速い。自分でもTableを作れるようになればもっと快適なpipesライフが送れるはず。Referenceを眺めつつ理解を深めるのも良いけど、やっぱりSampleを眺めるのが一番手っ取り早い。と言う訳でReferenceのSampleから分かる事をメモ。

Spotify Artist Search。execute要素のない基本的な形。

<?xml version="1.0" encoding="UTF-8"?>
<table xmlns="http://query.yahooapis.com/v1/schema/table.xsd">
  <meta>
    <sampleQuery>SELECT * FROM {table} where artist = 'The New Pornographers';</sampleQuery>
    <author>Max Manders</author>
    <documentationURL>http://developer.spotify.com/en/metadata-api/overview/</documentationURL>
    <description>A YQL wrapper around the Spotify Metadata API.</description>
  </meta>
  <bindings>
    <select itemPath="artists.artist" produces="XML">
      <urls>
        <url>http://ws.spotify.com/search/1/artist?q={artist}</url>
      </urls>
      <inputs>
        <key id="artist" type="xs:string" paramType="path" />
      </inputs>
    </select>
    <select itemPath="json.artists.artist" produces="JSON">
      <urls>
        <url>http://ws.spotify.com/search/1/artist.json?q={artist}</url>
      </urls>
      <inputs>
        <key id="artist" type="xs:string" paramType="path" />
      </inputs>
    </select>
  </bindings>
</table>

table

xmlns属性(名前空間)はつけておかないと実行時に怒られる。どっかから雛形をコピペしてきた時は忘れずに。

meta

特筆する事は何もない。強いて言えばsampleQuery要素の{table}くらいか。これには実行時にtable名が自動で入る。

select

select文を定義する。Referenceにはinsert、update、deleteもあるんだけどYQLからdata table自体を操作する事が果たしてあるんだろうか。

produces

YQLがweb serviceから受け取るデータ形式XML or JSONの何れかを指定。

itemPath

返すdataに対して指定する。上記の例で言うと

<artists>
  <artist>
    <name></name>
  </artist>
</artists>

というselect文の実行結果が返ってくる場合、itemPath="artists.artist"を指定すると実行結果は

<name></name>

となる。

url

特筆する事は何もなし。{artist}はkey要素で述べる。

key

tableで使用するパラメーターを指定。URL要素で{artist}という使い方をする。

paramType

query、path、header、variableの4つの値を取り得る。headerは文字通りHTTP headerの事。しかしparamType=headerを指定したsampleがない。まあheaderのfield-nameとfield-valueをセットで指定する事は少ない気もする。variableを指定した場合execute要素内で変数として使う。queryとpathの違いはselect文でwhere artist="hoge"と指定した場合、URL要素では下記の様に展開される。

paramType 戻り値
query artist=hoge
path hoge

V2エンジンでの困りごと

Url BuilderでUTF-8エンコードが出来なくなった

これはまあいい。エンコードする時なんか検索クエリを作る時くらいだから。基本はこれで、下記のようなスクリプトスクリプトマネージャーから追加して使う。

function queryencode(str) {
	return encodeURI(str).replace(/%20/gi, "+")
}

これでqueryencode関数がSpreadSheetで使えるようになる。上記の物はUTF-8だがEUC-JPでもSHIFT-JISでも好きな物を追加すれば良い。JavaScriptだからコード自体はぐぐれば出てくるだろう。

Date Builderの挙動がおかしい

問題はこれ。

2011-1-1 2011-1-1 00:00
2011-1-1 JST 2011-1-1 00:00
2011-1-1 00:00 JST 2010-12-31 15:00

要するに日本標準時を指定したければ必要なくても時刻指定必須。

2011-1-1 26:00 認識しない
2011-1-32 認識しない

これも地味に痛い。日付計算がやり辛くなった。V1の頃はこれでちゃんと認識してくれたのに。
どうしてもやりたければYEAR、MONTH、DAY、HOUR、MINUTEと5要素に分解してUTIMEを計算するしか現状無さげ。
ああ、マジでどうしよう。

GoogleDocsをPipeの入力テーブルとして使う

検索結果をRSSとして取得するというのはよくある。要するにニコ動なんだけど。しかしString Builderで入力データ作ってString Tokenizerで複数itemに分割というのは一覧性があまりよろしくない。とか思ってると、「GoogleスプレッドシートとYahooPipesで簡易CMSを作る」と言うのを見つけたので試しにやってみた。

  1. まずは、Google Docsスプレッドシートを作ってデータを入力。
  2. 画面右上の共有メニューから「共有設定」を選ぶ。ファイル設定画面が出てくるので「変更」をクリックする。出てきた公開設定オプションで「リンクを知っている全員」に変更する。
  3. 画面右上の共有メニューから(2011-10-23追記)UIデザインが変わってファイルメニューから「ウェブページとして一般公開」を選ぶ。公開するシートを選べるので変える必要があるなら変える。下のメニューから各形式のリンクが取得できる。取得する形式はatomとかRSSも選べるけど結局csvが使いやすい。リンクはhttpsになってるのでsを削っておく。Yahoo Pipeshttpsをサポートしていない。

という流れになる。このやり方は中々使い勝手が良い。複数の属性を持つitemを複数作るのは面倒なんだけどこれなら楽で良い。検索クエリとして使うからにはUTF-8などでエンコードしておく必要があるので、CSVのほうにエンコードされたテキストを書いておく。ちなみに、公開設定が「リンクを知っている全員」の場合は検索のインデックスに登録されないらしい。

当たり前だけど単なる文字列置換はGoogle Docsのほうが得意。IF関数だってあるし。blogのRSSをマージするだけならともかく、feedのurlを作るまでの道のりが長いようなのは、url作成までやってPipeに渡した方が速い。