XML 再び検索 -LiveCode備忘録

子・孫・・・検索

以前検索窓を作りましたが、その後色々試したことを備忘録としてまとめてみました。
LiveCodeでも find コマンドはハイパーカードと同じように使えるようですが、フィールドの内容に対してしか検索できません。メモリー上にデータを展開しているXMLデータに対して検索をかける場合どうしたらいいのか考えてみました。

on mouseUp pButtonNumber
   
   get  field "IDfield"--XMLデータのIDを得る
   put it into gDenTree
   put empty into field kensakuKekka
   
   get fld KensakuFld
   put it into tStext
   if it is empty then exit mouseUp
   
   put textEncode(tStext,"utf8")into uStext--日本語はエンコードしておく
   put revXMLRootNode(gDenTree)into tRoot--ルートノードを得る
   
   put revXMLNumberOfChildren(gDenTree, "/",  , 0) into tNum--子の数を数える
   put revXMLChildNames(gDenTree, "/", return, , true, ) into tFChild--子の名前をリストアップしている
   
   repeat with x=1 to tNum--子の数だけ繰り返す
      put line x of tFChild into tFChiledNo
      put tRoot&"/"&tFChiledNo into tNode
      
      put revXMLChildContents(gDenTree, tNode, tab, return, true, 3) into kotae--孫の内容をリストアップしている
      put textDecode(kotae,"utf8")into uKotae--デコードする
      
      if uKotae contains tStext then--検索ワードが混じっているか調べる
         get line 1 of uKotae
         put it & return  after field kensakuKekka
      end if
      
   end repeat
   
   if fld kensakuKekka is empty
   then
      put "ありませんでした"into field kensakukekka
   end if
   
end mouseUp

カードには、IDナンバーを入れておくIDfield と検索語を入れるKensakuFld、検索結果を入れるkensakuKekkaの三つのフィールドと左のスクリプトを書き込むボタンを用意しました。
対象にするXMLデータはbookstore.xmlです。

はじめにXMLデータのIDナンバーを取得して、検索結果fldを空にしています。次に検索語を取り込んで、もし空ならば処理終了。今は”2003″を検索語として入れてあるとします。
取り込んだ日本語はutf8にエンコードします。
8行目 ルートノードを得ます。自作のXMLデータならばルートノードが分かっている場合も多いかと思いますが、こうすることで汎用性が確保できます。サブルーチン化する時にもこうしておいた方が便利ですね。
9行目 子の数を数えます。
tNum=4
10行目 子の名前をリストアップします。tFChildの内容は

book[1]
book[2]
book[3]
book[4]

11行目 リピートループに入ります。子供の数だけ繰り返します。
12行目 上のtFChild から順に一行ずつ取り出します。
13行目 それをルートノードと合わせて取り出すデータのアドレスを用意します。その内容が孫の内容です。
14行目 孫の内容をリストアップします。Kotaeの内容は

title[1]	Everyday Italian
author[1]	Giada De Laurentiis
year[1]	2005
price[1]	30.00

15行目でデコードしていますが、このサンプルファイルは2バイト文字を含まないので関係ないですね。
16行目 上のKotaeの内容に検索語”2003″が含まれているかどうかをみています。含まれていなければ次のリピートへ。含まれていれば、このKotaeの1行目と改行をフィールドkensakuKekkaへ収めます。そして次のリピートへ。
最後まで行ってkensakuKekkaが空ならば”ありませんでした”のメッセージを表示します。今回の結果では、

title[1]	XQuery Kick Start
title[1]	Learning XML

と、なりました。
ここで、get line 1 of uKotae
get line x of tFChild とすれば、子のアドレスがはいりますから、結果は

book[3]
book[4]

となります。このアドレスの属性(category)を求めれば、本の分類が得られるわけです。

全て丸ごと検索する

on mouseUp pButtonNumber
   
   get  field "IDfield"
   put it into gDenTree
   put empty into field kensakuKekka
   
   get fld KensakuFld
   put it into tStext
   if it is empty then exit mouseUp
   
   put textEncode(tStext,"utf8")into uStext--日本語はエンコードしておく
   put revXMLRootNode(gDenTree)into tRoot--ルートノードを得る
  
   put revXMLChildContents(gDenTree, "/", tab, return, full, -1) into kotae--孫の内容をリストアップしている
   
   put 0 into ansLine--変数を初期化
   
   repeat
 
      put lineOffset(uStext, Kotae,ansLine)into ansLine2
      if ansLIne2 is 0 then exit repeat
      put ansLine + ansLine2 into ansLine
      put line ansLine of Kotae & return after Kekka
      
   end repeat
  put textDecode(Kekka,"utf8") into fld kensakuKekka
   
   if fld kensakuKekka is empty
   then
      put "ありませんでした"into field kensakuKekka
   end if
   
end mouseUp

前の方法では、ターゲットとする階層を絞って検索をしていましたが、実はもっと簡単に全体を相手に検索することも出来ます。
今度のスクリプトでは、8行目のルートノードを得るところまでは一緒です。
その次、revXMLChildContentsで、ルート以下の全ての内容を取り出しています。この中に探すものがあるかどうかを調べようというわけです。
下が、kotaeの内容

