XML-синтаксический анализ

ОБНОВЛЕНИЕ: я переработал вопрос, чтобы показать прогресс, который я сделал, и, возможно, облегчить ответ.

ОБНОВЛЕНИЕ 2: Я добавил другое значение в XML. Расширение доступно в каждом почтовом ящике. Каждый элемент может иметь несколько элементов, разделенных вкладкой. Поэтому он будет структурирован так. Платформа> Расширение (подгруппа)> Имя> Заголовок. Если элемент имеет более одного расширения, он будет отображаться в нескольких местах.

У меня есть следующий файл XML.

<Item> <Platform>Windows</Platform> <Ext>gif jpeg doc</Ext> <Name>File Group 1</Name> <Title>This is the first file group</Title> <DownloadPath>/this/windows/1/1.zip</DownloadPath> </Item> <Item> <Platform>Windows</Platform> <Ext>gif doc</Ext> <Name>File Group 1</Name> <Title>This is the first file group</Title> <DownloadPath>/this/windows/1/2.zip</DownloadPath> </Item> <Item> <Platform>Windows</Platform> <Ext>gif</Ext> <Name>File Group 1</Name> <Title>This is in the same group but has a different title</Title> <DownloadPath>/this/windows/1/3.zip</DownloadPath> </Item> <Item> <Platform>Mac</Platform> <Ext>gif jpeg doc</Ext> <Name>File Group 1</Name> <Title>This has the same group name but a different platform. Because it has the same title and name the files are added to this array below.</Title> <DownloadPath>/this/mac/1/1.zip</DownloadPath> </Item> <Item> <Platform>Mac</Platform> <Ext>jpeg doc</Ext> <Name>File Group 1</Name> <Title>This has the same group name but a different platform. Because it has the same title and name the files are added to this array below.</Title> <DownloadPath>/this/mac/1/2.zip</DownloadPath> </Item> <Item> <Platform>Windows</Platform> <Ext>gif jpeg doc</Ext> <Name>File Group 2</Name> <Title>This is the second file group</Title> <DownloadPath>/this/windows/2/1.zip</DownloadPath> </Item> <Item> <Platform>Windows</Platform> <Ext>gif jpeg doc</Ext> <Name>File Group 2</Name> <Title>This is the second file group</Title> <DownloadPath>/this/windows/2/2.zip</DownloadPath> </Item> <Item> <Platform>Mac</Platform> <Ext>gif jpeg doc</Ext> <Name>File Group 3</Name> <Title>This is the second mac file group really.</Title> <DownloadPath>/this/windows/3/1.zip</DownloadPath> </Item> 

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

 [Windows] => Array ( [0] => array( "Name" => "File Group 1", "Title" => "This is the first file group", "Files" => array( [0] => array( "DownloadPath" => "/this/windows/1/1.zip" ), [1] => array( "DownloadPath" => "/this/windows/1/2.zip" ) ) ), [1] => array( "Name" => "File Group 1", "Title" => "This has the same name but has a different title, so it should be seperate.", "Files" => array( [0] => array( "DownloadPath" => "/this/windows/1/3.zip" ) ) ), [1] => array( "Name" => "File Group 2", "Title" => "This is the second file group", "Files" => array( [0] => array( "DownloadPath" => "/this/windows/2/1.zip" ), [1] => array( "DownloadPath" => "/this/windows/2/2.zip" ) ) ) ), [Mac] => Array( [0] => array( "Name" => "File Group 1", "Title" => "This has the same group name but a different platform. Because it has the same title and name the files are added to this array below.", "Files" => array( [0] => array( "DownloadPath" => "/this/mac/1/1.zip" ), [1] => array( "DownloadPath" => "/this/mac/1/2.zip" ) ) ), [1] => array( "Name" => "File Group 3", "Title" => "This is the second mac file group really.", "Files" => array( [0] => array( "DownloadPath" => "/this/mac/1/1.zip" ), [1] => array( "DownloadPath" => "/this/mac/1/2.zip" ) ) ), ) 

