На работе попросили провести исследование какими средствами лучше разбирать объёмный XML файл.
Все знают что конечно же это XMLReader, но вдруг кто-то и не знает 🙂
В итоге получили множество вариантов:
1. Domit XML Class
2. Simple XML
3. DOM
4. xml_parser (SAX)
5. XMLReader
Domit XML Class
Минусы: работает очень медленно, собирает весь файл в память, дерево составляется в отдельных массив ( в итоге ошибка allowed memory size неизбежна)
Плюсы: распространённость (множество CMS, включая Joomle <= 2.5 включают его в набор), простота работы (на выходе объект)
Simple XML
Минусы: работает сбором всего файла (см. domit)
Плюсы: поддержка php 4
DOM
Минусы: см. simple xml
Плюсы: всем привычный DOM J
Предыдущие 3 нам не подходят из-за работы с целым файлом, т.к. файлы у нас бывают по 20-30 Mb, и во время работы с ними некоторые блоки образуют цепочку (массив) в 100> Mb
Теперь остановимся на следующих: xml_parser и XMLReader.
Оба способа работают чтением файла построчно что подходит идеально для поставленной задачи.
Разница между xml_parser и XMLReader в том что, в первом случае вам нужно будет писать собственные функции которые будут реагировать на начало и конец тэга.
Проще говоря, xml_parser работает через 2 триггера – тэг открыт, тэг закрыт. Его не волнует что там идёт дальше, какие данные используются и т.д. Для работы вы задаёте 2 триггера указывающие на функции обработки.
В XMLReader всё проще. Во первых, это класс. Все триггеры уже заданы константами (их всего 17), чтение осуществляется функцией read() которая читает первое вхождение подходящее под заданные триггеры. Далее мы получаем объект в который ханосится тип данны (аля триггер), название тэга, его значение. Также XMLReader отлично работает с аттрибутами тэгов.
<? Class QuizXMLReader{ var $reader; var $tag; function QuizXMLReader(){ $this->reader = new XMLReader(); } // read while result is whitespace function ignore_whitespace() { while ( $this->reader->nodeType == XMLReader::SIGNIFICANT_WHITESPACE ) $this->reader->read(); } // if $ignoreDepth == 1 then will parse just first level, else parse 2th level too /* * Example: * parseBlock('quiz_question'): * * <quiz_question> <question_text><![CDATA[]]></question_text> <question_image><![CDATA[]]></question_image> <question_info> <question_date></question_date> <question_rating></question_rating> </question_info> </quiz_question> $ignoreDepth = 1 return array( [question_text] [question_image] ) $ignoreDepth = 0 return array( [question_text] [question_image] [question_info] = array ( [question_date] [question_rating] ) ) */ function parseBlock( $name, $ignoreDepth = 1 ) { if ( $this->reader->name == $name && $this->reader->nodeType == XMLReader::ELEMENT ) { $result = array(); while ( !($this->reader->name == $name && $this->reader->nodeType == XMLReader::END_ELEMENT) ) { //echo $this->reader->name. ' - '.$this->reader->nodeType." - ".$this->reader->depth."\n"; switch($this->reader->nodeType){ case 1: if ( $this->reader->depth > 3 && !$ignoreDepth ) { $result[ $nodeName ] = ( isset($result[ $nodeName ]) ? $result[ $nodeName ] : array() ); while ( !($this->reader->name == $nodeName && $this->reader->nodeType == XMLReader::END_ELEMENT) ) { $resultSubBlock = $this->parseBlock($this->reader->name, 1); if ( !empty($resultSubBlock) ) $result[ $nodeName ][] = $resultSubBlock; unset($resultSubBlock); $this->reader->read(); } } $nodeName = $this->reader->name; if($this->reader->hasAttributes){ $attributeCount = $this->reader->attributeCount; for($i = 0; $i < $attributeCount; $i++){ $this->reader->moveToAttributeNo($i); $result['attr'][$this->reader->name] = $this->reader->value; } $this->reader->moveToElement(); } break; case 3: case 4: $result[$nodeName] = $this->reader->value; $this->reader->read(); break; } $this->reader->read(); } return $result; } } function quiz_categories() { if ( $this->reader->name == 'quiz_categories' ) { // while not found end tag read blocks while ( !($this->reader->name == 'quiz_categories' && $this->reader->nodeType == XMLReader::END_ELEMENT) ) { $quiz_category = $this->parseBlock('quiz_category'); //DO SOMETHING unset($quiz_category); $this->reader->read(); } $this->reader->read(); } } function quiz_certificates() { if ( $this->reader->name == 'quiz_certificates' ) { $quiz_certificates = $this->parseBlock('quiz_certificates'); //DO SOMETHING } } function parse($filename) { if(!$filename) return array(); $this->reader = new XMLReader(); $this->reader->open($filename); // begin read XML while( $this->reader->read() ) { $this->quiz_categories(); $this->quiz_certificates(); } // while } // func } $xmlr = new QuizXMLReader(); $r = $xmlr->parse('export.xml');
Согласно найденным бенчмаркам с большими файлами XMLReader справляется лучше чем xml_parser.
Требования к xml_reader:
PHP 5.0 >
libxml
Требования к XMLReader:
PHP 5.1
libxml
P.S. Советы и комментарии с удовольствием выслушаю. Прошу сильно не пинать
ссылка на оригинал статьи https://habrahabr.ru/post/283048/
Добавить комментарий