Skip to content

Commit ae7e4fd

Browse files
committed
Add: Generating Epub samples with adherence to the Epub 3 checking procedures
1 parent 5063a36 commit ae7e4fd

File tree

11 files changed

+350
-38
lines changed

11 files changed

+350
-38
lines changed

samples/Sample_Header.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
}
3232

3333
// Set writers
34-
$writers = ['Word2007' => 'docx', 'ODText' => 'odt', 'RTF' => 'rtf', 'HTML' => 'html', 'PDF' => 'pdf'];
34+
$writers = ['Word2007' => 'docx', 'ODText' => 'odt', 'RTF' => 'rtf', 'HTML' => 'html', 'PDF' => 'pdf', 'EPub3' => 'epub'];
3535

3636
// Set PDF renderer
3737
if (null === Settings::getPdfRendererPath()) {

src/PhpWord/Shared/ZipArchive.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,4 +423,15 @@ public function pclzipLocateName($filename)
423423

424424
return ($listIndex > -1) ? $listIndex : false;
425425
}
426+
427+
/**
428+
* Add an empty directory to the zip archive (emulate \ZipArchive).
429+
*
430+
* @param string $dirname Directory name to add to the zip archive
431+
*/
432+
public function addEmptyDir(string $dirname): bool
433+
{
434+
// Create a directory entry by adding an empty file with trailing slash
435+
return $this->addFromString(rtrim($dirname, '/') . '/', '');
436+
}
426437
}

src/PhpWord/Writer/EPub3.php

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
namespace PhpOffice\PhpWord\Writer;
2020

21-
use PhpOffice\PhpWord\Media;
2221
use PhpOffice\PhpWord\PhpWord;
2322
use PhpOffice\PhpWord\Writer\EPub3\Part\AbstractPart;
2423

@@ -42,14 +41,16 @@ public function __construct(?PhpWord $phpWord = null)
4241
'Toc' => 'toc.ncx',
4342
'Styles' => 'styles.css',
4443
'Manifest' => 'META-INF/container.xml',
44+
'Nav' => 'nav.xhtml',
45+
'ContentXhtml' => 'content.xhtml',
4546
];
4647
foreach (array_keys($this->parts) as $partName) {
4748
$partClass = static::class . '\\Part\\' . $partName;
4849
if (class_exists($partClass)) {
49-
/** @var AbstractPart $partObject Type hint */
50-
$partObject = new $partClass();
51-
$partObject->setParentWriter($this);
52-
$this->writerParts[strtolower($partName)] = $partObject;
50+
/** @var WriterPartInterface $part */
51+
$part = new $partClass($partName === 'Content' || $partName === 'ContentXhtml' ? $phpWord : null);
52+
$part->setParentWriter($this);
53+
$this->writerParts[strtolower($partName)] = $part;
5354
}
5455
}
5556

@@ -65,13 +66,11 @@ public function save(string $filename): void
6566
$filename = $this->getTempFile($filename);
6667
$zip = $this->getZipArchive($filename);
6768

68-
// Add section media files
69-
$sectionMedia = Media::getElements('section');
70-
if (!empty($sectionMedia)) {
71-
$this->addFilesToPackage($zip, $sectionMedia);
72-
}
69+
// Add mimetype first without compression
70+
$zip->addFromString('mimetype', 'application/epub+zip');
71+
$zip->addEmptyDir('META-INF');
7372

74-
// Write parts
73+
// Add other files
7574
foreach ($this->parts as $partName => $fileName) {
7675
if ($fileName === '') {
7776
continue;
@@ -80,12 +79,13 @@ public function save(string $filename): void
8079
if (!$part instanceof AbstractPart) {
8180
continue;
8281
}
83-
8482
$zip->addFromString($fileName, $part->write());
8583
}
8684

87-
// Close zip archive and cleanup temp file
85+
// Close zip archive
8886
$zip->close();
87+
88+
// Cleanup temp file
8989
$this->cleanupTempFile();
9090
}
9191
}

src/PhpWord/Writer/EPub3/Part/AbstractPart.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@
1919
namespace PhpOffice\PhpWord\Writer\EPub3\Part;
2020

2121
use PhpOffice\PhpWord\Writer\AbstractWriter;
22+
use PhpOffice\PhpWord\Writer\WriterPartInterface;
2223

