RubyのREXMLで、下記のような構造のデータを、イテレータでidごとに処理したいのです;


<a id="1">
 <b>あ</b>
 <c>い</c>
 <d>
  <e>う</e>
  <f>え</f>
 </d>
</a>
<a id="2">
 <b>あ</b>
 <c>い</c>
 <d>
  <e>う</e>
  <f>え</f>
 </d>
</a>







いろいろチュートリアルを見ても、<b></b>など1種類の要素だけをイテレータで連続して取得する方法はあっても
<b>と<c>と<e>と<f>など、複数の要素を配列に入れるなどして、連続取得する方法がみつかりませんでした。

どなたか、複数の要素をイテレータで取得する方法を教えてください。


ちなみに最終的には、配列に入れたものを、
MySQLにinsertしようと考えています。

回答の条件
  • 1人2回まで
  • 登録:
  • 終了:2009/11/20 18:41:47
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

ベストアンサー

id:kn1967 No.1

回答回数2915ベストアンサー獲得回数301

ポイント200pt

サンプルを作ってみました。

# データ準備
require "rexml/document"
doc = REXML::Document.new '
<?xml version="1.0" ?>
<data>
<a id="1"><b>aaa</b><c>aab</c><d><e>aac</e><f>aad</f></d></a>
<a id="2"><b>bba</b><c>bbb</c><d><e>bbc</e><f>bbd</f></d></a>
</data>
'
# 関数
def saiki(e1, k, v)
    if e1.text then
        k.push e1.name
        v.push "'" + e1.text + "'"
    elsif e1.children then
        e1.elements.each {|e2|
            saiki(e2, k, v)
        }
    end
end
# 呼び出し
doc.elements.each("data/a") {|e|
    k = Array.new(['id'])
    v = Array.new([e.attributes['id']])
    saiki(e, k, v)
    p "INSERT INTO tablename (" + k.join(",") + ") VALUES (" + v.join(",") + ")"
}

出力結果は以下のようになります。

"INSERT INTO tablename (id,b,c,e,f) VALUES (1,'aaa','aab','aac','aad')"
"INSERT INTO tablename (id,b,c,e,f) VALUES (2,'bba','bbb','bbc','bbd')"

※さらに調べるのであれば関数の再帰呼び出しがキーワードになります。

※MySQLとの接続関連は行ってません。単純に生成したSQL文を出力します。

※当方の環境で少々文字化けを起こしたので、

 スミマセンがデータはアルファベットにしてます。

※xmlの要素が存在しなくてもINSERT文の生成を行ってしまいますので、

 実際の運用に際しては要素存在有無のチェックを入れる必要があります。

 (saiki関数から返ってきたkとvの内容をチェック)

※コードの記述方法については、これがベストとは間違っても申せません。

 処理の流れをチェックする程度にご理解ください。

id:gets_itai

サンプル、ありがとうございます! まだ実験中ですが、質問の回答は満たしていただいております! 多謝っす☆

2009/11/20 12:46:27
  • id:gets_itai
    「再帰」... 難しい...
  • id:gets_itai
    例題では、<a> には「id」という属性しかありあませんが、<a id="1" sub_id="15">のように、複数の属性が合った場合、どうすればよいのでしょうか? もしご覧になっていらっしゃいましたら、教えてください。
  • id:kn1967
    >「再帰」... 難しい...

    確かに、難しいですね・・・。
    ただ、その分、一旦抜け出すと、スッキリするんですけどね・・・。

    まずは、def saiki(e1, k, v) のすぐ後に p e1.name と入れてみてください。
    "a"
    "b"
    "c"
    "d"
    "e"
    "f"
    "INSERT INTO tablename (" + k.join(",") + ") VALUES (" + v.join(",") + ")"
    と出力されるはずです(実際には、もう少しあるけど省略)
    これにて、自分自身を呼び出して、再び自分自身に帰ってきてる(再帰)
    という動作が確認できるかと思います。

    再帰を言葉で表現するのは難しいのですが、チャレンジしてみます。
    最初に呼び出された時の e1 は a という事になるわけですが、
    (1)a にはtextが含まれておらずb/c/dのchildrenが含まれているためループ処理します。
      ループ開始
      (2a)ループ1回目 b にはtextが含まれており child はいませんので、ループに再帰します。
      (2b)ループ2回目 c にはtextが含まれており child はいませんので、ループに再帰します。
      (2c)ループ3回目 d には children がいますので、ここでさらにループを重ねます。
        ループ開始
        (3a)2重ループの1回目 e にはtextが含まれており child はいませんので、ループに再帰します。
        (3b)2重ループの2回目 f にはtextが含まれており child はいませんので、ループに再帰します。
        ループ終了
      ループ終了
    (1)関数終了
    以上のような感じになります。

    ※ちなみに、今回の関数saikiではtextの有無だけで判別してるので
    <a id="1">111<b>aaa</b><c>aab</c><d><e>aac</e><f>aad</f></d></a>
    <a id="2"><b>bba</b><c>bbb</c><d>ddd<e>bbc</e><f>bbd</f></d></a>
    のようなデータだと別の形でループを抜けてしまいます。まだまだ工夫が必要という事ですが、
    最初から複雑にしてしまうと動作すら判らなくなるので、print を適宜入れて動作を追うようにして、
    徐々に慣れていってもらうしか・・・。
  • id:kn1967
    属性は
      .attributes['名称']
    で取得できますので、例えば、
    k = Array.new(['id', 'sub_id'])
    v = Array.new([e.attributes['id'], e.attributes['sub_id']])
    という感じで取れます。
  • id:kn1967
    誤 にはtextが含まれており child はいませんので、ループに再帰します。
    正 にはtextが含まれておりますので、ループに帰ります。

    textの有無だけ判別してるんだから、おかしいよね・・・っと自分で気づいたので訂正。
  • id:gets_itai
    マトリョーシカ、みたいな感じなんですかね。。。(←って誤魔化しちゃダメですね 苦笑)


    もう1点だけ、可能であれば教えてください。

    >※xmlの要素が存在しなくてもINSERT文の生成を行ってしまいますので、
    >実際の運用に際しては要素存在有無のチェックを入れる必要があります。
    >(saiki関数から返ってきたkとvの内容をチェック)


    要素が空だった場合の例文を頂けると、大変助かります。
  • id:kn1967
    マトリョーシカ(笑)

    (例1)単純にデータ数を数える
    if k.count > 2 then
    p "INSERT INTO tablename (" + k.join(",") + ") VALUES (" + v.join(",") + ")"
    else
    p k.join(",") + " : Nothing!!"
    end
    (例2)項目毎に存在をチェックする
      if k.index("g") then
    p "INSERT INTO tablename (" + k.join(",") + ") VALUES (" + v.join(",") + ")"
    else
    p k.join(",") + " : Nothing!!"
    end
    など・・・
  • id:gets_itai
    すみません。聞き方が正しくありませんでした。

    現在のままだと、ある要素が空だった場合、 k が v のフィールド名ではなくなってしまいます。v がない場合、v に「""」など「空」だという情報を渡したいのです。。。 お時間が許しましたら、教えてください。
  • id:kn1967
    コメント欄だからインデントは消えると思うけど・・・

    空っぽの状態を返す例
    def saiki(e1, k, v)
    k.push e1.name
    if e1.text then
    v.push "'" + e1.text + "'"
    else
    v.push "''"
    end
    if e1.children then
    e1.elements.each {|e2|
    saiki(e2, k, v)
    }
    end
    end
  • id:gets_itai
    kn1967さま


    何から何まで、本当にありがとうございました。


    精進させていただきます!!

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません