はじめに
if〜else
構造と、付随するコメントの書き方で、以下のようなコードを見ました。
/* --- List 1 --- */ // 20 以上の場合 if (age >= 20) { console.log("20以上です"); } // 未成年の場合 else { console.log("未成年です"); }
上記 List 1 について「コメント内容」「位置」「書き方」について思うところを書きます。
続きを読む if〜else
構造と、付随するコメントの書き方で、以下のようなコードを見ました。
/* --- List 1 --- */ // 20 以上の場合 if (age >= 20) { console.log("20以上です"); } // 未成年の場合 else { console.log("未成年です"); }
上記 List 1 について「コメント内容」「位置」「書き方」について思うところを書きます。
続きを読むC# や VB.NET で使われる XML コメントには「表」を書くことができます(まあ書いている人は見たことありませんが)。正確には「table タイプの list」でしょうか。書き方を調べてみると、噴飯ものの煩わしさです。
先日、味をしめた方法で自動化します。
続きを読む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(A1, "([A-Z])\w+")
関数の定義 =REGEXP_MATCH(引数1, 引数2, [[引数3], 引数4])
正規表現検索を行う対象の文字列を入力します。参照先のセルアドレスでも構いません。
正規表現パターンの文字列を指定します。
オプションです。指定しない場合、最初にマッチした文字列を返します。
2 を指定すれば二番目にマッチした文字列を返します。
オプションです。既定は 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() / JavaScript: match()
Missing librairies in /usr/lib on … | Apple Developer Forums
頑張らなくてもそのうち Microsoft が標準で対応しような気がしますが...。
昨今の Markdown 界はどうなっているだろうかとネットサーフィンをしていると、最近はこのようなエディタが人気らしいではないですか。
ふむふむ、これは良い WYSIWYG ですね。ていうか、ここまでする?
「Excel の表をコピーしてペーストすると自動的に Markdown の表になる」という機能に驚かされました。
これは良い。なんという逆転の発想。コピーするときに Markdown 文字列に変換するのではなく、ペーストする瞬間に処理を行う。これなら Excel だろうが Numbers だろうが Google Spreadsheet だろうが関係ありません。好きなスプレッドシートを使うことができます。もう、あっちは VBA、こっちは AppleScript で、向こうは JavaScript なんて考えなくて済みます。
何を驚いているんだい?そもそも初めから本来こうあるべきだろう?
そんなスティーブ = ジョブズの名言が聞こえてくるようです。1
クリップボードから TSV 文字列を取り出すのは pbpaste
コマンドがやってくれますから、それを加工する処理だけ、好きなスクリプト言語を使って作れます。
ファイル名を「TSVを表としてペースト.@$v.php」なんてファイル名で CotEditor のスクリプトフォルダに入れれば、「⌘ + Shift + V」で実行できます。
表計算ソフトからセル範囲を選択して「コピー」を行います。
CotEditor に戻って「⌘ + Shift + V」します。以下のようになります。
Id |Name |Price ------|----------------|------------ 1 |Apple |200 2 |Banana |150 3 |Citrus |300 九九九|ドラゴンフルーツ|価格未設定。
#!/usr/bin/php -q <?php // %%%{CotEditorXInput=Selection}%%% // %%%{CotEditorXOutput=ReplaceSelection}%%% mb_detect_order("UTF-8,SJIS,sjis-win"); // 文字列の長さを、全角:2、半角:1 で数える。 function jp_str_len($str) { $len = 0; foreach (preg_split('//u', $str, null, PREG_SPLIT_NO_EMPTY) as $chr) { if (mb_ereg_match("[ -~]", $chr)) { $len += 1; } else { $len += 2; } } return $len; } // 文字列の長さを、全角:2、半角:1 として PAD する。 function jp_str_pad($input, $pad_length, $pad_string = " ", $pad_style=STR_PAD_RIGHT) { $input_len = jp_str_len($input); $pad_len = $pad_length - $input_len; $pad = str_repeat($pad_string, $pad_len); $ret = ""; if ($pad_style === STR_PAD_RIGHT) { $ret = $input . $pad; } else { $ret = $pad . $input; } return $ret; } // ペーストボード取得 $pb = mb_convert_encoding(`pbpaste`, 'UTF-8', 'SJIS'); //$pb = `pbpaste`; // 入力チェック if (strpos($pb, "\n") === false && strpos($pb, "\t") === false) { $msg = "改行/タブのいずれも含まれていないようです。"; $title = "クリップボードの内容は TSV ではありません"; `osascript -e 'display notification "$msg" with title "$title"'`; exit(0); } // 行分割 $linesSrc = explode("\n", $pb); // 列分割 $lines = []; foreach ($linesSrc as $line) { $lines[] = explode("\t", $line); } // 列幅取得 $colWidth = []; for ($i = 0; $i < count($lines[0]); $i++) { foreach ($lines as $line) { $c = $line[$i]; $w = jp_str_len($c); $colWidth[$i] = max($w, $colWidth[$i]); } } // 配列全体 PAD 関数定義 $pad_string = ' '; $array_pad_fnc = function ($text, $cidx) use ($colWidth, &$pad_string) { $pad = jp_str_pad($text, $colWidth[$cidx], $pad_string, STR_PAD_RIGHT); return $pad; }; // 出力関数定義 function echo_markdown_line($ary) { echo implode('|', $ary) . "\n"; } // 見出し出力 $titles = array_map($array_pad_fnc, $lines[0], range(0, count($lines[0])-1)); echo_markdown_line($titles); // 水平線出力 $pad_string = '-'; $tmp = array_fill(0, count($colWidth), '-'); $hr = array_map($array_pad_fnc, $tmp, range(0, count($tmp)-1)); echo_markdown_line($hr); // データ出力 $pad_string = ' '; for ($i = 1; $i < count($lines); $i++) { $colTxt = $lines[$i]; $cols = array_map($array_pad_fnc, $lines[$i], range(0, count($lines[$i])-1)); echo_markdown_line($cols); }
以下の環境で開発・動作確認しました。
ProductName: Mac OS X ProductVersion: 10.15.7 BuildVersion: 19H1323 PHP 7.3.11 (cli) (built: Jun 5 2020 23:50:40) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.3.11, Copyright (c) 1998-2018 Zend Technologies CotEditor version: 2.7.4 Numbers.app version: 10.3.9
また、Google Spreadsheet でも確認しました。
CotEditor のスクリプトで動かした時のみ、pbpaste
の結果を SJIS
から UTF-8
に変換しないといけないのが、何となく納得できませんでした。Numbers.app 以外のアプリケーションから取得すると、また違うのかもしれません。
サポンテは CotEditor を用いて Markdown ドキュメントを書いていますが、Markdown のリンクを作るのにブックマークレットを使っていました。
ですが、いちいちアプリケーションを切り替えるのと、クリップボードを経由するのがなんとなく煩わしく感じてきました。
AppleScript を用いれば、Safari で開いているページから Markdown リンクを作成して編集中の CotEditor 書類に挿入できるはずです。
誰かが既に作っていそうですが「AppleScript Safari CotEditor Markdown link」などのキーワードで軽く検索した限りでは、CotEditor 用のスクリプトは見つかりませんでした。
しかし「アプリケーションからリンクを取得する」という点に限って言えば AppleScript はたくさんありました。
これなんかすごいですね。スゴすぎる。:Get Front Mac App Title and URL and make a Markdown Link to it
今のところ Safari だけで用は足りるのですけれども、そのうち Google Chrome も併用することになるかもしれないで、上記のスクリプトから切り出して作ってみますね。
ただ CotEditor スクリプトで動かすということは frontmost application
は CotEditor なので「二番目以降のアプリケーション」を「順番に」取得しないといけない(笑)。
意外と梃子摺った...。Swift も併用しました。ここまでやる必要があったでしょうか。
下記 Swift プログラムを .appList.swift
として、その次の AppleScript と一緒に CotEditor のスクリプトフォルダに入れてください。
起動中の、ドキュメントウィンドウを開いているアプリケーション一覧を上から順に取得する Swift スクリプトです。
AppleScript から呼び出すので、次のようにしてください。
.appList.swift
としてください。chmod a+x .appList.swift
./.appList.swift
としてアプリケーションの一覧が表示されれば OK です。#!/usr/bin/swift // Apple Swift version 5.3.2 (swiftlang-1200.0.45 clang-1200.0.32.28) // Target: x86_64-apple-darwin19.6.0 import Foundation import Cocoa // 起動中のウィンドウのプロセス一覧を取得する let options = CGWindowListOption(arrayLiteral: CGWindowListOption.excludeDesktopElements, CGWindowListOption.optionOnScreenOnly) let windowList = CGWindowListCopyWindowInfo(options, CGWindowID(0)) let windows = windowList as NSArray? as! [[String: AnyObject]] var ids = [pid_t]() for window in windows { let pid = pid_t(window[kCGWindowOwnerPID as String]! as! Int) ids.append(pid) } // 重複行を出さないための配列を初期化する var apps = [NSRunningApplication]() // プロセス ID からアプリケーション名に変換して出力していく for id in ids { // 変換 let app = NSWorkspace.shared.runningApplications.filter { (app) -> Bool in app.activationPolicy == .regular && id == app.processIdentifier }.first // 変換結果の確認 if app == nil { continue } // 既に出力済みかどうか確認する if apps.contains(app!) { continue } // アプリケーション名を出力する print(app?.bundleIdentifier ?? "") // 出力済みアプリケーションを記録しておく apps.append(app!) }
本体スクリプトです。スクリプトエディタにコピペして、スクリプトとして CotEditor のスクリプトフォルダに保存してください。ファイル名はなんでも良いです。CotEditor のスクリプトメニューのメニュー名になります。
-- 開発環境: -- AppleScript 2.7 -- CotEditor 4.0.2(457) -- macOS Catalina 10.15.7 set theApplication to "" set theText to "" set theBody to "" -- コマンドを実行して、ドキュメントウィンドウを開いているアプリケーションのリストを取得する set current_path to "" tell application "Finder" set current_path to (POSIX path of (container of (path to me) as alias)) end tell log current_path set r_path to current_path & ".appList.swift" set appListSrc to do shell script "/usr/bin/swift " & "'" & r_path & "'" set appList to paragraphs of appListSrc log appList -- 最近操作したブラウザウィンドウから、URL とタイトルを取得する repeat with theApplication in appList log (theApplication as string = "com.google.Chrome") if theApplication as string = "com.google.Chrome" then tell application id "com.google.Chrome" using terms from application "Google Chrome" set theText to title of active tab of first window set theBody to get URL of active tab of first window end using terms from end tell exit repeat else if theApplication as string = "com.apple.Safari" then tell application id "com.apple.Safari" using terms from application "Safari" set theTab to front document set theText to name of theTab set theBody to URL of theTab end using terms from end tell exit repeat end if end repeat if theBody = "" then display notification "CotEditor の次に有効なアプリケーションが対応ブラウザではないようです。" with title "ブラウザ不明" end if -- CotEditor に Markdown を挿入する tell application "CotEditor" if not (exists front document) then make new document end if set selectedText to contents of selection of front document if selectedText /= "" then set theText to selectedText end if tell front document set contents of selection to ("[" & theText & "](" & theBody & ")") end tell end tell
スクリプトを起動すると、開いているブラウザ画面でもっとも近いものからタイトルと URL を取得して CotEditor に挿入します。
CotEditor で選択中の文字列があると、それをタイトルとして使用します。
VBA を書いていると、処理対象の Selection オブジェクトが、想定しているものであるかどうか調べたいことがあります。
図形が選択されているか判定する-VarType関数・ShapeRangeプロパティ:エクセルマクロ・Excel VBAの使い方/DrawingObjectsオブジェクト
セル範囲が選択状態にあるかどうかを判定するものは見つかりませんでした。「図形が選択されていなければ」という判定では不十分の可能性があります。グラフオブジェクトかもしれませんしね。
ということで「セルまたはセル範囲が選択されている状態であるかどうか」を