Home > database >  Php inner loop problem with xml and continue
Php inner loop problem with xml and continue

Time:07-11

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, 

https://onlinephp.io/c/e5df5

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
                )

        )

)
  • Related