/bookstore/book[1]	
/bookstore/book[1]/title	Everyday Italian
/bookstore/book[1]/author	Giada De Laurentiis
/bookstore/book[1]/year	2005
/bookstore/book[1]/price	30.00
/bookstore/book[2]	
/bookstore/book[2]/title	Harry Potter
/bookstore/book[2]/author	J K. Rowling
/bookstore/book[2]/year	2005
/bookstore/book[2]/price	29.99
/bookstore/book[3]	
/bookstore/book[3]/title	XQuery Kick Start
/bookstore/book[3]/author[1]	James McGovern
/bookstore/book[3]/author[2]	Per Bothner
/bookstore/book[3]/author[3]	Kurt Cagle
/bookstore/book[3]/author[4]	James Linn
/bookstore/book[3]/author[5]	Vaidyanathan Nagarajan
/bookstore/book[3]/year	2003
/bookstore/book[3]/price	49.99
/bookstore/book[4]	
/bookstore/book[4]/title	Learning XML
/bookstore/book[4]/author	Erik T. Ray
/bookstore/book[4]/year	2003
/bookstore/book[4]/price	39,95

11行目で、変数 ansLineに0を入れて初期化します。
12行目からリピートループ。lineOffset関数で何行目に検索語があるか調べます。検索語がないと関数は0を返しますので、青文字のループから脱出する一文を入れておきます。これを忘れるとアプリはフリーズしますので要注意です。あとはちょっとした工夫で、得られた行数をフィールドに収めた後、その行数から検索をスタートして、次は前の行数に新しく得られた行数を足した行をフィールドに足していくという作業を関数が0を返すまで繰り返します。
下がfld kensakuKekkaに入る内容です。上のkotaeの内容から該当する行を書き出しています。

/bookstore/book[1]/year	2005
/bookstore/book[2]/year	2005

完全一致で検索

on mouseUp pButtonNumber
   
   get  field "IDfield"
   put it into gDenTree
   put empty into field kensakuKekka
   
   get fld KensakuFld
   put it into tStext
   if it is empty then exit mouseUp
   
   put textEncode(tStext,"utf8")into uStext--日本語はエンコードしておく
   put revXMLRootNode(gDenTree)into tRoot--ルートノードを得る
   put revXMLFirstChild(gDenTree, tRoot,) into       tFChildNode--ここからの5行で第1子を得る
   set the itemDel to slash
   put last item of tFChildNode into tFChildNode 
   set the itemDel to "["
   put item 1 of tFChildNode into tFChildNode -- 第1子のノード名を得る。これもエンコードの必要なし

  put revXMLNumberOfChildren(gDenTree, tRoot,  , 0) into tNum--子の数を数える

repeat with x=1 to tNum--子の数だけ繰り返す
      put tRoot&"/"&tFChildNode&"["&x&"]" into tNode
      
      put revXMLChildContents(gDenTree, tNode, tab, return, true, 3)into KenKotae--子の孫一覧を得る
      
      if  uStext is among the words of KenKotae then --検索ワードが混じっているか調べる。
         
         put (tRoot&"/"&tFChildNode&"["&x&"]")& return after Kekka--あれば変数へ格納
         
      end if
   end repeat
  put textDecode(Kekka,"utf8") into fld kensakuKekka
   
   if fld kensakuKekka is empty
   then
      put "ありませんでした"into field kensakuKekka
   end if
   
end mouseUp

最初から8行目までは上と同じです。
そのあと、第1子のノード名をスクリプトで得ています。
このやり方はLiveCodeのフォーラムに載っていたものです。
こんどのやり方では、is amongを使います。まず子の持つ孫を取り出します。下が一つ目のKenKotaeの内容。

title[1]	Everyday Italian
author[1]	Giada De Laurentiis
year[1]	2005
price[1]	30.00

ここまでは一番上のcontains を使った時と同じ。この中に検索語があるかどうか調べます。
uStext is among the words of KenKotae
もしあればこの時の行を変数に格納します。
最後に変数の中身をデコードして、フィールドに表示します。
下が結果です。

bookstore/book[1]
bookstore/book[2]

こちらの方がそのまま次の用途に使えますので有利かと思いますが、
is among関数は完全一致で検索している様です。例えば、「every」という言葉で検索してみます。
containを使う場合の答え

title[1]	Everyday Italian

  部分一致で返ってきます。
lineOffsetを使った場合の答え

/bookstore/book[1]/title	Everyday Italian

  これも部分一致で返ってきます。
is amongを使った場合の答え

ありませんでした

部分一致ではだめな様です。検索語を「Everyday」とした時には

bookstore/book[1]

が返ってきます。
ここで、wholeMatches プロパティを使って、

 set the wholeMatches to true
put lineOffset(uStext, Kotae,ansLine)into ansLine2

とすると、is amongと同じように完全一致での検索になります。
これは、wordOffset,lineOffset,itemOffsetの場合だけのようですね。

これら、3つの検索方法の他に、属性で検索するやり方がXML専用ですがあります。

タイトルとURLをコピーしました