子・孫・・・検索
以前検索窓を作りましたが、その後色々試したことを備忘録としてまとめてみました。
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専用ですがあります。