XML-entry not found в SimpleXLSX


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

SimpleXLSX github

Изменение 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 - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегизаметки, php, SimpleXLSX, парсинг

Обсудить или задать вопрос по этой или любой другой теме можно на нашем форуме



Не так страшен битрикс, или выгрузка в bitrix24
Лишний слайд в Slick slider
Почему бан на фейсбуке и как избежать его повторно