Извлечь файл из строки ZIP
У меня есть строка BASE64 zip файла, содержащего один файл XML.
Любые идеи о том, как я могу получить содержимое файла XML без необходимости иметь дело с файлами на диске?
Мне бы очень хотелось сохранить весь процесс в памяти, поскольку XML имеет только 1-5k.
Было бы неприятно писать zip, извлекать XML, а затем загружать его и удалять все.
Ответы
Ответ 1
После нескольких часов исследований я думаю, что удивительно невозможно обработать zip без временного файла:
- Первая попытка с
php://memory
не будет работать, вывести поток, который не может быть прочитан функциями типа file_get_contents()
или ZipArchive::open()
. В комментариях есть ссылка на php-bugtracker из-за отсутствия документации по этой проблеме.
- Существует поддержка потока
ZipArchive
с ::getStream()
, но, как указано в руководстве, она поддерживает только операцию чтения в открытом файле. Таким образом, вы не можете создавать архив на лету с этим.
- Обертка
zip://
также доступна только для чтения: Создать ZIP файл с помощью обертки fopen()
-
Я также сделал некоторые попытки с другими php-обертками/протоколлами, например
file_get_contents("zip://data://text/plain;base64,{$base64_string}#test.txt")
$zip->open("php://filter/read=convert.base64-decode/resource={$base64_string}")
$zip->open("php://filter/read=/resource=php://memory")
но для меня они вообще не работают, даже если в руководстве есть примеры. Поэтому вам нужно усвоить таблетку и создать временный файл.
Исходный ответ:
Это просто способ временного хранения. Надеюсь, вы сами справитесь с обработкой zip и анализом xml.
Используйте php php://memory
(doc) wrapper. Имейте в виду, что это полезно только для небольших файлов, потому что оно хранится в памяти - очевидно. В противном случае используйте php://temp
.
<?php
// the decoded content of your zip file
$text = 'base64 _decoded_ zip content';
// this will empty the memory and appen your zip content
$written = file_put_contents('php://memory', $text);
// bytes written to memory
var_dump($written);
// new instance of the ZipArchive
$zip = new ZipArchive;
// success of the archive reading
var_dump(true === $zip->open('php://memory'));
Ответ 2
У меня была аналогичная проблема, я закончил ее вручную.
https://www.pkware.com/documents/casestudies/APPNOTE.TXT
Это извлекает один файл (только первый), без ошибок /crc, предполагает, что используется deflate.
// zip in a string
$data = file_get_contents('test.zip');
// magic
$head = unpack("Vsig/vver/vflag/vmeth/vmodt/vmodd/Vcrc/Vcsize/Vsize/vnamelen/vexlen", substr($data,0,30));
$filename = substr($data,30,$head['namelen']);
$raw = gzinflate(substr($data,30+$head['namelen']+$head['exlen'],$head['csize']));
// first file uncompressed and ready to use
file_put_contents($filename,$raw);
Ответ 3
toster-cx правильно, вы должны наградить его очками, это пример, когда zip происходит от ответа на мыло в виде байтового массива (двоичного), содержимое представляет собой XML файл:
$objResponse = $objClient->__soapCall("sendBill",array(parameters));
$fileData=unzipByteArray($objResponse->applicationResponse);
header("Content-type: text/xml");
echo $fileData;
function unzipByteArray($data){
/*this firts is a directory*/
$head = unpack("Vsig/vver/vflag/vmeth/vmodt/vmodd/Vcrc/Vcsize/Vsize/vnamelen/vexlen", substr($data,0,30));
$filename = substr($data,30,$head['namelen']);
$if=30+$head['namelen']+$head['exlen']+$head['csize'];
/*this second is the actua file*/
$head = unpack("Vsig/vver/vflag/vmeth/vmodt/vmodd/Vcrc/Vcsize/Vsize/vnamelen/vexlen", substr($data,$if,30));
$raw = gzinflate(substr($data,$if+$head['namelen']+$head['exlen']+30,$head['csize']));
/*you can create a loop and continue decompressing more files if the were*/
return $raw;
}
Ответ 4
Если вы знаете имя файла внутри .zip, просто выполните следующее:
<?php
$xml = file_get_contents('zip://./your-zip.zip#your-file.xml');
Если у вас простая строка, просто выполните следующее:
<?php
$xml = file_get_contents('compress.zlib://data://text/plain;base64,'.$base64_encoded_string);
[править] Документация есть: http://www.php.net/manual/en/wrappers.php
Из комментариев: если у вас нет кодированной в base64 строки, вам нужно выполнить urlencode() перед использованием оболочки data://
.
<?php
$xml = file_get_contents('compress.zlib://data://text/plain,'.urlencode($text));
[edit 2] Даже если вы уже нашли решение с файлом, есть решение (для проверки), которое я не видел в вашем ответе:
<?php
$zip = new ZipArchive;
$zip->open('data::text/plain,'.urlencode($base64_decoded_string));
$zip2 = new ZipArchive;
$zip2->open('data::text/plain;base64,'.urlencode($base64_string));
Ответ 5
Если вы работаете в Linux и у вас есть администрация системы. Вы можете смонтировать небольшой виртуальный диск с помощью tmpfs, тогда будут работать стандартные функции file_get/put и ZipArchive, за исключением того, что он не записывает на диск, он записывает в память. Чтобы он был постоянно готов, fstab - это что-то вроде:
/media/ramdisk tmpfs nodev,nosuid,noexec,nodiratime,size=2M 0 0
Установите свой размер и местоположение соответственно, чтобы он подходил вам. Использование php для монтирования виртуального диска и его удаления после использования (если оно даже имеет привилегии), вероятно, менее эффективно, чем просто запись на диск, если только у вас нет большого количества файлов для обработки за один раз. Хотя это не чисто php-решение и не переносимо. Вам все равно нужно будет удалить "файлы" после использования или попросить ОС очистить старые файлы. Они не будут сохраняться после перезагрузки или перемонтирования виртуального диска.
Ответ 6
если вы хотите прочитать содержимое файла из zip и xml внутри вас, посмотрите на это, я использую его для подсчета слов из docx (это zip)
if (!function_exists('docx_word_count')) {
function docx_word_count($filename)
{
$zip = new ZipArchive();
if ($zip->open($filename) === true) {
if (($index = $zip->locateName('docProps/app.xml')) !== false) {
$data = $zip->getFromIndex($index);
$zip->close();
$xml = new SimpleXMLElement($data);
return $xml->Words;
}
$zip->close();
}
return 0;
}
}