PHP: Cara mem-parsing file XML yang kompleks dan tidak tenggelam dalam kode asli

Waktu yang baik hari ini!

Cakupan format XML cukup luas. Bersama dengan CSV, JSON dan lainnya, XML adalah salah satu cara paling umum untuk menyajikan data untuk pertukaran antara berbagai layanan, program, dan situs. Sebagai contoh, kita dapat mengutip format CommerceML untuk bertukar barang dan pesanan antara 1C "Manajemen Perdagangan" dan toko online.

Oleh karena itu, hampir semua orang yang membuat layanan web dari waktu ke waktu harus berurusan dengan kebutuhan untuk mem-parsing dokumen XML. Dalam posting saya, saya mengusulkan salah satu metode tentang cara melakukan ini sejelas dan transparan mungkin menggunakan XMLReader.

PHP menawarkan beberapa cara untuk bekerja dengan format XML. Tanpa merinci, saya akan mengatakan bahwa pada prinsipnya mereka dapat dibagi menjadi dua kelompok:

  1. Memuat seluruh dokumen XML ke dalam memori sebagai objek dan bekerja dengan objek ini
  2. Langkah-demi-langkah pembacaan string XML di tingkat tag, atribut, dan konten teks

Cara pertama lebih intuitif, kode terlihat lebih transparan. Metode ini berfungsi dengan baik untuk file kecil.

Metode kedua adalah pendekatan tingkat rendah, yang memberi kita sejumlah keuntungan, dan pada saat yang sama, agak menaungi kehidupan. Mari kita bahas lebih detail. Pro:

  • Kecepatan parsing. Anda dapat membaca lebih detail di sini .
  • Konsumsi RAM lebih sedikit. Kami tidak menyimpan semua data dalam bentuk objek yang sangat mahal dalam memori.

Tetapi: kami mengorbankan keterbacaan kode. Jika tujuan parsing kami adalah, untuk menghitung jumlah nilai di tempat-tempat tertentu di dalam XML dengan struktur sederhana, maka tidak ada masalah.
Namun, jika struktur file rumit, bahkan bekerja dengan data tergantung pada path lengkap ke data ini, dan hasilnya harus mencakup banyak parameter, maka di sini kita sampai pada kode yang agak berantakan.

Jadi saya menulis kelas yang kemudian membuat hidup saya lebih mudah. Penggunaannya menyederhanakan penulisan aturan dan sangat meningkatkan keterbacaan program, ukurannya menjadi jauh lebih kecil, dan kode lebih indah.

Gagasan utamanya adalah ini: kita akan menyimpan skema XML kita dan bagaimana cara bekerja dengannya dalam satu array yang mengulang hirarki dari hanya tag yang kita butuhkan. Juga, untuk salah satu tag dalam array yang sama, kita akan dapat mendaftarkan fungsi yang kita butuhkan yaitu penangan untuk membuka tag, menutupnya, membaca atribut, atau membaca teks, atau semuanya bersama-sama. Jadi, kami menyimpan struktur XML dan penangan kami di satu tempat. Sekilas pada struktur pemrosesan kami akan cukup untuk memahami apa yang kami lakukan dengan file XML kami. Saya akan membuat reservasi bahwa pada tugas-tugas sederhana (seperti dalam contoh di bawah) keuntungan dalam keterbacaan kecil, tetapi akan terlihat jelas ketika bekerja dengan file dari struktur yang relatif kompleks - misalnya, format pertukaran dengan 1C.

Sekarang spesifik. Ini kelas kami:

