PHP 8.3.0 RC 6 available for testing

xml_parse_into_struct

(PHP 4, PHP 5, PHP 7, PHP 8)

xml_parse_into_struct解析 XML 数据到数组中

说明

xml_parse_into_struct(
    XMLParser $parser,
    string $data,
    array &$values,
    array &$index = null
): int

该函数解析 XML 字符串到两个对应的数组中,一个(index)含有指向 values 数组中对应值的指针。最后两个参数必须通过引用传递。

参数

parser

引用的 XML 解析器。

data

包含 XML 数据的字符串。

values

包含 XML 数据值的数组

index

包含指向 $values 中适当值位置的指针的数组。

返回值

xml_parse_into_struct() 失败时返回 0,成功返回 1。这和 falsetrue 不同,使用运算符(比如 ===)时要小心。

更新日志

版本 说明
8.0.0 parser 现在接受 XMLParser 实例;之前接受有效的 xml resource

示例

下面的示例说明了函数生成的数组的内部结构。使用嵌入在 para 标签中的简单 note 标签,然后解析并打印出生成的结构:

示例 #1 xml_parse_into_struct() 示例

<?php
$simple
= "<para><note>simple note</note></para>";
$p = xml_parser_create();
xml_parse_into_struct($p, $simple, $vals, $index);
xml_parser_free($p);
echo
"Index array\n";
print_r($index);
echo
"\nVals array\n";
print_r($vals);
?>

运行以上代码,输出将是:

 
Index array
Array
(
    [PARA] => Array
        (
            [0] => 0
            [1] => 2
        )

    [NOTE] => Array
        (
            [0] => 1
        )

)

Vals array
Array
(
    [0] => Array
        (
            [tag] => PARA
            [type] => open
            [level] => 1
        )

    [1] => Array
        (
            [tag] => NOTE
            [type] => complete
            [level] => 2
            [value] => simple note
        )

    [2] => Array
        (
            [tag] => PARA
            [type] => close
            [level] => 1
        )

)

当 XML 文档很复杂时,事件驱动的解析(基于 expat 库)会变得复杂。此函数不会生成 DOM 风格对象,但会生成适合以树形方式遍历的结构。因此,可以轻松地创建表示 XML 文件中数据的对象。考虑以下表示氨基酸信息小型数据库的 XML 文件:

示例 #2 moldb.xml - 分子信息的小型数据库

<?xml version="1.0"?>
<moldb>

  <molecule>
      <name>Alanine</name>
      <symbol>ala</symbol>
      <code>A</code>
      <type>hydrophobic</type>
  </molecule>

  <molecule>
      <name>Lysine</name>
      <symbol>lys</symbol>
      <code>K</code>
      <type>charged</type>
  </molecule>

</moldb>
还有一些代码用来解析文档并生成相应对象:

示例 #3 parsemoldb.php - 将 moldb.xml 解析到分子(molecular)对象的数组中

<?php

class AminoAcid {
var
$name; // aa name
var $symbol; // three letter symbol
var $code; // one letter code
var $type; // hydrophobic, charged or neutral

function __construct ($aa)
{
foreach (
$aa as $k=>$v)
$this->$k = $aa[$k];
}
}

function
readDatabase($filename)
{
// read the XML database of aminoacids
$data = file_get_contents($filename);
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
xml_parse_into_struct($parser, $data, $values, $tags);
xml_parser_free($parser);

// loop through the structures
foreach ($tags as $key=>$val) {
if (
$key == "molecule") {
$molranges = $val;
// each contiguous pair of array entries are the
// lower and upper range for each molecule definition
for ($i=0; $i < count($molranges); $i+=2) {
$offset = $molranges[$i] + 1;
$len = $molranges[$i + 1] - $offset;
$tdb[] = parseMol(array_slice($values, $offset, $len));
}
} else {
continue;
}
}
return
$tdb;
}

function
parseMol($mvalues)
{
for (
$i=0; $i < count($mvalues); $i++) {
$mol[$mvalues[$i]["tag"]] = $mvalues[$i]["value"];
}
return new
AminoAcid($mol);
}

$db = readDatabase("moldb.xml");
echo
"** Database of AminoAcid objects:\n";
print_r($db);

?>
在执行完 parsemoldb.php 后,变量 $db 将包含有由 AminoAcid 对象组成的数组,该脚本的输出如下:
 
** Database of AminoAcid objects:
Array
(
    [0] => aminoacid Object
        (
            [name] => Alanine
            [symbol] => ala
            [code] => A
            [type] => hydrophobic
        )

    [1] => aminoacid Object
        (
            [name] => Lysine
            [symbol] => lys
            [code] => K
            [type] => charged
        )

)

add a note

User Contributed Notes 33 notes

up
15
efredricksen at gmail dot com
17 years ago
Perhaps the one true parser:? I modified xademax's fine code to tidy it up, codewise and style wise, rationalize some minor crazyness, and make names fit nomenclature from the XML spec. (There are no uses of eval, and shame on you people who do.)

<?php
class XmlElement {
var
$name;
var
$attributes;
var
$content;
var
$children;
};

function
xml_to_object($xml) {
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
xml_parse_into_struct($parser, $xml, $tags);
xml_parser_free($parser);

$elements = array(); // the currently filling [child] XmlElement array
$stack = array();
foreach (
$tags as $tag) {
$index = count($elements);
if (
$tag['type'] == "complete" || $tag['type'] == "open") {
$elements[$index] = new XmlElement;
$elements[$index]->name = $tag['tag'];
$elements[$index]->attributes = $tag['attributes'];
$elements[$index]->content = $tag['value'];
if (
$tag['type'] == "open") { // push
$elements[$index]->children = array();
$stack[count($stack)] = &$elements;
$elements = &$elements[$index]->children;
}
}
if (
$tag['type'] == "close") { // pop
$elements = &$stack[count($stack) - 1];
unset(
$stack[count($stack) - 1]);
}
}
return
$elements[0]; // the single top-level element
}

// For example:
$xml = '
<parser>
<name language="en-us">Fred Parser</name>
<category>
<name>Nomenclature</name>
<note>Noteworthy</note>
</category>
</parser>
'
;
print_r(xml_to_object($xml));
?>

will give:

xmlelement Object
(
[name] => parser
[attributes] =>
[content] =>
[children] => Array
(
[0] => xmlelement Object
(
[name] => name
[attributes] => Array
(
[language] => en-us
)

[content] => Fred Parser
[children] =>
)

[1] => xmlelement Object
(
[name] => category
[attributes] =>
[content] =>
[children] => Array
(
[0] => xmlelement Object
(
[name] => name
[attributes] =>
[content] => Nomenclature
[children] =>
)

[1] => xmlelement Object
(
[name] => note
[attributes] =>
[content] => Noteworthy
[children] =>
)

)

)

)

)
up
5
beaudurrant at gmail dot com
15 years ago
This is extending what Alf Marius Foss Olsen had posted above.

It takes into account array keys with the same name and uses an increment for them instead of overwriting the keys.

I am using it for SOAP requests (20K - 150K) and it parses very fast compared to PEAR.

<?

class XMLParser {

// raw xml
private $rawXML;
// xml parser
private $parser = null;
// array returned by the xml parser
private $valueArray = array();
private $keyArray = array();

// arrays for dealing with duplicate keys
private $duplicateKeys = array();

// return data
private $output = array();
private $status;

public function XMLParser($xml){
$this->rawXML = $xml;
$this->parser = xml_parser_create();
return $this->parse();
}

private function parse(){

$parser = $this->parser;

xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); // Dont mess with my cAsE sEtTings
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); // Dont bother with empty info
if(!xml_parse_into_struct($parser, $this->rawXML, $this->valueArray, $this->keyArray)){
$this->status = 'error: '.xml_error_string(xml_get_error_code($parser)).' at line '.xml_get_current_line_number($parser);
return false;
}
xml_parser_free($parser);

$this->findDuplicateKeys();

// tmp array used for stacking
$stack = array();
$increment = 0;

foreach($this->valueArray as $val) {
if($val['type'] == "open") {
//if array key is duplicate then send in increment
if(array_key_exists($val['tag'], $this->duplicateKeys)){
array_push($stack, $this->duplicateKeys[$val['tag']]);
$this->duplicateKeys[$val['tag']]++;
}
else{
// else send in tag
array_push($stack, $val['tag']);
}
} elseif($val['type'] == "close") {
array_pop($stack);
// reset the increment if they tag does not exists in the stack
if(array_key_exists($val['tag'], $stack)){
$this->duplicateKeys[$val['tag']] = 0;
}
} elseif($val['type'] == "complete") {
//if array key is duplicate then send in increment
if(array_key_exists($val['tag'], $this->duplicateKeys)){
array_push($stack, $this->duplicateKeys[$val['tag']]);
$this->duplicateKeys[$val['tag']]++;
}
else{
// else send in tag
array_push($stack, $val['tag']);
}
$this->setArrayValue($this->output, $stack, $val['value']);
array_pop($stack);
}
$increment++;
}

$this->status = 'success: xml was parsed';
return true;

}

private function findDuplicateKeys(){

for($i=0;$i < count($this->valueArray); $i++) {
// duplicate keys are when two complete tags are side by side
if($this->valueArray[$i]['type'] == "complete"){
if( $i+1 < count($this->valueArray) ){
if($this->valueArray[$i+1]['tag'] == $this->valueArray[$i]['tag'] && $this->valueArray[$i+1]['type'] == "complete"){
$this->duplicateKeys[$this->valueArray[$i]['tag']] = 0;
}
}
}
// also when a close tag is before an open tag and the tags are the same
if($this->valueArray[$i]['type'] == "close"){
if( $i+1 < count($this->valueArray) ){
if( $this->valueArray[$i+1]['type'] == "open" && $this->valueArray[$i+1]['tag'] == $this->valueArray[$i]['tag'])
$this->duplicateKeys[$this->valueArray[$i]['tag']] = 0;
}
}

}

}

private function setArrayValue(&$array, $stack, $value){
if ($stack) {
$key = array_shift($stack);
$this->setArrayValue($array[$key], $stack, $value);
return $array;
} else {
$array = $value;
}
}

public function getOutput(){
return $this->output;
}

public function getStatus(){
return $this->status;
}

}

?>

Usage:

$p = new XMLParser($xml);
$p->getOutput();
up
3
Alf Marius Foss Olsen
16 years ago
<?php
/*
An easy lightweight (Array ->) XML -> Array algorithm..

Typical case: You have an array you want to export to an external server,
so you make XML out of it, exports it, and "on the other side"
make it into an array again. These two functions will take care
of that last part, ie XML -> Array

NOTE! The function XMLToArray assumes that the XML _dont_ have nodes on the
same level with the same name, then it just wont work. This is not a
problem, as this case deals with Array -> XML -> Array, and an array
cant have to identical indexes/keys.
*/

function XMLToArray($xml) {
$parser = xml_parser_create('ISO-8859-1'); // For Latin-1 charset
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); // Dont mess with my cAsE sEtTings
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); // Dont bother with empty info
xml_parse_into_struct($parser, $xml, $values);
xml_parser_free($parser);

$return = array(); // The returned array
$stack = array(); // tmp array used for stacking
foreach($values as $val) {
if(
$val['type'] == "open") {
array_push($stack, $val['tag']);
} elseif(
$val['type'] == "close") {
array_pop($stack);
} elseif(
$val['type'] == "complete") {
array_push($stack, $val['tag']);
setArrayValue($return, $stack, $val['value']);
array_pop($stack);
}
//if-elseif
}//foreach
return $return;
}
//function XMLToArray

function setArrayValue(&$array, $stack, $value) {
if (
$stack) {
$key = array_shift($stack);
setArrayValue($array[$key], $stack, $value);
return
$array;
} else {
$array = $value;
}
//if-else
}//function setArrayValue

// USAGE:
$xml = <<<QQQ
<?xml version="1.0"?>
<root>
<node1>Some text</node1>
<node2a>
<node2b>
<node2c>Some text</node2c>
</node2b>
</node2a>
</root>\n
QQQ;
$array = XMLToArray($xml);
print
"<pre>";
print_r($array);
print
"</pre>";
// Output:
//
// Array
// (
// [root] => Array
// (
// [node1] => Some text
// [node2a] => Array
// (
// [node2b] => Array
// (
// [node2c] => Some text
// )
// )
// )
// )
?>
up
1
mathiasrav at gmail dot com
15 years ago
In response to Anonymous' post at 26-Feb-2008 11:50:

Saying that you "don't understand everything" isn't going to get you very popular - you should understand the code you use.
foreach isn't *slow* in PHP, it is actually faster than the equivalent for-construct (which, in many cases, isn't available).

The reason your script is slow is simply your use of xml_parse_into_struct - it reads the whole XML-string and doesn't return until it has parsed and validated it all. If you're looking for efficiency, you'll have to use the more low-level xml_parser_create, xml_set_*_handler functions. Then make sure you don't keep everything in a huge array before outputting it (at least don't if you're going for efficiency).
up
1
jemptymethod at gmail dot com
15 years ago
@wickedfather, re: beaudurrant....I just cannot concur that the latter's class is "great"....First of all, it doesn't handle attributes. Second of all, as my code below demonstrates, there is no need waste processing by determining duplicates within their own loop. Thirdly, when there are duplicates, it collapses the layer in the array above the duplicates.

Here is some much more consise code, though not (yet) object oriented, and also not yet handling attributes; it maintains a stack for each level:

