mirror of
https://github.com/PHPOffice/PhpSpreadsheet.git
synced 2026-09-11 10:36:37 +00:00
Xlsx Writer Support Data URI for Images
Fix #4823. When writing a spreadsheet to Html, a data Uri can be used if `embedImages` is true. Reading such an Html spreadsheet and attempting to write it to Xlsx results in an Exception. It should be noted that Excel itself cannot open the Html properly; none of the images are present. The PhpSpreadsheet problem arises not with the inclusion of the image, but rather with attempting to include the appropriate entry in `[ContentTypes].xml`. This PR corrects that problem. For the record, Xls Writer does not have a problem with this situation. Just to demonstrate that, a parallel test for Xls Writer is added in addition to the new Xlsx Writer test.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Worksheet;
|
||||
|
||||
use Composer\Pcre\Preg;
|
||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||
use ZipArchive;
|
||||
|
||||
@@ -58,6 +59,9 @@ class Drawing extends BaseDrawing
|
||||
*/
|
||||
public function getExtension(): string
|
||||
{
|
||||
if (Preg::isMatch('~^data:image/([^;]+);base64,~', $this->path, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
$exploded = explode('.', basename($this->path));
|
||||
|
||||
return $exploded[count($exploded) - 1];
|
||||
@@ -96,7 +100,7 @@ class Drawing extends BaseDrawing
|
||||
public function setPath(string $path, bool $verifyFile = true, ?ZipArchive $zip = null, bool $allowExternal = true, ?callable $isWhitelisted = null): static
|
||||
{
|
||||
$this->isUrl = false;
|
||||
if (preg_match('~^data:image/[a-z]+;base64,~', $path) === 1) {
|
||||
if (Preg::isMatch('~^data:image/[a-z]+;base64,~', $path)) {
|
||||
$this->path = $path;
|
||||
|
||||
return $this;
|
||||
@@ -114,7 +118,7 @@ class Drawing extends BaseDrawing
|
||||
}
|
||||
// Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979
|
||||
} elseif (filter_var($path, FILTER_VALIDATE_URL) || (preg_match('/^([\w\s\x00-\x1f]+):/u', $path) && !preg_match('/^([\w]+):/u', $path))) {
|
||||
if (!preg_match('/^(http|https|file|ftp|s3):/', $path)) {
|
||||
if (!Preg::isMatch('/^(http|https|file|ftp|s3):/', $path)) {
|
||||
throw new PhpSpreadsheetException('Invalid protocol for linked drawing');
|
||||
}
|
||||
if (!$allowExternal) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
|
||||
use Composer\Pcre\Preg;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\File;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
|
||||
@@ -281,6 +282,9 @@ class ContentTypes extends WriterPart
|
||||
*/
|
||||
private function getImageMimeType(string $filename): string
|
||||
{
|
||||
if (Preg::isMatch('~^data:(image/[^;]+);base64,~', $filename, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
if (File::fileExists($filename)) {
|
||||
$image = getimagesize($filename);
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xls;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Html as HtmlReader;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
|
||||
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
|
||||
|
||||
class Issue4823Test extends AbstractFunctional
|
||||
{
|
||||
/**
|
||||
* Xlsx Writer did not handle image data uri's correctly (Xls was okay).
|
||||
*/
|
||||
public function testIssue4823(): void
|
||||
{
|
||||
$infile = 'tests/data/Reader/HTML/issue.4823.html';
|
||||
$reader = new HtmlReader();
|
||||
$spreadsheet = $reader->load($infile);
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$drawings = $sheet->getDrawingCollection();
|
||||
self::assertCount(1, $drawings);
|
||||
$drawing = $drawings[0];
|
||||
self::assertInstanceOf(Drawing::class, $drawing);
|
||||
self::assertStringStartsWith('data:image/png;base64,', $drawing->getPath());
|
||||
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xls');
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
$rsheet = $reloadedSpreadsheet->getActiveSheet();
|
||||
$drawings = $rsheet->getDrawingCollection();
|
||||
self::assertCount(1, $drawings);
|
||||
$drawing = $drawings[0];
|
||||
self::assertInstanceOf(MemoryDrawing::class, $drawing);
|
||||
self::assertSame('image/png', $drawing->getMimeType());
|
||||
$reloadedSpreadsheet->disconnectWorksheets();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Html as HtmlReader;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
|
||||
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
|
||||
|
||||
class Issue4823Test extends AbstractFunctional
|
||||
{
|
||||
/**
|
||||
* Xlsx Writer did not handle image data uri's correctly (Xls was okay).
|
||||
*/
|
||||
public function testIssue4823(): void
|
||||
{
|
||||
$infile = 'tests/data/Reader/HTML/issue.4823.html';
|
||||
$reader = new HtmlReader();
|
||||
$spreadsheet = $reader->load($infile);
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$drawings = $sheet->getDrawingCollection();
|
||||
self::assertCount(1, $drawings);
|
||||
$drawing = $drawings[0];
|
||||
self::assertInstanceOf(Drawing::class, $drawing);
|
||||
self::assertStringStartsWith('data:image/png;base64,', $drawing->getPath());
|
||||
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
$rsheet = $reloadedSpreadsheet->getActiveSheet();
|
||||
$drawings = $rsheet->getDrawingCollection();
|
||||
self::assertCount(1, $drawings);
|
||||
$drawing = $drawings[0];
|
||||
self::assertNotNull($drawing);
|
||||
self::assertInstanceOf(Drawing::class, $drawing);
|
||||
self::assertSame(1, preg_match('~^zip:.*/\w+[.]png$~', $drawing->getPath()));
|
||||
$reloadedSpreadsheet->disconnectWorksheets();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="generator" content="PhpSpreadsheet, https://github.com/PHPOffice/PhpSpreadsheet" />
|
||||
<title>Untitled Spreadsheet</title>
|
||||
<meta name="author" content="Unknown Creator" />
|
||||
<meta name="title" content="Untitled Spreadsheet" />
|
||||
<meta name="lastModifiedBy" content="Unknown Creator" />
|
||||
<meta name="created" content="2026-03-09T20:10:17-07:00" />
|
||||
<meta name="modified" content="2026-03-09T20:10:17-07:00" />
|
||||
<style type="text/css">
|
||||
html { font-family:Calibri, Arial, Helvetica, sans-serif; font-size:11pt; background-color:white }
|
||||
a.comment-indicator:hover + div.comment { background:#ffd; position:absolute; display:block; border:1px solid black; padding:0.5em }
|
||||
a.comment-indicator { background:red; display:inline-block; border:1px solid black; width:0.5em; height:0.5em }
|
||||
div.comment { display:none }
|
||||
table { border-collapse:collapse }
|
||||
.b { text-align:center }
|
||||
.e { text-align:center }
|
||||
.f { text-align:right }
|
||||
.inlineStr { text-align:left }
|
||||
.n { text-align:right }
|
||||
.s { text-align:left }
|
||||
.floatright { float:right }
|
||||
.floatleft { float:left }
|
||||
td.style0, th.style0 { vertical-align:bottom; border-bottom:none #000000; border-top:none #000000; border-left:none #000000; border-right:none #000000; color:#000000; font-family:'Calibri'; font-size:11pt }
|
||||
table.sheet0 col.col0 { width:42pt }
|
||||
table.sheet0 tr { height:15pt }
|
||||
@page page0 { margin-left: 0.7in; margin-right: 0.7in; margin-top: 0.75in; margin-bottom: 0.75in; }
|
||||
.navigation {page-break-after: always;}
|
||||
.scrpgbrk, div + div {page-break-before: always;}
|
||||
@media screen {
|
||||
.gridlines td {border: 1px solid black;}
|
||||
.gridlines th {border: 1px solid black;}
|
||||
body>div {margin-top: 5px;}
|
||||
body>div:first-child {margin-top: 0;}
|
||||
.scrpgbrk {margin-top: 1px;}
|
||||
}
|
||||
@media print {
|
||||
.gridlinesp td {border: 1px solid black;}
|
||||
.gridlinesp th {border: 1px solid black;}
|
||||
.navigation {display: none;}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style='page: page0'>
|
||||
<table id='sheet0' class='sheet0 gridlines'>
|
||||
<col class="col0" />
|
||||
<tbody>
|
||||
<tr class="row0">
|
||||
<td class="column0 style0 null" style='position: relative;'>
|
||||
<img style="position: absolute; z-index: 1; left: 0px; top: 0px; width: 100px; height: 100px;" src="data:image/png;base64,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" alt="Embedded image" /> </td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user