Skip to content

Commit

Permalink
Hyperlink Support for Ods (#3669)
Browse files Browse the repository at this point in the history
Fix #3660. Code existed to read a hyperlink in a cell for Ods, but did not exist for writing. Hyperlinks pointing within a document use a different representation than for Excel (and therefore for PhpSpreadsheet); read and write will both handle the mapping from one to the other.

While researching how to write the text part of a started Xml element, it transpired that writing 2 Ods properties (Company and Category) did not escape their values properly. Confirmed and fixed problem. I do not believe that there is such an exposure for any other writer. As it turns out, Ods Reader was not processing Company or Category properly; that is fixed.
  • Loading branch information
oleibman committed Aug 12, 2023
1 parent efd04e6 commit 6f5ef03
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 4 deletions.
3 changes: 3 additions & 0 deletions src/PhpSpreadsheet/Reader/Ods.php
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,9 @@ public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
}

if ($hyperlink !== null) {
if ($hyperlink[0] === '#') {
$hyperlink = 'sheet://' . substr($hyperlink, 1);
}
$cell->getHyperlink()
->setUrl($hyperlink);
}
Expand Down
9 changes: 8 additions & 1 deletion src/PhpSpreadsheet/Reader/Ods/Properties.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,14 @@ private function setMetaProperties(

break;
case 'user-defined':
$this->setUserDefinedProperty($propertyValueAttributes, $propertyValue, $docProps);
$name2 = (string) ($propertyValueAttributes['name'] ?? '');
if ($name2 === 'Company') {
$docProps->setCompany($propertyValue);
} elseif ($name2 === 'category') {
$docProps->setCategory($propertyValue);
} else {
$this->setUserDefinedProperty($propertyValueAttributes, $propertyValue, $docProps);
}

break;
}
Expand Down
18 changes: 17 additions & 1 deletion src/PhpSpreadsheet/Writer/Ods/Content.php
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,23 @@ private function writeCells(XMLWriter $objWriter, RowCellIterator $cells): void
// break intentionally omitted
case DataType::TYPE_STRING:
$objWriter->writeAttribute('office:value-type', 'string');
$objWriter->writeElement('text:p', $cell->getValue());
$url = $cell->getHyperlink()->getUrl();
if (empty($url)) {
$objWriter->writeElement('text:p', $cell->getValue());
} else {
$objWriter->startElement('text:p');
$objWriter->startElement('text:a');
$sheets = 'sheet://';
$lensheets = strlen($sheets);
if (substr($url, 0, $lensheets) === $sheets) {
$url = '#' . substr($url, $lensheets);
}
$objWriter->writeAttribute('xlink:href', $url);
$objWriter->writeAttribute('xlink:type', 'simple');
$objWriter->text($cell->getValue());
$objWriter->endElement(); // text:a
$objWriter->endElement(); // text:p
}

break;
}
Expand Down
4 changes: 2 additions & 2 deletions src/PhpSpreadsheet/Writer/Ods/Meta.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ public function write(): string
//<meta:document-statistic meta:table-count="XXX" meta:cell-count="XXX" meta:object-count="XXX"/>
$objWriter->startElement('meta:user-defined');
$objWriter->writeAttribute('meta:name', 'Company');
$objWriter->writeRaw($spreadsheet->getProperties()->getCompany());
$objWriter->writeRawData($spreadsheet->getProperties()->getCompany());
$objWriter->endElement();

$objWriter->startElement('meta:user-defined');
$objWriter->writeAttribute('meta:name', 'category');
$objWriter->writeRaw($spreadsheet->getProperties()->getCategory());
$objWriter->writeRawData($spreadsheet->getProperties()->getCategory());
$objWriter->endElement();

self::writeDocPropsCustom($objWriter, $spreadsheet);
Expand Down
52 changes: 52 additions & 0 deletions tests/PhpSpreadsheetTests/Reader/Ods/HyperlinkTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace PhpOffice\PhpSpreadsheetTests\Reader\Ods;

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Ods;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;

class HyperlinkTest extends AbstractFunctional
{
public function testSaveAndLoadHyperlinks(): void
{
$spreadsheetOld = new Spreadsheet();
$spreadsheetOld->getProperties()->setCompany('g</meta:user-defined>zorg');
$spreadsheetOld->getProperties()->setCategory('h</meta:user-defined>zorg');
$sheet = $spreadsheetOld->getActiveSheet();
$sheet->getCell('A1')->setValue('Hello World');
$sheet->getCell('A2')->setValue('http://example.org');
$sheet->getCell('A2')->getHyperlink()->setUrl('http://example.org/');
$sheet->getCell('A3')->setValue('pa<ge1');
$sheet->getCell('A3')->getHyperlink()->setUrl('http://example.org/page1.html');
$sheet2 = $spreadsheetOld->createSheet();
$sheet2->setTitle('TargetSheet');
$sheet2->setCellValue('B4', 'TargetCell');
$sheet2->setCellValue('B3', 'not target');
$sheet->getCell('A4')->setValue('go to Target');
$sheet->getCell('A4')->getHyperlink()->setUrl('sheet://TargetSheet!B4');
$spreadsheet = $this->writeAndReload($spreadsheetOld, 'Ods');
$spreadsheetOld->disconnectWorksheets();

$newSheet = $spreadsheet->getActiveSheet();
self::assertSame('g</meta:user-defined>zorg', $spreadsheet->getProperties()->getCompany());
self::assertSame('h</meta:user-defined>zorg', $spreadsheet->getProperties()->getCategory());
self::assertSame('http://example.org', $newSheet->getCell('A2')->getValue());
self::assertSame('http://example.org/', $newSheet->getCell('A2')->getHyperlink()->getUrl());
self::assertSame('pa<ge1', $newSheet->getCell('A3')->getValue());
self::assertSame('http://example.org/page1.html', $newSheet->getCell('A3')->getHyperlink()->getUrl());
self::assertSame('go to Target', $newSheet->getCell('A4')->getValue());
self::assertSame('sheet://TargetSheet!B4', $newSheet->getCell('A4')->getHyperlink()->getUrl());

// Verify that http links are unchanged,
// but internal sheet link has changed.
$writer = new Ods($spreadsheet);
$content = $writer->getWriterPartContent()->write();
self::assertStringContainsString('xlink:href="http://example.org/"', $content);
self::assertStringContainsString('xlink:href="http://example.org/page1.html"', $content);
self::assertStringContainsString('xlink:href="#TargetSheet!B4"', $content);
self::assertStringNotContainsString('sheet:', $content);

$spreadsheet->disconnectWorksheets();
}
}

0 comments on commit 6f5ef03

Please sign in to comment.