Print
Parent Category: Datorer
Category: Programmering
Hits: 1999

Nå har det vært lenge siden jeg har skrevet noe her i det hele tatt. Så lenge siden at jeg vet nesten ikek hva det er jeg driver med.crying. Da får man lyst til å gråte litt... ehhmmm. Er nok ikke rette til det men men..her er situasjonen:

Ene modulen på nettsiden min http://norskesøk.no har blitt utdatert. Dette er kanskje feil ord, men nettsiden som modulen henter data fra har forandret seg. Ikke moro. Ikke bare har den forandret seg men de har begynt å bruke javascript også. Dette fører selvfølgelig til at jeg ikke kan bruke CURL for hente data derfra. Jeg vet iallefall ikke noen måte. Men Norges Bank har vært så snill at de legger ut både tekst-filer og "spreadsheets" som inneholder de nyeste dataen som man trenger. Min fil inneholder "daglige valutakurser" helt tilbake til 1981 og er av typen ".xlsx". Fila er ganske stor og jeg fant ut at dette er ikke bare-bare, da man stort sett får feilmeldinger angående minne når vi bruker PHP. MEN,...

Det finnes faktisk en løsning på dette og. Vi trenger bare å laste inn de delene av fila vi trenger. Nedenfor er en oversikt over hva jeg har gjort for å forenkle ting litt.

  1. Databasen er utvidet med to nye tabeller: Den ene inneholder land, valuta og valuta-kode, og den andre inneholder all data(daglige valutakurser ett år tilbake i tid for alle land).
  2. Laget et php-skript som hentet Land-data og la det inn i databasen
  3. Laget et php.skript som skrev inn alle valutakurser for alle land ett år tilbake inn i databasen
  4. Laget et php-skript som sjekker dato på/i fila og oppdaterer databasen med nye verdier
  5. Laget et BASH-skript som henter fila en gang om dagen og styrer oppdateringene gjennom crontab

Ja. Det ble mye mer jobb enn forventet(mye mer enn bare å hente data med CURL), men det positive er at jeg fikk oppdatert PHP, SQL og MySQL kunnskapene mine. Fant også ut at mange av de fuksjonene jeg bruker nå kommer til å bli "deprecated" når versjon 5.5 av PHP kommer. Det gleder jeg meg ikke til for å si det sånn. Men den tid, den sorg.

For å være helt ærlig så likte jeg ikke phpExcel. Stort og uoversiktelig. Dokumentasjonen er så som så(masse pdf-dokumenter som er tungvindt å se gjennom). Istedenfor å lære seg, så ble det å bruke eksempler på nettet(disse var også så som så ville jeg ha sagt). Men men. Poenget er at man kommer i mål. Og det gjorde jeg..tilslutt.

Jeg skal snakke litt om punkt 5 ovenfor. Dette php-skriptet skal hente data fra 2 rader i xlsx-fila.

  1. Rad 1 inneholder 'Land'(navnet på landet)
  2. Rad 2 inneholder dagens valutaverdi tilhørende 'Land'

I mitt eksempel så er det rad 5 og 8. For å kunne laste inn bare disse to radene er man først nødt til å lage et filtersett til Reader-objektet. Dette kan gjøres på denne måten:

class MyReadFilter implements PHPExcel_Reader_IReadFilter 
  { 
      public function readCell($column, $row, $worksheetName = '') 
      { 
	  //  Read row 5 & 8 and 
	  if ($row == 5 || $row == 8) 
	  {
	    return true;
	  } 
	  return false; 
      } 
  }


Og så er det en del initialiseringer, for å skape flere objekter:

 $filterSubset = new MyReadFilter();
  /** Create a new Reader of the type defined in $inputFileType **/
  $objReader = PHPExcel_IOFactory::createReader($inputFileType);
  $objReader->setLoadSheetsOnly($sheetname);
  $objReader->setReadDataOnly(true);
  $objReader->setReadFilter($filterSubset);
  
  $objPHPExcel = $objReader->load($inputFileName);
 
  $objWorksheet = $objPHPExcel->setActiveSheetIndex(0);
  $curColumn = 0;
  $lastColumn = PHPExcel_Cell::columnIndexFromString('AP');

Linje 1 og 6 er det viktige her. Like viktig som det andre, men for å få filteret til å fungere så må man ha med disse linjene. En nyttig funksjon er å bruke "columnIndexFromString(..). (linje 12)Det er mye bedre enn å begynne å telle med fingeren. Den oversetter kolonne-navnet til en kolonne index-nummer slik at vi vet når vi skal slutte å lese. Veldig bra.

