サポンテ 勉強ノート

サポンテの勉強ノート・読書メモなどを晒します。

Excel のワークシート関数で正規表現を使う【Mac版】

はじめに

 Excel のワークシート関数には正規表現が使えません。ですが、ユーザー定義関数を追加することで使うことができます。

 Windows 版には、すでにいくつかの作例があります。

 しかし上記の作例は Windows 固有のライブラリを使用するため Mac では利用できません。本記事では Mac 版を実装してみます。

 開発には Excel:mac 2011 を使用しましたが、2016 以降では別途対応が必要かもしれません。以下を参考にしてください。

実装方法

 VBA からシェルコマンドで PHP を呼び出して、PHP正規表現処理を利用しています。

 Mac は バックスラッシュ(Chr(&h5C))と円記号(Chr(%h80))が明確に分かれているのでハマりました。

 シェルの呼び出し(結果の受け取り)は以下のコードを参考にさせていただきました。

excel - VBA Shell function in Office 2011 for Mac - Stack Overflow

インストールの仕方

 まず「ユーザー定義関数とはなにか」というところから知りたい場合は > こちらをご覧ください。

 後述のソースコードを、どのように Excel に設定してユーザー定義関数として使えるようにするかについては > こちらをご覧ください。

使い方

関数 REGEXP_MATCH()

関数の記述例
=REGEXP_MATCH(A1, "([A-Z])\w+")
関数の定義
=REGEXP_MATCH(引数1, 引数2, [[引数3], 引数4])

引数

引数1:subject

 正規表現検索を行う対象の文字列を入力します。参照先のセルアドレスでも構いません。

引数2:pattern

 正規表現パターンの文字列を指定します。

引数3:subMatchIndex

 オプションです。指定しない場合、最初にマッチした文字列を返します。

 2 を指定すれば二番目にマッチした文字列を返します。

引数4:isIgnoreCase

 オプションです。既定は FALSE です。

 TRUE を指定すると、大文字小文字の違いを無視した検索を行います。

返値

 マッチした文字列を返します。

ソースコード

 開発環境は以下の通りです。

Option Explicit

Const REGEXP_MATCH_DEBUG_MODE As Boolean = False

Private Declare Function popen Lib "libc.dylib" (ByVal command As String, ByVal mode As String) As Long
Private Declare Function pclose Lib "libc.dylib" (ByVal file As Long) As Long
Private Declare Function fread Lib "libc.dylib" (ByVal outStr As String, ByVal size As Long, ByVal items As Long, ByVal stream As Long) As Long
Private Declare Function feof Lib "libc.dylib" (ByVal file As Long) As Long
Private Declare Function regex_match Lib "libc++.dylib" (ByVal basic_string As String, ByVal basic_regex As String, ByVal match_flag_type As Integer)

Public Sub RegisterREGEXP_MATCH()
'    Application.MacroOptions Macro:="REGEXP_MATCH", _
'        Description:="正規表現検索を行い、マッチした文字列を返します。", _
'        Category:="文字列操作", _
'        HelpFile:=""
    Application.Volatile True
    Application.MacroOptions _
        Macro:="REGEXP_MATCH", _
        Description:="正規表現検索を行い、マッチした文字列を返します。", _
        Category:="文字列操作"
End Sub

Public Function REGEXP_MATCH(subject As String, _
                             pattern As String, _
                    Optional subMatchIndex As Integer = -1, _
                    Optional isIgnoreCase As Boolean = False) As Variant

    Dim optionChars As String
    Dim cmd As String
    Dim result As String
    Dim exitCode As Long

    If isIgnoreCase Then
        optionChars = "i"
    End If

    If subMatchIndex < 0 Then subMatchIndex = 1

    cmd = CreateCommandString(subject, pattern, subMatchIndex - 1, optionChars)
    result = execShell(cmd, exitCode) 'ByRef exitCode

    If exitCode <> 0 Then
        REGEXP_MATCH = CVErr(xlErrNA)
        Debug.Print result
    Else
        REGEXP_MATCH = result
    End If
End Function

