ローソンでAmazonのギフト券を買ってみた


ローソンでAmazonのギフト券が販売されていたので、買ってみました。

プレゼントや自己使用で利用しようと思っているひと向けに、ギフト券の内容や、実際の登録方法等を説明します。
今の時期だと、入社や入学祝いなどで利用する人も多いかもしれませんね。


ギフト券の販売場所

ギフト券のカードはローソンと、イオン・スリーエフで販売しています。

カードではなく、紙切れに印刷してある形式でも良ければ、セブンイレブンや、ファミリーマート・ミニストップ・サークルKでも購入可能です。他に、ネットで買うのであれば当然Amazonでも購入可能です。
ちなみに、登録時に必要なのはギフト券番号だけなので、カードを買ってもギフト券番号だけ伝えればOKです。登録時にカード自体は不要なので、メールでプレゼントすることも可能です。

ローソンで販売しているギフト券はこんな感じ

店頭で販売されている状態です。


こちらは裏面。


裏面に記載されている利用細則のアップです。



紙のカバーを開いた状態です。
上面にカードの登録方法と、メッセージを書く欄が有ります。


カードの登録方法アップです。登録は、http://www.amazon.co.jp/giftcard/useから行います。


カード本体の表面。プラスチックです。


カード裏面。ギフト券番号のところはスクラッチで隠されています。



スクラッチを削った後の状態。ギフト券番号は15桁の英数字で構成されています。



金額

3,000円、5,000円、10,000円が有ります。また、イオン限定で1500/20000円のカードもあるようです。

有効期限

有効期限は、購入日から1年間です。
登録日ではなく買った日が基準なので、事前に購入しておく場合は注意が必要です。

購入方法

ローソンで購入する場合は、店頭にカードが置いてあるので、レジに持って行くだけです。

購入時に、クレジットカード決済は出来ません。
また、ローソンではポイントカードとしてPontaがありますが、2012/01現在、カードを提示しても来店ポイントのみが付与される様です。


登録方法

カードのに登録方法が記載されているので、手順どおり作業します。

Amazonギフト券のご利用方法
  1. http://www.amazon.co.jp/giftcard/useにアクセスする。
  2. サインインする
  3. ギフト券番号を入力し、「アカウントに登録する」をクリックする。
  4. 登録されたギフト券の金額が画面右側に表示されます。
  5. ショッピングをお楽しみください。



1.ギフト券の登録ページにアクセスする。

以下のURLをクリックします。
http://www.amazon.co.jp/giftcard/use

2.サインインする

メールアドレスと、パスワードを入力し、サインインのボタンをクリックします。
ユーザ登録がまだの場合は、”初めて利用します”を選択します



3.ギフト券番号を入力し、「アカウントに登録する」をクリックする。

番号の入力画面が表示されます。


カード裏面にある番号を入力し、登録するボタンをクリックします。


4.登録されたギフト券の金額が画面右側に表示されます。


番号の入力が正しければ、以下のメッセージが表示されます。


Amazonギフト券がお客様のアカウントに追加されました。
右下の「登録したギフト券金額」に表示されている金額がアカウントに登録されました。
「現在のギフト券残高」を商品の購入時に利用することが出来ます。
なお、商品購入時にギフト券番号を再度入力する必要はありません。





ギフト券が登録されたか確認する

登録された内容は、アカウントサービスページより確認できます。

ログイン後、画面右上のリンクをクリックします。


支払い方法の設定から、ギフト券の「残高・利用履歴を確認する」を選択します。



登録が成功していれば、利用履歴の一覧に登録の行が追加されています。




商品購入時のポイント利用する

代引きやカードで購入時と同じように購入手続きを行えば、自動的にギフト券が優先して使用されます。
ギフト券の金額より、購入代金のほうが高い場合は、差額分だけ支払いが必要です。


WordPressが接続しているMySQLのパスワードが分からなくなったときの調べ方

WordPressをMySQLで使用しているとき、DBのパスワードが分からなくなった際のチェック方法です。

サーバのWordPressのインストールフォルダにあるファイル、「wp-config.php」を開くと、プレーンテキストで記載されています。

下記の例だと、30行目が相当します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/** 
 * WordPress 基本設定
 *
 * このファイルは、MySQL、テーブル接頭辞、秘密鍵、言語、ABSPATH の設定を含みます。
 * より詳しい情報は {@link http://wpdocs.sourceforge.jp/wp-config.php_%E3%81%AE%E7%B7%A8%E9%9B%86 
 * wp-config.php の編集} を参照してください。MySQL の設定情報はホスティング先より入手できます。
 *
 * このファイルはインストール時に wp-config.php 作成ウィザードが利用します。
 * ウィザードを介さず、このファイルを "wp-config.php" という名前でコピーして直接編集し値を
 * 入力しても構いません。
 *
 * @package WordPress
 */
 
// 注意:
// Windows の "メモ帳" でこのファイルを編集しないでください !
// 問題なく使えるテキストエディタ
// (http://wpdocs.sourceforge.jp/Codex:%E8%AB%87%E8%A9%B1%E5%AE%A4 参照)
// を使用し必ず UTF-8 の BOM なし (UTF-8N) で保存してください。
 
 
// ** MySQL 設定 - こちらの情報はホスティング先から入手してください。 ** //
/** WordPress のデータベース名 */
define('DB_NAME', 'dbname');
 
/** MySQL のユーザー名 */
define('DB_USER', 'user');
 
/** MySQL のパスワード */
define('DB_PASSWORD', 'xxxx');                             <=================================ここ
 
/** MySQL のホスト名 (ほとんどの場合変更する必要はありません。) */
define('DB_HOST', 'db-hostname');
 
/** データベーステーブルのキャラクターセット (ほとんどの場合変更する必要はありません。) */
define('DB_CHARSET', 'utf8');
 
/** データベースの照合順序 (ほとんどの場合変更する必要はありません。) */
define('DB_COLLATE', '');


[WordPress]各ページに対して適用されるテンプレートの優先順

WordPressでテーマを使用している際の、各種ページに対するテンプレートの適用優先順です。

表中のスラッグは各ページに対する名称のことで、投稿の編集ページ上部にある”表示オプション”のチェックをONにすると指定可能になります。

また、表より分かるように、どのページであっても最終的にはindex.phpが適用されます。

優先順一覧(数字が小さいほど優先度が高い)

メイン
ページ
個別
ページ
固定
ページ
カテゴリ
アーカイブ
日付
アーカイブ
タグ
アーカイブ
home.php1
single-投稿タイプ.php1
single.php2
カスタムテンプレート1
page-スラッグ.php2
page-ページID.php3
page.php4
date.php1
category-スラッグ.php1
category-カテゴリID.php2
category.php3
tag-スラッグ.php1
tag-タグID.php2
tag.php3
archive.php424
index.php235535

[JavaScript]windowオブジェクトが保持しているプロパティ一覧を列挙する

ブラウザで動作するJavaScriptでは、windowというグローバル変数が有ります。
この変数は文字通りブラウザのウィンドウを意味する変数です。


window変数がどんなプロパティを持っているか気になったので、確認するスクリプトを作ってみました。

<input id="btnInput" type="button" value="表示" />
< pre id="result"></pre >
 
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js">
</script>
<script>
$(document).ready(function(){
    // 入力ボタンがクリックされたときのハンドラ
    $("#btnInput").click(function() {
        for( var prop in window ) {
            if ( prop == null || prop == "sessionStorage" ) {
                continue;
            }
 
            // データ型の取得
            var typeStr = typeof window[ prop ];
 
            // 値の取得
            var value = "";
            if ( window[ prop ] != null ) {
                value   = window[ prop ].toString().replaceAll( "\n", "" );
            }
 
            // 直接保持しているプロパティか否かを判定
            var info = "     ";
            if ( window.hasOwnProperty( prop ) == false ) {
                info = "継承 ";
            }
 
            // 取得した情報を出力
            info += "型:" + typeStr + " " + prop + "  " + value + "\n";
            $( "#result" ).text( $( "#result" ).text() + info );
 
        }
    });
 
    String.prototype.replaceAll = function (org, dest){  
      return this.split(org).join(dest);  
    } 
});