Versi debug (dengan $ debug parameter):
Kelas XMLReaderStruct - klik untuk membuka
class XMLReaderStruct extends XMLReader { public function xmlStruct($xml, $structure, $encoding = null, $options = 0, $debug = false) { $this->xml($xml, $encoding, $options); $stack = array(); $node = &$structure; $skipToDepth = false; while ($this->read()) { switch ($this->nodeType) { case self::ELEMENT: if ($skipToDepth === false) { //       ,     ,  :      //  ,   ,      ,      .  //  ,    ,        . if (isset($node[$this->name])) { if ($debug) echo "[  ]: ",$this->name," -   .   .\r\n"; $stack[$this->depth] = &$node; $node = &$node[$this->name]; if (isset($node["__open"])) { if ($debug) echo "    ",$this->name," - .\r\n"; if (false === $node["__open"]()) return false; } if (isset($node["__attrs"])) { if ($debug) echo "    ",$this->name," - .\r\n"; $attrs = array(); if ($this->hasAttributes) while ($this->moveToNextAttribute()) $attrs[$this->name] = $this->value; if (false === $node["__attrs"]($attrs)) return false; } if ($this->isEmptyElement) { if ($debug) echo "  ",$this->name," .   .\r\n"; if (isset($node["__close"])) { if ($debug) echo "    ",$this->name," - .\r\n"; if (false === $node["__close"]()) return false; } $node = &$stack[$this->depth]; } } else { $skipToDepth = $this->depth; if ($debug) echo "[  ]: ",$this->name," -    .        ",$skipToDepth,".\r\n"; } } else { if ($debug) echo "(  ): ",$this->name," -    .\r\n"; } break; case self::TEXT: if ($skipToDepth === false) { if ($debug) echo "[  ]: ",$this->value," -  .\r\n"; if (isset($node["__text"])) { if ($debug) echo "    - .\r\n"; if (false === $node["__text"]($this->value)) return false; } } else { if ($debug) echo "(  ): ",$this->value," -    .\r\n"; } break; case self::END_ELEMENT: if ($skipToDepth === false) { //  $skipToDepth  ,   ,        , //       . if ($debug) echo "[  ]: ",$this->name," -   .   .\r\n"; if (isset($node["__close"])) { if ($debug) echo "    ",$this->name," - .\r\n"; if (false === $node["__close"]()) return false; } $node = &$stack[$this->depth]; } elseif ($this->depth === $skipToDepth) { //  $skipToDepth ,   ,    ,         . if ($debug) echo "[  ]: ",$this->name," -   ",$skipToDepth,".    .\r\n"; $skipToDepth = false; } else { if ($debug) echo "(  ): ",$this->name," -    .\r\n"; } break; } } return true; } } 


Versi rilis (tanpa parameter $ komentar dan komentar):
Kelas XMLReaderStruct - klik untuk membuka
 class XMLReaderStruct extends XMLReader { public function xmlStruct($xml, $structure, $encoding = null, $options = 0) { $this->xml($xml, $encoding, $options); $stack = array(); $node = &$structure; $skipToDepth = false; while ($this->read()) { switch ($this->nodeType) { case self::ELEMENT: if ($skipToDepth === false) { if (isset($node[$this->name])) { $stack[$this->depth] = &$node; $node = &$node[$this->name]; if (isset($node["__open"]) && (false === $node["__open"]())) return false; if (isset($node["__attrs"])) { $attrs = array(); if ($this->hasAttributes) while ($this->moveToNextAttribute()) $attrs[$this->name] = $this->value; if (false === $node["__attrs"]($attrs)) return false; } if ($this->isEmptyElement) { if (isset($node["__close"]) && (false === $node["__close"]())) return false; $node = &$stack[$this->depth]; } } else { $skipToDepth = $this->depth; } } break; case self::TEXT: if ($skipToDepth === false) { if (isset($node["__text"]) && (false === $node["__text"]($this->value))) return false; } break; case self::END_ELEMENT: if ($skipToDepth === false) { if (isset($node["__close"]) && (false === $node["__close"]())) return false; $node = &$stack[$this->depth]; } elseif ($this->depth === $skipToDepth) { $skipToDepth = false; } break; } } return true; } } 



Seperti yang Anda lihat, kelas kami memperluas kemampuan kelas XMLReader standar, yang kami tambahkan satu metode:

 xmlStruct($xml, $structure, $encoding = null, $options = 0, $debug = false) 

Parameter:

  • $ xml, $ encoding, $ options : seperti pada XMLReader :: xml ()
  • $ structure : array asosiatif yang sepenuhnya menjelaskan bagaimana kita harus bekerja dengan file kita. Dipahami bahwa penampilannya diketahui sebelumnya, dan kita tahu persis apa tag dan apa yang harus kita lakukan.
  • $ debug : (hanya untuk versi Debug) apakah akan menampilkan informasi debug (secara default - mati).

Argumen $ structure .

Ini adalah array asosiatif, struktur yang mengulangi hierarki tag dalam file XML, ditambah, jika perlu, masing-masing elemen struktur dapat memiliki fungsi handler (didefinisikan sebagai bidang dengan kunci yang sesuai):

