Общие функции для парсеров текущих курсов валют

Для работы всех парсеров текущих курсов валют требуются еще несколько дополнительных общих для всех или некоторых функций.


Функция нормирования курсов валют

Разные банки предоставляют курсы валют по разному. Одни приводят к единице своей валюты, другие указывают курс одной единицы исходной валюты, существуют и промежуточные варианты. Данная функция нормализует все курсы валют к единому интервалу. Исходная валюта дается единицами кратными 10 так, чтобы курс в единицах валюты банка находился в диапазоне значений от 20 до 200. В этом случае мы имеем оптимальное число значащих цифр 4-5 и наименьшую погрешность округления. Значение курс дополнительно округляется до двух цифр после запятой.

function curency_prepare_rate($rate, $low_limit = 20, $precision = 2) {
  $low_limit = (int) $low_limit;
  $high_limit = $low_limit*10;
  $precision = (int) $precision;
  $tovalue = $rate['tovalue'];
  $fromvalue = $rate['fromvalue'];
  if($tovalue < $low_limit) {
    while ($tovalue < $low_limit) {
      $fromvalue = $fromvalue*10;
      $tovalue = $tovalue*10.;
    }
  }
  else if ($tovalue>=$high_limit) {
    while ($tovalue>=$high_limit) {
      $fromvalue = $fromvalue/10;
      $tovalue = $tovalue/10.;
    }
  }
  $rate['tovalue'] = round($tovalue, $precision);
  $rate['fromvalue'] = $fromvalue;
  return $rate;
}


Функция очистки сырых данных с HTML-страниц

Удаление лишних тегов, пробелов, если источник данных представляет собой веб-страницу.

function currency_clear_array($arr) {
   $res = array();
   $s = array('</table>', '<br>', '</body>', '</html>', ' ', '&nbsp;', "\xC2"."\xA0");
   $r = array( '', ' ', '', '', '', '', '');
   array_shift($arr);
   foreach ($arr as $el) {
     $el = trim (strip_tags (str_replace( $s, $r, $el)));
     /*if (!empty ($el)) */$res[] = $el;
   }
   return $res;
}


Функция преобразования текста из национальной кодировки в UTF-8

Некоторые источники данных являются веб-страницами в национальной кодировке. С помощью этой функции данные преобразуются в формат UTF-8. Функция взята из кода CMS Drupal.

function currency_convert_to_utf8($data, $encoding) {
   if (function_exists('iconv')) {
     $out = @iconv($encoding, 'utf-8', $data);
   }
   else if (function_exists('mb_convert_encoding')) {
     $out = @mb_convert_encoding($encoding, $data, 'utf-8');
   }
   else {
     return FALSE;
   }
   return $out;
}


Функция получения XML или HTML данных с сайтов банков

Предназначена для получения данных по одному из интернет-протоколов. Функция взята из кода CMS Drupal.