スクリプトのポイントを幾つか説明します。




        for( var prop in window ) {


windowオブジェクトが持つプロパティは、上記のforで列挙可能です。これは実際はwindowは連想配列の形になっており、keyがプロパティ名、valueがプロパティ値となっているからです。




            var typeStr = typeof window[ prop ];


データ型はtypeofで取得可能です。typeof演算子の戻り値は文字列型となります。




            if ( window.hasOwnProperty( prop ) == false ) {


取得したプロパティは、自身で定義したものと継承元クラス(javascriptの場合,厳密にはプロトタイプ)で定義済みのものが有ります。各プロパティが、どちらであるかはhasOwnProperty()で確認可能です。



ちなみに、手元の環境(firefox9.0.1)で実行したところ、以下のプロパティを保持していました。
結構沢山有りますね。

     型:object   window  [object Window]
     型:object   navigator  [object Navigator]
     型:object   document  [object HTMLDocument]
     型:object   InstallTrigger  [object Object]
     型:object   console  [object Object]
     型:object   location  file:///C:/test.html
     型:function $  function (a, b) {    return new e.fn.init(a, b, h);}
     型:function jQuery  function (a, b) {    return new e.fn.init(a, b, h);}
     型:object   qlk  [object Object]
     型:function getInterface  function getInterface() {    [native code]}
継承 型:function addEventListener  function addEventListener() {    [native code]}
継承 型:function removeEventListener  function removeEventListener() {    [native code]}
継承 型:function dispatchEvent  function dispatchEvent() {    [native code]}
継承 型:function dump  function dump() {    [native code]}
継承 型:string   name  
継承 型:object   parent  [object Window]
継承 型:object   top  [object Window]
継承 型:object   localStorage  [object Storage]
継承 型:object   globalStorage  [object StorageList]
継承 型:function getComputedStyle  function getComputedStyle() {    [native code]}
継承 型:function getSelection  function getSelection() {    [native code]}
継承 型:function scrollByLines  function scrollByLines() {    [native code]}
継承 型:object   self  [object Window]
継承 型:object   history  [object History]
継承 型:object   locationbar  [object BarProp]
継承 型:object   menubar  [object BarProp]
継承 型:object   personalbar  [object BarProp]
継承 型:object   scrollbars  [object BarProp]
継承 型:object   statusbar  [object BarProp]
継承 型:object   toolbar  [object BarProp]
継承 型:string   status  
継承 型:function close  function close() {    [native code]}
継承 型:function stop  function stop() {    [native code]}
継承 型:function focus  function focus() {    [native code]}
継承 型:function blur  function blur() {    [native code]}
継承 型:number   length  0
継承 型:object   opener  
継承 型:object   frameElement  
継承 型:object   applicationCache  [object OfflineResourceList]
継承 型:function alert  function alert() {    [native code]}
継承 型:function confirm  function confirm() {    [native code]}
継承 型:function prompt  function prompt() {    [native code]}
継承 型:function print  function print() {    [native code]}
継承 型:function showModalDialog  function showModalDialog() {    [native code]}
継承 型:function postMessage  function postMessage() {    [native code]}
継承 型:function atob  function atob() {    [native code]}
継承 型:function btoa  function btoa() {    [native code]}
継承 型:function matchMedia  function matchMedia() {    [native code]}
継承 型:object   screen  [object Screen]
継承 型:number   innerWidth  1136
継承 型:number   innerHeight  311
継承 型:number   scrollX  0
継承 型:number   pageXOffset  0
継承 型:number   scrollY  0
継承 型:number   pageYOffset  0
継承 型:function scroll  function scroll() {    [native code]}
継承 型:function scrollTo  function scrollTo() {    [native code]}
継承 型:function scrollBy  function scrollBy() {    [native code]}
継承 型:number   screenX  290
継承 型:number   screenY  15
継承 型:number   outerWidth  1154
継承 型:number   outerHeight  795
継承 型:function scrollByPages  function scrollByPages() {    [native code]}
継承 型:function sizeToContent  function sizeToContent() {    [native code]}
継承 型:object   content  [object Window]
継承 型:boolean  closed  false
継承 型:object   crypto  [object Crypto]
継承 型:object   pkcs11  
継承 型:object   controllers  [object XULControllers]
継承 型:string   defaultStatus  
継承 型:number   mozInnerScreenX  299
継承 型:number   mozInnerScreenY  185
継承 型:number   scrollMaxX  385
継承 型:number   scrollMaxY  1037
継承 型:boolean  fullScreen  false
継承 型:function back  function back() {    [native code]}
継承 型:function forward  function forward() {    [native code]}
継承 型:function home  function home() {    [native code]}
継承 型:function moveTo  function moveTo() {    [native code]}
継承 型:function moveBy  function moveBy() {    [native code]}
継承 型:function resizeTo  function resizeTo() {    [native code]}
継承 型:function resizeBy  function resizeBy() {    [native code]}
継承 型:function updateCommands  function updateCommands() {    [native code]}
継承 型:function find  function find() {    [native code]}
継承 型:number   mozPaintCount  21
継承 型:function mozRequestAnimationFrame  function mozRequestAnimationFrame() {    [native code]}
継承 型:number   mozAnimationStartTime  1327684982160
継承 型:object   URL  [object MozURLProperty]
継承 型:object   onafterprint  
継承 型:object   onbeforeprint  
継承 型:object   onbeforeunload  
継承 型:object   onhashchange  
継承 型:object   onmessage  
継承 型:object   onoffline  
継承 型:object   ononline  
継承 型:object   onpopstate  
継承 型:object   onpagehide  
継承 型:object   onpageshow  
継承 型:object   onresize  
継承 型:object   onunload  
継承 型:object   ondevicemotion  
継承 型:object   ondeviceorientation  
継承 型:function setTimeout  function setTimeout() {    [native code]}
継承 型:function setInterval  function setInterval() {    [native code]}
継承 型:function clearTimeout  function clearTimeout() {    [native code]}
継承 型:function clearInterval  function clearInterval() {    [native code]}
継承 型:function setResizable  function setResizable() {    [native code]}
継承 型:function captureEvents  function captureEvents() {    [native code]}
継承 型:function releaseEvents  function releaseEvents() {    [native code]}
継承 型:function routeEvent  function routeEvent() {    [native code]}
継承 型:function enableExternalCapture  function enableExternalCapture() {    [native code]}
継承 型:function disableExternalCapture  function disableExternalCapture() {    [native code]}
継承 型:function open  function open() {    [native code]}
継承 型:function openDialog  function openDialog() {    [native code]}
継承 型:object   frames  [object Window]
継承 型:object   onabort  
継承 型:object   onblur  
継承 型:object   oncanplay  
継承 型:object   oncanplaythrough  
継承 型:object   onchange  
継承 型:object   onclick  
継承 型:object   oncontextmenu  
継承 型:object   ondblclick  
継承 型:object   ondrag  
継承 型:object   ondragend  
継承 型:object   ondragenter  
継承 型:object   ondragleave  
継承 型:object   ondragover  
継承 型:object ondragstart  
継承 型:object ondrop  
継承 型:object ondurationchange  
継承 型:object onemptied  
継承 型:object onended  
継承 型:object onerror  
継承 型:object onfocus  
継承 型:object oninput  
継承 型:object oninvalid  
継承 型:object onkeydown  
継承 型:object onkeypress  
継承 型:object onkeyup  
継承 型:object onload  
継承 型:object onloadeddata  
継承 型:object onloadedmetadata  
継承 型:object onloadstart  
継承 型:object onmousedown  
継承 型:object onmousemove  
継承 型:object onmouseout  
継承 型:object onmouseover  
継承 型:object onmouseup  
継承 型:object onmozfullscreenchange  
継承 型:object onpause  
継承 型:object onplay  
継承 型:object onplaying  
継承 型:object onprogress  
継承 型:object onratechange  
継承 型:object onreset  
継承 型:object onscroll  
継承 型:object onseeked  
継承 型:object onseeking  
継承 型:object onselect  
継承 型:object onshow  
継承 型:object onstalled  
継承 型:object onsubmit  
継承 型:object onsuspend  
継承 型:object ontimeupdate  
継承 型:object onvolumechange  
継承 型:object onwaiting  
継承 型:object oncopy  
継承 型:object oncut  
継承 型:object onpaste  
継承 型:object onbeforescriptexecute  
継承 型:object onafterscriptexecute  
継承 型:object mozIndexedDB  [object IDBFactory]
継承 型:object performance  [object Performance]

[ThinkPad]Fnキー+カーソルキーで、Media Playerを操作する


ThinkPadでは、Fn+カーソルキーの組み合わせでいつでもWindows Media Playerが操作できます。
ウィンドウが最前面に出ていなくてもOKです。

各キーの意味は以下の通り。

Fn+下キー : 再生、一時停止ボタン
Fn+上キー : 停止ボタン
Fn+右キー : 次の曲へ
Fn+右キー : 前の曲へ



改めてキーボードを見てみると、青色でアイコンが表示されてました。

ヤマト運輸 問い合わせ窓口の一覧

クロネコヤマトの宅急便で有名な、ヤマト運輸の問い合わせ窓口一覧です。
受付時間は、どのセンターも8:00~21:00です。

センターコード 問い合わせセンター名 電話番号 FAX番号 営業所数
000005 札幌主管支店 サービスセンター 011-330-3333 011-896-4200 105
002005 函館主管支店サービスセンター 0138-38-1111 0138-49-3800 38
003005 千歳主管支店 サービスセンター 0123-48-3710 0123-28-7712 59
004005 道北主管支店 サービスセンター 0166-30-1111 0166-49-4100 63
007005 道東主管支店 サービスセンター 0155-62-8010 0155-61-2886 60
010005 青森主管支店 サービスセンター 017-771-3333 017-762-5370 87
011005 秋田主管支店 サービスセンター 018-803-1111 018-826-2020 76
012005 岩手主管支店 サービスセンター 0197-68-3822 0197-81-4747 91
013005 宮城主管支店 サービスセンター 022-374-8111 022-374-8059 143
014005 山形主管支店 サービスセンター 023-606-1111 023-687-4180 74
015005 福島主管支店  サービスセンター 024-911-1112 024-968-1050 120
021005 茨城主管支店サービスセンター 029-831-7111 029-831-3413 156
022005 栃木主管支店サービスセンター 0289-93-4206 0289-76-6911 106
023005 群馬主管支店サービスセンター 027-202-1111 027-287-4548 112
024005 埼玉主管支店サービスセンター 048-611-1111 048-722-3141 165
025005 千葉主管支店サービスセンター 043-331-1111 043-259-3643 163
026005 横浜主管支店サービスセンター 045-330-6668 045-773-9155 150
027005 厚木主管支店サービスセンター 046-203-2222 046-296-9411 142
028005 神奈川主管支店 サービスセンター 045-345-6666 045-521-8700 239
030005 北東京主管支店 サービスセンター 03-4335-2200 03-3927-9970 261
031005 東京主管支店サービスセンター 03-6733-7000 03-5531-3255 215
032006 南東京主管支店 コールセンター 03-5723-2082 03-5723-6584 149
033005 西東京主管支店 サービスセンター 042-504-2222 042-549-7424 183
034005 西埼玉主管支店サービスセンター 0493-40-1111 0493-25-0683 143
035005 船橋主管支店 サービスセンター 047-432-7011 047-432-9302 163
036005 新東京主管支店  サービスセンター 03-4335-2211 03-5460-4140 305
037005 山梨主管支店サービスセンター 055-213-2222 055-268-5606 64
038005 東東京主管支店サービスセンター 03-4333-6200 03-3649-5161 159
039005 埼京主管支店サービスセンター 048-233-1111 048-483-2089 177
040005 新潟主管支店  サービスセンター 025-333-2222 025-230-3311 101
041005 長岡主管支店 サービスセンター 0258-40-3334 0258-46-0585 67
042005 長野主管支店 サービスセンター 026-296-2111 026-295-4444 77
043005 松本主管支店  サービスセンター 0263-60-1111 0263-51-1638 83
044005 富山主管支店 サービスセンター 0766-33-2222 0766-55-4966 63
045005 金沢主管支店 サービスセンター 076-203-2222 076-240-1692 76
046005 福井主管支店 サービスセンター 0776-31-2222 0776-31-5533 48
051005 静岡主管支店 サービスセンター 054-903-5555 054-285-1122 131
052005 浜松主管支店 サービスセンター 053-435-5111 053-435-4445 81
053005 三河主管支店 サービスセンター 0566-33-2223 0566-98-1018 75
054005 名古屋主管支店 サービスセンター 052-309-2011 052-309-2178 105
055005 三重主管支店 サービスセンター 0598-50-5602 059-329-6177 95
057005 愛知主管支店 サービスセンター 052-307-1111 0561-61-5022 160
058005 岐阜主管支店 サービスセンター 0575-25-6677 0575-25-6668 92
060005 大阪主管支店 サービスセンター 06-6733-4196 06-4702-2515 268
061005 西大阪主管支店 サービスセンター 06-6733-1111 06-6419-6174 218
062005 京都主管支店 サービスセンター 075-320-2222 075-633-2660 149
063005 滋賀主管支店 サービスセンター 077-503-2222 077-553-9810 73
064005 奈良主管支店 サービスセンター 0743-50-1111 0743-59-5516 76
065005 和歌山主管支店サービスセンター 073-403-2222 073-499-4572 60
066005 兵庫主管支店 サービスセンター 078-330-2222 078-903-3067 193
067005 姫路主管支店サービスセンター 079-244-1111 079-252-8808 92
068005 北大阪主管支店  サービスセンター 06-6907-4111 06-4252-3510 141
070005 岡山主管支店 サービスセンター 086-899-2223 086-277-3551 118
071005 三次主管支店 サービスセンター 0824-56-1111 0824-62-3061 60
072005 広島主管支店 サービスセンター 082-849-1234 082-849-0090 138
073005 山口主管支店 サービスセンター 083-983-1111 083-986-3872 75
077005 津山主管支店 サービスセンター 0868-54-5070 0868-54-7166 66
080005 香川主管支店 サービスセンター 0877-45-5109 0877-45-5744 66
082005 徳島主管支店 サービスセンター 088-603-2222 088-699-7571 48
083005 高知主管支店 サービスセンター 088-813-2222 088-862-0043 46
084005 愛媛主管支店 サービスセンター 089-903-2222 089-963-8673 66
090005 福岡主管支店 サービスセンター 092-303-2222 092-411-2766 194
091005 北九州主管支店サービスセンター 093-475-8010 093-475-8014 88
092005 佐賀主管支店 サービスセンター 0952-98-3392 0952-98-3547 44
093005 長崎主管支店 サービスセンター 0957-49-5536 0957-48-6670 98
094005 熊本主管支店  サービスセンター 096-287-8787 096-287-8070 101
095005 大分主管支店 サービスセンター 097-502-2222 097-514-3031 75
096005 宮崎主管支店 サービスセンター 0985-68-2222 0985-56-0122 70
097005 鹿児島主管支店 サービスセンター 0995-67-7700 0995-65-9717 101
098800 沖縄主管 サービスセンター 098-840-3581 098-851-3268 22

いいがかりマニュアル―できる!クレームでひと儲け!
「ムリ!」を「うん…まあ(笑)」に変える ゴネる技術


[PHP] DOMDocument#loadHTML()で、特定の文字が含まれていると正しいDOMを作ってくれない

DOMDocument#loadHTML()の出力がおかしい

PHPで、DOMDocument#loadHTML()を使用すると、HTMLのテキストからDomのツリーを作成することが出来ます。

とあるファイルを、loadHTML()したのですが、なぜか正しくツリーが作られないことがありました。
しかも、まったく作られない訳ではなく途中で途切れてしまう。

中身を調べてみると、”㈱”(かっこかぶ:機種依存文字)や、”鎌”の文字が出てきたところで解析が打ち切られています。”かっこかぶ”は明らかにヤバそうですが、”鎌”はダメ文字なので、loadHTMLが文字コードを勘違いしている雰囲気です。

余談:「ダメ文字」とは?

ダメ文字とは、2byte目が0x5cになっている文字で、主なものには以下の文字があります。
詳しくは、wikipediaでも見てください。

ーソЫⅨ噂浬欺圭構蚕十申曾箪貼能表暴予禄兔喀媾彌拿杤歃濬畚秉綵臀藹觸軆鐔饅鷭偆砡纊犾




原因を調べる

loadHTML()に渡したhtmlテキストのheadを見ると、以下の定義がされてた。

<meta http-equiv="Content-Type" content="text/html; charset=shift_jis">



SJISに㈱は無い(windowsの機種依存文字だから)ので、loadHTMLがこけるのも仕方が無い状況でした。



charsetを修正してみるがうまくいかない…

元となるhtmlテキストを変更すれば簡単なのですが、今回、元ネタは変更できない状況だったので、loadHTML()をコールする直前でcharsetを強制変換させてみた。

$doc = new DOMDocument();
@$doc->loadHTML( $html );




$doc = new DOMDocument();
 
$html = str_replace("charset=shift_jis", "charset=Windows-31J", $html );
@$doc->loadHTML( $html );



これでいけるはず… なのにうまくいかない。
というか全体が文字化けしているのでcharsetを正しく解釈してくれていないみたい。


CP932の指定だと、なぜかうまく動いた

仕方が無いのでダメ元で、CP932を指定してみる。

$doc = new DOMDocument();
 
$html = str_replace("charset=shift_jis", "charset=CP932", $html );
@$doc->loadHTML( $html );



これで、なぜかうまく動作しました。
IANAだと、charsetにCP932は無いはずなのに、なぜだろう…と思ったけど使い捨てのプログラムなので深く考えないことにする。



別解:元データの文字コードを変更してしまう手もあり?

あと、元データの文字コード自体をutf8に変更するという方法でもOKでした。

$doc = new DOMDocument();
$html = mb_convert_encoding($html, "UTF-8", "SJIS-win" );
$html = str_replace("charset=shift_jis", "charset=utf-8", $html );
@$doc->loadHTML( $html );


遅そうだったので今回は不採用でしたが、CP932は絶対に指定したくないという人は、この方法も良いかもしれません。

[PHP5]”Notice: Trying to get property of non-object”の警告が表示される

PHPのプログラムを実行すると、以下の警告が表示される場合があります。

PHP Notice:  Trying to get property of non-object in C:test.php on line 77



これは、存在しない(or 値がnullな)変数のプロパティを参照しようとしたときに発生します。

例としては、以下のプログラムの実行で再現させることが出来ます。

<?php
$a = null;
echo $a->value;	// ここでTrying to get property of non-objecが発生する




対処法としては、以下の3つあります。

その1:is_nullでチェックする


対象のオブジェクトがnullで無いことを,is_null()で事前にチェックします。

<?php
$a = null;
if ( is_null( $a ) || is_null( $a->value ) ) {
  // エラー処理
  echo "valueは存在しません\n";
}
echo $a->value;




その2:emptyでチェックする


empty()を使用して、プロパティの存在チェックを行うことも可能です。

<?php
$a = null;
if ( empty( $a->value ) ) {
  // エラー処理
  echo "valueは存在しません\n";
}
echo $a->value;



その3:issetでチェックする


isset()を使用すると、値がnullで無い事に加え、値がセットされているかどうかのチェックができます。

<?php
$a = null;
if ( !isset( $a->value ) ) {
  // エラー処理
  echo "valueは存在しません\n";
}
echo $a->value;




ちなみに、PHP4のときは$aがnullのとき、$a->valueはnullを返す仕様でした。
ですのでPHP4のスクリプトをPHP5に移植した際に警告が出るようになった場合は、データのチェックを追加するか、元変数がnullの場合はプロパティ値もnullとみなすと良いです。


PHPの入門書は、下記の書籍がおすすめです。
通読してもよいですし、手元に置いておき必要な時に見るためのリファレンスとして最適です。

PHPの資格試験の認定教材にも選ばれているため、内容も安心です。

初めてのPHP5 増補改訂版

[YamaTrack]ヤマト運輸の荷物問合せサイトを作成しました

ヤマト運輸オフィシャルの荷物状況問合せページが不便だったので、検索ページを自作しました。

URL: http://nanoappli.com/tracking/
YamaTrack | ヤマト運輸 荷物問い合わせ



本サイトの特徴


ヤマト運輸にも問合せページはありますが、オフィシャルと比べ本検索サイトは以下の特徴があります。

  • 伝票No範囲指定で、一括検索できる
  • バーコードリーダを使用した、連続検索が便利
  • 検索結果のCSVダウンロードが可能
  • 伝票No最終桁は省略可
  • シンプルなデザイン

各特長の内容は以下の通りです。

伝票No範囲指定で、一括検索



業務やオークションなどで大量の商品を出荷する場合、伝票番号は連番になることが多いです。
オフィシャルでは1伝票づつしか検索できないところですが、本サイトでは伝票NoのFrom-Toを入れるだけで、まとめて検索してくれます。
(一度に最大100伝票までOK!)

バーコードリーダを使用した、連続検索が便利



単一伝票検索時、複数伝票をバーコードリーダで連続スキャンが可能です。
通常だと、「スキャン→検索」が順番に行われるため、次のバーコードをスキャンするまで若干の待ちが発生してしまいますが、本サイトではスキャン処理と検索処理を平行してに行っている(非同期処理)ため、待たせることがありません。

検索結果のCSVダウンロードが可能


検索結果欄の「CSV形式」をクリックすることで、csvでのデータ取得が可能です。
これで、配送状況を他のプログラムやexcelで管理したい場合も簡単です。

伝票No最終桁は省略可



伝票No最終桁(12桁目)はチェックデジットなのですが、自動計算するので11桁のみ入力すればOKです。
わずかではありますが、手入力する際の手間を省けます。

シンプルなデザイン




余計なものが無いシンプルデザインです。

WebAPIも公開中


マッシュアップや他アプリとの連携も考慮して、WebAPIも公開しています。
RESTfulなURLで、使い方も簡単です。

仕様や制限は、下記のページを参照してください。

xml/json/yamlでのダウンロード
ヤマト運輸の配送状況を確認するAPIを作ってみた

jsonpの仕様
ヤマトの伝票情報取得APIをJSONP対応しました

その他


ロゴ作成は,まどマギ ジェネレーターというサイトを利用させていただきました。

[JavaScript]配列中の大量データを非同期でゆっくり処理する

先日、クロネコヤマトの伝票番号から配送状況を取得するAPIを作りました。
このAPIですが、負荷軽減のため、呼び出し頻度が毎秒1回という制限を設けています。

制限があるのは良いとして、このAPIを使って複数(大量)のデータをJavaScriptで処理したい場合どうやって作ったらよいのだろうか? と思い、色々試行錯誤した事の過程と結果です。



長文になってしまったので、最初に目次を書いておきます。

  • その1:ループで処理する
  • その2:ループ内でスリープさせる
  • その3:setTimeout()で非同期処理させる
  • その4:非同期処理の関数に汎用性を持たせる
  • ところで、「毎秒1回」という呼び出し制限がない場合は?
  • その5:画面が固まることなく,大量データを素早く処理する
  • まとめ

結論だけ欲しい人は、最後の”まとめ”にある関数だけコピーすればOKです。


その1:ループで処理する

普通に考えると、当然for文等でループさせて処理を行います。

まぁ、以下のような感じのプログラムになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<div>
    テスト実行:<input id="btnInput1" type="button" onClick="btnInput1_Click();" value="開始" /
    <div id="result1"></div>
</div>
 
<script>
function btnInput1_Click() {
 
    var params = [ "000000000001",
                   "000000000002",
                   "000000000003",
                   "000000000004",
                   "000000000005",
                   "000000000006",
                   "000000000007",
                   "000000000008",
                   "000000000009",
                   // ... 1000件ぐらいの大量データ
                   "000000001000" ];
 
    // 指定された全伝票データの検索を行う
    for ( var i = 0; i < params.length; i++ ) {
        var slipNo = params[ i ];
        getDataDummy( slipNo );
    }
 
    return false;
}
 
function getDataDummy( inputStr ) {
    // WebAPIを使って伝票データを取得する関数...
    // 今回はテストなので、指定されたパラメータを画面に出力する処理に変更。
    p = document.getElementById( 'result1' );
    p.appendChild( document.createTextNode( "検索 key=" + inputStr ) );
    p.appendChild( document.createElement( "br" ) );
}
</script>



見たら分かるような処理ですが、最初なので解説します。

  • 画面のボタン:
     inputタグ(ボタン)のonClickでbtnInput1_Click()をコールしています。

  • btnInput1_Click()
     ここで、大量の伝票データの検索処理を行います。
     今回は説明のため、検索条件のKeyは配列に入れて有ります。
     (実際はtextarea等から取得する等をイメージしてください。)
     forループで、データ件数分検索処理(getDataDummy関数)をコールします。

  • getDataDummy()
     こちらも説明のため、実際の検索は行わず、検索キーを画面に表示させるだけになっています。
     ※実際の検索処理ロジックが知りたい場合は、この記事を参照してください。


ループさせているので、当然全データの処理を行うことが出来ます。
でもこのパターンだと、全力でAPIをコールすることになるので、”API呼び出しは毎秒1回以内で”という制限を満たせません。



sample1: ループで処理を行う
テスト実行:





その2:ループ内でスリープさせる


Cやjavaのプログラマからすると、前述の問題は「for文でウェイトが入っていないのが問題でしょ。」という話になります。ではsleep()を入れようか...ってことになるわけですが、残念ながらJavaScriptにはsleep関数が有りません。

なけりゃ作れば良いだけなので,さくっと自作します。

1
2
3
4
5
6
7
8
9
function sleep( msec ) {
    var start = new Date;
    while (1) {
        var cur = new Date;
        if ( msec <= cur.getTime() - start.getTime()) {
            break;
        }
    }
}


引数でスリープさせる時間を指定して、その分の時間が経過するまでループし続けて待つだけです。
(上記の処理だとビジーウェイトになるので良くないのですが、サンプルなのでそこはスルーします)


呼び元側は以下のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function btnInput2_Click() {
    var params = [ "000000000001",
                   "000000000002",
                   // ... 1000件ぐらいの大量データ
                   "000000001000" ];
 
    for ( var i = 0; i < params.length; i++ ) {
        var slipNo = params[ i ];
        getDataDummy2( slipNo );
 
        sleep( 400 );  // 毎回0.4秒スリープ(本当は1secだけど、確認のため短めに変更) 
    }
    return false;
}



これで確かに指定時間の周期で処理はコールされます。
ですが、残念ながら別の面でうまく動作しません。


実際に走らせて見ると分かるのですが、この関数が終了するまでの間、ブラウザ画面の再表示が行われないため画面が固まったように見えてしまいます。これは、JavaScriptの場合はスクリプトの処理がUIの更新処理と同じスレッドで走ることが原因です。

下のボタンで、プログラムを実行し振る舞いを確認してみてください。
sample2:スリープを入れて処理する(注意:実行すると4秒ほどブラウザが固まります)
テスト実行:



さらに、スクリプトの実行に長時間掛かる場合は、使い勝手が落ちるだけでなく、ブラウザより以下のような警告ダイアログが表示されてしまいます。




その3:setTimeout()で非同期処理させる


先ほどのプログラムでは、ブラウザによって一回のイベントハンドラ内で作業できる時間が制限されている以上、スリープを入れたところで意味が無いことが分かりました。

それではどうすべきかという話になるわけですが、先に結論を書いてしまうとJavaScriptでこの手の処理を行う場合は、setTimeout()を利用して非同期処理を行います。

言葉で説明しても分かり辛いので、まずはサンプルです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function btnInput3_Click() {
    var params = [ "000000000001",
                   "000000000002",
                   // ... 1000件ぐらいの大量データ
                   "000000001000" ];
 
    getDataMain3( params );
 
    return false;
}
 
var paramList3;
function getDataMain3( params ) {
    paramList3 = params.concat();   // 配列のコピーを作る
    var slipNo = paramList3.shift();
 
    getDataDummy3( slipNo );
    if ( paramList3.length > 0 ) {
        // 400mSec後に自身を呼び出す(本当は1secだけど、確認のため短めに...)
        setTimeout( "getDataMain3( paramList )", 400 );
    }
}
 
function getDataDummy3( inputStr ) {
    p = document.getElementById( 'result3' );
    p.appendChild( document.createTextNode( "検索 key=" + inputStr ) );
    p.appendChild( document.createElement( "br" ) );
}



前回までは、イベントハンドラ内でデータ取得関数をコールしていたのですが、今回は替わりにgetDataMainをコールしており、その中でgetDataDummy()を呼び出しています。

増えたgetDataMain3()ですが、何をしているか順を追って確認します。

paramList3 = params.concat();   // 配列のコピーを作る


ここでは、受け取った引数のコピーを作って別の変数にコピーをしています。
本来concat()メソッドは複数配列の結合をする処理なのですが、concat()では結合処理後に新しい配列を生成するという性質を利用し、渡された配列のコピーを生成しています。




var slipNo = paramList3.shift();


次に、コピーした配列からshift()関数で最初の要素を取り出しています。
同時に、paramList3から最初の要素は削除されます。
(先ほどコピーを作ったのは、ここで配列の中身を書き換えてしまうのが理由です)




getDataDummy3( slipNo );


取得した最初の要素に対し、データ取得処理をコールします。
(パラメータは、配列中の最初の要素です)




if ( paramList3.length > 0 ) {
    setTimeout( "getDataDummy3Main( paramList )", 400 );
}


shiftした後の配列要素数を調べることで、まだ処理すべきデータが残っているかを確認し、データがあれば、setTimeout関数で400mSec後に自分自身をコールします。
次に自身がコールされる時の引数は、渡された配列の2要素目以降です。
(shift後の配列なので最初の要素は無くなっていることに注意)


こうすると、関数内では最初の1要素だけ処理するで、OnClickイベントはすぐに終了します。
配列をshift()しているので、setTimeoutによって次回コールされたときは2要素目を処理します。
順々にshiftして行くことで、結果的に関数の呼出し毎に1データづつ、最後の要素まで処理できます。

この仕組では、1回のメソッド呼び出しが長時間占有することがない為、画面が操作できなくという問題も有りません。また、各データを処理する間隔は、setTimeoutで任意に指定が可能です。


下のサンプルを実行してどのような動きになるか、また、処理中にブラウザの操作が可能である事を確認してください。

sample3:setTimeoutで非同期処理を行う
テスト実行:




その4:非同期処理の関数に汎用性を持たせる

先ほどのプログラムで、とりあえずの目的を達成することは出来ました。
せっかく作った仕組みなので、もう少し汎用性をもたせるようにします。

先ほどの関数では、非同期実行の制御関数(getDataMain)からコールされる実処理(getDataDummy)が決め打ちになっているので、まずこれを解消させます。

function btnInput4_Click() {
    var params = [ "000000000001",
                   "000000000002",
                   // ... 1000件ぐらいの大量データ
                   "000000001000" ];
 
    // 配列のデータを非同期で実行する
    getDataMain4( params, getDataDummy4 );
 
    return false;
}
 
var paramList4;
var onProc4;
function getDataMain4( params, onProcess ) {
    onProc4 = onProcess;
    paramList4 = params.concat();   // 配列のコピーを作る
    var slipNo = paramList4.shift();
 
    onProcess( slipNo );
    if ( paramList4.length > 0 ) {
        setTimeout( "getDataMain4( paramList4, onProc4 )", 100 );
    }
}
 
function getDataDummy4( inputStr ) {
    // 今までと同じ処理なので省略.
}



Main側の第二引数で、実処理の関数を指定できるようにしました。



次に、onProcと、paramListがグローバル変数になっているのを止めます。

function btnInput5_Click() {
    var params = [ "000000000001",
                   "000000000002",
                   // ... 1000件ぐらいの大量データ
                   "000000001000" ];
 
    getDataMain5( params, getDataDummy5 );
    return false;
}
 
function getDataMain5( params, onProcess ) {
    var paramList = params.concat();    // 配列のコピーを作る
 
    var runAsync = function() {
        var slipNo = paramList.shift();
 
        onProcess( slipNo );
        if ( paramList.length > 0 ) {
            setTimeout( arguments.callee, 100 );
        }
    }
    runAsync();
}
 
function getDataDummy5( inputStr ) {
    // 今までと同じ処理なので省略.
}


getDataMain内で、ローカル関数のrunAsync()を作成することで、グローバル変数の生成を抑制しています。また、runAsync()はsetTimeoutで自分自身を呼び出す必要がありますが、自分自身のメソッドは"arguments.callee"で取得可能なので、setTimeoutの第一引数を変更しています。



さらに、runAsync()という変数自体も必要ないため、無名関数に置き換えてしまいます。

function btnInput6_Click() {
    var params = [ "000000000001",
                   "000000000002",
                   // ... 1000件ぐらいの大量データ
                   "000000001000" ];
 
    getDataMain6( params, getDataDummy6 );
    return false;
}
 
// 配列データを非同期で処理する
function getDataMain6( params, onProcess ) {
    var paramList = params.concat();    // 配列のコピーを作る
 
    (function() {
        var slipNo = paramList.shift();
 
        onProcess( slipNo );
        if ( paramList.length > 0 ) {
            setTimeout( arguments.callee, 100 );
        }
    })();
}
 
function getDataDummy6( inputStr ) {
    // 今までと同じ処理なので省略.
}





ループで処理していた場合と異なり、setTimeout方式だと、処理の終了が検出できなくなってしまいます。これを避けるために処理終了時のハンドラを設定できるようにします。

// 配列データを1秒周期で非同期処理する
function asyncProcArray( params, onProcess, onFinish ) {
    var paramList = params.concat();    // 配列のコピーを作る
 
    (function() {
        var slipNo = paramList.shift();
 
        onProcess( slipNo );
        if ( paramList.length <= 0 ) {
            onFinish();
            return;
        }
 
        setTimeout( arguments.callee, 1000 );
    })();
}



呼び元側は以下のような感じです。

function btnInput7_Click() {
    var params = [ "000000000001",
                   "000000000002",
                   // ... 1000件ぐらいの大量データ
                   "000000001000" ];
 
    asyncProcArray( params, getDataDummy7, finishProc7 );
    return false;
}
 
function getDataDummy7( inputStr ) {
    // 今までと同じ処理なので省略.
}
 
function finishProc7() {
    alert( "終了しました" );
}



これで、かなり汎用的になりました。



ところで、「毎秒1回」という呼び出し制限がない場合は?


前述の関数で、配列にセットされた大量データを、指定した時間周期でゆっくり処理させることが出来るようになりました。今回の問題に対しては前述の関数で十分ですが、大量データを非同期で素早く処理したい場合は、まだ問題があります。


何が問題なのかというと、前述のasyncProcArray()関数を使う場合、1要素処理するたびに必ず100ミリ秒待ことになります。この為、仮にデータが100件ある場合には最低でも10秒掛かってしまいます。これでは、例えばJavaScriptでゲームを作り100個のキャラクタをを同時に動かしたいといった場合、毎フレーム10秒掛かることになり、現実的な解決方法にはなりません。


せっかくここまで考えたので、ついでに上記の問題も解決できるバージョンを作ってみます。


その5:画面が固まることなく,大量データを素早く処理する


簡単な解決案としては、一回あたりの処理量をもう多くするという案があります。例えば、毎回1データづつではなく5個づつ処理すれば待ちは1/5になります。
方法は簡単ですが、各データの処理負荷に応じて1サイクルの時間が変わってしまうためあまり良い方法ではありません。
また、データによって処理負荷が可変となる場合にも問題があります。


個々の要素に対する処理負荷に依存しない方が潰しがきくので、そこまで考慮のが以下の関数です。

function runAcyncArray( params, onProcess, onFinish ) {
    var paramList = params.concat();    // 配列のコピーを作る
 
    (function() {
        var startTime = new Date(); // 開始時刻を覚える
 
        //-----------------------------------
        // タイムアウトになるまで処理を行う
        //-----------------------------------
        while ( 1 ) {
            var curParam = paramList.shift();
 
            //----------------------
            // 配列を1要素処理する
            //----------------------
            onProcess( curParam );
 
            if ( paramList.length <= 0 ) {
                //--------------------------------------
                // 全要素処理を行った -> 終了処理
                //--------------------------------------
                onFinish( params );
                return;
            }
 
            if ( (new Date()) - startTime > 100 ) {
                //---------------------------------------
                // タイムアウト発生 -> 一旦処理を終わる
                //---------------------------------------
                break;
            }
        }
        //----------------------------------
        // ちょっと待って続きの処理を行う
        //----------------------------------
        setTimeout( arguments.callee, 40 );
    })();
}



上記プログラムでは100ミリ秒経過するまで処理を行い、規定時間が経過したら40ミリ秒後に続きのデータを処理させています。一度に行う処理時間は余り多いと画面を操作する際の遅延が目立ってしまうので、50~100ミリ程度が適切です。

また、再実行までのウェイトは20ミリ以下だと、画面を描画する側の処理が十分に走りきらない場合があり、こちらも使い勝手の低下につながります。ですので余裕を見て40ミリ秒とっています。

同様の非同期処理が複数同時に走る場合は、もう少し多めにウェイトを入れたほうが適切となります。




まとめ


まとめると、大量データをJavaScriptで処理する場合は、以下の点に注意する必要があります。
  • javascriptで大量データを処理する場合、画面応答性を考慮すると全データを一度に処理はできない。
  • setTimeoutを利用することで、UI描画に処理を明け渡しつつデータ処理を継続させることが出来る。



また、上記問題を解消する為に、汎用性のある関数を2つ作成しました。

一定周期ごとに、配列の各要素を処理したい場合

// 配列データを1秒周期で非同期処理する
function asyncProcArray( params, onProcess, onFinish ) {
    var paramList = params.concat();    // 配列のコピーを作る
 
    (function() {
        var slipNo = paramList.shift();
 
        onProcess( slipNo );
        if ( paramList.length <= 0 ) {
            onFinish();
            return;
        }
 
        setTimeout( arguments.callee, 1000 );
    })();
}




画面が固まることなく、出来る限り速く大量のデータを処理したい場合

function runAcyncArray( params, onProcess, onFinish ) {
    var paramList = params.concat();    // 配列のコピーを作る
 
    (function() {
        var startTime = new Date(); // 開始時刻を覚える
 
        //-----------------------------------
        // タイムアウトになるまで処理を行う
        //-----------------------------------
        while ( 1 ) {
            var curParam = paramList.shift();
 
            //----------------------
            // 配列を1要素処理する
            //----------------------
            onProcess( curParam );
 
            if ( paramList.length <= 0 ) {
                //--------------------------------------
                // 全要素処理を行った -> 終了処理
                //--------------------------------------
                onFinish( params );
                return;
            }
 
            if ( (new Date()) - startTime > 100 ) {
                //---------------------------------------
                // タイムアウト発生 -> 一旦処理を終わる
                //---------------------------------------
                break;
            }
        }
        //----------------------------------
        // ちょっと待って続きの処理を行う
        //----------------------------------
        setTimeout( arguments.callee, 40 );
    })();
}

ヤマトの伝票情報取得APIをJSONP対応しました

前回作成したヤマトの伝票情報取得APIをJSONP対応しました。


JSONP呼び出しURL

以下の形式でコールします。
(“123456789012″には伝票Noを指定します。)

http://nanoappli.com/tracking/api/123456789012/CallbackFuncName


Ajax Demo:

ヤマト伝票No(12桁) :



HTMLソース


ヤマト伝票No(12桁) :<input id="inputArea" vtype="text" />
<input id="btnInput" type="button" onClick="btnInput_Click();" value="入力" />
<div id="result"></div>




JavaScriptソース


//-----------------------------------------------------
// 入力ボタンがクリックされたときのハンドラ
//-----------------------------------------------------
function btnInput_Click() {
    var inputStr      = document.getElementById( 'inputArea' ).value;
    getData( inputStr );
 
    return false;
};
 
//-----------------------------------------------------
// jsonpでデータを取得
//-----------------------------------------------------
function getData( inputStr ) {
    var s = document.createElement('script');
    s.charset = 'UTF-8';
    s.src     = 'http://nanoappli.com/tracking/api/' + inputStr + '/displayData';
    document.body.appendChild(s);
};
 
//-----------------------------------------------------
// 取得データを画面に表示
//-----------------------------------------------------
function displayData( json ) {
    nodeParent = document.getElementById( 'result' );
    nodeParent.appendChild( document.createTextNode( "応答コード: " + json.result   ) );
    nodeParent.appendChild( document.createElement( "br" ) );
 
    nodeParent.appendChild( document.createTextNode( "状態: " + json.status   ) );
    nodeParent.appendChild( document.createElement( "br" ) );
 
    nodeParent.appendChild( document.createTextNode( "伝票No: " + json.slipNo   ) );
    nodeParent.appendChild( document.createElement( "br" ) );
 
    if ( json.result == 0 ) {
        nodeParent.appendChild( document.createTextNode( "商品種別: " + json.itemType ) );
        nodeParent.appendChild( document.createElement( "br" ) );
 
        var olNode = document.createElement('ol');
        for ( var loopItem = 0; loopItem < json.statusList.length; loopItem++ ) {
            var detail = json.statusList[ loopItem ];
            var detailText = detail.status + " at " + detail.date + " " + detail.time;
            detailText += " [" + detail.placeCode + ":" + detail.placeName + "]" ;
 
            var liNode = document.createElement( "li" );
            liNode.appendChild( document.createTextNode( detailText ));
            olNode.appendChild( liNode );
        }
        nodeParent.appendChild( olNode );
    }
};






JSONP対応の調査メモ(for JSONP developer)

API作成にあたって調査した際のメモです。
JavaScript初心者で、かつJSONPの仕組みを新たに作りたい人向けのドキュメントです。
(本APIの利用者向けでは有りません)
dankogai作のAWS APIからJSONの仕組みを理解する(1/2)
dankogai作のAWS APIからJSONの仕組みを理解する(2/2)

[JavaScript]数値入力欄で、全角数字や書式編集された文字を受け入れる。(全角/半角変換)

名古屋市の図書館には、Webサイトがありオンラインで予約が出来るのが便利です。
いつものように利用していたのですが、ふとログインフォームで1つ気になることがありました。



ありがちなログインの入力フォームですが、共通貸出券番号を半角で入力することを要求しています。
(ちなみに、貸出券番号は10桁の数字です)

試しに全角で入力してみると、以下のようにエラーが表示されました。



ちなみに誤った番号を入力した場合は以下のメッセージになるので、この画面は入力値が全角であるかチェックした上でエラーメッセージを出しているるようです。



コンピュータに慣れている人であれば、この間違いはすぐに気づくのですが、初心者の人にとっては不便なインターフェースになっています。
また、慣れている人でも、誤って全角で入力した際、再入力をするのが面倒というのもあります。


そこで今回は、上記のような入力値も受け入れることが出来る、ユーザに優しい入力インターフェースを作ります。




全角入力された数字を半角に変換する


以下の入力フォームに入れられた値を変換することを考えます。

<input id="inputArea" type="text" />
<input id="btnInput" type="button" value="入力" />






今回の御代ですが、全角数字をエラーとみなしていたのが問題ですので、処理の前に全角→半角に変換にすればOKです。
また、クライアントサイドで解決できるレベルの問題ですので、JavaScriptで対応することとします。

変換処理は、以下のようになります。

// 数値入力欄の正規化
normalizeNumber = function( inStr ){
	var outStr;
	var convMap;
 
	// 入力チェック(文字列型以外はスキップ)
	if( typeof( inStr ) != "string" ) {
		return inStr;
	}
 
	// 変換元文字、変換後文字の対応表
	convMap = { 
        "1":"1",
        "2":"2",
        "3":"3",
        "4":"4",
        "5":"5",
        "6":"6",
        "7":"7",
        "8":"8",
        "9":"9",
		"0":"0",
    };
 
	// 文字列の置換を行う
	outStr  = inStr;
    for ( var key in convMap ){
        outStr = outStr.replaceAll( key, convMap[key] );
    }
	// 置換後文字列を返す
	return outStr;
}
 
// String型にreplaceAll()関数を追加
String.prototype.replaceAll = function ( before, after ) {
  return this.split( before ).join( after );  
}
</script>



関数が2つ定義されていますが、全角から半角数値への正規化を行う処理はnormalizeNumber()関数です。
関数中では、convMap連想配列にkey:変換前文字、value:変換後文字という形式で変換ルールを定義します。

その後、forループ内で、対象データを全て置換しています。
置換処理では、同一文字が複数ある場合も正しく置換する必要がある(ex:”11”→”11″)為,replace()関数を呼ぶことは出来ません。
※replace()は最初に出現した文字のみを置換するため”11”→”11″となってしまいます。

この為、replaceAll()をコールしているのですが、残念ながらJavaScriptのString型は標準でreplaceAll()関数を持っていません。
この為、1つ目の関数でString型に対してreplaceAll()関数を追加しています。
(Stringのprototypeを変えたくない場合は、普通の関数にしてfor内でコールすればOKです)



また、上記の関数を呼び出す側は以下のようになります。
例ではjQueryを使用していますが、jQueryを使用できない・使用したくない場合は普通にOnClick内で前述の関数をコールすれば良いです。

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
	// 入力欄にフォーカスセット
	$("#inputArea").focus();
 
	// 入力ボタンがクリックされたときのハンドラ
	$("#btnInput").click(function() {
		// 入力データを取得する
		var inputStr;
		inputStr      = $("#inputArea").val();
		normalizedStr = normalizeNumber(inputStr);
 
		// 変換後データをコンソールに出力
		console.info( inputStr + "->", normalizedStr );
	});
});



