Я сделал простой инструмент, который позволяет заполнить поле ввода URL-адресом для XML-файла. Предполагается показать все узлы, чтобы пользователь мог сопоставить их с полями базы данных, которые я работаю для XML-файла с двумя «первичными» узлами. Пример файла XML:
<foods> <food> <name>ravioli</name> <recipe>food.com/ravioli</recipe> <time>10 minutes</time> </food> <food> <name>ravioli</name> <recipe>food.com/ravioli</recipe> <time>10 minutes</time> </food> </foods>
Это возвращает мне список, в котором говорится:
name recipe time
Проблема в том, что кто-то хочет использовать XML-файл, который не имеет 2 «первичных» узлов. Например, отсутствует узел <food>
. В этом случае он не сможет показать результат, потому что мой PHP-код ожидает 2 вместо 1 первичного.
Мой код выглядит следующим образом:
// Fetch the XML from the URL if (!$xml = simplexml_load_file($_GET['url'])) { // The XML file could not be reached echo 'Error loading XML. Please check the URL.'; } else { // Parse through the XML and fetch the nodes $child = $xml->children(); foreach($child->children() as $key => $value) { echo $key."<br>"; } }
Есть ли способ получить узлы, которые я хочу получить из любого XML-файла, независимо от количества родительских узлов?
Вы можете запрашивать данные из XML DOM, используя Xpath. Он доступен на PHP с использованием метода DOMXpath :: evaluation (). Второй аргумент – это контекст, поэтому вы можете относиться к другому узлу. Преобразование его в список записей (для базы данных, csv, …). потребуется несколько шагов. Начиная с некоторого бутстрапа:
$xml = <<<'XML' <foods> <food> <name>ravioli 1</name> <recipe>food.com/ravioli-1</recipe> <time unit="minutes">10</time> </food> <food> <name>ravioli 2</name> <recipe>food.com/ravioli-2</recipe> <time unit="minutes">11</time> </food> </foods> XML; $dom = new DOMDocument(); $dom->loadXml($xml); $xpath = new DOMXpath($dom);
Сначала нам нужно определить, какой элемент xml определяет запись, затем какие элементы определяют поля.
Итак, давайте создадим списки возможных путей записи и путей полей:
$paths = []; $leafs = []; foreach ($xpath->evaluate('//*|//@*') as $node) { $isPath = $xpath->evaluate('count(@*|*) > 0', $node); $isLeaf = !($xpath->evaluate('count(*) > 0', $node)); $path = ''; foreach ($xpath->evaluate('ancestor::*', $node) as $parent) { $path .= '/'.$parent->nodeName; } $path .= '/'.($node instanceOf DOMAttr ? '@' : '').$node->nodeName; if ($isLeaf) { $leafs[$path] = TRUE; } if ($isPath) { $paths[$path] = TRUE; } } $paths = array_keys($paths); $leafs = array_keys($leafs); var_dump($paths, $leafs);
Вывод:
array(3) { [0] => string(6) "/foods" [1] => string(11) "/foods/food" [2] => string(16) "/foods/food/time" } array(4) { [0] => string(16) "/foods/food/name" [1] => string(18) "/foods/food/recipe" [2] => string(16) "/foods/food/time" [3] => string(22) "/foods/food/time/@unit" }
Затем покажите возможные пути записи для пользователя. Пользователь должен выбрать один. Зная путь записи, создайте список возможных путей поля из массива leafs:
$path = '/foods/food'; $fieldLeafs = []; $pathLength = strlen($path) + 1; foreach ($leafs as $leaf) { if (0 === strpos($leaf, $path.'/')) { $fieldLeafs[] = substr($leaf, $pathLength); } } var_dump($fieldLeafs);
Вывод:
array(4) { [0] => string(4) "name" [1] => string(6) "recipe" [2] => string(4) "time" [3] => string(10) "time/@unit" }
Поместите несколько диалоговых окон, которые позволят пользователю выбрать путь для каждого поля.
$fieldDefinition = [ 'title' => 'name', 'url' => 'recipe', 'needed_time' => 'time', 'time_unit' => 'time/@unit' ];
Теперь используйте путь и сопоставление для создания массива записей:
$result = []; foreach ($xpath->evaluate($path) as $node) { $record = []; foreach ($fieldDefinition as $field => $expression) { $record[$field] = $xpath->evaluate( 'string('.$expression.')', $node ); } $result[] = $record; } var_dump($result);
Вывод:
array(2) { [0] => array(4) { 'title' => string(9) "ravioli 1" 'url' => string(18) "food.com/ravioli-1" 'needed_time' => string(2) "10" 'time_unit' => string(7) "minutes" } [1] => array(4) { 'title' => string(9) "ravioli 2" 'url' => string(18) "food.com/ravioli-2" 'needed_time' => string(2) "11" 'time_unit' => string(7) "minutes" } }
Полный пример можно найти по адресу: https://eval.in/118012
XML в примере никогда не преобразуется в общий массив. Это будет означать потерю информации и двойное хранение. Так не надо. Извлеките информацию о структуре из XML, дайте пользователю определить отображение. Используйте Xpath для извлечения данных и сохранения их непосредственно в формате результата.