I achieve this in a fast, sort of cheapskate way — because it’s long and winded I’ll just explain it in concept rather than code.
XLSX adheres to ISO 29500 which is publicly available if you want to manipulate a document thoroughly in php. Otherwise, realise that xlsx files are zipped archives of a bunch of xml files.
Make a template that you want, say it has alternating rows with styles of different types, making that in excel or an open xml editor of some description. Make sure you put some data in there, and make sure some fields are equal (just for learning purposes).
Then save your file as xlsx, rename it .zip, or open it in an archive extractor and observe the contents.
Firstly, note the [Content_Types].xml file, this describes the location of the major files in the archive and the standards to which it itself adheres and the content types of those files.
Everything outside the xl/
folder is just meta data really. But observe docProps/core.xml
contains author, modification and timestamp information — which you can replace in php when you recreate this file. Also everything that is pointed to say, docProps/core.xml
can be renamed to your tastes, [Content_Types].xml
can’t.
Okay so now you understand this, you’ll begin observing ids thrown around the place. They love to use this in the file format, everything refers to everything else by its index in a particular xml property list or similar. They also usually describe the quantity of items in such lists.
In xl/
you’ll see themes.xml, styles.xml, workbook.xml, sharedStrings.xml, _rels/, worksheets/
.
Styles is going to be inflated with a whole lot of unnecessary styles that excel builds by default if you used it. But you should be able to see how these styles work such that you can customise your own.
Themes to me is rather pointless so I delete it and its referenced ids throughout.
Next up you’ll see workbook, that’s the file containing information regarding the sheets which are inside of the spreadsheet document since you can have more than 1 obviously. It also contains some sheet metadata such as its size etc.
Now comes the first big hua you’ll encounter. sharedStrings.xml
is a weird file which stores all the information that will be inserted into cells in a static spreadsheet. They are indexed, but the engine reading the document figures out what their indexes are. Anything which repeats can be referred back to its old index in the sheet itself (inside worksheets folder) as to save on file size in large documents with repeated values.
Not the attributes count
and uniquecount
in the sst
element and what they obviously mean.
This is the stage in php where you populate an array of data containing what you want in your sheet, and dump it into an xml formatted list such as this file appears. Also note these files don’t need to be jammed up without newlines or linefeed characters as with or without is still valid xml and they will work in readers regardless.
Check out the _rels folder, it’s fairly obvious again.
Lastly is the sheet itself. The numbers in fields here refer to the indexed locations of strings in sharedStrings.xml
. The attribute s is the style, t is the type of data in the field. R is the cell location though why it needs that is beyond me when it could really be figured out rather easily.
Producing this file in php shouldn’t be too difficult either. Just use your indexes from your data array you used to make your sharedStrings.xml
file.
Oh also sheet has column width information in it which you can figure out based on the font you used and automatically size them in php too if need be.
Lastly is the packaging of it all in php.
My code is in a class which receives data and specific saved files I created with excel to keep it simple.
$this->folder_structure_simple = Array(
"_rels/.rels" => "_rels__rels",
"docProps/app.xml" => "docProps_app_xml",
"docProps/core.xml" => "docProps_core_xml",
"xl/_rels/workbook.xml.rels",
"xl/theme/theme1.xml",
"xl/worksheets/sheet1.xml",
"xl/sharedStrings.xml",
"xl/styles.xml",
"xl/workbook.xml",
"[Content_Types].xml" => "Content_Types_xml"
);
$zip = new ZipArchive;
$res = $zip->open($this->file_name, ZipArchive::CREATE);
if($res === TRUE){
foreach($this->folder_structure_simple as $file => $function){
$zip->addFromString($file, $this->$funtion);
}
$zip->close();
echo 'ok';
}else{
return FALSE;
}
And functions produce the required data. Very fast, not very flexible.
В первую очередь опишу проблему, которая заставила в тысячный раз вернуться к обсосанному со всех сторон вопросу: бестолковые менеджеры — без консультации с программистами — пообещали заказчику загрузку данных на сайт из xls(x).
Все бы ничего, но хостер заказчика дает 64мб памяти под выполнение и плевать он хотел на то, что у клиента Эксель файлы вообще без форматирования весят по 10-15мб, что при загрузке его PHPExel съедает (на тест-сервере) что-то около 500мб памяти.
Решение под катом с трудом дотянуло до 5мб.
Предусловия:
1. Имеется Exel документ листов так в 10-20 с данными о товарах в интернет-каталоге. В каждом листе шапка — «название», «цена» и т.п. + воз доп. характеристик в 40 столбцов — и собственно данные в количестве «у-экселя-сантиметровый-скроллер»;
2. никакого CSV использовать нельзя. Все данные у заказчика уже в Exel и пересохранять их он не собирается… пообещали тут и все;
3. Spreadsheet_Excel_Writer откинут по причине неуниверсальности, хотя написано про него много хорошего. Жду комментариев по memory tests;
4. что удивительно, универсальных решений гугль не предложил. Неужели никто не сталкивался с такой проблемой на PHP *nix, удивился я.
Решение:
После перебора различных способов, вежливо предоставленных гуглом, решили почитать спецификации (эхх, учил меня отец…). Увидев там ключевые слова основан на Open XML и используется сжатие ZIP быстро позвонили заказчику и перевели разговор в русло xslx и только: «Ну вы же понимаете! 21 век все-таки! Зачем нам хвататься за старое! Нужно одной ногой стоять в будущем!»
Далее алгоритм таков: принимаем файл, распаковываем его и внимательно смотрим на получившееся.
Полную инвентаризацию надо будет на досуге провести, а сейчас же нам наиболее интересно содержимое директории [xl], конкретно — /xl/worksheets/ и файл /xl/sharedStrings.xml.
В файле /xl/workbook.xml лежит описание листов, но т.к. задачи собрать названия листов не стояло, этот пункт я пропущу. При необходимости разобраться в нем не составит труда.
/xl/sharedStrings.xml
...
<si>
<t>Наименование</t>
</si>
<si>
<t>Описание</t>
</si>
<si>
<t>Изображение</t>
</si>
<si>
<t>URL</t>
</si>
<si>
<t>!Классификация</t>
</si>
<si>
<t>!Бренд</t>
</si>
<si>
<t>~1ф, 220-240 В, 50 Гц</t>
</si>
...
и так далее в том же духе. Представляет собой текстовые данные в ячейках исходного документа. Со всех листов! Пока просто соберем эти данные в массив.
$xml = simplexml_load_file(PATH . '/upload/xls_data/xl/sharedStrings.xml');
$sharedStringsArr = array();
foreach ($xml->children() as $item) {
$sharedStringsArr[] = (string)$item->t;
}
/xl/worksheets/
Это директория с файлами типа «sheet1.xml» с описанием данных листов. Конкретно нас интересует содержимое и его детей <row …>.
...
<sheetData>
...
<row r="1" spans="1:43" ht="48.75" customHeight="1" x14ac:dyDescent="0.2">
<c r="A1" s="1" t="s">
<v>0</v>
</c>
<c r="B1" s="1" t="s">
<v>1</v>
</c>
<c r="C1" s="2" t="s">
<v>2</v>
</c>
<c r="E2" s="12">
<v>2</v>
</c>
<c r="F2" s="12"/>
....
</row>
<row r="2" spans="1:43" ht="13.5" customHeight="1" x14ac:dyDescent="0.2">
...
</sheetData>
...
Методом сопоставлений и экспериментов было выяснено, что атрибут [t=«s»] у ячейки (судя по всему type=string) является указанием на то, что значение берем из файла sharedStrings.xml. Указатель — значение — номер элемента из $sharedStringsArr. Если не указан — берем само значение за значение ячейки.
Собираем:
$handle = @opendir(PATH . '/upload/xls_data/xl/worksheets');
$out = array();
while ($file = @readdir($handle)) {
//проходим по всем файлам из директории /xl/worksheets/
if ($file != "." && $file != ".." && $file != '_rels') {
$xml = simplexml_load_file(PATH . '/upload/xls_data/xl/worksheets/' . $file);
//по каждой строке
$row = 0;
foreach ($xml->sheetData->row as $item) {
$out[$file][$row] = array();
//по каждой ячейке строки
$cell = 0;
foreach ($item as $child) {
$attr = $child->attributes();
$value = isset($child->v)? (string)$child->v:false;
$out[$file][$row][$cell] = isset($attr['t']) ? $sharedStringsArr[$value] : $value;
$cell++;
}
$row++;
}
$page++;
}
}
var_dump($out);
На выходе получаем многомерный массив, с которым уже можно свободно работать, а можно и сразу в базу лить данные — это личное дело каждого.
Напоследок скажу, что толком в спецификации xlsx не разбирался, а только выполнил поставленную задачу с конкретными xlsx документами. Куда-то ведь должны писаться формулы и изображения (t=«i»?)? Когда столкнусь с такой задачей — непременно опишу, а пока представляю нетребовательный к системе алгоритм для сбора текстовых данных из xslx. Надеюсь, будет востребован, т.к. в поисках подобного не встречал.
P.S. Только расставляя метки наткнулся на Работа с большими файлами экселя. Хабрить надо было, а не гуглить — много бы времени сэкономил.
Автор: Elendai
Наверняка многие думают, что Excel формата XLSX очень сложен для чтения и нужно обязательно использовать сложные скрипты и библиотеки. Так было со старым форматом XLS. Для нового экселя – все куда проще. XLSX – это зип архив, внутри которого в виде XML расположены данные по ячейкам и все остальные – в стандартной структуре с малыми хитростями.
Знания этого можно применять для настройки импорта данных из экселя. Любых данных. В дальнейшем мы разберем, как можно самостоятельно создавать эксель файл, не прибегая к супер библиотекам.
Нам понадобятся PHP 5, встроенный модуль ZIP и SimpleXML. Как вы думаете, сколько строк кода нам понадобится?
Для примера — расположим файл в папке со скриптом. Назовем его file.xlsx. Из данного файла нам понадобятся файл с данными по первому листу. Он расположен по адресу
file.xlsx/xl/worksheets/sheet1.xml
И файл с частыми строками, сделанный из соображений экономии повторяющихся фраз
file.xlsx/xl/sharedStrings.xml
Нужно считать все строки и вставлять их значения при нахождении ссылки на строку
Сказано – сделано. Создаем ассоциативный массив
$file=file_get_contents('zip://file.xlsx#xl/sharedStrings.xml');
$xml=(array)simplexml_load_string($file);
$sst=array();
foreach ($xml['si'] as $item=>$val)$sst[]=iconv('UTF-8','windows-1251',(string)$val->t);
Первой строкой – мы считываем полный текст распакованного файла внутри архива
Далее – собираем XML структуру в ассоциативный массив
После чего – пробегаемся по элементам массива, записывая значения в итоговый массив $sst. ICONV используется только в случае, если у вас кодировка – не UTF-8
Что дальше? Дальше чуть сложнее. Двумерный массив с возможными ассоциациями
$file=file_get_contents('zip://file.xlsx#xl/worksheets/sheet1.xml');
$xml=simplexml_load_string($file);
$data=array();
foreach ($xml->sheetData->row as $row){
$currow=array();
foreach ($row->c as $c){
$value=(string)$c->v;
$attrs=$c->attributes();
if ($attrs['t']=='s'){
$currow[]=$sst[$value];
}else{
$currow[]=$value;
}
}
$data[]=$currow;
}
Собираем первый лист. Формируем XML, после чего проходимся по каждой строке row и разбираем каждю ячейку. Для получения атрибутов ячейки – используем свойство attributes(). Если атрибут ‘t’==’s’ – значит значение ячейки – это строка в ассоциативном массиве, полученном ранее.
После сбора строки в массив – соединяем в наш общий массив $data. На самом деле – вместо этого можно было бы сразу добавлять в таблицу, или использовать дополнительные проверки.
Все. Дело сделано. Первый лист прочитан и собран в массив. Сколько строк получилось? 20. Можно было бы сжать сильнее, оптимизировать. Если интересна структура файла – берите архиватор ZIP и смело пытайте XLSX. У вас обязательно получится
Я достигаю этого быстрым, каким-то способом дешевого пути — потому что он длинный и извилистый, я просто объясню это в концепции, а не в коде.
XLSX придерживается стандарта ISO 29500, который является общедоступным, если вы хотите тщательно манипулировать документом на php. В противном случае, поймите, что xlsx файлы — это zipped-архивы из набора XML файлов.
Создайте шаблон, который вам нужен, скажем, что он чередует строки со стилями разных типов, делая это в excel или в открытом редакторе xml некоторого описания. Убедитесь, что вы поместили некоторые данные там, и убедитесь, что некоторые поля равны (только для учебных целей).
Затем сохраните файл как xlsx, переименуйте его .zip или откройте его в экстракторе архива и просмотрите содержимое.
Во-первых, обратите внимание на файл [Content_Types].xml, это описывает расположение основных файлов в архиве и стандарты, к которым он сам придерживается, и типы содержимого этих файлов.
Все, что находится за пределами папки xl/
, действительно является метаданных. Но наблюдение docProps/core.xml
содержит информацию об авторе, модификации и временной отметке, которую вы можете заменить в php, когда вы воссоздаете этот файл. Также все, на что указывает, docProps/core.xml
можно переименовать на ваши вкусы, [Content_Types].xml
не может.
Ладно, теперь вы понимаете это, вы начнете наблюдать за идами, брошенными вокруг места. Они любят использовать это в формате файла, все ссылается на все остальное своим индексом в конкретном списке свойств xml или аналогичном. Они также обычно описывают количество элементов в таких списках.
В xl/
вы увидите themes.xml, styles.xml, workbook.xml, sharedStrings.xml, _rels/, worksheets/
.
Стили будут накачаны множеством ненужных стилей, которые по умолчанию строятся по умолчанию, если вы его использовали. Но вы должны уметь видеть, как эти стили работают так, что вы можете настроить свои собственные.
Темы для меня довольно бессмысленны, поэтому я удаляю его и ссылающиеся на него идентификаторы.
Далее вы увидите книгу, в которой содержится файл, содержащий информацию о листах, которые находятся внутри документа электронной таблицы, так как вы можете иметь более 1, очевидно. Он также содержит некоторые метаданные листа, такие как его размер и т.д.
Теперь наступает первый большой хуа, с которым вы столкнетесь. sharedStrings.xml
— это странный файл, который хранит всю информацию, которая будет вставлена в ячейки в статической электронной таблице. Они индексируются, но движок, читающий документ, определяет, каковы их индексы. Все, что повторяется, можно вернуть обратно к своему старому индексу в самом листе (внутри папки рабочих листов), чтобы сохранить размер файла в больших документах с повторяющимися значениями.
Не атрибуты count
и uniquecount
в элементе sst
и то, что они явно означают.
Это этап в php, где вы заполняете массив данных, содержащий то, что вы хотите на своем листе, и выгружаете его в список форматированных XML файлов, например, этот файл появляется. Также обратите внимание, что эти файлы не нужно зажимать без символов новой строки или символов перевода строки, так как с или без них все еще действует xml, и они будут работать независимо от читателей.
Проверьте папку _rels, она будет достаточно очевидной.
Наконец, это сам лист. Числа в полях здесь относятся к индексированным местоположениям строк в sharedStrings.xml
. Атрибутом s является стиль, t — тип данных в поле. R — это местоположение ячейки, но почему это нужно, что находится за пределами меня, когда это действительно можно понять довольно легко.
Создание этого файла в php тоже не должно быть слишком сложным. Просто используйте свои индексы из своего массива данных, которые вы использовали для создания вашего файла sharedStrings.xml
.
О, также в листе есть информация о ширине столбца, в которой вы можете найти на основе шрифта, который вы использовали, и автоматически их размер в php тоже, если это необходимо.
Наконец, это упаковка всего в php.
Мой код находится в классе, который получает данные и определенные сохраненные файлы, которые я создал с помощью excel, чтобы упростить его.
$this->folder_structure_simple = Array(
"_rels/.rels" => "_rels__rels",
"docProps/app.xml" => "docProps_app_xml",
"docProps/core.xml" => "docProps_core_xml",
"xl/_rels/workbook.xml.rels",
"xl/theme/theme1.xml",
"xl/worksheets/sheet1.xml",
"xl/sharedStrings.xml",
"xl/styles.xml",
"xl/workbook.xml",
"[Content_Types].xml" => "Content_Types_xml"
);
$zip = new ZipArchive;
$res = $zip->open($this->file_name, ZipArchive::CREATE);
if($res === TRUE){
foreach($this->folder_structure_simple as $file => $function){
$zip->addFromString($file, $this->$funtion);
}
$zip->close();
echo 'ok';
}else{
return FALSE;
}
И функции производят требуемые данные. Очень быстро, не очень гибко.
The spreadsheet is a very popular data organizing format used for personal and business data because it proposes a way to store tabular data in a systematized way. The spreadsheet provides a format for storing information in the form of rows and columns. Hence, spreadsheets have become an indispensable part of keeping, organizing, and analyzing data. Automated solutions are more trending in business nowadays, the inclination of creating and manipulating Excel documents (XLS/XLSX) has emerged and budding at a fast pace. In this article, we’re going to explain how to create and edit Excel XLS or XLSX files in PHP-based apps. Basically, there are two ways to create Excel spreadsheets in PHP: (i) by using a PHP library or (ii) without using any library. The main focus will be on creating or manipulating an excel sheet by using a library.
PHP Libraries
Excel files can be difficult to work with from the command line or outside of Microsoft office software suites, that’s why PHP libraries were introduced to deal with excel files and make this task easier. There are many libraries that work efficiently like PHP spreadsheet, Spout, PHP excel template, Aspose. Cells for PHP via Java (it’s a powerful API) and PHPExcel library etc. PHP excel library allows you to read and create spreadsheets in various formats including ODS, XLSX, CSV, and XLS. Make sure that you have PHP’s upgraded version and not older than PHP 5.2. Also, it’s better if you install these extensions: php_qd2, php_zip and php_xml.
Creating the spreadsheet from Scratch
In PHP development, spreadsheet creation is common as it is used to export data to an Excel spreadsheet. The following code can be used to create a spreadsheet from scratch using the PHPExcel library:
1. Include PHPExcel library and creation of its object:
require(‘PHPExcel.php’);
$phpExcel=new PHPExcel;
2. Set the font to Arial Black:
$phpExcel->getDefaultStyle()->getFont()->setName(‘Arial Black’);
3. Now set the Font size to 12
$phpExcel->getDefaultStyle()->getFont()->setSize(12);
4. Now set title, description, and creator
$phpExcel->getProperties()->setTitle(“Purchase List”);
$phpExcel->getProperties()->setCreator(“Sebastian”);
$phpExcel->getProperties()->setDescription(“Excel Spreadsheet in PHP”);
5. We will create writer object and XLSX file in Excel 2007 and above
$writer=PHPExcelIOfactory::createWriter($phpExcel. “Excel2007”);
6. Along with creating the writer object, the first sheet is also created. So we will get the already created sheet
$sheet->setTitle(‘My Products List’);
7. Create the spreadsheet header
$sheet->getCell(‘A1’)->setValue(‘Product1’);
$sheet->getCell(‘B1’)->setValue(‘Quantity’);
$sheet->getCell(‘C1’)->setValue(‘Price’);
8. Make the header text bold and larger
$sheet->getStyle(‘A1:D1’)->getFont()->setBold(true)->setSize(14);
9. Insert product data and auto size the columns
$sheet->getColumnDimension(‘A’)->setAutosaveSize(true);
$sheet->getColumnDimension(‘B’)->setAutosaveSize(true);
$sheet->getColumnDimension(‘C’)->setAutosaveSize(true);
10. Now save the spreadsheet
$writer->save(‘productslist.xlsx’);
Editing an Existing Spreadsheet in PHP
Editing a spreadsheet is quite similar to creating a new spreadsheet process. The following code can be used to edit:
1. Include the PHPExcel library and creates its object
require(‘PHPExcel.PHP);
2. Open an existing spreadsheet
$phpExcel=PHPExcel_IOFactory::load(‘productslist.xlsx’);
3. Get the first spreadsheet
$sheet=$phpExcel->getActiveSheet();
4. Now Remove two rows, starting from the row 2
$sheet->removeRow(2,2);
5. Insert a new row before row 2
$sheet->insertNewRowBefore(2,1);
6. We will create writer object and XLSX file in Excel 2007 and above
$writer=PHPExcel_IOFactory::createWriter($phpExcel,”Excel2007”);
7. Now save it
$writer->save(‘productslist.xlsx’);
Preparing a Spreadsheet for Printing
To prepare a spreadsheet for printing, you need to set size, paper orientation and margins with the help of the following coding:
1. $sheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_LANDSCAPE);
2. $sheet->getPageSetup()->setPaperSize(PHPExcel_Wroksheet_PaperSetup::PAPERSIZE_A4);
3. $sheet->getPageMargins()->setTop(1);
4. $sheet-getPageMargins()->setRight(0.80);
5. $sheet-getPageMargins()->setLeft(0.80);
6. $sheet-getPageMargins()->setBottom(1);
Making a spreadsheet in PHP can be done easily by using the above guide.
Credit: Laura Jimenez, Ishine365