Page 1 sur 3

Lib Xml, alternative de SimpleXml pour PHP < 5

Posté : 17 déc. 2006, 22:46
par Hywan
Bonjour à tous,

ma démarche est pour la moins particulière.

Introduction

J'ai développé (pour un projet personnel) une class Xml.
Cette class plutôt compliquée je dois avoué m'a demandé pas mal de temps de développement. Elle est enfin terminée (je le crois du moins), mais en lisant un livre sur PHP 5, je me rend compte que SimpleXml (module de PHP 5) fait la même chose, mais pas tout à fait de la même façon.

J'arrive à la conclusion que ma class peut être une alternative de Simple Xml pour les versions de PHP étant inférieur à la version 5 (même si le traitement est légèrement différent de Simple Xml, le résultat est quasiment identique).

Déjà, je ne vous raconte pas la déception, je pensais avoir « innové » dans cette branche, mais bon, sûrement un rêve de gosse :P

Je viens plus précisement pour vous demander de tester ma class Xml, pour une correction de bug.
Je voulais également savoir si ma class pouvait être utile, et si ça valait le coup d'en parler un peu, de la faire connaître.
Je n'ai pas la prétention d'avoir fait un outil surpuissant, mais je pense sincérement qu'il peut être utile (et amélioré).
Et je voulais savoir quelle licence est la mieux pour représenter ma class Xml.

Ca fait pas mal de questions ...


Bon une petite explication du fonctionnement de la class :

Démonstration

source Xml :

Code : Tout sélectionner

<?xml version="1.0" encoding="utf-8"?> <global> <a>hello world</a> <b attr="value">avec des attributs !</b> <c> <d>limbrication fonctionne aussi</d> </c> <e> <f>le multi tag de meme</f> <f>la preuve</f> <f>en image</f> </e> <g a="b"> <h attr="plpl" b="c">le multi-tag avec imbrication et multi-attribut aussi</h> <h welc="ome" d="e" f="g">la preuve</h> <h id="k">je ne sais plus quoi marquer</h> </g> <i>ca devrait suffir</i> </global>
et le résultat de la class :

Code : Tout sélectionner

Array ( [a] => hello world [b] => avec des attributs ! [b-ATTR] => Array ( [attr] => value ) [c] => Array ( [d] => limbrication fonctionne aussi ) [e] => Array ( [f] => Array ( [0] => le multi tag de meme [1] => la preuve [2] => en image ) ) [g] => Array ( [h] => Array ( [0] => le multi-tag avec imbrication et multi-attribut aussi [1] => la preuve [2] => je ne sais plus quoi marquer ) [h-ATTR] => Array ( [0] => Array ( [attr] => plpl [b] => c ) [1] => Array ( [welc] => ome [d] => e [f] => g ) [2] => Array ( [id] => k ) ) ) [g-ATTR] => Array ( [a] => b ) [i] => ca devrait suffir )


voilà, j'espère que ca vous donne un aperçu de ce qu'elle peut faire.
je ne publie pas la source par simple manque de place (je ne veux pas encombrer le premier billet) je doute que ce soit une bonne idée hehe, mais c'est la première que je fais ce genre de démarche, je suis plutôt engoissé :P


j'aimerais votre opinion sur tout ca
merci :)

Hywan


PS : j'attend déjà quelques critiques avant de publier la class en fait :) même si critiquer quelque chose inexistant est difficile, mais basé vous sur l'exemple ...

Posté : 18 déc. 2006, 18:44
par Hywan
Après réflexion, je publie ma class.
Vous pourrez alors la tester (si vous en avez envie hein :P)

