follow us in feedly

プログラミング

WindowsのCakePHP3で発生したintlエラーを解決する

CakePHP3で開発するとintl拡張モジュールのエラーに出くわしますよね。
今回はWindowsらしい原因だったので、ちょっとハマりました。
ということで、解決した方法を書いていきます。

環境

  • Windows7
  • PHP 7.1.8
  • CakePHP 3.5.5

エラー内容

エラーはエクステンション関連の内容です。
なので、すぐに解決するだろうと思ってました。

Fatal error: You must enable the intl extension to use CakePHP.

intl拡張モジュールはあるし、php.iniにも設定してる

調べてみると解決方法として紹介されているのは、次の両方またはどちらかです。

  • intl拡張モジュールをインストールする
  • php.iniのextensionに設定する

でも、自分の環境ではどちらも問題ありませんでした。

C:¥php¥extにphp_intl.dllはあるし、
php.iniにも「extension=php_intl.dll」が設定されています。

Windows環境変数への設定が原因だった

モジュールも設定も問題ないのでハマりました。
Apacheの再起動とか、OSの再起動とか試してもやっぱりダメ。
そんななか、環境変数の設定をチェックしたところ。

ユーザー環境変数のほうにPHPのパスが書いてある。。。

すぐにシステム環境変数のほうにPHPのパスを追加して解決!
サイトが無事に表示されました。

まとめ

CakePHP3で見かけるintl拡張モジュールのエラー解決方法を紹介しました。
Windowsのシステム環境変数にPHPのパスを追加することで解決できます。
モジュールがないことやphp.iniの設定ばかりが原因ではないんですね。
Windows環境でCakePHP3を利用するときは注意してください。

Windows版Python3でCSVをSQLiteに保存する

前回のWindows版PHPでのCSVパース問題を解決するためPythonを採用しました。
今回は、そのWindows + PythonでCSVを取り込む方法をご紹介します。
詳細な解説はほとんどなく、ほぼソースコードのみです。

環境

  • Windows 7
  • Python 3.6.3

ソースコード

CSVを読み込んでデータベースに保存します。
仕事で作ったものなので改変しています。

# -*- coding: utf-8 -*-

import os
import io
import sys
import csv
import sqlite3

# 標準出力の文字コードをUTF-8にする
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

# ディレクトリを移動
os.chdir(r'C:\Users\77dogs\Sample')

# データベースに接続
conn = sqlite3.connect('db\contents.sqlite3')
c = conn.cursor()

# contentsテーブルのカラムを取得
sql = 'SELECT * FROM contents LIMIT 1'
c.execute(sql)
columns = []
for row in c.description:
    columns.append(row[0])

sql = 'INSERT INTO contents (%s) VALUES (%s)' \
        % (','.join(columns), ('?,' * len(columns)).rstrip(','))

# CSVの読み込み & 保存
with open('csv\data.csv', encoding='utf-8') as csvfile:
    reader = csv.reader(csvfile, delimiter=',', quotechar='"')
    next(reader)  # ヘッダ読み捨て

    for row in reader:
        c.execute(sql, tuple(row))  # SQLを実行
        conn.commit()  # commit()は必須

conn.close()

標準出力の文字コードを変更する

MSYS2にprint()で出力したときに文字化けしたのでその対策。
sys.stdoutのデフォルトの文字コードはCP932なのでUTF-8に変更。
ちなみに、上記ソースコードにはprint()はありません。

print(sys.stdout.encoding)  # cp932
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
print(sys.stdout.encoding)  # utf-8

raw文字列によってエスケープシーケンスを無効にする

エスケープシーケンス(\n, \t など)として解釈してほしくない場合の対策。
下記の場合は、「\U」があるのでraw文字列でないとエラーになる。

# SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 2-3: truncated \UXXXXXXXX escape
os.chdir('C:\Users\77dogs\Sample')

# raw文字列(r'文字列')だとエスケープシーケンスを無効化
os.chdir(r'C:\Users\77dogs\Sample')

まとめ

Windows + Python3を使って無事にCSVの取り込むができました。
情報もたくさんあるので、多少のつまづきはあったものの簡単に実装できました。
ファイルパスにバックスラッシュを使う場合はエスケープシーケンスに注意です。
はじめてのPython成果物ですが参考になれば幸いです。

参考

sys.stdout のエンコードを変更する in Python3.0
raw文字列 – 文字列 – Python入門
エスケープシーケンス – 文字列 – Python入門
Pythonでsqlite

Windows版PHPでCSVが正しくパースされない問題に悩んだ

仕事でちょっとした多言語サイトを開発することになったわけですが
掲載情報をCSVで取り込むときに正しくパースされない問題に悩まされました。
ということで、解決(?)するまでの経緯を書いていきます。

環境

Windows7
PHP 7.1.8
SQLite 3.20.0

Windows7 + PHPでマルチバイト文字のCSVのパースができなかった

