Skip to content

[WIP] Feature: WPS Writer Support #2769

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion samples/Sample_Header.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
}

// Set writers
$writers = ['Word2007' => 'docx', 'ODText' => 'odt', 'RTF' => 'rtf', 'HTML' => 'html', 'PDF' => 'pdf', 'EPub3' => 'epub'];
$writers = ['Word2007' => 'docx', 'ODText' => 'odt', 'RTF' => 'rtf', 'HTML' => 'html', 'PDF' => 'pdf', 'EPub3' => 'epub', 'WPS' => 'wps'];

// Set PDF renderer
if (null === Settings::getPdfRendererPath()) {
Expand Down
2 changes: 1 addition & 1 deletion src/PhpWord/IOFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ abstract class IOFactory
*/
public static function createWriter(PhpWord $phpWord, $name = 'Word2007')
{
if ($name !== 'WriterInterface' && !in_array($name, ['ODText', 'RTF', 'Word2007', 'HTML', 'PDF', 'EPub3'], true)) {
if ($name !== 'WriterInterface' && !in_array($name, ['ODText', 'RTF', 'Word2007', 'HTML', 'PDF', 'EPub3', 'WPS'], true)) {
throw new Exception("\"{$name}\" is not a valid writer.");
}

Expand Down
117 changes: 117 additions & 0 deletions src/PhpWord/Writer/WPS.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/

namespace PhpOffice\PhpWord\Writer;

use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Writer\WPS\Media;
use PhpOffice\PhpWord\Writer\WPS\Part\AbstractPart;

/**
* WPS writer.
*/
class WPS extends AbstractWriter implements WriterInterface
{
/**
* Create new WPS writer.
*/
public function __construct(?PhpWord $phpWord = null)
{
// Assign PhpWord
$this->setPhpWord($phpWord);

// Create parts
$this->parts = [
'Content' => 'content.xml',
'Styles' => 'styles.xml',
'Meta' => 'meta.xml',
'Manifest' => 'META-INF/manifest.xml',
];
foreach (array_keys($this->parts) as $partName) {
$partClass = "PhpOffice\\PhpWord\\Writer\\WPS\\Part\\{$partName}";
if (class_exists($partClass)) {
/** @var AbstractPart $part */
$part = new $partClass();
$part->setParentWriter($this);
$this->writerParts[strtolower($partName)] = $part;
}
}

// Set package paths
$this->mediaPaths = ['image' => 'Pictures/'];
}

/**
* Save PhpWord to file.
*/
public function save(string $filename): void
{
$filename = $this->getTempFile($filename);
$zip = $this->getZipArchive($filename);
$phpWord = $this->getPhpWord();

// Clear any previous media elements
Media::clearElements();

// Collect media relations by traversing the document
foreach ($phpWord->getSections() as $section) {
Media::collectMediaRelations('section', $section);
foreach ($section->getHeaders() as $header) {
Media::collectMediaRelations('header', $header);
}
foreach ($section->getFooters() as $footer) {
Media::collectMediaRelations('footer', $footer);
}
}

// Add collected media files to the package
$sectionMedia = Media::getElements('section');
if (!empty($sectionMedia)) {
$this->addFilesToPackage($zip, $sectionMedia);
}
$headerMedia = Media::getElements('header');
if (!empty($headerMedia)) {
$this->addFilesToPackage($zip, $headerMedia);
}
$footerMedia = Media::getElements('footer');
if (!empty($footerMedia)) {
$this->addFilesToPackage($zip, $footerMedia);
}

// Make sure required directories exist
$zip->addEmptyDir('Pictures'); // Ensure Pictures directory exists for images
$zip->addEmptyDir('META-INF');

// Write parts
foreach ($this->parts as $partName => $fileName) {
if ($fileName === '') {
continue;
}
$part = $this->getWriterPart($partName);
if (!$part instanceof AbstractPart) {
continue;
}

$zip->addFromString($fileName, $part->write());
}

// Close zip archive and cleanup temp file
$zip->close();
$this->cleanupTempFile();
}
}
105 changes: 105 additions & 0 deletions src/PhpWord/Writer/WPS/Media.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/

namespace PhpOffice\PhpWord\Writer\WPS;

use PhpOffice\PhpWord\Element\AbstractContainer;
use PhpOffice\PhpWord\Element\AbstractElement;
use PhpOffice\PhpWord\Element\Image;

/**
* WPS Media handler.
*
* @since 0.18.0
*/
class Media
{
/**
* Media elements collection, categorized by document part (section, header, footer).
*
* @var array<string, array<int, array>>
*/
private static $elements = [];

/**
* Add a media element to the collection.
*
* @param string $docPart e.g., 'section', 'header', 'footer'
* @param AbstractElement $element The media element (e.g., Image)
*/
public static function addElement(string $docPart, AbstractElement $element): void
{
if (!isset(self::$elements[$docPart])) {
self::$elements[$docPart] = [];
}

if ($element instanceof Image) {
$mediaIndex = count(self::$elements[$docPart]) + 1;
$element->setMediaIndex($mediaIndex);
$element->setTarget("image{$mediaIndex}.{$element->getImageExtension()}");

self::$elements[$docPart][] = [
'type' => 'image', // Add the missing 'type' index
'source' => $element->getSource(),
'target' => $element->getTarget(),
'isMemImage' => $element->isMemImage(),
'imageString' => $element->isMemImage() ? $element->getImageString() : null,
];
}
// Add handling for other media types (OLEObject) if needed
}

/**
* Get all media elements for a specific document part.
*
* @param string $docPart e.g., 'section', 'header', 'footer'
*
* @return array<int, array>
*/
public static function getElements(string $docPart): array
{
return self::$elements[$docPart] ?? [];
}

/**
* Clear all stored media elements.
*/
public static function clearElements(): void
{
self::$elements = [];
}

/**
* Recursively collect media elements from a container.
*
* @param string $docPart The document part ('section', 'header', 'footer')
* @param AbstractContainer $container The container element to traverse
*/
public static function collectMediaRelations(string $docPart, AbstractContainer $container): void
{
foreach ($container->getElements() as $element) {
if ($element instanceof Image) {
self::addElement($docPart, $element);
} elseif ($element instanceof AbstractContainer) {
// Recursively check sub-containers
self::collectMediaRelations($docPart, $element);
}
// Add checks for other media types (OLEObject) if needed
}
}
}
76 changes: 76 additions & 0 deletions src/PhpWord/Writer/WPS/Part/AbstractPart.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/

namespace PhpOffice\PhpWord\Writer\WPS\Part;

use PhpOffice\PhpWord\Settings;
use PhpOffice\PhpWord\Shared\XMLWriter;
use PhpOffice\PhpWord\Writer\AbstractWriter;
use PhpOffice\PhpWord\Writer\WriterPartInterface;

/**
* Abstract writer part class.
*/
abstract class AbstractPart implements WriterPartInterface
{
/**
* Parent writer.
*
* @var AbstractWriter
*/
protected $parentWriter;

/**
* @var XMLWriter
*/
protected $xmlWriter;

/**
* Set parent writer.
*/
public function setParentWriter(AbstractWriter $parentWriter): void
{
$this->parentWriter = $parentWriter;
}

/**
* Get parent writer.
*/
public function getParentWriter(): AbstractWriter
{
return $this->parentWriter;
}

/**
* Get XML Writer.
*/
protected function getXmlWriter(): XMLWriter
{
if (!$this->xmlWriter instanceof XMLWriter) {
$compatibility = Settings::hasCompatibility() ? 1 : 0; // Convert boolean to integer
$this->xmlWriter = new XMLWriter($compatibility);
}

return $this->xmlWriter;
}

/**
* Write part.
*/
abstract public function write(): string;
}
Loading