Cette lib a été mise à jours le 16 mai 2007, les messages qui suivent (jusqu'à cette date) ne sont donc plus juste :P
-> amélioration de la gestion des attributs en multi-tag, avec des tags en contenu. (bug trouvé par rodsouza sur PHPClasses.org)
-> bug dans la gestion des attributs (bug trouvé par Bidule sur PHPFrance.com)

Code source
<?php

####### GNU General Public License #############################################
#                                                                              #
# This file is part of HOA Open Accessibility.                                 #
# Copyright (c) 2006 Ivan ENDERLIN. All rights reserved.                       #
#                                                                              #
# HOA Open Accessibility 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 2 of the License, or            #
# (at your option) any later version.                                          #
#                                                                              #
# HOA Open Accessibility 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 HOA Open Accessibility; if not, write to the Free Software        #
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA   #
#                                                                              #
####### !GNU General Public License ############################################

/**
 * Class Xml.
 *
 * Parse a XML document into a nested array.
 * @author ENDERLIN Ivan <[email protected]>
 * @copyright 2007 ENDERLIN Ivan.
 * @since PHP4
 * @version 0.3
 * @package Xml
 * @licence GNU GPL
 */

class Xml {
	/**
	 * Xml parser container.
	 *
	 * @var resource parser
	 */
	var $parser;

	/**
	 * Parse result.
	 *
	 * @var array pOut
	 */
	var $pOut = array();

	/**
	 * Contain the overlap tag temporarily .
	 *
	 * @var array track
	 */
	var $track = array();

	/**
	 * Current tag level.
	 *
	 * @var string tmpLevel
	 */
	var $tmpLevel = '';

	/**
	 * Attribut of current tag.
	 *
	 * @var array tmpAttrLevel
	 */
	var $tmpAttrLevel = array();

	/**
	 * Write result.
	 *
	 * @var string wOut
	 */
	var $wOut = '';




	/**
	 * parse
	 * Set the parser Xml and theses options.
	 * Xml file could be a string, a file, or curl.
	 * When the source is loaded, we run the parse.
	 * After, we clean all the memory and variables,
	 * and return the result in an array.
	 *
	 * @access  public
	 * @param   src       string    Source
	 * @param   typeof    string    Source type : NULL, FILE, CURL.
	 * @param   encoding  string    Encoding type.
	 * @return  array
	 */
	function parse ( $src, $typeof = 'FILE', $encoding = 'UTF-8' ) {

		// ini;
		// (re)set array;
		$this->pOut = array();
		$this->parser = xml_parser_create();

		xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
		xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, $encoding);

		xml_set_object($this->parser, $this);
		xml_set_element_handler($this->parser, 'startHandler', 'endHandler');
		xml_set_character_data_handler($this->parser, 'contentHandler');


		// format source;
		if($typeof == NULL)
			$data = $src;
		elseif($typeof == 'FILE') {
			$fop = fopen($src, 'r');
			$data = fread($fop, filesize($src));
			fclose($fop);
		}
		elseif($typeof == 'CURL') {
			$curl = curl_init();
			curl_setopt($curl, CURLOPT_URL, $src);
			curl_setopt($curl, CURLOPT_HEADER, 0);
			$data = curl_exec($curl);
			curl_close($curl);
		}
		else
			return Fw::raiseError('Xml parser need data.', 0);

		// parse $data;
		$parse = xml_parse($this->parser, $data);
		if(!$parse)
			return Fw::raiseError('XML Error : %s at line %d.', 1,
				array(xml_error_string(xml_get_error_code($this->parser)),
					xml_get_current_line_number($this->parser)));

		// destroy parser;
		xml_parser_free($this->parser);

		// unset extra vars;
		unset($data,
			  $this->track,
			  $this->tmpLevel);

		// remove global tag and return the result;
		return $this->pOut[0][key($this->pOut[0])];
	}



	/**
	 * startHandler
	 * Manage the open tag, and these attributs by callback.
	 * The purpose is to create a pointer : {{int ptr}}.
	 * If the pointer exists, we have a multi-tag situation.
	 * Tag name  is stocked like : '<tag>'
	 * Attributs is stocked like : '<tag>-ATTR'
	 * Return true but built $this->pOut.
	 *
	 * @access  private
	 * @param   parser  resource    Parser resource.
	 * @param   tag     string      Tag name.
	 * @param   attr    array       Attribut.
	 * @return  bool
	 */
	function startHandler ( $parser, $tag, $attr ) {

		// built $this->track;
		$this->track[] = $tag;
		// place pointer to the end;
		end($this->track);
		// temp level;
		$this->tmpLevel = key($this->track);

		// built attrLevel into $this->tmpAttrLevel
		if(isset($this->tmpAttrLevel[$this->tmpLevel]['attrLevel']))
			$this->tmpAttrLevel[$this->tmpLevel]['attrLevel']++;

		// built $this->pOut;
		if(!isset($this->pOut[key($this->track)][$tag])) {
			$this->pOut[key($this->track)][$tag] = '{{'.key($this->track).'}}';

			if(!isset($this->tmpAttrLevel[$this->tmpLevel]['attrLevel']))
				$this->tmpAttrLevel[$this->tmpLevel]['attrLevel'] = 0;				
		}

		// built attributs;
		if(!empty($attr)) {

			$this->tmpAttrLevel[$this->tmpLevel][] = $this->tmpAttrLevel[$this->tmpLevel]['attrLevel'];
			end($this->tmpAttrLevel[$this->tmpLevel]);

			// it's the first attribut;
			if(!isset($this->pOut[key($this->track)][$tag.'-ATTR']))
					$this->pOut[key($this->track)][$tag.'-ATTR'] = $attr;

			// or it's not the first;
			else {
				// so it's the second;
				if(!prev($this->tmpAttrLevel[$this->tmpLevel])) {
					$this->pOut[key($this->track)][$tag.'-ATTR'] = array(
						current($this->tmpAttrLevel[$this->tmpLevel]) => $this->pOut[key($this->track)][$tag.'-ATTR'],
						next($this->tmpAttrLevel[$this->tmpLevel])    => $attr
					);
				}
				// or one other;
				else
					$this->pOut[key($this->track)][$tag.'-ATTR'][$this->tmpAttrLevel[$this->tmpLevel]['attrLevel']] = $attr;
			}
		}

		return true;
	}



	/**
	 * contentHandler
	 * Detect the pointer, or the multi-tag by callback.
	 * If we have a pointer, the method replaces this pointer by the content.
	 * Else we have a multi-tag, the method add a element to this array.
	 * Return true but built $this->pOut.
	 *
	 * @access  private
	 * @param   parser          resource    Parser resource.
	 * @param   contentHandler  string      Tag content.
	 * @return  bool
	 */
	function contentHandler ( $parser, $contentHandler ) {

		// remove all spaces;
		if(!preg_match('#^\s*$#', $contentHandler)) {

			// $contentHandler is a string;
			if(is_string($this->pOut[key($this->track)][current($this->track)])) {

				// then $contentHandler is a pointer : {{int ptr}}     case 1;
				if(preg_match('#{{([0-9]+)}}#', $this->pOut[key($this->track)][current($this->track)]))
					$this->pOut[key($this->track)][current($this->track)] = $contentHandler;

				// or then $contentHandler is a multi-tag content      case 2;
				else {
					$this->pOut[key($this->track)][current($this->track)] = array(
						0 => $this->pOut[key($this->track)][current($this->track)],
						1 => $contentHandler
					);
				}
			}
			// or $contentHandler is an array;
			else {

				// then $contentHandler is the multi-tag array         case 1;
				if(isset($this->pOut[key($this->track)][current($this->track)][0]))
					$this->pOut[key($this->track)][current($this->track)][] = $contentHandler;

				// or then $contentHandler is a node-tag               case 2;
				else
					$this->pOut[key($this->track)][current($this->track)] = array(
						0 => $this->pOut[key($this->track)][current($this->track)],
						1 => $contentHandler
					);
			}

		}

		return true;
	}



	/**
	 * endHandler
	 * Detect the last pointer by callback.
	 * Move the last tags block up.
	 * And reset some temp variables.
	 * Return true but built $this->pOut.
	 *
	 * @access  private
	 * @param   parser  resource    Parser resource.
	 * @param   tag     string      Tag name.
	 * @return  bool
	 */
	function endHandler ( $parser, $tag ) {

		// if level--;
		if(key($this->track) == $this->tmpLevel-1) {
			// search up tag;
			// use array_keys if an empty tag exists (taking the last tag);

			// if it's a normal framaset;
			$keyBack = array_keys($this->pOut[key($this->track)], '{{'.key($this->track).'}}');
			$count = count($keyBack);

			if($count != 0) {
				$keyBack = $keyBack{$count-1};
				// move this level up;
				$this->pOut[key($this->track)][$keyBack] = $this->pOut[key($this->track)+1];
			}

			// if we have a multi-tag framaset ($count == 0);
			else {
				// if place is set;
				if(isset($this->pOut[key($this->track)][current($this->track)][0])) {

					// if it's a string, we built an array;
					if(is_string($this->pOut[key($this->track)][current($this->track)]))
						$this->pOut[key($this->track)][current($this->track)] = array(
							0 => $this->pOut[key($this->track)][current($this->track)],
							1 => $this->pOut[key($this->track)+1]
						);

					// else add an index into the array;
					else
						$this->pOut[key($this->track)][current($this->track)][] = $this->pOut[key($this->track)+1];
				}
				// else set the place;
				else
					$this->pOut[key($this->track)][current($this->track)] = array(
						0 => $this->pOut[key($this->track)][current($this->track)],
						1 => $this->pOut[key($this->track)+1]
					);
			}

			// kick $this->pOut level out;
			array_pop($this->pOut);
			end($this->pOut);
		}

		// re-temp level;
		$this->tmpLevel = key($this->track);

		while(isset($this->tmpAttrLevel[$this->tmpLevel+1]))
			array_pop($this->tmpAttrLevel);

		// kick $this->track level out;
		array_pop($this->track);
		end($this->track);

		return true;
	}

}

?>


Utilisation
<?php

header('Content-type: text/plain');

include('lib.xml.php');

$xml = new Xml;
$r = $xml->parse('chemin/vers/le/fichier.xml');

print_r($r);

?>

voilà, merci de me dire ce que vous en penser

Posté : 18 déc. 2006, 20:31
par Cyrano
Intéressant. Toutefois, je ne saurais trop te recommander vivement de commenter davantage. Par exemple, rajouter un commentaire entre les informations sur la licence GPL et le début de la classe, puis au niveau des méthodes : tu as certes précisé un vague titre et les paramètre et retours, mais aucun commentaire expliquant ce que fait la méthode, ce qu'elle attend et ce qu'elle retourne. Pour créer une doc utilisateur utile, il n'y a rien à part une liste d'éléments. Donc il faudrait des choses du genre :
/**
 * Classe Xml.
 *
 * Description de la classe
 * @author Ton nom <[email protected]> (courriel facultatif, mais les utilisateur qui voudraient communiquer pour présenter des ajouts aimeraient pouvoir te joindre)
 * @since PHP4.x.x
 * @copyright Ton nom ou ta société 2006 par exemple
 * @link http://le.site.com/la/classe.php (s'il y a un site qui la décrit par exemple)
 * @package XML
 */
class Xml {
    //.... etc...
    /**
     * Xml::parse.
     * 
     * Méthode qui fait bla bla bla...
     * La méthode attend un parametre xyz et retourne gna gna...
     * @access public Comme c'est du PHP4, il est bon de préciser l'accès, ça aide beaucoup pour convertir rapidement en PHP5
     * @param   string    $src     Source
     * @param   string    $typeof  Type of source : NULL, FILE, CURL.
     * @return   array 
     */
    function parse($src, $typeof = 'FILE') {
        //.... etc ....
J'ai fait ça à l'arrache, il faudrait aussi faire la même chose pour les propriétés en précisant une courte description, le type comme tu l'as fait et l'accès public/private/protected comme indiqué dans les commentaire pour la méthode ci-dessus

Je comprends que ça puisse sembler fastidieux, mais dis toi que ça servira énormément aux utilisateur d'une part, et à toi-même le jour où, dans 6 mois, un an, deux ans, tu voudras reprendre ce code pour le rafraîchir un peu et ajouter de nouvelles fonctionnalités.

:)

Posté : 18 déc. 2006, 20:36
par Hywan
Merci beaucoup :)
je vais commenter tout ça et la republier (avec un edit)

