Blog. Just Blog

Чтение файла XLSX на PHP без сторонних библиотек

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Web-мастеру и не только | Автор: ManHunter
Чтение файла XLSX на PHP без сторонних библиотек
Чтение файла XLSX на PHP без сторонних библиотек

На работе возникла задача читать данные из таблиц формата XLSX. Много лет для этого я использовал замечательную библиотеку PHPExcel, но по ряду технических причин дальнейшее ее использование не представляется возможным. При этом задачу никто не отменял. Поэтому пришлось рисовать свое решение, максимально простое и быстрое.

Начнем с теории. Файлы формата XLSX представляют собой обычный ZIP-архив, в котором, во вложенной папке \xl, содержится информация о документе. Первое, что надо знать, это количество листов в книге. В файле \xl\workbook.xml об этом есть вся информация. Особое значение имеет файл \xl\sharedStrings.xml, в нем содержится список строк приблизительно в таком виде:

[si] => Array (
  [0] => SimpleXMLElement Object (
    [t] => Тип упаковки
  )

  [1] => SimpleXMLElement Object (
    [t] => Вес груза
  )

  [2] => SimpleXMLElement Object (
    [t] => Количество
  )

У каждой строки получается собственный номер в порядке следования. Файл имеет простейшую структуру, разобрать его не представляет труда.

Теперь самое интересное. В папке \xl\worksheets находятся файлы вида sheet1.xml, sheet2.xml и так далее по количеству листов в книге. Если такой файл загрузить в память и разобрать при помощи функции simplexml_load_string, то в поле sheetData будет содержаться вся информация о строках и столбцах, а также об ячейках. Структура примерно следующая:

[sheetData] => SimpleXMLElement Object (
  [row] => Array (
    [0] => SimpleXMLElement Object (
      [@attributes] => Array (
        [r] => 1
        [spans] => 1:15
        [ht] => 46.5
        [customHeight] => 1
      )
      [c] => Array (
        [0] => SimpleXMLElement Object (
          [@attributes] => Array (
            [r] => A1
            [s] => 36
            [t] => s
          )
          [v] => 11
        )

        [1] => SimpleXMLElement Object (
          [@attributes] => Array (
            [r] => B1
            [s] => 37
            [t] => s
          )
          [v] => 1
        )

        [2] => SimpleXMLElement Object (
          [@attributes] => Array (
            [r] => C1
            [s] => 38
          )
          [v] => 1178
        )

        [3] => SimpleXMLElement Object (
          [@attributes] => Array (
            [r] => D1
            [s] => 38
          )
          [v] => 2.2
        )

В теге @attributes в поле r находится экселевское название ячейки, в поле v записано содержимое ячейки. При этом, если установлено поле t и оно имеет значение "s", то числовое значение ячейки является индексом строки из файла sharedStrings.xml. Если этого поля нет или оно имеет другое значение, то значение поля v рассматривается как конечное значение этой ячейки. Это самый простейший вариант структуры XLSX-файла, без формул, без объединений ячеек и сложной структуры, без перекрестных ссылок и всякого такого прочего. Обычные статичные данные. Со структурой разобрались.

Теперь о том, как извлечь XML-файлы из контейнера. Проще всего для этого использовать штатный PHP-класс ZipArchive. Открываем XLSX-файл как ZIP-архив, при необходимости запрашиваем информацию о количестве листов в книге, обязательно извлекаем все строки из sharedStrings.xml, затем загружаем первый или нужное количество листов sheetN.xml.

Минимальный код для преобразования содержимого первого листа книги в двумерный массив у меня получился следующий:
  1. <?
  2. $z=new ZipArchive();
  3. $z->open('file.xlsx');
  4.  
  5. $str_values=array();
  6.  
  7. // Прочитать строковые значения
  8. if ($fp=$z->getStream('xl/sharedStrings.xml')) {
  9.   $data='';
  10.   while (!feof($fp)) {
  11.     $data.=fread($fp1024);
  12.   }
  13.   fclose($fp);
  14.  
  15.   $xml=simplexml_load_string($data);
  16.  
  17.   if (isset($xml->si) && count($xml->si)) {
  18.     foreach ($xml->si as $data) {
  19.       $data=(array)$data;
  20.       $str_values[]=$data['t'];
  21.     }
  22.   }
  23. }
  24.  
  25. $xls_values=array();
  26.  
  27. // Прочитать значения из первой страницы документа
  28. if ($fp=$z->getStream('xl/worksheets/sheet1.xml')) {
  29.   $data='';
  30.   while (!feof($fp)) {
  31.     $data.=fread($fp1024);
  32.   }
  33.   fclose($fp);
  34.  
  35.   $xml=simplexml_load_string($data);
  36.  
  37.   if (isset($xml->sheetData)) {
  38.     $sheetData=(array)($xml->sheetData);
  39.     if (isset($sheetData['row']) && count($sheetData['row'])>0) {
  40.       foreach($sheetData['row'] as $row) {
  41.         $row=(array)$row;
  42.  
  43.         // Особый случай для одноколоночной страницы
  44.         if (!is_array($row['c'])) {
  45.           $row['c']=array($row['c']);
  46.         }
  47.  
  48.         foreach ($row['c'] as $col) {
  49.           $col=(array)$col;
  50.  
  51.           // Столбец и колонка
  52.           preg_match('/([A-Z]+)(\d+)/',$col['@attributes']['r'],$matches);
  53.           // Строка из списка
  54.           if (isset($col['@attributes']['t'])
  55.             && $col['@attributes']['t']=='s'
  56.             && isset($str_values[$col['v']]))
  57.           {
  58.             $xls_values[$matches[2]][$matches[1]]=$str_values[$col['v']];
  59.           }
  60.           // Непосредственное значение
  61.           elseif (isset($col['v'])) {
  62.             $xls_values[$matches[2]][$matches[1]]=$col['v'];
  63.           }
  64.         }
  65.       }
  66.     }
  67.   }
  68. }
  69.  
  70. $z->close();
  71.  
  72. // Массив со значениями ячеек
  73. print_r($xls_values);
Здесь я пропустил большинство проверок на содержимое файла книги, корректность структуры данных и наличие необходимых полей, это вы можете сделать самостоятельно.

О том, как создавать файлы формата XSLX без использования сторонних библиотек, вы можете прочитать в этой статье.

Поделиться ссылкой ВКонтакте
Просмотров: 560 | Комментариев: 7

Метки: PHP

Комментарии

Отзывы посетителей сайта о статье
ID (13.06.2024 в 06:41):
ManHunter, Олдскул ;)
ManHunter (26.04.2024 в 12:45):
в общих случаях моя схема следующая:
if (file_exists($file) && is_file($file) && is_readable($file)) {
  if ($f=fopen($file,'r')) {
    // известные читаем по filesize(), неизвестные до eof()
    fclose($f);
  }
  else {
    // что-то не так с доступностью файла
  }
}
else {
  // что-то не так с наличием файла
}
в частных, как тут, меняется только начальная проверка доступности.
Чик (26.04.2024 в 12:36):
Мне просто для работы надо, я же не спорю.) Думал, вдруг я что-то упущу, если не воспользуюсь усложненным для меня вариантом. Спасибо.
ManHunter (26.04.2024 в 12:28):
Вот, "легче" уже начинает утяжеляться проверками :) В остальном спорить не буду, мне работать с файловыми сущностями удобнее именно так.
Чик (26.04.2024 в 12:23):
Если не сложно, можете, пожалуйста, объяснить, про какие именно ресурсы тут идет речь? Файл xl/sharedStrings.xml можно проверить с условием
    $sharedStrings = $obZip->getFromName('xl/sharedStrings.xml');
    if ($sharedStrings !== false) {
        $sharedStringsXml = simplexml_load_string($sharedStrings);

Или тут речь про содержание файла? Что может произойти не так в таком случае?
ManHunter (26.04.2024 в 11:54):
"легче" != "лучше". У меня есть хорошая привычка проверять доступность ресурса перед чтением из него, а не тупо тянуть на удачу, что вытянется.
Чик (26.04.2024 в 11:01):
В конечном коде не легче было использовать:
$sharedStrings = $obZip->getFromName('xl/sharedStrings.xml');
$xml = simplexml_load_string($sharedStrings);

Вместо:
if ($fp=$z->getStream('xl/sharedStrings.xml')) {
  $data='';
  while (!feof($fp)) {
    $data.=fread($fp, 1024);
  }
  fclose($fp);

  $xml=simplexml_load_string($data);

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0033 сек.) / Память: 4.5 Mb
Наверх