上記スクリプトを実行してみると、確かに変換が出来ていることを確認できます。





書式編集された入力値を受け入れる


上記のスクリプトで、全角数字対応は出来たのですが、クレジットカードや郵便番号を入力する際は、ハイフンを入力される場合があります。
ハイフンありの入力値を正規化したい場合は、上記関数の変換表を変えるだけで対応可能です。

// 変換元文字、変換後文字の対応表
convMap = { 
    "1":"1",
    "2":"2",
    "3":"3",
    "4":"4",
    "5":"5",
    "6":"6",
    "7":"7",
    "8":"8",
    "9":"9",
	"0":"0",
	"-" :"",
	"-":"",
	"ー":"",
};




さらに、数値入力時のカンマや、日付入力時の”/”や”:”まで配慮すると、以下のような形になります。

// 変換元文字、変換後文字の対応表
convMap = { 
    "1":"1",
    "2":"2",
    "3":"3",
    "4":"4",
    "5":"5",
    "6":"6",
    "7":"7",
    "8":"8",
    "9":"9",
	"0":"0",
	"-" :"",
	"-":"",
	"ー":"",
	"," :"",
	",":"",
	":" :"",
	":":"",
	"/" :"",
	"/":"",
};



変更後再テストすると、期待通りの変換が行われています。