2324
/**
2425
* Abstract class for EPub3 parts.
2526
*/
26-
abstract class AbstractPart
27+
abstract class AbstractPart implements WriterPartInterface
2728
{
2829
/**
2930
* Parent writer.
@@ -35,11 +36,9 @@ abstract class AbstractPart
3536
/**
3637
* Set parent writer.
3738
*/
38-
public function setParentWriter(AbstractWriter $writer): self
39+
public function setParentWriter(AbstractWriter $writer): void
3940
{
4041
$this->parentWriter = $writer;
41-
42-
return $this;
4342
}
4443

4544
/**

src/PhpWord/Writer/EPub3/Part/Content.php

Lines changed: 98 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,113 @@
1818

1919
namespace PhpOffice\PhpWord\Writer\EPub3\Part;
2020

21+
use PhpOffice\PhpWord\Exception\Exception;
22+
use PhpOffice\PhpWord\PhpWord;
23+
use XMLWriter;
24+
2125
/**
2226
* Class for EPub3 content part.
2327
*/
2428
class Content extends AbstractPart
2529
{
30+
/**
31+
* PHPWord object.
32+
*
33+
* @var ?PhpWord
34+
*/
35+
private $phpWord;
36+
37+
/**
38+
* Constructor.
39+
*/
40+
public function __construct(?PhpWord $phpWord = null)
41+
{
42+
$this->phpWord = $phpWord;
43+
}
44+
45+
/**
46+
* Get XML Writer.
47+
*
48+
* @return XMLWriter
49+
*/
50+
protected function getXmlWriter()
51+
{
52+
$xmlWriter = new XMLWriter();
53+
$xmlWriter->openMemory();
54+
$xmlWriter->startDocument('1.0', 'UTF-8');
55+
56+
return $xmlWriter;
57+
}
58+
2659
/**
2760
* Write part content.
2861
*/
2962
public function write(): string
3063
{
31-
$content = '<?xml version="1.0" encoding="UTF-8"?>';
32-
$content .= '<package xmlns="http://www.idpf.org/2007/opf" version="3.0">';
33-
$content .= '<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">';
34-
$content .= '<dc:title>Sample EPub3 Document</dc:title>';
35-
$content .= '<dc:language>en</dc:language>';
36-
$content .= '</metadata>';
37-
$content .= '<manifest>';
38-
$content .= '<item id="content" href="content.xhtml" media-type="application/xhtml+xml"/>';
39-
$content .= '</manifest>';
40-
$content .= '<spine>';
41-
$content .= '<itemref idref="content"/>';
42-
$content .= '</spine>';
43-
$content .= '</package>';
44-
45-
return $content;
64+
if ($this->phpWord === null) {
65+
throw new Exception('No PhpWord assigned.');
66+
}
67+
68+
$xmlWriter = $this->getXmlWriter();
69+
$docInfo = $this->phpWord->getDocInfo();
70+
71+
// Write package
72+
$xmlWriter->startElement('package');
73+
$xmlWriter->writeAttribute('xmlns', 'http://www.idpf.org/2007/opf');
74+
$xmlWriter->writeAttribute('version', '3.0');
75+
$xmlWriter->writeAttribute('unique-identifier', 'book-id');
76+
$xmlWriter->writeAttribute('xml:lang', 'en');
77+
78+
// Write metadata
79+
$xmlWriter->startElement('metadata');
80+
$xmlWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
81+
$xmlWriter->writeAttribute('xmlns:opf', 'http://www.idpf.org/2007/opf');
82+
83+
// Required elements
84+
$xmlWriter->startElement('dc:identifier');
85+
$xmlWriter->writeAttribute('id', 'book-id');
86+
$xmlWriter->text('book-id-' . uniqid());
87+
$xmlWriter->endElement();
88+
$xmlWriter->writeElement('dc:title', $docInfo->getTitle() ?: 'Untitled');
89+
$xmlWriter->writeElement('dc:language', 'en');
90+
91+
// Required modified timestamp
92+
$xmlWriter->startElement('meta');
93+
$xmlWriter->writeAttribute('property', 'dcterms:modified');
94+
$xmlWriter->text(date('Y-m-d\TH:i:s\Z'));
95+
$xmlWriter->endElement();
96+
97+
$xmlWriter->endElement(); // metadata
98+
99+
// Write manifest
100+
$xmlWriter->startElement('manifest');
101+
102+
// Add nav document (required)
103+
$xmlWriter->startElement('item');
104+
$xmlWriter->writeAttribute('id', 'nav');
105+
$xmlWriter->writeAttribute('href', 'nav.xhtml');
106+
$xmlWriter->writeAttribute('media-type', 'application/xhtml+xml');
107+
$xmlWriter->writeAttribute('properties', 'nav');
108+
$xmlWriter->endElement();
109+
110+
// Add content document
111+
$xmlWriter->startElement('item');
112+
$xmlWriter->writeAttribute('id', 'content');
113+
$xmlWriter->writeAttribute('href', 'content.xhtml');
114+
$xmlWriter->writeAttribute('media-type', 'application/xhtml+xml');
115+
$xmlWriter->endElement();
116+
117+
$xmlWriter->endElement(); // manifest
118+
119+
// Write spine
120+
$xmlWriter->startElement('spine');
121+
$xmlWriter->startElement('itemref');
122+
$xmlWriter->writeAttribute('idref', 'content');
123+
$xmlWriter->endElement();
124+
$xmlWriter->endElement(); // spine
125+
126+
$xmlWriter->endElement(); // package
127+
128+
return $xmlWriter->outputMemory(true);
46129
}
47130
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpWord\Writer\EPub3\Part;
4+
5+
use PhpOffice\PhpWord\PhpWord;
6+
use XMLWriter;
7+
8+
/**
9+
* Class for EPub3 content.xhtml part.
10+
*/
11+
class ContentXhtml extends AbstractPart
12+
{
13+
/**
14+
* PHPWord object.
15+
*
16+
* @var ?PhpWord
17+
*/
18+
private $phpWord;
19+
20+
/**
21+
* Constructor.
22+
*/
23+
public function __construct(?PhpWord $phpWord = null)
24+
{
25+
$this->phpWord = $phpWord;
26+
}
27+
28+
/**
29+
* Get XML Writer.
30+
*
31+
* @return XMLWriter
32+
*/
33+
protected function getXmlWriter()
34+
{
35+
$xmlWriter = new XMLWriter();
36+
$xmlWriter->openMemory();
37+
38+
return $xmlWriter;
39+
}
40+
41+
/**
42+
* Write part content.
43+
*/
44+
public function write(): string
45+
{
46+
if ($this->phpWord === null) {
47+
throw new \PhpOffice\PhpWord\Exception\Exception('No PhpWord assigned.');
48+
}
49+
50+
$xmlWriter = $this->getXmlWriter();
51+
52+
$xmlWriter->startDocument('1.0', 'UTF-8');
53+
$xmlWriter->startElement('html');
54+
$xmlWriter->writeAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
55+
$xmlWriter->writeAttribute('xmlns:epub', 'http://www.idpf.org/2007/ops');
56+
$xmlWriter->startElement('head');
57+
$xmlWriter->writeElement('title', $this->phpWord->getDocInfo()->getTitle() ?: 'Untitled');
58+
$xmlWriter->endElement(); // head
59+
$xmlWriter->startElement('body');
60+
61+
// Write sections content
62+
foreach ($this->phpWord->getSections() as $section) {
63+
$xmlWriter->startElement('div');
64+
$xmlWriter->writeAttribute('class', 'section');
65+
66+
foreach ($section->getElements() as $element) {
67+
if ($element instanceof \PhpOffice\PhpWord\Element\TextRun) {
68+
$xmlWriter->startElement('p');
69+
foreach ($element->getElements() as $textElement) {
70+
if ($textElement instanceof \PhpOffice\PhpWord\Element\Text) {
71+
$xmlWriter->text($textElement->getText());
72+
} elseif (method_exists($textElement, 'getText')) {
73+
$xmlWriter->text($textElement->getText());
74+
}
75+
}
76+
$xmlWriter->endElement(); // p
77+
} elseif (method_exists($element, 'getText')) {
78+
$textValue = $element->getText();
79+
if ($textValue instanceof \PhpOffice\PhpWord\Element\TextRun) {
80+
$xmlWriter->startElement('p');
81+
foreach ($textValue->getElements() as $childElement) {
82+
if ($childElement instanceof \PhpOffice\PhpWord\Element\Text) {
83+
$xmlWriter->text($childElement->getText());
84+
} elseif (method_exists($childElement, 'getText')) {
85+
$xmlWriter->text($childElement->getText());
86+
}
87+
}
88+
$xmlWriter->endElement(); // p
89+
} else {
90+
$xmlWriter->startElement('p');
91+
$xmlWriter->text($textValue);
92+
$xmlWriter->endElement(); // p
93+
}
94+
}
95+
}
96+
97+
$xmlWriter->endElement(); // div
98+
}
99+
100+
$xmlWriter->endElement(); // body
101+
$xmlWriter->endElement(); // html
102+
103+
return $xmlWriter->outputMemory(true);
104+
}
105+
}

0 commit comments

Comments
 (0)