正規表現でXMLの要素を取得する方法

このQ&Aのポイント
  • preg_match_all関数を使用してXML内の特定の要素を取得する正規表現を作成する方法を教えてください。
  • 質問文章に示された正規表現は、<category>タグの中の<item>要素のみを取得するため、完全な取得ができていません。
  • 質問者は、.*?の処理の意味を理解しておらず、正規表現の結果に満足していません。
回答を見る
  • ベストアンサー

preg_match_allの 正規表現

<root>  <Space>    <Id>2075028953</Id>    <Depth>2</Depth>    <Title>コンテスト</Title>  </Space>  <Category>   <Count>2</Count>   <Item>     <Id>2075028959</Id>     <Title>グルメ、ドリンク</Title>   </Item>   <Item>     <Id>2075028966</Id>     <Title>音楽</Title>   </Item>  </Category>  <Site>   <Item>     <Id>2078063954</Id>     <Title>建築</Title>   </Item>  </Site> </root> のようなXMLで、preg_match_allで<category>タグの中の複数ある<item>要素のあらゆる要素を取得する正規表現を教えてください。 次の正規表現はどこが間違っていますでしょうか。 preg_match_all('@<Category>.*?(<Item>.*?<Id>(?P<id>.*?)</Id>.*?<Title>(?P<title>.*?)</Title>.*?</Item>).*?</Category>@s', $buf,$match,PREG_SET_ORDER); print_r($match); あるサンプル紹介サイトの真似をしただけでしたのでうまくいきませんでした。<category>タグ内の中身と、最初の<item>タグの中身しか取得できません。 だいたい.*?の処理の意味自体解っておりません。 恐縮ですが、よろしくお願いします。

  • brosis
  • お礼率94% (100/106)
  • PHP
  • 回答数2
  • ありがとう数2

質問者が選んだベストアンサー

  • ベストアンサー
  • sakusaker7
  • ベストアンサー率62% (800/1280)
回答No.2

まず質問者さんに。 こういう構造をもったデータ、それも繰り返しがあるものを 解析するのに正規表現を使うのはあまり賢いやり方ではありません。 PHPにはXMLを解析するライブラリ関数がありますから、 それらを利用することを考えた方が良いと思います。 次に#1の回答者さんに。 >「.」=改行を除く全ての文字にマッチ >「*」=0回以上の繰り返し >「?」=最短マッチ s修飾子が指定されていますから、'.'のマッチ対象に改行も含まれます。 ? は 最短マッチではなく、直前のパターンの0/1回の出現です。 *? と組み合わせることによって、0回以上の、最短マッチになります。 > 正規表現内で使用するメタ文字は適切にエスケープしなければいけません。 > なので、「</Item>」ではなく「<\/Item>」にしなければいけないのです。 '/' がメタ文字であるような正規表現を使っているものはありません。 PHP: パターン構文 - Manual http://jp.php.net/manual/ja/reference.pcre.pattern.syntax.php 通常 '/' をエスケープしなければならないのは、 正規表現のリテラルを表すときのデリミタに良く使われるので、 デリミタでない'/'を埋め込むにはエスケープしなければならない という理由によります。 このデリミタを変更することのできる言語もよくあり、 preg_* もその一つです。 > preg_match_all('@<Category>.*?( 質問で提示されているこの例では、'@' がデリミタになります。 PHP: 正規表現関数(Perl 互換) - Manual http://jp.php.net/manual/ja/ref.pcre.php | この正規表現関数で使用するパターンの構文は、Perl と類似しています。 | 正規表現は、スラッシュ (/) などのデリミタで囲う必要があります。 | デリミタとしては、英数字およびバックスラッシュ(\) 以外のすべての文字を使用可能です。 結論を言うと、一つの正規表現で質問者さんの希望をかなえるのは preg_* で使える正規表現では無理です。 Perl6で採用されるものならできなくはないと思いますが。 とりあえずの策として、 preg_match_all('@<Category>.*?((?:<Item>.*?<Id>(?P<id>.*?)</Id>.*?<Title>(?P<title>.*?)</Title>(.*?)</Item>.*?)*?)</Category>@s', $xmldata, $match, PREG_SET_ORDER); この正規表現で、$match[0][1] に <Item> <Id>2075028959</Id> <Title>グルメ、ドリンク</Title> </Item> <Item> <Id>2075028966</Id> <Title>音楽</Title> </Item> という内容が取れますので、 これをさらに解析して個々の Itemを取ってください。