大手サイトの対応状況は?

この記事を書くために幾つかのサイトで、数字入力欄の対応状況をチェックをしてみました。

楽天

“価格帯を指定して絞り込む”欄で確認しました。


以下の入力に対して、かなり融通が利くように変換してくれています。

500     → 500
1,000 → 1000
23:59     → エラー(未入力とみなす)
</a>
 
 
<h3>東京三菱UFJ銀行<h3>
 
オンラインバンキングのログインフォームでチェックしてみました。
<a href="http://nanoappli.com/blog/wp-content/uploads/20120123_02.jpg"><img src="http://nanoappli.com/blog/wp-content/uploads/20120123_02-500x196.jpg" alt="" title="20120123_02" width="500" height="196" class="alignnone size-large wp-image-729" /></a>
 
 
契約番号は「数字5桁 + "-" + 数字5桁」のフォーマットなのですが、半角数字で入力することが要求されています。
また、ハイフンは省略可である事が明記されています。
<a href="http://nanoappli.com/blog/wp-content/uploads/20120123_01.jpg"><img src="http://nanoappli.com/blog/wp-content/uploads/20120123_01.jpg" alt="" title="20120123_01" width="460" height="63" class="alignnone size-full wp-image-728" /></a>
 
 
契約番号が"12345-67890"の場合、以下のように何でもOKになっていました。
<pre lang="x">
1234567890             → ログインOK
12345-67890            → ログインOK
1234567890   → ログインOK
12345-67890 → ログインOK




