Y's note

Web技術・プロダクトマネジメント・そして経営について

本ブログの更新を停止しており、今後は下記Noteに記載していきます。
https://note.com/yutakikuchi/

ギャル文字変換Bookmarklet

概要

ギャル文字も新しい日本語表記の一つだと勝手に思っています。
「ギャル文字の方が断然読みやすいです!」ギャルの中でも少数派だとは思っていますが、
そんな方のためにWebページのコンテンツをギャル文字に変換するスクリプトを書いてみました。
変換機能はbookmarkletとして提供します。方法は閲覧中のWebページでbookmarkletを実行するだけです。
※利用は自己責任でお願いします。また何かしら問題が発生したら提供を即中止します。

Bookmarklet

ブックマーク

ギャル文字変換bookmarklet
※上のリンク先に遷移し、表示されているリンクをbookmarkしてください。

bookmarkソース
javascript:void((function(){var%20s=document.createElement('script');s.setAttribute('src','http://mobiles-proxy.appspot.com/statics/js/v1/gmbookmarklet.js');s.setAttribute('id','gmbookmark');document.body.appendChild(s)})());

上のソースをそのままbookmarkに貼付けてもらっても大丈夫です。

変換後イメージ

某サイトのTopics(iPhone-Safari)

twitterのstatus(safari)

GitHub

https://github.com/yutakikuchi/JS/tree/master/gmconv
JSに詳しい方、修正点などありましたらご指摘いただけると助かります。
マルチバイトkeyのhashテーブルを基に変換処理をしているだけです。

ソースも一部blogに張っておきます。

bookmarkletから呼び出されるscript
if( !GM ) var GM = {};
if( !IE ) var IE = ( /*@cc_on!@*/0 ) ? true : false;
if( !JS ) var JS = {};
(function(){
    JS.util = {};
    JS.util.getTagName = function( tag ) {
        return document.getElementsByTagName( tag );
    }
    JS.util.$ = function( id ) {
        return document.getElementById( id );
    }
    JS.util.createElem = function( elem ) {
        return document.createElement( elem );
    }
})();

var meta = JS.util.getTagName( 'meta' );
var charset = 'utf-8';
var content,chars,reg;
for(var i=0; i<meta.length; i++ ){
    chars = meta[i].getAttribute( 'charset' );
    if( chars !== null ) {
        charset = chars;
        break;
    }
    content = meta[i].getAttribute( 'content' );
    if( content !== null ) {
        reg = /charset=(.*?)$/gi;
        content.match( reg );
        if( RegExp.$1 != '' && RegExp.$1 != null ) {
            charset =  RegExp.$1;
            break;
        }
    }
}
if( charset='t' ) charset = 'utf-8';
var script = JS.util.createElem( 'script' );
//script.setAttribute( 'src', 'gmconv_' + charset.toLowerCase() + '.js' );
script.setAttribute( 'src', 'http://mobiles-proxy.appspot.com/statics/js/v1/gmconv_' + charset.toLowerCase() + '.js' );
script.setAttribute( 'charset', charset );
script.setAttribute( 'id', 'gmconv' );
document.body.appendChild( script );
変換スクリプト
if( !GM ) var GM = {};
(function(){
GM.convert = function( data ) {
    var hash = {};
    hash[ 'あ' ] = '†の';
    hash[ 'ア' ] = '了';
    hash[ 'い' ] = 'レヽ'; 
    hash[ 'イ' ] = 'ィ';
    hash[ 'う' ] = 'ぅ';
    hash[ 'ウ' ] = '宀';
    hash[ 'え' ] = 'ぇ'; 

(略)

    for( var i in hash ) {
        var reg = new RegExp( i, 'g' );
        data = data.replace( reg, hash[i] );
    }
    return data;
}
})();
var body  = JS.util.getTagName( 'body' );
if( typeof( body[0].innerHTML ) !== 'undefined' ) {
    body[0].innerHTML = GM.convert( body[0].innerHTML );
}

対応文字

  1. カタカナ
  2. ひらがな
  3. 漢字(一部)

文字コード

変換したい元のページの文字コードが以下のものであれば変換可能としています。

  1. UTF-8
  2. EUC-JP
  3. Shift_JIS
  4. ISO-2022-JP

対応ブラウザ

