PHP: كيفية تحليل ملف XML معقد وليس الغرق في الكود الأصلي

وقت جيد من اليوم!

نطاق تنسيق XML واسع جدًا. إلى جانب CSV و JSON وغيرها ، تعد XML واحدة من أكثر الطرق شيوعًا لتقديم البيانات للتبادل بين الخدمات والبرامج والمواقع المختلفة. على سبيل المثال ، يمكننا الاستشهاد بتنسيق CommerceML لتبادل السلع والطلبات بين 1C "إدارة التجارة" ومتجر إلكتروني.

لذلك ، يجب على كل شخص يقوم بإنشاء خدمات الويب من وقت لآخر التعامل مع الحاجة إلى تحليل مستندات XML. في مشاركتي ، أقترح إحدى الطرق حول كيفية القيام بذلك بشكل واضح وشفاف قدر الإمكان باستخدام XMLReader.

يوفر PHP عدة طرق للعمل باستخدام تنسيق XML. دون الخوض في التفاصيل ، سأقول أنه من حيث المبدأ يمكن تقسيمها إلى مجموعتين:

  1. تحميل مستند XML بأكمله في الذاكرة ككائن والعمل مع هذا الكائن
  2. قراءة خطوة بخطوة لسلسلة XML على مستوى العلامات والسمات ومحتوى النص

الطريقة الأولى هي أكثر سهولة ، الرمز يبدو أكثر شفافية. هذه الطريقة تعمل بشكل جيد للملفات الصغيرة.

الطريقة الثانية هي نهج المستوى الأدنى ، الذي يعطينا عددًا من المزايا ، وفي الوقت نفسه ، يحجب الحياة إلى حد ما. دعونا نتناولها بمزيد من التفاصيل. الايجابيات:

  • تحليل السرعة. يمكنك قراءة المزيد من التفاصيل هنا .
  • استهلاك أقل من ذاكرة الوصول العشوائي. لا نقوم بتخزين جميع البيانات في شكل كائن مكلف للغاية في الذاكرة.

لكن: نحن نضحي بقراءة الكود. إذا كان الهدف من تحليلنا هو ، على سبيل المثال ، حساب مجموع القيم في أماكن معينة داخل XML باستخدام بنية بسيطة ، فلا توجد مشاكل.
ومع ذلك ، إذا كانت بنية الملف معقدة ، فحتى العمل مع البيانات يعتمد على المسار الكامل لهذه البيانات ، وينبغي أن تتضمن النتيجة العديد من المعلمات ، ثم نأتي إلى رمز غير واضح إلى حد ما.

لذلك كتبت فصلاً جعل حياتي في وقت لاحق أسهل. استخدامه يبسط كتابة القواعد ويحسن إلى حد كبير من قابلية قراءة البرامج ، يصبح حجمها أصغر عدة مرات ، والرمز أكثر جمالا.

الفكرة الرئيسية هي: سنقوم بتخزين مخطط XML الخاص بنا وكيفية التعامل معه في صفيف واحد يكرر التسلسل الهرمي للعلامات التي نحتاجها فقط. أيضًا ، بالنسبة لأي من العلامات الموجودة في نفس الصفيف ، سنكون قادرين على تسجيل الوظائف التي نحتاجها والتي هي معالجات لفتح علامة أو إغلاقها أو قراءة السمات أو قراءة النص أو الكل معًا. وبالتالي ، نقوم بتخزين بنية XML ومعالجاتها في مكان واحد. ستكون نظرة واحدة على بنية المعالجة الخاصة بنا كافية لفهم ما نقوم به مع ملف XML الخاص بنا. سأحجز أنه في المهام البسيطة (كما هو موضح في الأمثلة أدناه) تكون ميزة قابلية القراءة صغيرة ، ولكن سيكون من الواضح عند العمل مع ملفات ذات بنية معقدة نسبيًا - على سبيل المثال ، تنسيق تبادل مع 1C.

الآن التفاصيل. هنا صفنا:

إصدار تصحيح الأخطاء (مع معلمة تصحيح $):
فئة XMLReaderStruct - انقر للتوسيع
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; } } 


إصدار الإصدار (بدون المعلمة debug $ والتعليقات):
فئة XMLReaderStruct - انقر للتوسيع
 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; } } 



كما ترون ، فإن صفنا يمتد من إمكانيات فئة XMLReader القياسية ، والتي أضفنا إليها طريقة واحدة:

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

خيارات:

  • خيارات $ xml ، $ encoding ، $ : كما في XMLReader :: xml ()
  • بنية $ : صفيف ترابطي يصف تمامًا كيف ينبغي لنا التعامل مع ملفنا. من المفهوم أن مظهره معروف مسبقًا ، ونعرف تمامًا العلامات وما يجب أن نفعله.
  • $ debug : (فقط لإصدار Debug) ما إذا كان سيتم إخراج معلومات تصحيح الأخطاء (بشكل افتراضي - إيقاف).

وسيطة هيكل $ .

هذا صفيف ترابطي ، يكرر هيكله التسلسل الهرمي للعلامات في ملف XML ، بالإضافة إلى ذلك ، إذا لزم الأمر ، يمكن أن يكون لكل عنصر من عناصر الهيكل وظائف معالج (يتم تعريفها كحقول مع المفتاح المقابل):

  • "__open" - وظيفة عند فتح علامة - function ()
  • "__attrs" - وظيفة لمعالجة سمات العلامات (إن وجدت) - وظيفة ($ assocArray)
  • "__text" - وظيفة في وجود قيمة النص للعلامة - وظيفة ($ text)
  • "__close" - تعمل عند إغلاق علامة - function ()

إذا أرجعت أي من المعالجات خطأ ، فسيتم إحباط التحليل ، وستُرجع الدالة xmlStruct () خطأ. توضح الأمثلة التالية كيفية إنشاء وسيطة بنية $:

مثال 1 يوضح الترتيب الذي يتم به استدعاء المعالجات
افترض أن هناك ملف 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); 

سيتم استدعاء معالجات (بالترتيب الزمني):

سمات الجذر>
حقل النص الجذر>
فتح الجذر> ب
فتح الجذر> ب-> س
جذر النص-> ب-> س
إغلاق الجذر> ب-> س
إغلاق الجذر> ب

لن تتم معالجة الحقول المتبقية (بما في ذلك الجذر-> d-> سيتم تجاهل x ، لأنه خارج الهيكل)

مثال 2 يوضح مهمة عملية بسيطة
افترض أن هناك ملف 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> 

هذا هو التحقق من أمين الصندوق مع السلع والخدمات.

يحتوي كل سجل تحقق على معرف السجل ونوعه (المنتج "المنتج" أو الخدمة "الخدمة") والاسم والكمية والسعر.

المهمة: احسب مقدار الشيك ، ولكن بشكل منفصل للسلع والخدمات.

 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/ar452648/


All Articles