[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]
AppleScriptやJavaScript for Automation(JXA)をいじってましたが、言語そのものがあまりにも整備されていないのでC/C++に戻ってきました。
Node.jsへのJXA導入についてはなかなか意欲的な試みに思えました。ただ例とはいえ、やってみせたのがApple純正アプリのバージョン確認というのは少々インパクトに欠けます。そういうことならアプリ内のinfo.plistからC++あたりで直接読み取ればいい、と思ってしまいました。
というわけで公開されていたものを参考にC++のコードを書きました。libxml2ライブラリを使ってSafariのinfo.plist(全797行)を読み込み、CFBundleShortVersionStringキーに対する値を取り出します。意外と手間が掛かりました。
JXAを通してJavaScriptを学ぶきっかけになるかと期待したのですが、また疎遠になりそうです。
しばらくはAppleのネイティブ言語をいじる気にならなさそうです。開発リソースが削られたのかメンテナンスが行き届いていない感じがします。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
--中略--
<key>CFBundleExecutable</key>
<string>Safari</string>
<key>CFBundleGetInfoString</key>
<string>14.1.2, Copyright © 2003-2021 Apple Inc.</string>
<key>CFBundleHelpBookFolder</key>
<string>Safari.help</string>
<key>CFBundleHelpBookName</key>
<string>com.apple.Safari.help</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>com.apple.Safari</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Safari</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>14.1.2</string> ⇦ ここ
<key>CFBundleSignature</key>
<string>sfri</string>
--以下略--
#include <cppstd.h> // 自製ライブラリ
#include <libxml/xmlreader.h>
int flag;
string version_key = "CFBundleShortVersionString";
// パース中のノード名
typedef enum {
STATE_NONE,
STATE_KEY,
STATE_STRING
} parsingStatus;
// ノード処理
void processNode(xmlTextReaderPtr reader)
{
static parsingStatus state = STATE_NONE;
int nodeType;
xmlChar *name, *value;
// ノード情報の取得
nodeType = xmlTextReaderNodeType(reader);
name = xmlTextReaderName(reader);
if (!name)
name = xmlStrdup(BAD_CAST "---");
if (nodeType == XML_READER_TYPE_ELEMENT) {
if ( xmlStrcmp(name, BAD_CAST "key") == 0 ) {
state = STATE_KEY;
} else if ( xmlStrcmp(name, BAD_CAST "string") == 0 ) {
state = STATE_STRING;
}
} else if (nodeType == XML_READER_TYPE_END_ELEMENT) {
printf("-----------------------\n");
state = STATE_NONE;
} else if (nodeType == XML_READER_TYPE_TEXT) {
// テキストを取得する
value = xmlTextReaderValue(reader);
const char* value_char = (char*) value;
string value_str = (string) value_char;
if (!value)
value = xmlStrdup(BAD_CAST "---");
if ( state == STATE_KEY ) {
if ( value_str == version_key){
flag = 1;
}
} else if ( state == STATE_STRING ) {
if (flag == 1){
printf("Safari version: %s\n", value);
flag = 2;
}
}
xmlFree(value);
}
xmlFree(name);
}
int main(int argc, char *argv[])
{
xmlTextReaderPtr reader;
int ret;
// Readerの作成
reader = xmlNewTextReaderFilename("/Applications/Safari.app/Contents/Info.plist");
if ( !reader ) {
printf("Failed to open XML file.\n");
return 1;
}
// 次のノードに移動
ret = xmlTextReaderRead(reader);
while (ret == 1) {
// ノード処理
processNode(reader);
if (flag == 2){
break;
}
// 次のノードに移動
ret = xmlTextReaderRead(reader);
}
// Readerのすべてのリソースを開放
xmlFreeTextReader(reader);
if (ret == -1) {
printf("Parse error.\n");
return 1;
}
return 0;
}
# コンパイラ設定
COMPILER = clang++
DEBUG = -g
# フラグ設定
CPPFLAGS = -std=c++17
LDFLAGS = -lc++
# includeパス(-I)
INCLUDE = -I./include -I/Volumes/DATA_m1/code/cpp/mylib/include \
-I/opt/homebrew/Cellar/libxml2/2.10.3/include/libxml2
# ライブラリパス(-l)
LIBRARY_l = -lxml2
# ライブラリパス(-L)
LIBRARY_L =
# ソースファイル
SRCDIR = ./src
SRCS = $(shell find $(SRCDIR) -type f)
# オブジェクトファイル
OBJDIR = ./obj
OBJS = $(addprefix $(OBJDIR), $(patsubst ./src/%.cpp,/%.o,$(SRCS)))
# 実行ファイル
TARGETDIR = ./bin
TARGET = test
# cppファイルからoファイル作成 $<:依存ファイル
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<
# oファイルから実行ファイル作成
$(TARGET):$(OBJS)
$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LIBRARY_l) $(LIBRARY_L) $(LDFLAGS)
# コンパイル&ビルド
.PHONY:all
all: clean $(OBJS) $(TARGET)
# oファイル・実行ファイル削除
.PHONY:clean
clean:
rm -rf $(OBJS) $(TARGETDIR)/$(TARGET)
Safari version: 14.1.2