Home > Software engineering >  Retrieve keys from xml in php
Retrieve keys from xml in php

Time:10-24

I need to fetch all the node keys in xml. I converted to array and then done with the following code,

<?php
$array='<?xml version="1.0" encoding="UTF-8"?>
<prestashop xmlns:xlink="http://www.w3.org/1999/xlink">
<country>
    <id>18</id>
    <id_zone xlink:href="https://www.example.com/api/zones/299">299</id_zone>
    <id_currency>0</id_currency>
    <call_prefix>469</call_prefix>
    <iso_code>SE</iso_code>
    <active>1</active>
    <contains_states>0</contains_states>
    <need_identification_number>0</need_identification_number>
    <need_zip_code>1</need_zip_code>
    <zip_code_format>NNN NN</zip_code_format>
    <display_tax_label>1</display_tax_label>
    <name><language id="1" xlink:href="https://www.example.com/api/languages/1">Suède</language><language id="2" xlink:href="https://www.example.com/api/languages/2">Sweden</language></name>
</country>
</prestashop>';
$array1=json_decode(json_encode((array)simplexml_load_string($array,'SimpleXMLElement',LIBXML_NOCDATA)),1);
//print_r($array1);die();
function getUniqueObjectKeyPaths(array $array, $parentKey = "") {
    $keys = [];
    foreach ($array as $parentKey => $v) {
        if (!is_numeric($parentKey) && !is_array($v)) {
            $keys[] = $parentKey;
        }
        if (is_array($v)) {
            $nestedKeys = getUniqueObjectKeyPaths($v, $parentKey);
            foreach($nestedKeys as $index => $key) {
                if (!is_numeric($parentKey) && !is_numeric($key)) {
                    $nestedKeys[$index] = $parentKey . "->" . $key;
                }
            }
            $keys = array_merge($keys, $nestedKeys);
        }
    }
    return $keys;
}
$k=getUniqueObjectKeyPaths($array1);
print_r($k);

I got the result as follows,

Array ( 
    [0] => country->id 
    [1] => country->id_zone 
    [2] => country->id_currency 
    [3] => country->call_prefix 
    [4] => country->iso_code 
    [5] => country->active 
    [6] => country->contains_states 
    [7] => country->need_identification_number 
    [8] => country->need_zip_code 
    [9] => country->zip_code_format 
    [10] => country->display_tax_label 
)

The expected result is,

I also need the key country->name->language->0,country->name->language->1.

Any quick help would be greatly appreciated.

Thanks, Rekha

CodePudding user response:

You were on the right track with using a recursive function. You needed to add the $keys as a function argument alongside the $parentKey.

function getUniqueObjectKeyPaths(array $array, $parentKey = '', $keys = [])
{
    foreach ($array as $key => $value) {
        if (!empty($parentKey))
            $key = $parentKey . '->' . $key;

        if (is_array($value))
            return getUniqueObjectKeyPaths($value, $key, $keys);

        $keys[] = $key;
    }
    return $keys;
}

CodePudding user response:

I don't think there's a need for the json_decode(json_encode()) stuff and casting SimpleXMLElement to an array adds an [@attributes] index as well. You can traverse a SimpleXMLElement object by itself:

Function:

function getUniqueObjectKeyPaths(SimpleXMLElement $el, array $trail = []) : array {
  // intialize collected paths
  $paths = [];
  
  // keep track of identically named children
  $sameNameIndices = [];
  foreach($el as $child) {
    // store child's name
    $name = $child->getName();
    
    // we can count how may identically named children with $name there are in element by using count($el->childName)
    // and with the special syntax $el->{$name} we can use the name stored in the variabel $name
    $sameNameCount = count($el->{$name});
    
    // if we have multiple identically named children...
    if($sameNameCount > 1) {
      // initialize or increment identically named child index
      $sameNameIndices[$name] = !isset($sameNameIndices[$name]) ? 0 : $sameNameIndices[$name]   1;
    
      // add name with its index to trail
      $trail[] = $name . '->' . $sameNameIndices[$name];
    }
    // else...
    else {
      // only add name
      $trail[] = $name;
    }
    
    // since we only want leaves, recurse if this child has children of its own
    if(count($child)) {
      // push recursed paths to our local paths collection
      array_push($paths, ...getUniqueObjectKeyPaths($child, $trail));
    }
    // else add our path to the paths collection
    else {
      $paths[] = implode('->', $trail);
    }
    
    // remove name from the trail again
    array_pop($trail);
  }
  
  // return collected paths
  return $paths;
}

Usage:


$xml = '<?xml version="1.0" encoding="UTF-8"?>
<prestashop xmlns:xlink="http://www.w3.org/1999/xlink">
  <country>
      <id>18</id>
      <id_zone xlink:href="https://www.example.com/api/zones/299">299</id_zone>
      <id_currency>0</id_currency>
      <call_prefix>469</call_prefix>
      <iso_code>SE</iso_code>
      <active>1</active>
      <contains_states>0</contains_states>
      <need_identification_number>0</need_identification_number>
      <need_zip_code>1</need_zip_code>
      <zip_code_format>NNN NN</zip_code_format>
      <display_tax_label>1</display_tax_label>
      <name>
        <language id="1" xlink:href="https://www.example.com/api/languages/1"><test>Suède</test></language>
        <test id="2" xlink:href="https://www.example.com/api/languages/2">Sweden</test>
        <language id="2" xlink:href="https://www.example.com/api/languages/2">Sweden</language>
      </name>
  </country>
</prestashop>';

$el = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);

print_r(getUniqueObjectKeyPaths($el));

...prints:

Array
(
    [0] => country->id
    [1] => country->id_zone
    [2] => country->id_currency
    [3] => country->call_prefix
    [4] => country->iso_code
    [5] => country->active
    [6] => country->contains_states
    [7] => country->need_identification_number
    [8] => country->need_zip_code
    [9] => country->zip_code_format
    [10] => country->display_tax_label
    [11] => country->name->language->0
    [12] => country->name->language->1
)
  • Related