brosis
質問者

お礼

いつもお世話になっております。 一気に個々のId,Titleが取れるのですね。 <Item>_______</item>のところをどう()で括って表現するか、いろいろ試していました。 ((?:________.*?)*?)とは。考えても思いつかなかったと思います。 xmlライブラリ関数は、次回の機会に習得したいです。 ありがとうございました。

その他の回答 (1)

回答No.1

まず質問する前に正規表現について少しは調べた方が良いと思いますよ。 > だいたい.*?の処理の意味自体解っておりません 「.」=改行を除く全ての文字にマッチ 「*」=0回以上の繰り返し 「?」=最短マッチ 上記の意味から、例えば <Category>.*?<Item> このような正規表現の意味は 「<Category>から最初の<Item>」までがマッチします。 これが最短マッチです。 試しに「?」を抜いて試してみてください。 おそらく「<Category>から一番最後の<Item>」までがマッチしてしまうのではないでしょうか。 あと、正規表現内で使用するメタ文字は適切にエスケープしなければいけません。 なので、「</Item>」ではなく「<\/Item>」にしなければいけないのです。 このような場合は一気に全てを取得するのではなく、 正規表現を分解していって一つづつ取得していったほうが分かりやすいと思いますよ。

brosis
質問者

お礼

回答ありがとうございます。 mookun2007さまのおっしゃることはよくわかります。 ただ、自分のやりたいことが実現可能なのか?できないのであれば早く他の方法なりを見つけなければならないと思うとあせってしまうのです。 .*?については調べながら質問しました。最短マッチということでグーグル検索に引っかかったしだいです。 ご迷惑をかけまして申し訳ありません。 どうやら一気に取得するのは不可能なようですね。