sinon la licence GPL semble la plus appropriée non ?

et est-ce que c'est vraiment utile ou pas ? :P
histoire de la faire connaître si vraiment elle présente des avantages dans l'optique PHP/Xml :)

Merci

Posté : 18 déc. 2006, 20:42
par Cyrano
Disons que la licence GPL est une licence intéressante, pour ma part les rares fois où il pourrait m'arriver de publier, ce serait sous licence CeCILL, version en droit français compatible GPL. Et je te recommande de mettre une copie de la licence en fichier texte dans l'archive si on peut télécharger la classe quelque part. Tu pourrais même publier sur PHPClasses.org, il s'en publie entre deux et trois par jour de tous les pays du monde, c'est un annuaire intéressant. Mais j'y trouve trop souvent malheureusement des classes non commentées dans des packages parfois complexes et ça me prendrait moins de temps de développer mon propre système que d'arriver à décortiquer tout ça ;)

Posté : 18 déc. 2006, 20:55
par Hywan
Hehe ok :P

je vais commenter tout ça ce soir et demain (les partiels approchent, c'est chiant, sinon jle ferais plus), et après tu pourrais faire quelques tests s'il te plaît (mince tu ou vous ??) ? j'ai peut être oublié des cas particuliers etc.

y a aussi le fait que c'est ma première publication :P (normal, 18ans c'est pas encore bien vieux ^^) et j'ai jamais commenté mes libs

merci pour le conseil et l'exemple, je l'applique de suite

:)