  • "__open" - berfungsi saat membuka tag - function ()
  • "__attrs" - fungsi untuk memproses atribut tag (jika ada) - function ($ assocArray)
  • "__text" - berfungsi di hadapan nilai teks dari tag - function ($ text)
  • "__close" - berfungsi saat menutup tag - function ()

Jika salah satu penangan mengembalikan false, parsing akan dibatalkan, dan fungsi xmlStruct () akan mengembalikan false. Contoh berikut menunjukkan cara membuat argumen $ structure:

Contoh 1 menunjukkan urutan pemanggil dipanggil
Misalkan ada file XML:

 <?xml version="1.0" encoding="UTF-8"?> <root> <a attr_1="123" attr_2="456">Abc</a> <b> <x>This is node x inside b</x> </b> <c></c> <d> <x>This is node x inside d</x> </d> <e></e> </root> 

 $structure = array( 'root' => array( 'a' => array( "__attrs" => function($array) { echo "ATTR ARRAY IS ",json_encode($array),"\r\n"; }, "__text" => function($text) use (&$a) { echo "TEXT a {$text}\r\n"; } ), 'b' => array( "__open" => function() { echo "OPEN b\r\n"; }, "__close" => function() { echo "CLOSE b\r\n"; }, 'x' => array( "__open" => function() { echo "OPEN x\r\n"; }, "__text" => function($text) { echo "TEXT x {$text}\r\n"; }, "__close" => function() { echo "CLOSE x\r\n"; } ) ) ) ); $xmlReaderStruct->xmlStruct($xml, $structure); 

Penangan akan dipanggil (dalam urutan kronologis):

atribut akar-> a
root bidang teks-> a
membuka root-> b
membuka root-> b-> x
root teks-> b-> x
root penutup-> b-> x
root penutup-> b

Kolom yang tersisa tidak akan diproses (termasuk root-> d-> x akan diabaikan, karena berada di luar struktur)

Contoh 2 menggambarkan tugas praktis yang sederhana
Misalkan ada file XML:

 <?xml version="1.0" encoding="UTF-8"?> <shop> <record> <id>0</id> <type>product</type> <name>Some product name. ID:0</name> <qty>0</qty> <price>0</price> </record> <record> <id>1</id> <type>service</type> <name>Some product name. ID:1</name> <qty>1</qty> <price>15</price> </record> <record> <id>2</id> <type>product</type> <name>Some product name. ID:2</name> <qty>2</qty> <price>30</price> </record> <record> <id>3</id> <type>service</type> <name>Some product name. ID:3</name> <qty>3</qty> <price>45</price> </record> <record> <id>4</id> <type>product</type> <name>Some product name. ID:4</name> <qty>4</qty> <price>60</price> </record> <record> <id>5</id> <type>service</type> <name>Some product name. ID:5</name> <qty>5</qty> <price>75</price> </record> </shop> 

Ini adalah cek kasir dengan barang dan jasa.

Setiap catatan cek berisi pengidentifikasi catatan, jenis (produk "produk" atau "layanan" layanan), nama, jumlah dan harga.

Tugas: menghitung jumlah cek, tetapi secara terpisah untuk barang dan jasa.

 include_once "xmlreaderstruct.class.php"; $x = new XMLReaderStruct(); $productsSum = 0; $servicesSum = 0; $structure = array( 'shop' => array( 'record' => array( 'type' => array( "__text" => function($text) use (&$currentRecord) { $currentRecord['isService'] = $text === 'service'; } ), 'qty' => array( "__text" => function($text) use (&$currentRecord) { $currentRecord['qty'] = (int)$text; } ), 'price' => array( "__text" => function($text) use (&$currentRecord) { $currentRecord['price'] = (int)$text; } ), '__open' => function() use (&$currentRecord) { $currentRecord = array(); }, '__close' => function() use (&$currentRecord, &$productsSum, &$servicesSum) { $money = $currentRecord['qty'] * $currentRecord['price']; if ($currentRecord['isService']) $servicesSum += $money; else $productsSum += $money; } ) ) ); $x->xmlStruct(file_get_contents('example.xml'), $structure); echo 'Overal products price: ', $productsSum, ', Overal services price: ', $servicesSum; 

Source: https://habr.com/ru/post/id452648/


All Articles