XMLノードをNSBrowserで表示

NSBrowser の情報があまり見つからなかったので、XMLデータのノード名を表示させた方法をメモっておく。

まずは、NSXMLDocument オブジェクト(インスタンス変数)を作る。ここでは、initWithContentsOfURL:options:error: でファイルから作る。initWithXMLString:options:error: で、読み込んだテキストファイルから XML 形式の文字列を作って、それを使って作ってもいい。

@xmlDoc = NSXMLDocument.alloc.initWithContentsOfURL(url,options:NSXMLDocumentTidyXML,error:nil)

NSBrowser の UI は、Xcode で置きたいビューに配置する。

配置したら、Browser を使いたいクラスで outlet (@browser) として繋ぎ、そのクラスを @browser の delegate にする。

delegate にしたクラスに用意する必要があるのは、browser:numberOfRowsInColumn:browser:willDisplayCell:atRow:column: の 2 つ。前者は、表示するカラムで用意する必要がある行数を返し、もう一つは表示するデータを返す。NSTableViewDelegate の numberOfRowsInTableView:tableView:objectValueForTableColumn:row: に相当するようもの。

ここでは、次のようなスクリプトを書いてみた。

def browser(browser,numberOfRowsInColumn:col)

if @xmlDoc

if col == 0

@xmlDoc.rootElement.childCount

else

@xmlDoc.rootElement.nodesForXPath("/#{browser.path.split("/")[0..col].join("/")}",error:nil)[0].childCount

end

else

0

end

end

def browser(browser,willDisplayCell:cell,atRow:row,column:col)

if @xmlDoc

if col == 0

item = @xmlDoc.rootElement.children[row]

cell.setLeaf(true) if item.childCount == 0

cell.setStringValue(item.name)

else

item = @xmlDoc.rootElement.nodesForXPath("/#{browser.path.split("/")[0..col].join("/")}",error:nil)[0].children[row]

cell.setLeaf(true) if item.childCount == 0

cell.setStringValue(item.name)

end

end

end

まず、browser:numberOfRowsInColumn: では、@xmlDoc が存在しなければ、0 を返し、存在すれば、行数を返すようにしてあるが、2 列(カラム)目以降はその前のカラムで選択された項目で行数が決まるので、その処理をするためにちょっと面倒臭いことしている。

col == 0 つまり、一つ目のカラムの時は、ルートの要素の子ノードの数なので、@xmlDoc.rootElement.childCount で、その数を取り出している。それ以降は、browser.path で、最後に選択した要素のパスを取り出して、一つ手前の要素までの部分を抜き出して XPath として rootElement に対し nodesForXPath でそのパスのノードを探してその子ノード数を返す。具体的には、/ で分割して、一つ手前の部分まで、つまり、col の値までを抜き出して / で結合している。さらに先頭に / をつけているのは、XPath では「//要素」でその要素を直接指定できることを利用している。最後の要素で直接指定すればいいものでもあるが、同じ要素がある場合に面倒なのでこうしてある。こうして、クリックして選択された要素の子ノード数がわかるのでそれを返している。

browser:willDisplayCell:atRow:column: では、browser:numberOfRowsInColumn: で返した子ノードそれぞれにラベルとなる物を返すのであるが、(NS)String を返すのではなく、cell の中身を返すので、cell.setStringValue() で cell に文字列を設定する。何行目かという情報は、row に入るのでそれを利用する。また、その要素の子ノードがない場合は、その次のカラムにデータを表示しないように、cell.setLeaf(true) で末端であるという情報を返している。

あと、ブラウザで cell をクリックした時に、その cell に結びつけた要素の情報を得るために、browserCellSelected: というメソッドを追加した。setSendsActionOnArrowKeys(bool) は、カーソルキーでも操作ができるようにするための設定。

def awakeFromNib

@browser.setTarget(self)

@browser.setAction("browserCellSelected:")

@browser.setSendsActionOnArrowKeys(true)

end

def browserCellSelected(sender)

end

まだまだ改善の余地はあると思うが、とりあえず動いているので、当面はこれで行く。

最後の上記のスクリプトでどんな表示になるかのサンプル。