XML-entry not found в SimpleXLSX
Один сервис, который я поддерживаю и разрабатываю, перестал парсить файлы выборки СПАРК. Выдается ошибка «XML-entry not found: /xl/workbook.xml». Эта ошибка возникает, когда библиотека SimpleXLSX не может найти или прочитать файл workbook.xml внутри XLSX-архива. Разберемся как устранить её.

Изменение workbook.xml
Первое же, что я сделал – попробовал открыть файл в свое экселе на компьютере. Файл открылся, ошибок эксель не выдал. Сохранил и попробовал заново загрузить в сервис – прошло. Все бы хорошо, но сервис на сервере, он понятия не имеет об экселе и как открывать-сохранять файлы, поэтому я решил для начала найти различия в файлах.
Файлы xlsx – это, по сути, архивы и распаковываются, например, 7zip. Внутри будет несколько папок, так как ошибку выдает xl/workbook.xml, то его и смотрим. Тот файл, который не может открыть SimpleXLSX такой:
<?xml version="1.0" encoding="utf-8"?><x:workbook xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><x:workbookPr codeName="ThisWorkbook" /><x:bookViews><x:workbookView firstSheet="0" activeTab="0" /></x:bookViews><x:sheets><x:sheet name="report" sheetId="2" r:id="rId2" /><x:sheet name="Условия запроса" sheetId="3" r:id="rId3" /></x:sheets><x:definedNames /><x:calcPr calcId="125725" /></x:workbook>А вот содержимое пересохраненного файла:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x15 xr xr6 xr10 xr2" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:xr6="http://schemas.microsoft.com/office/spreadsheetml/2016/revision6" xmlns:xr10="http://schemas.microsoft.com/office/spreadsheetml/2016/revision10" xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2"><fileVersion appName="xl" lastEdited="7" lowestEdited="7" rupBuild="20827"/><workbookPr codeName="ThisWorkbook"/><mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"><mc:Choice Requires="x15"><x15ac:absPath url="C:\Users\up\Desktop\" xmlns:x15ac="http://schemas.microsoft.com/office/spreadsheetml/2010/11/ac"/></mc:Choice></mc:AlternateContent><xr:revisionPtr revIDLastSave="0" documentId="13_ncr:1_{8602B8A7-C897-479F-92D4-2745B8E64C2B}" xr6:coauthVersionLast="37" xr6:coauthVersionMax="37" xr10:uidLastSave="{00000000-0000-0000-0000-000000000000}"/><bookViews><workbookView xWindow="0" yWindow="0" windowWidth="19200" windowHeight="10785" xr2:uid="{00000000-000D-0000-FFFF-FFFF00000000}"/></bookViews><sheets><sheet name="report" sheetId="2" r:id="rId1"/><sheet name="Условия запроса" sheetId="3" r:id="rId2"/></sheets><calcPr calcId="0"/></workbook>Бросается в глаза сразу иной корневой элемент. Напише функцию для его замены.
function simpleFixWorkbookXml($xml) { $fixed = str_replace( '<x:workbook xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main">', '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x15 xr xr6 xr10 xr2" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:xr6="http://schemas.microsoft.com/office/spreadsheetml/2016/revision6" xmlns:xr10="http://schemas.microsoft.com/office/spreadsheetml/2016/revision10" xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2">', $xml ); $fixed = str_replace('</x:workbook>', '</workbook>', $fixed); $fixed = str_replace(['<x:', '</x:'], ['<', '</'], $fixed); return $fixed; }А вот полная функция для распаковывания, замены и запаковывания
function repairXlsxFile($inputPath, $outputPath) { // Создаем временную директорию $tempDir = sys_get_temp_dir() . '/xlsx_repair_' . uniqid(); mkdir($tempDir); try { // Распаковываем XLSX $zip = new ZipArchive; if ($zip->open($inputPath) !== true) { throw new Exception("Cannot open input file"); } $zip->extractTo($tempDir); // Исправляем workbook.xml $workbookPath = $tempDir . '/xl/workbook.xml'; if (file_exists($workbookPath)) { $xmlContent = file_get_contents($workbookPath); $fixedXml = simpleFixWorkbookXml($xmlContent); file_put_contents($workbookPath, $fixedXml); } // Создаем новый архив $newZip = new ZipArchive; if ($newZip->open($outputPath, ZipArchive::CREATE) !== true) { throw new Exception("Cannot create output file"); } // Добавляем все файлы обратно $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($tempDir), RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($files as $file) { if (!$file->isDir()) { $relativePath = substr($file->getRealPath(), strlen($tempDir) + 1); $newZip->addFile($file->getRealPath(), $relativePath); } } $newZip->close(); // Очищаем временные файлы array_map('unlink', glob("$tempDir/xl/*")); rmdir("$tempDir/xl"); rmdir($tempDir); return true; } catch (Exception $e) { error_log("Repair error: " . $e->getMessage()); return false; } }Функция на PHP отлично отработал, файл заменила, но SimpleXLSX все равно выдавал ошибку. Видимо, различия и в других файлах, может кроме корневого элемента надо все менять. В общем, я решил пойти по другому пути.
LibreOffice выручает
Если помогает пересохранение, то попробовать открыть другим редактором файл и пересохранить на сервере. Есть же и для Ubuntu что-то подобное, открытое ПО? Есть, например LibreOffice.
Ставим его
sudo add-apt-repository ppa:libreoffice/ppa sudo apt update sudo apt install libreofficeПробуем запустить из командной строки конвертацию файла в самого себя
libreoffice --headless --convert-to xlsx --outdir выходной_каталог входящий_файлИ преобразованный файл SimpleXLSX отлично принял, как родного!
Теперь набросаем cкрипт на PHP для конвертации через LibreOffice. Что-то типа такого, самый простой вариант через exec
$command = "libreoffice --headless --convert-to xlsx --outdir " . escapeshellarg(dirname($outputPath)) . " " . escapeshellarg($inputPath); exec($command, $output, $return_var); if ($return_var !== 0) { echo "Command failed with return code: $return_var\n"; echo "Output:\n"; print_r($output); } else { echo "Conversion successful!"; }И получаем ошибку
Command failed with return code: 77 Output: Array ( [0] => javaldx failed! [1] => Warning: failed to read path from javaldx [2] => LibreOffice 7.6 - Fatal Error: The application cannot be started. [3] => User installation could not be completed. )Java в системе точно есть, значит, проблема в профиле. Создаем его явно, плюсом добавляем крое -что и вот итоговая работающая функция
function resaveWithLibreOffice($inputFile) { $command = 'export HOME=/tmp && /usr/bin/libreoffice -env:UserInstallation=file:///tmp/libreoffice_profile --headless --nologo --norestore --nofirststartwizard --convert-to xlsx:"Calc MS Excel 2007 XML" --outdir /var/www/html/parser2/temp/2 '.$inputFile.' 2>&1'; exec($command, $output, $return_var); return $return_var; }Вот так можно решить проблему. Есть вопросы? Пишите. На форуме сайта вам могут ответить бесплатно, если спрашиваете лично у меня - консультации платные.

Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.
Обсудить или задать вопрос по этой или любой другой теме можно на нашем форуме