関連するQ&A

  • 正規表現 preg_match_all 

    $c = preg_match_all('@href="/Top/World/Japanese/(?P<query>.*?)">(?:<b>|)(?P<cate>.*?)(?:</b>|)</a>@s', $buf,$match,PREG_SET_ORDER); print_r($match); の(?:<b>|)と(?:</b>|)の部分がまちがっているので、<b></b>タグがあってもなくてもいい場合にマッチさせることができないと思っています。 どうか教えてくださいませ。 ちなみにPHPの5.2.2です。

    • ベストアンサー
    • PHP
  • http://とhttps://のどちらでもマッチングできる正規表現

    http://とhttps://のどちらでもマッチングできる正規表現の書き方が分かりません。aタグの中のhref="のあとのURL部分にhttpでもhttpsのどちらでもOKのマッチングをかけたいです。 具体的にはhttp://www.aaa.co.jpでもhttps://www.aaa.co.jpのどちらでもpreg_match_allでマッチングをかけたいです。 $count=preg_match_all("正規表現",$URL,$maches); という感じでhttpかhttpsのURLに引っ掛けたいです。 教えてください

    • ベストアンサー
    • PHP
  • preg_matchの正規表現がうまくいかない

    たとえば、http://gehasoku.com/?p=2のソースコードには ------------------- <rdf:Description rdf:about="http://www.com/aaa.html" trackback:ping="http://www.com" dc:title="タイトル" dc:identifier="http://iii.com/bbb.html" dc:subject="ハードウェア" dc:description="1 名前:名無しさん " dc:creator="soft_net" dc:date="2012-02-21T20:05:01+09:00" /> ------------------- のような形の<rdf:Description~~~/>タグの情報ががいくつかあると思います。これらすべてを文字列として取得したくて、次のようなphpを作成しましたが、うまく動作しません。 原因はどうやらpreg_match_allの正規表現がうまくいっていないからのようですので、 <rdf:Description~~~/>を抜き出すことができる正規表現を教えてください。 自分でもここでチェックしながらやりましたが、 http://www.rider-n.sakura.ne.jp/regexp/regexp.php <rdf:Description~をマッチすることはできても、/>で閉じることができませんでした。 よろしくお願いします。 <?php $url="http://gehasoku.com/?p=2"; $html=file_get_contents($urls); preg_match_all("/<rdf:Description(.*)?\/>/",$html,$match); print_r($match); foreach($match[1] as $value){ echo $value; } ?>

    • ベストアンサー
    • PHP
  • preg_match_allで正規表現

    preg_match_allにて、次のように<img>が連続したソースから、 ※URLは全てダミーです <img src="http://a.com/a/a.jpg"><img width="10" src='http://b.com/b/b.gif' height="10"><img width="10" height="10" src='http://c.com/c/c.png"><img src=http://d.com/d/d.jpg><img src="http://e.com/hogefuga/e.jpg"> 個々のimgタグ全体と"<img~>"、 個々のsrc="~"のURLを抽出したいのですが、下記の条件でマッチさせることはできるでしょうか? 1. imgの属性位置・有無に問わず 2. 属性のくくりが、'" のどちらでも、または有無に問わず 3. src=~内に、"hogefuga"を含まないものにだけマッチ(否定形) 次のような正規表現で試したところ、 $pattern = '/<img.*?src=(\'|")?((?(?!hogefuga).)*\.(jpg|gif|png))(\'|")?.*?>/i'; 下のようにimgタグが1行づつ並んでいる場合は上手くいったのですが、連続して並んでいる場合は個々の、"<img~>"でマッチされませんでした。 ※URLは全てダミーです <img src="http://a.com/a/a.jpg"> <img width="10" src='http://b.com/b/b.gif' height="10"> <img width="10" height="10" src='http://c.com/c/c.png"> <img src=http://d.com/d/d.jpg> <img src="http://e.com/hogefuga/e.jpg"> 最終的にはsrc="~"のURLに応じて、画像をテキストリンクへ置換する、ということをしたいのですが、正規表現がよくわからず試行錯誤しています。 このような場合はどのような正規表現を書くべきなのでしょうか? どうぞよろしくお願いします。

    • ベストアンサー
    • PHP
  • PHP:preg_match_allで複数条件指定

    preg_match_allで複数条件を設定する方法についてお尋ねします。 環境 PHP:5.1.6 MySQL:5.0.77 --------ソース例------------------------------------------ <a href="1000/23">1: 【国語】接続後について (授業1) (20)</a> <a href="1000/26">2: 【数学】分数 (206)</a> --------------------------------------------------------- 上記のようなHTMLソースを分解してMySQLに登録することを考えています。 (1行目) 1000/23 | 1 | 【国語】接続後について (授業1) | 20 (2行目) 1000/26 | 2 | 【数学】分数 | 206 という形に a hrefの中身 |スレッド番号 | タイトル | コメント数 4つに分解してMySQLに登録します。 正規表現を学んで、個別にデータを取り出すことはできました。 以下に作成したソースを記載します。 $contents='<a href="1000/23">1: 【国語】接続後について (授業1) (20)</a><a href="1000/26">2: 【数学】分数 (206)</a>'; preg_match_all( '/<a href="(.*?)">/su', $contents, $match ); foreach ( $match[ 1 ] as $var ) { echo htmlspecialchars($var)."<br>"; } preg_match_all( '/">([0-9]*?):/su', $contents, $match2 ); foreach ( $match2[ 1 ] as $var2 ) { echo htmlspecialchars($var2)."■<br>"; } preg_match_all( '/[0-9]: (.*?)\([0-9]*\)<\/a>/su', $contents, $match3 ); foreach ( $match3[ 1 ] as $var3 ) { echo htmlspecialchars($var3)."■<br>"; } preg_match_all( '/\(([0-9]*?)\)</su', $contents, $match4 ); foreach ( $match4[ 1 ] as $var4 ) { echo htmlspecialchars($var4)."●<br>"; } 【実行結果】 1000/23 1000/26 1 2 【国語】接続後について (授業1) 【数学】分数 20 206 1行ずつMySQLに登録したいので preg_match_all( '/条件1,条件2,条件3,条件4/su', $contents, $match ); のような形で指定して個々の値を下記の変数に入れることは可能でしょうか? ループ処理開始{ $url $num $title $res //MySQLに接続 //データ登録 }

    • ベストアンサー
    • PHP
  • 正規表現について

    PHPにおける正規表現についての質問なのですが、 preg_match('#^/user/(?P<id>[^/]+)$#', $string) 第一引数の正規表現がいまいち理解できません。 ご教授のほど宜しく御願いします。

    • 締切済み
    • PHP
  • preg_replaceでの正規表現について

    お世話になります。 ereg_replace で書いた正規表現による置換処理を、 preg_replace に置き換えようとしています。 (preg_replaceの方が処理が速いとマニュアルにあったので) ところが、preg_replaceでの正規表現検索がうまくいかないで困っています。 基本的には、ereg_replaceの正規表現部分を//で囲んでいるだけです。 具体的には、下記のような処理を書いています。 間違いがあれば、ご指摘いただけましたら幸いです。 //### うまくいっている ereg_replace のパターン ここから ### $html=file_get_contents('template/temple.html'); //ひな形読み込み $search='\{dokuji tag\}.+\{_dokuji tag\}'; $replace=''; $html=ereg_replace($search,$replace,$html); //不要部分の削除 //### うまくいっている ereg_replace のパターン ここまで ### ↓ //### うまくいかない preg_replace のパターン ここから ### $html=file_get_contents('template/temple.html'); //ひな形読み込み $search='/\{dokuji tag\}.+\{_dokuji tag\}/m'; $replace=''; $html=preg_replace($search,$replace,$html); //不要部分の削除 //### うまくいかない preg_replace のパターン ここまで ### //### 置き換えるソース('template/temple.html') ここから ### <!-- {dokuji tag} --> <tr> <td nowrap><strong>タイトル:</strong></td> <td> <input type="text" name="name" size="80" value="{name}" /> </td> </tr> <!-- {_dokuji tag} --> //### 置き換えるソース('template/temple.html') ここまで ### よろしくお願い申し上げます。

    • ベストアンサー
    • PHP
  • 正規表現でpreg_系を使ってタグ内は置き換えないようにする方法

    正規表現でpreg_系を使って、半角スペースを&nbsp;に置き換える処理をしたのですが、タグ内のスペースも&nbs;に置き換わってしましました。 タグ内は置き換えないようにするにはどのようにしたら良いでしょうか? データは複数行渡っています。以下はタグが置き換わるので、逆にタグでないものを置き換えたいです。 <?php function test_call($matches){ return str_replace(" ","&nbsp;",$matches[0]); } $data = <<< EOT この横は変換 します。 <a href="http://www.yahoo.co.jp/" target="_blank">タグの半角スペースは変換せず、 この横は変換する。 </a> このタグも変換してはいけません。< br/> EOT; $data = preg_replace_callback('/<("[^"]*"|\'[^\']*\'|[^>])*>[ ]?/', "test_call", $data); var_dump($data); ?>

    • ベストアンサー
    • PHP
  • 正規表現の「^」と「$」がうまくいかない

    正規表現の「^」と「$」を使うとうまく働いてくれません。 あるデータの中の、行頭がhttpで始まる1行を取り出したいです。 あるデータ$dataを preg_match_all("/^http(.*?)$/",$data,$result); print_r($result); で出力すると、 ■結果 Array ( [0] => Array ( ) [1] => Array ( ) ) となり、うまくいきません。 preg_match_all("/http(.*?)\n/",$data,$result); やけでこんな感じにやったら、行頭から始まるhttp.....も行頭からはじまらないものもゲットしてすべて出力してくれてしまいます。 行頭から始まるものだけを出力するにはどうしたらいいでしょうか。 php5 UTF-8環境

    • ベストアンサー
    • PHP
  • preg_match_all関数でaタグの属性を

    PHPのpreg_match_all関数でaタグの属性hrefの値だけをすべて取得するにはどうしたらよいでしょう?? 正規表現の部分が分かりません。 お願いします。

    • ベストアンサー
    • PHP

専門家に質問してみよう