やはり営利企業のほうが、この辺のインターフェースはきちんと作りこまれているようです。



ヤマト運輸 営業所コードのコード体系

ヤマト運輸の各営業所は6桁のコードを持っていますが、6桁のコード体系です。

1桁目:商品種別

0 宅急便
3 メール便
4 ヤマトグローバルエキスプレス
上記以外 その他


2,3桁目:担当区域

コード 担当区域 支店コード
00 札幌 北海道支社
02 函館 北海道支社
03 千歳 北海道支社
04 道北 北海道支社
07 道東 北海道支社
10 青森 東北支社
11 秋田 東北支社
12 岩手 東北支社
13 宮城 東北支社
14 山形 東北支社
15 福島 東北支社
16 八戸エリア支店 東北支社
17 大館エリア支店 東北支社
18 横手エリア支店 東北支社
20 北東京 東京支社
30 北東京 東京支社
31 東京 東京支社
47 東京 東京支社
32 南東京 東京支社
33 西東京 東京支社
36 新東京 東京支社
48 新東京 東京支社
38 東東京 東京支社
39 埼京 東京支社
21 茨城 関東支社
22 栃木 関東支社
23 群馬 関東支社
24 埼玉 関東支社
25 千葉 関東支社
26 横浜 関東支社
27 厚木 関東支社
28 神奈川 関東支社
34 西埼玉 関東支社
35 船橋 関東支社
37 山梨 関東支社
40 新潟 北信越支社
41 長岡 北信越支社
42 長野 北信越支社
43 松本 北信越支社
44 富山 北信越支社
45 金沢 北信越支社
46 福井 北信越支社
50 静岡西ベース 中部支社
51 静岡 中部支社
52 浜松 中部支社
53 三河 中部支社
54 名古屋 中部支社
55 三重 中部支社
57 愛知 中部支社
58 岐阜 中部支社
60 大阪 関西支社
61 西大阪 関西支社
62 京都 関西支社
63 滋賀 関西支社
64 奈良 関西支社
65 和歌山 関西支社
66 兵庫 関西支社
67 姫路 関西支社
68 北大阪 関西支社
70 岡山 中国支社
71 三次 中国支社
72 広島 中国支社
73 山口 中国支社
77 津山 中国支社
80 香川 四国支社
82 徳島 四国支社
83 高知 四国支社
84 愛媛 四国支社
90 福岡 九州支社
91 北九州 九州支社
92 佐賀 九州支社
93 長崎 九州支社
94 熊本 九州支社
95 大分 九州支社
96 宮崎 九州支社
97 鹿児島 九州支社
98 沖縄 沖縄ヤマト


