22

In PHP, it is easy to pass back JSON objects by using the json_encode() function.

Is there an XML equivalent for this?

PhilMasteG
  • 3,095
  • 1
  • 20
  • 27
lulalala
  • 17,572
  • 15
  • 110
  • 169

6 Answers6

10

You can define your own xml_encode() function such as this the one from http://darklaunch.com/2009/05/23/php-xml-encode-using-domdocument-convert-array-to-xml-json-encode

function xml_encode($mixed, $domElement=null, $DOMDocument=null) {
    if (is_null($DOMDocument)) {
        $DOMDocument =new DOMDocument;
        $DOMDocument->formatOutput = true;
        xml_encode($mixed, $DOMDocument, $DOMDocument);
        echo $DOMDocument->saveXML();
    }
    else {
        // To cope with embedded objects 
        if (is_object($mixed)) {
          $mixed = get_object_vars($mixed);
        }
        if (is_array($mixed)) {
            foreach ($mixed as $index => $mixedElement) {
                if (is_int($index)) {
                    if ($index === 0) {
                        $node = $domElement;
                    }
                    else {
                        $node = $DOMDocument->createElement($domElement->tagName);
                        $domElement->parentNode->appendChild($node);
                    }
                }
                else {
                    $plural = $DOMDocument->createElement($index);
                    $domElement->appendChild($plural);
                    $node = $plural;
                    if (!(rtrim($index, 's') === $index)) {
                        $singular = $DOMDocument->createElement(rtrim($index, 's'));
                        $plural->appendChild($singular);
                        $node = $singular;
                    }
                }

                xml_encode($mixedElement, $node, $DOMDocument);
            }
        }
        else {
            $mixed = is_bool($mixed) ? ($mixed ? 'true' : 'false') : $mixed;
            $domElement->appendChild($DOMDocument->createTextNode($mixed));
        }
    }
}
KayCee
  • 115
  • 7
Seph
  • 8,472
  • 10
  • 63
  • 94
  • 4
    Works fine, just beware, it is too smart for its own good - if you have a tag name that ends with 's' - it will automatically make a singular form of the tag and add that inside ... (Try that e.g when making kml that has a 'coordinates'-tag that should not have any 'coordinate' subtag) :-P – MortenSickel Nov 11 '13 at 10:22
7

You could use xmlrpc_encode.

 xmlrpc_encode ($your_array);

Be careful because this function is EXPERIMENTAL.

Reference: http://php.net/manual/en/function.xmlrpc-encode.php

antonjs
  • 14,060
  • 14
  • 65
  • 91
7

JSON can express php arrays, integers, strings, etc. natively. XML has no such concepts - just elements, attributes, and text. If you want to transfer an object verbatim, use JSON. If you want to implement a complex API, use XML, for example the php DOM interface.

phihag
  • 278,196
  • 72
  • 453
  • 469
  • note however that JSON is not binary safe, meaning you can not safely add any variable that may contain binary data to json. (this means, for instance, that you cannot safely send PDFs over JSON) - for example this is _very_ likely to fail and just return bool(false): json encoding error: ```random_bytes(1337))));``` – hanshenrik Mar 17 '19 at 08:37
  • I cheat, I convert binary data to string data and send that with json. on the other end you just convert it back again. – djack109 Nov 23 '20 at 18:17
2

