レガシー環境のためのWindows Script Host(WSH)の解説

ここでは、XPやVista以前のレガシーな環境でのスクリプト処理を行うためのWSHについて説明します。

もし、Windows7以降のみが前提であれば、Windows PowerShell の使用も検討しましょう。
ただしPowerShellだとOfficeのCOMの解放処理が面倒だったりInternetExploreの操作が安定しなかったりする

概要

WSHとはWindows Script Hostの略です。

Microsoft Visual Basic Scripting Edition (以下 VBScript) と, Microsoft JScriptの2種類がサポートされています。※
※環境によってはEdgeのエンジンが使用可能です。参照

JScriptはMS独自の規格で一般なJavaScriptとは規格が異なります。

最大の強みは開発環境のないマシンでも修正、実行が行えることで、バッチファイルより複雑な処理を実現することが可能です。

具体的には下記のようなことが可能です。

・システム情報の取得と変更
・ファイル・フォルダの作成、コピー、属性変更
・レジストリの読み書き
・プログラムの実行
・COMコンポーネントの作成・利用

スクリプトの作成と実行

ここではスクリプトの作成方法と実行方法について記述します。
まず、下記のようなファイルを作成してください。

var sHello;
sHello="Hello World!";
WScript.Echo(sHello);

このプログラムは"HelloWorld!"の文字を表示するだけのプログラムです。 このプログラムをWSHで実行させるには下記のコマンドをコマンドプロンプトから実行してください。

CScript Hello.js

コマンドプロンプトに文字が出力されたと思います。

実は、WSHは上記の例のようなコマンドラインベースのものだけでなく、GUIを使用した対話型のプログラムも作成できます。

コマンドプロンプトで下記のようなコマンドを実行してください。

WScript Hello.js

おそらく、メッセージボックスが表示されたかと思います。
また、CScriptとWScriptは32bit用と64bit用のそれぞれが存在します。

Path 説明
C:\Windows\SysWOW64\CScript.exe 32ビット用 コンソール表示に対応したプログラム
C:\Windows\SysWOW64\WScript.exe 32ビット用 ウィンドウに対応したプログラムに対応したプログラム
C:\Windows\system32\CScript.exe 64ビット用 コンソール表示に対応したプログラム
C:\Windows\system32\WScript.exe 64ビット用 ウィンドウに対応したプログラムに対応したプログラム

COMを使用する場合、64bitで動かすか、32bitで動かすかは重要なことになります。

CScriptおよびWScriptのオプション

パラメータ 説明
//B バッチ モードでスクリプトを実行します。ユーザー プロンプトおよびスクリプト エラーをコマンド ライン表示しません。既定の設定は、対話モードです。
//D アクティブ デバッグを使用可能にします。
//E:engine 指定したスクリプト エンジンでスクリプトを実行します。
//H:CScript または //H:Wscript CScript.exe または WScript.exe をスクリプト実行用の既定アプリケーションとして登録します。どちらも指定しなければ、WScript.exe が既定のアプリケーションとみなされます。
//I 既定値です。対話モードでスクリプトを実行します。バッチ モードとは逆に、ユーザー プロンプトおよびスクリプト エラーの表示を有効にします。
//Job: 指定した JobID を .wsf ファイルから実行します。
//logo 既定値です。バナーを表示します。nologo の逆の設定です。
//nologo 実行時にバナーを表示しません。既定の設定では、logo が適用されます。
//S このユーザーに対して、現在のコマンド ライン オプションを保存します。
//T:nn スクリプトの実行を継続できる時間の上限 (タイムアウト) を設定します。既定では、スクリプトは制限なしで実行されます。//T パラメータでタイムアウトを設定すると、スクリプトが長時間にわたって実行されるのを防止できます。実行時間が指定値を超過すると、CScript が IActiveScript::InterruptThread メソッドを使用してスクリプト エンジンに割り込み、プロセスを強制終了します。
//U Windows NT および Windows 2000 で使用できるオプションです。コマンド ライン出力を Unicode にします。CScript には、Unicode と ANSI を自動的に判別する機能はありません。既定の設定では、ANSI が使用されます。
//X スクリプトをデバッガで実行します。
//? コマンド パラメータの説明および使用法に関する概略を表示します

http://msdn.microsoft.com/ja-jp/library/cc392525.aspx

デバッグ方法

WSHはVisual Studioでデバッグを行うことが可能です。
Visual StudioはExpressの場合にデバッグが行えないかもしれません。Professional以降が必要かもしれません。

1.//xオプションを付与してWSHを実行します

CScript Hello.js //x

2.下記のようなウィンドウが表示されるのでデバッグを行いたいVisualStudioのバージョンを選択して「はい」を押してください。
無題.png

3.あとは通常のVSのデバッグが使用できます。
無題.png

外部のファイルを利用する方法

以下の例では外部に記述したVBScriptを使用する方法について記述します。

.\lib\ADOCtrl.vbs

Option Explicit

'*
'* ADOCtrlのオブジェクトを作成・取得する.
'* @return ADOCtrl
'*
Function getADOCtrl()
    set getADOCtrl = new ADOCtrl
End Function

'! @class ADOCtrl
'!
class ADOCtrl
    Private m_objADOCnn

    '* DBに接続
    '* @param[in] host ホスト名
    '* @param[in] db   データベース名
    '* @param[in] user ユーザ名
    '* @param[in] pass パスワード
    '*
    Public Function ConnectSqlServer(Byval host, Byval db, Byval user, Byval pass )
        Set m_objADOCnn = CreateObject("ADODB.Connection")

        Dim cnn

        cnn = "PROVIDER=SQLOLEDB" & _
              ";SERVER=" & host & _
              ";DATABASE=" & db & _
              ";UID=" & user & _ 
              ";PWD=" & pass & ";"

        Call m_objADOCnn.Open( cnn )
        If m_objADOCnn.Errors.Count = 0 Then
            ConnectSqlServer = True
        Else
            ConnectSqlServer = False
        End If
    End Function

    '*
    '* DBの接続を切断する。
    '*
    Public Sub Close
        If Not m_objADOCnn Is Nothing Then
            m_objADOCnn.Close
            Set m_objADOCnn = Nothing
        End If
    End Sub

    '* 
    '* SQLの実行
    '* @param[in]  SQL文
    '* @param[out] 出力結果
    '*
    Public Function ExcuteSQL( ByVal sSQL, ByRef outRet )
        Dim objRS
        ExcuteSQL = False

        Set objRS = CreateObject("ADODB.Recordset")
        Call objRS.Open( sSQL, m_objADOCnn, 0, 1, 1 )
        If m_objADOCnn.Errors.Count > 0 Then
            Set objRS = NoThing
            Exit Function
        End If

        If objRS.EOF Then
            Set objRS = NoThing
            Exit Function
        End If

        outRet = objRS.GetRows()
        objRS.Close

        Set objRS = NoThing
        ExcuteSQL = True

    End Function
End Class

外部ファイルの使用例


<?XML version="1.0" encoding="Shift_JIS" ?>
<package>
<job id="GetTSQLFunction()">
<comment>
ADOCtrlのテスト
</comment>
<script language="VBScript" src=".\lib\ADOCtrl.vbs"/>
<script language="VBScript">
<![CDATA[
        Option Explicit

        Dim DBCtrl
        Set DBCtrl = getADOCtrl()

        IF Not DBCtrl.ConnectSqlServer("XXX-PC","TestDB","username","password") Then
                WScript.Quit -1
        End If

        Dim vRecs       ' 列,行
        If Not DBCtrl.ExcuteSQL("SELECT    specific_name,object_definition(object_id(specific_name)) FROM  information_schema.routines ORDER BY specific_name",vRecs) Then
                WScript.Quit -1
        End If
        Dim i
        Dim objData
        For i = LBound(vRecs,2) To UBound(vRecs,2)
                Call WScript.Echo("=======================================") 
                Call WScript.Echo(vRecs(0,i) )
                Call WScript.Echo( vRecs(1,i)) 
        Next
        Call DBCtrl.Close()
        Set DBCtrl = Nothing

]]>
</script>
</job>
</package>

以下のようにscriptタグのsrcを付与することで外部ファイルを使用できるようになります。

<script language="VBScript" src=".\lib\ADOCtrl.vbs"/>

VBAまたはVBSからCOM経由で使用できる.NETのライブラリの作成方法

このドキュメントではVBAやVBScriptからCOM経由で.NETの処理を実行する方法について記述する。

環境

・Visual Studio2008 Pro
・.NET Framework 2.0 を対象とする。
・32ビットのOffice2010

.NETを使用したクラスモジュールをCOMにする方法

1.Visual Studio 2008 を管理者権限で起動する。
 これはCOM登録時にレジストリを変更するため。

2.プロジェクトの追加で「.NET Framework 2.0」 と 「クラスライブラリ」を選択
5sban21z.png

3. ビルドにてプラットフォームのターゲットを「x86」または「x64」とする。
これは使用するExcelに合わせる。

1w04banj.png

4. ビルドにてCOM相互運用機能の登録にチェックをつける。
1w04banj.png

5. アプリケーションのアセンブリ情報で、「アセンブリをCOM参照可能にする」
1w04banj.png

6. 公開したいインターフェイスとその実態について以下のように記述する

using System.Runtime.InteropServices;
namespace NMeCabCom
{
    [ComVisible(true)]
    public interface INmcTagger
    {
        void Create();
        NmcNode[] Parse(string text);
    }

    [ClassInterface(ClassInterfaceType.None)]
    public class NmcTagger : INmcTagger
    {
        private MeCabTagger nmcab;
        public NmcTagger()
        {
        }

        public void Create()
        {
            MeCabParam p = new MeCabParam();
            p.DicDir = @"C:\dev\NMeCab\dic\ipadic";

            this.nmcab = MeCabTagger.Create(p);
        }

        public NmcNode[] Parse(string text)
        {
            List<NmcNode> result = new List<NmcNode>();
            if (this.nmcab == null)
            {
                return result.ToArray();
            }
            MeCabNode node = this.nmcab.ParseToNode(text);
            while (node != null)
            {
                result.Add(new NmcNode(node));
                node = node.Next;
            }
            return result.ToArray();
        }

    }
}

まず、公開するインターフェイスの直前に「ComVisible(true)」と記述する。

    [ComVisible(true)]
    public interface INmcTagger
    {
        void Create();
        NmcNode[] Parse(string text);
    }