function currency_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3) {
   global $db_prefix;

   $result = new stdClass();

   // Parse the URL and make sure we can handle the schema.
   $uri = parse_url($url);

   if ($uri == FALSE) {
     $result->error = 'unable to parse URL';
     $result->code = -1001;
     return $result;
   }

   if (!isset($uri['scheme'])) {
     $result->error = 'missing schema';
     $result->code = -1002;
     return $result;
   }

   switch ($uri['scheme']) {
     case 'http':
     case 'feed':
       $port = isset($uri['port']) ? $uri['port'] : 80;
       $host = $uri['host'] . ($port != 80 ? ':'. $port : '');
       $fp = @fsockopen($uri['host'], $port, $errno, $errstr, 15);
       break;
     case 'https':
       // Note: Only works for PHP 4.3 compiled with OpenSSL.
       $port = isset($uri['port']) ? $uri['port'] : 443;
       $host = $uri['host'] . ($port != 443 ? ':'. $port : '');
       $fp = @fsockopen('ssl://'. $uri['host'], $port, $errno, $errstr, 20);
       break;
     default:
       $result->error = 'invalid schema '. $uri['scheme'];
       $result->code = -1003;
       return $result;
   }

   // Make sure the socket opened properly.
   if (!$fp) {
     // When a network error occurs, we use a negative number so it does not
     // clash with the HTTP status codes.
     $result->code = -$errno;
     $result->error = trim($errstr);

     // Mark that this request failed. This will trigger a check of the web
     // server's ability to make outgoing HTTP requests the next time that
     // requirements checking is performed.
     // @see system_requirements()
     variable_set('drupal_http_request_fails', TRUE);

     return $result;
   }

   // Construct the path to act on.
   $path = isset($uri['path']) ? $uri['path'] : '/';
   if (isset($uri['query'])) {
     $path .= '?'. $uri['query'];
   }

   // Create HTTP request.
   $defaults = array(
     // RFC 2616: "non-standard ports MUST, default ports MAY be included".
     // We don't add the port to prevent from breaking rewrite rules checking the
     // host that do not take into account the port number.
     'Host' => "Host: $host",
     'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)',
   );

   // Only add Content-Length if we actually have any content or if it is a POST
   // or PUT request. Some non-standard servers get confused by Content-Length in
   // at least HEAD/GET requests, and Squid always requires Content-Length in
   // POST/PUT requests.
   $content_length = strlen($data);
   if ($content_length > 0 || $method == 'POST' || $method == 'PUT') {
     $defaults['Content-Length'] = 'Content-Length: '. $content_length;
   }

   // If the server url has a user then attempt to use basic authentication
   if (isset($uri['user'])) {
     $defaults['Authorization'] = 'Authorization: Basic '. base64_encode($uri['user'] . (!empty($uri['pass']) ? ":". $uri['pass'] : ''));
   }

   // If the database prefix is being used by SimpleTest to run the tests in a copied
   // database then set the user-agent header to the database prefix so that any
   // calls to other Drupal pages will run the SimpleTest prefixed database. The
   // user-agent is used to ensure that multiple testing sessions running at the
   // same time won't interfere with each other as they would if the database
   // prefix were stored statically in a file or database variable.
   if (is_string($db_prefix) && preg_match("/^simpletest\d+$/", $db_prefix, $matches)) {
     $defaults['User-Agent'] = 'User-Agent: ' . $matches[0];
   }

   foreach ($headers as $header => $value) {
     $defaults[$header] = $header .': '. $value;
   }

   $request = $method .' '. $path ." HTTP/1.0\r\n";
   $request .= implode("\r\n", $defaults);
   $request .= "\r\n\r\n";
   $request .= $data;

   $result->request = $request;

   fwrite($fp, $request);

   // Fetch response.
   $response = '';
   while (!feof($fp) && $chunk = fread($fp, 1024)) {
     $response .= $chunk;
   }
   fclose($fp);

   // Parse response.
   list($split, $result->data) = explode("\r\n\r\n", $response, 2);
   $split = preg_split("/\r\n|\n|\r/", $split);

   list($protocol, $code, $status_message) = explode(' ', trim(array_shift($split)), 3);
   $result->protocol = $protocol;
   $result->status_message = $status_message;

   $result->headers = array();

   // Parse headers.
   while ($line = trim(array_shift($split))) {
     list($header, $value) = explode(':', $line, 2);
     if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
       // RFC 2109: the Set-Cookie response header comprises the token Set-
       // Cookie:, followed by a comma-separated list of one or more cookies.
       $result->headers[$header] .= ','. trim($value);
     }
     else {
       $result->headers[$header] = trim($value);
     }
   }

   $responses = array(
     100 => 'Continue', 101 => 'Switching Protocols',
     200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
     300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
     400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed',
     500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported'
   );
   // RFC 2616 states that all unknown HTTP codes must be treated the same as the
   // base code in their class.
   if (!isset($responses[$code])) {
     $code = floor($code / 100) * 100;
   }

   switch ($code) {
     case 200: // OK
     case 304: // Not modified
       break;
     case 301: // Moved permanently
     case 302: // Moved temporarily
     case 307: // Moved temporarily
       $location = $result->headers['Location'];

       if ($retry) {
         $result = currency_http_request($result->headers['Location'], $headers, $method, $data, --$retry);
         $result->redirect_code = $result->code;
       }
       $result->redirect_url = $location;

       break;
     default:
       $result->error = $status_message;
   }

   $result->code = $code;
   return $result;
}

Исправление 2

Устранена ошибка в коде функции
Строка 161:

$result = currency_http_request($result->headers['Location'], $headers, $method, $data, --$retry);

заменена на
$result = currency_http_request($result->headers['Location'], $headers, $method, $data, --$retry);

Тяжелое наследство Drupal'а. :D

Качественные перила на заказ в Киеве | this