Google Apps ユーザーフォーラムでSidebarガジェットの発表をしてきました(前編)
Google Apps ユーザーフォーラムへ行ってきたよ
会社の人事総務部で導入しているGoogleAppsのユーザフォーラムが開催(主催:株式会社電算システム様)されたので、ちょっとお手伝いに行ってきてついでに(?)発表してきました。
弊社では、社員の内線番号を氏名から検索できるGmail Sidebar ガジェットを導入しています。
このお話を5分程度させていただいたところ、思いの外好評でしたので、そのノウハウを書いておきます。
今回作ってみるガジェットについて
GoogleSpreadSheetにデータを保存し、そのデータをSQLのような方法で取得し、検索をするという動作をするものです。
Gmail Sidebar ガジェットについて
Sidebar ガジェットは、GmailのLabsを有効にし、”Add any gadget by URL” という機能を有効にして利用します。ここにガジェット自体のXMLを登録することで、サイドバーに表示されるというものです。ガジェットは一般的なガジェット(iGoogleなどに追加できるガジェット)のすべてを登録できます。
今回はその自作ガジェットの超基本についてです。
極めてシンプルなガジェットを作ってみました。
<?xml version="1.0" encoding="utf-8"?> <Module> <ModulePrefs title="はじめてのSideBarGadget" description="サイドバーにhello gadgetと時刻を表示します" author="ITO Tetsunosuke" author_email="tetsunosuke.ito@photocreate.co.jp" /> <Content type="html"> <![CDATA[ <p>hello gadget!</p> <p id="time"></p> <script type="text/javascript"> // 時刻を生成して、id="time"の中に表示 var d = new Date(); var textNode = document.createTextNode(d.toString()); document.getElementById("time").appendChild(textNode); </script> ]]> </Content> </Module>
CDATAの中にHTMLやJavaScriptを書くことができます。
このガジェットファイルをXMLとして保存し、Webサーバにアップします。今回はHTTPS領域にファイルを置きたいため、DropBoxを利用しました。ファイルの実体はこちらにあります:https://dl.dropbox.com/u/2374674/googleappsuserforum/googlesidebargadget.xml
SpreadSheetにアクセスする
「ガジェットから」を一旦忘れ、単なるHTML+JavaScriptでGoogleSpreadSheetとのデータのやりとりをしてみます。
SpreadSheetとのやりとりは、Visualization APIを用います。
まずは仮のデータとして日本の歴代総理大臣のシートを作った(けど10代で挫折した)ので、こちらのデータを取得して表示してみるプログラムを作ってみます。
歴代総理大臣のシート: https://docs.google.com/spreadsheet/ccc?key=0AvdfXiBhjpT3dHdNY1ZIV053Nnd6d0s5Y240Z0h1Znc#gid=0
プログラムはこちらで実行、ソースの確認ができます:https://dl.dropbox.com/u/2374674/googleappsuserforum/spreadsheet.html
<html> <head> <script type="text/javascript" src="https://www.google.com/jsapi"></script> <script type="text/javascript"> google.load("visualization", "1"); google.load("jquery", "1.4.2"); // 読み込み完了したらinitメソッドを呼ぶ google.setOnLoadCallback(init); // クエリ実行後のコールバック function handleQueryResponse(response) { var data = response.getDataTable(); // 結果を一行ずつ処理 for (var row = 0; row < data.getNumberOfRows(); row++) { a = data.getFormattedValue(row, 0); // 1列目つまりA列 b = data.getFormattedValue(row, 1); // 2列目つまりB列 c = data.getFormattedValue(row, 2); // 3列目つまりC列 line = ("苗字=" + a + "," + "名前=" + b + "," + "番号=" + c); p = "<p>" + line + "</p>"; // HTMLのid="result"にこの結果を追加していく $("#result").append(p); } } // 読み込み完了時に実行される function init() { // スプレッドシートのURL var spreadsheet_url = "https://docs.google.com/spreadsheet/ccc?key=0AvdfXiBhjpT3dHdNY1ZIV053Nnd6d0s5Y240Z0h1Znc"; var query = new google.visualization.Query(spreadsheet_url); // 引っ張ってくる列 query.setQuery("select A, B, C"); // クエリを実行し、handleQueryResponseメソッドを実行 query.send(handleQueryResponse); } </script> </head> <body> <div id="result"></div> </body> </html>
今回利用するのはVisualizationAPIですが、その他にHTMLに対して簡単にアクセスしたいため、jQUeryも利用することにします。Google AJAX API を使えば、これらのライブラリを簡単に利用することができます。
それがこの部分です。
<script type="text/javascript" src="https://www.google.com/jsapi"></script> <script type="text/javascript"> google.load("visualization", "1"); google.load("jquery", "1.4.2"); // 読み込み完了したらinitメソッドを呼ぶ google.setOnLoadCallback(init);
google.setOnLoadCallbackで、読み込み完了後に実行される処理、つまり今回行いたい処理を記載します。
細かいことは一旦気にせず、スプレッドシートのURLを用いて、その表をまさにデータベースのテーブルの用に扱い、SQLのようなクエリでデータを検索、クエリを実行して結果が取得できたら、その結果を用いて一行ずつデータを取りだしていく、という処理を行なっています。
VisualizationAPIを使うと、非常に簡易的なデータベースを構築できるので、こういった用途にはオススメです。
これらを組み合わせて、内線表ガジェットができていくのですが、続きは後編で。
参考資料
- Gadgets API: https://developers.google.com/gadgets/?hl=ja
- Google Visualization API Reference - Google Chart Tools: https://developers.google.com/chart/interactive/docs/index
- Google Loader: https://developers.google.com/loader/
数値の割り算でハマる
「PHPとPostgreSQLに投げてfloorを計算させたら結果が違うんですっ!」
とか言われてちょっと何言ってるかわからない状態になった。
どうもこういうことらしい。int(に見えるリテラル)をintで割ったときの挙動の問題のようだ。
PHPの floor(-155/10)の結果
$ php -r "echo floor(-155/10);" -16
PostgreSQLのfloor(-155/10) の結果
# select floor(-155/10); floor ------- -15 (1 row)
おっとっと。
というわけで、floorをかける前の値が何になっているか調べてみる。
# select -155/10, 1555/10.0, floor(-155/10); ?column? | ?column? | floor ----------+----------------------+------- -15 | 155.5000000000000000 | -15 (1 row)
PHPでは
$ php -r "var_dump(-155/10);" float(-15.5)
なるほど。PostgreSQLはint同士の計算をintに変換するがPHPではしないのだな。
ちょっと気になったので他の言語でもやってみる
Ruby:
irb(main):001:0> (-155/10).floor => -16 irb(main):002:0> -155/10 => -16
>>> import math >>> math.floor(-155/10) -16.0 >>> -155/10 -16 # -15になる処理系も確認されている。詳細不明
JavaScript:
> -155/10 -15.5 > Math.floor(-155/10) -16
JavaScriptにはそもそもNumberという概念しかないから型変換もクソもないようだ。
(メモ) jsTestDriver で HtmlDocを使う
jsTestDriverではテストコードの中にHTMLのDOM要素へアクセスできるらしいので、やってみた。下記のように書く。
TestCase("domTest", { setUp: function() { }, tearDown: function() { }, "test inner div document": function() { /*:DOC div = <div><p id="foo">bar</p></div>*/ tagP = this.div.getElementsByTagName("p"); assertEquals("bar", tagP[0].innerText); } });
ポイントは、DOCで宣言したものには "this.div"のようにして参照するということ。
つまり、たとえば
function getDivValue(id) { return document.getElementById(id).innerText; }
みたいな関数を書いてしまうと、documentっていうグローバル変数にアクセスするため、テストしづらいということになりそうな感じ。
function getDivValue(dom, id) { return dom.getElementById(id).innerText; }
みたいにしろってこと??
そうすれば
TestCase("domTest", { "test inner div document": function() { /*:DOC div = <div><p id="foo">bar</p></div>*/ assertEquals("bar", getDivValue(this.div, "foo")); } });
こんな感じに書けるということかな・・・?
ファイルからメソッドを抜き出して分割しまくってやるぜ
複数のファイルに全く同じメソッドを発見してしまいキレたの巻。
メソッドごとに切り出してそれが同じものであればコピペしやがったなこのやろうしけいだ!ってできるかな、と思った。まあ昨日の流れでphpcpdとかでチェックする話なのだろうけど、いざチェックしたとして、それを関数レベルでリファクタリングしていくのに有用ではないか、と感じたわけです。
とりあえず分解対象のクラスを作る。
<?php class Sample { public function __construct() { echo "__construct"; } private function privateFunction($param1) { echo "privateFunction"; } public function publicFunction($param1) { echo "publicFunction"; } /** * staticなFunctionです */ public static function staticFunction($param1, $param2 = 10) { echo "staticFunction"; } }
下記のコードでそれを分解します。
ファイルを読み込んでZend_Reflection_Fileを使うと、そのファイルの中にいくつクラスがあるか?などを調べてくれます。
その中で、クラスから関数を探索し、その関数の属性にあたる情報を下記のようにして取得できます。
<?php $path = "Sample.php" require $path; $autoloader = Zend_Loader_Autoloader::getInstance(); $ref = new Zend_Reflection_File($path); // 後で行レベルで内容を調べるため保持 $lines = file($path); foreach($ref->getClasses() as $cls) { // Zend_Reflection_Method $methods = $cls->getMethods(); foreach($methods as $method) { // 修飾子 $modifiers = join(" ", Reflection::getModifierNames($method->getModifiers())); // 関数名 $name = $method->getName(); // パラメータ $parameterNamesArray = array(); $params = $method->getParameters(); foreach($params as $param) { $parameterNamesArray[] = "$" . $param->getName(); } $parameters = join(", ", $parameterNamesArray); // シグネチャ $signature = sprintf("%s function %s (%s)", $modifiers, $name, $parameters); // 本体は行レベルで取得する $body = join("", array_slice($lines, $method->getStartLine(), $method->getEndLine() - $method->getStartLine())); // 出力 echo $signature . "\n" . $body . PHP_EOL; } }
タブとかはもちろん吹き飛んでしまうので出力はこんな具合になる。
public static function staticFunction ($param1, $param2) { echo "staticFunction"; }
これを何らかの命名規則のファイル(filepath_class_method.txtみたいな)に書きこんで、md5sumとかdiffとかで調べていけば、共通している部分の抽出ができるのではないか、と思う。
ちなみにこれをかけてみたら200行にも渡るファイルが発見され、それはそれでどうなんだ、ってなってるとこ。
phpcpd だとCopy Paste Detectをあまりしてくれない件、でjenkinsで困った。
phpcpdとpmd
とりあえずコピペした下記のような2つのクソコードを作ってみる。
Dollar.php
<?php class Dollar { private $prefix; private $value; public function __construct($value) { $this->prefix = "$"; $this->value = $value; } public function __toString() { return $this->prefix . $this->value; } }
Yen.php
<?php class Yen { private $prefix; private $value; public function __construct($value) { $this->prefix = "\\"; $this->value = $value; } public function __toString() { return $this->prefix . $this->value; } }
これをチェックしよう、と思うがとても残念なことになった。むしろ重複しまくっていると検出してくれないのか??
$ phpcpd . phpcpd 1.3.5 by Sebastian Bergmann. 0.00% duplicated lines out of 36 total lines of code. Time: 0 seconds, Memory: 1.25Mb
というわけで、本家pmdを使うことにする。
$ run.sh cpd --files . --minimum-tokens 75 --language php Found a 7 line (78 tokens) duplication in the following files: Starting at line 9 of /var/lib/jenkins/jobs/test/workspace/svn/src/Test/./Dollar.php Starting at line 9 of /var/lib/jenkins/jobs/test/workspace/svn/src/Test/./Yen.php $this->prefix = "$"; $this->value = $value; } public function __toString() { return $this->prefix . $this->value; ===================================================================== Found a 5 line (75 tokens) duplication in the following files: Starting at line 5 of /var/lib/jenkins/jobs/test/workspace/svn/src/Test/./Dollar.php Starting at line 5 of /var/lib/jenkins/jobs/test/workspace/svn/src/Test/./Yen.php private $prefix; private $value; public function __construct($value) { $this->prefix = "$";
jenkinsのタスクに入れよう
で、これをjenkins経由で入れようとして、build.xmlを変更。
<target name="cpd" description="Find duplicate code using PMD-CPD"> <exec executable="pmd/bin/run.sh"> <arg value="cpd" /> <arg value="--language php" /> <arg value="--minimum-tokens 75" /> <arg value="--files ${basedir}/svn/src" /> <arg value="--format xml" /> <arg value=" > ${basedir}/build/logs/pmd-cpd.xml" /> </exec> </target>
ところが、これだと、 "> ファイル" の部分がうまく動いてくれない。
(pmdにそもそもファイルに出力するオプションがあればいいのに標準出力をパイプしないといけない)
おそらくその原因はpmd以下のrun.sh が原因。
java "${HEAPSIZE}" -cp "${classpath}" "${CLASSNAME}" ${@}
こんな具合にしたら通った
command="java ${HEAPSIZE} -cp ${classpath} ${CLASSNAME} ${@}" $command
$ ant cpd Buildfile: build.xml cpd: BUILD SUCCESSFUL Total time: 0 seconds
分散分析を行う
下記のような表があったとき(はてな記法でcolspanのやつとかできないのね!めんどくさい!)
要因1,要因2の影響について調べる。
要因1 | ||||
---|---|---|---|---|
要因2 | A | B | C | D |
a | 34 | 41 | 52 | 56 |
b | 35 | 46 | 55 | 62 |
c | 40 | 53 | 56 | 63 |
このデータ、その形のままだとRに取り込みにくいのでこんな形の単純な表(データフレーム)にする。
factor1 factor2 data 1 a A 34 2 a B 41 3 a C 52 4 a D 56 5 b A 35 6 b B 46 7 b C 55 8 b D 62 9 c A 40 10 c B 53 11 c C 56 12 c D 63
そのためには下記のように。関数repは同じ回数分繰り返したデータを作ってくれるので、aを連続したければ rep("a", n)のようにする。
> factor1 = c(rep("a", 4), rep("b", 4), rep("c", 4)) > factor2 = rep(c("A", "B", "C", "D"),3) > data = c(34, 41,52,56,35,46,55,62,40,53,56,63) > data > f = data.frame(f1=factor1, f2=factor2, data)
これに対してaovで分散分析を行う
> summary(aov(data~f1+f2, data=f)) Df Sum Sq Mean Sq F value Pr(>F) f1 2 105.2 52.6 12.37 0.00743 ** f2 3 966.3 322.1 75.78 3.68e-05 *** Residuals 6 25.5 4.2 --- Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1
すると、P値が5%より低く、f1もf2もこの要因として適している、と分析できる。多分この***の多いf2の要因のほうが強いのだろう。
試しに、明らかに要因1の影響しか受けないこんなデータでやると、
> data = c(1, 1,1,1,2,2,2,2,3,3,3,3) > f = data.frame(f1=factor1, f2=factor2, data) > f f1 f2 data 1 a A 1 2 a B 1 3 a C 1 4 a D 1 5 b A 2 6 b B 2 7 b C 2 8 b D 2 9 c A 3 10 c B 3 11 c C 3 12 c D 3 > summary(aov(data~f1+f2, data=f)) Df Sum Sq Mean Sq F value Pr(>F) f1 2 8 4 3.865e+31 <2e-16 *** f2 3 0 0 1.000e+00 0.455 Residuals 6 0 0 --- Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘
f1のP値のみが小さく、f2は45%とかなり大きいのでf2の影響はないと言える
このデータを回帰分析してみよう
要するにa,b, A,B,C のみでこのデータは決まるわけなので、数量化理論一類にて、下記のような表と見なせる
a | b | A | B | C | data |
1 | 0 | 1 | 0 | 0 | 34 |
1 | 0 | 0 | 1 | 0 | 41 |
1 | 0 | 0 | 0 | 1 | 52 |
1 | 0 | 0 | 0 | 0 | 56 |
0 | 1 | 1 | 0 | 0 | 35 |
0 | 1 | 0 | 1 | 0 | 46 |
0 | 1 | 0 | 0 | 1 | 55 |
0 | 1 | 0 | 0 | 0 | 62 |
0 | 0 | 1 | 0 | 0 | 40 |
0 | 0 | 0 | 1 | 0 | 53 |
0 | 0 | 0 | 0 | 1 | 56 |
0 | 0 | 0 | 0 | 0 | 63 |
これをデータフレームにして
> a = c(1,1,1,1,0,0,0,0,0,0,0,0) > b = c(0,0,0,0,1,1,1,1,0,0,0,0) > A = c(1,0,0,0,1,0,0,0,1,0,0,0) > B = rep(c(0,1,0,0), 3) # 早速使ってみた > C = rep(c(0,0,1,0), 3) > data.frame(a=a,b=b,A=A,B=B,C=C, data=data) a b A B C data 1 1 0 1 0 0 34 2 1 0 0 1 0 41 3 1 0 0 0 1 52 4 1 0 0 0 0 56 5 0 1 1 0 0 35 6 0 1 0 1 0 46 7 0 1 0 0 1 55 8 0 1 0 0 0 62 9 0 0 1 0 0 40 10 0 0 0 1 0 53 11 0 0 0 0 1 56 12 0 0 0 0 0 63
線形回帰分析する
> summary(lm(data~a+b+A+B+C)) Call: lm(formula = data ~ a + b + A + B + C) Residuals: Min 1Q Median 3Q Max -2.0000 -1.0417 -0.2917 1.3333 2.7500 Coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) 63.917 1.458 43.846 9.42e-09 *** a -7.250 1.458 -4.973 0.002518 ** b -3.500 1.458 -2.401 0.053224 . A -24.000 1.683 -14.258 7.44e-06 *** B -13.667 1.683 -8.119 0.000187 *** C -6.000 1.683 -3.565 0.011862 * --- Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1 Residual standard error: 2.062 on 6 degrees of freedom Multiple R-squared: 0.9768, Adjusted R-squared: 0.9574 F-statistic: 50.42 on 5 and 6 DF, p-value: 8.03e-05
b,Cを抜いて
> summary(lm(data~a+A+B)) Call: lm(formula = data ~ a + A + B) Residuals: Min 1Q Median 3Q Max -4.1667 -2.6667 -0.1667 2.4583 4.5000 Coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) 59.167 1.646 35.952 3.92e-10 *** a -5.500 2.208 -2.491 0.03746 * A -21.000 2.550 -8.237 3.54e-05 *** B -10.667 2.550 -4.184 0.00306 ** --- Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1 Residual standard error: 3.606 on 8 degrees of freedom Multiple R-squared: 0.9052, Adjusted R-squared: 0.8696 F-statistic: 25.46 on 3 and 8 DF, p-value: 0.0001912
data = -5.5 * a - 21 * A - 10 * B + 59.167 の回帰式を得ることができた。
たとえば aとCの要因があるデータは、 data = -5.5 + 59.167 = 53.667 と求めることができるし、(実データは52)、cとDの場合は59.167(実データは63)となる。
等分散でないときのt検定(ウェルチのt検定)
同様に、下記のようなデータに対して分析をしてみます。
> a [1] 8.81 8.35 8.62 9.11 8.38 9.15 9.22 8.20 9.38 7.57 > b [1] 8.27 8.05 8.32 8.08 8.95 8.22 7.81 8.43 8.21 8.17
まずはF検定
> var.test(a, b) F test to compare two variances data: a and b F = 3.5907, num df = 9, denom df = 9, p-value = 0.07053 alternative hypothesis: true ratio of variances is not equal to 1 95 percent confidence interval: 0.8918783 14.4561248 sample estimates: ratio of variances 3.590697
検定の結果、両側検定のP値が10%を下回っているので、AとBは等分散ではないとみなせます。
この場合、t検定はvar.equal=T を付けないt.testを用います。実行すると「Welch」というのが出てきますね。
> t.test(a, b) Welch Two Sample t-test data: a and b t = 2.12, df = 13.652, p-value = 0.05284 alternative hypothesis: true difference in means is not equal to 0 95 percent confidence interval: -0.006049496 0.862049496 sample estimates: mean of x mean of y 8.679 8.251
ここでの両側検定のP値は0.05284なので、片側に直すと、0.02642。したがって5%以下なので、Bの平均がAの平均と等しい仮説は棄却され、Bの平均がAの平均よりも小さいといえます。