here is one for php7.0+ , i bet it is far from optimal , the code is non-trivial, and it has NOT been tested much, but at least it works for my data (unlike Seph's code)...

example:

$test = array (
        'normal1' => 'foo',
        'normal2' => 'bar',
        'foo_assoc' => [ 
                'foo',
                'bar',
                'baz',
                [ 
                        'derp',
                        'derpmore' 
                ] 
        ],
        'foo_nonassoc' => [ 
                'derppp' => 'yes',
                'daarpp' => 'no',
                'lel',
                'far' => 'away' 
        ],
        'normal3' => 'lala',
        'deep' => [ 
                'deeper' => [ 
                        'deeper2' => [ 
                                'deepest' => [ 
                                        'quite',
                                        'deep',
                                        'indeed' 
                                ],
                                'checkmate' 
                        ] 
                ] 
        ],
        'special' => 'encoding<special>characters&test',
        'me_n_you' => 'true' 
);

echo (hhb_xml_encode ( $test ));

output:

<normal1>foo</normal1>
<normal2>bar</normal2>
<foo_assoc>foo</foo_assoc>
<foo_assoc>bar</foo_assoc>
<foo_assoc>baz</foo_assoc>
<foo_assoc>derp</foo_assoc>
<foo_assoc>derpmore</foo_assoc>
<foo_nonassoc>
  <derppp>yes</derppp>
  <daarpp>no</daarpp>
  <foo_nonassoc>lel</foo_nonassoc>
  <far>away</far>
</foo_nonassoc>
<normal3>lala</normal3>
<deep>
  <deeper>
    <deeper2>
      <deepest>quite</deepest>
      <deepest>deep</deepest>
      <deepest>indeed</deepest>
      <deeper2>checkmate</deeper2>
    </deeper2>
  </deeper>
</deep>
<special>encoding&lt;special&gt;characters&amp;test</special>
<me_n_you>true</me_n_you>

function:

  • Edit: fixed a bug with encoding empty arrays.
  • Edit: made the code PHP8-compatible
    function hhb_xml_encode(array $arr, string $name_for_numeric_keys = 'val'): string {
        if (empty ( $arr )) {
            // avoid having a special case for <root/> and <root></root> i guess
            return '';
        }
        $is_iterable_compat = function ($v): bool {
            // php 7.0 compat for php7.1+'s is_itrable
            return is_array ( $v ) || ($v instanceof \Traversable);
        };
        $isAssoc = function (array $arr): bool {
            // thanks to Mark Amery for this
            if (array () === $arr)
                return false;
            return array_keys ( $arr ) !== range ( 0, count ( $arr ) - 1 );
        };
        $endsWith = function (string $haystack, string $needle): bool {
            // thanks to MrHus
            $length = strlen ( $needle );
            if ($length == 0) {
                return true;
            }
            return (substr ( $haystack, - $length ) === $needle);
        };
        $formatXML = function (string $xml) use ($endsWith): string {
            // there seems to be a bug with formatOutput on DOMDocuments that have used importNode with $deep=true
            // on PHP 7.0.15...
            $domd = new DOMDocument ( '1.0', 'UTF-8' );
            $domd->preserveWhiteSpace = false;
            $domd->formatOutput = true;
            $domd->loadXML ( '<root>' . $xml . '</root>' );
            $ret = trim ( $domd->saveXML ( $domd->getElementsByTagName ( "root" )->item ( 0 ) ) );
            assert ( 0 === strpos ( $ret, '<root>' ) );
            assert ( $endsWith ( $ret, '</root>' ) );
            $full = trim ( substr ( $ret, strlen ( '<root>' ), - strlen ( '</root>' ) ) );
            $ret = '';
            // ... seems each line except the first line starts with 2 ugly spaces,
            // presumably its the <root> element that starts with no spaces at all.
            foreach ( explode ( "\n", $full ) as $line ) {
                if (substr ( $line, 0, 2 ) === '  ') {
                    $ret .= substr ( $line, 2 ) . "\n";
                } else {
                    $ret .= $line . "\n";
                }
            }
            $ret = trim ( $ret );
            return $ret;
        };
        
        // $arr = new RecursiveArrayIterator ( $arr );
        // $iterator = new RecursiveIteratorIterator ( $arr, RecursiveIteratorIterator::SELF_FIRST );
        $iterator = $arr;
        $domd = new DOMDocument ();
        $root = $domd->createElement ( 'root' );
        foreach ( $iterator as $key => $val ) {
            // var_dump ( $key, $val );
            $ele = $domd->createElement ( is_int ( $key ) ? $name_for_numeric_keys : $key );
            if (! empty ( $val ) || $val === '0') {
                if ($is_iterable_compat ( $val )) {
                    $asoc = $isAssoc ( $val );
                    $tmp = hhb_xml_encode ( $val, is_int ( $key ) ? $name_for_numeric_keys : $key );
                    // var_dump ( $tmp );
                    // die ();
                    $tmpDom = new DOMDocument();
                    @$tmpDom->loadXML ( '<root>' . $tmp . '</root>' );
                    foreach ( $tmpDom->getElementsByTagName ( "root" )->item ( 0 )->childNodes ?? [ ] as $tmp2 ) {
                        $tmp3 = $domd->importNode ( $tmp2, true );
                        if ($asoc) {
                            $ele->appendChild ( $tmp3 );
                        } else {
                            $root->appendChild ( $tmp3 );
                        }
                    }
                    unset ( $tmp, $tmp2, $tmp3, $tmpDom );
                    if (! $asoc) {
                        // echo 'REMOVING';die();
                        // $ele->parentNode->removeChild($ele);
                        continue;
                    }
                } else {
                    $ele->textContent = $val;
                }
            }
            $root->appendChild ( $ele );
        }
        $domd->preserveWhiteSpace = false;
        $domd->formatOutput = true;
        $ret = trim ( $domd->saveXML ( $root ) );
        assert ( 0 === strpos ( $ret, '<root>' ) );
        assert ( $endsWith ( $ret, '</root>' ) );
        $ret = trim ( substr ( $ret, strlen ( '<root>' ), - strlen ( '</root>' ) ) );
        // seems to be a bug with formatOutput on DOMDocuments that have used importNode with $deep=true..
        $ret = $formatXML ( $ret );
        return $ret;
    }
J2TeamNNL
  • 3
  • 1
  • 5
hanshenrik
  • 19,904
  • 4
  • 43
  • 89
  • 1
    A bit late, but it is worth noting that this does not include a doctype or a root element, thus becoming poorly formed XML, you can replace `return $ret;` with `return '' . $ret . '';` – Ben Nov 22 '17 at 00:50
  • 1
    "More than a year later," I'm very appreciative to you – and, subsequently, to Ben – for having shared this bit of code. It worked beautifully. Thank you very much for having "saved my bacon." :-) – Mike Robinson Sep 14 '18 at 00:34
0

My contrib:

function xml_encode(mixed $value=null, string $key="root", SimpleXMLElement $parent=null){
    if(is_object($value)) $value = (array) $value;
    if(!is_array($value)){
        if($parent === null){   
            if(is_numeric($key)) $key = 'item';             
            if($value===null) $node = new SimpleXMLElement("<$key />");
            else              $node = new SimpleXMLElement("<$key>$value</$key>");
        }
        else{
            $parent->addChild($key, $value);
            $node = $parent;
        }
    }
    else{
        $array_numeric = false;
        if($parent === null){ 
            if(empty($value)) $node = new SimpleXMLElement("<$key />");
            else              $node = new SimpleXMLElement("<$key></$key>");
        }
        else{
            if(!isset($value[0])) $node = $parent->addChild($key);
            else{
                $array_numeric = true;
                $node = $parent;
            }
        }
        foreach( $value as $k => $v ) {
            if($array_numeric) xml_encode($v, $key, $node);
            else xml_encode($v, $k, $node);
        }
    }       
    return $node;
}

Simple Example:

   $a = "hello";
   $xml_element = xml_encode($a,'a');
   echo $xml_element->asXML();

Null Example:

   $xml_element = xml_encode(null,'example');
   echo $xml_element->asXML();

Complex Example:

   $w = new stdClass();
   $w->special = true;
   $w->name = 'Birthday Susan';
    
   $v = new stdClass();
   $v->name = 'John';
   $v->surname = 'Smith';
   $v->hobbies = array('soccer','cinema');
   $v->job = 'policeman';
   $v->events = new stdClass();
   $v->events->tomorrow = false;
   $v->events->yesterday = true;
   $v->events->list = array($v->hobbies, $w);
    
   $xml_element = xml_encode($v,'oembed');
   echo $xml_element->asXML();
-2

This works for me in most cases:

$str = htmlentities($str , ENT_XML1);

Docs: http://php.net/manual/en/function.htmlentities.php

jimconte
  • 127
  • 4
  • doesn't support arrays, and doesn't create xml files, it simply xml encode a single string (converting <> to >< etc) – hanshenrik Aug 24 '17 at 11:59
  • I respectfully share the opinion that this response is really not relevant to the OP's situation, although it is perfectly suitable to the situation for which it is intended. But, thanks anyway. – Mike Robinson Sep 14 '18 at 00:33
  • 1
    If your XML structure is already generated, this is actually quite handy. – keanu_reeves Jul 24 '19 at 17:29