Hi, I'm sure someone's already done this (and much better) but I it was fun to do.
It's a class to help generate xml output.
I know that the $NAME_PREG needs work at the moment as it doesn't allow non Latin-1 characters, also the xmlize method can be broken pretty easily so I'd really like advice on those in particular. Obviously anything else which comes up.
Well here's the code
<?php
class xmlCreate {
var $INVALID_SEARCH = array('<','>','&', '"', "'");
var $INVALID_REPLACE = array('<','>','&','"', ''');
VAR $NAME_PREG = '/^[a-zA-Z\-\.\_:0-9\xC0-\xFF]+$/'; //Doesn't include non-ascii characters from extended character sets
var $err=null;
var $stack=array();
var $depth=0;
var $content;
/* xmlCreate - Constructor
* @return null - It's a constructor <img src="images/smilies/rolleyes.gif" border="0" alt="">
* @desc - It just adds the xml declaration to the top of the contents (assumes Latin 1)
*/
function xmlCreate() {
$this->content='<?xml version="1.0" encoding="ISO-8859-1"?>'."\n";
}
/* openElement - Open a new element at the correct indent depth
* @return bool
* @param? String $name - The element name
* @param? Array $att_names - An array of attribute names (must be the same size as the $att_vals array)
* @param Array $att_vals - An array of attribute vals (must be the same size as the $att_names array)
* @desc - This method adds an extra element to the output at the correct depth. The element and attribute names are checked for illegal characters
* - and return false they contain them. The attribute values have any illegal values replaced with their ASCII code. The element name is added
* - to the element stack and the depth is incremented.
*/
function openElement($name, $att_names=array(), $att_vals=array()) {
//Check the two arrays' lengths match up
if(count($att_names) != count($att_vals)) {
$this->err='Attribute names and values mismatch';
return false;
} else {
if(!preg_match($this->NAME_PREG, $name)) {
$this->err='The element name contains invalid characters';
return false;
}
if(count(preg_grep($this->NAME_PREG, $att_names)) != count($att_names)) {
$this->err='One or more of the element attributes contains invalid characters';
return false;
}
//I've never used str_replace with all parameters as arrays so I'm not quite sure how well this will work.
$att_vals=str_replace($this->INVALID_CHARS, $this->INVALID_REPLACE, $att_vals);
//Add the name to the stack, we don't really need the depth var as we can just do a count on stack but what the hell <img src="images/smilies/wink.gif" border="0" alt="">
$this->stack[]=$name;
//indent and add the element
$this->content.=str_repeat(' ', $this->depth*4).'<'.$name;
for($i=0;$i<count($att_names);$i++) {
$this->content.=' '.$att_names[$i].'="'.$att_vals[$i].'"';
}
$this->content.=">\n";
$this->depth++;
return true;
}
}
/* addContent - Add raw content
* @return bool
* @param String $content - The raw contents which is to be appended to the current content
* @desc - Not much to say here, it's a prtty simple method
*/
function addContent($content) {
//replace invalid characters and append to the content
$this->content.=str_repeat(' ', ($this->depth+1)*4).str_replace($this->INVALID_CHARS, $this->INVALID_REPLACE, trim($content));
return true;
}
/* closeElement - Close one or more open elements
* @return bool
* @param? int $num - Optionally add this paramters if you wish to close more than one element at a time
* @desc - Again, not much to say really, it closes elements. The element name is in the stack so it just pops it off the end,
* - decrements the depth and appends the closing tag. Note, if you try to close more elements than are open it will return false
*/
function closeElement($num=1) {
if($num>$this->depth) {
$this->err='Closing more elements than are open :/';
return false;
}
for($i=0;$i<$num;$i++) {
$this->depth--;
$element=array_pop($this->stack);
$this->content.="\n".str_repeat(' ',$this->depth*4).'</'.$element.">\n";
}
return true;
}
/* writeToFile - Write the XML to a file
* @return bool
* @param String $file - This is the path (full or relative) of the file which the output is to be written to
* @desc - These output methods (both this one and the writeToOutput method) use the checkWrite method which checks whether
* - or not all elements have been closed
*/
function writeToFile($file) {
if(!$this->checkWrite()) {
return false;
}
if(!$fh=fopen($file, 'w')) {
$this->err='Could not open handle on file '.$file;
return false;
} else {
if(fwrite($fh, $this->content) === false) {
$this->err='Could not write to file '.$file;
fclose($fh);
return false;
} else {
fclose($fh);
return true;
}
}
}
/* writeToOutput - Write the XML to Stdout
* @return bool
* @desc - Essentially the same as writeToFile but it writes to the output using echo()
*/
function writeToOutput() {
if(!$this->checkWrite()) {
return false;
}
header('Content-type: text/xml;');
echo($this->content);
return true;
}
/* checkWrite - Check all elements are closed
* @return bool
* @desc - Does exactly what is says on the tin. It checks whether or not all the elements have been closed
*/
function checkWrite() {
if($this->depth>0 || count($this->stack)>0) {
$this->err='Elements still open';
return false;
} else {
return true;
}
}
/* xmlize - Turn a simple array into xml
* @return bool
* @param Array $array - The array which is to be turned into xml
* @param? Array $iteration_replace - It the array is iterative then you can replace the numeric values with a element name
* @desc - Turns a simple array into xml, breaks quite easily ... ideas on improving it?
*/
function xmlize($array, $iteration_replace=false) {
foreach($array as $key => $value) {
if($iteration_replace!=false && is_numeric($key)) {
$key=$iteration_replace;
}
if(!$this->openElement($key)) {
return false;
}
if(is_array($value)) {
if(!$this->xmlize($value, $iteration_replace))
return false;
} else {
$this->addContent($value);
}
$this->closeElement();
}
return true;
}
/* getErr - Return the last error which occured */
function getErr() {
return $this->err;
}
}
An an example of use
$xml = new xmlCreate();
if(!$xml->xmlize(array('users'=>$users), 'user'))
die($xml->getErr());
$xml->writeToOutput();
Cheers
Bubble