Having this xml
$xml_text = "<file>
<record>
<name>A</name>
<total_parts>2</total_parts>
<value>25</value>
<part>2</part>
</record>
<record>
<name>D</name>
<total_parts>1</total_parts>
<value>30</value>
<part>1</part>
</record>
<record>
<name>B</name>
<total_parts>1</total_parts>
<value>75</value>
<part>1</part>
</record>
<record>
<name>A</name>
<total_parts>2</total_parts>
<value>80</value>
<part>1</part>
</record>
<record>
<name>T</name>
<total_parts>1</total_parts>
<value>1</value>
<part>1</part>
</record>
<record>
<name>Z</name>
<total_parts>1</total_parts>
<value>3</value>
<part>1</part>
</record>
</file>";
Then $xml
:
$xml = simplexml_load_string($xml_text);
If I want to extract for each <name>
the list of <value>
respecting <part>
order (exept T
):
foreach ($xml as $record) {
$print = '';
if ($record->total_parts == 1) {
$print .= $record->name . ": " . $record->value;
} else {
$print .= $record->name . ": ";
for ($i=1;$i<=$record->total_parts;$i ) {
foreach ($xml as $record_inner) {
if (($record_inner->name == $record->name) and ($record_inner->part==$i)) {
$print .= $record_inner->value . ", ";
break;
}
}
}
}
if ($record->name=="T") {
continue;
}
echo $print;
echo "\n";
}
This is what I want:
A: 80, 25,
D: 30
B: 75
A: 80, 25,
Z: 3
This is the php result:
A: 25,
D: 30
B: 75
A: 80,
Why in the first inner loop condition name = A
and part = 1
doesn't catch value 80
and in the second inner loop name = A
and part = 2
doesn't catch value 25
?
Why after continue
to skip T
, Z
is not considered?
CodePudding user response:
You can get all the results into a new array.
$xml = simplexml_load_string($xml_text);
$records = [];
foreach($xml->record as $record) {
$name = (string)$record->name;
if('T' === $name) {
continue;
}
$records[$name][(int)$record->part - 1] = (int)$record->value;
}
Which is then
Array
(
[A] => Array
(
[1] => 25
[0] => 80
)
[D] => Array
(
[0] => 30
)
[B] => Array
(
[0] => 75
)
[Z] => Array
(
[0] => 3
)
)
and can easily brought to your wanted output
foreach($records as $key => $values) {
ksort($values, SORT_NUMERIC);
echo "$key: ", implode(', ', $values), "\n";
}
printing as
A: 80, 25
D: 30
B: 75
Z: 3
I think your expected result is that printed above and not the repeated letters?
CodePudding user response:
This is a different approach than in the OP, namely trying to not put everything into one big loop, but have discreet steps, which help to understand the transformation:
- Load XML string
- XPath query for 'record' elements where sub element 'name' is not equal to 'T'
- Cast array of SimpleXMLElements to array of arrays
- Create lookup table of 'part' & 'value' for each
- Modify each entry in lookup table by sorting 'part' & 'value' by 'part' and then dropping 'part'
- Modify result array to contain only 'name' and corresponding 'values' from lookup table
$xmlString = simplexml_load_string($xml_text);
$xmlElements = $xmlString->xpath('//record[name != "T"]');
$result = array_map(fn($item) => (array)$item, $xmlElements);
$lookupValues = array_reduce(
$result,
function ($acc, $item) {
$acc[$item['name']][] = [ 'part' => $item['part'], 'value' => $item['value'] ];
return $acc;
},
[]
);
array_walk(
$lookupValues,
function (&$value, $key) {
$parts = array_column($value, 'part');
$values = array_column($value, 'value');
array_multisort($parts, $values);
$value = $values;
}
);
array_walk(
$result,
function (&$value, $key) use ($lookupValues) {
$value = [ 'name' => $value['name'], 'values' => $lookupValues[$value['name']] ];
}
);
print_r($result);
Output:
Array
(
[0] => Array
(
[name] => A
[values] => Array
(
[0] => 80
[1] => 25
)
)
[1] => Array
(
[name] => D
[values] => Array
(
[0] => 30
)
)
[2] => Array
(
[name] => B
[values] => Array
(
[0] => 75
)
)
[3] => Array
(
[name] => A
[values] => Array
(
[0] => 80
[1] => 25
)
)
[4] => Array
(
[name] => Z
[values] => Array
(
[0] => 3
)
)
)