<?php
function xml_parse_into_assoc($file) {
$data = implode("", file($file));
$p = xml_parser_create();

xml_parser_set_option($p, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($p, XML_OPTION_SKIP_WHITE, 1);

xml_parse_into_struct($p, $data, $vals, $index);
xml_parser_free($p);

$levels = array(null);

foreach (
$vals as $val) {
if (
$val['type'] == 'open' || $val['type'] == 'complete') {
if (!
array_key_exists($val['level'], $levels)) {
$levels[$val['level']] = array();
}
}

$prevLevel =& $levels[$val['level'] - 1];
$parent = $prevLevel[sizeof($prevLevel)-1];

if (
$val['type'] == 'open') {
$val['children'] = array();
array_push(&$levels[$val['level']], $val);
continue;
}

else if (
$val['type'] == 'complete') {
$parent['children'][$val['tag']] = $val['value'];
}

else if (
$val['type'] == 'close') {
$pop = array_pop($levels[$val['level']]);
$tag = $pop['tag'];

if (
$parent) {
if (!
array_key_exists($tag, $parent['children'])) {
$parent['children'][$tag] = $pop['children'];
}
else if (
is_array($parent['children'][$tag])) {
$parent['children'][$tag][] = $pop['children'];
}
}
else {
return(array(
$pop['tag'] => $pop['children']));
}
}

$prevLevel[sizeof($prevLevel)-1] = $parent;
}
}
?>
up
2
siteres at gmail dot com
16 years ago
PHP: XML to Array and backwards:

Here the XML with PHP solution: XML->Array and Array->XML.
Work with it as with usual array.

Sources are here:
http://mysrc.blogspot.com/2007/02/php-xml-to-array-and-backwards.html
(leave me comments:)

Example #1 (1.xml):

<ddd>
<onemore dd="55">
<tt>333</tt>
<tt ss="s1">555</tt>
<tt>777</tt>
</onemore>
<two>sdf rr</two>
</ddd>

The code:

$xml=xml2ary(file_get_contents('1.xml'));
print_r($xml);

Here is the Array result:

Array
(
[ddd] => Array (
[_c] => Array (
[_p] => Array *RECURSION*
[onemore] => Array (
[_a] => Array (
[dd] => 55
)
[_c] => Array (
[_p] => Array *RECURSION*
[tt] => Array (
[0] => Array (
[_v] => 333
)
[1] => Array (
[_a] => Array (
[ss] => s1
)
[_v] => 555
)
[2] => Array (
[_v] => 777
)
)
)
)
[two] => Array (
[_v] => sdf rr
)
)
)
)
up
1
dale at ucsc dot edu
15 years ago
here you can [re]assemble xml from a xml_parse_into_struct generated array: ie array2xml

<?php
$xml_parser
= xml_parser_create( 'UTF-8' ); // UTF-8 or ISO-8859-1
xml_parser_set_option( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
xml_parser_set_option( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
xml_parse_into_struct( $xml_parser, $my_text_xml, $aryXML );
xml_parser_free($xml_parser);

//now use aryXML array to xml string:
$o='';
foreach(
$aryXML as $tag ){
//tab space:
for($i=1; $i < $tag['level'];$i++)
$o.="\t";
if(
$tag['type']!='close'){
if(
$tag['type']!='cdata')
$o.='<'.$tag['tag'];
if(isset(
$tag['attributes'])){
foreach(
$tag['attributes'] as $attr=>$aval){
$o.=' '.$attr.'="'.$aval.'"';
}
}
if(
$tag['type']!='cdata'){
$o.=($tag['type']=='complete')?'/>'."\n":'>'."\n";
}
$o.=(isset($tag['value']))?$tag['value']:'';
}else{
$o.='</'.$tag['tag'].'>';
}
}
?>
up
1
wickedfather at hotmail dot com
15 years ago
Had a comment up that was removed saying I'd found drawbacks, but here's a class what I wrote which works fine for me. Comments removed due to post restrictions. Pass the xml into the parse method or in the constructor. Php4 and 5 safe.

class XMLThing
{
var $rawXML;
var $valueArray = array();
var $keyArray = array();
var $parsed = array();
var $index = 0;
var $attribKey = 'attributes';
var $valueKey = 'value';
var $cdataKey = 'cdata';
var $isError = false;
var $error = '';

function XMLThing($xml = NULL)
{
$this->rawXML = $xml;
}

function parse($xml = NULL)
{
if (!is_null($xml))
{
$this->rawXML = $xml;
}

$this->isError = false;

if (!$this->parse_init())
{
return false;
}

$this->index = 0;
$this->parsed = $this->parse_recurse();
$this->status = 'parsing complete';

return $this->parsed;
}

function parse_recurse()
{
$found = array();
$tagCount = array();

while (isset($this->valueArray[$this->index]))
{
$tag = $this->valueArray[$this->index];
$this->index++;

if ($tag['type'] == 'close')
{
return $found;
}

if ($tag['type'] == 'cdata')
{
$tag['tag'] = $this->cdataKey;
$tag['type'] = 'complete';
}

$tagName = $tag['tag'];

if (isset($tagCount[$tagName]))
{
if ($tagCount[$tagName] == 1)
{
$found[$tagName] = array($found[$tagName]);
}

$tagRef =& $found[$tagName][$tagCount[$tagName]];
$tagCount[$tagName]++;
}
else
{
$tagCount[$tagName] = 1;
$tagRef =& $found[$tagName];
}

switch ($tag['type'])
{
case 'open':
$tagRef = $this->parse_recurse();

if (isset($tag['attributes']))
{
$tagRef[$this->attribKey] = $tag['attributes'];
}

if (isset($tag['value']))
{
if (isset($tagRef[$this->cdataKey]))
{
$tagRef[$this->cdataKey] = (array)$tagRef[$this->cdataKey];
array_unshift($tagRef[$this->cdataKey], $tag['value']);
}
else
{
$tagRef[$this->cdataKey] = $tag['value'];
}
}
break;

case 'complete':
if (isset($tag['attributes']))
{
$tagRef[$this->attribKey] = $tag['attributes'];
$tagRef =& $tagRef[$this->valueKey];
}

if (isset($tag['value']))
{
$tagRef = $tag['value'];
}
break;
}
}

return $found;
}

function parse_init()
{
$this->parser = xml_parser_create();

$parser = $this->parser;
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
if (!$res = (bool)xml_parse_into_struct($parser, $this->rawXML, $this->valueArray, $this->keyArray))
{
$this->isError = true;
$this->error = 'error: '.xml_error_string(xml_get_error_code($parser)).' at line '.xml_get_current_line_number($parser);
}
xml_parser_free($parser);

return $res;
}
}
up
1
happyseb at free dot fr
19 years ago
I would just add something about the solution proposed by "gordon at kanazawa dot ac dot jp".

Something went wrong about the fact that "cdata" are treated the same way as "complete" in the xml_get_children first and in the xml_get_child then.

When ?
with this method, something like this :
<tagA>
<tagB>valueB</tagB>
valueA
<tagC>valueC</tagC>
</tagA>
is considered as the same thing as
<tagA>
<tagB>valueB</tagB>
<tagA>valueA</tagA>
<tagC>valueC</tagC>
</tagA>

The algorithm is nomore bijective.

Even if it might not be perfect :-) i propose a little modification of the xml_get_child function :

function xml_get_child(&$v, $children=NULL) {
$c = array(); // the "child"
if (isset($v['tag']) && $v['type'] != 'cdata') $c['tag'] = $v['tag'];
if (isset($v['value'])) $c['value'] = $v['value'];
if (isset($v['attributes'])) $c['attributes'] = $v['attributes'];
if (is_array($children)) $c['children'] = $children;
return $c;
}

moreover, the following function will take the tree (made from the xml_get_tree function) as a parameter and build the xml content back

/*
// setTree
// set xml Tree from a well-formed array
// manage indentation
// Parameters are :
// $1 (needed) : Tree to transform
// $2 (optional) : Indentation type (default is one tabulation)
// $3 (optional) : First indent (default is empty string)
*/

function setTree($Atree, $indentType = ' ', $Sindent = '')
{
if (! isset ($Atree['tag']) && isset ($Atree['value'])) return ($Sindent.trim($Atree['value'])."\r\n") ;
else {
$Stemp = $Sindent.'<'.$Atree['tag'].' ' ;
if (isset ($Atree['attributes']) && is_array ($Atree['attributes'])) {
foreach ($Atree['attributes'] as $Aattribute => $Svalue) $Stemp .= $Aattribute.'="'.$Svalue.'" ' ;
}
if (! isset ($Atree['children']) && ! isset ($Atree['value'])) $Stemp .= '/>'."\r\n" ;
else {
$Stemp .= '>'."\r\n" ;
if (isset ($Atree['value'])) $Stemp .= $Sindent.$indentType.trim($Atree['value'])."\r\n" ;
if (isset ($Atree['children']) && is_array ($Atree['children']))
foreach ($Atree['children'] as $Achild) $Stemp .= $this->setTree($Achild, $indentType, $Sindent.$indentType) ;
$Stemp .= $Sindent.'</'.$Atree['tag'].'>'."\r\n" ;
}
return ($Stemp) ;
}
}
up
0
Adam Tylmad
14 years ago
Thanx to jukea i've upgraded the characterData-function to allow the value 0. Here is the new complete code:

<?php

class XMLParser {
var
$path;
var
$result;

function
cleanString($string) {
return
trim(str_replace("'", "&#39;", $string));
}

function
XMLParser($encoding, $data) {
$this->path = "\$this->result";
$this->index = 0;

$xml_parser = xml_parser_create($encoding);
xml_set_object($xml_parser, &$this);
xml_set_element_handler($xml_parser, 'startElement', 'endElement');
xml_set_character_data_handler($xml_parser, 'characterData');

xml_parse($xml_parser, $data, true);
xml_parser_free($xml_parser);
}

function
startElement($parser, $tag, $attributeList) {
$this->path .= "->".$tag;
eval(
"\$data = ".$this->path.";");
if (
is_array($data)) {
$index = sizeof($data);
$this->path .= "[".$index."]";
} else if (
is_object($data)) {
eval(
$this->path." = array(".$this->path.");");
$this->path .= "[1]";
}

foreach(
$attributeList as $name => $value)
eval(
$this->path."->".$name. " = '".XMLParser::cleanString($value)."';");
}

function
endElement($parser, $tag) {
$this->path = substr($this->path, 0, strrpos($this->path, "->"));
}

function
characterData($parser, $data) {
if (
strlen($data = XMLParser::cleanString($data)))
eval(
$this->path." = '$data';");
}
}

?>
up
0
aerik at thesylvans dot com
14 years ago
Here's another attempt at dealing with rebuilding the xml from the array. I'm basically preventing certain tags from being self closing by just checking for them explicitly (sure wish expat didn't interpret <textarea /> and <textarea></textarea> the same way (as "complete" tags).

<?php
function array2xml($xmlary){
$o='';
foreach(
$xmlary as $tag ){
if(
$tag['tag'] == 'textarea' && !isset($tag['value'])){
//fake a value so it won't self close
$tag['value']='';
}
//tab space:
$t = '';
for(
$i=1; $i < $tag['level'];$i++){
$t.="\t";
}
switch(
$tag['type']){
case
'complete':
case
'open':
$o.=$t.'<'.$tag['tag'];
if(isset(
$tag['attributes'])){
foreach(
$tag['attributes'] as $attr=>$aval){
$o.=' '.$attr.'="'.$aval.'"';
}
//foreach
}//attributes
if($tag['type'] == 'complete'){
if(!isset(
$tag['value'])){
$o .= ' />'."\n";
} else {
$o .= '>'."\n".$t.$tag['value']."\n".$t.'</'.$tag['tag'].'>'."\n";
}
}else{
$o .= '>'."\n";
}
break;
case
'close':
$o .= $t.'</'.$tag['tag'].'>'."\n";
break;
case
'cdata':
$o .= $t.$tag['value']."\n";
break;
}
//switch
}//foreach
}
?>
up
0
Llorenc
15 years ago
Sorry, forgot my last post below, I confused CDATA sections (<![CDATA[]]>) with the value type "cdata", which despite its name it's very different! The first allows adding markup (&,<,>) as regular text and the second is to indicate a not complete node between the open and close nodes. What is true is that the CDATA delimiters are lost after xml_parse_into_struct so it's not possible to revert the process.

Please, feel free to remove my comments or leave them if you think they can help anyone.
up
1
farinspace-at-hotmail-dot-com
21 years ago
Wow, took a while to consume all of this. I am a noobie at XML, in fact i just really started working with it today. I went ahead and added to the above code by gdemartini@bol.com.br. I also added the update by nyk@cowham.net.

Added 'value' => $vals[$i]['value'] to the open case, as some nodes have both cdata as well as a children node. In previous versions of the functions the cdata of a node(a) that also had children would get stored in the children's array of the parent node(a). Now only children values are in the children array and the cdata is in the value key of the parent node(a).

Added 'value' => $vals[$i]['value'] to tree array as the vals array produced by PHP includes a value to the top most node if no children exist (for completeness).

Also, using xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE,1) will strip all white space from both around the XML tags as well as inside the cdata. I added a piece of code by waldo@wh-e.com to take care of this. All SPACE characters are now only stripped from around the XML tags and cdata spacing is retained.

CODE:
***************************************

function GetChildren($vals, &$i) {

$children = array();

while (++$i < sizeof($vals)) {

// compair type
switch ($vals[$i]['type']) {

case 'cdata':
$children[] = $vals[$i]['value'];
break;
case 'complete':
$children[] = array(
'tag' => $vals[$i]['tag'],
'attributes' => $vals[$i]['attributes'],
'value' => $vals[$i]['value']
);
break;
case 'open':
$children[] = array(
'tag' => $vals[$i]['tag'],
'attributes' => $vals[$i]['attributes'],
'value' => $vals[$i]['value'],
'children' => GetChildren($vals, $i)
);
break;
case 'close':
return $children;
}
}
}

function GetXMLTree($file) {

$data = implode('', file($file));

// by: waldo@wh-e.com - trim space around tags not within
$data = eregi_replace(">"."[[:space:]]+"."<","><",$data);

// XML functions
$p = xml_parser_create();

// by: anony@mous.com - meets XML 1.0 specification
xml_parser_set_option($p, XML_OPTION_CASE_FOLDING, 0);
xml_parse_into_struct($p, $data, &$vals, &$index);
xml_parser_free($p);

$i = 0;
$tree = array();
$tree[] = array(
'tag' => $vals[$i]['tag'],
'attributes' => $vals[$i]['attributes'],
'value' => $vals[$i]['value'],
'children' => GetChildren($vals, $i)
);

return $tree;
}

***************************************
up
0
wickedfather at hotmail dot com
15 years ago
To beaudurrant - that class is great and structures things in a very sensible way. Only problem is that it raises an error if a tag is empty, so would suggest a simple mod to the parse method just to add an isset test.

if (isset($val['value']))
{
$this->setArrayValue($this->output, $stack, $val['value']);
}
up
0
vinod at citadel-soft dot com
16 years ago
My previous code was having some bugs in, that is fixed now

<?php
class CSLXmlReader {
private
$tagstack;
private
$xmlvals;
private
$xmlvarArrPos;
private
$xmlfile;
function
__construct($filename) // constructor to intialize the stack and val array
{

$this->tagstack = array(); // contain the open tags till now
$this->xmlvals = array();
$this->xmlvarArrPos = $this->xmlvals; // temporary variable to hold the current tag position
$this->xmlfile = $filename;
}
function
readDatabase()
{
// read the XML database
$data = implode("", file($this->xmlfile));
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
xml_parse_into_struct($parser, $data, $values, $tags);
xml_parser_free($parser);

foreach(
$values as $key => $val) //
{
if(
$val['type'] == "open")
{
array_push($this->tagstack, $val['tag']);
$this->getArrayPath();
if(
count($this->xmlvarArrPos) > 0 && (!array_key_exists(0,$this->xmlvarArrPos)))
{
$temp1 = $this->xmlvarArrPos;
$this->xmlvarArrPos = array();
$this->xmlvarArrPos[0] = $temp1;
array_push($this->tagstack, 1);
}
else if(
array_key_exists(0,$this->xmlvarArrPos)){
$opncount = count($this->xmlvarArrPos);
array_push($this->tagstack, $opncount);
}
$tagStackPointer += 1;
}else if(
$val['type'] == "close")
{
while(
$val['tag'] != ($lastOpened = array_pop($this->tagstack))){}
}else if(
$val['type'] == "complete")
{
$this->getArrayPath();
if(
array_key_exists($val['tag'],$this->xmlvarArrPos))
{
if(
array_key_exists(0,$this->xmlvarArrPos[$val['tag']]))
{
$elementCount = count($this->xmlvarArrPos[$val['tag']]);
$this->xmlvarArrPos[$val['tag']][$elementCount] = $val['value'];
}else
{
$temp1 = $this->xmlvarArrPos[$val['tag']];
$this->xmlvarArrPos[$val['tag']] = array();
$this->xmlvarArrPos[$val['tag']][0] = $temp1;
$this->xmlvarArrPos[$val['tag']][1] = $val['value'];
}
} else
{
$this->xmlvarArrPos[$val['tag']] = $val['value'];
}
}
}
reset($this->xmlvals);
return
$this->xmlvals;
}
function
getArrayPath()
{

reset($this->xmlvals);
$this->xmlvarArrPos = &$this->xmlvals;
foreach(
$this->tagstack as $key)
{
$this->xmlvarArrPos = &$this->xmlvarArrPos[$key];

}
}

}

$readerObj = new CSLXmlReader("test.xml");
$xmlvals = $readerObj->readDatabase();

echo
"########## XML Values In array as the multidimentional array #############\n";
echo
"<pre>";
print_r($xmlvals);
echo
"</pre>";

?>
up
0
webmaster at after5webdesign dot com
16 years ago
That parser also has a problem in which it will not parse more items than the current level it is on. That is, parsing this: <1><2>A</2><2>B</2><2>C</2></1>
Will only result in this: A B
C is never processed.

It might be better with something like this:
$file = get_url('http://news.search.yahoo.com/news/rss?p=current+events', URL_CONTENT);
$data = $file['content'];
$xml_parser = xml_parser_create();
xml_parse_into_struct($xml_parser, $data, $vals, $index);
xml_parser_free($xml_parser);
//Uncomment the lines below to see the entire structure of your XML document
//echo "<pre>INDEX: \n";
//print_r ($index);
//echo "\n \n \n VALUES:";
//print_r ($vals);
//echo "</pre>";
$params = array();
$level = array();
$start_level = 1;
foreach ($vals as $xml_elem) {
if ($xml_elem['type'] == 'open') {
if (array_key_exists('attributes',$xml_elem)) {
list($level[$xml_elem['level']],$extra) = array_values($xml_elem['attributes']);
} else {
$level[$xml_elem['level']] = $xml_elem['tag'];
}
}
if ($xml_elem['type'] == 'complete') {
$php_stmt = '$params';
while($start_level < $xml_elem['level']) {
$php_stmt .= '[$level['.$start_level.']]';
$start_level++;
}
$php_stmt .= '[$xml_elem[\'tag\']][] = $xml_elem[\'value\'];';
eval($php_stmt);
$start_level--;
}
}
echo "<pre>";
print_r ($params);
echo "</pre>";

~Tim_Myth
up
0
Elad Elrom
17 years ago
This is a quick fix for parsing XML from remote URL, some of the example above will work when trying to parse on your local server without "http://" but not when trying to parse from remote server using "http://www.URL"...

<?
$file="http://www.URL.com/file.XML";

$xml_parser = xml_parser_create();

$handle = fopen($file, "rb");
$contents = '';
while (!feof($handle)) {
$data .= fread($handle, 8192);
}
fclose($handle);

xml_parse_into_struct($xml_parser, $data, $vals, $index);
xml_parser_free($xml_parser);

$params = array();
$level = array();
foreach ($vals as $xml_elem) {
if ($xml_elem['type'] == 'open') {
if (array_key_exists('attributes',$xml_elem)) {
list($level[$xml_elem['level']],$extra) = array_values($xml_elem['attributes']);
} else {
$level[$xml_elem['level']] = $xml_elem['tag'];
}
}
if ($xml_elem['type'] == 'complete') {
$start_level = 1;
$php_stmt = '$params';
while($start_level < $xml_elem['level']) {
$php_stmt .= '[$level['.$start_level.']]';
$start_level++;
}
$php_stmt .= '[$xml_elem[\'tag\']] = $xml_elem[\'value\'];';
eval($php_stmt);
}
}

echo "<pre>";
print_r ($params);
echo "</pre>";
?>
up
0
Stuart
17 years ago
This is a great little function for a lot of XML work, but note that this function does not handle XML entities properly.

The basic XML entities &lt; &gt; &amp; &quot; are fine, just anything else will not work:

If the entity is defined in the XML header, the parser will drop it completely from the struct it creates.

If the entity is not defined in the XML then the parser will crash out with an undefined entity error.

You should be able to work around this limitation by using a preg_replace on your XML string before passing it to the parser.

(Further details in Bug #35271; this is just a warning to those thinking of using this function for parsing real XML documents not just trivial XML examples)
up
0
xademax at gmail dot com
17 years ago
This is just another simple xml parser :)

<?php

class Xml
{
var
$tag;
var
$value;
var
$attributes;
var
$next;
}

function
xml2array($xml_string)
{
$Parser = xml_parser_create();
xml_parser_set_option($Parser, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($Parser, XML_OPTION_SKIP_WHITE, 1);
xml_parse_into_struct($Parser, $xml_string, $Xml_Values);
xml_parser_free($Parser);
$XmlClass = array();
$LastObj = array();
$NowObj = &$XmlClass;

foreach(
$Xml_Values as $Xml_Key => $Xml_Value)
{
$Index = count($NowObj);
if(
$Xml_Value["type"] == "complete")
{
$NowObj[$Index] = new Xml;
$NowObj[$Index]->tag = $Xml_Value["tag"];
$NowObj[$Index]->value = $Xml_Value["value"];
$NowObj[$Index]->attributes = $Xml_Value["attributes"];
}
elseif(
$Xml_Value["type"] == "open")
{
$NowObj[$Index] = new Xml;
$NowObj[$Index]->tag = $Xml_Value["tag"];
$NowObj[$Index]->value = $Xml_Value["value"];
$NowObj[$Index]->attributes = $Xml_Value["attributes"];
$NowObj[$Index]->next = array();
$LastObj[count($LastObj)] = &$NowObj;
$NowObj = &$NowObj[$Index]->next;
}
elseif(
$Xml_Value["type"] == "close")
{
$NowObj = &$LastObj[count($LastObj) - 1];
unset(
$LastObj[count($LastObj) - 1]);
}

}

return
$XmlClass;
}

$String = "
<parser>
<parseur_name>MyParser</parseur_name>
<category>
<name>Name 1</name>
<note>A note 1</note>
</category>
</parser>
"
;
$Xml = xml2array($String);

print_r($Xml);
?>

This exemple will show :
Array
(
[0] => Xml Object
(
[tag] => parser
[value] =>
[attributes] =>
[next] => Array
(
[0] => Xml Object
(
[tag] => parseur_name
[value] => MyParser
[attributes] =>
[next] =>
)

[1] => Xml Object
(
[tag] => category
[value] =>
[attributes] =>
[next] => Array
(
[0] => Xml Object
(
[tag] => name
[value] => Name 1
[attributes] =>
[next] =>
)

[1] => Xml Object
(
[tag] => note
[value] => A note 1
[attributes] =>
[next] =>
)

)

)

)

)

)
up
0
kieran at kieran dot ca
18 years ago
<?
/*
|
| _xml2array - another abstraction layer on xml_parse_into_struct
| that returns a nice nested array.
|
| @param: $xml is a string containing a full xml document
|
| returns: a nested php array that looks like this:
|
| array
| (
| [_name] => the name of the tag
| [_attributes] => an array of 'attribute'=>'value' combos
| [_value] => the text contents of the node
| [_children] => an array of these arrays, one for each node.
| )
|
| notes: thanks to 'jeffg at activestate dot com' who inspired
| me to essentially re-write his example code from php.net
|
| me: Kieran Huggins < kieran[at]kieran[dot]ca >
|
*/
function _xml2array($xml){
global $keys;
global $level;
if(!is_array($xml)){ // init on first run
$raw_xml = $xml;
$p = xml_parser_create();
xml_parser_set_option($p, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($p, XML_OPTION_SKIP_WHITE, 1);
xml_parse_into_struct($p, $raw_xml, $xml, $idx);
xml_parser_free($p);
}
for($i=0;$i<count($xml,1);$i++){
// set the current level
$level = $xml[$i]['level'];

if($level<1)break;

// mark this level's tag in the array
$keys[$level] = '['.$i.']';

// if we've come down a level, sort output and destroy the upper level
if(count($keys)>$level) unset($keys[count($keys)]);

// ignore close tags, they're useless
if($xml[$i]['type']=="open" || $xml[$i]['type']=="complete"){

// build the evalstring
$e = '$output'.implode('[\'_children\']',$keys);

// set the tag name
eval($e.'[\'_name\'] = $xml[$i][\'tag\'];');

// set the attributes
if($xml[$i]['attributes']){
eval($e.'[\'_attributes\'] = $xml[$i][\'attributes\'];');
}

// set the value
if($xml[$i]['value']){
eval($e.'[\'_value\'] = trim($xml[$i][\'value\']);');
}

}

}

return $output;
}
?>
up
0
Chris Hester
18 years ago
The array generated from XML stores not only the elements but also any spaces and linebreaks between the tags. This results in a much longer array. (I had 24 array fields instead of 10!) To cure this use the following code when creating the parser:

<?php
$xml_parser
= xml_parser_create();
xml_parser_set_option($xml_parser,XML_OPTION_SKIP_WHITE,1);
?>
up
0
PhF at madmac dot fr
18 years ago
The code previously posted by noob at noobsrule dot com doesn't work when the same tag name is used at different levels.
(but perhaps "$php_used_prefix" was intended for that ?)
For example:
<RatedShipment>
<TransportationCharges>
...
</TransportationCharges>
<RatedPackage>
<TransportationCharges>
...
</TransportationCharges>
</RatedPackage>
</RatedShipment>
<?php
$p
= xml_parser_create();
xml_parser_set_option($p, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($p, XML_OPTION_SKIP_WHITE, 1);
xml_parse_into_struct($p, $UPSxml, $values, $idx);
xml_parser_free($p);

// tracking used keys
$usedKeys = array();
$deepLevel = -1;

// start a php array string (evaluated later)
$forEvalPrefix = '$xml_array';

// loop throught the value array
foreach ($values as $key => $val) {
$tagName = $val['tag']; // pass the key tag into a more friendly looking variable
$level = $val['level']; // idem
if($val['type'] == 'open') {
$deepLevel++; // increase deep level
$forEvalPrefix .= '[\''. $tagName .'\']';

// begin used keys checks to allow multidimensionatity under the same tag
(isset($usedKeys[$level][$tagName])) ? $usedKeys[$level][$tagName]++ : $usedKeys[$level][$tagName] = 0;
$forEvalPrefix .= '['. $usedKeys[$level][$tagName] .']';
}
if(
$val['type'] == 'complete') {
(
$level > $deepLevel) ? $deepLevel++ : ''; // increase $deepLevel only if current level is bigger
$tagValue = addslashes($val['value'] ); // format the value for evaluation as a string
$forEvalSuffix = '[\''. $tagName .'\'] = \''. $tagValue .'\';'; // create a string to append to the current prefix
$forEval = $forEvalPrefix . $forEvalSuffix; // (without "$php_used_prefix"...)
eval($forEval); // write the string to the array structure
}
if(
$val['type'] == 'close') {
unset(
$usedKeys[$deepLevel]); // Suppress tagname's keys useless
$deepLevel--;
$forEvalPrefix = substr($forEvalPrefix, 0, strrpos($forEvalPrefix, '[')); // cut off the used keys node
$forEvalPrefix = substr($forEvalPrefix, 0, strrpos($forEvalPrefix, '[')); // cut off the end level of the array string prefix
}
}
echo
'<pre>';
print_r ($xml_array);
echo
'</pre>';
?>

MADmac ;-)
up
0
peter at elemental dot org
18 years ago
Quick addendum to my post below... The code does *not* handle cases where multiple elements are used without attribute IDs, as is the case with the moldb example. (It is probably worth noting that some of the other code below doesn't do this either, and is still more complex.)

It is not difficult to add that functionality, but I have yet to find an elegant way to integrate that feature, and since I don't need that functionality at the moment, I'm going leave that as an exercise to the reader. ;-)

-p.
up
0
peter at elemental dot org
18 years ago
Some of the code posted previously seemed absurdly complex, so I thought I'd see if I could simplify things a bit. Here's what I came up with. (I'm still a PHP newbie, so perhaps I simplified too much. ;-) Untested for speed, but it *should* be pretty quick since it uses references instead of lookups or (gack) evals.

Enjoy!

/*
* Convert a string containing XML into a nested array
*
* Takes a raw XML string as input
* Returns a nested array
*/

function xml2array ($xml_data)
{
// parse the XML datastring
$xml_parser = xml_parser_create ();
xml_parse_into_struct ($xml_parser, $xml_data, $vals, $index);
xml_parser_free ($xml_parser);

// convert the parsed data into a PHP datatype
$params = array();
$ptrs[0] = & $params;
foreach ($vals as $xml_elem) {
$level = $xml_elem['level'] - 1;
switch ($xml_elem['type']) {
case 'open':
$tag_or_id = (array_key_exists ('attributes', $xml_elem)) ? $xml_elem['attributes']['ID'] : $xml_elem['tag'];
$ptrs[$level][$tag_or_id] = array ();
$ptrs[$level+1] = & $ptrs[$level][$tag_or_id];
break;
case 'complete':
$ptrs[$level][$xml_elem['tag']] = (isset ($xml_elem['value'])) ? $xml_elem['value'] : '';
break;
}
}

return ($params);

}
up
0
noob at noobsrule dot com
18 years ago
This is my first time contributing but I found all the examples either didn't work or were beyond my comprehension. I also wanted to maintain the XML multi-dimensionality so I could do loops based on keys I was expecting rather than numbers or some other whatnot. This script is probably way inefficient, but should give you a multidimensional array with a depth that will match the XML source. Great for situations when you aren't sure what you'll be getting back. Its also only based on the values array of xml_parse_into_str() using the 3 tag types (open, complete, and close) -- hopefully it will make more sense to other noobs.

Basically it just constructs a string ($php_prefix) that is tweaked if it hits an "open" or "close" tag and evaluated if it hits a "complete" tag.

Thanks.

<?php

# initiate curl session...
$ch = curl_init( "http://yourtargethost" ) or die ( "could not initialize curl" );

# set curl options...
curl_setopt( $ch, CURLOPT_HEADER, 0 );
curl_setopt( $ch, CURLOPT_POST, 1 );
curl_setopt( $ch, CURLOPT_POSTFIELDS, $request_str );
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 0 );
curl_setopt( $ch, CURLOPT_REFERER, https://foobar.com" );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );

# execute curl commands...
$curl_result = curl_exec( $ch ) or die ( "could not execute the post" );
curl_close( $ch ); // close curl session

# pass the XML result into 2 arrays ( index and values )...
$p = xml_parser_create();
xml_parser_set_option($p, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($p, XML_OPTION_SKIP_WHITE, 1);
xml_parse_into_struct($p, $curl_result, $vals, $index);
xml_parser_free($p);

# create an empty used key tracker...
$used_keys = array();

# start a php array string (evaluated later)...
$php_prefix = "$"."my_array";

# loop through the values array...
foreach ($vals as $key => $val) {
if(
$val['type'] == "open" ) {
$my_tag = $val['tag']; // pass the key tag into a more friendly looking variable...
$php_prefix .= "['".$my_tag."']"; # add a new level to the array string...

# begin used key checks to allow multidimensionality under the same tag, adds a number to the prefix if necessary...
if( ! array_key_exists( $my_tag, $used_keys ) ) {
$used_keys[$my_tag] = 0;
$php_prefix .= "[".$used_keys[$my_tag]."]";
}else {
$used_keys[$my_tag]++; // increment the key tracker to avoid overwriting existing key...
$php_prefix .= "[".$used_keys[$my_tag]."]";
}
} elseif(
$val['type'] == "complete" ) {
$my_value = addslashes( $val['value'] ); # format the value for evaluation as a string...
$php_suffix = "['".$val['tag']."'] = \"".$my_value."\";"; # create a string with the tag and value to append to the current prefix (just used here)...
$php_str = $php_prefix.$php_used_prefix.$php_suffix;
eval(
$php_str ); # write the string to the array structure...
}
if(
$val['type'] == "close" ) {
$php_prefix = substr( $php_prefix, 0, strrpos( $php_prefix, "[" ) ); # cut off the used keys node...
$php_prefix = substr( $php_prefix, 0, strrpos( $php_prefix, "[" ) ); # cut off the end level of the array string prefix...
}
}

echo
"<pre>";
//print_r ($index);
//echo "<br><br><br>";
//print_r ($vals);
//echo "<br><br><br>";
print_r ($my_array);
echo
"</pre>";

?>
up
0
dudus at onet dot pl
18 years ago
This function return array from XML. Included attributes and tags in same level.
I wrote this function based on mmustafa code.

enjoy!
dUDA

-------------------------------

<?
function XMLtoArray($XML)
{
$xml_parser = xml_parser_create();
xml_parse_into_struct($xml_parser, $XML, $vals);
xml_parser_free($xml_parser);
// wyznaczamy tablice z powtarzajacymi sie tagami na tym samym poziomie
$_tmp='';
foreach ($vals as $xml_elem)
{
$x_tag=$xml_elem['tag'];
$x_level=$xml_elem['level'];
$x_type=$xml_elem['type'];
if ($x_level!=1 && $x_type == 'close')
{
if (isset($multi_key[$x_tag][$x_level]))
$multi_key[$x_tag][$x_level]=1;
else
$multi_key[$x_tag][$x_level]=0;
}
if ($x_level!=1 && $x_type == 'complete')
{
if ($_tmp==$x_tag)
$multi_key[$x_tag][$x_level]=1;
$_tmp=$x_tag;
}
}
// jedziemy po tablicy
foreach ($vals as $xml_elem)
{
$x_tag=$xml_elem['tag'];
$x_level=$xml_elem['level'];
$x_type=$xml_elem['type'];
if ($x_type == 'open')
$level[$x_level] = $x_tag;
$start_level = 1;
$php_stmt = '$xml_array';
if ($x_type=='close' && $x_level!=1)
$multi_key[$x_tag][$x_level]++;
while($start_level < $x_level)
{
$php_stmt .= '[$level['.$start_level.']]';
if (isset($multi_key[$level[$start_level]][$start_level]) && $multi_key[$level[$start_level]][$start_level])
$php_stmt .= '['.($multi_key[$level[$start_level]][$start_level]-1).']';
$start_level++;
}
$add='';
if (isset($multi_key[$x_tag][$x_level]) && $multi_key[$x_tag][$x_level] && ($x_type=='open' || $x_type=='complete'))
{
if (!isset($multi_key2[$x_tag][$x_level]))
$multi_key2[$x_tag][$x_level]=0;
else
$multi_key2[$x_tag][$x_level]++;
$add='['.$multi_key2[$x_tag][$x_level].']';
}
if (isset($xml_elem['value']) && trim($xml_elem['value'])!='' && !array_key_exists('attributes',$xml_elem))
{
if ($x_type == 'open')
$php_stmt_main=$php_stmt.'[$x_type]'.$add.'[\'content\'] = $xml_elem[\'value\'];';
else
$php_stmt_main=$php_stmt.'[$x_tag]'.$add.' = $xml_elem[\'value\'];';
eval($php_stmt_main);
}
if (array_key_exists('attributes',$xml_elem))
{
if (isset($xml_elem['value']))
{
$php_stmt_main=$php_stmt.'[$x_tag]'.$add.'[\'content\'] = $xml_elem[\'value\'];';
eval($php_stmt_main);
}
foreach ($xml_elem['attributes'] as $key=>$value)
{
$php_stmt_att=$php_stmt.'[$x_tag]'.$add.'[$key] = $value;';
eval($php_stmt_att);
}
}
}
return $xml_array;
} // END XMLtoArray
?>
up
0
sdh33 at cornell dot edu
19 years ago
(The previous post I wrote was incorrect. I fix that herein)
I used the algorithm from stephane for writing back to a file, but ran into the following problem. My original XML looks like

<ENTRY id="1"> ... </ENTRY><ENTRY id="2"> ... </ENTRY>

This got saved back as

<1> ... </1><2> ... </2>,

which expat doesn't like. So I propose the following modification:

function writeXmlToFile($data, $f, $niv = 0) {
foreach($data as $name => $elem) {
if (preg_match("/\A[0-9]/",$name)) {
$open = "ENTRY id=\"$name\""; $name = "ENTRY";
} else {
$open = $name;
}
if(is_array($elem)) {
fwrite($f, str_repeat(" ", $niv)."<".$open.">"."\n");
writeXmlToFile($elem, $f, $niv + 1);
fwrite($f, str_repeat(" ", $niv)."</".$name.">"."\n");
} else {
fwrite($f, str_repeat(" ", $niv)."<".$open.">".$elem."</".$name.">"."\n");
}
}
}
up
0
stephane dot dekeyzer at noemail dot com
19 years ago
for writing the array of mmustafa back to a xml file, use this code:

function writeXmlFile($fileName, $struct){
$f = fopen($fileName, "w");
writeXmlToFile($struct, $f);
fclose($f);
}

function writeXmlToFile($data, $f, $niv = 0){
foreach($data as $name => $elem){
if(is_array($elem)){
fwrite($f, str_repeat(" ", $niv)."<".$name.">"."\n");
writeXmlToFile($elem, $f, $niv + 1);
fwrite($f, str_repeat(" ", $niv)."</".$name.">"."\n");
}
else{
fwrite($f, str_repeat(" ", $niv)."<".$name.">".$elem."</".$name.">"."\n");
}
}
}
up
0
mmustafa at vsnl dot com
19 years ago
A small and simple php code to convert the raw array returned by xml_parse_into_struct into a proper data structure ..

<?php
$file
= "data.xml";
$xml_parser = xml_parser_create();

if (!(
$fp = fopen($file, "r"))) {
die(
"could not open XML input");
}

$data = fread($fp, filesize($file));
fclose($fp);
xml_parse_into_struct($xml_parser, $data, $vals, $index);
xml_parser_free($xml_parser);

$params = array();
$level = array();
foreach (
$vals as $xml_elem) {
if (
$xml_elem['type'] == 'open') {
if (
array_key_exists('attributes',$xml_elem)) {
list(
$level[$xml_elem['level']],$extra) = array_values($xml_elem['attributes']);
} else {
$level[$xml_elem['level']] = $xml_elem['tag'];
}
}
if (
$xml_elem['type'] == 'complete') {
$start_level = 1;
$php_stmt = '$params';
while(
$start_level < $xml_elem['level']) {
$php_stmt .= '[$level['.$start_level.']]';
$start_level++;
}
$php_stmt .= '[$xml_elem[\'tag\']] = $xml_elem[\'value\'];';
eval(
$php_stmt);
}
}

echo
"<pre>";
print_r ($params);
echo
"</pre>";
?>

Converts an XML document like ..

<country id="ZZ">
<name>My Land</name>
<location>15E</location>
<area>40000</area>
<state1>
<name>Hi State</name>
<area>1000</area>
<population>2000</population>
<city1>
<location>13E</location>
<population>500</population>
<area>500</area>
</city1>
<city2>
<location>13E</location>
<population>500</population>
<area>5000</area>
</city2>
</state1>
<state2>
<name>Low State</name>
<area>3000</area>
<population>20000</population>
<city1>
<location>15E</location>
<population>5000</population>
<area>1500</area>
</city1>
</state2>
</country>

To a php datastructure like ..

Array
(
[ZZ] => Array
(
[NAME] => My Land
[LOCATION] => 15E
[AREA] => 40000
[STATE1] => Array
(
[NAME] => Hi State
[AREA] => 1000
[POPULATION] => 2000
[CITY1] => Array
(
[LOCATION] => 13E
[POPULATION] => 500
[AREA] => 500
)

[CITY2] => Array
(
[LOCATION] => 13E
[POPULATION] => 500
[AREA] => 5000
)

)

[STATE2] => Array
(
[NAME] => Low State
[AREA] => 3000
[POPULATION] => 20000
[CITY1] => Array
(
[LOCATION] => 15E
[POPULATION] => 5000
[AREA] => 1500
)

)

)

)
up
0
bisqwit at iki dot fi dot not dot for dot ads dot invalid
20 years ago
I made my own xml2tree()-function.
The difference to the previously suggested ones on this manual page is that this creates a tree where the tags are keys.

Example input:

<tree>
<leaf name="gruu"> pl?? </leaf>
<animal type="cat" />
</tree>

Example output:

Array
( 'tree' => Array
( 0 => Array
( 'children' => Array
( 'leaf' => Array
( 0 => Array
( 'attrs' => Array('name' => 'gruu'),
'values' => Array(0 => ' pl?? ')
) ),
'animal' => Array
( 0 => Array
( 'attrs' => Array('type' => 'cat')
) ) ) ) ) )

function xml2tree2($string)
{
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
xml_parse_into_struct($parser, $string, $vals, $index);
xml_parser_free($parser);

$stack = array( array() );
$stacktop = 0;
$parent = array();
foreach($vals as $val)
{
$type = $val['type'];
if($type=='open' || $type=='complete')
{
// open tag
$stack[$stacktop++] = $tagi;
$tagi = array('tag' => $val['tag']);

if(isset($val['attributes'])) $tagi['attrs'] = $val['attributes'];
if(isset($val['value']))
$tagi['values'][] = $val['value'];
}
if($type=='complete' || $type=='close')
{
// finish tag
$tags[] = $oldtagi = $tagi;
$tagi = $stack[--$stacktop];
$oldtag = $oldtagi['tag'];
unset($oldtagi['tag']);
$tagi['children'][$oldtag][] = $oldtagi;
$parent = $tagi;
}
if($type=='cdata')
{
$tagi['values'][] = $val['value'];
}
}
return $parent['children'];
}

Ps: Note that the xml parsing for some reason chokes if the values contain <> (even inside ""). You need to escape them as &lt; and &gt;.
Same for & (for obvious reasons). Escape it as &amp;.
For example "&nbsp;" should be written as "&amp;nbsp;".
up
0
Anonymous
21 years ago
This is based on a comment above by mh@mainlab.de. This version makes more sense to me and I included a few comments that might make it more usefull for specific purposes.

// Look through $vals and $index,
// generated by parse_xml_data(),
// for $location. $location resembles a unix path.
// E.g. "my/xml/path/to/my/data".
// Note: This is NOT a full-feature xml utility.
// Your path must be unique in the xml data
// and the tags must not contain slashes.
//
// returns: cdata from last element of $location
// or NULL.
function get_xml_values($vals, $index, $location) {
$location = explode('/', $location);
$location_level = count($location);
for ($n=0; $n<$location_level; ++$n) {
$val_indexes=$index[$location[$n]];
$good=0;
for ($i=0; $i<count($val_indexes); ++$i) {
$v = $vals[$val_indexes[$i]];
// level is 1 based, n is 0 based:
if ($v['level']-1 == $n) {
if ($v['type'] == 'open') {
// We found an open tag at the correct level.
$good=1;
break; // Remove break to avoid 'open' hiding
// 'complete' at last $n. (Speed Vs. Robustness:
// For now, I'm going with speed).
}
if ($v['type'] == 'complete') {
// We found it, after finding all the prior elements:
// You may want to change this to
// return $v
// so you can call $v['value'] and $v['attributes']
return $v['value'];
}
}
}
if (!$good) {
return NULL;
}
}
}
up
0
dave at ovumdesign dot com
21 years ago
Here is another variation on gdemartini@bol.com.br's nice tree algorithm. I have included nyk@cowham.net's "cdata" suggestion. I made a few changes to suppress unitialized variable warnings with error_reporting(E_ALL), and made a few stylistic changes.

There are two functional modifications as well. The first is the added option to expat to turn off "case folding", since XML is technically a case-sensitive language. The second is the returned tree - instead of returning a single-element list (an array with index 0 pointing to the top node) like the original algorithm, it returns the top node itself. This saves you an unnecessary level of indirection.

function xml_get_children($vals, &$i) {
$children = array();
if (isset($vals[$i]['value'])) $children[] = $vals[$i]['value'];

while (++$i < count($vals)) {
switch ($vals[$i]['type']) {
case 'cdata':
$children[] = $vals[$i]['value'];
break;

case 'complete':
$children[] = array(
'tag' => $vals[$i]['tag'],
'attributes' => isset($vals[$i]['attributes'])?
$vals[$i]['attributes'] : null,
'value' => $vals[$i]['value'],
);
break;

case 'open':
$children[] = array(
'tag' => $vals[$i]['tag'],
'attributes' => isset($vals[$i]['attributes'])?
$vals[$i]['attributes'] : null,
'children' => xml_get_children($vals, $i),
);
break;

case 'close':
return $children;
}
}
}

function xml_get_tree($file) {
$data = join('', file($file));

$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
xml_parse_into_struct($parser, $data, $vals, $index);
xml_parser_free($parser);

return array(
'tag' => $vals[0]['tag'],
'attributes' => isset($vals[0]['attributes'])?
$vals[0]['attributes'] : null,
'children' => xml_get_children($vals, $i = 0),
);
}

This function takes a tree returned by xml_get_tree() and simplifies it similarly to voncken@mailandnews.com and oknapp@soly.de's suggestions. It finds any nodes that expat considered "complete" (childless) and contain no attributes, and it moves the tag name (as the key) and value into an element in the parent node's "values" array.

For example:
$tree = xml_get_tree('moldb.xml');
$tree = xml_condense_tree($tree);
echo $tree['children'][1]['values']['name'];

Outputs:
Lysine

function xml_condense_tree($tree) {
foreach ($tree['children'] as $index => $node) {
if (isset($node['children'])) {
$tree['children'][$index] = xml_condense_tree($node);

} elseif (isset($node['value']) and !$node['attributes']) {
$tree['values'][$node['tag']] = $node['value'];
unset($tree['children'][$index]);
}
}

if (!$tree['children']) unset($tree['children']);
return $tree;
}

Enjoy,
Dave
up
-1
mbirth at webwriters dot de
18 years ago
Searching for a nice and working way to get a RSS feed into an array-structure, I found the solution posted by kieran but disliked those several eval()s. So I wrote my own using references/pointers.

<?php

class RSSParser {

var
$struct = array(); // holds final structure
var $curptr; // current branch on $struct
var $parents = array(); // parent branches of current branch

function RSSParser($url) {
$this->curptr =& $this->struct; // set ref to base
$xmlparser = xml_parser_create();
xml_set_object($xmlparser, $this);
xml_set_element_handler($xmlparser, 'tag_open', 'tag_close');
xml_set_character_data_handler($xmlparser, 'cdata');
$fp = fopen($url, 'r');

while (
$data = fread($fp, 4096))
xml_parse($xmlparser, $data, feof($fp))
|| die(
sprintf("XML error: %s at line %d",
xml_error_string(xml_get_error_code($xmlparser)),
xml_get_current_line_number($xmlparser)));

fclose($fp);
xml_parser_free($xmlparser);
}

function
tag_open($parser, $tag, $attr) {
$i = count($this->curptr['children']);
$j = count($this->parents);
$this->curptr['children'][$i]=array(); // add new child element
$this->parents[$j] =& $this->curptr; // store current position as parent
$this->curptr =& $this->curptr['children'][$i]; // submerge to newly created child element
$this->curptr['name'] = $tag;
if (
count($attr)>0) $this->curptr['attr'] = $attr;
}

function
tag_close($parser, $tag) {
$i = count($this->parents);
if (
$i>0) $this->curptr =& $this->parents[$i-1]; // return to parent element
unset($this->parents[$i-1]); // clear from list of parents
}

function
cdata($parser, $data) {
$data = trim($data);
if (!empty(
$data)) {
$this->curptr['value'] .= $data;
}
}

}

$myparser = new RSSParser('getitems.xml');
$anotherparser = new RSSParser('http://johndoe:secret@myfeeds.com/getfeed.xml');

print_r($myparser->struct);
print_r($anotherparser->struct);

?>
To Top