そして、そのインターフェイスの実装部分はClassInterfaceType.Noneとする。

    [ClassInterface(ClassInterfaceType.None)]
    public class NmcTagger : INmcTagger

7. ビルドを行うことでCOMの登録まで行われる

インターフェイスの設計時に注意すべきこと。

・List<>などのジェネリック型はCOMとしては公開できない

・ClassInterfaceTypeにAutoDualを記述すると、インターフェイス不要にできるがすべきでない
http://msdn.microsoft.com/ja-jp/library/ms182205.aspx

・Uint、ULONG等のUnsignedの型をインターフェイスに使用してはいけない
COMとしては使用できても、VBAやVBSはUnsignedをサポートしていないので、ULONGなどのインターフェイスは使用できない。

・オブジェクトの配列は返えしてはいけない

以下のようなCOMのインターフェイスがあったとする。

    public interface INmcTagger
    {
        string GetModulePath();
        void Create(NmcParam p);
        NmcNode[] Parse(string text);
    }

このインターフェイスの場合、VBAやC#などの型を明示できるプログラミング言語では適切に処理できる

Dim ret As NmcNode()
ret = t.Parse("This is a pen.")

しかし、VBSの場合、型が明示できないため、Unknownとなり処理できなくなる。

Dim ret ' As NmcNode()
ret = t.Parse("This is a pen.")
WScript.Echo TypeName(ret) ' Unknownとなり、以降処理できない。

こういう場合は、配列を管理するインターフェイスを作成して、それを経由するようにする。

    public interface INmcNodeCollection
    {
        int Count { get; }
        NmcNode GetItem(int index);
    }

配列ではないオブジェクトを返した場合は、VBSでも処理ができる

set ret = t.Parse("This is a pen.")
For i = 0 To ret.Count- 1
  WScript.Echo TypeName(ret.GetItem(i))
  WScript.Echo ret.GetItem(i).Surface
Next

利用方法

開発環境以外の登録方法

.NET Frameworkのフォルダに存在するregasmを使用する。
作成したdllに対して /tlb と /codebase を付与してregasmを実行する。
regasmは各バージョンの.NETに存在する。

SET BIN=C:\Windows\Microsoft.NET\Framework\v2.0.50727

REM 登録
%BIN%\regasm  C:\dev\NMeCabCom\NMeCabCom\bin\Debug\NMeCabCom.dll /tlb /codebase

この際、以下のメッセージが出力される場合がある。

「RegAsm : warning RA0000 : 署名されていないアセンブリを /codebase を使用して登録すると、同じコンピュータにインストールされるその他のアプリケーションとの競合が生じる可能性があります。/codebase スイッチは署名されたアセンブリのみに使用できます。アセンブリに厳密な名前を付けて、再登録してください。」

これは、警告だけであり、COMの登録はされている。
この警告を消すには、プロジェクトの設定で「署名」→「アセンブリの署名」 厳密な名前のキーファイルを選択する必要がある。

1w04banj.png

依存するすべてのDLLが同様に厳密な名前を有しなければならないので気をつけること。

COMの削除方法

/tlb と /unregister を付与したregasmコマンドを実行後、ファイルを消すればよい。

regasm /tlb C:\dev\NMeCabCom\NMeCabCom\bin\Debug\NMeCabCom.dll  /unregister

ExcelVBAで利用する方法

参照設定してやれば、使用できるようになる
1w04banj.png

以下のようにインテリセンスが効く。
1w04banj.png

VBSからの利用方法

以下のようにCreateObjectを利用する。

set t = CreateObject("NMeCabCom.NmcTagger")

実際のサンプル

VBAやVBScriptで形態素解析を行う方法
http://qiita.com/mima_ita/items/bc2aeb060ee12d280d7b

VBAやVBScriptで形態素解析を行う方法

このドキュメントではVBAやVBScriptを使用して形態素解析を行う方法について説明します。

単語の分割だけでいい場合は下記を参考。

VBScriptで分かち書きを実行(MS標準機能のみで実装)
http://qiita.com/nezuq/items/2e4e0cc63316474b630d

NMeCab

NMeCabは形態素解析エンジンMeCabの解析処理部分を、.NETライブラリとして移植したものであり、下記からダウンロードできます。

http://sourceforge.jp/projects/nmecab/releases/p11745

今回は、NMecabをCOMでラッパーするNMecabComを作成し、そこを経由してVBA,VBScriptから形態素解析を行います。

b0232065_21104163.png

具体的な作成方法については下記を参考の事。
VBAまたはVBSからCOM経由で使用できる.NETのライブラリの作成方法

作成したNMeCabComは下記からダウンロードできます。
http://needtec.sakura.ne.jp/release/NMeCabCom.zip

環境

・Visual Studio2008 Pro
・.NET Framework 2.0 を対象とする。
・32ビットのOffice2010

使用方法

インストール方法

1.下記からファイルをダウンロードします
http://needtec.sakura.ne.jp/release/NMeCabCom.zip

2.NMeCabCom\binにあるsetup.batを管理者権限で実行してください。

もし、Excelの64ビットのプロセスで使用したい場合は、NMeCabCom.dllとLibNMeCab.dllを64ビットでビルドしなおしてください。

また、アンインストールを行い対場合はuninstall.batを管理者権限で実行してください。

Excel VBAからの使用方法

インストールが適切に終了していれば、参照設定にNMecabComが追加されるのでチェックを入れてください。

1w04banj.png

その後以下のような実装をします。


    Dim t As New NmcTagger
    Dim c As NmcNodeCollection
    Dim p As New NmcParam
    p.dicdir = "C:\back\NMeCabCom\bin\ipadic"
    Call t.Create(p)
    Set c = t.Parse("私の名前はLです")
    Dim i As Long
    For i = 0 To c.Count - 1
        Debug.Print c.GetItem(i).Surface & " " & c.GetItem(i).Feature
    Next

作成例

NmcTaggerに渡すNmcParamにはシステム辞書とユーザ辞書を指定できます。
ユーザー辞書の作成については、本家のMecabのホームページを参考に作成してください。

VBScriptからの使用方法

CreateObjectで遅延バインディングする場合は以下のようになります。


dim t
dim ret
dim i
dim p
dim node
set t = CreateObject("NMeCabCom.NmcTagger")
WScript.Echo TypeName(t)

set p = CreateObject("NMeCabCom.NmcParam")

p.dicdir = "./ipadic"
p.userdic = ""
t.Create(p)

set ret = t.Parse("This is a pen.")
WScript.Echo TypeName(ret)

For i = 0 To ret.Count- 1
  WScript.Echo TypeName(ret.GetItem(i))
  WScript.Echo ret.GetItem(i).Surface
Next

応用例

bin/NicoMsgAnalyze.xlsm はニコニコ動画のコメントを集計して解析するサンプルです。

ExcelVBA を使ったニコニコ動画のコメントの分析
http://www.nicovideo.jp/watch/sm22332042

Node.js + expressにおける多言語化の考察

目的

本稿はnode.js+expressにおいて多言語化をどのように行うべきかを考察する。

サンプル

http://needtec.sakura.ne.jp/release/node_globalization_sample.zip

このサンプルはログイン画面とユーザ登録画面について、多言語化対応できるように実装している。

基本的な考え方

ユーザに提示する、すべてのメッセージは1つのファイルに記述する。
たとえば、エラー時に表示するメッセージやボタンに表示するテキストがこれにあたる。

今回はroutes/message.jsonがメッセージ情報を格納するファイルとなる。

routes/message.json

{
  "message": {
    "error": {
      "NotfoundUser": "ユーザ名(%s)は存在しないかパスワードが間違っています.",
      "DatabaseError": "データベースの接続に失敗しました。<BR>%s",
      "ConstraintError": "制約違反が発生しました。%s",
      "UniqueConstraintError": "項目(%s)には同じ値を重複して登録できません。",
      "FieldError": "入力フィールド:%s にてエラーが発生しました。",
      "DbLockError": "データベースファイルがロックされています。システム管理者に問い合わせてください",
      "NotDirectoryError": "指定のパス(%s)はディレクトリではありません。",
      "RequirementFieldError": "指定の項目(%s)は必須項目です。"
    },"ValidationError": {
      "is": "",
      "not": "",
      "isEmail": "Emailの書式のみ許可されています。",
      "isUrl": "URLの書式のみ許可されています。",
      "isIP": "IPv4またはIPv6の書式のみ許可されています。",
      "isIPv4": "IPv4の書式のみ許可されています。",
      "isIPv6": "IPv6の書式のみ許可されています。",
      "isAlpha": "英字のみ許可されています。",
      "isAlphanumeric": "英数字のみ許可されています。",
      "isNumeric": "数字のみ許可されています。",
      "isInt": "整数のみ許可されています。",
      "isFloat": "浮動小数点のみ許可されています",
      "isDecimal": "Decimalのみ許可されています",
      "isLowercase": "小文字のみ許可されています",
      "isUppercase": "大文字のみ許可されています",
      "notNull": "NULLは許可されていません。",
      "isNull": "NULLのみ許可されています。",
      "notEmpty": "空文字は許可されていません。",
      "equals": "%sのみ許可されています",
      "contains": "",
      "notIn": "",
      "isIn": "",
      "notContains": "",
      "len": "文字数は(%d~%d)の範囲にしてください。",
      "isUUID": "",
      "isDate": "",
      "isAfter": "",
      "isBefore": "",
      "max": "",
      "min": "",
      "isArray": "",
      "isCreditCard": ""
    }
  },
  "view": {
    "login": {
      "title" : "ログイン",
      "linkToRegister": "新規登録",
      "user": "ユーザ名",
      "password": "パスワード",
      "doLogin": "ログイン"
    },
    "register": {
      "title" : "ユーザ登録",
      "user": "ユーザ名",
      "password": "パスワード",
      "passwordConfirm": "確認用パスワード",
      "doRegister": "新規登録",
      "MissmatchPassword": "入力したパスワードが一致しません",
      "Success": "ユーザの登録に成功しました。"
    },
    "project": {
      "title" : "プロジェクトの一覧",
      "projectName": "プロジェクト名",
      "path": "パス",
      "saveBtn": "保存",
      "confirmDelete": "プロジェクト:%sを削除しますか?"
    },
    "index": {
      "logout": "ログアウト",
      "projectSetting": "プロジェクト設定",
      "title": "Jasmine Test Runner",
      "projectName": "プロジェクト名",
      "path": "パス",
      "contextRun": "Jasmine実行"
    }
  },
  "model": {
    "User": {
      "username": "メールアドレス",
      "password": "パスワード"
    },
    "Project": {
      "name": "プロジェクト名"
    },
    "RootPath": {
      "path": "パス",
      "ProjectId": "プロジェクト"
    }
  }
}

