Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

Some kind of followup to my last question: for loop - move deeper on numeric key in multidimensional array

I have this array as input:

Array
(
  [0] => apl_struct Object
    (
      [funcname] => say
      [args] => Array
        (
          [0] => Array
            (
              [0] => apl_struct Object
                (
                  [funcname] => text
                  [args] => Array
                    (
                      [value] => hello
                    )
                )
            )
        )
    )
)

I now have 2 functions working for me. One is a func only for getting the next key/value in an associative array. None of the next(), prev(), etc. were working for me like on indexed arrays:

function getnext($array, $key) {
  $keys = array_keys($array);

  if ((false !== ($p = array_search($key, $keys))) && ($p < count($keys) - 1)) {
    return array('key' => $keys[++$p], 'value' => $array[$keys[$p]]);
  } else {return false;}
}

The next function is my executer or constructer. he creates a semi-xmlstruct for me. I tried to add recursion for skipping the numeric key. They're obviously nonsense and can be skipped.

I then want to check if all the values of the non-numeric keys are arrays or not. If it is an array it indicates arguments to be followed and output should look like: INPUT.

If not, it's either the functionname (funcname) or indeed a real value for us like "hello".

function arr2xml($array, $level = 1, $pos = 1) {
  $xml = '';

  foreach ($array as $key => $value) {
    if (is_object($value)) {$value = get_object_vars($value);}// convert object to array
      if (is_numeric($key)) {
        $xml .= arr2xml($value);
      } else {
        if (!is_array($value)) {
          switch ($key) {
            case 'funcname':
              $nextkey = getnext($array, $key);
              $xml .= str_repeat("\t", $level) . "<apl:$value>\n";
              $xml .= arr2xml($nextkey['value'], $level++);
              $xml .= str_repeat("\t", $level) . "</apl:$value>\n";
              break;
            case 'value':
              $xml .= str_repeat("\t", $level) . "\t$value\n";
              break;
          }
        } else {
            $xml .= str_repeat("\t", $level) . "<$key pos='$pos'>\n\t";
            $xml .= arr2xml($value, $level++, $pos++);
            $xml .= str_repeat("\t", $level) . "</$key>\n";
        }
      }
  }

return $xml;
}

but what I am getting out of this so far is this: the function name was inserted right. it is say and text. also, in some wild circumstances, the -tag and the value are executed properly.

<apl:say>
<apl:text>
    hello
    </apl:text>
    <args pos='1'>
        hello
      </args>
    </apl:say>
    <args pos='1'>
    <apl:text>
    hello
    </apl:text>
    <args pos='1'>
        hello
      </args>
      </args>
</xml>

for me it looks like the recursion isn't really working. Am i missing something here? I've tried to rebuild it from previous mentioned post.

Also I'm wondering about the multiple output I am getting here. The tags seem to get filled right, but the actual arrangement is quite confusing for me.

I was expecting the output to look like this:

<apl:say>
  <args pos='1'>
    <apl:text>
      <args pos='1'>
      hello
      </args>
    </apl:text>
  </args>
</apl:say>

Thanks in advance

share|improve this question
    
Please review your pasted code. Should xml = ''; be $xml = '';? And $xml .= >arr2xml($value); should be $xml .= arr2xml($value);? –  rjdown Oct 22 at 23:45
    
The structure of your input is inconsistent - is that intentional? For example one of your 'args' has an array which contains an array which finally contains an actual object as its first element. The other args is just a single array with the string as the first element. –  gnack Oct 23 at 0:12

1 Answer 1

up vote 0 down vote accepted

TL;DR

It's a combination of the (completely superfluous function) getnext(), and how your arr2xml() recurses. I've provided here an arr2xml() replacement function which will do what you want, without any need for getnext().

A detailed description of what went wrong in your code, and how I suggest fixing it, follows.

function arr2xml($array, $level = 0, $pos = 1) {
  $xml = '';

  foreach ($array as $key => $value) {
    if (is_object($value)) {
      $value = get_object_vars($value);
    }
    if (is_numeric($key)) {
      $xml .= arr2xml($value, $level+1);
      continue;
    } else {
      if (!is_array($value)) {
        switch ($key) {
          case 'funcname':
            array_shift($array);
            $xml .= str_repeat("    ", $level) . "<apl:$value>\n";
            $xml .= arr2xml($array, $level+1);
            $xml .= str_repeat("    ", $level) . "</apl:$value>\n";
            return $xml;
          case 'value':
            $xml .= str_repeat("    ", $level) . "    $value\n";
            return $xml;
        }
      } else {
          $xml .= str_repeat("    ", $level) . "<$key pos='$pos'>\n    ";
          $xml .= arr2xml($value, $level+1, $pos+1);
          $xml .= str_repeat("    ", $level) . "</$key>\n";
          return $xml;
      }
    }
  }

  return $xml;
}

Here is an eval.in showing this new function in use on the same data structure you provided, giving you more or less the desired output (the whitespace may not be exactly what you wanted, I'll leave that as an exercise for you.)

What went wrong with your code

When funcname is 'say', the condition case 'funcname': calls getnext() with $key set to 'funcname' and $array set to:

array(2) {
  ["funcname"]=>
  string(3) "say"
  ["args"]=>
  array(1) {
    [0]=>
    array(1) {
      [0]=>
      object(apl_struct)#1 (2) {
        ["funcname"]=>
        string(4) "text"
        ["args"]=>
        array(1) {
          ["value"]=>
          string(5) "hello"
        }
      }
    }
  }
}

You then find 'funcname' in that array ($p = array_search($key, $keys)) and create a new array containing only the value of the next item in the array:

return array('key' => $keys[++$p], 'value' => $array[$keys[$p]]);

The result is an array that no longer contains the 'args' key:

array(2) {
  ["key"]=>
  string(4) "args"
  ["value"]=>
  array(1) {
    [0]=>
    array(1) {
      [0]=>
      object(apl_struct)#1 (2) {
        ["funcname"]=>
        string(4) "text"
        ["args"]=>
        array(1) {
          ["value"]=>
          string(5) "hello"
        }
      }
    }
  }
}

Thus, you will never get the tag you were hoping for, because the data structure has been corrupted by getnext() to remove the key you expected to find in order to construct it.

The duplicate values can be resolved by returning from the inner recursion earlier. Right now, you are recursing, processing the "inner" nodes, then returning back to the top and processing them again.

Instead, we can drop getnext entirely (since it doesn't even do what you wanted), and we can just use array_shift instead to throw away the left-most value of the array. We then continue processing $array like normal.

share|improve this answer
    
thanks. that works. i totally missed the point of creating new, corrupted value by calling getnext(). array_shift() does the trick now. i remember trying array_shift() once and the output was entirely broken. seems that the function was falsy at another point then. –  Xferd 2 days ago

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.