今回、開発するサイトは多言語のサイトです。
日本語に加えて英語、韓国語、中国語(繁体字・簡体字)も扱います。

各言語のCSVをPHPのSplFileObjectで読み込もうとするも
日本語、韓国語、中国語といったマルチバイト文字の言語が適切にパースされませんでした。
文字コードはすべてUTF-8です。

文字エンコードをしてみる

UTF-8のまま読みたかったが仕方がないので、下記のとおりにエンコードすることに。
すると、いい感じにパースされたので、「解決!」と思ったのだけど。。。

  • 日本語→CP932
  • 韓国語→CP949
  • 繁体字→CP950
  • 簡体字→CP936

エンコードできない文字がある

いい感じだったのは日本語、韓国語まで。
繁体字になってエンコードできない文字(?になってる)があることが判明。
簡体字も繁体字ほどではないにしろ同様で、よくよくみると韓国語もまれにあるという状況でした。

setlocaleも試してみる

文字化けするのなら、UTF-8でなんとかするしかないかと考えて
setlocale()をするもパース位置が変わる程度で解決には至らず。

ちなみに、Windowsでのsetlocale()はセットの仕方に注意。

# これだとセットできない
setlocale(LC_ALL, 'ja_JP'):

# これだとセットできる
setlocale(LC_ALL, 'Japanese_Japan');
setlocale(LC_ALL, 'Japanese_Japan.932');
setlocale(LC_ALL, 'ja-JP');

他の環境での動きを見てみる

以下の3つの環境でどうなるかを見てみた。

  • CendOS6 + PHP5.3
  • Windows7 + Python3
  • Windows PowerShell

結果としては、上記の環境ではUTF-8のまま問題なくパースできました。
ということで、Windows版PHPがダメらしいということがわかったのです。

Windows7 + Python3でやることにした

時間も限られているので、PHPでの解決は諦めることにして
Windows7 + Python3でCSVを取り込むことにしました。

理由としては、

  • 今回は諸事情によりWindows環境でやるしかないこと
  • PowerShellでもいいのだけど、Pythonを使ってみたかったからです。

ここにはPythonでの詳細は書きませんが無事に取り込めました。

まとめ

Windows7 + PHPでの開発で遭遇したCSVのパース問題をご紹介しました。

  • CSVがマルチバイト文字の言語だと正しくパースされなかった
  • 各言語をUTF-8からエンコードするとパースはされたが一部文字化けした
  • setlocale()するも期待した結果は得られなかった

結局、Windows版PHPがダメだと判断して、Pythonを使うことで解決しました。
Windows + PHPでの開発をお考えの方はご注意ください。

Excel VBAで選択中セルのURLをQRコード表示する

約3年ぶりの更新ですね。
今回もどこまで続けられるのかは誰にもわかりません。

1発目は、はじめて使ったExcel VBAについて。
仕事の中で「こんなの作れない?」とお願いされたのがきっかけです。

作ったのは、選択中セルにあるURLをQRコードに変換して表示するもの。
ExcelのURL一覧をスマートフォンで確認していくのに使うようです。
バージョンはExcel 2013です。
もともとは仕事用なので業務に特化したところは変えてます。

ソースコード

ソースコードはこんな感じです。
ユーザーフォーム (UrlForm.frm)は使ってみたかっただけです。
これくらいならInputBoxを使ったほうが楽だと思う。

MakeQRCodeModule.bas

Option Explicit

Sub MakeQRcode()
  On Error GoTo ErrorMsg

  Dim url As String
  Dim objIE As Object

  '選択中のセルからURLを取得
  url = ActiveCell.Value

  'URLが取得できない場合はフォーム入力(1回のみ)
  If url = Empty Then
    UrlForm.Tag = "Cancel"
    UrlForm.Show

    'OKボタン以外(キャンセルボタン、閉じるボタン)は終了
    If Not UrlForm.Tag = "OK" Then
      Exit Sub
    End If

    'テキストボックスにURLが入力されなかったら終了
    If UrlForm.UrlTextBox.Value = Empty Then
      MsgBox ("URLが取得できません")
      Exit Sub
    End If

    'テキストボックスからURLを取得
    url = UrlForm.UrlTextBox.Value
  End If

  'Google Chart APIのURLを作成
  url = "http://chart.apis.google.com/chart?cht=qr&chs=150x150&chl=" & Application.WorksheetFunction.EncodeURL(url)

  'Internet ExplorerでGoogle Chart APIからQRコードを取得して表示
  Set objIE = CreateObject("InternetExplorer.Application")
  With objIE
    .Visible = True
    .Top = 100
    .Left = 500
    .Width = 500
    .Height = 500
    .Navigate (url)
  End With

  Do While objIE.Busy = True Or objIE.ReadyState <> 4
    DoEvents
  Loop

  Exit Sub

ErrorMsg:
  MsgBox ("Error:" & Err.Description)
End Sub

UrlForm.frm

