Как обновить SimpleXMLElement с помощью массива

У меня есть этот xml, который работает для Flash Encoder:

<?xml version="1.0" encoding="UTF-8"?> <flashmedialiveencoder_profile> <preset> <name></name> <description></description> </preset> <capture> <video> <device></device> <crossbar_input>1</crossbar_input> <frame_rate>25.00</frame_rate> <size> <width></width> <height></height> </size> </video> <audio> <device></device> <crossbar_input>2</crossbar_input> <sample_rate></sample_rate> <channels>1</channels> <input_volume>60</input_volume> </audio> </capture> <encode> <video> <format>H.264</format> <datarate></datarate> <outputsize></outputsize> <advanced> <profile></profile> <level></level> <keyframe_frequency>5 Seconds</keyframe_frequency> </advanced> <autoadjust> <enable>false</enable> <maxbuffersize>1</maxbuffersize> <dropframes> <enable>false</enable> </dropframes> <degradequality> <enable>false</enable> <minvideobitrate></minvideobitrate> <preservepfq>false</preservepfq> </degradequality> </autoadjust> </video> <audio> <format>MP3</format> <datarate></datarate> </audio> </encode> <restartinterval> <days></days> <hours></hours> <minutes></minutes> </restartinterval> <reconnectinterval> <attempts></attempts> <interval></interval> </reconnectinterval> <output> <rtmp> <url></url> <backup_url></backup_url> <stream></stream> </rtmp> </output> <metadata> <entry> <key>author</key> <value></value> </entry> <entry> <key>copyright</key> <value></value> </entry> <entry> <key>description</key> <value></value> </entry> <entry> <key>keywords</key> <value></value> </entry> <entry> <key>rating</key> <value></value> </entry> <entry> <key>title</key> <value></value> </entry> </metadata> <preview> <video> <input> <zoom>50%</zoom> </input> <output> <zoom>50%</zoom> </output> </video> <audio></audio> </preview> <log> <level>100</level> <directory></directory> </log> </flashmedialiveencoder_profile> 

И мне нужно обновить некоторые данные в зависимости от типа xml, который требуется пользователю. Поэтому я хочу сделать что-то вроде этого:

 $data = array ( 'preset' => array ( 'name' => 'Low Bandwidth (150 Kbps) - H.264', ), 'capture' => array ( 'audio' => array ( 'sample_rate' => 44100 ) ), 'encode' => array ( 'video' => array ( 'datarate' => '300;', 'outputsize' => '480x360;', 'advanced' => array ( 'profile' => 'Main', 'level' => 2.1, ) ), 'audio' => array ( 'datarate' => 48 ) ), ); 

Я знаю, что это дерево работает, потому что я могу сделать это вручную, но проблема в том, что я не хочу делать это так, поэтому, если в будущем мне нужно изменить другое мнение в xml, я должен только добавить его к конфигурации массива.

Как мне это сделать? Я могу написать рекурсивную функцию для этого, но я действительно не хочу этого делать. Есть ли другой способ сделать это? Я не знаю … может быть что-то вроде:

 $xml->updateFromArray($data); 

Повторяю, я могу написать эту функцию и расширить SimpleXMLElement, но если есть какой-либо другой собственный метод php для этого, это будет лучше.

Учитывая, что $xml является элементом документа как SimpleXMLElement а $data – ваш массив (как в вопросе), если вас беспокоит нумерация детей, например, для метаданных, я пронумеровал его в массиве следующим образом:

 'metadata' => array( 'entry' => array( 5 => array( 'value' => 'Sunny Days', ), ), ), 

Ниже показано, как решить проблему нерекурсивным образом с помощью стека:

 while (list($set, $node) = array_pop($stack)) { if (!is_array($set)) { $node[0] = $set; continue; } foreach ($set as $element => $value) { $parent = $node->$element; if ($parent[0] == NULL) { throw new Exception(sprintf("Child-Element '%s' not found.", $element)); } $stack[] = array($value, $parent); } } 

Некоторые примечания:

  • Я изменил конкретный тип исключения, чтобы удалить зависимость.
  • $parent[0] == NULL проверяет элемент $parent не пуст (сравните / см. SimpleXML Type Cheatsheet ).
  • По мере того, как узел элемента помещается в стек, который требуется извлечь позже, $node[0] необходимо использовать для его установки после того, как он был извлечен из стека (нумерованный элемент уже находится в $node (первый по умолчанию), для его изменения позже, число 0 должно использоваться как смещение).

И онлайн-демонстрация .


Пример до сих пор не позволяет создавать новые элементы, если они пока не существуют. Чтобы добавить добавление новых элементов, исключение, отправленное для несуществующих детей:

  if ($parent[0] == NULL) { throw new Exception(sprintf("Child-Element '%s' not found.", $element)); } 

должен быть заменен некоторым кодом, который добавляет новых детей, включая первый:

  if ($parent[0] == NULL) { if (is_int($element)) { if ($element != $node->count()) { throw new Exception(sprintf("Element Number out of order: %d unfitting for %d elements so far.", $element, $node->count())); } $node[] = ''; $parent = $node->$element; } else { $parent[0] = $node->addChild($element); } } 

Исключением все еще является случай, когда добавляется новый элемент, но его число больше, чем существующее количество элементов плюс одно. например, у вас есть 4 элемента, а затем вы добавляете элемент с номером 6, это не сработает. Значение равно нулю, и это обычно не должно быть проблемой.

демонстрация

Я сделал этот объект, расширяя SimpleXMLElement, но если есть лучший способ сделать это, я буду очень благодарен 🙂

 /** * Object to manipulate and add information to an xml object * * @package Stream * @subpackage SimpleXMLElement * @author abraham.sustaita@gmail.com * @author Abraham Cruz * @version 2 Added functionality to update data with array * @example $this->rows[0] = array (); now can be edited */ class Stream_SimpleXMLElement extends SimpleXMLElement { /** * Method to update the information of the xml * from an array * * @throws Core_Exception * @param array $array * @return Stream_SimpleXMLElement */ public function updateFromArray(array $array) { foreach ($array as $key => $data) { if ($this->$key->count() == 0) { throw new Core_Exception("The key {$key} does not exists within the XML object."); } else if ($this->$key->count() > 1) { foreach ($data as $i => $value) { $tmpData = &$this->$key; $tmpkey = &$tmpData [$i]; if (is_array($value)) { foreach ($value as $key2 => $value2) { if ($tmpkey->$key2 instanceof Stream_SimpleXMLElement && is_array($value2)) { $tmpkey->$key2->updateFromArray($value2); } else { $tmpkey->$key2 = $value2; } } } else { $tmpkey = $value; } unset ($tmpData, $tmpkey); } } else { if (is_array($data)) { $this->$key->updateFromArray($data); } else { $this->$key = $data; } } } return $this; } } 

Поскольку объект Stream_SimpleXMLElement расширяет SimpleXMLElement , тогда каждый дочерний объект объекта будет экземпляром Stream_SimpleXMLElement , поэтому мы можем использовать тот же метод …