4,5,6桁目

各営業所のコード



wikipediaの内容より整形

dankogai作のAWS APIからJSONの仕組みを理解する(2/2)

前回の記事では、サンプルとして提示されているJavaScriptの内容を確認しました。
dankogai作のAWS APIからJSONの仕組みを理解する(1/2)

今回は前回の内容を踏まえてソースの簡略化を行い、その後JSONPの仕組みを理解して行きます。



コードの簡略化

まずは、前回の解析を元にJSONPの理解と直接関係ない処理を削除してコードをシンプルにしていく。

最初は、画面load時の処理と、取得結果列挙の処理変更を行う。load時の処理は不要なので削除し、検索結果は書籍のタイトル表示だけにとどめておく。
この変更で、ソースは以下のように半分(81->40行)まで減らせた。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<a id="amazon.link" target="_blank" href="">
<img id="amazon.img" src="" style="float:right; margin-left:1em">
</a>
ASIN: <input id="asin" value="4534045220" size="10" type="text"><input onclick="JSONP.get(this.previousSibling.value)" type="submit">
<div id="amazon.attr"></div>
 
<script>
(function(d){
var $ = function(id){ return d.getElementById(id) };
 
var json2list = function(json){
	var spanNode = d.createElement('span');
	var textNode = d.createTextNode( json.Title);
	spanNode.appendChild( textNode );
	return textNode;
};
 
JSONP = {
  get:function(asin){
    var u = 'http://api.dan.co.jp/asin/' + asin + '/JSONP.run';
    var s = d.createElement('script');
    s.charset = 'UTF-8';
    s.id = s.src = u;
    d.body.appendChild(s);
  },
  run:function(json){
    $('amazon.attr').innerHTML = '';
    if (json.Error){
        $('amazon.attr').appendChild(json2list(json));
        return;
    }
    $('amazon.link').href = 'http://www.amazon.co.jp/gp/product/' + json.ASIN
      + '?ie=UTF8&linkCode=as2tag=blogsofdankog-22';
    if (json.ImageSets){
       var imageset = json.ImageSets.ImageSet;
       if (imageset.length) imageset = imageset[0];
       $('amazon.img').src = imageset.MediumImage.URL;
    }else{
       $('amazon.img').src = 'http://ec1.images-amazon.com/'
         + 'images/G/09/nav2/dp/no-image-no-ciu._AA128_.gif';
    }
    $('amazon.attr').appendChild(json2list(json.ItemAttributes));
  }
};
 
})(document);
</script>



実行してみるとボタン表示で、問題なく書籍のタイトルが表示された。



さらに、JSONPのハッシュテーブル解除、商品画像表示処理と、スクリプト全体を囲っている大枠の無名関数をカットする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ASIN: <input id="asin" value="4534045220" size="10" type="text"><input onclick="getData(this.previousSibling.value)" type="submit">
<div id="amazon.attr"></div>
 
<script>
var $ = function(id){ return document.getElementById(id) };
 
var json2list = function(json){
	var spanNode = document.createElement('span');
	var textNode = document.createTextNode( json.Title);
	spanNode.appendChild( textNode );
	return textNode;
};
 
function getData(asin){
    var u = 'http://api.dan.co.jp/asin/' + asin + '/displayData';
    var s = document.createElement('script');
    s.charset = 'UTF-8';
    s.id = s.src = u;
    document.body.appendChild(s);
};
 
function displayData(json){
    $('amazon.attr').innerHTML = '';
    if (json.Error){
        $('amazon.attr').appendChild(json2list(json));
        return;
    }
    $('amazon.attr').appendChild(json2list(json.ItemAttributes));
};
</script>



再度、期待通り動くことを確認。




さらに、変数$の削除、json2list関数をdisplayData()に埋め込みを行い、エラー表示も端折ってしまう。
これで、最小限の処理まで削りきった。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ASIN: <input id="asin" value="4534045220" size="10" type="text"><input onclick="getData(this.previousSibling.value)" type="submit">
<div id="result"></div>
<script>
function getData( asin ) {
    var s = document.createElement('script');
    s.charset = 'UTF-8';
    s.src     = 'http://api.dan.co.jp/asin/' + asin + '/displayTitle';
    document.body.appendChild(s);
}
function displayData( json ) {
	nodeParent = document.getElementById( 'result' );
	nodeResult = document.createTextNode( "タイトル :" + json.ItemAttributes.Title );
	nodeParent.appendChild( nodeResult );
};
</script>



このソースを元に、JSONPの仕組みを見て行くことにする。


JSONPの動作確認(サーバ側)


まずはボタンをクリックしたときのサーバとのやり取りを、firebugで確認する。

ヘッダをのContent-Typeを確認すると、”application/x-javascript”なので、javascriptのプログラムが送られてきていることが分かる。


次はレスポンスのデータ側。内容は非常に長いけど、注意深く見てみるとJSONデータを引数に持つ関数が1つ定義されているだけということが分かる。


この関数名をサーバがどうやって決めたかというと、最初に作ったリクエストURLの末尾に付与されているので、それをオウム返ししているだけ。(javascriptを見るとのURLに書いてある)

1
s.src = 'http://api.dan.co.jp/asin/' + asin + '/displayTitle';





試しにこれをdisplayTitle2に書き換えると、以下のようにレスポンスで渡される関数名が変更されてきた。
想定どおりの振る舞い。



なので、サーバ側は(JSONの時と比べると)JSONPでは以下の対応を行えば良い事になる。
1.レスポンスヘッダを”application/x-javascript”に変更する
2.レスポンスデータを関数名でくくる。



JSONPの動作確認(クライアント側)

サーバの流れは把握したので、次は、クライアント側のJavaScriptを確認する。

まずは、ボタンクリック時のハンドラから。

1
2
3
4
5
6
7
function getData( asin ) {
    var s = document.createElement('script');
    s.charset = 'UTF-8';
    s.src     = 'http://api.dan.co.jp/asin/' + asin + '/displayTitle';
    document.body.appendChild(s);
}
</script>



関数内の処理では,htmlにscriptタグを挿入している。
実際にボタンのクリック前後でhtmlを比較してみると確かにscriptタグが追加されていて、先ほどサーバより取得した関数になっている。
スクリプトの中を見てみると、関数呼び出しの形式なっている。なのでクライアント側では、サーバからの応答受信をトリガーに、displayData()関数がコールされることになる。



実際に呼ばれる関数の中身は以下の通り。

1
2
3
4
5
6
function displayData( json ) {
	nodeParent = document.getElementById( 'result' );
	nodeResult = document.createTextNode( "タイトル :" + json.ItemAttributes.Title );
	nodeParent.appendChild( nodeResult );
};
</script>



この処理自体は、普通のjavascriptで、idがresultのタグを持つ親ノードの末尾にテキストを追加している。関数の引数は、前述のサーバ側処理確認時に把握したように、サーバが指定している。

上記の仕組みによって、結果的にボタンのクリックをすると画面に書籍名が表示される流れが出来上がった。


まとめ

JSONPは、以下の仕組みで実現されていることが分かった。


1.クライアントはHTML上にscriptタグのノードを新しく作る。この際コールバックの関数名も渡す。
2.スクリプトの内容は、サーバから取得する。
3.サーバは、リクエストに対して、データを動的に取得する。
データを取得したら、クライアントへ指定されたコールバック関数呼び出しを行うスクリプトを返す。
4.クライアント側は応答受信をトリガに、コールバック関数を実行する。
(この際、引数にJSONデータがセットされている)



追記:今回学んだことを元に、JSONPのAPIを作成しました。
→ ヤマトの伝票情報取得APIがJSONP対応しました

dankogai作のAWS APIからJSONの仕組みを理解する(1/2)

はじめに