Private Sub okButton_Click()
  UrlForm.Tag = "OK"
  UrlForm.Hide
End Sub

Private Sub cancelButton_Click()
  UrlForm.Hide
End Sub

Private Sub UrlForm_Activate()
  UrlForm.UrlTextBox.SetFocus
End Sub

ポイント

選択中の結合セルの値を取得

結合されたセルから取得する必要があったので
調べていくつか出てきた方法を試したけど取得できず。
結局、下記のようなセルの値を取得する方法でできました。

ActiveCell.Value

Excel 2013だからできたのかもしれないです。
上記の全ソースコードにはないけど業務用バージョンでは使ってるので紹介。

ユーザーフォームのどのボタンをクリックしたか判別する

キャンセルボタンをクリックしたときにエラー表示なしで終了するために実装。

まず、UserFormのTagプロパティに情報を入れる。

UrlForm.Tag = "OK"

そうすると、モジュール側で判定できるようになります。

If Not UrlForm.Tag = "OK" Then
  Exit Sub
End If

この方法が調べたなかで一番シンプルでした。
Tagプロパティに入れた情報は終了しても残るので、フォームを起動するごとに初期化してます。

ユーザーフォームのフォーカス

テキストボックスにフォーカスさせるならSetFocusメソッドを使う。
ユーザーフォーム表示時にセットする場合は、Activateイベント内でメソッドを呼ぶ。
Initializeイベント内で呼び出してもできませんでした。

まとめ

今回は、選択中セルにあるURLをQRコードに変換して表示するExcel VBAを作りました。
いくつか苦労したポイントを紹介していますが、QRコードを表示したいだけなら
Google Chart APIのURLを作成してIEを起動すればいいので、もっと簡単にできると思います。
初Excel VBAということもあってよろしくない実装もあるかもですが、参考になれば幸いです。

参考

Excel VBA 入門講座
Excel VBA入門
VBAでIE制御ライブラリの追加 | IE操作の自動化
Excel VBA を学ぶなら moug モーグ | 即効テクニック | Tagプロパティを利用してユーザーフォームからの情報を受け取る

Rubyのinjectで配列の各要素の合計を計算する

Rubyのinjectで配列の各要素の合計を計算する方法のメモ。

Rubyで配列の合計を計算したいとなったんだけど、
ふと、いつもと違うやり方はないかな、ちょっとでも簡単に書きたいなと思って、
Rubyなら何か方法があるんじゃないのと調べてみました。

そしたら、ありました。
Enumerable#injectで配列の合計がちょっとだけスッキリ書けます。

injectで配列の値の合計を計算する

配列の合計を計算する場合は、たいていこんな感じになります。
下記は1から10までの値を合計しています。

sum = 0
(1..10).each { |i| sum += i }
p sum # 55

上記をinjectを使って書くとこうなります。
injectに渡す引数は、sumの初期値になります。つまり、sum = 0です。
引数を省略したときは、先頭の要素をsumに代入して、2番目の要素からループが開始します。
また、injectは返り値としてsumの値を返します。

(1..10).inject(0) { |sum, i| sum + i } # 55

# 引数を省略
# sum = 1として、sum + 2からループ開始
(1..10).inject { |sum, i| sum + i } # 55

ということで、injectで配列の合計の計算がちょっとだけスッキリ書けました。

injectは、引数に渡すオブジェクトの種類によって使い道がいろいろあるようですが
そのあたりはもうちょっと勉強して使ってみないとよくわかんないな。

Rubyのハッシュ作成時にデフォルト値を指定する方法が便利

Rubyでハッシュの値にハッシュや配列を持たせたい場合の便利な方法をメモ。
ハッシュの値としてハッシュを入れる場合は毎回初期化してあげないとエラーになりますが、
ハッシュの作成時にキーに対応する値が未定義の場合のデフォルト値を指定すると上手くできます。

ブロックを使ってハッシュにデフォルト値を指定する

下記のようにh[‘foo’][‘bar’]に値を代入しようとしてもエラーになります。
h[‘foo’]はnilなので、[]=メソッドがないと怒られます。

h = Hash.new
h['foo']['bar'] = 100 # NoMethodError: undefined method `[]=' for nil:NilClass

今まではエラーにならないように毎回初期化してたんだけど。

h = Hash.new
h['foo'] = Hash.new
hash['foo']['bar'] = 100
p hash # {"foo"=>{"bar"=>100}}

下記の1行目のようにすると、h[‘foo’]が未定義の場合に自動でデフォルト値を代入してくれる。
この場合は、{}(ハッシュ)が代入される。

h = Hash.new { |hash, key| hash[key] = Hash.new }
hash['foo']['bar'] = 100
p h # {"foo"=>{"bar"=>100}}

# 値があればデフォルト値は代入されない
h['hoge'] = 200
p h # {"foo"=>{"bar"=>100}, "hoge"=>200}

なかなか便利でいい感じです。
ソースコードがちょっとすっきりしました。