Macのブラウザでテストをしています。以下のブラウザではほとんどのページが変換可能であることを確かめました。

  1. Mobile Safari(iPhone)
  2. Safari
  3. FireFox
  4. Chrome ※2011/2/28 動作が微妙です
  5. Opera

iPhoneのwindow.getSelection()について

値の取得

window.getSelection(document.getSelection)は画面のドラッグにより選択した文字列を読み取るjavascriptの関数です。
以前はsafariなどで利用できなかったようですが、現在はほとんどのブラウザで読み取ることが可能です。
(safari/firefox/chrome/operaでは動作を確認しています。2011/2/15日現在)
iphoneでwindow.getSelectionを利用するときに少しハマったので、メモしておきます。

browser-javascript

HTMLに仕込んだjavascriptのwindow.getSelection()はiphoneでも動作します。
HTMLを読み込んだ時点でjavascriptが正しく設定されていると値が取得できるようです。
以下は選択肢た文字列をgoogle検索結果に自動的に飛ばす処理を書いています。

<!DOCTYPE html>
<html><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">
<title>get Selection Test</title>
<script>
var drag = false;
var getText = function() {
    if( drag === true  ) {
        window.open( 'http://www.google.com?q=' + encodeURI( window.getSelection() ) );
    }
    drag = false;
}

var setDragStatus = function() {
    drag = true;
}

document.addEventListener( 'touchend', getText, false );
document.addEventListener( 'touchstart', setDragStatus, false );
</script>
</head>
<body>
選択文字列 iPhone テスト<br> 
選択文字列 iPhone テスト<br> 
選択文字列 iPhone テスト<br> 
<br><br>
選択文字列 iPhone テスト<br> 
</body>
</html>

bookmarklet-javascript

bookmarkletの場合は注意が必要です。※PCのbrowserでは以下のコードが動作しても、iphone上では動作しません。
作業手順は文字列選択→bookmarklet起動という流れです。

javascript:void((function(){alert(window.getSelection());})());

上の件を調査してみると、bookmarkletとしてwindow.getSelection()を使うことはできないようです。bugとして報告されています。
http://openradar.appspot.com/7013257

解決方法

以下単純ですが、iPhone対策としてのbookmarklet-javascriptを記述します。適用できないケースも有ると思うので、そこに関してはご勘弁を。
※上と作業手順が変わります。bookmarklet起動→文字列選択となります。
変更点は2点です。

  1. bookmarkletではwindow.getSelectionを呼び出さない。
  2. bookmarkletではtouchendイベントにてwindow.getSelection()を取得するようなコードをHTMLに仕込む。

変更後のサンプルです。hoge.jsの箇所は適当に読み込みたいjavascriptファイルパスに変えてください。

bookmarklet
javascript:void((function(){s=document.createElement('script');s.setAttribute('src','hoge.js');document.body.appendChild(s);})());
bookmarkletから読み込まれるjavascript
var drag = false;
var getText = function() {
    if( drag === true  ) {
        window.open( 'http://www.google.com?q=' + encodeURI( window.getSelection() ) );
        //alert( window.getSelection() );
    }
    drag = false;
}

var setDragStatus = function() {
    drag = true;
}
document.addEventListener( 'mouseup', getText, false );
document.addEventListener( 'mousedown', setDragStatus, false );
document.addEventListener( 'touchend', getText, false );
document.addEventListener( 'touchstart', setDragStatus, false );

このように変更するとiphoneでもbookmarklet起動で選択文字列が取得できます。
意外とiPhoneの画面ドラッグだけでGoogle検索に飛ばすのはUI的に楽かもしれないですね。

innerHTMLのeventに関する罠

innerHTMLの罠を紹介します。
addEventListenerにてtagにeventハンドラを追加している場合、
そのタグの中身をinnerHTMLを使って書き換えを行うと、eventが利用できなくなります。

sampleコード

<html>
<script>
var init = function() {
    var sample = document.getElementById( 'child_id' );
    sample.addEventListener( 'mousedown', down, false );
}

var down = function() {
    alert( 'mouse down' );
}

var replaceHTML = function() {
    var parent = document.getElementById( 'parent_id' );
    var tmp = parent.innerHTML;
    parent.innerHTML = tmp;
}