Private Function CreateCommandString(ByVal subject As String, ByVal pattern As String, ByVal idx As String, ByVal optionChars As String) As String
    Dim BS As String
    Dim QQ As String
    Dim SQ As String
    
    Dim s As String
    Dim p As String
    s = SubjectEscape(subject)
    p = RegexPatternEscape(pattern)
    
    BS = Chr(&H5C)
    QQ = """"
    SQ = "'"
    
    Dim cmd As String
    cmd = "php -r " & SQ & _
        "$m=" & QQ & QQ & ";" & _
        "try{" & _
        "if(false!==preg_match_all(" & QQ & "/" & p & "/" & optionChars & QQ & ", " & _
        QQ & s & QQ & ",$m,0,0)){" & _
        "echo $m[0][" & idx & "];" & _
        "}" & _
        "}catch(Exception $e){" & _
        "var_dump($e);" & _
        "exit(1);" & _
        "}" & _
        "exit(0);" & _
        SQ
    
    If REGEXP_MATCH_DEBUG_MODE Then
        cmd = cmd & " 2>&1"
        Debug.Print cmd
    End If
    
    CreateCommandString = cmd
End Function

Private Function SubjectEscape(ByVal src As String) As String
    Dim BS As String
    Dim QQ As String
    Dim SQ As String
    BS = Chr(&H5C)
    QQ = """"
    SQ = "'"
    
    Dim ret As String
    ret = src
    ret = Replace(ret, Chr(&H80), Chr(&H5C))
    ret = Replace(ret, QQ, BS & QQ)
    ret = Replace(ret, SQ, BS & SQ)
    ret = Replace(ret, vbLf, BS & "n")
    SubjectEscape = ret
End Function

