プログラミングの最近のブログ記事

以前からこのブログにもたまに書いていたが、LibreOffice Calc でサーバが吐き出したテキストファイルを読み込み集計、最後にその結果を元に印刷処理を行うマクロを Python で書いていたのだが、OpenOffice.org 系オフィススイーツの定番マクロ言語である Basic で書き直すことにした。

理由は色々あるので、別のエントリーにまとめようかな。

とりあえず、今日の日付をセルにセットするだけの簡単な処理を Python と Basic で書いてみたので貼っておく。どちらもまったく同じ動きをする。

def get_today(*args):

    doc = XSCRIPTCONTEXT.getDocument()

    # シートを選択
    sheet = doc.getSheets().getByName('Main')

    # 今日の日付の取得
    dt_now = (datetime.datetime.now()).strftime('%Y/%m/%d')

    # すでにセルに値が入っている時は更新するか確認する
    kaisai_nengetsu = sheet.getCellRangeByName('C3')

    if kaisai_nengetsu.String:
        msg_box = Bridge()
        res = msg_box.run_querydialog(title='開催年月日セット', message='すでに値がセットされています。\n上書きでセットしますか?')

        # res=2 はい res=3 いいえ
        if res != 2:    # 「はい」でなければ終了
            return

    kaisai_nengetsu.String = dt_now

次に Basic で書かれたマクロ。

Option Compatible

Sub get_now_date

Dim objDoc As Object
Dim objSheet As Object
Dim objCell As Object

objDoc = StarDesktop.CurrentComponent
objSheet = objDoc.Sheets.getByName("Main")

' 既にセルに値が入っていたら、上書きするかを聞く       
objCell = objSheet.getCellRangeByName("C3")

If objCell.String <> "" Then
If MsgBox ("すでに値がセットされています。" & vbCrLf & "上書きでセットしますか?", 292, "開催年月日セット") = 7 Then
        ' いいえのボタンが押された場合
return
End If
End If 

' 現在時刻の取得
objCell.String = Format(Now(), "yyyy/mm/dd")

End Sub

以上。まあ、これだけ簡単な処理では特にどっちの言語で書いてもなんの問題もない。
見比べてもあまりおもしろいもんじゃないな(笑)

<追記>
コメントで「文字列リテラル中の \n は効かない」ということを教えていただきましたので、Option Compatible を指定して VBA compatibility モードをオンにし、vbCrLf 定数を連結する形に修正しました。16進文字"\x0D\x0A"を直打ちでもよさげですが、ついでに VBA compatibility モードを試してみたかったので。
VisualBasic から ADO.NET を使用して SQL Server に接続・使用する簡単な例。

SQL Server のインストールや、DB作成、TABLE作成は済んでいること。

(1) データソースの選択と、DB接続設定を行う

    • [表示]→[サーバーエクスプローラー] でサーバーエクスプローラーを表示する。
    • サーバーエクスプローラー上にある DB アイコン(データベースへの接続)をクリック。
    • 「データソースの選択」ダイヤログが開くので、データソースで「Microsoft SQL Server」を選択。データプロバイダーは「.NET Framework SQL Server用データプロバイダー」を選択する。[続行]ボタン押下。
    • 「接続の追加」画面が開くので、「サーバー名」を設定。サーバ名がわからなければ SQL Server Management Studio 等で調べる。うちの場合は「XVIDEOS\SQLEXPRESS」。XVIDEOS は PC名。(一覧から選択するのかと思ったが、フルに手入力しないといけなかった)
    • 「サーバにログオンする」の「認証」を「SQL Server 認証」に変更する。ユーザ名とパスワードが入力可能になるので入力。(あらかじめ SQL Server Management Studio で作成したもの)
    • 「データベースへの接続」の「データベース名の選択または入力」を選択。DB名を入力する。
    • 画面下の「テスト接続」ボタンを押してみて正常に接続されれば「OK」ボタン押下。(うまくいかなければ、DB名やユーザアカウント、パスワードが間違っていないか?)

(2) データソースの登録

    • [表示]→[その他のウィンドウ]→[データソース]でデータソースウィンドウを表示。
    • データソースウィンドウの上の一番左の DB アイコン(データソースの追加)をクリック。
    • 「データソースの種類の選択」画面で「データベース」を選択し「次へ」ボタン押下。
    • 「データベースモデルの選択」画面で「データセット」を選択して「次へ」ボタン押下。
    • 「データ接続の選択」では、対象の「データ接続」を選択し、「はい。重要情報を接続文字列に含めます」を選択し「次へ」ボタン押下。
    • 「接続文字列をアプリケーション構成ファイルに保存する」画面では、そのまま「次へ」ボタン押下。
    • 「データベースオブジェクトの選択」画面では、プログラムから参照する TABLE などをすべて選択する。「完了」ボタン押下。