Вот что я получил до сих пор с моим php

  $scrape_xml = "files.xml"; $xml = simplexml_load_file($scrape_xml); $groups = array(); foreach ($xml->Item as $file){ if (!isset($groups[stripslashes($file->Platform)][stripslashes($file->Name)][stripslashes($file->Title)])){ $groups[stripslashes($file->Platform)][stripslashes($file->Name)][stripslashes($file->Title)] = array( 'Platform' => $file->Platform, 'Name' => $file->Name, 'Title' => $file->Title ); } $groups[stripslashes($file->Platform)][stripslashes($file->Name)][stripslashes($file->Title)]['Files'][] = $file->DownloadPath; } echo "count=" . $i; echo "<pre>"; print_r($groups); echo "</pre>"; 

это дает мне этот результат

 Array ( [Windows] => Array ( [File Group 1] => Array ( [This is the first file group] => Array ( [Platform] => SimpleXMLElement Object ( [0] => Windows ) [Name] => SimpleXMLElement Object ( [0] => File Group 1 ) [Title] => SimpleXMLElement Object ( [0] => This is the first file group ) [Files] => Array ( [0] => SimpleXMLElement Object ( [0] => /this/windows/1/1.zip ) [1] => SimpleXMLElement Object ( [0] => /this/windows/1/2.zip ) ) ) [This is in the same group but has a different title] => Array ( [Platform] => SimpleXMLElement Object ( [0] => Windows ) [Name] => SimpleXMLElement Object ( [0] => File Group 1 ) [Title] => SimpleXMLElement Object ( [0] => This is in the same group but has a different title ) [Files] => Array ( [0] => SimpleXMLElement Object ( [0] => /this/windows/1/3.zip ) ) ) ) [File Group 2] => Array ( [This is the second file group] => Array ( [Platform] => SimpleXMLElement Object ( [0] => Windows ) [Name] => SimpleXMLElement Object ( [0] => File Group 2 ) [Title] => SimpleXMLElement Object ( [0] => This is the second file group ) [Files] => Array ( [0] => SimpleXMLElement Object ( [0] => /this/windows/2/1.zip ) [1] => SimpleXMLElement Object ( [0] => /this/windows/2/2.zip ) ) ) ) ) [Mac] => Array ( [File Group 1] => Array ( [This has the same group name but a different platform. Because it has the same title and name the files are added to this array below.] => Array ( [Platform] => SimpleXMLElement Object ( [0] => Mac ) [Name] => SimpleXMLElement Object ( [0] => File Group 1 ) [Title] => SimpleXMLElement Object ( [0] => This has the same group name but a different platform. Because it has the same title and name the files are added to this array below. ) [Files] => Array ( [0] => SimpleXMLElement Object ( [0] => /this/mac/1/1.zip ) [1] => SimpleXMLElement Object ( [0] => /this/mac/1/2.zip ) ) ) ) [File Group 3] => Array ( [This is the second mac file group really.] => Array ( [Platform] => SimpleXMLElement Object ( [0] => Mac ) [Name] => SimpleXMLElement Object ( [0] => File Group 3 ) [Title] => SimpleXMLElement Object ( [0] => This is the second mac file group really. ) [Files] => Array ( [0] => SimpleXMLElement Object ( [0] => /this/windows/3/1.zip ) ) ) ) ) ) 

