PHP Xpath: How to get table headers where another table is inside the first row?

172 Views Asked by At

I am trying to extract the headers table from this url: https://www4.bcb.gov.br/pec/poupanca/poupanca.asp

enter image description here

Unfortunately, they use a "non-standard" html table where instead of rowspan they used a (bizarre) table inside the first row ...

So I would like to extract dynamically this header in an array of 8 items like this (merging the the headers with two rows size):

$columns_extracted_result = [
    'Data',
    'DataFim',
    'Depósitos até 03.05.2012 - Remuneração básica ',
    'Depósitos até 03.05.2012 - Remuneração adicional',
    'Depósitos até 03.05.2012 - Remuneração total',
    'Depósitos a partir de 04.05.2012 (*) -Remuneração básica',
    'Depósitos a partir de 04.05.2012 (*) - Remuneração adicional',
    'Depósitos a partir de 04.05.2012 (*) - Remuneração total'
];

And after that, create a array where the keys will be the $columns_extracted_result like:

$table = [
    [
        'Data' => '26/04/2022',
        'DataFim' => '26/05/2022',
        'Depósitos até 03.05.2012 - Remuneração básica' => '0,1538'
    //...
    ],
    [
        'Data' => '27/04/2022',
        'DataFim' => '27/05/2022',
        'Depósitos até 03.05.2012 - Remuneração básica' => '0,1568'
    //...
    ]
];

How Can I achieve this using DomXpath ?

1

There are 1 best solutions below

0
Solonl On BEST ANSWER

Solution

get_columns

The get_columns function returns a list of columns as defined in the table headers:

array(8) {
  [0]=>
  string(4) "Data"
  [1]=>
  string(8) "Data fim"
  [2]=>
  string(50) "Depósitos até 03.05.2012 - Remuneração básica"
  [3]=>
  string(52) "Depósitos até 03.05.2012 - Remuneração adicional"
  [4]=>
  string(48) "Depósitos até 03.05.2012 - Remuneração total"
  [5]=>
  string(61) "Depósitos a partir de 04.05.2012 (*) - Remuneração básica"
  [6]=>
  string(63) "Depósitos a partir de 04.05.2012 (*) - Remuneração adicional"
  [7]=>
  string(59) "Depósitos a partir de 04.05.2012 (*) - Remuneração total"
}

get_rows

The get_rows function returns the complete data set, see code and output:

Complete code

<?php

$doc = new \DOMDocument();
@$doc->loadHTMLFile("https://www4.bcb.gov.br/pec/poupanca/poupanca.asp");
$xpath = new DOMXpath($doc);

$data = get_rows($xpath, get_columns($xpath));
var_dump($data);

function get_inner_html(DOMElement $node) : string 
{
    $innerHTML= '';
    $children = $node->childNodes;
    foreach ($children as $child) {
        $innerHTML .= $child->ownerDocument->saveXML( $child );
    }

    return $innerHTML;
}

function get_clean_value(DomElement $text) : string 
{
    $html = get_inner_html($text);
    $decode = html_entity_decode($html);
    $replace = str_replace(["<br/>", "\t", PHP_EOL, " ", "\xc2\xa0"], " ", $decode);
    $stripped = strip_tags($replace);
    return trim($stripped);
}

function get_columns(DOMXPath $xpath) : array 
{
    $tds = $xpath->query("(/html/body/table/tbody/tr)[1]/td");
    $columns = [];
    foreach($tds as $td) {
        $tdFirst = $xpath->query('(table)[1]/tr/td', $td);
        $tdSecond = $xpath->query("(table)[2]/tr/td", $td);
        if($tdFirst->count() > 0 && $tdSecond->count() > 0) {
            $columnPrefix = get_clean_value($tdFirst[0]);
            foreach($tdSecond as $item) {
                $columns[] = $columnPrefix . " - " . get_clean_value($item);
            }
        } else {
            $columns[] = get_clean_value($td);
        }
    }
    return $columns;
}

function get_rows(DOMXPath $xpath, array $columns) : array 
{
    $result = [];
    $trs = $xpath->query("/html/body/table/tbody/tr");
    foreach($trs as $count=>$tr) {
        if($count === 0) continue;
        $tds = $xpath->query("td", $tr);
        $row = [];
        foreach($tds as $td) {
            $row[] = get_clean_value($td);
        }
        $result[] = array_combine($columns, $row);
    }
    return $result;
}

Output

array(45) {
  [0]=>
  array(8) {
    ["Data"]=>
    string(10) "27/04/2022"
    ["Data fim"]=>
    string(10) "27/05/2022"
    ["Depósitos até 03.05.2012 - Remuneração básica"]=>
    string(6) "0,1568"
    ["Depósitos até 03.05.2012 - Remuneração adicional"]=>
    string(6) "0,5000"
    ["Depósitos até 03.05.2012 - Remuneração total"]=>
    string(6) "0,6576"
    ["Depósitos a partir de 04.05.2012 (*) - Remuneração básica"]=>
    string(6) "0,1568"
    ["Depósitos a partir de 04.05.2012 (*) - Remuneração adicional"]=>
    string(6) "0,5000"
    ["Depósitos a partir de 04.05.2012 (*) - Remuneração total"]=>
    string(6) "0,6576"
  }
...