(3) プログラム開発

サンプル用に、超簡単なプログラムを作る。
ID と NAME という項目を持つ m_user という TABLE を参照し、NAME を画面に出すというプログラム。
例えば、ID='admin' の NAMEは'偉い人'である。

20200701_VB1.jpg

開発する画面は「IDを入力し『参考』ボタン(・・・参照と打ったつもりなのに(^^;)を押すと、下に『名前は○○です』」と表示する」という単純なもの。

「参考」ボタンの Click イベントのソースを下記のとおり書いた。

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        Dim sql As String

        'SQLステートメント
        sql = "SELECT name FROM m_user WHERE id='" & TextBox1.Text & "'"

        Label1.Text = ""

        'コネクション指定
        Using connection As New SqlClient.SqlConnection(My.Settings.nandfConnectionString)

            'データコマンドを定義する
            Using command As New SqlClient.SqlCommand(sql, connection)

                'コネクションを開く
                connection.Open()

                'データリーダの生成
                Dim dr As SqlClient.SqlDataReader = command.ExecuteReader()

                'データが存在する場合、label1にセット
                '検索結果は1件だけ
                Do While dr.Read
                    Label1.Text = "名前は " & dr("name") & " です"
                Loop

                'データリーダを閉じる
                dr.Close()

                'コネクションを閉じる
                connection.Close()

            End Using

        End Using

    End Sub

いやあ、ADO.NET はデータソースの選択までのアレコレが煩わしいね。まあ、おかげでソースを書くのは楽なんだけど(笑)

それより、うーむ・・・まだ 5年前にやった VB.NET 案件で DB 利用をどのようにコーディングしたか思い出せない。なんとなく、インタフェースは ADO.NET だったような気がするが・・・
LibreOffice のマクロを Python で書いた場合の、「OK」ボタンが1つだけのダイアログボックスと、「はい」「いいえ」の選択肢のあるダイヤログボックスの使い方。
情報をググってみても、なんか「表示させるまで」の情報や、「OK」ボタン一個だけの場合の情報ばかりが多かったので、具体的な例をあげとこう。

a.セルに値をセットする時、すでに値がセットされていたら上書きするか確認表示

 20200613_dialog1.jpg

b.読み込むファイルが無い時、その旨をアラート表示

 20200613_dialog2.jpg

ソースは以下のとおり。(@ty21kyさんの Qiita の記事を参考に)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import uno
import datetime
import re
import os

CARD_DATA = '/data/number_cardid.csv'

class Bridge(object):

    def __init__(self):

        self._context = XSCRIPTCONTEXT.getComponentContext()
        self._desktop = XSCRIPTCONTEXT.getDesktop()
        self._frame = self._desktop.CurrentFrame
        self._window = self._frame.ContainerWindow
        self._toolkit = self._window.Toolkit

    def run_infodialog(self, title='', message=''):

        msgbox = self._toolkit.createMessageBox(self._window,
                                                'infobox',    # ヒントアイコン
                                                1,            # 「OK」ボタン
                                                title,
                                                message)
        msgbox.execute()
        msgbox.dispose()

    def run_querydialog(self, title='', message=''):

        msgbox = self._toolkit.createMessageBox(self._window,
                                                'querybox',   # 疑問符アイコン
                                                3,            # 「はい」「いいえ」ボタン
                                                title,
                                                message)
        return msgbox.execute()

def get_today(*args):

    doc = XSCRIPTCONTEXT.getDocument()
    sheet = doc.getSheets().getByName('Main')
    dt_now = (datetime.datetime.now()).strftime('%Y/%m/%d')
    kaisai_nengetsu = sheet.getCellRangeByName('C3')

    if kaisai_nengetsu.String:
        msg_box = Bridge()
        res = msg_box.run_querydialog(title='開催年月日セット', message='すでに値がセットされています。\n上書きでセットしますか?')

        if res != 2:    # 「はい」でなければ終了(res=2 はい res=3 いいえ)
            return

    kaisai_nengetsu.String = dt_now

def load_card_all(*args):

    doc = XSCRIPTCONTEXT.getDocument()
    sheet = doc.getSheets().getByName('Card')

    if not os.path.isfile(CARD_DATA):
        msg_box = Bridge()
        msg_box.run_infodialog(title='ファイルエラー', message='ファイルが存在しません\n' + CARD_DATA)
        return

    f = open(CARD_DATA)
    lines = f.readlines()
    f.close()
    i = 0;
    for line in lines:
        list = re.split("\|\|", line.rstrip('\n'))
        card = sheet.getCellByPosition(0, i)
        id = sheet.getCellByPosition(1, i)
        card.Value = list[0]
        id.String = list[1]
        i += 1

窓の大きさとかは他の方のブログを見ると、設定はできるのに無視されるようだ。

つーか、LibreOffice(OpenOffice.org も)の Python マクロについて書かれた本って無いのね。
まあ、マニアックなネタだからな(笑)

<追記>
エラーダイアログは「infobox」ではなく「errorbox」だよね。
ダイアログ表示関数にメッセージタイプも渡すように修正してもいいが、エラーはエラーで別関数を用意した方がソース的にはわかりやすいかな?

 20200614_windows3.jpg

LibreOffice の Python マクロを久しぶりに作っているので、「知っていたのに忘れてしまったこと」で小一時間ほど無駄にしてしまった(^^;

例えば以下のようなマクロを実行する時。

<略>
def get_today():
    '''
    今日の日付を取得して開催年月日にセット
    '''

    doc = XSCRIPTCONTEXT.getDocument()

    # シートを選択
    sheet = doc.getSheets().getByName('Main')

    # 今日の日付の取得(YYYY/MM/DD)
    dt_now = (datetime.datetime.now()).strftime('%Y/%m/%d')

    # 開催年月日のセルにセット
    # すでに日付が入っている時は更新するか確認する
    kaisai_nengetsu = sheet.getCellRangeByName('C3')

    if kaisai_nengetsu.String:
        msg_box = Bridge()
        res = msg_box.run_querydialog(title='開催年月日セット', message='すでに値がセットされています。\n上書きでセットしますか?')

        if res != 2:    # 「はい」でなければ終了
            return

    kaisai_nengetsu.String = dt_now
<略>

これを、「ツール」→「マクロ」→「マクロの管理」→「Python」でマクロを実行したときは問題ない。

20200612_libre1.jpg

しかし、これをフォームコントロールの「プッシュボタン」のイベントとして実行すると、

com.sun.star.uno.RuntimeException: Error during invoking function get_today in module file:///C:/Users/hoge/AppData/Roaming/LibreOffice/4/user/Scripts/python/m_main.py (<class 'TypeError'>: get_today() takes 0 positional arguments but 1 was given
  File "C:\Program Files\LibreOffice\program\pythonscript.py", line 921, in invoke
    ret = self.func( *args )
)

というエラーになってしまうのである。

20200612_libre2.jpg.jpg

get_today 関数の引数は無しなのに、ボタンのマクロ設定には m_main.py$get_today (user, Python) と何やら渡されてるから?
0 positional arguments(引数はゼロ個(無し))なのに、but 1 was given(しかし、引数がひとつ渡された)ってことよね?

そう、すっかり忘れていたのである。
ボタンで呼ばれるマクロの関数は、*args という任意の数の引数を受け取れる可変長引数を指定しないといけないことを・・・
でも、思い出したでぇ~(笑)

というわけで、関数宣言部を、

def get_today(*args):

と直したらエラーは消えたのでありました。めでたし、めでたし。

以前このブログでも何度かエントリーを書いた SONY RC-S380 で FeliCa カードを読み込んだ情報を Python で利用する話。

Windows 7 Professional で開発をしてたんだけど、今回、環境を Windows 10 Pro に移した。

その際、ちょっと Windows 7 の時何をやったかすっかり忘れてて、うろ覚えでセットアップして「プチはまり」をしたので環境構築についてメモっておく(^^;

ちなみに、Python 3.8.3 はすでにインストールされている。

1.WinUSB ドライバのインストール

https://zadig.akeo.ie/ からダウンロードした zadig-2.5.exe を使って WinUSB のインストールを行う。

RC-S380 を Windows 10 に接続したときに自動でインストールされた sonynfcport100c(v1.5.7.2)を WinUSB(v6.1.7600.16385)に Replace する。

・・・が、これ、本当に必要だったのか?Windows 7 のときには必要だったが、Windows 10 だと sonynfcport100c ドライバがインストールされているので、WinUSB ドライバを入れる必要があるのかどうなのか?

もし、このページを見てセットアップをしようと思っている人は、一度、この作業無しで RC-S380 が読めるか試してみてほしい。俺は面倒臭いので試さないけど(笑)

2.pip のバージョンアップ

必須ではないが、モジュールのインストールの度にワーニングが出るのでバージョンアップしておく。

コマンドプロンプトで、
python -m pip install --upgrade pip

3.nfcpy モジュールのインストール

コマンドプロンプトで、
pip3 install nfcpy

libusb1  1.8
ndeflib  0.3.3
nfcpy    1.0.3
pip      20.1.1
pyDes    2.0.1
pyserial 3.4
などがインストールされる。

4.libusb-1.0.dllのインストール

から最新版ダウンロード。
今日時点の最新版は、

ダウンロード後、展開したファイルを、Windows システムフォルダにコピー。

・64bit版
展開場所\MS64\dll\libusb-1.0.dll → C:\Windows\System32 にコピー

・32bit版
展開場所\MS32\dll\libusb-1.0.dll → C:\Windows\SysWOW64 にコピー

余談だが、64bit プログラムを格納する場所が System32 だとか、このわかりづらい構成はどうにかならんものか?(^^;>Windows

以上で、Windows 10 でも、Python プログラムの中から FeliCa カードが読めるはずだ。

ちなみに、作成したプログラムの中で HTTP プロトコルを使ってサーバにカード情報を飛ばしているので、requests モジュールも pip でインストールしないといけないのだが、これは、まあ、俺独自の話なので(笑)

複数の Raspberry Pi などの端末につながったカードリーダーで FeliCa カードを読んで、http プロトコルでカード情報をサーバに送る仕組みを作ってる。

データを送ってリターンコードを受け取るだけの簡単な API の仕組みなので(データ種はカード新規登録、読み込み時間報告、カード登録キャンセルなど色々あるけど)、サーバ側も Apache とかインストールはせず Python で書こうかと思った。クライアント側のプログラムを Python で書いてるんでね。

ところが、ネット(Qiita「pythonでローカルwebサーバを立ち上げる」など)で拾った下のようなスクリプトを走らせて、そこに Python で作ったクライアントソフトでアクセスしてみると・・・

import http.server
import socketserver

PORT = 3000
Handler = http.server.SimplehttpRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print("serving at port", PORT)
    httpd.serve_forever()

これが、501 エラーになるのだ。

127.0.0.1 - - [06/Feb/2020 22:41:16] code 501, message Unsupported method ('POST')

ってエラーログ吐いてる。

POST メソッドに対応してないようだ。ええ???

確かに、ブラウザからアクセス(つまり GET メソッドのアクセス)してみたらエラーにならず、サーバスクリプトを置いているディレクトリのファイルリストが表示された。GET メソッドは OK のようだ。

20200206_pyserver.jpg

うーん・・・POST メソッドを使うには、http.server 以外にも import せんといかんのかいな?面倒くさっ。

というわけで、サーバ側は Node.js で組むことにした。(そして今、ほぼ完成してちゃんと動いている)

ただ、Python も JavaScript(しかも Node.js 拡張版)も日頃使ってる言語じゃないので、いちいちググりながら組んでいくのが面倒だけど、ま、これも勉強だからな。
仕事をしつつ勉強できるというのは良いことだ(笑)。そう思っておこう。
Python の requests ライブラリを使って Web API を叩くクライアントソフトを作ってるんだけど、接続時のタイムアウト処理をしようと思って、

    try:
        res = requests.post('https://127.0.0.1:3000/', data={'data': data}, stream=True, timeout=(3.0, 7.5))
    except requests.exceptions.ConnectTimeout:
        print('Requests.POST Timeout ' + data)
        return(901)
    else:
        print('Success' + data)
        return(res.status_code)
    finally:
        pass

こんな風に書いてたんだけど、サーバを立ち上げていない状態で一向にタイムアウト例外が発生しない。

んん???と思って、

    except requests.exceptions.RequestException as e:
        print(str(e))
        return(909)

と全部の requets 関係の例外を引っ掛けてみたら、

httpConnectionPool(host='127.0.0.1', port=3000): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.httpConnection object at 0x000000000523AE48>: Failed to establish a new connection: [WinError 10061] 対象のコンピューターによって拒否されたため、接続できませんでした。'))

だって。そうか、そうか。接続先のサーバにそもそも接続できない時は、レスポンス待ちにもなっていないわけだから、接続タイムアウトエラーではなく接続エラーなんだな。

というわけで、接続エラーを引っ掛ける

    except requests.exceptions.ConnectionError:
        print('Requests.POST ConnectionError ' + data)
        return(901)

という例外処理も追加しておかねばな。サーバが落ちてるんか、立ち上がっているけど反応が遅いのかわからんからな。
20200114_libre1.jpg

つまらんミスでハマってしまった(^^;

LibreOffice Calc のフォームボタン押下でマクロを実行するには、

  1. ボタン上で右ボタンクリック→「コントロールのプロパティ」を選択
  2. イベントタブを選択し、イベントの登録(「...」ボタンを押してマクロの選択)

という手順なわけだけど、ずっと「キーを押した時」ってイベントが俺の頭の中では「ボタンを押した時」に自動変換されてて、ここに割り当てたマクロが「ボタンを押しても実行されない」って悩んでたわ(^^;

「ボタンを押す」というイベントでマクロを実行したい時は「実行時」というイベントにマクロを登録する(関連付ける)。
「実行時」=「ボタンを押した時」なわけだ。

小一時間くらい悩んでたわ・・・(^^; 疲れてるなあ、俺。もう寝ます。
APSO は Alternative python script organizer という IDE である。

とりあえず LibreOffice Calc 上で Python の開発をするのに IDE 入れた方が便利かなと思って、「Python iDE LibreOffice Calc マクロ」とかで検索してみて、最初に当たった情報が APSO だったので入れてみただけである。

1.まず、「ツール」→「拡張機能マネージャー」で表示された画面の「ほかの拡張機能をオンラインで取得...」リンクをクリック。

20200113_LibroPython1.jpg

2.LibreOffice Extensions and Templates の画面が開くので「APSO」で検索。

3.「APSO」にヒットした一覧が表示されるので(と言っても 1個だけだと思うけど)、APSO - Alternative Script Organizer for Python のリンクをクリック

4.APSO のページから(現在使用中の)LibreOffice 6.3.0.4 に対応した版(APSO 1.2.6.2)のダウンロードページへ移動。apso.oxt(48KBほど)をダウンロードする。

5.「拡張機能マネージャー」で「追加」ボタン押下。今ダウンロードした apso.txt を選択。

6.「拡張機能マネージャー」に「APSO 1.2.6.2」が追加されたのを確認

20200113_LibroPython4.jpg

7.「閉じる」ボタン押下→再起動

8.Calc が再起動したら、「ツール」→「マクロ」→「Python スクリプトの管理」で APSO 画面を起動。

20200113_LibroPython6.jpg

9.試しに、APSO console を起動してみる。

20200113_LibroPython7.jpg

ちゃんと起動したので、とりあえず APSO のインストールは正常に終了してるってことでええのかな?
今の仕事が NetCOBOL(PowerCOBOL)だって話は以前書いたと思う。

まあ、イベント駆動型の言語なんで、おおむね PowerCOBOL はそれほど違和感を感じることなく楽しく使ってるんだけど、それでも時々 「COBOL だなぁ(^^;;」と思わせられることがある。

そのひとつが変数代入時の桁落ち。

昨日も、21,002回(中途半端な数字なのは気にしないで(笑))回す処理が 1002回しか回らなくて、なんでやねんと思ったら、案の定、21002という回数をセットする変数が、

01 WK-COUNT  PIC 9(4).

になってた(笑)
なので 5桁の数値が入らず、下 4桁だけがセットされたというわけ。

まあ、俺の単純ミスなんだけど、こういうのって普通はコンパイル時になんかエラーになったりワーニングが出たりせんもんかいな?

まあ、簡単に任意の桁の切り出しができるのが「便利」と言うことなのかもしれんが、例えば 5桁の数値から下 4桁の数値を切り出すのなら、ちゃんと明示的に、

01 WK-VAL.
  02 WK-H1   PIC 9(1).
  02 WK-T4   PIC 9(4).

という変数作って、

MOVE   12345   TO  WK-VAL.

して、やればいいやん。よっぽど後でソースも読みやすい。

いや、1〜10桁の間で桁数は変化して、その中の下 4桁が取りたいんや・・・ってことなら、WK-H1 を 6桁にすればええだけや。
どっちにしても COBOLの場合、「実際、何桁になるかわからん」場合もちゃんと固定長で変数宣言せんといかん。ちゅうても、何十桁もあるような数値、そもそも扱わんやろ、COBOLで(笑)

というわけで、桁あふれしてもなんも教えてくれん COBOL には若干苛立ちをおぼえるのお。

このアーカイブについて

このページには、過去に書かれたブログ記事のうちプログラミングカテゴリに属しているものが含まれています。

前のカテゴリはパソコンです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。


月別 アーカイブ

電気ウナギ的○○ mobile ver.

携帯版「電気ウナギ的○○」はこちら