ОБНОВЛЕНИЕ 2: Новая структура массива

 [Windows] => Array ( [gif] =>Array( [0] => array( "Name" => "File Group 1", "Title" => "This is the first file group", "Files" => array( [0] => array( "DownloadPath" => "/this/windows/1/1.zip" ), [1] => array( "DownloadPath" => "/this/windows/1/2.zip" ) ) ) ), [jpeg] => array( [0] => array( "Name" => "File Group 1", "Title" => "This is the first file group", "Files" => array( [0] => array( "DownloadPath" => "/this/windows/1/1.zip" ), [1] => array( "DownloadPath" => "/this/windows/1/2.zip" ) ) ), [1] => array( "Name" => "File Group 2", "Title" => "This is the second file group", "Files" => array( [0] => array( "DownloadPath" => "/this/windows/2/1.zip" ), [1] => array( "DownloadPath" => "/this/windows/2/2.zip" ) ) ) ), [doc] => array( [0] => array( "Name" => "File Group 1", "Title" => "This is the first file group", "Files" => array( [0] => array( "DownloadPath" => "/this/windows/1/1.zip" ), [1] => array( "DownloadPath" => "/this/windows/1/2.zip" ) ) ), [1] => array( "Name" => "File Group 1", "Title" => "This has the same name but has a different title, so it should be seperate.", "Files" => array( [0] => array( "DownloadPath" => "/this/windows/1/3.zip" ) ) ), [2] => array( "Name" => "File Group 2", "Title" => "This is the second file group", "Files" => array( [0] => array( "DownloadPath" => "/this/windows/2/1.zip" ), [1] => array( "DownloadPath" => "/this/windows/2/2.zip" ) ) ) ) ), [Mac] => Array( [gif] => array( [0] => array( "Name" => "File Group 2", "Title" => "This is the second file group", "Files" => array( [0] => array( "DownloadPath" => "/this/mac/2/1.zip" ), [1] => array( "DownloadPath" => "/this/mac/2/2.zip" ) ) ), [1] => array( "Name" => "File Group 2", "Title" => "This is the second file group", "Files" => array( [0] => array( "DownloadPath" => "/this/mac/2/1.zip" ), [1] => array( "DownloadPath" => "/this/mac/2/2.zip" ) ) ), ) [jepg] => array( [0] => array( "Name" => "File Group 2", "Title" => "This is the second file group", "Files" => array( [0] => array( "DownloadPath" => "/this/mac/2/1.zip" ), [1] => array( "DownloadPath" => "/this/mac/2/2.zip" ) ) ) ) [doc] => array( [0] => array( "Name" => "File Group 1", "Title" => "This has the same group name but a different platform. Because it has the same title and name the files are added to this array below.", "Files" => array( [0] => array( "DownloadPath" => "/this/mac/1/1.zip" ), [1] => array( "DownloadPath" => "/this/mac/1/2.zip" ) ) ), [1] => array( "Name" => "File Group 3", "Title" => "This is the second mac file group really.", "Files" => array( [0] => array( "DownloadPath" => "/this/mac/1/1.zip" ), [1] => array( "DownloadPath" => "/this/mac/1/2.zip" ) ) ) ) ) 

ОБНОВЛЕНИЕ 3: В списке файлов есть мусор.

 <Item> <Platform>Windows</Platform> <Ext>gif jpeg doc</Ext> <Name>File Group 1</Name> <Title>This is the first file group</Title> <DownloadPath>/this/windows/1/1.zip</DownloadPath> </Item> <Item> <Platform>Windows</Platform> <Ext>gif jpeg doc</Ext> <Name>File Group 1</Name> <Title>This is the first file group</Title> <DownloadPath>/this/windows/1/2.zip</DownloadPath> </Item> <Item> <Platform>Windows</Platform> <Ext>gif jpeg doc</Ext> <Name>File Group 1</Name> <Title>This is the first file group</Title> <DownloadPath>/this/windows/2/1.zip</DownloadPath> </Item> <Item> <Platform>Windows</Platform> <Ext>gif jpeg doc</Ext> <Name>File Group 1</Name> <Title>This is the first file group</Title> <DownloadPath>/this/windows/2/2.zip</DownloadPath> </Item> 

Существует элемент с той же платформой, расширениями, именем и названием. Элементы 3 и 4 выше должны быть пропущены и сохранить их в массив, который я буду обрабатывать позже.

