ホームページ開発ツール>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

 以上を踏まえ、(残りの)仕様は以下の通りとしました。

 Xojoでの実装
【ソースコードのコピー&ペーストについて】
ソースコード(グレー背景部分の全文)をコピーし、指定のウィンドウ/クラスにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
ただし、この方法は、メソッドでは問題ないようですが、イベント/アクション/プロパティでは不安定?なので、ペーストできない場合は、各項目のカッコ内を適用して下さい。
  1. Xojoで新規プロジェクトを作成
  2. Toolbarをプロジェクトに追加(Name:Toolbar1)後、以下のアイテムを追加。その後、Window1に追加(Name:Toolbar11)。
    ToolItem1(開く)/ToolItem2(Preferencesを開く)
  3. 以下を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
    
  4. Window1に、Label2個(Name:Label1、Name:Label2)、ListBox(Name:Listbox1, ColumnCount:4, Hierarchical:オン)を追加
  5. 以下をListBox1にペースト(できなければ、Function - Endの間をListBox1の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
    
    注)言語リファレンス>ListBox>CellBackgroundPaintのサンプルを使わせて頂きました。
  6. 以下を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
    
  7. 以下を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
    
  8. 以下を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
    
  9. 以下を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
    
  10. 以下をWindow1にペースト(できなければプロパティに、名前:pPlist、データ型:Ptr、を追加)
    Private Property pPlist as Ptr
    
  11. 新規ファイルタイプ(名前は、ここではデフォルトの「FileTypes1」)を作成し、ファイルタイプを追加して、名前:PropertyList、Extensions:.plistとする。
  12. 新規モジュール(名前は、ここでは「Globals」)を作成。
  13. 以下を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
    
  14. 以下を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
    
  15. 以下を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
    
  16. 以下をGlobalsにペースト
    Public Function gMB3264s() as Integer
      #if Target32Bit
        return 4
      #endif
      #if Target64Bit
        return 8
      #endif
    End Function
    
  17. 以下を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
    
  18. 以下を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
    
  19. 以下を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
    
 実行してみたところ、plistの内容がリスト表示されることを確認しました。
S Shot1


 おわりに

 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]