公開日:2020年10月22日 最終更新日:2021年1月27日
「WXR形式のxmlファイル」とはWordPressのエクスポート機能により出力されたファイルの事です。
出力されたxmlファイルの中には、基本的に全ての投稿、全てのカテゴリーとタグ、投稿とカテゴリー、タグの関連付け、投稿毎のカスタムフィールドが含まれます。
今回はこのWXR形式のxmlファイルを別のCMSへインポートする為のコンバーターを作成する中でSimpleXML関数が一筋縄では行かなかったので、その理由と対処法です。
WXR形式のxmlファイルの中はだいたいこんな感じのフォーマットになっています。(これはpostデータの部分)今回はこれをパースします。
<item>
<title>
ここにタイトル </title>
<link>http://example.com/20201022/</link>
<pubDate>Fri, 22 May 2015 09:33:13 +0000</pubDate>
<dc:creator><![CDATA[nishioka]]></dc:creator>
<guid isPermaLink="false">https://example.com/wp/?p=1681</guid>
<description></description>
<content:encoded>
<![CDATA[
ここにコンテンツ本文が入っています。
]]> </content:encoded>
<excerpt:encoded>
<![CDATA[
ここには抜粋が入っています。
]]> </excerpt:encoded>
<wp:post_id>1681</wp:post_id>
<wp:post_date><![CDATA[2015-05-22 18:33:13]]></wp:post_date>
<wp:post_date_gmt><![CDATA[2015-05-22 09:33:13]]></wp:post_date_gmt>
<wp:comment_status><![CDATA[closed]]></wp:comment_status>
<wp:ping_status><![CDATA[closed]]></wp:ping_status>
<wp:post_name><![CDATA[mov20140717]]></wp:post_name>
<wp:status><![CDATA[publish]]></wp:status>
<wp:post_parent>0</wp:post_parent>
<wp:menu_order>0</wp:menu_order>
<wp:post_type><![CDATA[post]]></wp:post_type>
<wp:post_password><![CDATA[]]></wp:post_password>
<wp:is_sticky>0</wp:is_sticky>
<category domain="category" nicename="movie"><![CDATA[MOVIE]]></category>
<wp:postmeta>
<wp:meta_key><![CDATA[_edit_last]]></wp:meta_key>
<wp:meta_value><![CDATA[1]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key><![CDATA[_wp_old_slug]]></wp:meta_key>
<wp:meta_value><![CDATA[xxxxxxxx]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key><![CDATA[_wp_old_slug]]></wp:meta_key>
<wp:meta_value><![CDATA[zzzzzzzz]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key><![CDATA[_thumbnail_id]]></wp:meta_key>
<wp:meta_value><![CDATA[1684]]></wp:meta_value>
</wp:postmeta>
</item>
SimpleXML関数でxmlフォーマットの文章をパースする場合、次の2つの関数どちらかを使うことになると思います。
simplexml_load_file() //ファイルから直接
simplexml_load_string() //文字列としてのxmlから
今回はsimplexml_load_string関数の方を使っています。
早速実装してみます。
$xmlstr = file_get_contents($import_file_path);
$parse_data = simplexml_load_string($xmlstr ,'SimpleXMLElement', LIBXML_NOCDATA);
こんな感じで、まずはfile_get_contentsでxml文書を読み込み、続けさま、simplexml_load_stringでパースします。(file_get_contentsを使った理由は後ほど)
あとは、パースされたデータをループぶん回してそれぞれの値をいい感じに加工して、別CMSへさっと取り込み完了!!と思いきや
Warning: simplexml_load_string() [function.simplexml-load-string]: Entity: line 1: parser error : attributes construct error in...
こんな感じで、simplexml_load_string呼び出し直後にエラーとなりパース自体ができません。
この原因はsimplexml_load_stringがxml文書中に含まれる制御文字をいい感に扱ってくれないからです。
このエラーの解消は、原因が制御文字なので不要な制御文字をパース前に削除すれば大丈夫。
以下のコードは「改行(LF,CP)」以外の制御文字を半角スペースに置き換えます。
$xmlstr = preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/',' ',$xmlstr); //改行は残す
WXR形式のxmlファイルをパースする際、改行まで削除するとコンテンツ部分の改行も削除され、コンテンツの体裁が崩れますのでご注意を。
今回、事前にfile_get_contentsでファイルの内容を読み込んでからsimplexml_load_stringでパースした理由はこの対処をする為です。
気を取り直してもう一度実行。
今度はちゃんとエラーなくパースされたようですが、思っていたデータが取れていませんでした。
「タイトル」を取得したい場合は以下のコードで取得できます
// $itemはループ中に親ノードから取得
$item->title;
では「コンテンツ(本文)」を取得したい場合
// NGコード
$item->content;
上記コードでは取得できません。
そう!ネームスペース(名前空間)が定義されたノードの値はそのままで取得できないんです。
こちらの記事ではgetNamespacesメソッドを利用して、一旦名前空間のデータを取得して対象ノードのデータを取得しています。https://liginc.co.jp/programmer/archives/5317
LIGのやり方でもいいですが、私の場合は以下のコードにて
// OKコード
// String型にキャストしない場合、Object型で返されます
(String)$item->children('content',true)->encoded;
childrenメソッドで名前空間名を指定するので、わざわざgetNamespacesを使う必要はないんじゃないかと思いました。
なんか、もうちょっとサクッと進んでくれると思ってて、名前空間に関しては仕方ない気もしますが、制御文字系はライブラリ内で正しくパースできるようにして欲しいですよね。