多言語化対応を行う場合は、このmessage.jsonと同様のJSONファイルを対応言語分用意する。
そして、設定に応じて使用するjsonファイルを切り替える。

プログラムには、直接文字を記述せず必ずmessage.jsonから文字を取得するようにする。
このさい、util.formatを使用してすると、以下のように動的に文字を構築てきる。

var name = 'Jack'
var c = 10
util.format('%s は %d の リンゴを持っている', name, c)
//-> Jack は 10 のリンゴを持っている

この際、注意することして、翻訳の際は%sや%dの順番が同じになるようにしなければならない。

message.jsonに登録する際は文章単位で登録する。けっしてプログラムで連結してはいけない。

英語の場合:
str1 // "I have "
str2 // "pen"
strRet= str1 + str2;
// I have pen. ... OK

日本語の場合:
str1 = "私は持っている";
str2 = "ペン";
strRet = str1 + " " + str2;
// 私は持っているペン ... NG

言語によって文法は異なるので単語単位の翻訳ではなく、文章単位の翻訳を行うこと。

多言語化の例

実際の多言語化の例をソースコードと共に例示する。

サーバーの応答の多言語化

サーバーの応答に含む文字を多言語化する例を以下に示す。

以下の例では、エラーが発生した場合に「ユーザ名(%s)は存在しないかパスワードが間違っています.」と表示する。

この場合、直接、文字をプログラムに記述していない。common.getMessageにコードを指定し、エラーメッセージを作成している。

router/login.js

    var data = {};
    if (result) {
      data = {result: true};
      req.session.user = email;
      data = {
        result: true
      };
    } else {
      data = {
        result: false,
        error: common.getMessage('error',
                                 'NotfoundUser',
                                 [email])
      };
    }
    res.contentType('application/json');
    var json = JSON.stringify(data);
    res.send(json);

common.getMessageでおこなっていることはmessage.jsonから指定のキーのデータを取得して引数で指定された文字と併せて文字列として返している。

routes/common.js

var util = require('util');
var message = require('./message.json');
/**
 * メッセージの取得
 * @param {string} category カテゴリ名
 * @param {string} id ID
 * @param {Array} args 引数の配列
 */
function _getMessage(category, id, args) {
  var msg = message.message[category];
  if (msg) {
    if (msg[id]) {
      var param = [msg[id]];
      param = param.concat(args);
      return util.format.apply(this, param);
    }
  }
  return util.format('category:%s id:%s args:%s' ,category, id, args);
}

Viewにおけるメッセージの構築例

ボタンのタイトルやラベルの文字を多言語化するには、レンダリングするさいに、ビューで必要なメッセージをすべて渡せばよい

以下の例ではユーザの新規登録画面における例を示している。
以下のように、message.jsonから必要なメッセージを common.getViewMessageを用いてすべて取得して、テンプレートエンジンに渡している。

routes/login.js

exports.register = function(req, res){
  res.render('register',
             { message: common.getViewMessage('register') });
};

ここで使用しているテンプレートは次のようになる。

doctype html
html
  head
    title #{message.title}
    link(rel='stylesheet', href='/stylesheets/style.css')
    link(rel='stylesheet', href='/javascripts/lib/msgbox/msgBoxLight.css')
    script(type='text/javascript', src='/javascripts/lib/jquery-1.11.1.min.js')
    script(type='text/javascript', src='/javascripts/lib/msgbox/jquery.msgBox.js')
    script(type='text/javascript', src='/javascripts/src/ui_util.js')
    script(type='text/javascript', src='/javascripts/src/register.js')
  body
    h2 #{message.title}
    form(action='/login/doRegister' method='POST')#registerForm
      div #{message.user}.
        input(type='text', name='user', placeholder='#{message.user}')
      div #{message.password}.
        input(type='password' name='password', placeholder='#{message.password}')
      div #{message.passwordConfirm}.
        input(type='password' name='passwordConfirm', placeholder='#{message.passwordConfirm}')
      div
        button #{message.doRegister}
    include message

InputBoxの表題やボタン名などユーザに表示する文字は、直接記述していない。

以下のように、message.jsonのデータが格納されているオブジェクトを経由して表示している。

        button #{message.doRegister}

クライアントサイドのJavaScriptでメッセージを使用する

クライアントサイドのJavaScriptで文字を表示しようする場合はどうするべきか?

先のテンプレートの最期に下記のようにincludeを行っている。

    include message

これは別のmessage.jadeというテンプレートを埋め込むことを意味している。

div#message(style='display:none')
  - for(var key in message) {
    div(id='#{key}') #{message[key]}
  -}

このように記述することで、

というタグの中に、必要なメッセージを埋め込む。

これを、以下のようなコードで取得すればよい。

$('#message').find('#' + id).text();

もし、%sや%dなどを使用したい場合はutil.formatのコードで必要な部分をクライアントサイドのJavaに移植してやればよい。

public/javascripts/src/ui_util.js

var formatRegExp = /%[sdj%]/g;
function _format(f) {
  if (!_isString(f)) {
    var objects = [];
    for (var i = 0; i < arguments.length; i++) {
      objects.push(arguments[i]);
    }
    return objects.join(' ');
  }

  var i = 1;
  var args = arguments;
  var len = args.length;
  var str = String(f).replace(formatRegExp, function(x) {
    if (x === '%%') return '%';
    if (i >= len) return x;
    switch (x) {
      case '%s': return String(args[i++]);
      case '%d': return Number(args[i++]);
      case '%j':
        try {
          return JSON.stringify(args[i++]);
        } catch (_) {
          return '[Circular]';
        }
      default:
        return x;
    }
  });
  for (var x = args[i]; i < len; x = args[++i]) {
    if (isNull(x) || !isObject(x)) {
      str += ' ' + x;
    } else {
      str += ' ' + inspect(x);
    }
  }
  return str;
}

function _isObject(arg) {
  return typeof arg === 'object' && arg !== null;
}

function _isNull(arg) {
  return arg === null;
}

function _isString(arg) {
  return typeof arg === 'string';
}

function _isUndefined(arg) {
  return arg === void 0;
} 

別解

自前で実装せずにnode用の多言語のライブラリを使用する
https://github.com/mashpie/i18n-node

一般的な多言語対応における注意事項

多言語化で一般的に気を付けることは以下のとおりである。

長さ

 同じ意味を持つ言葉でも言語によって長さが異なる。そのため、描画を行う際には文字が長くなった時のレイアウトも考慮しなければならない。

改行

ラベルなどのコントロールに複数行の文字を表示する場合があります。 この場合、改行の方法は言語によってことなる。

日本語の場合、単語の途中で改行する事は許されている。

天の光はすべ
て敵ですか?

しかし英語の場合、単語の途中で改行することは許されていない。

NG

Are all the ligh
ts in the heavens our enemy?

OK

Are all the lights 
in the heavens our enemy?

ブラウザを使用するなら適切に描画してくれるが、もし自分で改行を行う場合は注意すること。

比較とソート

地域によってアルファベットの順番は異なり、ソート順と文字の比較に影響を与える。
たとえばデンマーク言語ではÆをアルファベットのZの後にくる独特な文字として扱う。
以下のような配列をソートしたとする。

[0]: Apple [1]: Æble [2]: Zebra

"en-US"の環境では下記のソート順にする必要がある

    [0]: Æble [1]: Apple [2]: Zebra

"da-DK"の環境では下記のソート順にする必要がある。

    [0]: Apple [1]: Zebra [2]: Æble

複数形

数字と組み合わせて表示を行う場合、複数を考える必要がある。
英語には 2 種類の複数形、単数と複数しかないが、スロベニア語には 4 つの複数形があるので単純なifではいけない。

以下のように()でごまかす場合も多々ある。

2 photo(s).

色とアイコン

色とアイコンも地域化の場合に注意を払う必要がある項目である。
文化によって色やアイコンの意味合いがことなることがある。

文字の方向

英語は左から右に読みますが、アラビア語は右から左に読みます。
右から左に読む言語は以下の通りになります。

Questions & Answers: Which languages are written right-to-left (RTL)?
http://www.i18nguy.com/temp/rtl.html

その他

・タイムゾーン
・日付の表記方法 yyyy-mm-dd か dd-mm-yyyy ...
・金額の表記方法
・数値の表記方法 1,053.3 か 1.050,3 か 1’050.3 ...

参考

国際対応アプリケーションの開発
http://msdn.microsoft.com/ja-jp/library/aa719955(VS.71).aspx

ウェブサイトのローカライズで気をつけたい6つのポイント
http://builder.japan.zdnet.com/sp/web-design-localize-2008/

Visual Studioのマクロを使用してDoxygenコメントを記述する

概要

Visual Studio のProfessionalEdition以上ではマクロを使用することができる。
そのマクロを使用することで、VisualStudioの操作を自動化することが可能である。
今回はVCのコードでDoxygenコメントを記述するマクロを作成する。

VisualStudio2008 Proffesionalでサンプルを記述しているが、新しいバージョンでも同等のことができる。

Vs2012から使えないの拡張機能を使う必要がある。

http://variantsoft.co.jp/blog/2014/01/14/visualstudio2013%E3%81%AE%E6%8B%A1%E5%BC%B5%E6%A9%9F%E8%83%BD%E3%82%92%E4%BD%9C%E3%82%8B/

(2019/12/15 追記)
復活のVisualStudioのマクロ
https://needtec.sakura.ne.jp/wod07672/?p=9152
いくつかの制限付きながら拡張機能としてVisualStudioのマクロが復活しているようです。

マクロの登録方法

1.[ツール]→[マクロ]→[マクロエクスプローラ]を選択する。

macro.png

2.マクロエクスプローラにて新規にモジュールを追加する。

macro.png

3.モジュール追加のダイアログで任意の名前でモジュールを追加する。

macro.png

4.マクロエクスプローラで追加したモジュールを選択して編集を行う

macro.png

5.MicrosoftVisual Studio Macrosが起動するので以後、そこでマクロを記述する。

macro.png

選択中の項目にDoxygenコメントを記述するコード


Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics

Public Module mdlVCSrcMacros
    ' カーソルで選択中の関数/Class/EnumにDoxgenで使用するコメントを付与します.
    ' 何も選択せずにファイルの先頭にある場合⇒ファイル用のヘッダコメント
    ' cppファイルで関数を選択して実行⇒関数用ヘッダ
    ' ※クラスのメンバはClassViewが認識していないとコメントを付与してくれない
    '   TODO:課題
    ' classを選択して実行⇒クラス用ヘッダ
    ' enumを選択して実行⇒enum用ヘッダ.
    ' 参考
    '  http://www.microsoft.com/japan/msdn/academic/Articles/fun/06/
    Sub CreateHeaderComment()
        Dim sel As TextSelection = DTE.ActiveDocument.Selection
        Dim strCom As String
        Dim myFCM As FileCodeModel = DTE.ActiveDocument.ProjectItem.FileCodeModel
        strCom = ""

        ' ファイルヘッダ.
        If sel.TopPoint.Line = 1 And sel.Text.Length = 0 Then
            sel.Text = "/**"
            sel.NewLine()
            sel.Text = " * @file" + vbTab + DTE.ActiveDocument.Name
            sel.NewLine()
            sel.Text = "* @brief" + vbTab + "Copyright(c) " + Year(Today()).ToString()
            sel.Text = vbTab + "Nikon Corporation - All rights reserved."
            sel.NewLine()
            sel.Text = "* @date" + vbTab + Format(Today(), "yyyy/MM/dd")
            sel.NewLine()
            sel.Text = "* @author" + vbTab + "作者名"
            sel.NewLine()
            sel.Text = "* @note" + vbTab + ""
            sel.NewLine()
            sel.Text = "*/"
            Exit Sub
        End If
        Dim objCodeElement As CodeElement ' コード情報取得用オブジェクト

        ' 関数ヘッダ
        objCodeElement = myFCM.CodeElementFromPoint(sel.ActivePoint, vsCMElement.vsCMElementFunction) ' ソースコード情報の取得
        If IsNothing(objCodeElement) = False Then
            If objCodeElement.Kind = vsCMElement.vsCMElementFunction Then
                ' 関数の場合.
                Dim codeFun As CodeFunction = objCodeElement
                strCom = "/**" + vbCrLf
                strCom = strCom + " * " + codeFun.Name + vbCrLf
                strCom = strCom + "* " + vbCrLf
                Dim j As Integer
                For j = 1 To codeFun.Parameters.Count
                    Dim codeParam As CodeParameter
                    codeParam = codeFun.Parameters.Item(j)
                    strCom = strCom + "* @param[in/out]" + vbTab + codeParam.Type.AsString + vbTab + codeParam.Name + vbCrLf
                Next j
                If codeFun.Parameters.Count > 1 Then
                    strCom = strCom + "* " + vbCrLf
                End If
                strCom = strCom + "* @return" + vbTab + codeFun.Type.AsString + vbCrLf
                strCom = strCom + "* @note" + vbCrLf
                strCom = strCom + "*/" + vbCrLf
                sel.MoveToPoint(objCodeElement.StartPoint)
            End If
        End If

        ' class 名
        objCodeElement = myFCM.CodeElementFromPoint(sel.ActivePoint, vsCMElement.vsCMElementClass) ' ソースコード情報の取得
        If IsNothing(objCodeElement) = False Then
            If objCodeElement.Kind = vsCMElement.vsCMElementClass Then
                strCom = "/**" + vbCrLf
                strCom = strCom + " * @class" + vbTab + objCodeElement.Name + vbCrLf
                strCom = strCom + "* @brief" + vbCrLf
                strCom = strCom + "*/" + vbCrLf
                sel.MoveToPoint(objCodeElement.StartPoint)
            End If

        End If

        ' enum 名
        objCodeElement = myFCM.CodeElementFromPoint(sel.ActivePoint, vsCMElement.vsCMElementEnum) ' ソースコード情報の取得
        If IsNothing(objCodeElement) = False Then
            If objCodeElement.Kind = vsCMElement.vsCMElementEnum Then
                strCom = "/**" + vbCrLf
                strCom = strCom + " * @enum" + vbTab + objCodeElement.Name + vbCrLf
                strCom = strCom + "* @brief" + vbCrLf
                strCom = strCom + "*/" + vbCrLf
                sel.MoveToPoint(objCodeElement.StartPoint)
            End If

        End If

        ' struct
        objCodeElement = myFCM.CodeElementFromPoint(sel.ActivePoint, vsCMElement.vsCMElementStruct) ' ソースコード情報の取得
        If IsNothing(objCodeElement) = False Then
            If objCodeElement.Kind = vsCMElement.vsCMElementStruct Then
                Dim codeStruct As CodeStruct = objCodeElement

                strCom = "/**" + vbCrLf
                strCom = strCom + " * @struct" + vbTab + objCodeElement.Name + vbCrLf
                strCom = strCom + "* @brief" + vbCrLf
                strCom = strCom + "*/" + vbCrLf
                sel.MoveToPoint(objCodeElement.StartPoint)
            End If
        End If

        If Not strCom.Length = 0 Then
            sel.StartOfLine(vsStartOfLineOptions.vsStartOfLineOptionsFirstColumn)
            sel.LineUp(True)
            sel.Text = strCom
        End If

    End Sub

End Module

DTEインターフェイスを経由することで、VisualStudioの操作がおこなえる。
http://msdn.microsoft.com/ja-jp/library/envdte.dte.aspx