前回、ヤマト運輸の配送状況を確認勝手APIを作成してxml,yaml,jsonの形式でデータ取得できるようになったのですが、まだJSONPは未対応でした。
[WebAPI]ヤマト運輸の配送状況を確認するAPIを作ってみた

で、以下のページをチェックしてみると、dankogaiがAmazonの商品情報取得のAPIを作成&紹介しているのですが、xml/yamlに加えてJSONPも対応しているようです。しかもソースも公開してくれています。
AWS Caching Proxy w/ Authentication Support


今回はdankogaiのサイトを見ながらJSONPの仕組みを理解&うちのAPIをJSONP対応してみます。



そもそもJSONPってなに?


Wikipediaより

JSONP(JSON with padding)とは、scriptタグを使用してクロスドメインなデータを取得する仕組みのことである。HTMLのscriptタグ、JavaScript(関数)、JSONを組み合わせて実現される。

…(中略)

JSONPでは、通常、上記src属性のレスポンスの内容はjavascript関数呼び出しの形式となるため、src属性に指定するURLにその関数の名前をクエリ文字列の形式で付加する。一般的な方法では、この時に指定する関数名はWebページ側ですでに定義されているコールバック用の関数の名前になる。


一言でいうと、JSONは単なるデータ形式だけど、JSONPはデータ取得後に呼び元が指定した関数を呼んでくれるって仕組みらしい。


とりあえず、お手本のソースを見てみた。
が、JavaScriptは昔の知識+雑誌で最近の仕組みをチラ読みなレベルで止まっているので、さっぱり分からない…

とりあえず、提示されているHTML Source:と、JS Source:を1ファイルにコピーしてローカルにhtmlファイルを作ったら動作したので、ちょっとづつ改造して仕組みを理解してみた。
※もちろん、コピーする際にJavaScriptはscriptタグでくくりますよ。


まずは大枠をつかんでみる


まず、htmlの方は大した内容じゃないのでスルーします。

で、javascriptを見ると80行ぐらいしかないけど、構造がさっぱり分からない。
なので、まずは大枠だけ見てみる。

(function(d){
 ...
})(document);



かなり削った…
ググったところ、この表記は以下の処理と(ほぼ)等価らしい。

func = function(d){
 ...
}
func(document);



でもって,これは…

function func(d){
 ...
}
func(document);


と等価。
実際に上記のように書き換えて、スクリプトが動作することを確認した。

ここまでくると、関数を1つ作ってそれをコールしているだけということが分かる。
括弧でくくっているあたりは、たぶんスコープとかネームスペースがらみだと思うけど、細かいことは後で調べるとして、次は関数内を見て行く。





初期処理の仕組みを確認


大枠が把握できたので、関数内の構造をざっくり把握する。

function(d){
    var $         = ...;
    var json2list = ...;
    JSONP         = ...;
 
    (function(f){
      if (1 /*@cc_on -1 @*/){ /* good */
        window.setTimeout(f, 100)
      }else{ /* evil */
        window.attachEvent('onload', f)
      }
    })(
      function(){
        JSONP.get($('asin').value);
        $('demo.src')[ 
            d.body.innerText ? 'innerText' : 'textContent'
        ] = $('demo').innerHTML;
      }
    );
}



3つの変数代入と、なんか処理が書いてある。
最初の3つは置いといて、最後の処理を見てみる。

これは、最初見た大枠と同じ構成なので…

(function(f){
  if (1 /*@cc_on -1 @*/){ /* good */
    window.setTimeout(f, 100)
  }else{ /* evil */
    window.attachEvent('onload', f)
  }
})(
  function(){
    JSONP.get($('asin').value);
    $('demo.src')[ 
        d.body.innerText ? 'innerText' : 'textContent'
    ] = $('demo').innerHTML;
  }
);



以下の形に書き換え可能だ。

function func2(f){
  if (1 /*@cc_on -1 @*/){ /* good */
    window.setTimeout(f, 100)
  }else{ /* evil */
    window.attachEvent('onload', f)
  }
})
 
func2( function(){
    JSONP.get($('asin').value);
    $('demo.src')[ 
        d.body.innerText ? 'innerText' : 'textContent'
    ] = $('demo').innerHTML;
});




でもって、さらに書き換えると、以下の形式になる。

function func2(f){
  if (1 /*@cc_on -1 @*/){ /* good */
    window.setTimeout(f, 100)
  }else{ /* evil */
    window.attachEvent('onload', f)
  }
})
 
function func3(){
    JSONP.get($('asin').value);
    $('demo.src')[ 
        d.body.innerText ? 'innerText' : 'textContent'
    ] = $('demo').innerHTML;
}
 
func2( func3() );




さらにさらに書き換えて…

 
if (1 /*@cc_on -1 @*/){ /* good */
    window.setTimeout(func3, 100)
}else{ /* evil */
    window.attachEvent('onload', func3)
}
 
 
function func3(){
    JSONP.get($('asin').value);
    $('demo.src')[ 
        d.body.innerText ? 'innerText' : 'textContent'
    ] = $('demo').innerHTML;
}



上記の形に持ってこれた。

この中で、”/*@cc_on -1 @*/”が不明だったので調べてみると、IE用の条件付きコンパイル構文らしい
@cc_on ステートメント

この仕組みで、IE以外の時は(条件式が1なので)if側、IEの時はelse側に行かせるよう、クライアントによって分岐させている。


なので、仮にIE側だけで考えると、以下のようになる。

 
window.attachEvent('onload', func3)
 
function func3(){
    JSONP.get($('asin').value);
    $('demo.src')[ 
        d.body.innerText ? 'innerText' : 'textContent'
    ] = $('demo').innerHTML;
}




画面が開いたときにfunc3の処理をコールしているだけだ。
これによって、画面が開いたタイミングで書籍情報が表示されるようになっている。
JSONPとは直接関係無さそうなので、本関数についてはこれ以上の深追いはやめとく。


変数$とjson2listを確認する


次は、さっきと飛ばした3つの変数のうち、簡単そうな$とjson2listをチェックする。

function(d){
    var $         = ...;
    var json2list = ...;
    JSONP         = ...;
}



ます最初の$は本当に簡単。

var $ = function(id){ return d.getElementById(id) };



コレは単にd.getElementById(x)を、$(x)って書きたい為のalias。
毎回d.getElementById()と書くと長いから省略してるだけだ。

試しにスクリプト中の$(x)を、d.getElementById(x)に置き換えても正しく動作する。
さらに、dは、documentなので、document.getElementById()と書き換えても動く。




次にjson2list。
これも書き換えてしまうと以下の構造になっていて、何らかの形で取得したJSONの構造体をhtmlのelementに置換する、名称どおりの関数。

function json2list(json){
    ...
    return ul; //または,return ol;
}



これはこれで興味があるけど、今回知りたい”JSONの仕組み”からは少し外れるし、見た感じで大体分かるので、詳細のチェックはスルーしておく。


で、残りのJSONP変数は…

そして、ついに本命の処理っぽいJSONP変数の定義を確認する。

構造は以下のような感じになっているのだけど…

JSONP = {
  get:function(asin){ ... },
  run:function(json){ ... }
};



この構文が分からないので、ちょっと調べてみる.




検索すると、JavaScript の配列と連想配列の違い に情報が載ってた。


JSONPは連想配列(hash)で、keyがgetとrun、valueがそれぞれ関数になっている。

ということは、該当のコードは単なるハッシュなので、以下のように書き換え可能となるはず。

JSONP = new Array();
JSONP[ "get" ] = function(asin){
    var u = 'http://api.dan.co.jp/asin/' + asin + '/JSONP.run';
    var s = d.createElement('script');
    s.charset = 'UTF-8';
    s.id = s.src = u;
    d.body.appendChild(s);
};
 
JSONP[ "run" ] = function(json){
    $('amazon.attr').innerHTML = '';
    if (json.Error){
        $('amazon.attr').appendChild(json2list(json));
        return;
    }
    $('amazon.link').href = 'http://www.amazon.co.jp/gp/product/' + json.ASIN
      + '?ie=UTF8&linkCode=as2tag=blogsofdankog-22';
    if (json.ImageSets){
       var imageset = json.ImageSets.ImageSet;
       if (imageset.length) imageset = imageset[0];
       $('amazon.img').src = imageset.MediumImage.URL;
    }else{
       $('amazon.img').src = 'http://ec1.images-amazon.com/'
         + 'images/G/09/nav2/dp/no-image-no-ciu._AA128_.gif';
    }
    $('amazon.attr').appendChild(json2list(json.ItemAttributes));
};


試しに書き換えてみたら問題なく動作した。



で、作ったハッシュのvalueには、ピリオドでアクセス出来る(JSONP.getのような感じ)

そういえばさっき端折った画面ロード時の処理に、以下の記述があった。
これは、JSONP連想配列内のkey”get”に対応づく関数をコールしているということになる。

JSONP.get($('asin').value);



こちらも連想配列形式でいけるはずなので、以下のように書き換えてもOKだった。
認識は間違ってないようだ。

JSONP[ "get" ]( $('asin').value );






で、最初に戻ると…


htmlのソースに戻ると、ボタンのクリックで今調べたJSONP.get()をコールしている。

JSONP.get(this.previousSibling.value)



これでやっと、スクリプトのつくりと、エントリポイントが理解できた。

次回は、本来の目的だった”JSONの仕組み”を確認します。
dankogai作のAWS APIからJSONの仕組みを理解する(2/2)

Google AnalyticsとAdSenseのブロック

下記のURLを、c:\windows\system32\drivers\etc\hosts(linuxの場合は/etc/hosts)に追加すると、Google AnalyticsとAdSenseをブロックできます。

0.0.0.0  pagead.googlesyndication.com
0.0.0.0  pagead2.googlesyndication.com
0.0.0.0  adservices.google.com
0.0.0.0  video-stats.video.google.com
0.0.0.0  ssl.google-analytics.com
0.0.0.0  www.google-analytics.com
0.0.0.0  4.afs.googleadservices.com
0.0.0.0  imageads.googleadservices.com
0.0.0.0  partner.googleadservices.com
0.0.0.0  www.googleadservices.com
0.0.0.0  apps5.oingo.com
0.0.0.0  www.appliedsemantics.com
0.0.0.0  service.urchin.com
0.0.0.0  googletagservices.com

[JavaScript]textareaを1行づつ処理する

htmlのtextareaタグ内に入力されたコンテンツを1行づつ配列にセットする処理です。

splitByLine = function() {
    var text  = document.getElementById('srcTextArea').value.replace(/\r\n|\r/g, "\n");
    var lines = text.split( '\n' );
    var outArray = new Array();
 
    for ( var i = 0; i < lines.length; i++ ) {
        // 空行は無視する
        if ( lines[i] == '' ) {
            continue;
        }
 
        outArray.push( lines[i] );
    }
 
    return outArray;
}




textarea内の改行文字は,クライアント側のOSによってCR,LF,CR+LFの場合があります。
この為、1行目で正規化(全て\nに統一)させています。その後、正規化後の文字列に対して\nを区切り文字として配列としてlines変数にセットします。

コメント入力など、空行も含めてデータとしたい場合はlinesの中身が欲しいデータとなりますし、空行はデータとして無効な場合は例のようにスキップさせればOKです。

