背景

私に提示されたホスティングで小さなサービスを開始することに決めたので、そこには単一のXMLパーサーはなく、SimpleXMLもDOMXMLもなく、libxmlとxml-rpcだけであることがわかりました。 考え直すことなく、自分で書くことにしました。 複雑でないrssフィードを解析する必要があったので、xml =>配列クラスだけで十分でした。
[1]しかし、興味深い記事のために、これは明らかに十分ではなかったので、今度はSimpleXMLの代わりを書きます。 そして同時に、PHP 5の多くの興味深い機能を検討します。
問題の声明
要素へのアクセスは、クラスのプロパティへのアクセス、たとえば
$ xml-> elementとして提供され、要素の属性へのアクセスは配列として、これらの
$ xml-> element [ 'attr' ] 、属性の存在チェックも使用します
isset ()および
foreachを使用して要素を反復処理します。 それでは、始めましょう。
ちょっとした魔法?
PHP 5では、特定の「マジック」メソッドがクラスに定義され、ダブルアンダースコア「__」で始まり、特定のアクションが発生したときに呼び出されます。
[2]次のものが必要です。
- void __construct([ mixed $ args [、 $ ... ]]) - new演算子でクラスを作成した後に呼び出される最も有名な魔法のメソッド。
- 混合 __get( $ name ) -対応するフィールドが見つからなかった場合、クラスプロパティにアクセスするときに呼び出されます。たとえば、 $ obj->要素は、 要素がクラスフィールドとして宣言されなかった場合に__get( 'element' )を呼び出します。
- void __set( $ name 、 $ value ) -クラスプロパティが変更されたときにそれぞれ呼び出されます。たとえば、 $ obj-> element = $ some_varは__set( 'element' 、 $ some_var )を呼び出します。
- string __toString() -クラスの操作中に呼び出されます。たとえば、文字列の場合と同様に、 echo $ objまたはstrval( $ obj )とします。 要素のコンテンツを取得するには、このメソッドが必要です。 残念ながら、文字列を返すメソッドはありません。そのため、要素を数値に変換するには、 intval(strval( $ obj ))を実行する必要があります。
SPL
標準PHPライブラリ-C ++の世界の
STLのような標準PHPライブラリは、典型的な問題を解決するための開発者ツールを提供するために作成されました。
[3]次のインターフェイスを実装する必要があります。
- ArrayAccess-クラスに配列としてアクセスするには、たとえば$ obj [ 'name' ]またはisset( $ obj [ 'name' ]) 。
- IteratorAggregate- foreachを使用してクラスを反復処理する可能性。
- カウント可能-要素の子の数を調べます。
XMLとexpat
これらは、XMLを操作し、XMLパーサーを作成するための標準ライブラリです。
[4]問題を解決するために必要なもの。 興味を引くために、XMLファイルの解析を手動で、たとえば正規表現で書くことができます。
expatで最も重要なのは、次の関数です。
- bool xml_set_element_handler( resource $ parser 、 callback $ start_element_handler 、 callback $ end_element_handler ) -開始タグと終了タグがそれぞれ見つかったときに呼び出される関数を設定します。
- bool xml_set_character_data_handler( resource $ parser 、 callback $ handler ) -関数を呼び出して、要素のシンボリックなコンテンツを渡し、そこに何もなかったとしても呼び出されます。
注:phpの
コールバックは、文字列または2つの値を持つ配列として渡される関数の名前です-最初はクラスの名前で、2番目はこのクラスのメソッドの名前です。
ポインタ
PHPのポインターは、CまたはC ++のようには機能しません。
[5]実際、
$ a =&
$ bの構築は
、 $ aが$ bと同じデータを持つエリアを指し、
$ bが
$ aを指すアドレスを変更すること
は不可能であることを意味するだけであり、アドレスの変更には、1レベルのネストがあります。
5番目のバージョンから、PHPではすべての変数がポインターによって関数に渡されますが、値を変更するとすぐに、新しい変数にメモリが割り当てられます。 この場合、ポインターは親要素を指すのに便利です。
コーディング
理論が完了したら、パーサーの作成に直接進みましょう。
各オブジェクトは1つのxml要素を表すため、タグ名、属性、データ、親への参照、子孫を持つ配列などのプロパティが必要になります。さらに、現在の要素へのポインタ変数が必要になります。 メソッドのうち、すべてのインターフェイスを実装し、子孫を追加し、親への参照を設定し、要素のコンテンツとパーサーに必要な3つの関数(タグの開閉と要素のコンテンツの受信)を割り当てる必要があります。
将来のクラスをスケッチしましょう:
クラス XMLはArrayAccess 、 IteratorAggregate 、 Countable {
プライベート$ポインター ;
private $ tagName ;
private $ attributes = array();
プライベート$ cdata ;
プライベート$親 ;
private $ childs = array();
パブリック 関数 __construct ( $ data ){}
パブリック 関数 __toString (){return; }
パブリック 関数 __get ( $ name ){return; }
パブリック 関数 offsetGet ( $ offset ){return; }
パブリック 関数 offsetExists ( $ offset ){return; }
パブリック 関数 offsetSet ( $ offset 、 $ value ){return; }
パブリック 関数 offsetUnset ( $ offset ){return; }
パブリック 関数 count (){return; }
パブリック 関数 getIterator (){return; }
パブリック 関数 appendChild ( $ tag 、 $ attributes ){return; }
パブリック 関数 setParent ( XML $ parent ){}
パブリック 関数 getParent (){return; }
パブリック 関数 setCData ( $ cdata ){}
プライベート 関数 解析 ( $ data ){}
プライベート 関数 tag_open ( $ parser 、 $ tag 、 $ attributes ){}
プライベート 関数 cdata ( $ parser 、 $ cdata ){}
プライベート 関数 tag_close ( $ parser 、 $ tag ){}
}
それでは、関数の実装に取り掛かりましょう。 順番に、コンストラクターから始めましょう。 私たちの場合、PHPの異なるパラメータを持つ1つのメソッドのオーバーロードがないため、文字列(xml)または2つの要素の配列(要素名、属性)の2種類の値を取ることができます-型を手動で確認する必要があります
パブリック 関数 __construct ( $ data ){
if( is_array ( $ data )){
list( $ this- > tagName 、 $ this- > attributes )= $ data ;
} else if( is_string ( $ data ))
$ this- > parse ( $ data );
}
既に述べたように、
__ toString()マジックメソッドの助けを借りて、ユーザーは要素データを文字列として取得し、それを必要な型に変換することができます。
同時に、次のマジックメソッド
__get( $ name )を分析し、それを使用して現在の要素の子孫にアクセスします。 子孫が1つしかない場合は、配列の0インデックスにアクセスする必要なく、すぐに子孫を返すのが論理的です。 例:
$ xml-> rss-> channel-> item [ 5 ]-> urlの代わりに、
$ xml-> rss [ 0 ]-> channel [ 0 ]-> item [ 5 ]-> url [ 0 ]の場合、要素rss、channel、およびurlは、ネストレベルで単一のインスタンスに存在します。
パブリック 関数 __toString (){
$ this- > cdataを 返し ます 。
}
パブリック 関数 __get ( $ name ){
if(isset( $ this- > childs [ $ name ])){
if( count ( $ this- > childs [ $ name ])== 1 )
return this this- > childs [ $ name ] [ 0 ];
他に
return $ this- > childs [ $ name ];
}
新しい 例外を スロー ( 「UFOが[$ name]を盗む!」 );
}
offsetGet 、
offsetExists 、
offsetSet、および
offsetUnset関数は、
ArrayAccessインターフェイスを実装して、オブジェクトを配列としてアクセスします。 これを使用して、要素の属性にアクセスします。
offsetSetと
offsetUnsetは、今のところスタブとして残されます。
パブリック 関数 offsetGet ( $ offset ){
if(isset( $ this- > attributes [ $ offset ]))
$ this- > attributes [ $ offset ];を 返し ます。
新しい 例外を スロー ( 「聖なる牛![$オフセット]属性はありません!」 );
}
パブリック 関数 offsetExists ( $ offset ){
return isset( $ this- > attributes [ $ offset ]);
}
そして今、私たちは最近の決定により問題に直面しています。 突然単一の要素で
foreachサイクルを開始したい場合、xmlオブジェクト自体で開始します! したがって、要素属性に
foreachを使用し、
getAttributes()メソッドを実装するには、
簡単な方法で機会を犠牲にする必要があります。 そして、呼び出された要素が属する要素の配列のイテレータと要素数を返します。親がない場合は、現在の要素の配列に対するイテレータを返します。 したがって、
IteratorAggregateおよび
Countableインターフェースが実装されます。
パブリック 関数 カウント (){
if( $ this- > parent != null )
return count ( $ this- > parent- > childs [ $ this- > tagName ]);
1を 返し ます。
}
パブリック 関数 getIterator (){
if( $ this- > parent != null )
新しい ArrayIteratorを返す ( $ this- > parent- > childs [ $ this- > tagName ]);
新しい ArrayIterator (array( $ this ));
}
子孫の追加は単純な関数です;唯一の興味深い点は、要素を追加した後、それへのリンクを返すことです。
パブリック 関数 appendChild ( $タグ 、 $属性 ){
$ element = new XML (array( $ tag 、 $ attributes ));
$要素 -> setParent ( $ this );
$ this- > childs [ $ tag ] [] = $ element ;
$要素を 返し ます 。
}
次に、パーサー自体を実装します。 ツリー構造を作成するには、現在の要素へのポインターを使用します。 最初は、現在の要素、タグが開かれたとき、開いた要素に直接インストールされ、その中に含まれるすべての要素がその子孫に追加され、タグが閉じられたときに親要素に追加されます。
プライベート 関数 parse ( $ data ){
$ this- > pointer =& $ this ;
$パーサー = xml_parser_create ();
xml_set_object ( $ parser 、 $ this );
xml_parser_set_option ( $ parser 、 XML_OPTION_CASE_FOLDING 、 false );
xml_set_element_handler ( $ parser 、 "tag_open" 、 "tag_close" );
xml_set_character_data_handler ( $ parser 、 "cdata" );
xml_parse ( $ parser 、 $ data );
}
プライベート 関数 tag_open ( $ parser 、 $ tag 、 $ attributes ){
$ this- > pointer =& $ this- > pointer- > appendChild ( $ tag 、 $ attributes );
}
プライベート 関数 cdata ( $ parser 、 $ cdata ){
$ this- > ポインター -> setCData ( $ cdata );
}
プライベート 関数 tag_close ( $ parser 、 $ tag ){
$ this- > pointer =& $ this- > pointer- > getParent ();
}
それだけです パーサーの準備が整いました。 記事をこれ以上膨らませないために、Googleドキュメントに関する
コメント と使用例も含め
て 、
完全なソースコードをアップロードしました。
[6]次は?
これは、まだSimpleXMLの完全な代替ではありません;パーサーは、その中のデータからxmlドキュメントを作成する方法をまだ知りません。 必要な機能を追加するのは難しい作業ではないので、宿題のやり方に興味がある人に任せます。
参照資料
1)
xml => array parserの最初のバージョン。
2)
マジックメソッドに関するドキュメント(eng)(
rus )。
3)
SPLドキュメント 。
4)
xmlパーサーの機能の説明。
5)
サイン (eng)
による文書化 (
rus )。
6)
パーサーの
最終バージョンと簡単な
使用例 。