Private Function RegexPatternEscape(ByVal src As String) As String
    Dim BS As String
    Dim QQ As String
    Dim SQ As String
    BS = Chr(&H5C)
    QQ = """"
    SQ = "'"
    
    Dim ret As String
    ret = src
    ret = Replace(ret, Chr(&H80), Chr(&H5C))
    ret = Replace(ret, QQ, BS & QQ)
    ret = Replace(ret, SQ, BS & SQ)
    ret = Replace(ret, vbLf, BS & "n")
    RegexPatternEscape = ret
End Function

Private Function execShell(command As String, Optional ByRef exitCode As Long) As String
    Dim file As Long
    file = popen(command, "r")

    If file = 0 Then
        Exit Function
    End If

    While feof(file) = 0
        Dim chunk As String
        Dim read As Long
        chunk = Space(50)
        read = fread(chunk, 1, Len(chunk) - 1, file)
        If read > 0 Then
            chunk = Left$(chunk, read)
            execShell = execShell & chunk
        End If
    Wend

    exitCode = pclose(file)
End Function

注意

  • マッチするものがなければ、何も返しません。
  • 深刻なエラーが起きた場合のみ、セルにはエラーが表示されます。
  • PHP の preg_match_all() を使用しています。正規表現のテストだけをしたい場合は、下記のようなサイトをご利用ください。

正規表現チェッカー PHP: preg_match() / JavaScript: match()

  • Big Sur でもし動かない場合は Microsoft が対応してくれるのを待つしかないかもしれません。

Missing librairies in /usr/lib on … | Apple Developer Forums

おわりに

  • PHP を使わずにできそうな気がするので、頑張ってみたい。
  • 頑張れば Mac / Windows 両対応のものも作れると思いますが、それは気が向いたら。

 頑張らなくてもそのうち Microsoft が標準で対応しような気がしますが...。

ルビの入力って面倒ですよね 【Markdown/HTML/CotEditorスクリプト】

はじめに

 Markdown でブログ書いていても、読み仮名は HTML で書く必要があります。読み仮名、ときどき入れたくなります。「人智アントロポゾフィー」とか。しかし、いまいち手間なんですよね。

CotEditor のスクリプトを書いた

 ブログの編集に使っている CotEditor で入力しやすいように、AppleScriptスクリプトを書きました。

 せっかくなので共有します。よろしければ CotEditor のスクリプトフォルダに入れてご利用ください。

続きを読む

Git で外部の diff ツールを使う方法のまとめ

はじめに

 Git で差分比較を行う際に、外部ツールを使う方法です。

 基本的にどんなツールも、すでに誰かが書いていることが多いので、リンクの紹介が主です。

 下記にない場合は 「git diff <ツール名>」 で検索すれば、何かしら情報が出てくるでしょう。

 Mac の場合は以下がいろいろ詳しいですね。

 参考:Mac で使える git mergetool をいろいろ試してみる - 準備編 | そんなこと覚えてない

 Merge のことはあんまり考えていません。個人開発が多いためか、あんまりコンフリクトしないので。

続きを読む

CotEditor をストップウォッチとして使う

はじめに

 時間を計測するストップウォッチですが、計測するということは即ちどこかに記録するということで、いっそそのままテキストデータになっていると嬉しい、のではないか。

 ということで、CotEditor から実行できるスクリプトとしてストップウォッチがわりに使えるものを作ってみました。

使い方

  1. CotEditor で新規ドキュメントを開きます。
  2. はじめに、ストップウォッチをスタートさせるスクリプトを実行します。
  3. 次に、ラップタイムを記録するスクリプトを実行します。

実行結果

  1. スタートのスクリプトを実行すると、CotEditor で編集中のドキュメントの末尾に「00:00.000」と出力します。
  2. ラップタイムのスクリプトを実行すると、スタートからのラップタイムを、同じ形式の文字列で出力します。やはりドキュメントの末尾です。

実行結果例

ショートカット

 それぞれのスクリプトにショートカットを割り当てたい場合は、下記のスクリプトを保存する際、ファイル名に特別な記号を付与してください。

 例えば、スタートを [Command] + [Control] + [S] にしたい場合のファイル名の例は「StopWatch_Start.@^s.php」です。「@」が [Command] に、「^」が [Control] に該当します。他のモディファイアキーは CotEditor の「ヘルプ」メニューから、「CotEditorスクリプトガイド」→「スクリプトについて」を開いていただき、「CotEditorスクリプトのファイル名規則」を参照してください。

副作用

 スタートのスクリプトを実行した時に、ユーザの「書類」フォルダの下に、テキストファイルを一つ作成します。内容はスタートした時刻文字列です。ラップのスクリプトは、このファイルを読んで差分時間をラップタイムとして出力しています。このファイルは不要になったら消してください。ファイル名は(スクリプト内に書かれていますが)「com.hatenablog.saponote.StopWatch.txt」です。

スクリプト

 以下の PHP スクリプトを、上記のように好みのファイル名で保存していただき、実行権を与えて、CotEditor のスクリプトフォルダに格納してください。

 実行権を忘れないでくださいね。

StopWatch_Start.php

#!/usr/bin/php -q
<?php
// %%%{CotEditorXOutput=AppendToAllText}%%%

echo "00:00.000\n";
file_put_contents($_SERVER['HOME'] . '/Documents/com.hatenablog.saponote.StopWatch.txt', microtime(true), 0);

StopWatch_Lap.php

#!/usr/bin/php -q
<?php
// %%%{CotEditorXOutput=AppendToAllText}%%%

$start_time = file_get_contents($_SERVER['HOME'] . '/Documents/com.hatenablog.saponote.StopWatch.txt', false);
$lap_time = microtime(true) - $start_time;

list($sec, $millisec) = explode('.', $lap_time . '');
$m = floor($sec / 60);
$s = $sec % 60;

echo str_pad($m, 2, 0, STR_PAD_LEFT) . ':' .
     str_pad($s, 2, 0, STR_PAD_LEFT) . '.' .
     substr($millisec, 0, 3) . "\n";

おわりに

 タイムキーパー的な仕事をしている人には役に立ちそうな気がしますが...そのような仕事をしていて、CotEditor を使っていて、スクリプトにも明るい人...使う人、いるのでしょうか...。

 動作を確認したサポンテの Mac では PHP のバージョンは 7.3.11 でした。

編集中のHTMLに書かれたイメージファイルをQuickLook【CotEditor】

はじめに

 html をテキストエディタで書く人って、少数派だとは思うのですが。

 以前サポンテは html を手書きしていました。html を身につければ、高価なエディタを使わずともホームページを作成できるためです。

 macOS には、QuickLook という非常に便利な機能があります。

 html 編集中に、img タグに埋め込まれているイメージファイルを、ちょこっと確認したいことがあります。Finder に戻っても良いのですが、images ディレクトリが込み入ったところにあったりすると、少々面倒です。そんなときは、編集中のエディタから直接確認できれば便利です。

 CotEditor には、編集中の書類に適用できるスクリプトを簡単に追加できる機能があります。CotEditor と QuickLook のコマンドライン版である qlmanage を AppleScript で組み合わせれば、それが実現可能です。

動作イメージ

 編集中の html(に限りませんが)書類から見た相対パス文字列を選択しスクリプトを起動することで、該当の相対パスに存在するファイルを QuickLook で表示します。確認したら Space キーで閉じることができます。

スクリプト動作イメージ

スクリプト

 一部のスクリプト呆備録様より拝借しました。

ファイル、フォルダの存在確認 - 呆備録

 下記のスクリプトを拡張子 .scpt で保存して CotEditor のスクリプトフォルダに入れれば、CotEditor から直接 QuickLook できるようなります。

-- CotEditor にアクセスする
tell application "CotEditor"
    -- CotEditor が何らかのドキュメントを開いているかどうか確認する
    if (exists front document) then
        -- CotEditor の選択テキストを取得する
        set theSelection to contents of selection of front document
        -- テキストが選択されていたことを確認する
        if theSelection is not "" then
            set dirpath to path of front document
            set dirpath to quoted form of dirpath
            -- 最前面書類のパスを取得する
            set thePath to do shell script "dirname " & dirpath
            -- パスが取得できたことを確認する
            if thePath is not "" then
                -- ファイルが存在することを確認する
                if isExistItem(thePath & "/" & theSelection) of me then
                    -- QuickLook を開く
                    do shell script "qlmanage -p " & thePath & "/" & theSelection
                end if
            end if
        end if
    end if
end tell

on isExistItem(thePosixPath)
    -- 拝借元:https://oppara.hatenadiary.org/entry/20060209/p1
    try
        set theFilePath to thePosixPath as POSIX file
        set theAliasPath to theFilePath as alias
        return true
    on error
        return false
    end try
end isExistItem

reStructuredText:CotEditor のファイルドロップ設定

はじめに

 テキストエディタ CotEditor では、エディタ画面にファイルをドラッグ&ドロップすると相対パスを自動で生成してくれる便利な機能があります。

 reStructuredText なら :doc: とか .. image:: とか入力して欲しい。ということで設定してみます。

:doc:

 これは、相対パスを入力して欲しいものの、拡張子は余分です。<<<RELATIVE-PATH-NOSUFFIX>>> のようなものがあれば良かったのですが。

 拡張子は「不要だけど、あっても良い」のでしたっけ?「あってはならない」のでしたっけ?ちょっと失念してしまいました。

:doc:`<<<FILENAME-NOSUFFIX>>> <<<<RELATIVE-PATH>>>>`

rst ファイルのドロップで :doc:`path` を挿入

.. image::

 イメージは相対パスを入力するだけですね。

.. image:: <<<RELATIVE-PATH>>>

イメージファイルのドロップで .. image:: path を挿入

キーワード

rst, Sphinx, reStructuredText, CotEditor, ドラッグ&ドロップ, doc link

Shift キーを素早く二回押すことで Clipy を起動する

f:id:saponte:20200217221911p:plain

クリップボード拡張ソフトの Clipy

Mac でも Windows でも標準では一つのアイテムしかクリップボードに格納できません。サードバーティの拡張機能を使うと、クリップボードに格納したテキストの履歴を持つことができます。サポンテはクリップボード拡張ソフトを、職場の Windows では Clibor を、自宅の Mac では Clipy を使用しています。初めて導入したのは職場の Windows の方でした。導入した途端、それ以降は仕事に欠かせないものとなりました。同じような機能を持ついくつかの選択肢がありますが、Windows 用のいくつかのものは文字エンコーディングで難があったので、最終的に Clibor に落ち着きました。自宅の Mac では OS のバージョンによってコロコロ変わっています。現在 Catalina では Clipy です。

Windows のホットキーを自宅の Mac でも

最初に導入した Windows 用のクリップボード拡張ソフトはホットキーが「Shift を素早く二回押す」というものでした。以降、使うソフトを変えても同じホットキーで起動できるように設定をしています。Windows 用のクリップボード拡張ソフトは、ホットキー設定の中にこの「Shift 二回」がたいてい入っているのですが、Mac の場合そうではありません。だいたい、モディファイアキー(修飾キー)と何かのキーの組み合わせです。職場でも自宅でも同じように使いたいため、最初に慣れてしまった「Shift 二連打」で統一したいと思います。

幸い、Mac には Karabiner-Elements がありますので設定してみます。

続きを読む