Posté : 18 déc. 2006, 21:29
par Cyrano
Disons surtout que je me suis personnellement discipliné à toujours commenter mes codes au fur et à mesure, je n'attends en général même pas d'avoir fini. Après il peut m'arriver de corriger un détail, mais finalement, ça me simplifie la vie à un point non négligeable.

Et éventuellement oui, je testerai ta classe, ce sera pas trop dur, je te dirai ce que j'en pense après usage. Je ne suis pas le meilleur beta-testeur, il y en a qui ont un talent particulier pour tomber du premier coup sur le petit détail insignifiant qui fout tout par terre, vite corrigé en général ;) On fera avec.

Posté : 18 déc. 2006, 21:41
par Cyrano
Je viens de tester, ça fonctionne très bien :pouce:

Posté : 18 déc. 2006, 22:11
par Hywan
ok merci beaucoup =)

j'ai bientôt fini de commenter, ce sera demain en ligne (je pense vers 10am, si jsuis debout :D)
sinon où est-ce que je pourrais le publier, autre que phpclasses.org. car phpclasses.org me semble un peu foutoir :P rien de plus sérieux ou conventionnel ?

merci pour ton test :)
si d'autres veulent essayer, jdis pas le contraire

Posté : 18 déc. 2006, 22:22
par Cyrano
Ben disons que tu l'as déjà publié ici, donc c'est un premier pas, mais pour la visibilité mondiale, c'est un peu loupé. Sur phpclasses.org, ça a l'air foutoir, mais en fait tout est classé par catégories, tu devrais trouver celle où la tienne a le plus de pertinence : XML par exemple, je dis ça au hasard :langue:

Alors j'ai quand même trouvé un truc qui cloche. J'ai essayé de parser une page XHTML, puisque après tout, c'est du XML aussi. Petit problème d'encodage, ta classe me transforme des caractères iso-8859-1 en utf-8 mais l'affiche à l'écran en iso-8859-1, donc un "é" devient à l'affichage un "é" ce qui est énervant... Et ma page XHTML au départ est bien en iso-8859-1.

Posté : 18 déc. 2006, 22:27
par Hywan
uè jforce l'encodage en utf-8, on va dire qu'on va laisser le choix, jvais modifier ça pour demain aussi :)
merci j'avais zappé :P

edit : c'est bon c'est fait hihi ^^
et sinon sourceforge ? ca vaut le coup d'ouvrir un projet dessus ?

Posté : 18 déc. 2006, 22:39
par Cyrano
Sourceforge ? Ben disons que c'est davantage orienté vers des projets pour regrouper une communauté de développeurs autour d'un objectif précis. J'ai peur que ce soit un peu léger comme point de départ.

Pour l'encodage, je suggère de mettre un paramètre dans le constructeur pour laisser le choix de sortie selon le besoin, avec utf-8 par défaut si tu veux, mais au moins on aurait pas besoin de bidouiller.

Posté : 18 déc. 2006, 22:58
par Hywan
pour l'encodage c'est précisement ce que j'ai fais :)
j'ai ajouté un paramètre avec comme valeur par défaut : utf-8

sinon je développe un CMS, bon un petit CMS
c'est juste un projet plus personnel.
je veux juste développer ce CMS pour pouvoir créer des sites plus rapidement pour moi, et pour pouvoir passer plus de temps sur le contenu et le contenant (texte, design, etc.) plutôt que sur le code :)

donc si je publie cette classe sur sourceforge avec le reste de mon projet ?

bon, phpclasses.org me tente pas trop mal en fait
pour sourceforge on verra plus tard en fonction de l'avancement de mon travail

merci, à demain :)

PS : dingue ce probleme de connection à la table, déjà 5fois aujourd'hui ... :P

Posté : 19 déc. 2006, 13:34
par Hywan
Mise à jours de la lib Xml : voir la mise à jours de la lib

cette upd gère l'encodage du Xml (utf, iso, etc.) que l'on passe en argument du parseur, valeur par default : utf-8

je ferai une doc après mes partiels et noel :)

:)

joyeux noel au fait :D

Posté : 19 déc. 2006, 14:34
par Cyrano
Ha ben là c'est mieux.

Un détail me chagrine un peu. Ta méthode startHandler() retourne toujours TRUE, dans tous les cas de figure. Finalement, le retour ne sert à rien dans ce cas puisque tu sais d'avance ce qu'elle retourne.

Et si tu veux faire évoluer cette classe, je serais tenté de dire qu'il manque maintenant un système de gestion d'erreur.

Peut-être après les partiels ? ;)