</script>
<body onload="init();">
<div id="parent_id">
<div id="child_id">div sample</div>
</div>
<input type="button" value="push" onclick="replaceHTML();">
</body>
</html>

sampleコードの中ではparent_id,child_idが入れ子の状態になっています。
parent_idのinnerHTMLに元々のhtmlをそのまま代入しているだけなのですが、
child_idのeventが失われてしまいます。innerHTMLの値を書き換える場合は注意が必要です。

回避策

必要な要素のみ書き換えを行うようにします。
上の場合ですと、child_idのinnerHTMLだけを書き換えます。
Safariでは正常動作確認済みです。

<html>
<script>
var init = function() {
    var child = document.getElementById( 'child_id' );
    child.addEventListener( 'mousedown', down, false );
}

var down = function() {
    alert( 'mouse down' );
}

var replaceHTML = function() {
    var child = document.getElementById( 'child_id' );
    var tmp = child.innerHTML;
    child.innerHTML = tmp;
}

</script>
<body onload="init();">
<div id="parent_id">
<div id="child_id">div sample</div>
</div>
<input type="button" value="push" onclick="replaceHTML();">
</body>
</html>

その他

jQueryのhtml関数を利用しても同様にeventが失われる結果になります。
以下はjQueryを利用した場合のサンプルコードになります。

<html>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.4.4.js" ></script>
<script>
var init = function() {
    var child = $( '#child_id' );
    child.mousedown(function(){
        alert( 'mouse down' );   
    });
}

var replaceHTML = function() {
    //var parent = document.getElementById( 'parent_id' );
    var parent = $( '#parent_id' );
    var tmp = parent.html();
    parent.html( tmp );
}

</script>
<body onload="init();">
<div id="parent_id">
<div id="child_id">div sample</div>
</div>
<input type="button" value="push" onclick="replaceHTML();">
</body>
</html>

お願い

※firstChildなどを使ってDOM操作すれば以下の問題も解決出来ますね。
子nodeのidが取れれば上の解決策で対応できるのですが、子nodeのidが取れない場合どうすれば良いでしょうか。Eventが引き継げるコードさえかければ大丈夫なのですが、方法が見つかりません。どなたかご存じの方ご指摘いただけると助かります。

20秒で理解するJSONP

1行理解

JSONP(JSON with Padding)とはjavascriptコールバック関数を利用し外部ドメインサーバとJSONデータをやり取りする方法である。

※通常のHTMLでは同一ドメイン以外の通信が行えないという制約があるが、javascriptタグのsrc属性とJSONデータ/コールバック関数を利用して外部ドメインとデータのやり取りを可能にする技術。

やること

■クライアント

外部ドメインのサーバから受け取ったJSONデータを処理するコールバック関数の定義。

※ここで定義するコールバック関数はサーバサイドで定義される関数名と一致させる必要がある。

■サーバ側

JSONデータを引数としたクライアント側のコールバック関数の呼び出し

クライアントサンプル

<html>
<head>
<script type='text/javascript' src='http://www.yutakikuchi.com/jsonp/data.dat?callback=callbackFunc' />
var number = Object();
var callbackFunc = function( data ) {
   //alert( data );
   number = eval( data ); //javascriptの配列データが格納される。
}
</head>
</html>

※scriptタグのsrc属性でサーバサイドへのJSONデータのリクエストを行います。callback関数名はクライアント側で自由に設定できることを想定しています。

サーバサイドサンプル

<?php 
$callbackfunc = $_GET[ 'callback' ] ;
$test_data = array( 'items' => array( 1,2,3 ) );
$json_data = json_encode( $test_data );
echo $callbackfunc . "( $json_data ); ";

※以下はサーバサイドの実行結果で、通常のjavascript関数呼び出しになります。

callbackFunc( {"items":[1,2,3]} );

セキュリティ対策

悪意ある第三者から攻撃されないようにサーバサイドはIP制限や認証トークンを利用したほうが良いです。

javascriptのクラスまとめ

クラスの概念

  • Javascriptにも一応クラスみたいなものといった概念が存在しますが、多言語のクラスに比べると規制が緩いもののようです。
  • javascriptではprototypeといったものをベースとしたオブジェクト指向として考えられています。(これはあとで説明)
  • prototypeのチェーンといった方法を利用して継承を実装することも可能です。

