Nested array from indented text

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
mrcoffee
Forum Commoner
Posts: 31
Joined: Tue Nov 10, 2009 3:03 pm
Location: Wyoming, USA

Nested array from indented text

Post by mrcoffee »

Hello,

I have an issue that I think has a simple solution but is driving me insane. Any help would be greatly appreciated.

To start, I have a single-dimension array similar to the following (it's output from "identify -verbose somefile.png"):

[text]
Array
(
[0] => Image: somefile.png
[1] => Format: PNG (Portable Network Graphics)
[2] => Class: DirectClass

[...]

[8] => Channel depth:
[9] => Red: 8-bit
[10] => Green: 8-bit
[11] => Blue: 8-bit
[12] => Alpha: 1-bit
[13] => Channel statistics:
[14] => Red:
[15] => Min: 0 (0)
[16] => Max: 255 (1)
[17] => Mean: 214.231 (0.84012)
[18] => Standard deviation: 67.8397 (0.266038)
)[/text]

My goal is to get this into a nested array, so I have something like:

[text]
Array
(
[Format] => PNG (Portable Network Graphics)
[Class] => DirectClass

[...]

[Channel depth] => Array
(
[Red] => 8-bit
[Green] => 8-bit
[Blue] => 8-bit
[Alpha] => 1-bit
)

[Channel statistics] => Array
(
[Red] => Array
(
[Min] => 0 (0)
[Max] => 255 (1)
[Mean] => 214.231 (0.84012)
[Standard deviation] => 67.8397 (0.266038)
)

)

)[/text]

I started by cycling through the single-dimension array, and exploding each value on ':' to get two elements, a key and a value. I then count the spaces before the key to determine the indent level. Basically, for each given line, I know the indent level, the key, and the value.

My problem is that I don't know how to recursively set an array from there.

Thank you.
User avatar
omniuni
Forum Regular
Posts: 738
Joined: Tue Jul 15, 2008 10:50 pm
Location: Carolina, USA

Re: Nested array from indented text

Post by omniuni »

Hmm.

trim() can probably get rid of the white space, which will help you somewhat. Then, split the string. Write into an array, where $result is the result of your explode: $array[$result[0]] = $result[1].

Does that help? You can also use things like strtolower() to fix formatting for your key.
mrcoffee
Forum Commoner
Posts: 31
Joined: Tue Nov 10, 2009 3:03 pm
Location: Wyoming, USA

Re: Nested array from indented text

Post by mrcoffee »

Thanks for the reply, but I was already doing that.

The problem with $array[$result[0]] = $result[1] is that I'll still have a single-dimension array.
I want something like that, but where $array is the last node in the current chain, so that:

Key 1:
--Key 2:
----Key 3: Value

Becomes $array['Key 1']['Key 2']['Key 3'] = 'Value'

Any ideas?
User avatar
omniuni
Forum Regular
Posts: 738
Joined: Tue Jul 15, 2008 10:50 pm
Location: Carolina, USA

Re: Nested array from indented text

Post by omniuni »

$finalArray[$position][$key]=$value;

Something like that, where $position increments each time you loop through?
mrcoffee
Forum Commoner
Posts: 31
Joined: Tue Nov 10, 2009 3:03 pm
Location: Wyoming, USA

Re: Nested array from indented text

Post by mrcoffee »

Sorry, that's still not it.

I wrote a script that hopefully better illustrates what I'm trying to do.

First, here's the full array that I'm starting with (sorry this is long, but I thought a full example might be useful):

[text]
This is the result of: exec('identify -verbose some_image.png' , $img_data);

Array
(
[0] => Image: some_image.png
[1] => Format: PNG (Portable Network Graphics)
[2] => Class: DirectClass
[3] => Geometry: 800x995+0+0
[4] => Type: TrueColorMatte
[5] => Endianess: Undefined
[6] => Colorspace: RGB
[7] => Depth: 8-bit
[8] => Channel depth:
[9] => Red: 8-bit
[10] => Green: 8-bit
[11] => Blue: 8-bit
[12] => Alpha: 1-bit
[13] => Channel statistics:
[14] => Red:
[15] => Min: 0 (0)
[16] => Max: 255 (1)
[17] => Mean: 214.231 (0.84012)
[18] => Standard deviation: 67.8397 (0.266038)
[19] => Green:
[20] => Min: 0 (0)
[21] => Max: 255 (1)
[22] => Mean: 203.409 (0.797681)
[23] => Standard deviation: 69.5713 (0.272829)
[24] => Blue:
[25] => Min: 0 (0)
[26] => Max: 255 (1)
[27] => Mean: 200.052 (0.784518)
[28] => Standard deviation: 77.8171 (0.305165)
[29] => Opacity:
[30] => Min: 0 (0)
[31] => Max: 0 (0)
[32] => Mean: 0 (0)
[33] => Standard deviation: 0 (0)
[34] => Rendering intent: Saturation
[35] => Gamma: 0.45455
[36] => Chromaticity:
[37] => red primary: (0.64,0.33)
[38] => green primary: (0.3,0.6)
[39] => blue primary: (0.15,0.06)
[40] => white point: (0.3127,0.329)
[41] => Resolution: 28.35x28.35
[42] => Units: PixelsPerCentimeter
[43] => Filesize: 220.906kb
[44] => Interlace: None
[45] => Background color: white
[46] => Border color: rgb(223,223,223)
[47] => Matte color: grey74
[48] => Transparent color: none
[49] => Page geometry: 800x995+0+0
[50] => Dispose: Undefined
[51] => Iterations: 0
[52] => Compression: Zip
[53] => Orientation: Undefined
[54] => Signature: 1518c127005b589fa58edddc32d21dff2d5711bade8258a93345f3c71163525b
[55] => Tainted: False
[56] => Version: ImageMagick 6.3.7 06/04/09 Q16 http://www.imagemagick.org
)
[/text]
And here's the code that kind of does what I want:

Code: Select all

<?php

exec('identify -verbose some_image.png',$img_data);

$chain = 'root';
// $chain will be added on to so that an entry might look like: root.Channel statistics.Green.Standard deviation=69...
// But instead of dots I want nested arrays: $root[Channel statistics][Green][Standard deviation] = 69

foreach($img_data as $line_number=>$line) {

	$line_parts = explode(':',$line);

	// determine number of spaces that line is indented
	$indent = 0;
	while($line_parts[0][$indent] == ' ')
		$indent ++;
	// $indent is now the quantity of leading spaces
	
	$indent_diff = ($indent - $last_indent) / 2; // $indent_diff is difference from last indent (2 spaces per indentation)
	/*
Example:
LINE ONE		//$indent_diff = 0
  LINE TWO		//$indent_diff = +1
    LINE THREE		//$indent_diff = +1
LINE FOUR		//$indent_diff = -2
	*/
	
	// trim key and value
	foreach($line_parts as $part=>$value) $line_parts[$part] = trim($value);
		
	if($indent_diff < 0)  { // remove nodes from chain
		for($remove_i = $indent_diff; $remove_i < 0; $remove_i ++) {
			$chain = substr($chain,0,strrpos($chain,'.'));	
		}
	}

	if($line_parts[1] == '') { // line does not have a value, so add a node 
		$chain .= '.' . $line_parts[0] ;
	}
	else { // line has a value, make an entry in $final, where $line_parts[0] is the last node
		$final[] = $chain . '.' . $line_parts[0] . '=' . $line_parts[1];
	}

	$last_indent = $indent; // set for next iteration
}

print_r($final);
?>
Which outputs:

[text]
Array
(
[0] => root.Image=some_image.png
[1] => root.Format=PNG (Portable Network Graphics)
[2] => root.Class=DirectClass
[3] => root.Geometry=800x995+0+0
[4] => root.Type=TrueColorMatte
[5] => root.Endianess=Undefined
[6] => root.Colorspace=RGB
[7] => root.Depth=8-bit
[8] => root.Channel depth.Red=8-bit
[9] => root.Channel depth.Green=8-bit
[10] => root.Channel depth.Blue=8-bit
[11] => root.Channel depth.Alpha=1-bit
[12] => root.Channel statistics.Red.Min=0 (0)
[13] => root.Channel statistics.Red.Max=255 (1)
[14] => root.Channel statistics.Red.Mean=214.231 (0.84012)
[15] => root.Channel statistics.Red.Standard deviation=67.8397 (0.266038)
[16] => root.Channel statistics.Green.Min=0 (0)
[17] => root.Channel statistics.Green.Max=255 (1)
[18] => root.Channel statistics.Green.Mean=203.409 (0.797681)
[19] => root.Channel statistics.Green.Standard deviation=69.5713 (0.272829)
[20] => root.Channel statistics.Blue.Min=0 (0)
[21] => root.Channel statistics.Blue.Max=255 (1)
[22] => root.Channel statistics.Blue.Mean=200.052 (0.784518)
[23] => root.Channel statistics.Blue.Standard deviation=77.8171 (0.305165)
[24] => root.Channel statistics.Opacity.Min=0 (0)
[25] => root.Channel statistics.Opacity.Max=0 (0)
[26] => root.Channel statistics.Opacity.Mean=0 (0)
[27] => root.Channel statistics.Opacity.Standard deviation=0 (0)
[28] => root.Rendering intent=Saturation
[29] => root.Gamma=0.45455
[30] => root.Chromaticity.red primary=(0.64,0.33)
[31] => root.Chromaticity.green primary=(0.3,0.6)
[32] => root.Chromaticity.blue primary=(0.15,0.06)
[33] => root.Chromaticity.white point=(0.3127,0.329)
[34] => root.Resolution=28.35x28.35
[35] => root.Units=PixelsPerCentimeter
[36] => root.Filesize=220.906kb
[37] => root.Interlace=None
[38] => root.Background color=white
[39] => root.Border color=rgb(223,223,223)
[40] => root.Matte color=grey74
[41] => root.Transparent color=none
[42] => root.Page geometry=800x995+0+0
[43] => root.Dispose=Undefined
[44] => root.Iterations=0
[45] => root.Compression=Zip
[46] => root.Orientation=Undefined
[47] => root.Signature=1518c127005b589fa58edddc32d21dff2d5711bade8258a93345f3c71163525b
[48] => root.Tainted=False
[49] => root.Version=ImageMagick 6.3.7 06/04/09 Q16 http
)
[/text]

The "dots" are merely to demonstrate what I'm trying to get with a PHP array.
So instead of element 12, for example, I want: $root[Channel statistics][Red][Min] = '0 (0)'

My primary problem is that while I can add and remove nodes onto a string, I don't know how to do so with an array (while keeping previous entries).

Thank you again.
User avatar
omniuni
Forum Regular
Posts: 738
Joined: Tue Jul 15, 2008 10:50 pm
Location: Carolina, USA

Re: Nested array from indented text

Post by omniuni »

Well, you really just basically wrote it yourself:

$root['Channel statistics']['Red']['Min'] = '0 (0)'; should work just fine. It's not efficient, but it should work.
mrcoffee
Forum Commoner
Posts: 31
Joined: Tue Nov 10, 2009 3:03 pm
Location: Wyoming, USA

Re: Nested array from indented text

Post by mrcoffee »

But each key ('Channel statistics', 'Red', 'Min') is dynamic, as well as how many keys there are (the original array is the result of running a system script, I have no control over how it's initial format).

You did give me an idea, though, that perhaps I could do:
$root[$node1][$node2][$node3] = $value;

but the number of level/nodes change.

I suppose I could then do:

Code: Select all

switch($levels)
     case 1:
          $root[$node1] = $value;
     break;
     case 2:
          $root[$node1][$node2] = $value
     break;
     // etc...
}
Because with this data there will only be a maximum of 3 nodes I think this would be plausible, but it seems kind of messy.

I think there's a better way to do this, that would allow for an unlimited number of levels. I've seen similar with scripts that build a nested array of a file system, but I can't figure this one out.
User avatar
omniuni
Forum Regular
Posts: 738
Joined: Tue Jul 15, 2008 10:50 pm
Location: Carolina, USA

Re: Nested array from indented text

Post by omniuni »

Oh! I finally caught on. If you counted the spaces in a loop, which paused every time it hit 0, and used that number of spaces to count the times in a loop that you would need to write the array? In other words, would the number of spaces be able to tell you how many entries deep the array needs to be to hold the information?
mrcoffee
Forum Commoner
Posts: 31
Joined: Tue Nov 10, 2009 3:03 pm
Location: Wyoming, USA

Re: Nested array from indented text

Post by mrcoffee »

No worries - I'm having a hard time even explaining what it is that I'm trying to do.

In answer to your question, yes, the number of spaces indicates the depth of the array (kind of, the output is actually 2 spaces per level, but I can just divide by 2). If it is indented from the previous entry, it should be part of the previous node (and will never be indented more than one level from the previous). If it is out-dented, it should "back out" by how many indents there are, so 4 spaces, or 2 indents, would be 2 levels less deep than the prior entry. I hope that makes some sense.

Thanks again.
User avatar
omniuni
Forum Regular
Posts: 738
Joined: Tue Jul 15, 2008 10:50 pm
Location: Carolina, USA

Re: Nested array from indented text

Post by omniuni »

I don't suppose something like this would work, would it?

http://www.php.net/manual/en/function.e ... d-data.php
User avatar
AbraCadaver
DevNet Master
Posts: 2572
Joined: Mon Feb 24, 2003 10:12 am
Location: The Republic of Texas
Contact:

Re: Nested array from indented text

Post by AbraCadaver »

I only tested on one array so its a little specific to the format you posted and haven't optimized it, but here's a start:

Code: Select all

$image = array_shift($img_data);
$result = $current = array();
$parent = &$result;
$prev_level = 0;

foreach($img_data) as $item)  {
	list($key, $val) = explode(': ', $item, 2);
	$level = preg_match_all('/[ ]/', $key, $m) - preg_match_all('/[ ][^ ]/', $key, $m);
	$key = trim(preg_replace('/:$/', '', $key));
	$val = trim($val);

	if($level > $prev_level) {
		$current[$level] = &$parent;
	} elseif($level <= $prev_level) {
		$parent = &$current[$level];
	}
	if($val == '') {
		$parent[$key] = array();
		$parent = &$parent[$key];
		$prev_level = $level;
	} else {
		$parent[$key] = $val;
	}
}
print_r($result);
mysql_function(): WARNING: This extension is deprecated as of PHP 5.5.0, and will be removed in the future. Instead, the MySQLi or PDO_MySQLextension should be used. See also MySQL: choosing an API guide and related FAQ for more information.
mrcoffee
Forum Commoner
Posts: 31
Joined: Tue Nov 10, 2009 3:03 pm
Location: Wyoming, USA

Re: Nested array from indented text

Post by mrcoffee »

AbraCadaver, that will work perfectly. Thank you so much.
Post Reply