ホームページ>開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでplistを読み込む
Xojo / Real Studio Trial and Error
目次
CocoaのDeclareでplistを読み込む
はじめに
以下は、Xojo Cocoaビルドについての話題です。
plistの読み込みと内容の表示を試してみました。
なお検証には、Xojo 2016 Release 3を用いています。(Mac mini mid 2010 + OS X 10.13.6 High Sierra)
方針
plistは、ご承知の通り、Xcodeで表示することができます。
作成または編集したい場合は、使い勝手を考えても、Xcodeを用いるのがベストと思われますが、単に値を見たいだけの場合には、ちょっと重た過ぎる気もします。
と言うことで、ブラウズに特化したものを作成してみます。
plistの読み込みは以前にもやりましたが、propertyListWithData:options:format:error:を用います。
これまでは限定的だったので、特定のキーのみを抽出したりしていましたが、今回は全体を扱います。
内容は、Hierarchical属性をオンにしたListBoxに表示します。
表示する項目の種別は、Xcodeに倣って、key, type, valueとします。
typeが文字列/数値/論理値の場合は、AddRowします。
DictionaryやArrayは子要素を持つのでAddFolderします。そうすることで、展開が可能になります。
この時、展開がし易いよう、ListBoxに隠し列を作って、DictionaryやArrayを指すポインターを整数化(更に文字列化)したものを格納しておきます。
また、DictionaryやArrayは、更にDictionaryやArrayを内包する場合があるので、リストの作成メソッドは再利用可能な形式とします。
数値/論理値はNSNumber型で格納されていますが、数値は元の型(int, unsigned int, double, ...)を取得して、その通り復元するものとします。
元の型はobjCTypeで取得できますが、戻り値はType Encodings形式で、これはObjective-CのランタイムAPIで使用するものと同一です。
一方、論理値については、参考サイト(1)が考察されていますが、ここではシンプルにboolValueの結果とします。
参考サイト(1):objective-cでJSON化するときはなぜNSNumber objectからBOOL型を区別できるか - konyavic/tech
参考サイト(2):Type Encodings
以上を踏まえ、(残りの)仕様は以下の通りとしました。
- 開く、は、任意の場所と、ユーザーのPreferencesフォルダの二種類とする。
- ファイル形式はバイナリとXMLの両方(上述のメソッドはデフォルトで両形式に対応)とし、どちらであるかを表示する。
- typeは、取得したままを表示する。(Xcodeは整形して、人間が読みやすくしているが、そこまではしない。)
- 対応するtypeは、__NSCFBoolean, __NSCFNumber, __NSCFString, __NSCFConstantString, __NSCFArray, __NSArrayM, __NSCFDictionary, __NSDate, __NSCFData
- __NSCFData(NSData)は、中身の仕様までは分からないので、descriptionによる記述に止める。
Xojoでの実装
【ソースコードのコピー&ペーストについて】
ソースコード(グレー背景部分の全文)をコピーし、指定のウィンドウ/クラスにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
ただし、この方法は、メソッドでは問題ないようですが、イベント/アクション/プロパティでは不安定?なので、ペーストできない場合は、各項目のカッコ内を適用して下さい。
実行してみたところ、plistの内容がリスト表示されることを確認しました。
- Xojoで新規プロジェクトを作成
- Toolbarをプロジェクトに追加(Name:Toolbar1)後、以下のアイテムを追加。その後、Window1に追加(Name:Toolbar11)。
ToolItem1(開く)/ToolItem2(Preferencesを開く)- 以下をToolbar11にペースト(できなければ、Sub - Endの間をToolbar11のActionイベントに記述)
Sub Action(item As ToolItem) Handles Action select case item.Name case "ToolItem1" OpenFile(0) case "ToolItem2" OpenFile(1) end select End Sub
- Window1に、Label2個(Name:Label1、Name:Label2)、ListBox(Name:Listbox1, ColumnCount:4, Hierarchical:オン)を追加
- 以下をListBox1にペースト(できなければ、Function - Endの間をListBox1のCellBackgroundPaintイベントに記述)
注)言語リファレンス>ListBox>CellBackgroundPaintのサンプルを使わせて頂きました。Function CellBackgroundPaint(g As Graphics, row As Integer, column As Integer) Handles CellBackgroundPaint as Boolean If row Mod 2 = 0 Then g.ForeColor = &cf3f6fA g.FillRect(0, 0, g.Width, g.Height) End If End Function
- 以下をListBox1にペースト(できなければ、Sub - Endの間をListBox1のExpandRowイベントに記述)
Sub ExpandRow(row As Integer) Handles ExpandRow Dim list As Ptr // 展開対象のリストを取得 if row=0 then list=pPlist // Root else list=gIntToPtr(Val(ListBox1.Cell(row,3))) // Sub Element end if // リストボックスにセット MakeList(row,list) End Sub
- 以下をWindow1にペースト
Private Function LoadPlist(flg As Integer, byRef fpath As String, byRef ftype As String) as Boolean // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // ファイルオープンダイアログ Dim dlg As OpenDialog = New OpenDialog dlg.Filter=FileTypes1.PropertyList if flg=1 then dlg.InitialDirectory=SpecialFolder.Preferences end if Dim f As FolderItem = dlg.ShowModal if f=nil then // キャンセルなら戻る return false end if fpath = f.NativePath // ファイルパス // plistファイルからdataを取得(バイナリ) Dim data As Ptr = NSClassFromString("NSData") // クラスメソッドなので、まずNSDataクラスを取得 Declare Function dataWithContentsOfFile Lib "Cocoa" Selector "dataWithContentsOfFile:" (receiver As Ptr, path As CFStringRef) As ptr data = dataWithContentsOfFile(data, fpath) // plist形式に変換 Dim option As Integer = 0 Dim mSize As Integer = gMB3264s() Dim format As New MemoryBlock(mSize) Dim error As New MemoryBlock(mSize) Dim pls As Ptr = NSClassFromString("NSPropertyListSerialization") // クラスメソッドなので、まずNSPropertyListSerializationクラスを取得 Declare Function propertyListWithData Lib "Cocoa" Selector "propertyListWithData:options:format:error:" (receiver As Ptr, data As Ptr, option As Integer, format As Ptr, err As Ptr) As Ptr Dim plist As Ptr = propertyListWithData(pls, data, option, format, error) Dim fmt As Integer = gMB3264v("UInt",format) Dim err As Integer = gMB3264v("UInt",error) if err <> 0 then // エラーなら戻る return false end if // フォーカスが切れても値が失われないよう、複製しておく if pPlist<>nil then // 既にplistを取得済であれば、解放 Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(pPlist) end if Declare Function mutableCopy Lib "Cocoa" Selector "mutableCopy" (receiver As Ptr) As Ptr pPlist = mutableCopy(plist) // フォーマット select case fmt case 100 // 100 = kCFPropertyListXMLFormat_v1_0 ftype="XML (v1.0)" case 200 // 200 = kCFPropertyListBinaryFormat_v1_0 ftype="Binary (v1.0)" else ftype="Unknown" end select return true End Function
- 以下をWindow1にペースト
Private Sub MakeList(row As Integer, list As Ptr) // 要素数を取得 Dim cnt As Integer = gGetCount(list) // Dictionaryなら、全てのキーを取得 Dim keyAry As Ptr if ListBox1.Cell(row,1)="__NSCFDictionary" then Declare Function allKeys Lib "Cocoa" Selector "allKeys" (receiver As Ptr) As Ptr keyAry = allKeys(list) end if // 要素数のループ Declare Function objectForKey Lib "Cocoa" Selector "objectForKey:" (receiver As Ptr, key As CFStringRef) As Ptr Declare Function boolValue Lib "Cocoa" Selector "boolValue" (receiver As Ptr) As Boolean Declare Function UTF8String Lib "Cocoa" Selector "UTF8String" (receiver As Ptr) As CString Declare Function description Lib "Cocoa" Selector "description" (receiver As Ptr) As CFStringRef Dim aKey, aTyp, aVal As String Dim pnt1 As Ptr Dim ii As Integer = 0 for i As Integer = 0 to cnt-1 // 要素の取得(Dictionaryはキー、Arrayは並び順) select case ListBox1.Cell(row,1) case "__NSCFDictionary" // Dictionary Declare Function objectAtIndex Lib "Cocoa" Selector "objectAtIndex:" (receiver As Ptr, idx As Integer) As CFStringRef aKey = objectAtIndex(keyAry, i) pnt1 = objectForKey(list, aKey) case "__NSCFArray", "__NSArrayM" // Array aKey = "" Declare Function objectAtIndex Lib "Cocoa" Selector "objectAtIndex:" (receiver As Ptr, idx As Integer) As Ptr pnt1 = objectAtIndex(list, i) end select // タイプごとに値を取得 aTyp=gGetObjType(pnt1) select case aTyp case "__NSCFBoolean" // Boolean = NSNumber aVal=if(boolValue(pnt1),"YES","NO") case "__NSCFNumber" // Number aVal=gNumberToString(pnt1) // NSNumber to String case "__NSCFString", "__NSCFConstantString" // String, Constant String aVal = UTF8String(pnt1) case "__NSCFArray", "__NSArrayM", "__NSCFDictionary" // Array, Dictionary aVal="("+Str(gGetCount(pnt1))+" item)" case "__NSDate", "__NSCFData" // Date, Data aVal = description(pnt1) else MsgBox "aTyp unknown="+aTyp end select // 行を追加 select case aTyp case "__NSCFArray", "__NSArrayM", "__NSCFDictionary" // Array, Dictionary ListBox1.AddFolder "" // サブ要素を展開できるよう、三角付き ListBox1.Cell(row+i+1,3)=Str(gPtrToInt(pnt1)) // サブ要素の展開をスムースに行うため、ポインターを保持 else ListBox1.AddRow "" // 通常の行 end select if aKey<>"" then ListBox1.Cell(row+i+1,0)=aKey // キーがあればキー else ListBox1.Cell(row+i+1,0)="item "+str(ii) // キーがなければ自動生成名 ii=i+1 end if ListBox1.Cell(row+i+1,1)=aTyp // タイプ ListBox1.Cell(row+i+1,2)=aVal // 値 next End Sub
- 以下をWindow1にペースト
Private Sub OpenFile(flg As Integer) // plistファイル読み込み Dim fpath, ftype As String if LoadPlist(flg,fpath,ftype)=false then // 中止なら戻る return end if // ファイルパスを表示 Label1.Text="パス:"+fpath // フォーマットを表示 Label2.Text="種別:"+ftype // まず全行削除 ListBox1.DeleteAllRows // Root行生成 ListBox1.AddFolder "" ListBox1.Cell(0,0)="Root" ListBox1.Cell(0,1)=gGetObjType(pPlist) ListBox1.Cell(0,2)="("+Str(gGetCount(pPlist))+" items)" // Root行を展開 ListBox1.Expanded(0)=true End Sub
- 以下をWindow1にペースト(できなければプロパティに、名前:pPlist、データ型:Ptr、を追加)
Private Property pPlist as Ptr
- 新規ファイルタイプ(名前は、ここではデフォルトの「FileTypes1」)を作成し、ファイルタイプを追加して、名前:PropertyList、Extensions:.plistとする。
- 新規モジュール(名前は、ここでは「Globals」)を作成。
- 以下をGlobalsにペースト
Public Function gGetCount(list As Ptr) as Integer // 要素数を取得 Declare Function count Lib "Cocoa" Selector "count" (receiver As Ptr) As Integer Dim cnt As Integer = count(list) // 要素数を返す return cnt End Function
- 以下をGlobalsにペースト
Public Function gGetObjType(obj As Ptr) as String // オブジェクトのクラス(=型)を返す Declare Function NSStringFromClass Lib "Cocoa" (aClass As Ptr) As CFStringRef Declare Function myClass Lib "Cocoa" Selector "class" (receiver As Ptr) As Ptr return NSStringFromClass(myClass(obj)) End Function
- 以下をGlobalsにペースト
Public Function gIntToPtr(vv As UInteger) as Ptr // UInteger to Pointer #if Target32Bit Dim m As new MemoryBlock(4) m.UInt32Value(0)=vv return m.Ptr(0) #endif #if Target64Bit Dim m As new MemoryBlock(8) m.UInt64Value(0)=vv return m.Ptr(0) #endif End Function
- 以下をGlobalsにペースト
Public Function gMB3264s() as Integer #if Target32Bit return 4 #endif #if Target64Bit return 8 #endif End Function
- 以下をGlobalsにペースト
Public Function gMB3264v(type As String, vv As MemoryBlock) as Variant select case type case "UInt" #if Target32Bit return vv.UInt32Value(0) #endif #if Target64Bit return vv.UInt64Value(0) #endif end select End Function
- 以下をGlobalsにペースト
Public Function gNumberToString(num As Ptr) as String // NSNumberが持つ型属性に従って数値化>文字列化 Declare Function objCType Lib "Cocoa" Selector "objCType" (receiver As Ptr) As CString Dim st As String = objCType(num) select case st case "q", "Q" if StrComp(st,"q",0)=0 then // 0 = Case Sensitive // long long = 64bit Declare Function longLongValue Lib "Cocoa" Selector "longLongValue" (receiver As Ptr) As Int64 return Str(longLongValue(num)) else // unsigned long long Declare Function unsignedLongLongValue Lib "Cocoa" Selector "unsignedLongLongValue" (receiver As Ptr) As UInt64 return Str(unsignedLongLongValue(num)) end if case "l", "L" if StrComp(st,"l",0)=0 then // 0 = Case Sensitive // long = 32bit Declare Function longValue Lib "Cocoa" Selector "longValue" (receiver As Ptr) As Integer return Str(longValue(num)) else // unsigned long Declare Function unsignedLongValue Lib "Cocoa" Selector "unsignedLongValue" (receiver As Ptr) As UInteger return Str(unsignedLongValue(num)) end if case "i", "I" if StrComp(st,"i",0)=0 then // 0 = Case Sensitive // int Declare Function intValue Lib "Cocoa" Selector "intValue" (receiver As Ptr) As Integer return Str(intValue(num)) else // unsigned int Declare Function unsignedIntValue Lib "Cocoa" Selector "unsignedIntValue" (receiver As Ptr) As UInteger return Str(unsignedIntValue(num)) end if case "s", "S" if StrComp(st,"s",0)=0 then // 0 = Case Sensitive // short = 16bit Declare Function shortValue Lib "Cocoa" Selector "shortValue" (receiver As Ptr) As Short return Str(shortValue(num)) else // unsigned short Declare Function unsignedShortValue Lib "Cocoa" Selector "unsignedShortValue" (receiver As Ptr) As UInt16 return Str(unsignedShortValue(num)) end if case "c", "C" if StrComp(st,"c",0)=0 then // 0 = Case Sensitive // char = 8bit Declare Function charValue Lib "Cocoa" Selector "charValue" (receiver As Ptr) As Integer return Str(charValue(num)) else // unsigned char Declare Function unsignedCharValue Lib "Cocoa" Selector "unsignedCharValue" (receiver As Ptr) As Integer return Str(unsignedCharValue(num)) end if case "d" // double Declare Function doubleValue Lib "Cocoa" Selector "doubleValue" (receiver As Ptr) As Double return Str(doubleValue(num)) case "f" // float = 32bit = Single Declare Function floatValue Lib "Cocoa" Selector "floatValue" (receiver As Ptr) As Single return Str(floatValue(num)) else // 上記以外が来たら、とりあえずdoubleValueで返すのが無難か? MsgBox "cType unknown="+st Declare Function doubleValue Lib "Cocoa" Selector "doubleValue" (receiver As Ptr) As Double return Str(doubleValue(num)) end select return "???" End Function
- 以下をGlobalsにペースト
Public Function gPtrToInt(pnt As Ptr) as UInteger // Pointer to UInteger #if Target32Bit Dim m As new MemoryBlock(4) m.Ptr(0)=pnt return m.UInt32Value(0) #endif #if Target64Bit Dim m As new MemoryBlock(8) m.Ptr(0)=pnt return m.UInt64Value(0) #endif End Function
おわりに
typeやType Encodingsは対症療法的に対応しているので、漏れがある可能性があります。
真面目に(苦笑)仕様を読み込むか、未対応を検出したら直ちに追加するか、なんらかの対処が必要かと。
お世話になったサイト
貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)
参考サイト(1):objective-cでJSON化するときはなぜNSNumber objectからBOOL型を区別できるか - konyavic/tech
参考サイト(2):Type Encodings
更新履歴
2019.08.23 新規作成
[Home] [MacSoft] [Donation] [History] [Privacy Policy] [Affiliate Policy]