試しにLtkでゴミのようなブラウザ(?)を作る


はじめに

Ltkを実際に使ってみようということで,とりあえずショッボいブラウザ的な何かを作ってみます.

コードの書き方がLispらしくないだとか,インターフェースクソすぎとか,ツッコミどころが腐るほどあるでしょうがよろしければお付き合いください...

参考にさせていただいた資料

Common LispでURLからページタイトルを取得する
Ltkサンプル集
LtkでGUIの練習
LTK - a Lisp binding to the Tk toolkit

環境

今回の開発・実行環境です.

メイン言語:CommonLisp
処理系:Clozure CL
GUI構築ライブラリ:Ltk
OS:Mac OS X 10.11.4(El Capitan)

要件

スーパー簡単なブラウザの偽物みたいなものを作ろうと思います.
実装する機能はとりあえず,

URLを入力すればそのページのソースを解析せずにそのまま取得して画面に表示する

これだけです!
ブラウザと呼ぶにはあまりにもプアップアなアプリですけど,今回は練習ということで許してはいただけませんでしょうか.

というわけで具体的に必要な機能としては,
GUI関係

  • URLを入力する部分
  • 検索を開始するボタン
  • ページのソースを表示する部分(スクロール可能なコンポーネント)

機能

  • 入力されたURLをテキストとして取得する機能
  • 取得したURLにアクセスしソースを取ってくる機能
  • ソースを表示用の場所に貼り付ける機能

といったところでしょうか.

成果物

とりあえず作ったのはこんな感じのアプリ(?)です.(このページのURLを使っています.)

起動時の画面

URL入力後「決定」ボタンを押下した時

こんな感じってだけです.

中身

コード的にもすごく簡単です.

ltk_gui_test.lisp
(ql:quickload :ltk)
(ql:quickload :drakma)
(ql:quickload :plump)
(ql:quickload :clss)

(setf drakma:*header-stream* *standard-output*)
(defparameter *contents* nil)

;;URLとHTMLタグを選択し,コンテンツを取得する
(defun get-html (hostname tag)
  (let ((contents (car (map 'list (lambda (val) (plump:text val))
            (clss:select tag
                     (plump:parse
                      (drakma:http-request hostname)))))))
    (setf *contents* contents)))


(defun app ()
  (ltk:with-ltk ()
    (ltk:wm-title ltk:*tk* "最終的に簡易ブラウザを作ってみたいアプリ")
    (let* ((lbl1 (make-instance 'ltk:label :text "URLの入力"
                      :width 10))
       (entry (make-instance 'ltk:entry))
       (btn (make-instance 'ltk:button :text "決定"))
       (lbl2 (make-instance 'ltk:text :selectborderwidth 500)))

      (ltk:pack lbl1)
      (ltk:pack entry)
      (ltk:pack btn)
      (ltk:pack lbl2)

      (ltk:bind btn "<ButtonRelease-1>"
        (lambda (e)
          (declare (ignore e))
          (when (ltk:text lbl2)
            (get-html (ltk:text entry) "body")
            (setf (ltk:text lbl2) *contents*)))))))

です.
一応簡単にコードの説明しておきます.

使用ライブラリについて

初期設定
(ql:quickload :ltk)
(ql:quickload :drakma)
(ql:quickload :plump)
(ql:quickload :clss)

(setf drakma:*header-stream* *standard-output*)
(defparameter *contents* nil)

見ての通り,ライブラリをインストールした後,drakma用のストリーム設定.
*contents*には後で,取得するページのソースを束縛します.

使用ライブラリについて
(正直それぞれのライブラリのことほとんどわかってません)
- drakma : HTTPクライアントライブラリ
- plump : 文字列をDOMに変換するライブラリ
- clss : パーサライブラリ

ページのソース取得

指定のURLのソースのうち,指定したタグのソースだけを取り出す
(defun get-html (hostname tag)
  (let ((contents (car (map 'list (lambda (val) (plump:text val))
            (clss:select tag
                     (plump:parse
                      (drakma:http-request hostname)))))))
    (setf *contents* contents)))

なんのひねりもありません.なんなら文字コードの処理すらしてません.
drakmaでページのソースを取得し,plumpでDOM化,clssで指定のタグだけ抽出,plumpで文字列化してそれをcontentsに束縛.
最後にグローバル変数の*contents*に束縛する,それだけです.

インターフェース設計

最後にインターフェースの構築です.
今回一番やりたかったのがここですね.
とは言ってもこのサイトから頂いてきた感じですけど...

インターフェース記述
(defun app ()
  (ltk:with-ltk ()
    (ltk:wm-title ltk:*tk* "最終的に簡易ブラウザを作ってみたいアプリ")
    (let* ((lbl1 (make-instance 'ltk:label :text "URLの入力"
                      :width 10))
       (entry (make-instance 'ltk:entry))
       (btn (make-instance 'ltk:button :text "決定"))
       (lbl2 (make-instance 'ltk:text :selectborderwidth 500)))

      (ltk:pack lbl1)
      (ltk:pack entry)
      (ltk:pack btn)
      (ltk:pack lbl2)

      (ltk:bind btn "<ButtonRelease-1>"
        (lambda (e)
          (declare (ignore e))
          (when (ltk:text lbl2)
            (get-html (ltk:text entry) "body")
            (setf (ltk:text lbl2) *contents*)))))))

ボタン(btn)を押下するとget-htmlが起動し,エントリ(entry)に書かれているURLのbodyタグ内のソースを取得,その内容をテキストボックス(lbl2)に転写する,だけです.

おわりに

自分で考えてる部分が少なくないか?

バグまみれの上にパッケージ管理についてもイマイチ理解していないので無駄が多いです.
ltkとタイプするのだけ早くなりました(^^;)

これからちょっとしたGUI構築をLtkでできるように勉強したいと思います,はい.

あぁ,勉強したいこといっぱいだなぁ...

読んでくださった方,お付き合いいただきありがとうございます!