[jQuery]JavaScriptの配列をtableへ1行追加する



JavaScript上の配列を、指定したtableタグ内へ1行追加する方法です。
よく行う処理なので関数化しました。

例えば以下のようなtableがあって…

<input type="button" id="btnTest" value="データを追加" />
 
<table id="tblResult"border="1px" style="border:1px">
    <tr>
        <th style="width:140px;">伝票No</th>
        <th style="width:180px;">配送種別</th>
        <th style="width:80px;">状態</th>
        <th style="width:110px;">時刻</th>
        <th style="width:260px;">センター名</th>
    </tr>
</table>



このようなデータを追加したい場合の処理を説明します。

var rowData = new Array();
rowData[0] = "123456789012";
rowData[1] = "クロネコメール便";
rowData[2] = "投函完了";
rowData[3] = "12/24 23:49";
rowData[4] = "001012:東京中央センター";





tableに行を追加する関数


以下のaddRow()関数で、テーブルへ行を追加できます。

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script>
    //*********************************************************************
    // テーブルへ行の追加を行う
    // eleTable : 行を追加する対象テーブル
    // rowData  : 追加するデータ(配列)
    //*********************************************************************
    addRow = function( eleTable, rowData ) {
        //-------------------------------------
        // 指定された行情報をマークアップする
        //-------------------------------------
        outStr = "<tr>";
        for ( var loopCol = 0; loopCol < rowData.length; loopCol++ ) {
            outStr += "<td>" + rowData[loopCol] + "</td>";
        }
        outStr += "</tr>";
 
        //-----------------------
        // 最後の行に追加
        //-----------------------
        eleTable.append( outStr )
        eleTable.find( "tr:last" ).hide().fadeIn();
    }
</script>





呼び元側の処理サンプル


呼び元側は、以下のような感じでコールします。

$(document).ready(function(){
    $( "#btnTest" ).click(function () { 
        var rowData = new Array();
        rowData[0] = "123456789012";
        rowData[1] = "クロネコメール便";
        rowData[2] = "投函完了";
        rowData[3] = "12/24 23:49";
        rowData[4] = "001012:東京中央センター";
 
        // テーブルに行を追加
        addRow( $('#tblResult'), rowData );
    });
});



例では、ボタンクリックのイベントハンドラで、行を追加しています。

使い方は簡単で、あらかじめhtml側で追加対象のテーブルにidを指定します。
その上でaddRowの第一引数に対象テーブル、第二引数に追加したいデータを指定するだけです。

[WebAPI]ヤマト運輸の配送状況を確認するAPIを作ってみた

ヤマト運輸の配送状況をプログラムで確認したかったのでWebAPIを作成しました。

使い方


xml,json,yaml形式に対応しています。
以下のURLでアクセスすると、xml,yaml,json形式で配送状況が返ってきます。(数字の部分は伝票Noです)
http://nanoappli.com/tracking/api/123456789012.json
http://nanoappli.com/tracking/api/123456789012.yaml
http://nanoappli.com/tracking/api/123456789012.xml

※2011/1/24追記:JSONPにも対応しました。詳細は以下のリンクを参照してください。
ヤマトの伝票情報取得APIがJSONP対応しました

※2012/2/1追記:営業所データの取得APIを追加しました

配送状況の情報は、オフィシャルサイトの問い合わせ画面で確認できるものと同じです。

データ構造


応答は、以下のようなデータ構造で帰ってきます。
例はxmlですが、yaml・jsonもルートタグの存在を除いて同じデータ構造ですので、他フォーマットの例は割愛します。

<?xml version=”1.0″ encoding=”UTF-8″?>
<root>
<result>0</result>
<status>投函完了</status>
<slipNo>123456789012</slipNo>
<itemType>クロネコメール便</itemType>
<statusList>
<item>
<status>発送</status>
<date>01/12</date>
<time>11:28</time>
<placeName>赤平センター</placeName>
<placeCode>004440</placeCode>
</item>
<item>
<status>投函完了</status>
<date>01/17</date>
<time>16:44</time>
<placeName>石垣島支店</placeName>
<placeCode>098400</placeCode>
</item>
</statusList>
</root>

resultは0が正常終了0以外が異常終了です。

誤った伝票Noや、ヤマトのシステム未登録の伝票を指定時は、それぞれ以下のような応答となります。(伝票Noの正当性はこちらのチェックデジット算出方法で確認できます)

<?xml version=”1.0″ encoding=”UTF-8″?>
<root version=”2.0″>
<result>-1</result>
<status>伝票番号誤り</status>
<slipNo>123456789012</slipNo>
</root>

<?xml version=”1.0″ encoding=”UTF-8″?>
<root version=”2.0″>
<result>-1</result>
<status>伝票番号未登録</status>
<slipNo>123456789013</slipNo>
</root>

利用について


  • どなたでもご自由に使用していただいてかまいません。

  • アクセスは毎秒1リクエストを超えないようにしてください。
    酷い場合はアクセス拒否やAPIの公開中止を行います。

  • 仕様変更、サービスの停止、廃止が予告無く行われる”可能性”があります。
    基本的にほったらかしにする予定ですが、勝手APIなのでなんとも…

  • 本APIを使用してサービスを作成した場合は、本ページへのリンクを張っていただけると有り難いです。
    (義務では有りません)

  • 個人が特定できない形で、動作ログや統計値の情報等を採取する可能性があります。

Apache+PHPで複数フォーマットのデータ生成をスマートに処理する

PHPでwebアプリを作成していると、動的に生成したファイルをダウンロードさせたい場合があります。
ありがちなパターンとしては、DBの内容をcsv形式でダウンロードさせる等です。

このとき、同じデータを複数のフォーマットで返したい場合があります。
例えば、注文No12345のデータを提供したいが、下記のように拡張子で書式が決まるといった状況です。

http://example.com/order/12345.xml
http://example.com/order/12345.yaml
http://example.com/order/12345.csv



この際、*.xml、*.yaml、*.csvの各フォーマット毎で異なるPGを用意すると、管理が煩雑です。



今回の記事では、PHPで1本のプログラムで、複数フォーマットを取り扱う場合のパターンを説明します。
例はPHPですがperlやruby等、他の言語でも考え方は同じです。


特定フォルダ以下の全URLを、単一のプログラムに応答させる

まずはapacheで、所定フォルダ以下に対するあらゆるURLに対して、単一のプログラムが応答するように設定します。このような構成は一般にフロントコントローラと呼ばれます。
※フロントコントローラの説明については、Martin FowlerのPoEAAが詳しいです。

フロントコントローラの構成はwebサーバ側の助けが要るので、apacheでの設定が必要です。
具体的には、対象のフォルダに、以下の内容で.htaccessファイルを作成します。(.htaccessファイルは最初の例だとDOCUMENT_ROOT/orderフォルダの下に置きます)

RewriteEngine on 
RewriteRule .* index.php



ちなみに余談ですが、Windowsの場合はエクスプローラから.htaccessと言う名前のファイルを作ろうとすると、以下のように、”ファイル名を入力して下さい”というエラーが表示されます。


この場合は、一旦a.htaccess等のファイル名で作成した後、コマンドプロンプトよりrenameコマンドで変更するとピリオドから始まるファイルを作成できます。



次に、肝心のindex.phpを作成します。
とりあえずテストなので、適当な文字を返すだけのPGにしておきます。

<?php
print "test";



これで、対象フォルダ(http://example.com/order/ 以下)の全URLに対して、order/index.phpが応答するようになりました。

以下のフォルダにテスト用のファイルを置いたので確認ができます。
http://nanoappli.com/dev/201101_frontController/

試しに以下のようなURLでアクセスしてみると、”/dev/201101_frontController/”以下に対しては、何であってもindex.phpが応答していることが分かります。
http://nanoappli.com/dev/201101_frontController/foo
http://nanoappli.com/dev/201101_frontController/foo.xml
http://nanoappli.com/dev/201101_frontController/foo.csv
http://nanoappli.com/dev/201101_frontController/foo/bar.txt

これで、フロントコントローラの設定が完了しました。


アクセスされたURLをPHP側で認識する

次にindex.php側のプログラムを少し改良し、自分がどんなURLでアクセスされてきたを認識できるようにします。

これはphp側のグローバル変数、$_SERVER[“REQUEST_URI”]を利用することで、URLが判別可能です。
※ちなみに、URL判別からは話が外れますが、HTTP_HOSTでサーバ名や、PHP_SELFで自身のプログラム名の取得も可能です。

動作確認のため、/dev/201101_frontController2/index.phpに以下のプログラムを作成しました。

アクセスされたURL:<br />
<?php echo htmlspecialchars( $_SERVER["REQUEST_URI"] ); ?>
<br /><br />
 
アクセスされたURL(ホスト名付):<br />
<?php echo htmlspecialchars( "http://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"] ); ?>
<br /><br />
 
実際に応答したPG名:<br />
<?php echo htmlspecialchars( $_SERVER['PHP_SELF'] ); ?>
<br /><br />



例えば、http://nanoappli.com/dev/201101_frontController2/foo/bar.csvへアクセスすると、以下のように$_SERVER[“REQUEST_URI”]の値を取得できます。


これで、PHP側にて自身が何のURLでコールされたかを自覚出来るようになりました。


URLの拡張子に応じて処理を分岐する


ここまでくれば、後はPHPの文字列処理だけになります。

例えば、最初の例(http://example.com/order/12345.xml)で、注文Noが5桁の数字という制限があったとした場合、以下の処理で伝票番号と拡張子を取得できます。



アクセスされたURL:


<?php echo $_SERVER["REQUEST_URI"] ?>
<br /><br />
 
function checkUrl() {
    // 数字5桁 + xmlまたはcsvで終わるURLのみOKとみなす
//  $pattern = "/\/dev\/201101_frontController3\/([0-9]{5})\.(xml|csv)$/";
    $pattern = "/\/order\/([0-9]{5})\.(xml|csv)$/";
    $matchResult = preg_match( $pattern, $_SERVER["REQUEST_URI"], $matches );
    if ( $matchResult <= 0 ) {
        print "URLの形式が不正です";
        return;
    }
 
    print "伝票No:"       . $matches[1] . "<br />";
    print "フォーマット:" . $matches[2] . "<br />";
}
 
checkUrl();
?>



上記のPGは、以下のURLで動作確認できます。URLのファイル名部分(12345.xml)を色々変更して試してみてください。
http://nanoappli.com/dev/201101_frontController3/12345.xml

プログラム内で、データ生成のキー(伝票No)と、フォーマットの文字列(拡張子)まで取得できれば、後はPHPのプログラムでデータ検索と,処理分岐を行うだけです。
(データ処理自体は、普通のPHPプログラムなのでこの記事では割愛します)





以上で、複数フォーマットのデータを1本のプログラムで生成させる,シンプルな仕組みが出来ました。


まとめ


1.特定URL以下へのアクセスを一本化するために、以下の内容で.htaccessファイルを作成する。

RewriteEngine on 
RewriteRule .* index.php



2.PHPでは、$_SERVER[“REQUEST_URI”]でアクセスされたURLを検出できる。

3.上記2.で取得した文字列を解析して、データ生成のキーと、フォーマットの文字列(拡張子)を取得する。