En annen funksjon jeg fant som er viktig er dato-oversetteren hvis man kan kalle det for det. Leser man bare cellen får man et stort tall som ingen har noe nytte av. Her er den:

$date_string = date('Y-m-d',PHPExcel_Shared_Date::ExcelToPHP($objWorksheet->getCellByColumnAndRow($curColumn++, $row)->getValue()));

PHP-funksjonen "date" er grei. Den tar 2 parametere. 1: dato-format(nesten som linux og date kommandoen), og parameter 2 tar dataen den skal gjøre om. "PHPExcel_Shared_Date::ExcelToPHP" fant jeg på nettet, når jeg søkte etter "how to convert date in phpExcel". Funksjons-navna er ikke akkurat noe man kan gjette seg fram til. Det er ikke innlysende. Da tror jeg nesten man er nødt til å lese hele "manuskriptet" deres og håpe på at man finner rette fuksjon. Eller man kan kanskje lese seg til dato-formatet på en eller annen side som forteller om EXCEL-datoer. Ikke noe man har lyst til med det første for å si det sånn.

Å få dette til fikk meg nesten til å gråtecrying. Men bare nesten, for dette er noe av det kjedeligste jeg noen gang har vært borti.

Det neste steget var å legge til all data. Skal legge fram dette også bare for å ha gjort det, slik at jeg har det framme i det åpne. Har faktisk begynt å bruke "sprintf" for å formatere sql-setningene mine. Synes faktisk dette er litt enklere. Litt mere oversiktlig enn å sette sammen en hel masse sub-strenger.

$countryName = "";
  $curValutaValue = 0;
  $country_id = 0;
  for($curColumn; $curColumn <= $lastColumn; $curColumn++)
  {
    $countryName = $objWorksheet->getCellByColumnAndRow($curColumn, $countryRow)->getValue();
    $sql_get_country_id = sprintf("SELECT valutakurser.valutaid FROM posten.valutakurser WHERE Land='%s'", $countryName);
    $result = mysql_query($sql_get_country_id, $conn);
    if(!$result)
    {
      echo "Error: ".mysql_error();
      mysql_close($conn);
      echo "\nsql_string: $sql_get_country_id\n";
      exit("Mysql-error..exiting<br />");
    }
    else
    {
      $country_id=mysql_result($result, 0);
    }
    //check and see if this date exists in database..
    $sql_check_date = sprintf("SELECT dato FROM valutaVerdier WHERE valutakurser_valutaid='%s' && dato='%s'", $country_id, $date_string);
    $result = mysql_query($sql_check_date, $conn);
    if(!$result)
    {
      echo "Error: ".mysql_error();
      mysql_close($conn);
      echo "\nsql_string: $sql_check_date\n";
      exit("Mysql-error..exiting<br />");
    }
    else {
        $date=mysql_result($result, 0);
        if($date == NULL)
	  echo "We can insert\n";
	else {
	    exit("We can't insert. Date allready exists\n");
	}
	
    }
    
    $curValutaValue = $objWorksheet->getCellByColumnAndRow($curColumn, $row)->getValue();
    $sql_insert_value = sprintf("INSERT INTO posten.valutaVerdier VALUES (DEFAULT, '%s','%s','%s')", $curValutaValue, $date_string, $country_id);
    $result = mysql_query($sql_insert_value, $conn);
    if(!$result)
    {
      echo "Error: ".mysql_error();
      mysql_close($conn);
      echo "\nsql_string: $sql_insert_value\n";
      exit("Mysql-error..exiting<br />");
    }
    
  }
  mysql_free_result($result);
  unset($objWorksheet);
  unset($objReader);
  if(isset($conn))
    mysql_close($conn);

All koden vil jo ikke si noe som helst til noen, men det som også kan være greit er å nulle ut alle objekter. Dette gjør man så enkelt som å bruke funksojnen "unset(...)", som tar en parameter som igjen er ojektet som skal nulles ut.

Konklusjonen min:

Nå har jeg et "formular" som fungerer. Men håper at jeg ikke er nødt til å bruke det igjen. Det går faktisk ikke så veldig fort. Det er ikek efefektiv kode. Selvfølgelig: Det er ei stor fil, med mye data, men det kan hende at jeg kanskje ville ha brukt noe annet. Kanskje ett C eller C++ bibliotek istedenfor. Forestiller meg at koden som blir produsert da er raskere, for man må vente noe sekunder før skriptet er ferdig-kjørt.