シンプルなインスタンス

関数で定義されたクラス的なものをnewすることでインスタンスを生成することが出来ます。下にサンプルコードを記述します。

var Parent = function() { //ある意味コンストラクタみたいなもの
   this.name = 'Parent'; //クラスのプロパティはthisで表現する。
   this.echo = function() { 
      alert( this.name );
   }
}

var instance = new Parent(); //インスタンス化
instance.echo();  // Parent

Prototypeの利用

ここがprototypeベースのオブジェクト指向の内容になります。上のインスタンス化の方法にはクラス的なものをnewするときにプロパティやメソッドをまるまるメモリに展開してしまうため、newの数が増えるたびに無駄な領域確保が発生してしまいます。そこでPrototypeといった属性を利用します。Prototypeを利用することにインスタンスからの参照を暗黙的参照に切り替えることが出来ます。コードを例に見てみます。

var Parent = function() {
      this.name = 'Parent';
}

// prototypeを利用
Parent.prototype.echo = functio() {
   alert( this.name ); // Parent
};

var instance = new Parent();
instance.echo(); //暗黙的な参照が行われる。

newしたときにParentが展開されます。instance.echo()のコールではまずParentがechoというメソッドを所有するかどうかを観に行きます。実際にはParent自体は所有していません。ところがParentが所有していないとjavascriptが解釈するとprototype属性以下のメソッドを探索の対象にします。prototype以下にはecho()メソッドが存在するのでそこを利用します。インスタンス自体がメソッドを所有しているかどうかのチェックは次のコードで確認できます。インスタンスはechoというメソッドを直接所有していません。prototypeは直接所有しています。

alert( instance.hasOwnProperty( 'echo' ) ); //false
alert( Parent.prototype.hasOwnProperty( 'echo' ) );  //true

Prototypeを利用した継承

Prototypeを利用すると継承を実現できます。実際にはPrototypeのチェーンというものにより継承が成り立ちます。以下はサンプルコードになります。

//親クラス
var Parent = function(){} 

//親のprototype
Parent.prototype = { 
    name : 'parent',
    method : function() { 
        alert( this.name ); 
    }
};

//子クラス
var Child = function(){} 

//継承
Child.prototype = new Parent(); 

//property追加
Child.prototype.name = 'child';

//無名関数とapplyを使った定義
(function() { 
    this.test = 'test';
    this.echo = function() {
        alert( this.test );
    }
}).apply( Child.prototype )

//子クラスの呼び出し
var childClass = new Child();
childClass.echo(); //test
childClass.method(); //child

Child.prototype = new Parent(); このように記述するとParentを継承することが出来ます。子どもの方ではprototype.property名 = value;といったようにprototypeを羅列するものOKですが、無名関数とapplyをうまく使うとすっきりしたコードを書くことが可能です。(サンプルでは両方で記述しています。)実は継承の書き方は複数ありますが、私は上のprototypeパターンしか知りませんでした。以下のページにその他の方法も沢山書いてあります。素晴らしくよくまとまっています。
http://d.hatena.ne.jp/shogo4405/20070121/1169394889

new演算子について

new演算子の内部では何をやっているのだろうというのを検証してみました。newした後に以下のコードを実行してみるとtrueとなります。

alert( childClass.__proto__ == Child.prototype ); //true

どうやらinstanceの__proto__属性に対してChild.prototypeを格納しているようです。他にも引数の受け取りやinstanceのreturnも行っていると思うのですが、正確な挙動が分からなかったので検索してみました。検索した結果とてもよいページを見つけました。newの内部的なコードは次のようになるようです。

Function.prototype.construct = function () {
  var newInstance = new Object();
  if (this.prototype instanceof Object)
    newInstance.__proto__ = this.prototype;

  var returnValue = this.apply(newInstance, arguments);
  if (returnValue instanceof Object)
    return returnValue;
  return newInstance;
};
var o = F.construct(arg); // == new F(arg)

流れは
・オブジェクト生成。
・__proto__に対してprototypeを設定。
インスタンスに対して引数をapplyする。
インスタンス/オブジェクトを返却。
※上のコードは以下のページより引用させていただきました。
http://nanto.asablo.jp/blog/2005/10/24/118564