現在選択中のコードの要素を列挙


        ''' ソース ファイル内のコード要素または構成体を表します
        Dim myFCM As FileCodeModel = DTE.ActiveDocument.ProjectItem.FileCodeModel
        Dim objCodeElement As CodeElement
        For Each objCodeElement In myFCM.CodeElements
            MsgBox(objCodeElement.FullName & ":" & objCodeElement.Kind)
        Next

CodeElement インターフェイス
http://msdn.microsoft.com/ja-jp/library/envdte.codeelement.aspx

kindを見ることで、Inculdeなのか、関数なのか、クラスなのか判断することができる。

カーソルの座標とKindからコードの要素を取得


Dim myFCM As FileCodeModel = DTE.ActiveDocument.ProjectItem.FileCodeModel
Dim objCodeElement As CodeElement ' コード情報取得用オブジェクト
objCodeElement = myFCM.CodeElementFromPoint(sel.ActivePoint, vsCMElement.vsCMElementFunction) ' ソースコード情報の取得

この関数はカーソルの位置とKindの種類をもとにしてコード要素を取得する。
条件があわなければnullとなる。

カーソルの位置を関数の先頭にあわせて文字を記述する


Dim sel As TextSelection = DTE.ActiveDocument.Selection

'コード要素の先頭に移動
sel.MoveToPoint(objCodeElement.StartPoint)

'一列目に移動
sel.StartOfLine(vsStartOfLineOptions.vsStartOfLineOptionsFirstColumn)

' 一行うえへ
sel.LineUp(True)

' 文字を記録
sel.Text = "XXXXXXXXXXXXXXX"

マクロの実行方法

1.ソースコードの任意の箇所を選択。

macro.png

2.マクロエクスプローラの「CreateHeaderComment()」をダブルクリックする。
3.マクロが実行されると以下のようにDoxygenコメントが付与される。

macro.png

ショートカットの登録

作成したマクロはショートカットキーに割り当てることができる。

1. [ツール] メニューの [オプション] をクリックして、[オプション] ダイアログ ボックスを表示する。
2.[環境] フォルダーの [キーボード] をクリックする。
3.[以下の文字列を含むコマンドを表示] ボックスにマクロ名を入力して絞りこみ、選択する。

macro.png

4.[ショートカット キー] ボックスをクリックし、特定のキーの組み合わせ押し「割り当て」ボタンをクリックする

以降設定したキーの組み合わせで、指定のマクロが動作する。

方法: マクロを実行する
http://msdn.microsoft.com/ja-jp/library/vstudio/a0003t62%28v=vs.100%29.aspx

応用

この記事でVisualStudioの自動化が行えることを説明した。

VisualStudioのマクロを使う例としては以下のようなことが考えられる。
・あるルールに従って、自動でコードを作成する。
・プロジェクトの設定を自動で行う
・プロジェクトエクスプローラーからファイルを選択してバージョン管理ソフトにコミットしたり、元に戻したりする。

TestLink1.9.10へのバージョンアップ時の調査

概要

このドキュメントは、TestLink1.7系からTestLink1.9にバージョンアップした際の違いについて記述する。

一般的なTestLinkの説明については下記を参照

脱Excel! TestLinkでアジャイルにテストをする (1/6)
http://www.atmarkit.co.jp/ait/articles/0910/23/news110.html

WindowのXAMPPにインストールした場合の問題

Window7のXAMPP1.8.3にTestLink1.9.10を導入した場合、下記のエラーが発生して動作しない。

Fatal error: Uncaught exception 'SmartyCompilerException' with message 'Syntax Error in template "C:\xampp183\htdocs\testlink\gui\templates\login.tpl" on line 12 "&lt;script language=&quot;JavaScript&quot; src=&quot;{$basehref}gui/niftycube/niftycube.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;" unknown tag "private_print_expression"' in C:\xampp183\htdocs\testlink\third_party\smarty3\libs\sysplugins\smarty_internal_templatecompilerbase.php:665 Stack trace: #0 C:\xampp183\htdocs\testlink\third_party\smarty3\libs\sysplugins\smarty_internal_templatecompilerbase.php(451): Smarty_Internal_TemplateCompilerBase->trigger_template_error('unknown tag "pr...', 12) #1 C:\xampp183\htdocs\testlink\third_party\smarty3\libs\sysplugins\smarty_internal_templateparser.php(2353): Smarty_Internal_TemplateCompilerBase->compileTag('private_print_e...', Array, Array) #2 C:\xampp183\htdocs\testlink\third_party\smarty3\libs\sysplugins\smarty_internal_templatepa in C:\xampp183\htdocs\testlink\third_party\smarty3\libs\sysplugins\smarty_internal_templatecompilerbase.php on line 665

この問題に関しては、下記のフォルダを最新のSmarty3に置き換えれば回避できる。

testlink\third_party\smarty3

http://www.smarty.net/download
Smarty 3.1.18(安定板)をダウンロードする。

なお、Smarty3のエラーはWindowsまたはXAMMPとの組み合わせの問題だと思われる。
1.9.9でDebianを対象に環境を構築した時はこの問題は発生しなかった。

また、XAMMPの場合PHPのタイムゾーンが異なっているので適切に修正すること。
php.ini ファイルの下記修正

デフォルトの設定

date.timezone = Europe/Berlin

東京の場合

date.timezone = Asia/Tokyo

1.7⇒1.9の間に追加された機能

・要件仕様の作成が可能になっている。要件とテスト仕様書の関連付がおこなえる。

・Platform Management よりプラットフォームを追加できる。これにより、同じテストケースを異なるプラットフォームでテストすることができる。
今回のテスト計画ではWindowsだけテストする。次のテスト計画ではすべてのOSをテストするといった使い方ができる。

・TracやRedmineとの連携は「Issue Tracker Management」で行うようになっている。各テストプロジェクトごとに設定できる。
TracやRedmineのサーバが動作していないときにテストの実行を行おうとすると固まるので、注意が必要。エラーメッセージも表示されない。

・CSVやExcelからのインポートができない。XMLからのみである。かってCSVからのインポートをサポートしたTestLinkCnvMacroはすでに存在しない。

・XML-RPCならびにRESTAPIで外部アプリから操作が可能である。

・上記にともない各テストケースに実行タイプが追加され、「手動」または「自動」の選択を行える。

Excelからのインポートを考える

http://needtec.sakura.ne.jp/release/testlink.xlsm

testlink.png

このExcelは入力した項目をTestLink用のXMLに変換してファイルとして出力する。
ステップと期待値はALT+ENTERでセル内で改行すると、それを1ステップとしてかぞえる。
中項目、小項目は省略でき、その場合は大項目の下にテストケースが作成される。
XMLへの変換はVBAのMSXMLで行っている。

Testlinkを利用した自動テストについて考える

jUnitやnUnitといったテストコードを記述し、かつ、その結果が適切に集計、出力されるものはTestlinkで管理するべきではないと考える。
これは、テストケースの記述がテストコードとTestlinkの両方に書かねばならなくなり、二重管理になるからだ。
また、xUnitで行うテスト項目をTestLinkでテストケースを記述するのは粒度の問題で適切ではないと思われる。

TestLinkを利用した自動テストは結合フェーズ以降で行うとよい。
これならば、テスト結果の集計処理などをTestLinkにまかせることができるので意味がでてくる。
具体的には以下のようなテストが考えられる
 ・画面をSeleniumやUWSCで動かしてその結果をTestLinkに登録する。
 ・バッチファイルからテスト対象プロセスを起動して、その結果作成されたファイルの是非をTestLinkに登録する。

REST APIの使用方法

ここではRESTAPIを使用してテストの実行結果を記録する方法を紹介する。
結論からいうとXML-RPCを使用したほうがいい。

1.APIキーを作成し取得する。

testlink.png

2.Pythonで以下のようなコードを作成する。
このコードはテストの実行結果を記録するものである。

# -*- coding:utf-8 -*-
import urllib2
import json

# 個人設定画面に表示されているAPIキー
api_key = 'e3c82f414672b5c23d6688ea03ba8b36'

# テストの実行
data = {
  # testlink.executions に以下のデータを記録する
  "testPlanID": 221, # データベースに登録されているID 
  "buildID": 1,      # データベースに登録されているID
  "platformID": 0,   # データベースに登録されているID
  "testCaseExternalID": "test2-4", # テストケースを編集する際に表示されるID
  "notes": "備考の表示",  # 備考
  "statusCode": "f",  # p:成功 f:失敗 b:ブロック
  "executionType": 2  # 1:手動 2:自動

}
url = 'http://localhost/testlink/lib/api/rest/v1/executions'
req = urllib2.Request(url, json.dumps(data))
req.add_header('Content-Type', 'application/json')
req.add_header('Php-Auth-User',  api_key) #PHP_AUTH_USERとしてはいけない。
req.get_method = lambda: 'POST'
response = urllib2.urlopen(req)
ret = response.read()
print 'Response:', ret

このRESTAPIには、いくつか注意点がある。
・add_headerで追加する際に「PHPAUTHUSER」としてはいけない。
クライアントのリクエストに「
」が混ざると正常にヘッダが認識されない。
なお、「-」はサーバー側で確認すると「
」に変換されている。

・testlink.executionsテーブルに記録する場合、データベースで使用しているIDが必要になる。
このIDは画面からも取得できないし、他のRESTAPIでも取得できない。
データベースを開いて確認するしかない。

・ロジックを見る限り、テスト計画の取得などの機能が実装されていない。

・ドキュメントはなさそうなのでtlRestApi.class.phpを自分で解析する必要がある。

・executionsのパラメータは下記の通り

プロパティ 説明
testPlanID テスト計画のデータベースに登録されている内部のID
buildID リリース/ビルドのデータベースに登録されているID
platformID プラットフォームのデータベースに登録されているID
testCaseExternalID テストケースの画面表示されるID
notes テスト結果の備考
statusCode テスト結果
p:成功
f:失敗
b:ブロック
executionType 実行タイプ
1:手動 2:自動

ここまでRESTAPIでTestLInkを操作した感想だが、「DBを直接開いて内部のIDを調べる」、「操作の仕様はソースコード。そして全部の機能はない」とかいう状況だと、他人に作業をふれないので、今しばらく避けたほうがいい。

参考

TestLink の REST API を触ってみた
http://qiita.com/bamchoh/items/430e07b084ffbd1c0b36

Jadeで処理を共通化する方法

Node.jsのexpressで使用するテンプレートエンジンである、Jadeで処理を共通化する方法を説明する。

http://jade-lang.com/reference/

Includeによる共通化

includeを用いることで外部ファイルを使用できる
複数のファイルで共通する処理は別ファイルにまとめて、includeすればよい。
与えられた変数はIncludeしたファイルでも使用できる。

div#message(style='display:none')
  - for(var key in message) {
    div(id='#{key}') #{message[key]}
  -}
// HTML
doctype html
html
  head
    title #{message.title}
    link(rel='stylesheet', href='/stylesheets/style.css')
    link(rel='stylesheet', href='/stylesheets/ui-lightness/jquery-ui-1.10.4.min.css')
    link(rel='stylesheet', href='/javascripts/lib/msgbox/msgBoxLight.css')
    script(type='text/javascript', src='/javascripts/lib/jquery-1.11.1.min.js')
    script(type='text/javascript', src='/javascripts/lib/jquery-ui-1.10.4.min.js')
    script(type='text/javascript', src='/javascripts/lib/msgbox/jquery.msgBox.js')
    script(type='text/javascript', src='/javascripts/src/ui_util.js')
    script(type='text/javascript', src='/javascripts/src/project.js')
  body
    h2 #{message.title}
    include message

mixinによる共通化

同じファイル内で特定のブロックを繰り返したい場合は、mixinを使用するといい。
mixinを使用する際に、引数を与えることができるので、それにより、各繰り返し要素の内容を変更できる。
以下の例では追加用のダイアログと変更用のダイアログのHTMLを共通的に作成している。

// エディット用のダイアログ
// @param id DialogのID
mixin editDialog(dialogId, saveBtnId)
  div(id=dialogId, class='dialog', style='display:none')
    div #{message.projectName}
      input(type='text', name='projectName')#projectName
    div #{message.path}
      input(type='text', name='path')#inputPath
      button#addPath +
      button#deletePath -
    br
    select(multiple style="width:100%;")#selectPath
    br
    button(id=saveBtnId) #{message.saveBtn}

// HTML
doctype html
html
  head
    title #{message.title}
    link(rel='stylesheet', href='/stylesheets/style.css')
    link(rel='stylesheet', href='/stylesheets/ui-lightness/jquery-ui-1.10.4.min.css')
    link(rel='stylesheet', href='/javascripts/lib/msgbox/msgBoxLight.css')
    script(type='text/javascript', src='/javascripts/lib/jquery-1.11.1.min.js')
    script(type='text/javascript', src='/javascripts/lib/jquery-ui-1.10.4.min.js')
    script(type='text/javascript', src='/javascripts/lib/msgbox/jquery.msgBox.js')
    script(type='text/javascript', src='/javascripts/src/ui_util.js')
    script(type='text/javascript', src='/javascripts/src/project.js')
  body
    h2 #{message.title}
    +editDialog('dlgAddProject', 'btnAddProject')
    +editDialog('dlgEditProject', 'btnUpdateProject')

Pythonで色々なデータベースを操作する

Pythonで主だったデータベースを操作する方法を記述する。
Pythonは2.xと3.x両方でWindows環境で実行している。

また、テストデータは下記のページのT01Prefecture.zipを解凍してテーブルとデータを使うものとする。

PHPプログラミング初心者入門講座
http://php5.seesaa.net/article/61269550.html

MySQL

環境

Python2.7 または3.3
MySQL 5.6.15

データベースの設定

テーブル

CREATE TABLE t01prefecture (
  PREF_CD int(3) NOT NULL DEFAULT '0',
  PREF_NAME varchar(10) DEFAULT NULL,
  PRIMARY KEY (PREF_CD)
) ENGINE=InnoDB D

単一のレコードセットを返すストアド

DELIMITER $$
CREATE DEFINER=username@% PROCEDURE test_sp(IN fromNo INT,
                                                  IN toNo INT)
BEGIN
    select * from t01prefecture WHERE PREF_CD BETWEEN fromNo AND toNo ;
END$$
DELIMITER ;

複数のレコードセットを返すストアド

DELIMITER $$
CREATE DEFINER=username@% PROCEDURE test_sp2(IN cd1 INT,IN cd2 INT)
BEGIN
  select * from t01prefecture WHERE PREF_CD = cd1;
  select * from t01prefecture WHERE PREF_CD = cd2;

END$$
DELIMITER ;

Functionの例

DELIMITER $$
CREATE DEFINER=username@% FUNCTION test_fn(cd INT) RETURNS varchar(10) CHARSET utf8
BEGIN
    DECLARE ret VARCHAR(10);
    SELECT PREF_NAME INTO ret  FROM t01prefecture WHERE PREF_CD = cd;

RETURN ret;
END$$
DELIMITER ;

Pythonのコード

mysql-connector-python を使用する
http://dev.mysql.com/downloads/connector/python/

# -*- coding: cp932 -*-
# mysqlの操作サンプル
# easy_install mysql-connector-python
import mysql.connector

try:
    cnn = mysql.connector.connect(host='localhost',
                                  port=3306,
                                  db='Sample001',
                                  user='root',
                                  passwd='root',
                                  charset="cp932")
    cur = cnn.cursor()

    #試験データの整理
    pref_cd = 100
    cur.execute("""DELETE FROM t01prefecture WHERE PREF_CD >= %s""" , (pref_cd,))
    cnn.commit()

    print("単純なSELECT文==========================")
    from_id = 45
    to_id = 999

    # 以下は環境の文字コードにあわせること!
    cur.execute("""SELECT PREF_CD,PREF_NAME FROM t01prefecture
                WHERE PREF_CD BETWEEN %s AND %s""" , (from_id, to_id, ))
    rows = cur.fetchall()
    for row in rows:
        print("%d %s" % (row[0], row[1]))

    print("コミットの試験==========================")
    pref_cd = 100
    pref_name = "モテモテ王国"
    cur.execute("""INSERT INTO t01prefecture(PREF_CD, PREF_NAME)
                VALUES (%s, %s)""" , (pref_cd, pref_name))

    pref_cd = 101
    pref_name = "野望の国"
    cur.execute("""INSERT INTO t01prefecture(PREF_CD,PREF_NAME)
                VALUES (%s, %s)""" , (pref_cd, pref_name,))
    cnn.commit()

    print("ロールバックの試験==========================")
    pref_cd = 102
    pref_name = "ロールバック"
    cur.execute("""INSERT INTO t01prefecture(PREF_CD,PREF_NAME)
                VALUES (%s, %s)""" , (pref_cd, pref_name,))
    cnn.rollback()

    print("ストアドプロシージャの試験==========================")
    cur.callproc("test_sp", (from_id, to_id))
    for rs in cur.stored_results():
        print("レコードセット...")
        rows = rs.fetchall()
        for row in rows:
            print ("%d %s" % (row[0], row[1]))

    print("ストアドプロシージャの試験(複数)==================")
    cur.callproc("test_sp2", (1, 100))
    for rs in cur.stored_results():
        print("レコードセット...")
        rows = rs.fetchall()
        for row in rows:
            print ("%d %s" % (row[0], row[1]))

    print("ファンクションの試験==========================")
    pref_cd = 100
    cur.execute("""SELECT test_fn(%s)""" , (pref_cd,))
    rows = cur.fetchall()
    for row in rows:
        print("code:%d name:%s" % (pref_cd, row[0]))

    cur.close()
    cnn.close()
except (mysql.connector.errors.ProgrammingError) as e:
    print (e)

使用感

・MYSQLのストアドはSQLSERVERに近い感じ。

・ストアドを実行したあとは、 cur.stored_resultsにレコードセットが入っている。。

・MySQL-pythonってライブラリもあるが3.x系で動作しない。使い方はだいたい同じ。
http://sourceforge.net/projects/mysql-python/

SQLSERVER

環境

Python2.7 or 3.3
SQLSERVER EXPRESS 2012

データベースの設定

SQL SERVER接続を許可する。

無題.png

テーブル

CREATE TABLE [dbo].[T01Prefecture](
    [PREF_CD] [int] NOT NULL,
    [PREF_NAME] [varchar](10) NULL,
PRIMARY KEY CLUSTERED
(
    [PREF_CD] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

単一のレコードセットをかえすストアド

CREATE PROCEDURE [dbo].[test_sp]
    @from INT,
    @to INT
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    -- Insert statements for procedure here
    SELECT * FROM T01Prefecture WHERE PREF_CD BETWEEN @from AND @to
END

複数のレコードセットをかえすストアド


CREATE PROCEDURE [dbo].[test_sp2]
    @cd1 INT,
    @cd2 INT
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    -- Insert statements for procedure here
    SELECT * FROM T01Prefecture WHERE PREF_CD = @cd1
    SELECT * FROM T01Prefecture WHERE PREF_CD = @cd2
END

Pythonのコード 

SQLSERVERを直接操作

pymssql
http://pymssql.org/en/latest/

動作が不安定
Python2.7(32bit) では一応動作する。
Python3.3(64bit) ではインストールすらできない。

# -*- coding: cp932 -*-
# mssqlの操作サンプル
# pymssqlはVARCHARのエンコードが上手くいかないのでやめとく。
# easy_install pymssql

import pymssql

cnn = pymssql.connect(host="127.0.0.1\SQLEXPRESS", user="sa", password="xxxx", database="Sample001")
cur = cnn.cursor()

# 試験データの整理
pref_cd = 100
cur.execute("""DELETE FROM t01prefecture WHERE PREF_CD >= %d"""
            % (pref_cd,))
cnn.commit()

print("単純なSELECT文==========================")
print("VARCHARのエンコードがおかしいことを確認")
from_id = 45
to_id = 999
cur.execute("""SELECT PREF_CD, PREF_NAME FROM T01Prefecture
            WHERE PREF_CD BETWEEN %d AND %d""" , (from_id, to_id))
rows = cur.fetchall()
for row in rows:
    print(row)
    # print("%d %s" % (row[0], row[1])) #Errorになる
    # VARCHARを取り扱った場合の文字コードの解析が不正
    # CP932の文字コードをUNICODEとして扱おうとしている。

print("VARCHARのフィールドは扱えないのでNVARCHARに変換する")
# VARCHARのフィールドは扱えないのでNVARCHARに変換して返す
cur.execute("""SELECT PREF_CD,CAST(PREF_NAME  AS NVARCHAR) FROM T01Prefecture
            WHERE PREF_CD BETWEEN %d AND %d""" , (from_id, to_id))
rows = cur.fetchall()
for row in rows:
    print(row)
    print("%d %s" % (row[0], row[1]))

print("コミットの試験==========================")
pref_cd = 100
pref_name = "モテモテ国"
cur.execute("""INSERT INTO t01prefecture(PREF_CD, PREF_NAME)
            VALUES (%d, %s)""" , (pref_cd, pref_name))

pref_cd = 101
pref_name = "野望の国"
cur.execute("""INSERT INTO t01prefecture(PREF_CD,PREF_NAME)
            VALUES (%d, %s)""" , (pref_cd, pref_name,))
cnn.commit()

print("ロールバックの試験==========================")
pref_cd = 102
pref_name = "ロール"
cur.execute("""INSERT INTO t01prefecture(PREF_CD,PREF_NAME)
            VALUES (%d, %s)""" , (pref_cd, pref_name,))
cnn.rollback()

cur.execute("""SELECT PREF_CD, CAST(PREF_NAME AS NVARCHAR) FROM t01prefecture
            WHERE PREF_CD BETWEEN %d AND %d""" , (from_id, to_id, ))
rows = cur.fetchall()
for row in rows:
    print("%d %s" % (row[0], row[1]))

print("ストアドプロシージャの試験==========================")
cur.execute("""exec test_sp %d, %d """ , (from_id, to_id, ))
rows = cur.fetchall()
for row in rows:
    #エンコードが不正
    #print("%d %s" % (row[0], row[1]))
    print(row)

print("ストアドプロシージャの試験 複数==========================")
cur.execute("""exec test_sp2 %d, %d """ , (1, 10, ))
for row in rows:
    while True:
        print ("レコードセット...")
        rows = cur.fetchall()
        for row in rows:
            #print("%d %s" % (row[0], row[1]))
            print(row)
        if not cur.nextset():
            break

cur.close()
cnn.close()

ODBC経由

pyodbcの使用:
https://code.google.com/p/pyodbc/

# -*- coding: cp932 -*-
# mssqlの操作サンプル
# easy_install pyodbc

import pyodbc

try:
    cnn = pyodbc.connect("DRIVER={SQL Server};SERVER=127.0.0.1\SQLEXPRESS;" +
                         "UID=sa;PWD=XXXX;DATABASE=Sample001")
    cur = cnn.cursor()
    #試験データの整理
    pref_cd = 100
    cur.execute("""DELETE FROM t01prefecture WHERE PREF_CD >= ?""", pref_cd)
    cnn.commit()

    print("単純なSELECT文==========================")
    from_id = 45
    to_id = 999
    # 以下は環境の文字コードにあわせること!
    cur.execute("""SELECT PREF_CD,PREF_NAME FROM t01prefecture
                WHERE PREF_CD BETWEEN ? AND ?""" , from_id, to_id)
    rows = cur.fetchall()
    for row in rows:
        print("%d %s" % (row[0], row[1]))

    print("コミットの試験==========================")
    pref_cd = 100
    pref_name = "モテモテ国"
    cur.execute("""INSERT INTO t01prefecture(PREF_CD, PREF_NAME)
                VALUES (?, ?)""" , pref_cd, pref_name)

    pref_cd = 101
    pref_name = "野望の国"
    cur.execute("""INSERT INTO t01prefecture(PREF_CD,PREF_NAME)
                VALUES (?, ?)""" , pref_cd, pref_name)
    cnn.commit()

    print("ロールバックの試験==========================")
    pref_cd = 102
    pref_name = "ロール"
    cur.execute("""INSERT INTO t01prefecture(PREF_CD,PREF_NAME)
                VALUES (?, ?)""" , pref_cd, pref_name)
    cnn.rollback()

    cur.execute("""SELECT PREF_CD,PREF_NAME FROM t01prefecture
                WHERE PREF_CD BETWEEN ? AND ?""" , from_id, to_id)
    rows = cur.fetchall()
    for row in rows:
        print("%d %s" % (row[0], row[1]))

    print("ストアドプロシージャの試験==========================")
    cur.execute("""exec test_sp ?, ? """ , from_id, to_id)
    rows = cur.fetchall()
    for row in rows:
        print("%d %s" % (row[0], row[1]))

    print("ストアドプロシージャの試験 複数 ====================")
    cur.execute("""exec test_sp2 ?, ? """ , 1, 10)
    while True:
        print ("レコードセット...")
        rows = cur.fetchall()
        for row in rows:
            print("%d %s" % (row[0], row[1]))
        if not cur.nextset():
            break

    cur.close()
    cnn.close()

except (pyodbc.Error) as e:
    print (e)
    print (e.args[1])

pypyodbcの使用例:
https://code.google.com/p/pypyodbc/

# -*- coding: cp932 -*-
# mssqlの操作サンプル
# easy_install pypyodbc

import pypyodbc

try:
    cnn = pypyodbc.connect("DRIVER={SQL Server};SERVER=127.0.0.1\SQLEXPRESS;UID=sa;PWD=xxxxx;DATABASE=Sample001")
    cur = cnn.cursor()

    #試験データの整理
    pref_cd = 100
    cur.execute("""DELETE FROM t01prefecture WHERE PREF_CD >= ?"""
                , (pref_cd,))
    cnn.commit()

    print("単純なSELECT文==========================")
    from_id = 45
    to_id = 999
    # 以下は環境の文字コードにあわせること!
    cur.execute("""SELECT PREF_CD,PREF_NAME FROM t01prefecture
                WHERE PREF_CD BETWEEN ? AND ?""" , (from_id, to_id, ))
    rows = cur.fetchall()
    for row in rows:
        print("%d %s" % (row[0], row[1]))

    print("コミットの試験==========================")
    pref_cd = 100
    pref_name = "モテモテ国"
    cur.execute("""INSERT INTO t01prefecture(PREF_CD, PREF_NAME)
                VALUES (?, ?)""" , (pref_cd, pref_name))

    pref_cd = 101
    pref_name = "野望の国"
    cur.execute("""INSERT INTO t01prefecture(PREF_CD,PREF_NAME)
                VALUES (?, ?)""" , (pref_cd, pref_name,))
    cnn.commit()

    print("ロールバックの試験==========================")
    pref_cd = 102
    pref_name = "ロール"
    cur.execute("""INSERT INTO t01prefecture(PREF_CD,PREF_NAME)
                VALUES (?, ?)""" , (pref_cd, pref_name,))
    cnn.rollback()

    cur.execute("""SELECT PREF_CD,PREF_NAME FROM t01prefecture
                WHERE PREF_CD BETWEEN ? AND ?""" , (from_id, to_id, ))
    rows = cur.fetchall()
    for row in rows:
        print("%d %s" % (row[0], row[1]))

    print("ストアドプロシージャの試験==========================")
    cur.execute("""exec test_sp ?, ? """ , (from_id, to_id, ))
    rows = cur.fetchall()
    for row in rows:
        print("%d %s" % (row[0], row[1]))

    print("ストアドプロシージャの試験 複数 ==========================")
    cur.execute("""exec test_sp2 ?, ? """ , (1, 10, ))
    while True:
        print ("レコードセット...")
        rows = cur.fetchall()
        for row in rows:
            print("%d %s" % (row[0], row[1]))
        if not cur.nextset():
            break

    cur.close()
    cnn.close()
except (pypyodbc.DatabaseError) as e:
    print (e.args[1])

使用感

pymssqlはODBCを経由せずに使用できるが、動作が極めて不安定。
VARCHARの挙動があやしくて、NVARCHARにキャストしないと使用できない。
また、64bitのPython3.3ではインストールすらできない。

ODBC経由のライブラリはいずれも動作した。
Windows以外で確認はしていないが、サポートはしているとのこと。

ORACLE

環境

Python 2.7 または 3.3
Oracle11
1.Oracleクライアントをインストールする。この際、開発者用の環境をつくる。
 
2.以下からダウンロードしてインストールするか、easy_installする。
cx_Oracle
http://cx-oracle.sourceforge.net/

Pythonのコード

# -*- coding: cp932 -*-
# cx_Oracleを用いたPythonでのORACLE操作
# 1.Oracleクライアントをインストールする。
#  この際、開発者用の環境をつくる。
#  (おそらく、OCIが必要?)
#  
# 2.以下からダウンロードしてインストールするか、easy_installする。
#  cx_Oracle
#  http://cx-oracle.sourceforge.net/
import cx_Oracle
import os
os.environ["NLS_LANG"] = "JAPANESE_JAPAN.JA16SJISTILDE"
try:
    tns = cx_Oracle.makedsn("localhost", 1521, "Sample")
    conn = cx_Oracle.connect("user", "pass", tns)
    cur = conn.cursor()
    from_cd = 45
    to_cd = 200
    cur.execute("""SELECT * FROM T01PREFECTURE
                WHERE PREF_CD BETWEEN :arg1 AND :arg2""",
                arg1=from_cd,
                arg2=to_cd)
    rows = cur.fetchall()
    for r in rows:
        print("%d : %s" % (r[0], r[1]))

    print ("===================")
    print ("100を消す")
    cur.execute("""DELETE FROM T01PREFECTURE WHERE PREF_CD=:arg1""",
                arg1=100)

    cur.execute("""SELECT * FROM T01PREFECTURE
                WHERE PREF_CD BETWEEN :arg1 AND :arg2""",
                arg1=from_cd,
                arg2=to_cd)
    rows = cur.fetchall()
    for r in rows:
        print("%d : %s" % (r[0], r[1]))

    print ("------------------")
    print ("100 を追加")
    cur.execute("""INSERT INTO T01PREFECTURE
                VALUES (:arg1, :arg2)""",
                arg1=100,
                arg2="あたた")
    conn.commit()

    cur.execute("""SELECT * FROM T01PREFECTURE
                WHERE PREF_CD BETWEEN :arg1 AND :arg2""",
                arg1=from_cd,
                arg2=to_cd)
    rows = cur.fetchall()
    for r in rows:
        print("%d : %s" % (r[0], r[1]))

    print ("===================")
    print ("101追加")
    cur.execute("""INSERT INTO T01PREFECTURE
                VALUES (:arg1, :arg2)""",
                arg1=101,
                arg2="北斗")

    cur.execute("""SELECT * FROM T01PREFECTURE
                WHERE PREF_CD BETWEEN :arg1 AND :arg2""",
                arg1=from_cd,
                arg2=to_cd)
    rows = cur.fetchall()
    for r in rows:
        print("%d : %s" % (r[0], r[1]))

    print ("------------------")
    print ("ロールバック")
    conn.rollback()
    cur.execute("""SELECT * FROM T01PREFECTURE
                WHERE PREF_CD BETWEEN :arg1 AND :arg2""",
                arg1=from_cd,
                arg2=to_cd)
    rows = cur.fetchall()
    for r in rows:
        print("%d : %s" % (r[0], r[1]))

except (cx_Oracle.DatabaseError) as ex:
    error, = ex.args
    print (error.message)

使用感

OCI経由で操作しているようなので、ORACLEクライアントをインストールせねばならない。
接続方法に癖がある。あと文字コードの指定は環境変数でおこなう。

ORACLEのPL/SQLはSQLSERVERと違って結果セット返さない。
(配列を返せるが、ここでは面倒なので実験していない)

明示的にCOMMITしないと変更は破棄される。

Postgresql

環境

Python 2.x / 3.x
PostgresSQL 9.3

ライブラリとしてpsycopg2をつかう
Windowsの場合は以下のページからダウンロードしてExeを実行
http://www.stickpeople.com/projects/python/win-psycopg/

データベースの設定

テーブル定義

CREATE TABLE t01prefecture
(
  pref_cd integer NOT NULL,
  pref_name character varying(10),
  CONSTRAINT t01prefecture_pkey PRIMARY KEY (pref_cd)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE t01prefecture
  OWNER TO postgres;

PostgresSQLのユーザ定義関数

CREATE OR REPLACE FUNCTION test_sp(IN from_cd integer, IN to_cd integer)
  RETURNS TABLE(code integer, name varchar) AS
$$
DECLARE
BEGIN
    RETURN QUERY SELECT PREF_CD,PREF_NAME FROM t01Prefecture
            WHERE PREF_CD BETWEEN from_cd AND to_cd;
END;
$$ LANGUAGE plpgsql;

Pythonのコード

# -*- coding: cp932 -*-
# Winddows の場合は以下からダウンロード
#  http://www.stickpeople.com/projects/python/win-psycopg/
# Python3.xの場合、unicode(row[1],'utf-8') は不要。
#
import psycopg2

try:
    cnn = psycopg2.connect("dbname=Sample001 host=localhost user=postgres password=xxxxx")
    cur = cnn.cursor()

    #試験データの整理
    pref_cd = 100
    cur.execute("""DELETE FROM t01prefecture WHERE PREF_CD >= %s"""
                , (pref_cd,))
    cnn.commit()

    print("単純なSELECT文==========================")
    from_id = 45
    to_id = 999
    cur.execute("""SELECT PREF_CD,PREF_NAME FROM t01prefecture
                WHERE PREF_CD BETWEEN %s AND %s""" , (from_id, to_id, ))
    rows = cur.fetchall()
    for row in rows:
        #print("%d %s" % (row[0], unicode(row[1],'utf-8')))
        print("%d %s" % (row[0], row[1]))

    print("コミットの試験==========================")
    pref_cd = 100
    pref_name = u"モテモテ国"
    cur.execute(u"""INSERT INTO t01prefecture(PREF_CD, PREF_NAME)
                VALUES (%s, %s)""" , (pref_cd, pref_name))

    pref_cd = 101
    pref_name = u"野望の国"
    cur.execute(u"""INSERT INTO t01prefecture(PREF_CD,PREF_NAME)
                VALUES (%s, %s)""" , (pref_cd, pref_name,))
    cnn.commit()
    cur.execute("""SELECT PREF_CD,PREF_NAME FROM t01prefecture
                WHERE PREF_CD BETWEEN %s AND %s""" , (from_id, to_id, ))
    rows = cur.fetchall()
    for row in rows:
        #print("%d %s" % (row[0],unicode(row[1],'utf-8')))
        print("%d %s" % (row[0],row[1]))

    print("ロールバックの試験==========================")
    pref_cd = 102
    pref_name = u"ロール"
    cur.execute(u"""INSERT INTO t01prefecture(PREF_CD,PREF_NAME)
                VALUES (%s, %s)""" , (pref_cd, pref_name,))

    cur.execute("""SELECT PREF_CD,PREF_NAME FROM t01prefecture
                WHERE PREF_CD BETWEEN %s AND %s""" , (from_id, to_id, ))
    rows = cur.fetchall()
    for row in rows:
        #print("%d %s" % (row[0], unicode(row[1],'utf-8')))
        print("%d %s" % (row[0], row[1]))

    print("-------------------------")
    cnn.rollback()
    cur.execute("""SELECT PREF_CD,PREF_NAME FROM t01prefecture
                WHERE PREF_CD BETWEEN %s AND %s""" , (from_id, to_id, ))
    rows = cur.fetchall()
    for row in rows:
        #print("%d %s" % (row[0], unicode(row[1],'utf-8')))
        print("%d %s" % (row[0], row[1]))

    print("ユーザー定義==========================")
    cur.execute("""SELECT * FROM test_sp(%s,%s)""" , (from_id, to_id, ))
    rows = cur.fetchall()
    for row in rows:
        #print("%d %s" % (row[0], unicode(row[1],'utf-8')))
        print("%d %s" % (row[0], row[1]))

    cur.close()
    cnn.close()

except (psycopg2.OperationalError) as e:
    print (e)

使用感

Python2.XはレコードセットがUTF8で帰ってくるので、一旦UNICODEに変換してやらねばならない。
Python3系は不要。

PostgresのストアドプロシージャーはORACLEのPL/SQLに近い。
ただ、テーブル型を戻り値にする関数を指定できる。

SQLite

環境

特記事項なし。
Python2.5以降なら標準でSQLITE3が操作できるはず。

Pythonのコード

# -*- coding: cp932 -*-
# sqlite3はPython2.5から以降から標準であるはず.
import sqlite3
conn = sqlite3.connect('test.sqlite3')
sql = '''CREATE TABLE  IF NOT EXISTS  t01prefecture(
                         pref_cd INTEGER,
                         pref_name TEXT);'''
conn.execute(sql)

conn.execute(u"DELETE FROM t01prefecture")

# コミットの試験
pref_cd = 100
pref_name = u"モテモテ国"
conn.execute(u"""INSERT INTO t01prefecture(PREF_CD, PREF_NAME)
            VALUES (?, ?)""" , (pref_cd, pref_name))

pref_cd = 101
pref_name = u"野望の国"
conn.execute(u"""INSERT INTO t01prefecture(PREF_CD,PREF_NAME)
            VALUES (?, ?)""" , (pref_cd, pref_name,))
conn.commit()

# ロールバックの試験
pref_cd = 102
pref_name = u"back"
conn.execute(u"""INSERT INTO t01prefecture(PREF_CD,PREF_NAME)
            VALUES (?, ?)""" , (pref_cd, pref_name,))
conn.rollback()

rows = conn.execute(u'SELECT * FROM t01prefecture WHERE pref_cd > ?', (0,))
for row in rows:
    print(u"%d %s" % (row[0], row[1]))

# ユーザ定義
# 文字を連結するのみ
class UserDef:
    def __init__(self):
        self.values = []
    def step(self, value):
        self.values.append(value)
    def finalize(self):
        return "/".join(map(str, self.values)) 

conn.create_aggregate("userdef", 1, UserDef)
rows = conn.execute(u'SELECT userdef(PREF_NAME) FROM t01prefecture')
for row in rows:
    print(u"%s" % (row[0]))

conn.close()

使用感

いままでのDBと違い、サーバーを構築する必要がない。
ユーザ定義の関数をクライアント側で設定できる。

pep8を用いてpythonのコードのスタイルをチェックする

2019.08.01

古いので以下を参照してください。
カウボーイには嫌がられるPythonの話
https://needtec.sakura.ne.jp/wod07672/?p=9191

概要

本ドキュメントでは、Python コードのためのコーディングスタイルであるPEP8に違反していないかチェックする方法について説明する。

PEP 8 -- Style Guide for Python Code
http://legacy.python.org/dev/peps/pep-0008/

訳:
https://dl.dropboxusercontent.com/u/555254/pep-0008.ja.html

インストール方法

PEP8 - Python style guide checker をインストールする
https://pypi.python.org/pypi/pep8/

easy_install pep8

または

pip install pep8

これによりコマンドラインからpep8を実行できる。

pep8 test.py
pep8 /test/directory

ディレクトリを指定した場合はサブディレクトリもチェックする

主なオプション

オプション名 説明
--version バージョンを表示する
-h,--help ヘルプを表示する
-v,--verbose チェック中のファイル名などの状態メッセージを表示する。--vvでデバッグメッセージが表示される
-q,--quiet ファイル名のみ表示する。-qqはなにも表示しない。
--first 同じエラーの場合、最初のみ表示する
--exclude=patterns 除外するファイル名やディレクトリ名のパターンを記述する。コンマで区切ることで複数していできる。
デフォルト:.svn,CVS,.bzr,.hg,.git,pycache
--filename=patterns ディレクトリを検索する場合に、ここで指定したパターンのファイルのみ検索する。コンマ区切りで複数指定できる。
デフォルト: *.py
--select=errors エラーや警告を指定する
例:E,W6
--ignore=errors 指定のエラーを無視する
例:E,W6
--show-source 各エラーにソースを表示する
--show-pep8 各エラーにPEP8の説明を追加する。--firstと伴につかったほうがいい
--statistics エラーや警告の数を集計して最後に表示する
--count 最後にエラーと警告の総数を表示する
--config=path 設定ファイルの場所を指定できる。

設定ファイル

configオプションで指定できる設定ファイルには、各オプションの値を指定することができる。

[pep8]
ignore = E111

Pythonからの使用

pep8をimportすることでPythonから使用することもできる。

import pep8
pep8style = pep8.StyleGuide(quiet=True)
ret = pep8style.check_files(['test.py']);
print ret.total_errors

Jenkinsからの利用

Jenkinsのプラグインである、Violationsを使用すれば集計が可能である。

pep8_2.png

シェルスクリプトから実行する場合は次のように先頭に「#!/bin/sh」を記述するのと最後にエラーコードを返さない処理を行う必要がある。

# !/bin/sh
pep8 /share/py/test.py > ${WORKSPACE}/test.txt
echo "....finished"

このような処理をする必要がある理由としては、下記のページを参照の事。
Jenkinsのシェルの実行について
Violationsの設定は下記の通り。

pep8_1.png

XMLで出力する必要はない。

Pythonを用いてTwitterの検索を行う

目的

Pythonを用いてTwitterの検索を行うスクリプトを記述する。
最終的な成果物については下記からダウンロードできる。

https://github.com/mima3/searchTwitter

準備

(1)Python2.7がインストール済みであること。
(2)python_twitterをインストールする

easy_install python_twitter

(3)下記のページよりTwitter用のAPIを取得する
https://dev.twitter.com/

詳しい取得方法は下記のページを参考にする。
http://support.dreamone.co.jp/Pandora/dp.do?jumpTo=DreamX&variables%28LPID%29=162

使用するTwitterのAPI

application/rate_limit_status.json

https://dev.twitter.com/docs/api/1.1/get/application/rate_limit_status
各APIの制限を取得する。
これにより、各APIが後何回使用できるか、そして、リセット時間がいつか調べることができる。

search/tweets.json

https://dev.twitter.com/docs/api/1.1/get/search/tweets
検索用のAPI。

一回のAPIで取得できる上限は100件となる。
APIを経由した検索は公式ページからの検索と結果が異なる場合がある。
公式では7日前のツイートでも検索されるが、APIの場合、過去のツイートは検索対象とならない。

また、result_typeにより検索で取得されるツイートがことなる。
recentは時系列で取得する
popularは人気のあるツイートを取得する
mixedは上記をまぜたものである。

検索文字

検索APIで指定できる文字は「高度な検索」で使用する検索文字を使用することができる。
https://twitter.com/search-advanced

AND,ORを用いた検索例

(えーりん OR 永琳) AND (BBA OR 婆 OR ババア)

「えーりん」または「永琳」と「BBA」または「婆」または「ババア」とつぶやかれているものを検索する。

特定のユーザがつぶやいたものを検索する例

「from:」のあとにユーザー名を入力する。

from:mima_ita

fromを使用した検索はAPIでは100件が上限だと思われる。

特定の場所でつぶやいたものを検索する例

「geocode:」の後に座標と範囲を指定する。
次の例は東京タワー半径500mのつぶやきを取得したものである。

geocode:35.65858,139.745433,0.5km

geocodeを使用した検索はAPIでは100件が上限だと思われる。

実装例

ここでは指定の検索文字を検索する処理の実装例を示す。
本来は1回の検索APIで100件しかとれないので、これを改造し制限いっぱい取得できるようにする。

まず1回目の検索APIを"result_type=recent"で検索して時系列で取得する。この時取得されるのは最新から過去100件のみである。

二回目の検索では、1回目の検索で取得したもっとも古いツイートより昔のものを取得するようにする。これを行うには"max_id = 前回の最小id-1"を指定すればよい。

すべての1件も取得できなくなるまでこれを繰り返すか、rate_limit_statusで取得したAPIの制限を超えるまで繰り返せばよい。

この簡単のサンプルを以下に示す。

# !/usr/bin/python
# -*- coding: utf-8 -*-
# python_twitter 1.1
import twitter
from twitter import Api
import sys
import time
reload(sys)
sys.setdefaultencoding('utf-8')
from collections import defaultdict

maxcount=1000
maxid =0
terms=["八意永琳","永琳","えーりん"]
search_str=" OR ".join(terms)

api = Api(base_url="https://api.twitter.com/1.1",
                  consumer_key='XXXXX',
                  consumer_secret='XXXXX',
                  access_token_key='XXXXX',
                  access_token_secret='XXXXX')
rate = api.GetRateLimitStatus()
print "Limit %d / %d" % (rate['resources']['search']['/search/tweets']['remaining'],rate['resources']['search']['/search/tweets']['limit'])
tm = time.localtime(rate['resources']['search']['/search/tweets']['reset'])
print "Reset Time  %d:%d" % (tm.tm_hour , tm.tm_min)
print "-----------------------------------------\n"
found = api.GetSearch(term=search_str,count=100,result_type='recent')
i = 0
while True:
  for f in found:
    if maxid > f.id or maxid == 0:
      maxid = f.id
    print f.text
    i = i + 1
  if len(found) == 0:
    break
  if maxcount <= i:
    break
  print maxid
  found = api.GetSearch(term=search_str,count=100,result_type='recent',max_id=maxid-1)

print "-----------------------------------------\n"
rate = api.GetRateLimitStatus()
print "Limit %d / %d" % (rate['resources']['search']['/search/tweets']['remaining'],rate['resources']['search']['/search/tweets']['limit'])
tm = time.localtime(rate['resources']['search']['/search/tweets']['reset'])
print "Reset Time  %d:%d" % (tm.tm_hour , tm.tm_min)

発展系

上記を発展させたスクリプトを下記からダウンロードできる。
https://github.com/mima3/searchTwitter

上記のスクリプトは検索結果をSQLITEに保存している。
このスクリプトはAPIの呼び出し制限ギリギリまで過去のツイートを検索する。
つぎにスクリプトを実行した場合、以下のようになる。

すべての検索可能な過去のツイートを検索済みの場合
DBに登録されているツイートより新しいツイートを検索する。

過去のツイートが残っている場合
前回取得したツイートより古いツイートを検索する。

このスクリプトにより、大量の検索結果を容易に取得することができる。