Archives par mot-clé : XML

Bibliothèque PHP de traitement de fichiers logs ADIF v3

Logo QScope.org (http://xv4y NULL.radioclub NULL.asia/wp-content/uploads/2013/08/logo_qscope_b NULL.png)Ceci est une nouvelle version de la bibliothèque PHP que j’avais précédemment publié (http://xv4y NULL.radioclub NULL.asia/2013/10/15/bibliotheque-php-de-traitement-de-fichiers-logs-adif-v2/). Elle accepte maintenant conforme à la norme ADIF v3 et accepte de manière transparente les fichiers au format traditionnels ADI ou au nouveau format ADX (XML). C’est la librairie que j’utilise pour mon site d’analyse de log en ligne QScope.org.

Ce code ne gagnerait pas un concours de beauté, mais il fonctionne. J’avoue que les tests avec le format XML ont été limités à l’exemple donné avec la norme ADIF v3, car je n’ai pas de logiciel capable de générer de tels fichiers. Aucune comparaison de performance n’a été faite entre les deux formats, mais je soupçonne que le format traditionnel est un peu plus rapide à importer, même si la limite viendra plus rapidement de la base de données que du parsing.

<?php

/*

=== ADIF v3 Parser library v2.00 (2015-03-10) ===

   This Library his based on work by Jason Harris KJ4IWX but has been simplified and improved for better ADIF compatiblity by Yannick DEVOS XV4Y
   Version 2.00 introduces transparent support for ADX (XML) file format coming with ADIF v3 specifications
   
   Copyright 2011-2013 Jason Harris KJ4IWX (Apache v2.00 License)
   Copyright 2015 Yannick DEVOS XV4Y (GNU GPL v3.00 License)
   Any commercial use is subject to authorization from the respective authors
	
	This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

---

	This PHP library is designed to work with the Jelix 1.5 PHP Framework and PHP 5.4.
	However, it can simply be called by any PHP program.
	
	Usage for ADI and ADX files (with Jelix):
		$ADIFParser = jClasses::createInstance('yourmodule~ADIFParser');
		$ADIFParser->initialize($complete_path_to_the_file);

		echo ($ADIFParser->get_header('adif_ver'));			// Will display the ADIF version
		$all_headers_array = $ADIFParser->get_header(); 	// Will return the whole headers in an array
		while(($record = $ADIFParser->get_record())!=-1) {	// Check for end of file
			if ($record!=false) {							// If line is empty we skip
				if(count($record) == 0) {
					break;
				};
				echo ($record['call']);
				echo ($record['qso_date']);
				echo ($record['mode']);
				// Do whatever you like with the ADIF fields...
			};
		};

	Fore more infos find contacts at:
	http://xv4y.radioclub.asia/
	http://www.qscope.org/

*/

class ADIFParser
{
	var $is_adx = false;	// default to ADI file (traditionnal format)
	var $data; 				// the adif data
	var $i; 				// the iterator
	var $headers = array();
	
	public function initialize($file) //this function parses the header
	{
		// We read the first line and check if it is XML (ADX) or not
		$this->file_handler = fopen($file,'r');
		$firstline = fgets($this->file_handler);
		if (stripos($firstline, "<?xml")!==false) $this->is_adx = true;
		fclose($this->file_handler);

		if (!$this->is_adx) {	// This is an old fashion ADI file format
			$eoh=$eof=false;
			$this->data='';
		
			// And we start from the begining
			$this->file_handler = fopen($file,'r');
			while (!$eoh && !$eof) {	// We will try to locate the <EOH> tag
				$newdata = fgets($this->file_handler);	// $this->file_handler contains a handler in the case of ADI files
				if (!$newdata) {
					$eof = true;
				} else {
					if (stripos($newdata, "<eoh>")===false) {
						$this->data .= $newdata;
					} else {
						$eoh = true;
					}
				};
			}
			if($eoh == false) //did we find the end of headers?
			{
				// If this ADIF file has no header (like FT5ZM), we need to re-open the file because we already went to the end
				fclose($this->file_handler);
				$this->file_handler = fopen($file,'r');
				return 0;
			};
		
		
			//get headers
		
			$this->i = 0;
			$in_tag = false;
			$tag = "";
			$value_length = "";
			$value = "";
			$pos = strlen($this->data)-1;
				
			while($this->i < $pos)
			{
				//skip comments
				if($this->data[$this->i] == "#")
				{
					while($this->i < $pos)
					{
						if($this->data[$this->i] == "\n")
						{
							break;
						}
				
						$this->i++;
					}
				}else{
					//find the beginning of a tag
					if($this->data[$this->i] == "<")
					{
						$this->i++;
						//record the key
						while($this->data[$this->i] < $pos && $this->data[$this->i] != ':')
						{
							$tag = $tag.$this->data[$this->i];
							$this->i++;
						}
					
						$this->i++; //iterate past the :
					
						//find out how long the value is
					
						while($this->data[$this->i] < $pos && $this->data[$this->i] != '>')
						{
							$value_length = $value_length.$this->data[$this->i];
							$this->i++;
						}
					
						$this->i++; //iterate past the >
					
						$len = (int)$value_length;
						//copy the value into the buffer
						while($len > 0 && $this->i < $pos)
						{
							$value = $value.$this->data[$this->i];
							$len--;
							$this->i++;
						};

						$this->headers[strtolower(trim($tag))] = $value; //convert it to lowercase and trim it in case of \r
						//clear all of our variables
						$tag = "";
						$value_length = "";
						$value = "";
					
					}
				}
			
				$this->i++;
			};
			return 1;
		} else {	// This is the new ADX (XML) file format
			$this->is_adx = true;
			$this->xml_reader = new XMLReader;
			$this->xml_reader->open($file); // In this case, $file contains a file path
			if (!$this->xml_reader) return -1;	// Not able to open the file

			while ($this->xml_reader->read() && $this->xml_reader->name != 'HEADER');		// Looking for the header

			if ($this->xml_reader->name == 'HEADER') {
				// Load the current xml element into simplexml
				$xml = simplexml_load_string($this->xml_reader->readOuterXML());
				$this->headers = array_change_key_case(json_decode(json_encode($xml),TRUE),CASE_LOWER);

			} else {
				return 0;	// No header, this should mean invalid file for ADX
			}
			while ($this->xml_reader->read() && $this->xml_reader->name != 'RECORD');		// Now we go to the first RECORD
			if ($this->xml_reader->name != 'RECORD') {
				$this->xml_reader->close();
				return -2;	// This is an empty or invalid ADX file
			}
		return 1;	// All is normal
		}
	}
	
	//the following function does the processing of the array into its key and value pairs
	public function record_to_array(&$record)
	{
		$return = array();
		for($a = 0; $a < strlen($record); $a++)
		{
			if($record[$a] == '<') //find the start of the tag
			{
				$tag_name = "";
				$value = "";
				$len_str = "";
				$len = 0;
				$a++; //go past the <
				while($record[$a] != ':') //get the tag
				{
					$tag_name = $tag_name.$record[$a]; //append this char to the tag name
					$a++;
				};
				$a++; //iterate past the colon
				while($record[$a] != '>' && $record[$a] != ':')
				{
					$len_str = $len_str.$record[$a];
					$a++;
				};
				if($record[$a] == ':')
				{
					while($record[$a] != '>')
					{
						$a++;
					};
				};
				$len = (int)$len_str;
				while($len > 0)
				{
					$a++;
					$value = $value.$record[$a];
					$len--;
				};
				$return[strtolower($tag_name)] = $value;
			};
			//skip comments
			if($record[$a] == "#")
			{
				while($a < strlen($record))
				{
					if($record[$a] == "\n")
					{
						break;
					}
					$a++;
				}
			}
		};
		return $return;
	}
	
	
	//finds the next record in the file
	public function get_record()
	{
		if (!$this->is_adx) {
			$record ='';
			$out_condition = false;
			while (!$out_condition) {
				$newdata = fgets($this->file_handler);
				if ($newdata===false) {
					$out_condition = true;
					fclose($this->file_handler);
					return -1;	// This is the end of file
				} else {
					$end = stripos($newdata , "<eor>");
					if($end !== false) { //we got a full line
						$record .= substr($newdata, 0, $end);
						$out_condition = true;
						return $this->record_to_array($record); //process and return output
					} else {
					$record .= $newdata;	// The line is not complete and we need to fetch a new string
					}
				}
			}
		} else {
			// Loop through the Records
			if ($this->xml_reader->name == 'RECORD') {
				// Load the current xml element into simplexml and we’re off and running!
				$xml = simplexml_load_string($this->xml_reader->readOuterXML());
				$this->xml_reader->next('RECORD');
				$array = array_change_key_case(json_decode(json_encode($xml),TRUE),CASE_LOWER);

				return $array;
			} else {
				$this->xml_reader->close();
				return -1;
			}
		}
 	}
	
	public function get_header($key='')	// Changed to return the whole array if no key provided
	{
		if(array_key_exists(strtolower($key), $this->headers))
		{
			return $this->headers[strtolower($key)];
		} else if (empty($key)) {
			return $this->headers;
		} else {
			return NULL;
		}
	}
	
}
?>