Вы просто отображаете входные значения в выходной массив, упорядочивая их по-разному, это ваша структура:

 Array( [... Item/Platform] => Array ( [... Item/Title as 0-n] => array( "Name" => Item/Name, "Title" => Item/Title, "Files" => array( [...] => array( "DownloadPath" => Item/DownloadPath ), ) ), 

Отображение может быть выполнено путем итерации по элементам в XML и сохранения значений в соответствующем месте в новом массиве (я назвал его $build ):

 $build = array(); foreach($items as $item) { $platform = (string) $item->Platform; $title = (string) $item->Title; isset($build[$platform][$title]) ?: $build[$platform][$title] = array( 'Name' => (string) $item->Name, 'Title' => $title ); $build[$platform][$title]['Files'][] = array('DownloadPath' => (string) $item->DownloadPath); } $build = array_map('array_values', $build); 

array_map выполняется в конце для преобразования ключей Item/Title в числовые.

Вот и все, здесь Демо .

Дайте мне знать, если это будет полезно.

Изменить: для ваших обновленных данных это небольшая модификация выше, основные принципы предыдущего примера все еще существуют, кроме того, он дополнительно заботится о дополнительном дублировании на каждое дополнительное расширение для каждого элемента, добавив еще одну итерацию внутри:

 $build = array(); foreach($items as $item) { $platform = (string) $item->Platform; $title = (string) $item->Title; foreach(preg_split("~\s+~", $item->Ext) as $ext) { isset($build[$platform][$ext][$title]) ?:$build[$platform][$ext][$title] = array( 'Name' => (string) $item->Name, 'Title' => $title ); $build[$platform][$ext][$title]['Files'][] = array('DownloadPath' => (string) $item->DownloadPath); } } $build = array_map(function($v) {return array_map('array_values', $v);}, $build); 

начать с объявления

 $groups[stripslashes($file->Platform)][stripslashes($file->Name)] [stripslashes($file->Title)] = (object)array( 'Name' => $file->Name, 'Title' => $file->Title, 'Files' = (object)array() ); 

Это приблизит вас.

Вы также должны проверить тип каждого XMLElement, как вы его видите, если его массив или простой объект. Затем относитесь соответственно.

Вы точно не объяснили, что вы видите, так что мне придется угадать.

Во-первых, в вашем источнике, ваш последний DownloadPath – это /this/windows/3/1.zip хотя он, как предполагается, является файлом Mac – неверный тип, я уверен, но результат будет «выглядеть не так» с тем, что там ,

Затем, если вам нужны строки, а не объекты SimpleXMLElement, вам это нужно (также нужно сделать несколько приемов, чтобы избежать так много stripslashes() ):

 foreach ($xml->Item as $file) { $platform = stripslashes((string) $file->Platform); $name = stripslashes((string) $file->Name); $title = stripslashes((string) $file->Title); if( !isset($groups[$platform][$name][$title])) { $groups[$platform][$name][$title] = array( 'Platform' => $platform, 'Name' => $name, 'Title' => $title ); } $groups[$platform][$name][$title]['Files'][] = (string) $file->DownloadPath; } 

Обратите внимание на биты (string) ? Они передают объект в строку, которая позволяет вам получить доступ к буквальному значению, а не к объекту. Это также причина, по которой ваши массивные ключи работали, потому что они были встроены в строки (в качестве ключей массива могут использоваться только строки и целое число).

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

Я предпочитаю DOM DOcument и XPath сам, так что это то, что я сделал бы …

 $xml = '\path\to\your\file.xml'; $doc = new DOMDocument( '1.0', 'UTF-8' ); $doc->load( $xml ); $dxpath = new DOMXPath( $doc ); $items = $dxpath->query( '//Item' ); $db = new PDO( 'mysql:dbname=YOURDB:host=YOURHOST', $DBUSER, $DBPASS ); $ins = $db->prepare(' INSERT INTO ur_table ( `platform` , `name` , `title` , `path` ) VALUES ( :platform , :name , :title , :path ); '); foreach( $items as $item ) { $ins->bindValue( ':platform' , $item->getElementsByTagName( 'PlatForm' )->item(0)->nodeValue , PDO::PARAM_STR ); $ins->bindValue( ':name' , $item->getElementsByTagName( 'Name' )->item(0)->nodeValue , PDO::PARAM_STR ); $ins->bindValue( ':title' , $item->getElementsByTagName( 'Title' )->item(0)->nodeValue , PDO::PARAM_STR ); $ins->bindValue( ':DownloadPath' , $item->getElementsByTagName( 'PlatForm' )->item(0)->nodeValue , PDO::PARAM_STR ); $ins->execute(); } 

Нет необходимости в stripslashes, а что нет – он будет обрабатывать все taht для вас.

Как что-то вроде этого? Код немного неаккуратно, и, вероятно, необходимо улучшить настройки для улучшения проверки.

 class XMLFileImporter { public $file; //Absolute path to import file public $import = array(); public $xml; public $error = false; public function __construct($file) { $this->file = $file; $this->load(); } public function load() { if(!is_readable($this->file)) { $this->error("File is not readable"); return false; } $xml = simplexml_load_file($this->file); if(!$xml) { $this->error("XML could not be parsed"); return false; } $this->xml = json_decode(json_encode($xml)); return true; } public function import() { $count = $this->parseItems(); echo "Imported $count rows"; } public function parseItems() { if($this->error()){ return false; } if(!self::validateXML($this->xml)) { $this->error("Invalid SimpleXML object"); return false; } if(!self::validateArray($this->xml->Item)) { $this->error("Invalid Array 'Item' on SimpleXML object"); return false; } $count = 0; foreach($this->xml->Item as $item) { if($this->parseItem($item)){ $count++; } } return $count; } public function parseItem($item) { if($this->error()){ return false; } if(!self::validateItem($item)) { $this->error("Invalid file item"); return false; } $item = self::normalizeItem($item); $this->handlePlatform((string)$item->Platform); $this->handleGroup($item); $this->handleSubGroup($item); $this->handleFile($item); return true; } public function handlePlatform($platform) { if(!isset($this->import[$platform])) { $this->import[$platform] = array(); } return true; } public function handleGroup($item) { if(!isset($this->import[$item->Platform][$item->Name])) { $this->import[$item->Platform][$item->Name] = array(); } return true; } public function handleSubGroup($item) { if(!isset($this->import[$item->Platform][$item->Name][$item->Title])) { $this->import[$item->Platform][$item->Name][$item->Title] = array(); } return true; } public function handleFile($item) { array_push($this->import[$item->Platform][$item->Name][$item->Title],$item->DownloadPath); } public function error($set=false) { if($set){ $this->error = $set; return true; } return $this->error; } public static function validateXML($xml) { return is_object($xml); } public static function validateArray($arr,$min=1){ return (isset($arr) && !empty($arr) && count($arr) > $min); } public static function validateItem($item){ return (isset($item->Title) && isset($item->Name) && isset($item->DownloadPath) && isset($item->Platform)); } public static function normalizeItem($item){ $item->Name = stripslashes(trim((string)$item->Name)); $item->Title = stripslashes(trim((string)$item->Title)); $item->Platform = (string)$item->Platform; $item->DownloadPath = (string)$item->DownloadPath; return $item; } public function output() { print_r($this->import); return true; } } $importer = new XMLFileImporter(dirname(__FILE__)."/files.xml"); $importer->load(); $importer->import(); $importer->output(); var_dump($importer->error()); 

Вы можете попробовать следующее:

 $scrape_xml = "files.xml"; $xml = simplexml_load_file($scrape_xml); $group = array(); foreach ($xml->Item as $file) { $platform = stripslashes($file->Platform); $name = stripslashes($file->Name); $title = stripslashes($file->Title); $downloadPath = stripslashes($file->DownloadPath); if(!isset($group[$platform])) { $group[$platform] = array(); $group[$platform][] = array("Name" => $name,"Title" => $title, "Files" => array($downloadPath)); } else { $found = false; for($i=0;$i<count($group[$platform]);$i++) { if($group[$platform][$i]["Name"] == $name && $group[$platform][$i]["Title"] == $title) { $group[$platform][$i]["Files"][] = $downloadPath; $found = true; break; } } if(!$found) { $group[$platform][] = array("Name" => $name,"Title" => $title, "Files" => array($downloadPath)); } } } echo "<pre>".print_r($group,true)."</pre>"; 

Это код, который даст вам результат, который вам нужен. ОБНОВЛЕНИЕ: Это касается последней группы, которую вы просили.

 $scrape_xml = "files.xml"; $xml = simplexml_load_file($scrape_xml); $groups = array(); foreach ($xml->Item as $file){ $platform = stripslashes($file->Platform); $name = stripslashes($file->Name); $title = stripslashes($file->Title); $extensions = explode(' ', $file->Ext); foreach($extensions as $extension) { if (!isset($groups2[$platform])) $groups2[$platform] = array(); if (!isset($groups2[$platform][$extension])) $groups2[$platform][$extension] = array(); $groupFound = false; for($idx = 0; $idx < count($groups2[$platform][$extension]); $idx ++) { if ($groups2[$platform][$extension][$idx]["Name"] == $name && $groups2[$platform][$extension][$idx]["Title"] == $title) { $groups2[$platform][$extension][$idx]["Files"][] = array('DownloadPath' => $file->DownloadPath.""); $groupFound = true; break; } } if ($groupFound) continue; $groups2[$platform][$extension][] = array( "Name" => $name, "Title" => $title, "Files" => array(array('DownloadPath' => $file->DownloadPath.""))); } } echo "<br />"; echo "<pre>"; print_r($groups2); echo "</pre>";