From 7c21e540b018541053ae5bacff7d9c54a9ceedb3 Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Sun, 4 May 2014 17:06:24 +0700 Subject: [PATCH 01/21] 0.10 release date: 4 May 2014 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e23edf91..34c57b24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ This is the changelog between releases of PHPWord. Releases are listed in reverse chronological order with the latest version listed on top, while additions/changes in each release are listed in chronological order. Changes in each release are divided into three parts: added or change features, bugfixes, and miscellaneous improvements. Each line contains short information about the change made, the person who made it, and the related issue number(s) in GitHub. -## 0.10.0 - Not yet released +## 0.10.0 - 4 May 2014 This release marked heavy refactorings on internal code structure with the creation of some abstract classes to reduce code duplication. `Element` subnamespace is introduced in this release to replace `Section`. Word2007 reader capability is greatly enhanced. Endnote is introduced. List numbering is now customizable. Basic HTML and PDF writing support is enabled. Basic ODText reader is introduced. From 232a30a1c41e9dbfb52a02610311072e37308dd7 Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Tue, 6 May 2014 17:32:32 +0700 Subject: [PATCH 02/21] Enable code coverage in Scrutinizer --- .scrutinizer.yml | 16 ++++++++++++++++ .travis.yml | 5 +++++ phpunit.xml.dist | 3 +++ 3 files changed, 24 insertions(+) create mode 100644 .scrutinizer.yml diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 00000000..8efd68d3 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,16 @@ +filter: + excluded_paths: [ 'vendor/*', 'tests/*', 'samples/*', 'src/PhpWord/Shared/PCLZip/*' ] + +before_commands: + - "composer install --prefer-source --dev" + +tools: + external_code_coverage: + enabled: true + timeout: 900 + php_code_coverage: + enabled: false + test_command: phpunit -c phpunit.xml.dist + php_sim: true + php_pdepend: true + php_analyzer: true diff --git a/.travis.yml b/.travis.yml index ad3df613..8cc348f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,3 +48,8 @@ script: #- php phploc.phar src/ ## PHPUnit - phpunit -c ./ --coverage-text + +after_script: + ## Scrutinizer + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f77b9ce3..c7e59676 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -21,4 +21,7 @@ + + + \ No newline at end of file From 356927127770d7042d65bde551a3b2eb9ae4b6b0 Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Wed, 21 May 2014 19:20:13 +0700 Subject: [PATCH 03/21] Word2007 Writer: Enable the missing custom document properties writer --- CHANGELOG.md | 1 + src/PhpWord/Shared/XMLWriter.php | 5 -- .../Writer/ODText/Part/AbstractPart.php | 5 ++ src/PhpWord/Writer/ODText/Part/Meta.php | 4 +- src/PhpWord/Writer/Word2007.php | 35 +++++---- .../Writer/Word2007/Part/AbstractPart.php | 5 ++ .../Writer/Word2007/Part/ContentTypes.php | 1 + .../Writer/Word2007/Part/DocPropsCore.php | 4 +- .../Writer/Word2007/Part/DocPropsCustom.php | 78 +++++++++++++++++++ src/PhpWord/Writer/Word2007/Part/Rels.php | 7 +- 10 files changed, 116 insertions(+), 29 deletions(-) create mode 100644 src/PhpWord/Writer/Word2007/Part/DocPropsCustom.php diff --git a/CHANGELOG.md b/CHANGELOG.md index fce2bdac..58dd0aff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ This release marked the change of PHPWord license from LGPL 2.1 to LGPL 3; new r - PDF Writer: Add TCPDF and mPDF as optional PDF renderer library - @ivanlanin - ODT Writer: Enable title element and custom document properties - @ivanlanin - ODT Reader: Ability to read standard and custom document properties - @ivanlanin +- Word2007 Writer: Enable the missing custom document properties writer - @ivanlanin ### Bugfixes diff --git a/src/PhpWord/Shared/XMLWriter.php b/src/PhpWord/Shared/XMLWriter.php index 683e09c0..dc85bfc1 100644 --- a/src/PhpWord/Shared/XMLWriter.php +++ b/src/PhpWord/Shared/XMLWriter.php @@ -58,11 +58,6 @@ class XMLWriter */ public function __construct($tempLocation = self::STORAGE_MEMORY, $tempFolder = './') { - // Define date format - if (!defined('DATE_W3C')) { - define('DATE_W3C', 'Y-m-d\TH:i:sP'); - } - // Create internal XMLWriter $this->xmlWriter = new \XMLWriter(); diff --git a/src/PhpWord/Writer/ODText/Part/AbstractPart.php b/src/PhpWord/Writer/ODText/Part/AbstractPart.php index d5397ffd..31118ef9 100644 --- a/src/PhpWord/Writer/ODText/Part/AbstractPart.php +++ b/src/PhpWord/Writer/ODText/Part/AbstractPart.php @@ -28,6 +28,11 @@ use PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart as Word2007AbstractPart; */ abstract class AbstractPart extends Word2007AbstractPart { + /** + * @var string Date format + */ + protected $dateFormat = 'Y-m-d\TH:i:s.000'; + /** * Write common root attributes */ diff --git a/src/PhpWord/Writer/ODText/Part/Meta.php b/src/PhpWord/Writer/ODText/Part/Meta.php index 50a38047..0d240a68 100644 --- a/src/PhpWord/Writer/ODText/Part/Meta.php +++ b/src/PhpWord/Writer/ODText/Part/Meta.php @@ -53,12 +53,12 @@ class Meta extends AbstractPart $xmlWriter->writeElement('dc:subject', $docProps->getSubject()); $xmlWriter->writeElement('dc:description', $docProps->getDescription()); $xmlWriter->writeElement('dc:creator', $docProps->getLastModifiedBy()); - $xmlWriter->writeElement('dc:date', gmdate('Y-m-d\TH:i:s.000', $docProps->getModified())); + $xmlWriter->writeElement('dc:date', gmdate($this->dateFormat, $docProps->getModified())); // Extended properties $xmlWriter->writeElement('meta:generator', 'PHPWord'); $xmlWriter->writeElement('meta:initial-creator', $docProps->getCreator()); - $xmlWriter->writeElement('meta:creation-date', gmdate('Y-m-d\TH:i:s.000', $docProps->getCreated())); + $xmlWriter->writeElement('meta:creation-date', gmdate($this->dateFormat, $docProps->getCreated())); $xmlWriter->writeElement('meta:keyword', $docProps->getKeywords()); // Category, company, and manager are put in meta namespace diff --git a/src/PhpWord/Writer/Word2007.php b/src/PhpWord/Writer/Word2007.php index 7d3c73bc..751b58b6 100644 --- a/src/PhpWord/Writer/Word2007.php +++ b/src/PhpWord/Writer/Word2007.php @@ -53,23 +53,24 @@ class Word2007 extends AbstractWriter implements WriterInterface // Create parts $this->parts = array( - 'ContentTypes' => '[Content_Types].xml', - 'Rels' => '_rels/.rels', - 'DocPropsApp' => 'docProps/app.xml', - 'DocPropsCore' => 'docProps/core.xml', - 'RelsDocument' => 'word/_rels/document.xml.rels', - 'Document' => 'word/document.xml', - 'Styles' => 'word/styles.xml', - 'Numbering' => 'word/numbering.xml', - 'Settings' => 'word/settings.xml', - 'WebSettings' => 'word/webSettings.xml', - 'FontTable' => 'word/fontTable.xml', - 'Theme' => 'word/theme/theme1.xml', - 'RelsPart' => '', - 'Header' => '', - 'Footer' => '', - 'Footnotes' => '', - 'Endnotes' => '', + 'ContentTypes' => '[Content_Types].xml', + 'Rels' => '_rels/.rels', + 'DocPropsApp' => 'docProps/app.xml', + 'DocPropsCore' => 'docProps/core.xml', + 'DocPropsCustom' => 'docProps/custom.xml', + 'RelsDocument' => 'word/_rels/document.xml.rels', + 'Document' => 'word/document.xml', + 'Styles' => 'word/styles.xml', + 'Numbering' => 'word/numbering.xml', + 'Settings' => 'word/settings.xml', + 'WebSettings' => 'word/webSettings.xml', + 'FontTable' => 'word/fontTable.xml', + 'Theme' => 'word/theme/theme1.xml', + 'RelsPart' => '', + 'Header' => '', + 'Footer' => '', + 'Footnotes' => '', + 'Endnotes' => '', ); foreach (array_keys($this->parts) as $partName) { $partClass = get_class($this) . '\\Part\\' . $partName; diff --git a/src/PhpWord/Writer/Word2007/Part/AbstractPart.php b/src/PhpWord/Writer/Word2007/Part/AbstractPart.php index fbd4a6e5..610a7616 100644 --- a/src/PhpWord/Writer/Word2007/Part/AbstractPart.php +++ b/src/PhpWord/Writer/Word2007/Part/AbstractPart.php @@ -33,6 +33,11 @@ abstract class AbstractPart */ protected $parentWriter; + /** + * @var string Date format + */ + protected $dateFormat = 'Y-m-d\TH:i:sP'; + /** * Write part * diff --git a/src/PhpWord/Writer/Word2007/Part/ContentTypes.php b/src/PhpWord/Writer/Word2007/Part/ContentTypes.php index f8cb2f26..b6f23f47 100644 --- a/src/PhpWord/Writer/Word2007/Part/ContentTypes.php +++ b/src/PhpWord/Writer/Word2007/Part/ContentTypes.php @@ -40,6 +40,7 @@ class ContentTypes extends AbstractPart $overrides = array( '/docProps/core.xml' => $openXMLPrefix . 'package.core-properties+xml', '/docProps/app.xml' => $openXMLPrefix . 'officedocument.extended-properties+xml', + '/docProps/custom.xml' => $openXMLPrefix . 'officedocument.custom-properties+xml', '/word/document.xml' => $wordMLPrefix . 'document.main+xml', '/word/styles.xml' => $wordMLPrefix . 'styles+xml', '/word/numbering.xml' => $wordMLPrefix . 'numbering+xml', diff --git a/src/PhpWord/Writer/Word2007/Part/DocPropsCore.php b/src/PhpWord/Writer/Word2007/Part/DocPropsCore.php index 2b3bce5a..38f6d235 100644 --- a/src/PhpWord/Writer/Word2007/Part/DocPropsCore.php +++ b/src/PhpWord/Writer/Word2007/Part/DocPropsCore.php @@ -54,13 +54,13 @@ class DocPropsCore extends AbstractPart // dcterms:created $xmlWriter->startElement('dcterms:created'); $xmlWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF'); - $xmlWriter->writeRaw(date(DATE_W3C, $phpWord->getDocumentProperties()->getCreated())); + $xmlWriter->writeRaw(date($this->dateFormat, $phpWord->getDocumentProperties()->getCreated())); $xmlWriter->endElement(); // dcterms:modified $xmlWriter->startElement('dcterms:modified'); $xmlWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF'); - $xmlWriter->writeRaw(date(DATE_W3C, $phpWord->getDocumentProperties()->getModified())); + $xmlWriter->writeRaw(date($this->dateFormat, $phpWord->getDocumentProperties()->getModified())); $xmlWriter->endElement(); $xmlWriter->endElement(); // cp:coreProperties diff --git a/src/PhpWord/Writer/Word2007/Part/DocPropsCustom.php b/src/PhpWord/Writer/Word2007/Part/DocPropsCustom.php new file mode 100644 index 00000000..c5a3cf9e --- /dev/null +++ b/src/PhpWord/Writer/Word2007/Part/DocPropsCustom.php @@ -0,0 +1,78 @@ +getParentWriter()->getPhpWord(); + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('Properties'); + $xmlWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/custom-properties'); + $xmlWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'); + + $docProps = $phpWord->getDocumentProperties(); + $properties = $docProps->getCustomProperties(); + foreach ($properties as $key => $property) { + $propertyValue = $docProps->getCustomPropertyValue($property); + $propertyType = $docProps->getCustomPropertyType($property); + + $xmlWriter->startElement('property'); + $xmlWriter->writeAttribute('fmtid', '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}'); + $xmlWriter->writeAttribute('pid', $key + 2); + $xmlWriter->writeAttribute('name', $property); + switch ($propertyType) { + case 'i': + $xmlWriter->writeElement('vt:i4', $propertyValue); + break; + case 'f': + $xmlWriter->writeElement('vt:r8', $propertyValue); + break; + case 'b': + $xmlWriter->writeElement('vt:bool', ($propertyValue) ? 'true' : 'false'); + break; + case 'd': + $xmlWriter->startElement('vt:filetime'); + $xmlWriter->writeRaw(date($this->dateFormat, $propertyValue)); + $xmlWriter->endElement(); + break; + default: + $xmlWriter->writeElement('vt:lpwstr', $propertyValue); + break; + } + $xmlWriter->endElement(); // property + } + + $xmlWriter->endElement(); // Properties + + return $xmlWriter->getData(); + } +} diff --git a/src/PhpWord/Writer/Word2007/Part/Rels.php b/src/PhpWord/Writer/Word2007/Part/Rels.php index af57a768..c1405258 100644 --- a/src/PhpWord/Writer/Word2007/Part/Rels.php +++ b/src/PhpWord/Writer/Word2007/Part/Rels.php @@ -35,9 +35,10 @@ class Rels extends AbstractPart public function write() { $xmlRels = array( - 'docProps/core.xml' => 'package/2006/relationships/metadata/core-properties', - 'docProps/app.xml' => 'officeDocument/2006/relationships/extended-properties', - 'word/document.xml' => 'officeDocument/2006/relationships/officeDocument', + 'docProps/core.xml' => 'package/2006/relationships/metadata/core-properties', + 'docProps/app.xml' => 'officeDocument/2006/relationships/extended-properties', + 'docProps/custom.xml' => 'officeDocument/2006/relationships/custom-properties', + 'word/document.xml' => 'officeDocument/2006/relationships/officeDocument', ); $xmlWriter = $this->getXmlWriter(); $this->writeRels($xmlWriter, $xmlRels); From d764de018c1212952097666b460a52589785ca4a Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Wed, 21 May 2014 22:10:36 +0700 Subject: [PATCH 04/21] Additional unit tests and @since documentation --- src/PhpWord/Reader/AbstractReader.php | 1 + src/PhpWord/Reader/ODText/AbstractPart.php | 3 +++ src/PhpWord/Reader/ReaderInterface.php | 2 ++ src/PhpWord/Reader/Word2007/AbstractPart.php | 3 +++ src/PhpWord/Reader/Word2007/DocPropsApp.php | 2 ++ src/PhpWord/Reader/Word2007/DocPropsCore.php | 2 ++ .../Reader/Word2007/DocPropsCustom.php | 2 ++ src/PhpWord/Reader/Word2007/Document.php | 2 ++ src/PhpWord/Reader/Word2007/Endnotes.php | 2 ++ src/PhpWord/Reader/Word2007/Footnotes.php | 2 ++ src/PhpWord/Reader/Word2007/Numbering.php | 2 ++ src/PhpWord/Reader/Word2007/Styles.php | 2 ++ .../PhpWord/Tests/Element/ListItemRunTest.php | 11 +++++++++++ tests/PhpWord/Tests/SettingsTest.php | 8 ++++++++ tests/PhpWord/Tests/Shared/StringTest.php | 10 ++++++++++ .../PhpWord/Tests/_files/documents/reader.odt | Bin 11758 -> 11952 bytes 16 files changed, 54 insertions(+) diff --git a/src/PhpWord/Reader/AbstractReader.php b/src/PhpWord/Reader/AbstractReader.php index a243c5d2..3cc2d490 100644 --- a/src/PhpWord/Reader/AbstractReader.php +++ b/src/PhpWord/Reader/AbstractReader.php @@ -22,6 +22,7 @@ use PhpOffice\PhpWord\Exception\Exception; /** * Reader abstract class * + * @since 0.8.0 * @codeCoverageIgnore Abstract class */ abstract class AbstractReader implements ReaderInterface diff --git a/src/PhpWord/Reader/ODText/AbstractPart.php b/src/PhpWord/Reader/ODText/AbstractPart.php index 815e60fd..392c3126 100644 --- a/src/PhpWord/Reader/ODText/AbstractPart.php +++ b/src/PhpWord/Reader/ODText/AbstractPart.php @@ -22,6 +22,9 @@ use PhpOffice\PhpWord\Shared\XMLReader; /** * Abstract part reader + * + * @since 0.10.0 + * @codeCoverageIgnore Nothing in here yet */ abstract class AbstractPart extends Word2007AbstractPart { diff --git a/src/PhpWord/Reader/ReaderInterface.php b/src/PhpWord/Reader/ReaderInterface.php index df663197..361c4137 100644 --- a/src/PhpWord/Reader/ReaderInterface.php +++ b/src/PhpWord/Reader/ReaderInterface.php @@ -19,6 +19,8 @@ namespace PhpOffice\PhpWord\Reader; /** * Reader interface + * + * @since 0.8.0 */ interface ReaderInterface { diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 9a0e0c63..2289a5e8 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -24,6 +24,8 @@ use PhpOffice\PhpWord\Shared\XMLReader; * Abstract part reader * * This class is inherited by ODText reader + * + * @since 0.10.0 */ abstract class AbstractPart { @@ -466,6 +468,7 @@ abstract class AbstractPart } } + /** @var array $styles Type hint */ return $styles; } diff --git a/src/PhpWord/Reader/Word2007/DocPropsApp.php b/src/PhpWord/Reader/Word2007/DocPropsApp.php index 06b58bcb..ddbe474f 100644 --- a/src/PhpWord/Reader/Word2007/DocPropsApp.php +++ b/src/PhpWord/Reader/Word2007/DocPropsApp.php @@ -19,6 +19,8 @@ namespace PhpOffice\PhpWord\Reader\Word2007; /** * Extended properties reader + * + * @since 0.10.0 */ class DocPropsApp extends DocPropsCore { diff --git a/src/PhpWord/Reader/Word2007/DocPropsCore.php b/src/PhpWord/Reader/Word2007/DocPropsCore.php index ae59ad51..0b92b64d 100644 --- a/src/PhpWord/Reader/Word2007/DocPropsCore.php +++ b/src/PhpWord/Reader/Word2007/DocPropsCore.php @@ -22,6 +22,8 @@ use PhpOffice\PhpWord\Shared\XMLReader; /** * Core properties reader + * + * @since 0.10.0 */ class DocPropsCore extends AbstractPart { diff --git a/src/PhpWord/Reader/Word2007/DocPropsCustom.php b/src/PhpWord/Reader/Word2007/DocPropsCustom.php index 969118b1..efbbfaa5 100644 --- a/src/PhpWord/Reader/Word2007/DocPropsCustom.php +++ b/src/PhpWord/Reader/Word2007/DocPropsCustom.php @@ -23,6 +23,8 @@ use PhpOffice\PhpWord\Shared\XMLReader; /** * Custom properties reader + * + * @since 0.11.0 */ class DocPropsCustom extends AbstractPart { diff --git a/src/PhpWord/Reader/Word2007/Document.php b/src/PhpWord/Reader/Word2007/Document.php index 3ac4e0eb..34460b52 100644 --- a/src/PhpWord/Reader/Word2007/Document.php +++ b/src/PhpWord/Reader/Word2007/Document.php @@ -22,6 +22,8 @@ use PhpOffice\PhpWord\Shared\XMLReader; /** * Document reader + * + * @since 0.10.0 */ class Document extends AbstractPart { diff --git a/src/PhpWord/Reader/Word2007/Endnotes.php b/src/PhpWord/Reader/Word2007/Endnotes.php index 02bf9e9e..c493c347 100644 --- a/src/PhpWord/Reader/Word2007/Endnotes.php +++ b/src/PhpWord/Reader/Word2007/Endnotes.php @@ -19,6 +19,8 @@ namespace PhpOffice\PhpWord\Reader\Word2007; /** * Endnotes reader + * + * @since 0.10.0 */ class Endnotes extends Footnotes { diff --git a/src/PhpWord/Reader/Word2007/Footnotes.php b/src/PhpWord/Reader/Word2007/Footnotes.php index 7aadf6c0..47713cfb 100644 --- a/src/PhpWord/Reader/Word2007/Footnotes.php +++ b/src/PhpWord/Reader/Word2007/Footnotes.php @@ -22,6 +22,8 @@ use PhpOffice\PhpWord\Shared\XMLReader; /** * Footnotes reader + * + * @since 0.10.0 */ class Footnotes extends AbstractPart { diff --git a/src/PhpWord/Reader/Word2007/Numbering.php b/src/PhpWord/Reader/Word2007/Numbering.php index 81939ae8..2dd3f521 100644 --- a/src/PhpWord/Reader/Word2007/Numbering.php +++ b/src/PhpWord/Reader/Word2007/Numbering.php @@ -22,6 +22,8 @@ use PhpOffice\PhpWord\Shared\XMLReader; /** * Numbering reader + * + * @since 0.10.0 */ class Numbering extends AbstractPart { diff --git a/src/PhpWord/Reader/Word2007/Styles.php b/src/PhpWord/Reader/Word2007/Styles.php index dd73f410..7dc4b6ea 100644 --- a/src/PhpWord/Reader/Word2007/Styles.php +++ b/src/PhpWord/Reader/Word2007/Styles.php @@ -22,6 +22,8 @@ use PhpOffice\PhpWord\Shared\XMLReader; /** * Styles reader + * + * @since 0.10.0 */ class Styles extends AbstractPart { diff --git a/tests/PhpWord/Tests/Element/ListItemRunTest.php b/tests/PhpWord/Tests/Element/ListItemRunTest.php index c034a8f8..37d679f9 100644 --- a/tests/PhpWord/Tests/Element/ListItemRunTest.php +++ b/tests/PhpWord/Tests/Element/ListItemRunTest.php @@ -51,6 +51,17 @@ class ListItemRunTest extends \PHPUnit_Framework_TestCase $this->assertEquals($oListItemRun->getParagraphStyle(), 'pStyle'); } + /** + * New instance with string + */ + public function testConstructListString() + { + $oListItemRun = new ListItemRun(0, 'numberingStyle'); + + $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\ListItemRun', $oListItemRun); + $this->assertCount(0, $oListItemRun->getElements()); + } + /** * New instance with array */ diff --git a/tests/PhpWord/Tests/SettingsTest.php b/tests/PhpWord/Tests/SettingsTest.php index d188595e..36565eb1 100644 --- a/tests/PhpWord/Tests/SettingsTest.php +++ b/tests/PhpWord/Tests/SettingsTest.php @@ -103,6 +103,14 @@ class SettingsTest extends \PHPUnit_Framework_TestCase 'defaultFontName' => 'Arial', 'defaultFontSize' => 10, ); + + // Test default value + $this->assertEquals($expected, Settings::loadConfig()); + + // Test with valid file $this->assertEquals($expected, Settings::loadConfig(__DIR__ . '/../../../phpword.ini.dist')); + + // Test with invalid file + $this->assertEmpty(Settings::loadConfig(__DIR__ . '/files/xsl/passthrough.xsl')); } } diff --git a/tests/PhpWord/Tests/Shared/StringTest.php b/tests/PhpWord/Tests/Shared/StringTest.php index 086823a2..bf5862fb 100644 --- a/tests/PhpWord/Tests/Shared/StringTest.php +++ b/tests/PhpWord/Tests/Shared/StringTest.php @@ -54,4 +54,14 @@ class StringTest extends \PHPUnit_Framework_TestCase $this->assertEquals('', String::controlCharacterPHP2OOXML('')); $this->assertEquals('_x0008_', String::controlCharacterPHP2OOXML(chr(0x08))); } + + /** + * Test unicode conversion + */ + public function testToUnicode() + { + $this->assertEquals('a', String::toUnicode('a')); + $this->assertEquals('\uc0{\u8364}', String::toUnicode('€')); + $this->assertEquals('\uc0{\u233}', String::toUnicode('é')); + } } diff --git a/tests/PhpWord/Tests/_files/documents/reader.odt b/tests/PhpWord/Tests/_files/documents/reader.odt index 9e18e61917e66eef0736944d35d1224939160068..d37c4e6629209390eca9e281454d1eb14e77307f 100644 GIT binary patch literal 11952 zcmbt)1z1$y*8b3v(v6^WH%LlKDBaD_okL4^H&W8wEh!C3hjdE{lG6D{z5GNk_kQ2^ z{D)^|%^c2t-@VpZXP>?IdZk}MK%xVFygvC=3H|)?uMgx~D`;t9U~Fh-0|XgcTG%qt zOaI5S@gF!FsH>}QrhnVm(&lGNxL?S;fAWz34=r`=Y;5!`Ky;4gX20nHe}P~>0AgTf z2?Xi?qJG##tOr^dn*$B?ZSQ&dHx=Q3ZX{p~G6!1yx5f|k(_G)eP6v3upMO*0K0s+@ zX=P{iqv?O?>O&XB%Er>rM&H)<|ITO&0)p)RhjtJ72ePy@`@ggOXk`Vo&^PGQ|UR7J|Gnz=eU1Y6kArw-p=qRxsu4-CEP&f2PD4vcY#q?6m z9W+uqfO(?A3?~yVNAl59^G$&0!Et>Z@-qUi0h8(qr4JHQv(_S+k7rGcMs6-%qYb2q#`et#bq6^MUvR=Cd%|w=UyqeCsp_nSOz% z1GACvgU(WWn_pCUW>X#$lS9M?zOsrbje<*(f^ipRB(@==hziv1#P@>!#6N1?IWQqH zK(i}s(-VE!6-`d-(Bp^W+c1OxH3?^py#J}X$V0y0uTjxWot-dEFYMU_OPVZtM7$7T z@RQN00e46=mvhFH5hL4xg72m6+{DP}%TG0Nq>MtMUWC};WXEV-A?D&ru*wry_FIGR z`>uA9SRh8CqVRz^hhs;YT=iEc0cMib=Rb_GXrT<8FkXRIB9j`mJ5GIf?=M6!dL6pm z^}@z;xv41HLq^iD%`tRGXCxlAc}X>^Hg@dK^!K*O-ub~Rs47RD)fj+FGxi; z$-17^_4?BAxi-Zmy&&vF<`}vlB%=tak}2QVLF68X0A(Xgh_@uv`sgNaiLzx>a6Mm- zhWhEe=?VNu_0}-_+zr`~d3KRAG_GLan{BUNQxjQ|;Ip;~u#-8tVDB6EFN^P#@l(u6 zn&0$Ol0T(4$sC_pafY&2#XGD*7|5&W`b19Da3#NTSTN<~PZYxfGI)G~a-CMsI(z6~ zW9$3r4VaXTl?Z)Q)_l6U`8T>`1puO90~be9Gbfs3+cSPffw9bF^Bwv|T4dboNa@Fnhvkw)i*MygT_K2ud#(O$7(9qo2X$t%@GF9$%VWzBL(nYRe6;XDqE<4MOKQQ zqa?b_NASd3ebd0m_{|Ec3N6MpWC_}U^B+g|qM?+g?@!#%ctds3InxFZ zrzwKRu!-_vpnz=DJ-O1HMpxwi*aOCPeIJ$uCW!gLh*@5*u z_1$;CTcT+(UB_h`mZPFSi|ao??SPD}dNC|j%- z-5z-X*Ti=6BDu@f)2TbgkFXbBY*1nSbPkn#?5wT-C5CUr9*Y1WZ9V!FWi0p8pf}P9 zR`fw}>I@_hdYmpu$FEa-XWh|H9d`LG3bpd|>RCn1%j;NnbkNt}Gbfc)nPXOOmK(yg zWHHwS=?{iwp>CGXxO1Tl4iIdC=R75VbLxZ4KqeM-1W5H=G>kfx(4gp#+8NPz#S0JZaXjMZqw%MG`DYeQLndmCwmY(t9Zh%}G(hdzh7cvoRhz7sd_t z<@~1OILrX*rZ4ApDL=mGmqRHQ8ZY|-ju}=3u7&3;dY)gF7yNv>zI9vcq85m6J?7GE z-hB)Wu|RmtVT9Yp9Z~)L z`gzl^)zJ5gOn!&S?W4vQPy?f|$v5;6>o=J#V_PYe_VBjjI~J1dmW<@hfV(NtuZLsR ziO~oY7y#f60RSM}J{-+~7RCnpwjeqiJ%j$}ehY5~WS?CpxZdP)Pfa+u73mn;FpVT53Q>kvets%d0YZWE}H zuP9fnHi@0l@5-_~UX^+{oe*hs)`n2!C4+NuoozyGEz#zD_?qkg0MS5mEe^iTwG=Y1$)*!TOi!~hlnsCWol%kkle@|<( zfy+h9*4^|{3;i^=_#Lt-b#5!OYtORMdUyd_>0P+~eYy*K8!ic9d45_k$(MgkIqFPC z(ui-P$_ohqVBA0LpYYz!O^ah%E!v3?{B~V~0=Cd6ofD-;pO9_C*28Fbh()#tX%`%C zix6jjs;N>fm}988*?k{6`Au>FQ{>UGW6>nMy8%?%adm5r4&lYgLF{%-VW^xDZUBkD zVB{8qW_wV!$4H@3hR`aJ_{fNZNA<)fu&B!vdY2K_L~P~C?E-A4&~oXubE_wn1fO5V zf_Bn$3bu-dgKVmw>-4+8zY$-9B(LwpEltuhpQ7ZHf6Y+-vAwY;sk^I+@^Jo(Dn)Ut z0Fo;*r>|9coPR~cQRPDXUPfE=k~7?8Qm(JnvOIi7yS=8XyXX#DY`M&k)e48p)71+4 zNS8$a6}qqG!*dg%d@+Qsm+nALHAf-)`b}weDR$U)ft5jF*v z1l;0;^XGNPi2HDC6%wPD7dUuPMfp|Yq951YczHi`(O7EDkU4{y`$QFD=c?d)>IK28 zlz!1lYS2SjP)v<8X}~~uq*P?dKb-~jikMvi7gfBlb|Q+x#OG;Ljdp;(kR4Gj&YhL3Qu6&W7oVoQ9B*vm|Z zKIV>+-K-t*@E>xkEhzQ5a&H78N0j9CdV~!aE3bL)M&D zhE!WV1j-OOi5!QrZtVw}E~zF>2{$d@{TbFP8dpM;fq3z3sB`W!Erp?O;HznGjS5^2 zXm0GH4Dr+>boiN)2iC$4j{usW^{h~S(}=XI)A@JHO}orgBQ%{#;Q|8(AA7r(ujY)8 zVB&>^{bsfb>6?rkW#<>4nSIm4vhf0{g53O*-Z4bF80i_!up*laLpH1^cp^QmgM01;} zluv&Rt+x6gkg#(Hu}Ib~{>t^fIkJKr%H|NF)t)MWCNPV@vGhRO0Z% z(KmttgJ+&(-B};`rgS03M`+gqo+#z0XnqKQD^C0v!XVMf#2}Hx!U$9CRCKntlf$lf zT@fQNrzwm zK|ikuL6}>M4VHW9CE4>CzmFI~P6+n3Fl`}xo{aYGi*#)gBP+1vTi#-W#{j5A73rG>y7zCJRa!{`07pQZMp^s&`|KrV{l z*9m+Z<{dNBJx(+98ga^>8z$VGWv!2Rn4w_}V+rjeIbu_vxVu*P5GXXG!AyC(#3 z5Hj_Xg@{`xSNbR_H;W<~FWW1$sW4>Zk zTB$1UOs0~4Zj-m*!poA@tsUIGx!riS*+`2%TIgM?Ye+;ThobL>Wp|tue86zF``yMT zm>%E+IE{bX#uDEjFeQkb|9Hut=6xX_{$~s#Cw1k<0gg)PW>0?LoJ_V9H*GVQF4^-X z4U-z8Q^5Se9-X0o6lp{T2?t^syiU3rVfY2aq`jO%`3pJ>&(*XBTxJo(r*Q3R>CvJT ztFyye9rk7kHAgcC2Mpj;R)$P9ig;h>#J|u{;k7uF$JFj2U51Pz$-#a+XCF(V{yc;+ ztxWF3(-}DDLZxSHFAFCnnv-r8;5ri9kWN(R(J!VUJ1XZ26cFl#*^FM)i*$VI z$*z=J3k;yzs1-%Upn5DA@h{ZdtUeY5&Q2Q2DO1Zwl=NQgW^Q2!p{5b>T*lkTf!ol}1p$vN)BE&$R0V7CnNzt?Zo+_Wsq43EQqA zbQQC#SN|kQ8nnaIff&B676l94C*zBqY_sj%$D)dPcu%8%Bh@7QWW`E-Ip!vh)JGRZ zlvI#;ri3P61&n_3DBqSg&1UpDuJ=1vJA-yh<3yE+1)sf`PcJ`WYcAh)M=Jg~ME}^0 z`!!*emkznMx&4|+4*>iaxNSjBX8J$J)Hzj=$D<7BAHGu;5Y_s!=-1GIp@IiKEhmxu zC^6d=F&ViX5vMiyz0abZyUJgDKk0gYPxJ{lFZ$URt~D=)bA|v*8gHl^*jKP~FA#X$ z9G}d?KY3Aq;IVB_(wY=Qs^FQA;LE+HOv4u+6#A4Gfl87c0rnXiI)<{DUImaeN786? zZQe-EK!`Glwj`cw1eI__9oRV&DLiu)$7-v_xtfZ$ z>SX;9_Uf47V-^j&jQqq===mCA*NIG{W+}165fXVyX^~?|-3Un=hhmoy`a%G4x0-g~ z+Ig1)h0JtyM=-MQ~9 z{p4g;%+n%N!|l6}6G~iH>nzt>qRde6GwiOVioCowp-$atl)KVi?darnj)#0$^307x zgU-{By(a>8WS+pl=iztDMqF-C@PkyZX`i26E1RkC$`2zC7M$@BK?k$fYBuUZWf0ew zWJU#E@bEHYV=I_nTZL1uL5_q{e?Sru(*uV(Be2dG;$ELd)5en=2uo$5HF?&N8h`EM z6hwiGeG=ntI4u#61+|@U8+;|Xino9iQr06@fOM#8 zre){q$v_S&wssm-!L(zW4fF;3Cu;n>*TddkmjJ0{1$}w>zx?v|uN)yh;Xi_UaVHYdg=a54X zTh?1k5KkETbn@o+QdI3~Z()2he;2rXciOPBA&5{BV&6p5 zM>{cv@*K`HiF7N1FoQ1Hb#7*WJUAm~qpncBJ-dC&LP2Zdt9^}M{TWks&zQ4LJ~l63 z3L&>Bgt^he!R;`2W+Rt5SUUGon0;qQuRO$KdL)54J6W*R{Y`<^Q-fX1fHq#UPxjs3 z6lbs@UHH65E*IK&;!D)yDgS0{t&GEWA-fB3<0{C8y4r7X6ilp}3GAL@?(Y)m`??#G z+Q8ElW_1yFFN~ECJux+AuH^XOTKs7ZH`*2Y6|%ZwHrgjMA@1@>rWI=yLO(I%ZtazI zn6JjM>&nY|JChv@vWPQn2cY)s{lq!887ybCtu3)QgAByc%da1Xp#a9V_FXBqZ${P~ zROs`4D!p3A8<}wujcYN3R^M9rCRgUB7QQ;2Q#%f0dV%D>qvFRD#Fg6&mk6h1w^(1M z%^^mvY!cPe_f4Dpc@cYmo!MnTCQs7yWfnRlDN0?NFar{Q<20^R>SJKQJg)c<1Di%` zg{eTPL>?Vp!CSIN;&a_1+XNv-&D0K}uaRBRz1RphGv7Cq@v9NFQ&r|}j5KM5W- zhjy?k0kTTAZx4UdS>!{l3TddUeP610*5EJ;4Z!Y)isX#;4I42-Z1H?|_zZU)EgE_g zJ%u?^Qv-cu&xGa!b5K<_Lgcv5jpt!Myr8q41GuRg=(44)q0SP zBu)uM7d|j#?anu`|8DXwSisp%*U9z`M^((zk)S=Txb_Atzr)S>QyU{n!7PlWW*Yw! zI)tw?%jVN4e$N-K=_Q<9feZ^8;mc-}f@!z^CnBJyhuchIk9*%WA@~YMp!kt{{S$jp zf=kM1BoYPLUp$WL8R_VSjSTC3EFz7n&_1&c?1x#cUfV|oSwKb#d_3|3> zAI0*XY^xw&1^Mr3w8VJpedaIKeENlFBay>_)cC{6(5}YX-f|xmJpxW_*=aIwgsYaKmF> zE4!1f^YImgVpssO1sR8D0ui1`*ODsX{L~5kE;-$fh&`n@Khl$gVHLu#_)Bf9?{ptZt#{>H)r;v{RU#Z834Tt=$HP~E)QwIsfQ3Sp^siil;UE31wvaw3;kPLk zS{UBdT<(5|cDLU9_r0MXbtZWuJ98Zipt0HS^4_m03>OY3sp;)k{oDD=kMh^O6G3-v z=&URZw_}y0(V+3W@@s|!P{WfRFqH1eKY5BIHr>1k!>K z5T8;X?Dn?v6e^mV&;7 zJ`@y`0=p9VF1@=l{}Q#Y<%z3IWY{V$99&QCT=KwKPTan--by%s3x3VAkkDfne5(!& zwSuEBbyTP16AQg11LzYzI}WZ_{$r2p-wT{H@q9g!l-YI8rh2}|rMID9WSNZm>a?QI zFt0wJ)0LY|AXpZ#d)ydBK)c5CJ;+}iX5^#di_(>tK1x1T!dc!md%DsCpUM+{jrN6I zLL897hn$oCDR=xb9^eCPLFUl=S{gae)r<|XCK2hI zruw?x$C%MNyre+&u9Z;nDu0A0wJ!7}yA580vEw?+QU0kZ<{CUSax^P=kkM#-sf&c- zA&u^s!g)$#5fy_U=Ki!h1{_W!4wWW`is%e;E7kb9r&<)N|2$;5<4P?9Zton^D4$?? zG0rPaMKhLFgH!V+l{p#1m-vdQQZ7Sr_N3ebExbr)w2PS^X-$cY9Dspn-DU8fuY953 z!cHw(Baql{HK04tIc)Xmti7llZR(;}3G9v_1$ALY2=f&av5rZ>Ibu)%vswBfaBnSO+x=FFT z!{B@NKUnrElPXB@#NMok=f4ZLM5k}yuPzdwM=T-xRG7%Prl15C=Q3hI>#Z&^F|?jGTDi!P(hO z4ioX!z^V#_jIXom`T40@#Rb#-Gt6_0>Jio%@V5f=!P#BS%DXu9wt#$@!U0h-nHn2xkJq70{SE=BUn zit~i$#|QMos+?EV^};glZm9J8pMDaiiWd_AZ~YY6n%P1@IM*bs0gSn{AgGgysfP=s zASOqNl+{`I_HM4$(;Df85n(n)GJ=<6p{(?(w6>O%+Zr@0>6#Yuf`(|lL(jud|J;6#Clm( z6wi3sDtBP?c$#97%x9$ z^K!mw<+qRC1}PPVY_hARWHZA~OeXDIZm_M+aIEdW_G6wtieM{Hy0~zW@1SUy7Bqfg zy*ZZEyLF^_Ep~a>P;g%GX=_o$nM&x~WQlU7ejL)KYEIH z!IL-9zK~f9jUb0-fOLzkw3B#b0Gm-^Ow1HUu?Fn~i{o)#1H}iP=jD`tio5^E$nA-o=*t)vTT5P11_hh7>Y&^K5WeAJ9wK{T6-MTo-+)bNNN zkBGYfMM#O;5Bh?Z7}r^bUPqLH6Thbq?W=;|Sn=)UCj_8SKFaa%Fx95YTqx0NU#j%a z;n;^suI!LXwVTCTN@YFB^#OGhfwzxnfU8M99b?MXoNhiF-pZX#?^G4R0(rzjAOP(Y z`zGk|g(gY)f(kB^sBa*Zy{u1>&y+niy>mXtp{BSnsV_T2HH5{Mk9J;K$y=EBIL?k zypd$V)TFISwtOiB5v)T4NU>+B!5Kax&bVJI$ICxya8Q3kD)o-KMmOePqnp+H&H^)C!swmRClYp3hDH#i^=}}&TQOfG z=cN%>Ko}Lql^z6{GgG)IsL<8f1P9MvjITT<7K{MzQOzeClBY;0FmuRCPRJFL#L#M7 zakN7Ewmvu5xim*WOPsipL4#j?0L<&p)QISYa@GWNqjxGk{pLD`apKG(r+*r3x!30R zj#m4zrm$vK?i}u9*cOZNo_h_G(Cqx%%7dIWran3+4rJ%tj%i64WlUzKj`^YM3oFvc zT)x1P*`$uV@3sED+$_VKRU>jkuRaFBP8MZ+JHFju+acPm%T~y$P82gnYi%0{(LtFm zW7Ml;F~BD^vD#=G_(EOUHvW1q+j*O(c)*x9Gt^!pW(c^_0lz7FY2db_rd(Gi*6XVL z1$H*fap}q?rvXI-2BNG~{doN?@`3{p{X7nKJDpkceP)jW8U(wu&P3y2fZN~yjRF4;2D%3r?hC{2v-NEg z{$#te!6NtBesApm?B2gV$p2yUxIJQY`~KUX`)v2bWcP^yL>Hu;wu4}Igm?D7n}3V4 zu>T?c5y;{Pi0=ew4>8>Z^3L98`df*I^#{}P15E!Ct=%UgEGPOI%RBqOaf$E`BJA6e z=ijmX7tx*2?LOgM43jN>U9>xU-?RT7Lxl$j@5FHrG4(J1V!E^cjS2S;*Mc8l`YovY z|Kz)YjA-22N-P!-WXIBrf{TA*$#5E5$@$;6xv;U3j&zxtyt=|5fAa`QF z|6is*ub1h6VEQExe28m@@t0S3_P1BRR&9UE2=9hOkK21I^Oj5Zx2W(T@OP9mKSOqB z{|5h4hH)Rf z+zA2hBMYf}dtl!_cQizQb^Ol$LjL_^__O4Dhy3HR4j|nk|EmCeANj7&6dGec`+R5b z_xa!JzxR>x9^ck2?p1>yLVu<+`xE`n{*C@;9ppZ`_3dN)cU9ye>^R%GpV)WyH}=2h z3;ZAX0#ESAcEx@0QVhzU8FOd<(|z>k?#KO(v-}DE$6m;Nrn?Jpqx|cRy|dDJ+b@E2D=E1t0bz!YA*GS-&S8Lo83u--OF%$DLRv|YMo_w?yCek!5fBiS zRzg6|U~Juf+xL3EbDclW;+luG?seC?*LqexKW#Ovixe0bco-N%k?9JS-2y~cF)%RB ze`pd0914e^d|V+muC6dB*aihfxBxv}>;MQGcc?o6;R(7KXMO?zzhtnCB04`m-}k=-S`aLg6+J5O<&)6a}|&b^q?ukFb2z_xv zQP4&I@5aEmfPr`3!wxc;)EF29JgN%U^}W+K=9^tuOc)~;Wyb6p7Y7+ARH9Sqp5_sQ zuBqHkH=Ru`w8m2DjT8BcHP{;SIfv#b4jUww7-Wp^Vyzvm?}|Hr(3-B-RZw2wH;AyK zYl*dQ(9j8YI^M|9bTdEg@NE{6U5iM0g<*arK7>Z~`rTY+C0#OBuG{x0I#md4*|7Ql zE3%YK5mdi0=0kByx^O5Il~LnRE!MygySnGUC}3vzZly&@gg!i0vU_=8Cp@DvWp|~{ z4yVLqy*%twzn!>-l6#3(f?_?qq|L+4KVkl*g+7R(#FN@6X?P~+ViRDEO&CE*_$<3M zKbF@#X`{yZ;9zQFb|{0`C1Ln8AR4F0095IDrE9=sexupdW_VsrIP@wtPIX0eDNQo> zq$(HQV}W8Sr772F_RZn=YF{0&-KRxbvztb5k5t1tOb%Z!uewf28nZeM+kZJ&1Pun6 zA$IY-)YjK``IbWeXGlgg`&*jF9+)i$2JAAQ zPZ)6g$t)>wVem;-^06VW!s(ZsB-GT#um;yDsG78T7?<#|+oy8NebKZ}Oa+RSc{AST zD$@JZS!R1#bO%9nA3wMRP?IKC4+rp8Sys_Ax)_haqha-#*i?0tH=wgo1W_<7^Y9Ei zfh*CwX`4l=YD;&ipBc<%Xb_}j=nqan6fjmXcXNX(;Gdo@MPUr6|WY#FA52lE* za@A(-E`-?cGpc_nX<-@t2 zCVHaCAz0sHfjTZ$$z=Pua_?5tCP&81X#V5;aR}%>8)3=Fy97>&;wogbB{x;n&PM5c z#xP+6*@%|Y{Xseg(RiftN|;<5!=AnJoMG`~=tYI9tie)8#p4 zb+E=s0t?aE8@gT>Cj~^`0vjmjdP}5d)USK8R(x=S`SNkk5mT0aAX(dU$4XM_Af7)l zJQ~?`SAV~H!sn(RQ)bAg zgknyaDiiBUTf9ggG^F*_EEzo1mi*#-*k@gn1*E$5@ECE~#GC$${n$t?ePBTMit=iH zv!+Ayc66~$z`SPZVr<%JVd=^n1Ls5!9tE9Ar8PDgzcKk4>HnL}vpWCp-suuo^(Z)~NxRTR_G<336 zYJf%aT2$9kk&JSnk%+VfOoabyKqCyK*Yg0*>MU zQ$rpt7S9Ll*UwG+oPFA{YK3ksmK_*?90*?7->?v)7ZJs3+`w@1lb~5WlN;LK97=UQ z9+`p}5_$!NrFE>fIUVB?#VChGcrnH-pMcs{gIXMp{YIodd4?sl%?s`N#h!Myn%Afv zSq%9vRM1t0+erHkZc6#T%3e2OWDNSuYE=<2^6J$yF~DPYaA0gxfXRtN-VyYGqVdVd zOEH9JeZ<;gttQz?o7ZV<=%GDep%U0PCGPJwhrO2pjCwM)!VyL-_sdM}Fq|@*A!tsF zQ*N?;cQ;kw zpr!%P~=Ie7ZWM7i4(+_ST{QgvLvoy1UCl$LTQ5 zNn6+#{iEV3`vQ-9!}llar2^;~PgpmvUhS+jNIQzHb*N>OAdMkxUli~B(w>`nq7%IC z@)-{h$IT^*;V*_;Xbe0!nar(`Y2jNKKH*Hhp(R813S^+$ww{)uCL?TDes&a=Hk6xa zEq?z{R9$i8uF)#ce~~cj^M!iX-o8-6ZnR#@*V~d344>`uo0mp>gFE(I>i}hjIcr@d zdzcYx1BKcalAbHZ=C@MBB$*h~;XPf8+w{M#XP(^(HbFX(<3W@g>zC8juLLp;r*fKW zxjY)Pc!~2Rgv!%lZ^Sj0h$6p&h;B_t`(B4k~*-6g)<&kH>&ldg|`nUQO1@d1#L z%2v}@rR1yE_e8S1#k$*N?CDUC+fGc>bBjztK;=CPcjmPH4L@@(8^@ST4aN)^89L_$inoA)|G8-Wx9j--Zq@() z+`kT}|C@c&|EmZF;xhaBm?WzufzaDW3{^!?%gbV8X0b|8%2$`-# z_H2!dQ+JrF74w+M*+Ml>*E0!H!n3mV=0VuZJQtR{Jx*z&UM5TZTAV!NHLZ9?9Ai&v z!|ZCeqB<4Z{FcqkFTPhCiASvF!h$`cXs-%2JcqkyJB#fWYUO+i`?5Sl+N~c{ez+uV zQF=RKP5M(jnR5HKBirj*oVT|}CO?E_K6TV2zv|EUkZiAhdsV1gC+0y)-dYi_aC=w4 zbDB(olu^uuJNedJjS-3itI2wc0<#W|FDZB+e-%OwBSo+mc3O>fZu`=(UMFd)BUJgg zD7Sz|KEmtVIRC)xMJ^0JQ#mtu!+|iOSq`9S9Cza=GYpF-+HF7tel3N#Tvp>Wp#qYU z!)9(fB_^bj&1!hG(cq>uSLQ6Y3@39=rz_f)ZZ2C$w|oHv%d?3Hr>mE$rK$&+KYO); zNtiYpU*Y#~m*x%~P){{wCvmLZ0AHI<$m0bqQFw9#Koy}CiJ&DLvDRG zK-=|aY6LSd@k2oO-QJ{ZS&k5LB;FM^+~_s-s{Sq_lLl(>&nzuFg}Ez+{7*yZ;>zV1 zMf^Wt`|p@j#@QO{y%>unJiVmoOflVnlf*)euj-mL@^%V(VrD%3DWER*dRXMx8!RUm zW(sq^(ArsvD>;R7FN`?zPtvf&g$teiiu0PvyU9DLYp4f4Zm`+1NOH>7-oBSuifevM z)mcoH68j=;)vWzUJU^&?P?@}#?^)W>t<_4RLXGHz?ydUPMU$yJJ)8yC9-c;8+UllV z!bKA6F8ME%sd{JVL!kU_W#~pL!`2C6pH6Y9 z8A1cDQoT>Fo>pgL_P2UneLAu9I`Dy*m=+I-sB$0B$~{uCH!6N=H*@Yut06d48Q**C zbtJcQ`U#1(A5BzqJ97lFH=dl%e6D{T>9&Q){M^u^6UXJ%!^E_t1PRfe(m8tR{vlqD z_r#K}?V%AZYf%q+ElSe>=^EG%BqV&z)hFqsgK@7fZ72igeePFQ?}gRgS8xyNiJhif z=WG%M%fo0irqbquG_l4wj1zjUEKriZ2*{7lA2FrL$V5yORzDEY_GEV-LHc6N;9uOX zp|U&SO<-=7(@Q|oi`*##4CEa&>VIe!^nQ|A=)>{ob-2HL%k#72?T0*2rdIBQU!te_1BR&7v^mjjOF6kFZMTB3S=4`=~^%5 z*<*TP)&s}l5_)m-$hL#UJ(XwFZ%;o^unuA;=#07D&))H&sI@k`%SVVe=ydNE^ggi? z+(##F__iC9#$eW&w23XfyAZ5LE&T1>WE-Y|ROi`*-l+s{fY}lZ1fv1_Ju7X3= z-DBhK@Kx7oWXl`0?ER1BanXZqRSZDd96{6k03I-_yKU_pP&`-A);@s84m=L;JJ(3% z_q+!rBH}PU)ydggKbV-G_7N|AmN6J4v~3;xT7z3D=M!Vb{b)8J?+Vj6YnO5VAl*q5|9 zuVgt!!MUOsbV-W+z2HYK({Y2zg11~28D2os1cS-ux4b492SVr6@vl@6nmV)ome(xf zKnD3N=9tNFW2Xydt9p98B-buDCH{rCZFH07%}#b?h?X~fwqGa*i&S1byFw-@_hn1$ z>+RU`0@SjLeFyL4A0*a(E`-O_cB|bzfq~xyfPt&gq_+}y_HV9WUK)Xhoh=n@3sNkdLu+K zH82uno3*XDsxUO!T}`k%+MT;+?|e*1FCG@%7dFX0Jdw?#Ph4;tyz_*6!j zyBq7z8A&r#4G8yNXH%1lNiK#x0$K*&vfsLEmh@VrKmBsQC)S#R=DccUuM~}#m*oAy zZb94}oN_vK2TjQ9bdg|p8T;3Bu@rU4`O1A4o2ggxL&ZmSs2!Q}3%99_)S^qc7#Qy8 zMfJ~1DG_=pb%&r(P!|XH^UJzwQ@GI*i0n&XP*}kNrOuYe3a4K-pz$#$ee0*WQ>xiu zdksM;RqpMrtz7IU15r`%y*uE9LAJeJNx?fWvZ%|&d@tmQ_tQNIM80DPCgOX8^jwyT zk?6^48X0OSa|5kh5RxMrqsbMYvL@0PRjx9BrzGvaoOeSc4q8SR-<3_r;b@JCY<|4s z>B*{>9AttA$U-#rrsnFDpig!QM@RU@CZ&NM9tHzSFZ8ey^`s^A4Wy4txfT2k?5N+Q z^(AIxWLi$_wlqJQzwvz(xI`ME>>T}Qf$zwbalh@ z;P?TPP)pv;X#SlkR{Q(U@lv{-Gc8}#u+syoarKsEKU^l&s8ZXUSy4P~O8Di zcmV{AJc-0-QWRs7Q`x}|?0iG?F+2o62ZN(IUx$lgd^8Cj=1zP-(=NTP$MLakDHfbe z?@rr}*$y|`ASr_p=Y^Rv+V8m3l^eh*6w6*zx%nH;B^+aJ?Jk|@xfS`vzZqX}_>?0j zoP}2?sUc8gP=Mk<)d3}7?~*Fz=%U-atyV3l5>7AkGbZE?@kMDTbv$G=vvu;Pfbxh| z)^o4JJJyFD9gKxIVPV0Pp*gECq?@xH`INV63+7FX|X_5Xx zkP4+>XD`!VdNKx)o<0LaX?(={Ubl(}YI19_t! zL7}SMwUvTS@J5$TJ7!}?zp9fguBiOeNfMY`5M@N~irL*Od{=pn3m*d8BZM+<`u4~{ zV)?WSFZbsx)~(%8Ty?~ynpMGMT2AeA#l=and1U4SB}=%`cz*JU{O zQl`b5h}VLuN_4d9oLKePxXSpLP8T`m?GVmGL4a9vfoe%S2~SWCABD~vFYT@~u+9?4 zm&!W&CG=u8fH~P}uB3jto)|Ze9cK?XxpdWj9WN9_uP}XsSeoN6K|ijVx{yF8SjiY` zI@OseWLEt3Jh#Y*@sx(rOezB&mF%sMw-aq#cww3DK_3dIrxXonm8|g#W7@s13hA%l zyu2e8Im_@#ao{eIUfPBNc7uQ~4JLb?!Hj?sv>iaOMAF~;Wx#%?gq<@R>Y?eGdh@qoBv?bg(DBD*9Oj#>JY^TkSuSI&h2j1wvv5T7h3xKvvu&HS;1l_=ZqzM z@~x~diE1)=582gre92NVXGJ6MVDA9#U-a9oeN*z;?%UKf;FyRqmCkogeQ}t{)}bfh zqfQ4F6>PsJrCe#u*^D($Zb?rBYi~8jqe!a&uJ@o80k}76gdC7@{jIs0aNCknk;QIx zn}*2@gV7km%&IDOBvssusQ`0p__pe({v`RpV_GD*n=!5RntXl5f|1#^rptKjbP?oT z`549T%u_k9=>;UG>IcJbeonAz0KygO24o3&_D{#Ch-cXMGI3LHkK}-{uP8cov~0so z0v%+pvT_((rHQ?HO=6bVp87&zhhOvU;l57I?g3T~Ww8>TUFxB{s;;E(*8MxP`ef5N ze>FDv0`b-6BUTw}D0U;9mn)uqDyq_b8{!AQe##mYyrpK9UG{FC#m}n{-n{x^|1?W} z@9RWsOm*!s5E}!7>#zSb(VpLxyQ6$ykgwD1yy-k*S>#esOG~@s!ou-skgFbp;{Gj%l@*xZ?EfwyYKwQxpagAGs_^i;-R!q#Y&-+H3o(s8;5~fbFir zt=x1ej&)R~I^KY{=FC3-(Z*iTsQhk6V^Gsd8u3ss_nL{trTjSkh>%`uySiDgjBG;HqLqy88g8*l;Ls%s64pRi}es?>w17t6xvvM0b*5M9Pr z?^vgk_T06ka_rFQ9w4|jTWM>hy}!ag!(~uvUzrch9~w%G+TgJOqzKBxiHA*}6;p*R z_E*w!-8STKSQpChc@@toIVC+r_n`9~Qlrz+@oD>2%OtIrqvShP3^zGm+VF!!i1y0; z5W72@&3zs9?1qgOvLpwkF-A|0x811;bLdOd_86}x;hRQV=C6>fxeuG^eY%*I-96Xk zA1G1Tg0;EIkl6yT^Gg)m<=DKU7v(D^X~HVKQs)5aZYVDYA5EvJk2prQCY-3+2>^s}m zx^hETse=WgCbaB~z6#hcIYY`q znN1dnd>N~jLjuEu7v(AejKcyl(Bld{L(XwbOuiWI@G`H+kg22XtEl;#|` z^C}Hh%K+%6TZiA8zaV$hNEi3{(;42znqxcRYBmh4aZBNTK}*-S3yFm`%fTxAq49y( zYU7G|gT@!IA9kD3unZE}HQC>g6+784vXIt!PM8f;-~%^M(md83PzaIAg5{Q5k%3Jw3hck`;8Lof+H=L<3*c3E!BLJGH1h^W*G-`GG8@Rq*H4zG?x zK;Y!je)^L3LuU$RKP1nt-BTX#HA>?fe5ASsYPotoGUVyFw33`8II|_R6ry44JjArp z9j#1JM@-I{Ot$t?My)q^vKuh$l3tl1sO=FO-7n0!0`f!Qq84KDZPOp>2S3!;Ic{H< zHc|}{SbVT;=~lOKX_c)->t%niLM*`%p-IV_*Uqkbdgs%KSu@H*GH-`V@m5R62+#LE zDKVCtBTq~H2SCHO93Lvi+)aB%H@=g6c3UI%BvKPY5Bp10-z4O@HuC;U4Zj0>Q>7WI zAbEbXmX-%;q*v!!%ba^Y61|nqejmQvl}cQhwpw(r&kZ;FB_kF#?|c!>#^S+qhb^no zmWf`8_X|eIsrQQYFYQsqc{|8AVjSzW%#$y>lr|IsJ65U%yeZzOdSS2E$M35P6?iRp zjn0uZ%(qchttv{>52A-mRi&i3bZyi`@C#Zz$P9;6W(^Z4 z;zggQAL=Fbg4IZQ6`|BK8v5F~SdI(k`k5AP13sRk4h@eFh=vIVZJig?ZJi`vu-Vau zm5Ar3j*ob`ANr*p#0Xd&94}WAEkBkjr?C;HptN~{38PGI%09wSlXpMGv;j?vMO5ZX zq!2T@?ZcrTGTh`YYiV)a^WhyDl*Gr+0Qu^%R!^t z{3&*mVQwPOfJD_?*PS9n0O4YNyX9${Gjj)^67qSSnWLs>$u6ypINjM>9$6(l4ZtB` zQ_XIYit;=4O=$^Qro&iPb=nMPyiB2M?yMab{VpFRrc6yd@t34BOLHA}?)bW7z-2{H zF8yB{_|(duSNr2E=SVT;FZqh4?L>9>`$6~?cwKN^H#w!Uc^8*d7A$DT4h~4MRJ;-y z$C6#$au>`g$D-CxIG!BowHccjzvWrdeZ3ns+hHR-3zP}$lY3kfS5nJK07vx z{J7h1{mhDFaZblq_Pkni%?SI`5Gs&vfmFVqd?IuEQemv>UtjKCV_Br>7eSd3?)3{p z@q6d5!o`d}dks<+nU$X+7xAfuM-SXRpKB#vxVU;>D81uE1AA=uq}IW=d6}1uhe;r= zoA~3BFiQqDzi_Bg{JtOQ-f*S4=aY7RRC*i<_J*1ABc98vetPpK*jCLn(D@#~=T%I* zF(RLk7Sp+q+MNqcekbeN2KlPDio9^aoSR6y3l^JWUD>n?^~bGUrxR1bZwWF8Ftq;y za6Y~$VdVbsVm|k})hkl-xt_+vo^WNt3)607@+jC@T?h993@Muq}VY z{2g0Pa+DT1q zH$O(rl0VM6I}~eDH)g%9uTAY;aBmflJ-PI*VH({;{P45S;Uc)?S1$T%o$!2#K5dk_ zaDHyo*S`BD1r67D1pH6ine%fa+{OiJ4{=8UkaqS%2}3TSd}Oz_B*P%X)3QKpY$RuY zy;D}JaJVrxaQDHbSI=S+40Upg-(^iKmnjJ@#8jni*wFVzRTQ@A3xT=V`&AsP*RO#Z z0rhz=L9^2}*O(qK>^OzyWpOunxp0eHC5Q2f62V3)b2z;p>`7`hv9wK!xv3C!SHKVbHFSWH4iGvt{||f(QS^XVZIkE8;!5te+v~#-=L}1 zC78UYBi{CKW2wiwc|0^n(t7(^%*gbs+eG;*mam(iZkmTK_$N8~Zi0P1#=CYFSQD+& z1va(bHA?uLt$hKEoY5{lsq$2S$qqkq_#C%Zwaa9n{Ny}*RR@k^*cUJ`La}~??+554 z8}Vq8?ji=p`R^-&2ZFLWKXOaTpdZYh5TrXA!z0DQ58z{AhPZ$ccIY#?6pMkr60aDG zj5L8H!rmSVhJb#+dU(?@ZY7>EY` zpiwU4nR8_lnt*Z6yn9o!u&mszXV)>Mfyi{&)txg{OSzU1&V^&z<9w($T_4*TGi9W zg;^av=3OL#|H|-9>?=FECwb8}A<_bT{6f5ZLc9Xv`h0vKei4wcI6zF`yMlkReiMh- zp{=?&@Pd87=(B?K57ytv|Kzp%j@**g))&?g6wv#w{)f!p)sl8#5DJQdL8P@+w2cr* zJ7z5feKa_XSr=ks2SG{#|E@ph{1^WP1V92J0CB#roL}qD8UHOnzmUKA>Hu;11`U(e zLieWD*Rc!~0`LL&*^Hnrf&$Dx$>*JHg@#}`LBJ@W5FbBWm`%$r17+3!Sz?YX_*sjk0p@_!e1 zwL#iAAZ=V7e=v)Gukb>L^ao8)^g9vkh-L?)(R4p*_{GIofYOrRM=VLzz3TuLfmW{0cVkrG+VU`h(^$l``I__v2~8AbafW{pz+FTc+cKT{Kv!BwEk$ z%5AoY>|S@8C(tfk*>wXUog5??J9J&5b@u%m4AsD`6zVbIG#cZ|jba*~=yL7VBUi>o zh6$x4)MmDA@%zW4k-Jr5wnIRw!^A;+O;nU{a+)1{h6|*x&R5>s(bS*T(_O`7-qcmC zZ5kSu{+Ss?@US*Hr6TUrUK78~llFu~UxJgYf-u)*eZrg$Ps@NHmCegZVdNcBzW-5)zL+R07|G_oi>52W~FTGTqL5(&@mj zi(I|aST+lKmX=1(#oB5YFexzpjI=~Y`j_+rclo>2FDzf7b3e-(=dB5&HxehPns2>vWnqvQFzvU4Ql zH(2AJwVVSme Date: Wed, 21 May 2014 10:35:40 -0600 Subject: [PATCH 05/21] Update composer requirements for php-zip With PCLZip support added php-zip isn't a requirement any more. --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index fafa9a28..f9fad331 100644 --- a/composer.json +++ b/composer.json @@ -33,13 +33,13 @@ ], "require": { "php": ">=5.3.3", - "ext-xml": "*", - "ext-zip": "*" + "ext-xml": "*" }, "require-dev": { "phpunit/phpunit": "3.7.*" }, "suggest": { + "ext-zip": "Used to write DOCX and ODT", "ext-gd2": "Required to add images", "ext-xmlwriter": "Required to write DOCX and ODT", "ext-xsl": "Required to apply XSL style sheet to template part", From 3e0d37ec94d1f82a006fb7d962693d9e82fffe42 Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Thu, 22 May 2014 00:05:12 +0700 Subject: [PATCH 06/21] Update changelog --- CHANGELOG.md | 6 ++++++ composer.json | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34c57b24..02439e1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ This is the changelog between releases of PHPWord. Releases are listed in reverse chronological order with the latest version listed on top, while additions/changes in each release are listed in chronological order. Changes in each release are divided into three parts: added or change features, bugfixes, and miscellaneous improvements. Each line contains short information about the change made, the person who made it, and the related issue number(s) in GitHub. +## 0.10.1 - 21 May 2014 + +This is a bugfix release for `php-zip` requirement in Composer. + +- Change Composer requirements for php-zip from `require` to `suggest` - @bskrtich GH-246 + ## 0.10.0 - 4 May 2014 This release marked heavy refactorings on internal code structure with the creation of some abstract classes to reduce code duplication. `Element` subnamespace is introduced in this release to replace `Section`. Word2007 reader capability is greatly enhanced. Endnote is introduced. List numbering is now customizable. Basic HTML and PDF writing support is enabled. Basic ODText reader is introduced. diff --git a/composer.json b/composer.json index f9fad331..e9daa890 100644 --- a/composer.json +++ b/composer.json @@ -40,10 +40,10 @@ }, "suggest": { "ext-zip": "Used to write DOCX and ODT", - "ext-gd2": "Required to add images", - "ext-xmlwriter": "Required to write DOCX and ODT", - "ext-xsl": "Required to apply XSL style sheet to template part", - "dompdf/dompdf": "Required to write PDF" + "ext-gd2": "Used to add images", + "ext-xmlwriter": "Used to write DOCX and ODT", + "ext-xsl": "Used to apply XSL style sheet to template part", + "dompdf/dompdf": "Used to write PDF" }, "autoload": { "psr-4": { From fbce1c8fc92817a77bc793c735135cab78613bff Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Wed, 21 May 2014 23:53:08 +0700 Subject: [PATCH 07/21] #244: Enable "image float left" --- CHANGELOG.md | 1 + samples/Sample_13_Images.php | 3 +++ src/PhpWord/Style/Image.php | 19 ++++++++++--------- src/PhpWord/Writer/Word2007/Style/Image.php | 7 +------ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58dd0aff..4a9b172f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ This release marked the change of PHPWord license from LGPL 2.1 to LGPL 3; new r - ODT Writer: Enable title element and custom document properties - @ivanlanin - ODT Reader: Ability to read standard and custom document properties - @ivanlanin - Word2007 Writer: Enable the missing custom document properties writer - @ivanlanin +- Image: Enable "image float left" - @ivanlanin GH-244 ### Bugfixes diff --git a/samples/Sample_13_Images.php b/samples/Sample_13_Images.php index dd5209bf..dd0c8801 100644 --- a/samples/Sample_13_Images.php +++ b/samples/Sample_13_Images.php @@ -40,6 +40,9 @@ $section->addImage( 'width' => \PhpOffice\PhpWord\Shared\Drawing::centimetersToPixels(3), 'height' => \PhpOffice\PhpWord\Shared\Drawing::centimetersToPixels(3), 'positioning' => \PhpOffice\PhpWord\Style\Image::POSITION_ABSOLUTE, + 'posHorizontal' => \PhpOffice\PhpWord\Style\Image::POSITION_HORIZONTAL_RIGHT, + 'posHorizontalRel' => \PhpOffice\PhpWord\Style\Image::POSITION_RELATIVE_TO_PAGE, + 'posVerticalRel' => \PhpOffice\PhpWord\Style\Image::POSITION_RELATIVE_TO_PAGE, 'marginLeft' => \PhpOffice\PhpWord\Shared\Drawing::centimetersToPixels(15.5), 'marginTop' => \PhpOffice\PhpWord\Shared\Drawing::centimetersToPixels(1.55) ) diff --git a/src/PhpWord/Style/Image.php b/src/PhpWord/Style/Image.php index fc9f9630..33c85ddf 100644 --- a/src/PhpWord/Style/Image.php +++ b/src/PhpWord/Style/Image.php @@ -61,6 +61,7 @@ class Image extends AbstractStyle const POSITION_RELATIVE_TO_PAGE = 'page'; const POSITION_RELATIVE_TO_COLUMN = 'column'; // horizontal only const POSITION_RELATIVE_TO_CHAR = 'char'; // horizontal only + const POSITION_RELATIVE_TO_TEXT = 'text'; // vertical only const POSITION_RELATIVE_TO_LINE = 'line'; // vertical only const POSITION_RELATIVE_TO_LMARGIN = 'left-margin-area'; // horizontal only const POSITION_RELATIVE_TO_RMARGIN = 'right-margin-area'; // horizontal only @@ -103,14 +104,14 @@ class Image extends AbstractStyle * * @var int */ - private $marginTop; + private $marginTop = 0; /** * Margin Left * * @var int */ - private $marginLeft; + private $marginLeft = 0; /** * Wrapping style @@ -247,9 +248,9 @@ class Image extends AbstractStyle * @param int $value * @return self */ - public function setMarginTop($value = null) + public function setMarginTop($value = 0) { - $this->marginTop = $value; + $this->marginTop = $this->setIntVal($value, 0); return $this; } @@ -270,9 +271,9 @@ class Image extends AbstractStyle * @param int $value * @return self */ - public function setMarginLeft($value = null) + public function setMarginLeft($value = 0) { - $this->marginLeft = $value; + $this->marginLeft = $this->setIntVal($value, 0); return $this; } @@ -352,7 +353,7 @@ class Image extends AbstractStyle { $enum = array( self::POSITION_HORIZONTAL_LEFT, self::POSITION_HORIZONTAL_CENTER, - self::POSITION_HORIZONTAL_RIGHT, + self::POSITION_HORIZONTAL_RIGHT, self::POSITION_ABSOLUTE ); $this->posHorizontal = $this->setEnumVal($alignment, $enum, $this->posHorizontal); @@ -381,7 +382,7 @@ class Image extends AbstractStyle $enum = array( self::POSITION_VERTICAL_TOP, self::POSITION_VERTICAL_CENTER, self::POSITION_VERTICAL_BOTTOM, self::POSITION_VERTICAL_INSIDE, - self::POSITION_VERTICAL_OUTSIDE, + self::POSITION_VERTICAL_OUTSIDE, self::POSITION_ABSOLUTE ); $this->posVertical = $this->setEnumVal($alignment, $enum, $this->posVertical); @@ -439,7 +440,7 @@ class Image extends AbstractStyle { $enum = array( self::POSITION_RELATIVE_TO_MARGIN, self::POSITION_RELATIVE_TO_PAGE, - self::POSITION_RELATIVE_TO_LINE, + self::POSITION_RELATIVE_TO_TEXT, self::POSITION_RELATIVE_TO_LINE, self::POSITION_RELATIVE_TO_TMARGIN, self::POSITION_RELATIVE_TO_BMARGIN, self::POSITION_RELATIVE_TO_IMARGIN, self::POSITION_RELATIVE_TO_OMARGIN, ); diff --git a/src/PhpWord/Writer/Word2007/Style/Image.php b/src/PhpWord/Writer/Word2007/Style/Image.php index 36b308f1..b6b46535 100644 --- a/src/PhpWord/Writer/Word2007/Style/Image.php +++ b/src/PhpWord/Writer/Word2007/Style/Image.php @@ -65,16 +65,11 @@ class Image extends AbstractStyle // Absolute/relative positioning $positioning = $style->getPositioning(); $styleArray['position'] = $positioning; - if ($positioning == ImageStyle::POSITION_ABSOLUTE) { - $styleArray['mso-position-horizontal-relative'] = 'page'; - $styleArray['mso-position-vertical-relative'] = 'page'; - } elseif ($positioning == ImageStyle::POSITION_RELATIVE) { + if ($positioning !== null) { $styleArray['mso-position-horizontal'] = $style->getPosHorizontal(); $styleArray['mso-position-vertical'] = $style->getPosVertical(); $styleArray['mso-position-horizontal-relative'] = $style->getPosHorizontalRel(); $styleArray['mso-position-vertical-relative'] = $style->getPosVerticalRel(); - $styleArray['margin-left'] = 0; - $styleArray['margin-top'] = 0; } // Wrapping style From 2086e9e2627a77f3ab9dcb7a88d598ec9a750621 Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Thu, 22 May 2014 19:08:09 +0700 Subject: [PATCH 08/21] Refactor: Reduce cyclomatic complexity --- src/PhpWord/DocumentProperties.php | 74 +++++----- src/PhpWord/Reader/ODText/AbstractPart.php | 72 ---------- src/PhpWord/Reader/Word2007/AbstractPart.php | 4 +- src/PhpWord/Reader/Word2007/Document.php | 111 +++++++++------ src/PhpWord/Writer/RTF.php | 141 +++++++------------ 5 files changed, 154 insertions(+), 248 deletions(-) diff --git a/src/PhpWord/DocumentProperties.php b/src/PhpWord/DocumentProperties.php index 17d57c1d..95159ec0 100644 --- a/src/PhpWord/DocumentProperties.php +++ b/src/PhpWord/DocumentProperties.php @@ -488,49 +488,23 @@ class DocumentProperties */ public static function convertProperty($propertyValue, $propertyType) { - switch ($propertyType) { - case 'empty': // Empty + $conversion = self::getConversion($propertyType); + + switch ($conversion) { + case 'empty': // Empty return ''; - case 'null': // Null + case 'null': // Null return null; - case 'i1': // 1-Byte Signed Integer - case 'i2': // 2-Byte Signed Integer - case 'i4': // 4-Byte Signed Integer - case 'i8': // 8-Byte Signed Integer - case 'int': // Integer + case 'int': // Signed integer return (int) $propertyValue; - case 'ui1': // 1-Byte Unsigned Integer - case 'ui2': // 2-Byte Unsigned Integer - case 'ui4': // 4-Byte Unsigned Integer - case 'ui8': // 8-Byte Unsigned Integer - case 'uint': // Unsigned Integer + case 'uint': // Unsigned integer return abs((int) $propertyValue); - case 'r4': // 4-Byte Real Number - case 'r8': // 8-Byte Real Number - case 'decimal': // Decimal + case 'float': // Float return (float) $propertyValue; - case 'date': // Date and Time - case 'filetime': // File Time + case 'date': // Date return strtotime($propertyValue); - case 'bool': // Boolean + case 'bool': // Boolean return ($propertyValue == 'true') ? true : false; - case 'lpstr': // LPSTR - case 'lpwstr': // LPWSTR - case 'bstr': // Basic String - case 'cy': // Currency - case 'error': // Error Status Code - case 'vector': // Vector - case 'array': // Array - case 'blob': // Binary Blob - case 'oblob': // Binary Blob Object - case 'stream': // Binary Stream - case 'ostream': // Binary Stream Object - case 'storage': // Binary Storage - case 'ostorage': // Binary Storage Object - case 'vstream': // Binary Versioned Stream - case 'clsid': // Class ID - case 'cf': // Clipboard Data - return $propertyValue; } return $propertyValue; @@ -569,10 +543,36 @@ class DocumentProperties */ private function setValue($value, $default) { - if (is_null($value) || $value == '') { + if ($value === null || $value == '') { $value = $default; } return $value; } + + /** + * Get conversion model depending on property type + * + * @param string $propertyType + * @return string + */ + private static function getConversion($propertyType) + { + $conversions = array( + 'empty' => array('empty'), + 'null' => array('null'), + 'int' => array('i1', 'i2', 'i4', 'i8', 'int'), + 'uint' => array('ui1', 'ui2', 'ui4', 'ui8', 'uint'), + 'float' => array('r4', 'r8', 'decimal'), + 'bool' => array('bool'), + 'date' => array('date', 'filetime'), + ); + foreach ($conversions as $conversion => $types) { + if (in_array($propertyType, $types)) { + return $conversion; + } + } + + return 'string'; + } } diff --git a/src/PhpWord/Reader/ODText/AbstractPart.php b/src/PhpWord/Reader/ODText/AbstractPart.php index 392c3126..2097df9c 100644 --- a/src/PhpWord/Reader/ODText/AbstractPart.php +++ b/src/PhpWord/Reader/ODText/AbstractPart.php @@ -18,7 +18,6 @@ namespace PhpOffice\PhpWord\Reader\ODText; use PhpOffice\PhpWord\Reader\Word2007\AbstractPart as Word2007AbstractPart; -use PhpOffice\PhpWord\Shared\XMLReader; /** * Abstract part reader @@ -28,75 +27,4 @@ use PhpOffice\PhpWord\Shared\XMLReader; */ abstract class AbstractPart extends Word2007AbstractPart { - /** - * Read w:p (override) - * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $domNode - * @param mixed $parent - * @param string $docPart - * - * @todo Get font style for preserve text - */ - protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, &$parent, $docPart) - { - } - - /** - * Read w:r (override) - * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $domNode - * @param mixed $parent - * @param string $docPart - * @param mixed $paragraphStyle - */ - protected function readRun(XMLReader $xmlReader, \DOMElement $domNode, &$parent, $docPart, $paragraphStyle = null) - { - } - - /** - * Read w:tbl (override) - * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $domNode - * @param mixed $parent - * @param string $docPart - */ - protected function readTable(XMLReader $xmlReader, \DOMElement $domNode, &$parent, $docPart) - { - } - - /** - * Read w:pPr (override) - */ - protected function readParagraphStyle(XMLReader $xmlReader, \DOMElement $domNode) - { - } - - /** - * Read w:rPr (override) - */ - protected function readFontStyle(XMLReader $xmlReader, \DOMElement $domNode) - { - } - - /** - * Read w:tblPr (override) - */ - protected function readTableStyle(XMLReader $xmlReader, \DOMElement $domNode) - { - } - - /** - * Read style definition (override) - * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $parentNode - * @param array $styleDefs - * @return array - */ - protected function readStyleDefs(XMLReader $xmlReader, \DOMElement $parentNode = null, $styleDefs = array()) - { - } } diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 2289a5e8..2936884f 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -98,7 +98,7 @@ abstract class AbstractPart * * @todo Get font style for preserve text */ - protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, &$parent, $docPart) + protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, &$parent, $docPart = 'document') { // Paragraph style $paragraphStyle = null; @@ -248,7 +248,7 @@ abstract class AbstractPart * @param mixed $parent * @param string $docPart */ - protected function readTable(XMLReader $xmlReader, \DOMElement $domNode, &$parent, $docPart) + protected function readTable(XMLReader $xmlReader, \DOMElement $domNode, &$parent, $docPart = 'document') { // Table style $tblStyle = null; diff --git a/src/PhpWord/Reader/Word2007/Document.php b/src/PhpWord/Reader/Word2007/Document.php index 34460b52..224c895e 100644 --- a/src/PhpWord/Reader/Word2007/Document.php +++ b/src/PhpWord/Reader/Word2007/Document.php @@ -19,14 +19,23 @@ namespace PhpOffice\PhpWord\Reader\Word2007; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Shared\XMLReader; +use PhpOffice\PhpWord\Element\Section; /** * Document reader * * @since 0.10.0 + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) For readWPNode */ class Document extends AbstractPart { + /** + * PhpWord object + * + * @var \PhpOffice\PhpWord\PhpWord + */ + private $phpWord; + /** * Read document.xml * @@ -34,45 +43,18 @@ class Document extends AbstractPart */ public function read(PhpWord &$phpWord) { + $this->phpWord = $phpWord; $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); + $readMethods = array('w:p' => 'readWPNode', 'w:tbl' => 'readTable', 'w:sectPr' => 'readWSectPrNode'); $nodes = $xmlReader->getElements('w:body/*'); if ($nodes->length > 0) { - $section = $phpWord->addSection(); + $section = $this->phpWord->addSection(); foreach ($nodes as $node) { - switch ($node->nodeName) { - - case 'w:p': // Paragraph - // Page break - // @todo - if ($xmlReader->getAttribute('w:type', $node, 'w:r/w:br') == 'page') { - $section->addPageBreak(); // PageBreak - } - - // Paragraph - $this->readParagraph($xmlReader, $node, $section, 'document'); - // Section properties - if ($xmlReader->elementExists('w:pPr/w:sectPr', $node)) { - $settingsNode = $xmlReader->getElement('w:pPr/w:sectPr', $node); - if (!is_null($settingsNode)) { - $settings = $this->readSectionStyle($xmlReader, $settingsNode); - $section->setSettings($settings); - $this->readHeaderFooter($settings, $section); - } - $section = $phpWord->addSection(); - } - break; - - case 'w:tbl': // Table - $this->readTable($xmlReader, $node, $section, 'document'); - break; - - case 'w:sectPr': // Last section - $settings = $this->readSectionStyle($xmlReader, $node); - $section->setSettings($settings); - $this->readHeaderFooter($settings, $section); - break; + if (array_key_exists($node->nodeName, $readMethods)) { + $readMethod = $readMethods[$node->nodeName]; + $this->$readMethod($xmlReader, $node, $section); } } } @@ -84,14 +66,16 @@ class Document extends AbstractPart * @param array $settings * @param \PhpOffice\PhpWord\Element\Section $section */ - private function readHeaderFooter($settings, &$section) + private function readHeaderFooter($settings, Section &$section) { + $readMethods = array('w:p' => 'readParagraph', 'w:tbl' => 'readTable'); + if (is_array($settings) && array_key_exists('hf', $settings)) { foreach ($settings['hf'] as $rId => $hfSetting) { if (array_key_exists($rId, $this->rels['document'])) { list($hfType, $xmlFile, $docPart) = array_values($this->rels['document'][$rId]); - $method = "add{$hfType}"; - $hfObject = $section->$method($hfSetting['type']); + $addMethod = "add{$hfType}"; + $hfObject = $section->$addMethod($hfSetting['type']); // Read header/footer content $xmlReader = new XMLReader(); @@ -99,15 +83,9 @@ class Document extends AbstractPart $nodes = $xmlReader->getElements('*'); if ($nodes->length > 0) { foreach ($nodes as $node) { - switch ($node->nodeName) { - - case 'w:p': // Paragraph - $this->readParagraph($xmlReader, $node, $hfObject, $docPart); - break; - - case 'w:tbl': // Table - $this->readTable($xmlReader, $node, $hfObject, $docPart); - break; + if (array_key_exists($node->nodeName, $readMethods)) { + $readMethod = $readMethods[$node->nodeName]; + $this->$readMethod($xmlReader, $node, $hfObject, $docPart); } } } @@ -157,4 +135,47 @@ class Document extends AbstractPart return $styles; } + + /** + * Read w:p node + * + * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader + * @param \DOMElement $node + * @param \PhpOffice\PhpWord\Element\Section $section + * + * @todo + */ + private function readWPNode(XMLReader $xmlReader, \DOMElement $node, Section &$section) + { + // Page break + if ($xmlReader->getAttribute('w:type', $node, 'w:r/w:br') == 'page') { + $section->addPageBreak(); // PageBreak + } + + // Paragraph + $this->readParagraph($xmlReader, $node, $section); + + // Section properties + if ($xmlReader->elementExists('w:pPr/w:sectPr', $node)) { + $sectPrNode = $xmlReader->getElement('w:pPr/w:sectPr', $node); + $this->readWSectPrNode($xmlReader, $sectPrNode, $section); + $section = $this->phpWord->addSection(); + } + } + + /** + * Read w:sectPr node + * + * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader + * @param \DOMElement $node + * @param \PhpOffice\PhpWord\Element\Section $section + */ + private function readWSectPrNode(XMLReader $xmlReader, \DOMElement $node, Section &$section) + { + if ($node !== null) { + $settings = $this->readSectionStyle($xmlReader, $node); + $section->setSettings($settings); + $this->readHeaderFooter($settings, $section); + } + } } diff --git a/src/PhpWord/Writer/RTF.php b/src/PhpWord/Writer/RTF.php index 315e6007..b2cf9a05 100644 --- a/src/PhpWord/Writer/RTF.php +++ b/src/PhpWord/Writer/RTF.php @@ -33,18 +33,18 @@ use PhpOffice\PhpWord\Writer\RTF\Element\Container; class RTF extends AbstractWriter implements WriterInterface { /** - * Color register + * Font table * * @var array */ - private $colorTable; + private $fontTable = array(); /** - * Font register + * Color table * * @var array */ - private $fontTable; + private $colorTable = array(); /** * Last paragraph style @@ -81,14 +81,6 @@ class RTF extends AbstractWriter implements WriterInterface $this->cleanupTempFile(); } - /** - * Get color table - */ - public function getColorTable() - { - return $this->colorTable; - } - /** * Get font table */ @@ -97,6 +89,14 @@ class RTF extends AbstractWriter implements WriterInterface return $this->fontTable; } + /** + * Get color table + */ + public function getColorTable() + { + return $this->colorTable; + } + /** * Get last paragraph style */ @@ -122,8 +122,7 @@ class RTF extends AbstractWriter implements WriterInterface */ private function writeDocument() { - $this->fontTable = $this->populateFontTable(); - $this->colorTable = $this->populateColorTable(); + $this->populateTableGroups(); // Set the default character set $content = '{\rtf1'; @@ -148,6 +147,8 @@ class RTF extends AbstractWriter implements WriterInterface $content .= ';}' . PHP_EOL; $content .= '{\*\generator PhpWord;}' . PHP_EOL; // Set the generator + + // Document formatting $content .= '\viewkind4'; // Set the view mode of the document $content .= '\uc1'; // Set the numberof bytes that follows a unicode character $content .= '\pard'; // Resets to default paragraph properties. @@ -155,7 +156,7 @@ class RTF extends AbstractWriter implements WriterInterface $content .= '\lang1036'; // Applies a language to a text run (1036 : French (France)) $content .= '\kerning1'; // Point size (in half-points) above which to kern character pairs $content .= '\fs' . (Settings::getDefaultFontSize() * 2); // Set the font size in half-points - $content .= PHP_EOL; + $content .= PHP_EOL . PHP_EOL; // Body $content .= $this->writeContent(); @@ -184,102 +185,58 @@ class RTF extends AbstractWriter implements WriterInterface } /** - * Get all fonts - * - * @return array + * Populate font and color table group */ - private function populateFontTable() + private function populateTableGroups() { $phpWord = $this->getPhpWord(); - $fontTable = array(); - $fontTable[] = Settings::getDefaultFontName(); + $this->fontTable[] = Settings::getDefaultFontName(); - // Browse styles + // Search font in styles $styles = Style::getStyles(); - if (count($styles) > 0) { - foreach ($styles as $style) { - // Font - if ($style instanceof Font) { - if (in_array($style->getName(), $fontTable) == false) { - $fontTable[] = $style->getName(); - } - } - } + foreach ($styles as $style) { + $this->pushTableGroupItems($style); } - // Search all fonts used + // Search font in elements $sections = $phpWord->getSections(); - $countSections = count($sections); - if ($countSections > 0) { - foreach ($sections as $section) { - $elements = $section->getElements(); - foreach ($elements as $element) { - if (method_exists($element, 'getFontStyle')) { - $fontStyle = $element->getFontStyle(); - if ($fontStyle instanceof Font) { - if (in_array($fontStyle->getName(), $fontTable) == false) { - $fontTable[] = $fontStyle->getName(); - } - } - } + foreach ($sections as $section) { + $elements = $section->getElements(); + foreach ($elements as $element) { + if (method_exists($element, 'getFontStyle')) { + $this->pushTableGroupItems($element->getFontStyle()); } } } - - return $fontTable; } /** - * Get all colors + * Push font and color table group items * - * @return array + * @param \PhpOffice\PhpWord\Style\AbstractStyle */ - private function populateColorTable() + private function pushTableGroupItems($style) { - $phpWord = $this->getPhpWord(); - $defaultFontColor = Settings::DEFAULT_FONT_COLOR; - $colorTable = array(); - - // Browse styles - $styles = Style::getStyles(); - if (count($styles) > 0) { - foreach ($styles as $style) { - // Font - if ($style instanceof Font) { - $color = $style->getColor(); - $fgcolor = $style->getFgColor(); - if (!in_array($color, $colorTable) && $color != $defaultFontColor && !empty($color)) { - $colorTable[] = $color; - } - if (!in_array($fgcolor, $colorTable) && $fgcolor != $defaultFontColor && !empty($fgcolor)) { - $colorTable[] = $fgcolor; - } - } - } + $defaultFont = Settings::getDefaultFontName(); + $defaultColor = Settings::DEFAULT_FONT_COLOR; + if ($style instanceof Font) { + $this->pushTableGroupItem($this->fontTable, $style->getName(), $defaultFont); + $this->pushTableGroupItem($this->colorTable, $style->getColor(), $defaultColor); + $this->pushTableGroupItem($this->colorTable, $style->getFgColor(), $defaultColor); } + } - // Search all fonts used - $sections = $phpWord->getSections(); - $countSections = count($sections); - if ($countSections > 0) { - foreach ($sections as $section) { - $elements = $section->getElements(); - foreach ($elements as $element) { - if (method_exists($element, 'getFontStyle')) { - $fontStyle = $element->getFontStyle(); - if ($fontStyle instanceof Font) { - if (in_array($fontStyle->getColor(), $colorTable) == false) { - $colorTable[] = $fontStyle->getColor(); - } - if (in_array($fontStyle->getFgColor(), $colorTable) == false) { - $colorTable[] = $fontStyle->getFgColor(); - } - } - } - } - } + /** + * Push individual font and color into corresponding table group + * + * @param array $tableGroup + * @param string $item + * @param string $default + */ + private function pushTableGroupItem(&$tableGroup, $item, $default) + { + if (!in_array($item, $tableGroup) && $item !== null && $item != $default) { + $tableGroup[] = $item; } - - return $colorTable; } } From 71a1db8650be29f80a6f3aa66588024f67907ba7 Mon Sep 17 00:00:00 2001 From: Roman Syroeshko Date: Thu, 22 May 2014 20:36:16 +0400 Subject: [PATCH 09/21] [CHANGED] License has been split into two files as recommended here http://www.gnu.org/licenses/gpl-howto.en.html. --- LICENSE.md => COPYING | 286 +++++++++++++++----------------------- COPYING.LESSER | 165 ++++++++++++++++++++++ README.md | 2 +- docs/intro.rst | 2 +- docs/src/documentation.md | 2 +- src/PhpWord/Media.php | 2 +- 6 files changed, 282 insertions(+), 177 deletions(-) rename LICENSE.md => COPYING (77%) create mode 100644 COPYING.LESSER diff --git a/LICENSE.md b/COPYING similarity index 77% rename from LICENSE.md rename to COPYING index a4343e8e..94a9ed02 100644 --- a/LICENSE.md +++ b/COPYING @@ -1,189 +1,74 @@ -# PHPWord License + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 -PHPWord, a pure PHP library for reading and writing word processing documents + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -Copyright (c) 2014 PHPWord + Preamble -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. + The GNU General Public License is a free, copyleft license for +software and other kinds of works. -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. -You should have received a copy of the GNU Lesser General Public License -along with this program. If not, see . + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. -## GNU LESSER GENERAL PUBLIC LICENSE + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. -Version 3, 29 June 2007 + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. - 0. Additional Definitions. + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. + The precise terms and conditions for copying, distribution and +modification follow. - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. - -## GNU GENERAL PUBLIC LICENSE - -Version 3, 29 June 2007 - -TERMS AND CONDITIONS + TERMS AND CONDITIONS 0. Definitions. @@ -732,3 +617,58 @@ reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/COPYING.LESSER b/COPYING.LESSER new file mode 100644 index 00000000..65c5ca88 --- /dev/null +++ b/COPYING.LESSER @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md index eb8dd445..ecbe1354 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ PHPWord is a library written in pure PHP that provides a set of classes to write to and read from different document file formats. The current version of PHPWord supports Microsoft [Office Open XML](http://en.wikipedia.org/wiki/Office_Open_XML) (OOXML or OpenXML), OASIS [Open Document Format for Office Applications](http://en.wikipedia.org/wiki/OpenDocument) (OpenDocument or ODF), [Rich Text Format](http://en.wikipedia.org/wiki/Rich_Text_Format) (RTF), HTML, and PDF. -PHPWord is an open source project licensed under the terms of [LGPL version 3](https://github.com/PHPOffice/PHPWord/blob/develop/LICENSE.md). PHPWord is aimed to be a high quality software product by incorporating [continuous integration](https://travis-ci.org/PHPOffice/PHPWord) and [unit testing](http://phpoffice.github.io/PHPWord/coverage/develop/). You can learn more about PHPWord by reading the [Developers' Documentation](http://phpword.readthedocs.org/) and the [API Documentation](http://phpoffice.github.io/PHPWord/docs/develop/). +PHPWord is an open source project licensed under the terms of [LGPL version 3](https://github.com/PHPOffice/PHPWord/blob/develop/COPYING.LESSER). PHPWord is aimed to be a high quality software product by incorporating [continuous integration](https://travis-ci.org/PHPOffice/PHPWord) and [unit testing](http://phpoffice.github.io/PHPWord/coverage/develop/). You can learn more about PHPWord by reading the [Developers' Documentation](http://phpword.readthedocs.org/) and the [API Documentation](http://phpoffice.github.io/PHPWord/docs/develop/). ## Features diff --git a/docs/intro.rst b/docs/intro.rst index 3c6277f5..f944c99b 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -13,7 +13,7 @@ Applications `__ Format `__ (RTF). PHPWord is an open source project licensed under the terms of `LGPL -version 3 `__. +version 3 `__. PHPWord is aimed to be a high quality software product by incorporating `continuous integration `__ and `unit testing `__. diff --git a/docs/src/documentation.md b/docs/src/documentation.md index ffa3edc3..7c8432c4 100644 --- a/docs/src/documentation.md +++ b/docs/src/documentation.md @@ -47,7 +47,7 @@ Don't forget to change `code::` directive to `code-block::` in the resulting rst PHPWord is a library written in pure PHP that provides a set of classes to write to and read from different document file formats. The current version of PHPWord supports Microsoft [Office Open XML](http://en.wikipedia.org/wiki/Office_Open_XML) (OOXML or OpenXML), OASIS [Open Document Format for Office Applications](http://en.wikipedia.org/wiki/OpenDocument) (OpenDocument or ODF), and [Rich Text Format](http://en.wikipedia.org/wiki/Rich_Text_Format) (RTF). -PHPWord is an open source project licensed under the terms of [LGPL version 3](https://github.com/PHPOffice/PHPWord/blob/develop/LICENSE.md). PHPWord is aimed to be a high quality software product by incorporating [continuous integration](https://travis-ci.org/PHPOffice/PHPWord) and [unit testing](http://phpoffice.github.io/PHPWord/coverage/develop/). You can learn more about PHPWord by reading this Developers' Documentation and the [API Documentation](http://phpoffice.github.io/PHPWord/docs/develop/). +PHPWord is an open source project licensed under the terms of [LGPL version 3](https://github.com/PHPOffice/PHPWord/blob/develop/COPYING.LESSER). PHPWord is aimed to be a high quality software product by incorporating [continuous integration](https://travis-ci.org/PHPOffice/PHPWord) and [unit testing](http://phpoffice.github.io/PHPWord/coverage/develop/). You can learn more about PHPWord by reading this Developers' Documentation and the [API Documentation](http://phpoffice.github.io/PHPWord/docs/develop/). ## Features diff --git a/src/PhpWord/Media.php b/src/PhpWord/Media.php index 8cb82013..0c4bd0ca 100644 --- a/src/PhpWord/Media.php +++ b/src/PhpWord/Media.php @@ -4,7 +4,7 @@ * 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. + * 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 From 23047c0bf7c7a8358a0c820c1679bee12ef0fa88 Mon Sep 17 00:00:00 2001 From: Roman Syroeshko Date: Thu, 22 May 2014 20:49:56 +0400 Subject: [PATCH 10/21] [CHANGED] License has been split into two files as recommended here http://www.gnu.org/licenses/gpl-howto.en.html. --- src/PhpWord/Media.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Media.php b/src/PhpWord/Media.php index 0c4bd0ca..8cb82013 100644 --- a/src/PhpWord/Media.php +++ b/src/PhpWord/Media.php @@ -4,7 +4,7 @@ * 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. + * 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 From 73bdd4f5a37337d04b83249c53225a92ec62f17b Mon Sep 17 00:00:00 2001 From: Roman Syroeshko Date: Thu, 22 May 2014 21:39:14 +0400 Subject: [PATCH 11/21] [CHANGED] License has been split into two files as recommended here http://www.gnu.org/licenses/gpl-howto.en.html. --- LICENSE | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cd15cbeb --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +PHPWord, a pure PHP library for reading and writing word processing documents. + +Copyright (c) 2010-2014 PHPWord. + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 3 as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License version 3 for more details. + +You should have received a copy of the GNU Lesser General Public License version 3 along with this program. If not, see http://www.gnu.org/licenses/. \ No newline at end of file From 571e0841d476b5defc3fa174dac916ff479ef937 Mon Sep 17 00:00:00 2001 From: Roman Syroeshko Date: Thu, 22 May 2014 21:43:19 +0400 Subject: [PATCH 12/21] [CHANGED] License has been split into two files as recommended here http://www.gnu.org/licenses/gpl-howto.en.html. --- LICENSE | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index cd15cbeb..3f6a8c38 100644 --- a/LICENSE +++ b/LICENSE @@ -2,8 +2,14 @@ PHPWord, a pure PHP library for reading and writing word processing documents. Copyright (c) 2010-2014 PHPWord. -This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 3 as published by the Free Software Foundation. +PHPWord is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License version 3 as published by +the Free Software Foundation. -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License version 3 for more details. +PHPWord is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License version 3 for more details. -You should have received a copy of the GNU Lesser General Public License version 3 along with this program. If not, see http://www.gnu.org/licenses/. \ No newline at end of file +You should have received a copy of the GNU Lesser General Public License version 3 +along with PHPWord. If not, see . From e46fecffdd79a550148e6086141f28fd971b61f9 Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Fri, 23 May 2014 00:21:22 +0700 Subject: [PATCH 13/21] RTF Writer: Ability to write document properties --- CHANGELOG.md | 1 + docs/intro.rst | 2 +- docs/src/documentation.md | 2 +- src/PhpWord/Writer/RTF.php | 226 +++--------------- src/PhpWord/Writer/RTF/Part/AbstractPart.php | 68 ++++++ src/PhpWord/Writer/RTF/Part/Document.php | 142 ++++++++++++ src/PhpWord/Writer/RTF/Part/Header.php | 227 +++++++++++++++++++ 7 files changed, 469 insertions(+), 199 deletions(-) create mode 100644 src/PhpWord/Writer/RTF/Part/AbstractPart.php create mode 100644 src/PhpWord/Writer/RTF/Part/Document.php create mode 100644 src/PhpWord/Writer/RTF/Part/Header.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9b172f..daaddb60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ This release marked the change of PHPWord license from LGPL 2.1 to LGPL 3; new r - ODT Reader: Ability to read standard and custom document properties - @ivanlanin - Word2007 Writer: Enable the missing custom document properties writer - @ivanlanin - Image: Enable "image float left" - @ivanlanin GH-244 +- RTF Writer: Ability to write document properties - @ivanlanin ### Bugfixes diff --git a/docs/intro.rst b/docs/intro.rst index 3c6277f5..c33e778f 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -61,7 +61,7 @@ Writers +---------------------------+----------------------+--------+-------+-------+--------+-------+ | Features | | DOCX | ODT | RTF | HTML | PDF | +===========================+======================+========+=======+=======+========+=======+ -| **Document Properties** | Standard | ✓ | ✓ | | | | +| **Document Properties** | Standard | ✓ | ✓ | ✓ | ✓ | ✓ | +---------------------------+----------------------+--------+-------+-------+--------+-------+ | | Custom | ✓ | ✓ | | | | +---------------------------+----------------------+--------+-------+-------+--------+-------+ diff --git a/docs/src/documentation.md b/docs/src/documentation.md index ffa3edc3..03c495ab 100644 --- a/docs/src/documentation.md +++ b/docs/src/documentation.md @@ -78,7 +78,7 @@ Below are the supported features for each file formats. | Features | | DOCX | ODT | RTF | HTML | PDF | |-------------------------|--------------------|------|-----|-----|------|-----| -| **Document Properties** | Standard | ✓ | ✓ | | | | +| **Document Properties** | Standard | ✓ | ✓ | ✓ | ✓ | | | | Custom | ✓ | ✓ | | | | | **Element Type** | Text | ✓ | ✓ | ✓ | ✓ | ✓ | | | Text Run | ✓ | ✓ | ✓ | ✓ | ✓ | diff --git a/src/PhpWord/Writer/RTF.php b/src/PhpWord/Writer/RTF.php index 315e6007..aaf412de 100644 --- a/src/PhpWord/Writer/RTF.php +++ b/src/PhpWord/Writer/RTF.php @@ -19,11 +19,6 @@ namespace PhpOffice\PhpWord\Writer; use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\PhpWord; -use PhpOffice\PhpWord\Settings; -use PhpOffice\PhpWord\Shared\Drawing; -use PhpOffice\PhpWord\Style; -use PhpOffice\PhpWord\Style\Font; -use PhpOffice\PhpWord\Writer\RTF\Element\Container; /** * RTF writer @@ -32,20 +27,6 @@ use PhpOffice\PhpWord\Writer\RTF\Element\Container; */ class RTF extends AbstractWriter implements WriterInterface { - /** - * Color register - * - * @var array - */ - private $colorTable; - - /** - * Font register - * - * @var array - */ - private $fontTable; - /** * Last paragraph style * @@ -60,6 +41,18 @@ class RTF extends AbstractWriter implements WriterInterface public function __construct(PhpWord $phpWord = null) { $this->setPhpWord($phpWord); + + $this->parts = array('Header', 'Document'); + foreach ($this->parts as $partName) { + $partClass = get_class($this) . '\\Part\\' . $partName; + if (class_exists($partClass)) { + /** @var \PhpOffice\PhpWord\Writer\RTF\Part\AbstractPart $part Type hint */ + $part = new $partClass(); + $part->setParentWriter($this); + $this->writerParts[strtolower($partName)] = $part; + } + } + } /** @@ -70,10 +63,17 @@ class RTF extends AbstractWriter implements WriterInterface */ public function save($filename = null) { + $content = ''; $filename = $this->getTempFile($filename); $hFile = fopen($filename, 'w'); if ($hFile !== false) { - fwrite($hFile, $this->writeDocument()); + $content .= '{'; + $content .= '\rtf1' . PHP_EOL; + $content .= $this->getWriterPart('Header')->write(); + $content .= $this->getWriterPart('Document')->write(); + $content .= '}'; + + fwrite($hFile, $content); fclose($hFile); } else { throw new Exception("Can't open file"); @@ -81,20 +81,20 @@ class RTF extends AbstractWriter implements WriterInterface $this->cleanupTempFile(); } - /** - * Get color table - */ - public function getColorTable() - { - return $this->colorTable; - } - /** * Get font table */ public function getFontTable() { - return $this->fontTable; + return $this->getWriterPart('Header')->getFontTable(); + } + + /** + * Get color table + */ + public function getColorTable() + { + return $this->getWriterPart('Header')->getColorTable(); } /** @@ -114,172 +114,4 @@ class RTF extends AbstractWriter implements WriterInterface { $this->lastParagraphStyle = $value; } - - /** - * Get all data - * - * @return string - */ - private function writeDocument() - { - $this->fontTable = $this->populateFontTable(); - $this->colorTable = $this->populateColorTable(); - - // Set the default character set - $content = '{\rtf1'; - $content .= '\ansi\ansicpg1252'; // Set the default font (the first one) - $content .= '\deff0'; // Set the default tab size (720 twips) - $content .= '\deftab720'; - $content .= PHP_EOL; - - // Set the font tbl group - $content .= '{\fonttbl'; - foreach ($this->fontTable as $idx => $font) { - $content .= '{\f' . $idx . '\fnil\fcharset0 ' . $font . ';}'; - } - $content .= '}' . PHP_EOL; - - // Set the color tbl group - $content .= '{\colortbl '; - foreach ($this->colorTable as $color) { - $arrColor = Drawing::htmlToRGB($color); - $content .= ';\red' . $arrColor[0] . '\green' . $arrColor[1] . '\blue' . $arrColor[2] . ''; - } - $content .= ';}' . PHP_EOL; - - $content .= '{\*\generator PhpWord;}' . PHP_EOL; // Set the generator - $content .= '\viewkind4'; // Set the view mode of the document - $content .= '\uc1'; // Set the numberof bytes that follows a unicode character - $content .= '\pard'; // Resets to default paragraph properties. - $content .= '\nowidctlpar'; // No widow/orphan control - $content .= '\lang1036'; // Applies a language to a text run (1036 : French (France)) - $content .= '\kerning1'; // Point size (in half-points) above which to kern character pairs - $content .= '\fs' . (Settings::getDefaultFontSize() * 2); // Set the font size in half-points - $content .= PHP_EOL; - - // Body - $content .= $this->writeContent(); - - $content .= '}'; - - return $content; - } - - /** - * Get content data - * - * @return string - */ - private function writeContent() - { - $content = ''; - - $sections = $this->getPhpWord()->getSections(); - foreach ($sections as $section) { - $writer = new Container($this, $section); - $content .= $writer->write(); - } - - return $content; - } - - /** - * Get all fonts - * - * @return array - */ - private function populateFontTable() - { - $phpWord = $this->getPhpWord(); - $fontTable = array(); - $fontTable[] = Settings::getDefaultFontName(); - - // Browse styles - $styles = Style::getStyles(); - if (count($styles) > 0) { - foreach ($styles as $style) { - // Font - if ($style instanceof Font) { - if (in_array($style->getName(), $fontTable) == false) { - $fontTable[] = $style->getName(); - } - } - } - } - - // Search all fonts used - $sections = $phpWord->getSections(); - $countSections = count($sections); - if ($countSections > 0) { - foreach ($sections as $section) { - $elements = $section->getElements(); - foreach ($elements as $element) { - if (method_exists($element, 'getFontStyle')) { - $fontStyle = $element->getFontStyle(); - if ($fontStyle instanceof Font) { - if (in_array($fontStyle->getName(), $fontTable) == false) { - $fontTable[] = $fontStyle->getName(); - } - } - } - } - } - } - - return $fontTable; - } - - /** - * Get all colors - * - * @return array - */ - private function populateColorTable() - { - $phpWord = $this->getPhpWord(); - $defaultFontColor = Settings::DEFAULT_FONT_COLOR; - $colorTable = array(); - - // Browse styles - $styles = Style::getStyles(); - if (count($styles) > 0) { - foreach ($styles as $style) { - // Font - if ($style instanceof Font) { - $color = $style->getColor(); - $fgcolor = $style->getFgColor(); - if (!in_array($color, $colorTable) && $color != $defaultFontColor && !empty($color)) { - $colorTable[] = $color; - } - if (!in_array($fgcolor, $colorTable) && $fgcolor != $defaultFontColor && !empty($fgcolor)) { - $colorTable[] = $fgcolor; - } - } - } - } - - // Search all fonts used - $sections = $phpWord->getSections(); - $countSections = count($sections); - if ($countSections > 0) { - foreach ($sections as $section) { - $elements = $section->getElements(); - foreach ($elements as $element) { - if (method_exists($element, 'getFontStyle')) { - $fontStyle = $element->getFontStyle(); - if ($fontStyle instanceof Font) { - if (in_array($fontStyle->getColor(), $colorTable) == false) { - $colorTable[] = $fontStyle->getColor(); - } - if (in_array($fontStyle->getFgColor(), $colorTable) == false) { - $colorTable[] = $fontStyle->getFgColor(); - } - } - } - } - } - } - - return $colorTable; - } } diff --git a/src/PhpWord/Writer/RTF/Part/AbstractPart.php b/src/PhpWord/Writer/RTF/Part/AbstractPart.php new file mode 100644 index 00000000..3a73a226 --- /dev/null +++ b/src/PhpWord/Writer/RTF/Part/AbstractPart.php @@ -0,0 +1,68 @@ +parentWriter = $writer; + } + + /** + * Get parent writer + * + * @return \PhpOffice\PhpWord\Writer\AbstractWriter + * @throws \PhpOffice\PhpWord\Exception\Exception + */ + public function getParentWriter() + { + if ($this->parentWriter !== null) { + return $this->parentWriter; + } else { + throw new Exception('No parent WriterInterface assigned.'); + } + } +} diff --git a/src/PhpWord/Writer/RTF/Part/Document.php b/src/PhpWord/Writer/RTF/Part/Document.php new file mode 100644 index 00000000..71565044 --- /dev/null +++ b/src/PhpWord/Writer/RTF/Part/Document.php @@ -0,0 +1,142 @@ +writeInfo(); + $content .= $this->writeFormatting(); + $content .= $this->writeSections(); + + return $content; + } + + /** + * Write document information + * + * @return string + */ + private function writeInfo() + { + $docProps = $this->getParentWriter()->getPhpWord()->getDocumentProperties(); + $properties = array('title', 'subject', 'category', 'keywords', 'comment', + 'author', 'operator', 'creatim', 'revtim', 'company', 'manager'); + $mapping = array('comment' => 'description', 'author' => 'creator', 'operator' => 'lastModifiedBy', + 'creatim' => 'created', 'revtim' => 'modified'); + $dateFields = array('creatim', 'revtim'); + + $content = ''; + + $content .= '{'; + $content .= '\info'; + foreach ($properties as $property) { + $method = 'get' . (array_key_exists($property, $mapping) ? $mapping[$property] : $property); + $value = $docProps->$method(); + $value = in_array($property, $dateFields) ? $this->getDateValue($value) : $value; + $content .= "{\\{$property} {$value}}"; + } + $content .= '}'; + $content .= PHP_EOL; + + return $content; + } + + /** + * Write document formatting properties + * + * @return string + */ + private function writeFormatting() + { + $content = ''; + + $content .= '\deftab720'; // Set the default tab size (720 twips) + $content .= '\viewkind1'; // Set the view mode of the document + + $content .= '\uc1'; // Set the numberof bytes that follows a unicode character + $content .= '\pard'; // Resets to default paragraph properties. + $content .= '\nowidctlpar'; // No widow/orphan control + $content .= '\lang1036'; // Applies a language to a text run (1036 : French (France)) + $content .= '\kerning1'; // Point size (in half-points) above which to kern character pairs + $content .= '\fs' . (Settings::getDefaultFontSize() * 2); // Set the font size in half-points + $content .= PHP_EOL; + + return $content; + } + + /** + * Write sections + * + * @return string + */ + private function writeSections() + { + $content = ''; + + $sections = $this->getParentWriter()->getPhpWord()->getSections(); + foreach ($sections as $section) { + $writer = new Container($this->getParentWriter(), $section); + $content .= $writer->write(); + } + + return $content; + } + + /** + * Get date value + * + * The format of date value is `\yr?\mo?\dy?\hr?\min?\sec?` + * + * @param int $value + * @return string + */ + private function getDateValue($value) + { + $dateParts = array( + 'Y' => 'yr', + 'm' => 'mo', + 'd' => 'dy', + 'H' => 'hr', + 'i' => 'min', + 's' => 'sec', + ); + $result = ''; + foreach ($dateParts as $dateFormat => $controlWord) { + $result .= '\\' . $controlWord . date($dateFormat, $value); + } + + return $result; + } +} diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php new file mode 100644 index 00000000..c401b500 --- /dev/null +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -0,0 +1,227 @@ +fontTable; + } + + /** + * Get color table + */ + public function getColorTable() + { + return $this->colorTable; + } + + /** + * Write part + * + * @return string + */ + public function write() + { + $this->registerFont(); + + $content = ''; + + $content .= $this->writeCharset(); + $content .= $this->writeDefaults(); + $content .= $this->writeFontTable(); + $content .= $this->writeColorTable(); + $content .= $this->writeGenerator(); + $content .= PHP_EOL; + + return $content; + } + + /** + * Write character set + * + * @return string + */ + private function writeCharset() + { + $content = ''; + + $content .= '\ansi'; + $content .= '\ansicpg1252'; + $content .= PHP_EOL; + + return $content; + } + + /** + * Write header defaults + * + * @return string + */ + private function writeDefaults() + { + $content = ''; + + $content .= '\deff0'; + $content .= PHP_EOL; + + return $content; + } + + /** + * Write font table + * + * @return string + */ + private function writeFontTable() + { + $content = ''; + + $content .= '{'; + $content .= '\fonttbl'; + foreach ($this->fontTable as $index => $font) { + $content .= "{\\f{$index}\\fnil\\fcharset0{$font};}"; + } + $content .= '}'; + $content .= PHP_EOL; + + return $content; + } + + /** + * Write color table + * + * @return string + */ + private function writeColorTable() + { + $content = ''; + + $content .= '{'; + $content .= '\colortbl'; + foreach ($this->colorTable as $color) { + list($red, $green, $blue) = Drawing::htmlToRGB($color); + $content .= ";\\red{$red}\\green{$green}\\blue{$blue}"; + } + $content .= '}'; + $content .= PHP_EOL; + + return $content; + } + + /** + * Write + * + * @return string + */ + private function writeGenerator() + { + $content = ''; + + $content .= '{\*\generator PhpWord;}'; // Set the generator + $content .= PHP_EOL; + + return $content; + } + + /** + * Register all fonts and colors in both named and inline styles to appropriate header table + */ + private function registerFont() + { + $phpWord = $this->getParentWriter()->getPhpWord(); + $this->fontTable[] = Settings::getDefaultFontName(); + + // Search named styles + $styles = Style::getStyles(); + foreach ($styles as $style) { + $this->registerFontItems($style); + } + + // Search inline styles + $sections = $phpWord->getSections(); + foreach ($sections as $section) { + $elements = $section->getElements(); + foreach ($elements as $element) { + if (method_exists($element, 'getFontStyle')) { + $style = $element->getFontStyle(); + $this->registerFontItems($style); + } + } + } + } + + /** + * Register fonts and colors + * + * @param \PhpOffice\PhpWord\Style\AbstractStyle $style + */ + private function registerFontItems($style) + { + $defaultFont = Settings::getDefaultFontName(); + $defaultColor = Settings::DEFAULT_FONT_COLOR; + + if ($style instanceof Font) { + $this->registerFontItem($this->fontTable, $style->getName(), $defaultFont); + $this->registerFontItem($this->colorTable, $style->getColor(), $defaultColor); + $this->registerFontItem($this->colorTable, $style->getFgColor(), $defaultColor); + } + } + + /** + * Register individual font and color + * + * @param array $table + * @param string $value + * @param string $default + */ + private function registerFontItem(&$table, $value, $default) + { + if (in_array($value, $table) === false && $value !== null && $value != $default) { + $table[] = $value; + } + } +} From e00b551aa2a0054ef917134acf0971958973b3e2 Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Fri, 23 May 2014 12:48:34 +0700 Subject: [PATCH 14/21] Breakdown HTML Writer to head and body parts and more cyclomatic complexity reductions --- src/PhpWord/Autoloader.php | 1 + src/PhpWord/Reader/Word2007/Document.php | 12 +- src/PhpWord/Shared/String.php | 33 +++- src/PhpWord/Writer/HTML.php | 175 ++---------------- src/PhpWord/Writer/HTML/Element/Image.php | 7 +- src/PhpWord/Writer/HTML/Part/AbstractPart.php | 68 +++++++ src/PhpWord/Writer/HTML/Part/Body.php | 89 +++++++++ src/PhpWord/Writer/HTML/Part/Head.php | 129 +++++++++++++ src/PhpWord/Writer/PDF/AbstractRenderer.php | 1 + src/PhpWord/Writer/RTF.php | 4 +- src/PhpWord/Writer/RTF/Part/AbstractPart.php | 43 +---- .../Writer/Word2007/Element/Container.php | 74 +++++--- 12 files changed, 391 insertions(+), 245 deletions(-) create mode 100644 src/PhpWord/Writer/HTML/Part/AbstractPart.php create mode 100644 src/PhpWord/Writer/HTML/Part/Body.php create mode 100644 src/PhpWord/Writer/HTML/Part/Head.php diff --git a/src/PhpWord/Autoloader.php b/src/PhpWord/Autoloader.php index 6acfff21..c60ae9a6 100644 --- a/src/PhpWord/Autoloader.php +++ b/src/PhpWord/Autoloader.php @@ -46,6 +46,7 @@ class Autoloader $file = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, $prefixLength)); $file = realpath(__DIR__ . (empty($file) ? '' : DIRECTORY_SEPARATOR) . $file . '.php'); if (file_exists($file)) { + /** @noinspection PhpIncludeInspection Dynamic includes */ require_once $file; } } diff --git a/src/PhpWord/Reader/Word2007/Document.php b/src/PhpWord/Reader/Word2007/Document.php index 224c895e..80d2dde2 100644 --- a/src/PhpWord/Reader/Word2007/Document.php +++ b/src/PhpWord/Reader/Word2007/Document.php @@ -158,7 +158,9 @@ class Document extends AbstractPart // Section properties if ($xmlReader->elementExists('w:pPr/w:sectPr', $node)) { $sectPrNode = $xmlReader->getElement('w:pPr/w:sectPr', $node); - $this->readWSectPrNode($xmlReader, $sectPrNode, $section); + if ($sectPrNode !== null) { + $this->readWSectPrNode($xmlReader, $sectPrNode, $section); + } $section = $this->phpWord->addSection(); } } @@ -172,10 +174,8 @@ class Document extends AbstractPart */ private function readWSectPrNode(XMLReader $xmlReader, \DOMElement $node, Section &$section) { - if ($node !== null) { - $settings = $this->readSectionStyle($xmlReader, $node); - $section->setSettings($settings); - $this->readHeaderFooter($settings, $section); - } + $settings = $this->readSectionStyle($xmlReader, $node); + $section->setSettings($settings); + $this->readHeaderFooter($settings, $section); } } diff --git a/src/PhpWord/Shared/String.php b/src/PhpWord/Shared/String.php index 259e904c..99b8cca3 100644 --- a/src/PhpWord/Shared/String.php +++ b/src/PhpWord/Shared/String.php @@ -86,14 +86,28 @@ class String } /** - * Returns unicode from UTF8 text + * Returns unicode array from UTF8 text + * + * The function is splitted to reduce cyclomatic complexity * * @param string $text UTF8 text * @return string Unicode text * @since 0.11.0 - * @link http://www.randomchaos.com/documents/?source=php_and_unicode */ public static function toUnicode($text) + { + return self::unicodeToEntities(self::utf8ToUnicode($text)); + } + + /** + * Returns unicode from UTF8 text + * + * @param string $text UTF8 text + * @return array + * @since 0.11.0 + * @link http://www.randomchaos.com/documents/?source=php_and_unicode + */ + private static function utf8ToUnicode($text) { $unicode = array(); $values = array(); @@ -122,8 +136,21 @@ class String } } - // Converts text with utf8 characters into rtf utf8 entites preserving ascii + return $unicode; + } + + /** + * Returns entites from unicode array + * + * @param array $unicode + * @return string + * @since 0.11.0 + * @link http://www.randomchaos.com/documents/?source=php_and_unicode + */ + private static function unicodeToEntities($unicode) + { $entities = ''; + foreach ($unicode as $value) { if ($value != 65279) { $entities .= $value > 127 ? '\uc0{\u' . $value . '}' : chr($value); diff --git a/src/PhpWord/Writer/HTML.php b/src/PhpWord/Writer/HTML.php index 98cab55e..88e7658d 100644 --- a/src/PhpWord/Writer/HTML.php +++ b/src/PhpWord/Writer/HTML.php @@ -19,15 +19,6 @@ namespace PhpOffice\PhpWord\Writer; use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\PhpWord; -use PhpOffice\PhpWord\Settings; -use PhpOffice\PhpWord\Style; -use PhpOffice\PhpWord\Style\Font; -use PhpOffice\PhpWord\Style\Paragraph; -use PhpOffice\PhpWord\Writer\HTML\Element\Container; -use PhpOffice\PhpWord\Writer\HTML\Element\TextRun as TextRunWriter; -use PhpOffice\PhpWord\Writer\HTML\Style\Font as FontStyleWriter; -use PhpOffice\PhpWord\Writer\HTML\Style\Generic as GenericStyleWriter; -use PhpOffice\PhpWord\Writer\HTML\Style\Paragraph as ParagraphStyleWriter; /** * HTML writer @@ -57,6 +48,17 @@ class HTML extends AbstractWriter implements WriterInterface public function __construct(PhpWord $phpWord = null) { $this->setPhpWord($phpWord); + + $this->parts = array('Head', 'Body'); + foreach ($this->parts as $partName) { + $partClass = 'PhpOffice\\PhpWord\\Writer\\HTML\\Part\\' . $partName; + if (class_exists($partClass)) { + /** @var \PhpOffice\PhpWord\Writer\HTML\Part\AbstractPart $part Type hint */ + $part = new $partClass(); + $part->setParentWriter($this); + $this->writerParts[strtolower($partName)] = $part; + } + } } /** @@ -89,164 +91,13 @@ class HTML extends AbstractWriter implements WriterInterface $content .= '' . PHP_EOL; $content .= '' . PHP_EOL; $content .= '' . PHP_EOL; - $content .= '' . PHP_EOL; - $content .= $this->writeHead(); - $content .= '' . PHP_EOL; - $content .= '' . PHP_EOL; - $content .= $this->writeBody(); - $content .= $this->writeNotes(); - $content .= '' . PHP_EOL; + $content .= $this->getWriterPart('Head')->write(); + $content .= $this->getWriterPart('Body')->write(); $content .= '' . PHP_EOL; return $content; } - /** - * Generate HTML header - * - * @return string - */ - private function writeHead() - { - $phpWord = $this->getPhpWord(); - $properties = $phpWord->getDocumentProperties(); - $propertiesMapping = array( - 'creator' => 'author', - 'title' => '', - 'description' => '', - 'subject' => '', - 'keywords' => '', - 'category' => '', - 'company' => '', - 'manager' => '' - ); - $title = $properties->getTitle(); - $title = ($title != '') ? $title : 'PHPWord'; - - $content = ''; - $content .= '' . PHP_EOL; - $content .= '' . htmlspecialchars($title) . '' . PHP_EOL; - foreach ($propertiesMapping as $key => $value) { - $value = ($value == '') ? $key : $value; - $method = "get" . $key; - if ($properties->$method() != '') { - $content .= '' . PHP_EOL; - } - } - $content .= $this->writeStyles(); - - return $content; - } - - /** - * Get content - * - * @return string - */ - private function writeBody() - { - $phpWord = $this->getPhpWord(); - $content = ''; - - $sections = $phpWord->getSections(); - $countSections = count($sections); - - if ($countSections > 0) { - foreach ($sections as $section) { - $writer = new Container($this, $section); - $content .= $writer->write(); - } - } - - return $content; - } - - /** - * Get styles - * - * @return string - */ - private function writeStyles() - { - $css = '' . PHP_EOL; - - return $css; - } - - /** - * Write footnote/endnote contents as textruns - */ - private function writeNotes() - { - $phpWord = $this->getPhpWord(); - $content = PHP_EOL; - - if (!empty($this->notes)) { - $content .= "
" . PHP_EOL; - foreach ($this->notes as $noteId => $noteMark) { - list($noteType, $noteTypeId) = explode('-', $noteMark); - $method = 'get' . ($noteType == 'endnote' ? 'Endnotes' : 'Footnotes'); - $collection = $phpWord->$method()->getItems(); - - if (array_key_exists($noteTypeId, $collection)) { - $element = $collection[$noteTypeId]; - $noteAnchor = ""; - $noteAnchor .= "{$noteId}"; - - $writer = new TextRunWriter($this, $element); - $writer->setOpeningText($noteAnchor); - $content .= $writer->write(); - } - } - } - - return $content; - } - /** * Get is PDF * diff --git a/src/PhpWord/Writer/HTML/Element/Image.php b/src/PhpWord/Writer/HTML/Element/Image.php index dafc1c38..698b7e86 100644 --- a/src/PhpWord/Writer/HTML/Element/Image.php +++ b/src/PhpWord/Writer/HTML/Element/Image.php @@ -88,7 +88,7 @@ class Image extends Text } else { $actualSource = $source; } - if (is_null($actualSource)) { + if ($actualSource === null) { return null; } @@ -100,12 +100,13 @@ class Image extends Text $imageBinary = ob_get_contents(); ob_end_clean(); } else { - if ($fileHandle = fopen($actualSource, 'rb', false)) { + $fileHandle = fopen($actualSource, 'rb', false); + if ($fileHandle !== false) { $imageBinary = fread($fileHandle, filesize($actualSource)); fclose($fileHandle); } } - if (!is_null($imageBinary)) { + if ($imageBinary !== null) { $base64 = chunk_split(base64_encode($imageBinary)); $imageData = 'data:' . $imageType . ';base64,' . $base64; } diff --git a/src/PhpWord/Writer/HTML/Part/AbstractPart.php b/src/PhpWord/Writer/HTML/Part/AbstractPart.php new file mode 100644 index 00000000..319aa20f --- /dev/null +++ b/src/PhpWord/Writer/HTML/Part/AbstractPart.php @@ -0,0 +1,68 @@ +parentWriter = $writer; + } + + /** + * Get parent writer + * + * @return \PhpOffice\PhpWord\Writer\AbstractWriter + * @throws \PhpOffice\PhpWord\Exception\Exception + */ + public function getParentWriter() + { + if ($this->parentWriter !== null) { + return $this->parentWriter; + } else { + throw new Exception('No parent WriterInterface assigned.'); + } + } +} diff --git a/src/PhpWord/Writer/HTML/Part/Body.php b/src/PhpWord/Writer/HTML/Part/Body.php new file mode 100644 index 00000000..4fd61a83 --- /dev/null +++ b/src/PhpWord/Writer/HTML/Part/Body.php @@ -0,0 +1,89 @@ +getParentWriter()->getPhpWord(); + + $content = ''; + + $content .= '' . PHP_EOL; + $sections = $phpWord->getSections(); + foreach ($sections as $section) { + $writer = new Container($this->getParentWriter(), $section); + $content .= $writer->write(); + } + + $content .= $this->writeNotes(); + $content .= '' . PHP_EOL; + + return $content; + } + + /** + * Write footnote/endnote contents as textruns + * + * @return string + */ + private function writeNotes() + { + /** @var \PhpOffice\PhpWord\Writer\HTML $parentWriter Type hint */ + $parentWriter = $this->getParentWriter(); + $phpWord = $parentWriter->getPhpWord(); + $notes = $parentWriter->getNotes(); + + $content = ''; + + if (!empty($notes)) { + $content .= "
" . PHP_EOL; + foreach ($notes as $noteId => $noteMark) { + list($noteType, $noteTypeId) = explode('-', $noteMark); + $method = 'get' . ($noteType == 'endnote' ? 'Endnotes' : 'Footnotes'); + $collection = $phpWord->$method()->getItems(); + + if (array_key_exists($noteTypeId, $collection)) { + $element = $collection[$noteTypeId]; + $noteAnchor = ""; + $noteAnchor .= "{$noteId}"; + + $writer = new TextRunWriter($this->getParentWriter(), $element); + $writer->setOpeningText($noteAnchor); + $content .= $writer->write(); + } + } + } + + return $content; + } +} diff --git a/src/PhpWord/Writer/HTML/Part/Head.php b/src/PhpWord/Writer/HTML/Part/Head.php new file mode 100644 index 00000000..389d568b --- /dev/null +++ b/src/PhpWord/Writer/HTML/Part/Head.php @@ -0,0 +1,129 @@ +getParentWriter()->getPhpWord()->getDocumentProperties(); + $propertiesMapping = array( + 'creator' => 'author', + 'title' => '', + 'description' => '', + 'subject' => '', + 'keywords' => '', + 'category' => '', + 'company' => '', + 'manager' => '' + ); + $title = $docProps->getTitle(); + $title = ($title != '') ? $title : 'PHPWord'; + + $content = ''; + + $content .= '' . PHP_EOL; + $content .= '' . PHP_EOL; + $content .= '' . htmlspecialchars($title) . '' . PHP_EOL; + foreach ($propertiesMapping as $key => $value) { + $value = ($value == '') ? $key : $value; + $method = "get" . $key; + if ($docProps->$method() != '') { + $content .= '' . PHP_EOL; + } + } + $content .= $this->writeStyles(); + $content .= '' . PHP_EOL; + + return $content; + } + /** + * Get styles + * + * @return string + */ + private function writeStyles() + { + $css = '' . PHP_EOL; + + return $css; + } +} diff --git a/src/PhpWord/Writer/PDF/AbstractRenderer.php b/src/PhpWord/Writer/PDF/AbstractRenderer.php index f7266d1a..83b02251 100644 --- a/src/PhpWord/Writer/PDF/AbstractRenderer.php +++ b/src/PhpWord/Writer/PDF/AbstractRenderer.php @@ -84,6 +84,7 @@ abstract class AbstractRenderer extends HTML parent::__construct($phpWord); $includeFile = Settings::getPdfRendererPath() . '/' . $this->includeFile; if (file_exists($includeFile)) { + /** @noinspection PhpIncludeInspection Dynamic includes */ require_once $includeFile; } else { throw new Exception('Unable to load PDF Rendering library'); diff --git a/src/PhpWord/Writer/RTF.php b/src/PhpWord/Writer/RTF.php index aaf412de..ef94b264 100644 --- a/src/PhpWord/Writer/RTF.php +++ b/src/PhpWord/Writer/RTF.php @@ -35,7 +35,8 @@ class RTF extends AbstractWriter implements WriterInterface private $lastParagraphStyle; /** - * Create new RTF writer + * Create new instance + * * @param \PhpOffice\PhpWord\PhpWord $phpWord */ public function __construct(PhpWord $phpWord = null) @@ -52,7 +53,6 @@ class RTF extends AbstractWriter implements WriterInterface $this->writerParts[strtolower($partName)] = $part; } } - } /** diff --git a/src/PhpWord/Writer/RTF/Part/AbstractPart.php b/src/PhpWord/Writer/RTF/Part/AbstractPart.php index 3a73a226..b10e5654 100644 --- a/src/PhpWord/Writer/RTF/Part/AbstractPart.php +++ b/src/PhpWord/Writer/RTF/Part/AbstractPart.php @@ -17,52 +17,13 @@ namespace PhpOffice\PhpWord\Writer\RTF\Part; -use PhpOffice\PhpWord\Exception\Exception; -use PhpOffice\PhpWord\Writer\AbstractWriter; +use PhpOffice\PhpWord\Writer\HTML\Part\AbstractPart as HTMLAbstractPart; /** * Abstract RTF part writer * * @since 0.11.0 */ -abstract class AbstractPart +abstract class AbstractPart extends HTMLAbstractPart { - /** - * Parent writer - * - * @var \PhpOffice\PhpWord\Writer\AbstractWriter - */ - private $parentWriter; - - /** - * Write part - * - * @return string - */ - abstract public function write(); - - /** - * Set parent writer - * - * @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer - */ - public function setParentWriter(AbstractWriter $writer = null) - { - $this->parentWriter = $writer; - } - - /** - * Get parent writer - * - * @return \PhpOffice\PhpWord\Writer\AbstractWriter - * @throws \PhpOffice\PhpWord\Exception\Exception - */ - public function getParentWriter() - { - if ($this->parentWriter !== null) { - return $this->parentWriter; - } else { - throw new Exception('No parent WriterInterface assigned.'); - } - } } diff --git a/src/PhpWord/Writer/Word2007/Element/Container.php b/src/PhpWord/Writer/Word2007/Element/Container.php index 7d1c8104..3dad824d 100644 --- a/src/PhpWord/Writer/Word2007/Element/Container.php +++ b/src/PhpWord/Writer/Word2007/Element/Container.php @@ -17,8 +17,10 @@ namespace PhpOffice\PhpWord\Writer\Word2007\Element; +use PhpOffice\PhpWord\Element\AbstractElement as Element; use PhpOffice\PhpWord\Element\AbstractContainer as ContainerElement; use PhpOffice\PhpWord\Element\TextBreak as TextBreakElement; +use PhpOffice\PhpWord\Shared\XMLWriter; /** * Container element writer (section, textrun, header, footnote, cell, etc.) @@ -51,36 +53,52 @@ class Container extends AbstractElement $elements = $container->getElements(); $elementClass = ''; foreach ($elements as $element) { - $elementClass = substr(get_class($element), strrpos(get_class($element), '\\') + 1); - $writerClass = $this->namespace . '\\' . $elementClass; - - // Check it's a page break. No need to write it, instead, flag containers' pageBreakBefore - // to be assigned to the next element - if ($elementClass == 'PageBreak') { - $this->setPageBreakBefore(true); - continue; - } - if (class_exists($writerClass)) { - // Get container's page break before and reset it - $pageBreakBefore = $this->hasPageBreakBefore(); - $this->setPageBreakBefore(false); - - /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $writer Type hint */ - $writer = new $writerClass($xmlWriter, $element, $withoutP); - $writer->setPageBreakBefore($pageBreakBefore); - $writer->write(); - } + $elementClass = $this->writeElement($xmlWriter, $element, $withoutP); } - // Special case for Cell: They have to contain a w:p element at the end. The $elementClass contains - // the last element name. If it's empty string or Table, the last element is not w:p - if ($containerClass == 'Cell') { - if ($elementClass == '' || $elementClass == 'Table') { - $writerClass = $this->namespace . '\\TextBreak'; - /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $writer Type hint */ - $writer = new $writerClass($xmlWriter, new TextBreakElement(), $withoutP); - $writer->write(); - } + // Special case for Cell: They have to contain a w:p element at the end. + // The $elementClass contains the last element name. If it's empty string + // or Table, the last element is not w:p + $writeLastTextBreak = ($containerClass == 'Cell') && ($elementClass == '' || $elementClass == 'Table'); + if ($writeLastTextBreak) { + $writerClass = $this->namespace . '\\TextBreak'; + /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $writer Type hint */ + $writer = new $writerClass($xmlWriter, new TextBreakElement(), $withoutP); + $writer->write(); } } + + /** + * Write individual element + * + * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\AbstractElement $element + * @param bool $withoutP + * @return string + */ + private function writeElement(XMLWriter $xmlWriter, Element $element, $withoutP) + { + $elementClass = substr(get_class($element), strrpos(get_class($element), '\\') + 1); + $writerClass = $this->namespace . '\\' . $elementClass; + + // Check it's a page break. No need to write it, instead, flag containers' + // pageBreakBefore to be assigned to the next element + if ($elementClass == 'PageBreak') { + $this->setPageBreakBefore(true); + return $elementClass; + } + + if (class_exists($writerClass)) { + // Get container's page break before and reset it + $pageBreakBefore = $this->hasPageBreakBefore(); + $this->setPageBreakBefore(false); + + /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $writer Type hint */ + $writer = new $writerClass($xmlWriter, $element, $withoutP); + $writer->setPageBreakBefore($pageBreakBefore); + $writer->write(); + } + + return $elementClass; + } } From a65c3c3cf1d2628502fbf5f766405466f2b20f68 Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Fri, 23 May 2014 18:32:56 +0700 Subject: [PATCH 15/21] RTF Writer: Ability to write image --- CHANGELOG.md | 1 + docs/intro.rst | 2 +- docs/src/documentation.md | 2 +- src/PhpWord/Element/AbstractContainer.php | 1 + src/PhpWord/Element/Image.php | 59 +++++++++++++++++++++ src/PhpWord/Style/Table.php | 2 +- src/PhpWord/Writer/HTML/Element/Image.php | 63 ++--------------------- src/PhpWord/Writer/HTML/Part/Head.php | 1 - src/PhpWord/Writer/RTF/Element/Image.php | 57 ++++++++++++++++++++ 9 files changed, 124 insertions(+), 64 deletions(-) create mode 100644 src/PhpWord/Writer/RTF/Element/Image.php diff --git a/CHANGELOG.md b/CHANGELOG.md index daaddb60..68ea9d2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ This release marked the change of PHPWord license from LGPL 2.1 to LGPL 3; new r - Word2007 Writer: Enable the missing custom document properties writer - @ivanlanin - Image: Enable "image float left" - @ivanlanin GH-244 - RTF Writer: Ability to write document properties - @ivanlanin +- RTF Writer: Ability to write image - @ivanlanin ### Bugfixes diff --git a/docs/intro.rst b/docs/intro.rst index 55a0764c..a64fb2ad 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -83,7 +83,7 @@ Writers +---------------------------+----------------------+--------+-------+-------+--------+-------+ | | Table | ✓ | ✓ | | ✓ | ✓ | +---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Image | ✓ | ✓ | | ✓ | | +| | Image | ✓ | ✓ | ✓ | ✓ | | +---------------------------+----------------------+--------+-------+-------+--------+-------+ | | Object | ✓ | | | | | +---------------------------+----------------------+--------+-------+-------+--------+-------+ diff --git a/docs/src/documentation.md b/docs/src/documentation.md index dce1f5db..abb8777e 100644 --- a/docs/src/documentation.md +++ b/docs/src/documentation.md @@ -89,7 +89,7 @@ Below are the supported features for each file formats. | | Page Break | ✓ | | ✓ | | | | | List | ✓ | | | | | | | Table | ✓ | ✓ | | ✓ | ✓ | -| | Image | ✓ | ✓ | | ✓ | | +| | Image | ✓ | ✓ | ✓ | ✓ | | | | Object | ✓ | | | | | | | Watermark | ✓ | | | | | | | Table of Contents | ✓ | | | | | diff --git a/src/PhpWord/Element/AbstractContainer.php b/src/PhpWord/Element/AbstractContainer.php index f469686d..06a94489 100644 --- a/src/PhpWord/Element/AbstractContainer.php +++ b/src/PhpWord/Element/AbstractContainer.php @@ -83,6 +83,7 @@ abstract class AbstractContainer extends AbstractElement $mediaContainer = $this->getMediaContainer(); if (in_array($elementName, array('Link', 'Image', 'Object'))) { if ($elementName == 'Image') { + /** @var \PhpOffice\PhpWord\Element\Image $element Type hint */ $rId = Media::addElement($mediaContainer, strtolower($elementName), $args[1], $element); } else { $rId = Media::addElement($mediaContainer, strtolower($elementName), $args[1]); diff --git a/src/PhpWord/Element/Image.php b/src/PhpWord/Element/Image.php index 8e30b78c..c38fda2c 100644 --- a/src/PhpWord/Element/Image.php +++ b/src/PhpWord/Element/Image.php @@ -279,6 +279,65 @@ class Image extends AbstractElement $this->mediaIndex = $value; } + /** + * Get image string data + * + * @param bool $base64 + * @return string|null + */ + public function getImageStringData($base64 = false) + { + $source = $this->source; + $actualSource = null; + $imageBinary = null; + $imageData = null; + + // Get actual source from archive image or other source + // Return null if not found + if ($this->sourceType == self::SOURCE_ARCHIVE) { + $source = substr($source, 6); + list($zipFilename, $imageFilename) = explode('#', $source); + + $zip = new ZipArchive(); + if ($zip->open($zipFilename) !== false) { + if ($zip->locateName($imageFilename)) { + $zip->extractTo(sys_get_temp_dir(), $imageFilename); + $actualSource = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $imageFilename; + } + } + $zip->close(); + } else { + $actualSource = $source; + } + if ($actualSource === null) { + return null; + } + + // Read image binary data and convert to hex + if ($this->sourceType == self::SOURCE_GD) { + $imageResource = call_user_func($this->imageCreateFunc, $actualSource); + ob_start(); + call_user_func($this->imageFunc, $imageResource); + $imageBinary = ob_get_contents(); + ob_end_clean(); + } else { + $fileHandle = fopen($actualSource, 'rb', false); + if ($fileHandle !== false) { + $imageBinary = fread($fileHandle, filesize($actualSource)); + fclose($fileHandle); + } + } + if ($imageBinary !== null) { + if ($base64) { + $imageData = chunk_split(base64_encode($imageBinary)); + } else { + $imageData = chunk_split(bin2hex($imageBinary)); + } + } + + return $imageData; + } + /** * Check memory image, supported type, image functions, and proportional width/height * diff --git a/src/PhpWord/Style/Table.php b/src/PhpWord/Style/Table.php index 68b53463..90e8282f 100644 --- a/src/PhpWord/Style/Table.php +++ b/src/PhpWord/Style/Table.php @@ -430,7 +430,7 @@ class Table extends Border /** * Get cell margin * - * @return integer[] + * @return int[] */ public function getCellMargin() { diff --git a/src/PhpWord/Writer/HTML/Element/Image.php b/src/PhpWord/Writer/HTML/Element/Image.php index 698b7e86..ab78990b 100644 --- a/src/PhpWord/Writer/HTML/Element/Image.php +++ b/src/PhpWord/Writer/HTML/Element/Image.php @@ -18,7 +18,6 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; use PhpOffice\PhpWord\Element\Image as ImageElement; -use PhpOffice\PhpWord\Shared\ZipArchive; use PhpOffice\PhpWord\Writer\HTML\Style\Image as ImageStyleWriter; /** @@ -43,10 +42,11 @@ class Image extends Text $content = ''; if (!$parentWriter->isPdf()) { - $imageData = $this->getBase64ImageData($this->element); - if (!is_null($imageData)) { + $imageData = $this->element->getImageStringData(true); + if ($imageData !== null) { $styleWriter = new ImageStyleWriter($this->element->getStyle()); $style = $styleWriter->write(); + $imageData = 'data:' . $this->element->getImageType() . ';base64,' . $imageData; $content .= $this->writeOpening(); $content .= ""; @@ -56,61 +56,4 @@ class Image extends Text return $content; } - - /** - * Get Base64 image data - * - * @param \PhpOffice\PhpWord\Element\Image $element - * @return string|null - */ - private function getBase64ImageData(ImageElement $element) - { - $source = $element->getSource(); - $imageType = $element->getImageType(); - $imageData = null; - $imageBinary = null; - $actualSource = null; - - // Get actual source from archive image or other source - // Return null if not found - if ($element->getSourceType() == ImageElement::SOURCE_ARCHIVE) { - $source = substr($source, 6); - list($zipFilename, $imageFilename) = explode('#', $source); - - $zip = new ZipArchive(); - if ($zip->open($zipFilename) !== false) { - if ($zip->locateName($imageFilename)) { - $zip->extractTo($this->parentWriter->getTempDir(), $imageFilename); - $actualSource = $this->parentWriter->getTempDir() . DIRECTORY_SEPARATOR . $imageFilename; - } - } - $zip->close(); - } else { - $actualSource = $source; - } - if ($actualSource === null) { - return null; - } - - // Read image binary data and convert into Base64 - if ($element->getSourceType() == ImageElement::SOURCE_GD) { - $imageResource = call_user_func($element->getImageCreateFunction(), $actualSource); - ob_start(); - call_user_func($element->getImageFunction(), $imageResource); - $imageBinary = ob_get_contents(); - ob_end_clean(); - } else { - $fileHandle = fopen($actualSource, 'rb', false); - if ($fileHandle !== false) { - $imageBinary = fread($fileHandle, filesize($actualSource)); - fclose($fileHandle); - } - } - if ($imageBinary !== null) { - $base64 = chunk_split(base64_encode($imageBinary)); - $imageData = 'data:' . $imageType . ';base64,' . $base64; - } - - return $imageData; - } } diff --git a/src/PhpWord/Writer/HTML/Part/Head.php b/src/PhpWord/Writer/HTML/Part/Head.php index 389d568b..edbc8dcf 100644 --- a/src/PhpWord/Writer/HTML/Part/Head.php +++ b/src/PhpWord/Writer/HTML/Part/Head.php @@ -32,7 +32,6 @@ use PhpOffice\PhpWord\Writer\HTML\Style\Paragraph as ParagraphStyleWriter; */ class Head extends AbstractPart { - /** * Write part * diff --git a/src/PhpWord/Writer/RTF/Element/Image.php b/src/PhpWord/Writer/RTF/Element/Image.php new file mode 100644 index 00000000..1daae2a0 --- /dev/null +++ b/src/PhpWord/Writer/RTF/Element/Image.php @@ -0,0 +1,57 @@ +element instanceof ImageElement) { + return ''; + } + + $this->getStyles(); + $style = $this->element->getStyle(); + + $content = ''; + $content .= $this->writeOpening(); + $content .= '{\*\shppict {\pict'; + $content .= '\pngblip\picscalex100\picscaley100'; + $content .= '\picwgoal' . round(Font::pixelSizeToTwips($style->getWidth())); + $content .= '\pichgoal' . round(Font::pixelSizeToTwips($style->getHeight())); + $content .= PHP_EOL; + $content .= $this->element->getImageStringData(); + $content .= '}}'; + $content .= $this->writeClosing(); + + return $content; + } +} From 991016a48b73d566dce6bc6982c81a2378bbe1fb Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Sat, 24 May 2014 00:11:06 +0700 Subject: [PATCH 16/21] Additional writer test --- src/PhpWord/Shared/String.php | 4 ++-- src/PhpWord/Writer/ODText/Part/Content.php | 10 ++++++---- tests/PhpWord/Tests/Writer/HTMLTest.php | 4 ++-- tests/PhpWord/Tests/Writer/ODText/Part/ContentTest.php | 8 +++++--- tests/PhpWord/Tests/Writer/ODText/StyleTest.php | 2 +- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/PhpWord/Shared/String.php b/src/PhpWord/Shared/String.php index 99b8cca3..1e4504cc 100644 --- a/src/PhpWord/Shared/String.php +++ b/src/PhpWord/Shared/String.php @@ -86,7 +86,7 @@ class String } /** - * Returns unicode array from UTF8 text + * Returns unicode from UTF8 text * * The function is splitted to reduce cyclomatic complexity * @@ -100,7 +100,7 @@ class String } /** - * Returns unicode from UTF8 text + * Returns unicode array from UTF8 text * * @param string $text UTF8 text * @return array diff --git a/src/PhpWord/Writer/ODText/Part/Content.php b/src/PhpWord/Writer/ODText/Part/Content.php index 900edfdd..f16937a0 100644 --- a/src/PhpWord/Writer/ODText/Part/Content.php +++ b/src/PhpWord/Writer/ODText/Part/Content.php @@ -115,12 +115,14 @@ class Content extends AbstractPart $xmlWriter->startElement('office:automatic-styles'); $this->writeTextStyles($xmlWriter); - foreach ($this->autoStyles as $element => $style) { + foreach ($this->autoStyles as $element => $styles) { $writerClass = 'PhpOffice\\PhpWord\\Writer\\ODText\\Style\\' . $element; + foreach ($styles as $style) { - /** @var \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle $styleWriter Type hint */ - $styleWriter = new $writerClass($xmlWriter, $style); - $styleWriter->write(); + /** @var \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle $styleWriter Type hint */ + $styleWriter = new $writerClass($xmlWriter, $style); + $styleWriter->write(); + } } $xmlWriter->endElement(); // office:automatic-styles diff --git a/tests/PhpWord/Tests/Writer/HTMLTest.php b/tests/PhpWord/Tests/Writer/HTMLTest.php index 34e9d2bd..0a59b3df 100644 --- a/tests/PhpWord/Tests/Writer/HTMLTest.php +++ b/tests/PhpWord/Tests/Writer/HTMLTest.php @@ -92,8 +92,8 @@ class HTMLTest extends \PHPUnit_Framework_TestCase $textrun = $section->addTextRun('Paragraph'); $textrun->addLink('http://test.com'); $textrun->addImage($localImage); - $textrun->addFootnote(); - $textrun->addEndnote(); + $textrun->addFootnote()->addText('Footnote'); + $textrun->addEndnote()->addText('Endnote'); $section = $phpWord->addSection(); diff --git a/tests/PhpWord/Tests/Writer/ODText/Part/ContentTest.php b/tests/PhpWord/Tests/Writer/ODText/Part/ContentTest.php index 03f0cfeb..42c95bcb 100644 --- a/tests/PhpWord/Tests/Writer/ODText/Part/ContentTest.php +++ b/tests/PhpWord/Tests/Writer/ODText/Part/ContentTest.php @@ -51,7 +51,7 @@ class ContentTest extends \PHPUnit_Framework_TestCase $phpWord->addFontStyle('Font', array('size' => 11)); $phpWord->addParagraphStyle('Paragraph', array('align' => 'center')); - $section = $phpWord->addSection(); + $section = $phpWord->addSection(array('colsNum' => 2)); $section->addText($expected); $section->addText('Test font style', 'Font'); $section->addText('Test paragraph style', null, 'Paragraph'); @@ -60,14 +60,14 @@ class ContentTest extends \PHPUnit_Framework_TestCase $section->addTextBreak(); $section->addPageBreak(); $section->addListItem('Test list item'); - $section->addImage($imageSrc); + $section->addImage($imageSrc, array('width' => 50)); $section->addObject($objectSrc); $section->addTOC(); $textrun = $section->addTextRun(); $textrun->addText('Test text run'); - $table = $section->addTable(); + $table = $section->addTable(array('width' => 50)); $cell = $table->addRow()->addCell(); $cell = $table->addRow()->addCell(); $cell->addText('Test'); @@ -82,6 +82,8 @@ class ContentTest extends \PHPUnit_Framework_TestCase $footer = $section->addFooter(); $footer->addPreserveText('{PAGE}'); + $table = $section->addTable('tblStyle')->addRow()->addCell(); + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); $element = "/office:document-content/office:body/office:text/text:section/text:p"; diff --git a/tests/PhpWord/Tests/Writer/ODText/StyleTest.php b/tests/PhpWord/Tests/Writer/ODText/StyleTest.php index 387accfc..cd5ea0eb 100644 --- a/tests/PhpWord/Tests/Writer/ODText/StyleTest.php +++ b/tests/PhpWord/Tests/Writer/ODText/StyleTest.php @@ -28,7 +28,7 @@ class StyleTest extends \PHPUnit_Framework_TestCase */ public function testEmptyStyles() { - $styles = array('Font', 'Paragraph'); + $styles = array('Font', 'Paragraph', 'Image', 'Section', 'Table'); foreach ($styles as $style) { $objectClass = 'PhpOffice\\PhpWord\\Writer\\ODText\\Style\\' . $style; $xmlWriter = new XMLWriter(); From 248d82d3c300ef5881ede2e29787207c94cb368e Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Sat, 24 May 2014 10:53:09 +0700 Subject: [PATCH 17/21] Add two recipes --- docs/recipes.rst | 39 +++++++++++++++++++++++++++++++++++++++ docs/src/documentation.md | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/docs/recipes.rst b/docs/recipes.rst index 033452b3..d900af8a 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -2,3 +2,42 @@ Recipes ======= + +Create float left image +----------------------- + +Use absolute positioning relative to margin horizontally and to line +vertically. + +.. code-block:: php + + $imageStyle = array( + 'width' => 40, + 'height' => 40 + 'wrappingStyle' => 'square', + 'positioning' => 'absolute', + 'posHorizontalRel' => 'margin', + 'posVerticalRel' => 'line', + ); + $textrun->addImage('resources/_earth.jpg', $imageStyle); + $textrun->addText($lipsumText); + +Download the produced file automatically +---------------------------------------- + +Use ``php://output`` as the filename. + +.. code-block:: php + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->createSection(); + $section->addText('Hello World!'); + $file = 'HelloWorld.docx'; + header("Content-Description: File Transfer"); + header('Content-Disposition: attachment; filename="' . $file . '"'); + header('Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document'); + header('Content-Transfer-Encoding: binary'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Expires: 0'); + $xmlWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007'); + $xmlWriter->save("php://output"); diff --git a/docs/src/documentation.md b/docs/src/documentation.md index abb8777e..6d34ff9f 100644 --- a/docs/src/documentation.md +++ b/docs/src/documentation.md @@ -40,6 +40,7 @@ Don't forget to change `code::` directive to `code-block::` in the resulting rst - [RTF](#rtf) - [HTML](#html) - [PDF](#pdf) +- [Recipes](#recipes) - [Frequently asked questions](#frequently-asked-questions) - [References](#references) @@ -929,6 +930,44 @@ To be completed. To be completed. +# Recipes + +## Create float left image + +Use absolute positioning relative to margin horizontally and to line vertically. + +```php +$imageStyle = array( + 'width' => 40, + 'height' => 40 + 'wrappingStyle' => 'square', + 'positioning' => 'absolute', + 'posHorizontalRel' => 'margin', + 'posVerticalRel' => 'line', +); +$textrun->addImage('resources/_earth.jpg', $imageStyle); +$textrun->addText($lipsumText); +``` + +## Download the produced file automatically + +Use `php://output` as the filename. + +```php +$phpWord = new \PhpOffice\PhpWord\PhpWord(); +$section = $phpWord->createSection(); +$section->addText('Hello World!'); +$file = 'HelloWorld.docx'; +header("Content-Description: File Transfer"); +header('Content-Disposition: attachment; filename="' . $file . '"'); +header('Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document'); +header('Content-Transfer-Encoding: binary'); +header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); +header('Expires: 0'); +$xmlWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007'); +$xmlWriter->save("php://output"); +``` + # Frequently asked questions ## Is this the same with PHPWord that I found in CodePlex? From 5ff47f44e982f510b00b4256f985455b077d077c Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Sat, 24 May 2014 11:11:12 +0700 Subject: [PATCH 18/21] QA: Misc. bugfixes and docblock improvements --- src/PhpWord/Style/Border.php | 2 +- src/PhpWord/Style/Table.php | 4 ++-- src/PhpWord/Style/TextBox.php | 2 +- src/PhpWord/Writer/Word2007/Style/MarginBorder.php | 4 ++-- tests/PhpWord/Tests/Writer/ODText/Part/ContentTest.php | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/PhpWord/Style/Border.php b/src/PhpWord/Style/Border.php index f0fd8650..84116d7a 100644 --- a/src/PhpWord/Style/Border.php +++ b/src/PhpWord/Style/Border.php @@ -81,7 +81,7 @@ class Border extends AbstractStyle /** * Get border size * - * @return int[] + * @return integer[] */ public function getBorderSize() { diff --git a/src/PhpWord/Style/Table.php b/src/PhpWord/Style/Table.php index 90e8282f..9875cc26 100644 --- a/src/PhpWord/Style/Table.php +++ b/src/PhpWord/Style/Table.php @@ -192,7 +192,7 @@ class Table extends Border /** * Get TLRBHV Border Size * - * @return int[] + * @return integer[] */ public function getBorderSize() { @@ -430,7 +430,7 @@ class Table extends Border /** * Get cell margin * - * @return int[] + * @return integer[] */ public function getCellMargin() { diff --git a/src/PhpWord/Style/TextBox.php b/src/PhpWord/Style/TextBox.php index da19cd10..eb215c06 100644 --- a/src/PhpWord/Style/TextBox.php +++ b/src/PhpWord/Style/TextBox.php @@ -162,7 +162,7 @@ class TextBox extends Image /** * Get cell margin * - * @return int[] + * @return integer[] */ public function getInnerMargin() { diff --git a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php index 23143ef5..88bb0109 100644 --- a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php +++ b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php @@ -29,7 +29,7 @@ class MarginBorder extends AbstractStyle /** * Sizes * - * @var int[] + * @var integer[] */ private $sizes = array(); @@ -103,7 +103,7 @@ class MarginBorder extends AbstractStyle /** * Set sizes * - * @param int[] $value + * @param integer[] $value */ public function setSizes($value) { diff --git a/tests/PhpWord/Tests/Writer/ODText/Part/ContentTest.php b/tests/PhpWord/Tests/Writer/ODText/Part/ContentTest.php index 42c95bcb..f75946cc 100644 --- a/tests/PhpWord/Tests/Writer/ODText/Part/ContentTest.php +++ b/tests/PhpWord/Tests/Writer/ODText/Part/ContentTest.php @@ -50,6 +50,7 @@ class ContentTest extends \PHPUnit_Framework_TestCase $phpWord->setDefaultFontName('Verdana'); $phpWord->addFontStyle('Font', array('size' => 11)); $phpWord->addParagraphStyle('Paragraph', array('align' => 'center')); + $phpWord->addTableStyle('tblStyle', array('width' => 100)); $section = $phpWord->addSection(array('colsNum' => 2)); $section->addText($expected); From 1e9a498ca20f0619fb752b52dbdec4c61bc96cf6 Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Sat, 24 May 2014 13:48:27 +0700 Subject: [PATCH 19/21] QA: Reduce some complexities: https://scrutinizer-ci.com/g/PHPOffice/PHPWord/code-structure/develop?elementType=operation --- src/PhpWord/Element/AbstractContainer.php | 32 ++---- src/PhpWord/Element/Image.php | 10 +- src/PhpWord/Media.php | 42 +++++--- src/PhpWord/Reader/Word2007/AbstractPart.php | 40 ++++--- src/PhpWord/Shared/Html.php | 4 +- .../Writer/Word2007/Element/TextBreak.php | 6 +- .../Writer/Word2007/Part/Numbering.php | 100 ++++++++++-------- src/PhpWord/Writer/Word2007/Part/Rels.php | 52 +++++---- src/PhpWord/Writer/Word2007/Part/RelsPart.php | 2 +- 9 files changed, 166 insertions(+), 122 deletions(-) diff --git a/src/PhpWord/Element/AbstractContainer.php b/src/PhpWord/Element/AbstractContainer.php index 06a94489..dc87cf18 100644 --- a/src/PhpWord/Element/AbstractContainer.php +++ b/src/PhpWord/Element/AbstractContainer.php @@ -56,38 +56,24 @@ abstract class AbstractContainer extends AbstractElement // Get arguments $args = func_get_args(); - $argsCount = func_num_args(); $withoutP = in_array($this->container, array('TextRun', 'Footnote', 'Endnote', 'ListItemRun')); if ($withoutP && ($elementName == 'Text' || $elementName == 'PreserveText')) { - $args[3] = null; + $args[3] = null; // Remove paragraph style for texts in textrun } - // Create element dynamically - + // Create element using reflection + $reflection = new \ReflectionClass($elementClass); + $elementArgs = $args; + array_shift($elementArgs); // Shift an element off the beginning of array: the $elementName /** @var \PhpOffice\PhpWord\Element\AbstractElement $element Type hint */ - if ($argsCount == 2) { // TextRun, TextBox, Table, Footnote, Endnote - $element = new $elementClass($args[1]); - } elseif ($argsCount == 3) { // Object, TextBreak, Title - $element = new $elementClass($args[1], $args[2]); - } elseif ($argsCount == 4) { // PreserveText, Text, Image - $element = new $elementClass($args[1], $args[2], $args[3]); - } elseif ($argsCount == 5) { // CheckBox, Link, ListItemRun, TOC - $element = new $elementClass($args[1], $args[2], $args[3], $args[4]); - } elseif ($argsCount == 6) { // ListItem - $element = new $elementClass($args[1], $args[2], $args[3], $args[4], $args[5]); - } else { // Page Break - $element = new $elementClass(); - } + $element = $reflection->newInstanceArgs($elementArgs); // Set relation Id for media collection $mediaContainer = $this->getMediaContainer(); if (in_array($elementName, array('Link', 'Image', 'Object'))) { - if ($elementName == 'Image') { - /** @var \PhpOffice\PhpWord\Element\Image $element Type hint */ - $rId = Media::addElement($mediaContainer, strtolower($elementName), $args[1], $element); - } else { - $rId = Media::addElement($mediaContainer, strtolower($elementName), $args[1]); - } + /** @var \PhpOffice\PhpWord\Element\Image $element Type hint */ + $image = ($elementName == 'Image') ? $element : null; + $rId = Media::addElement($mediaContainer, strtolower($elementName), $args[1], $image); $element->setRelationId($rId); } if ($elementName == 'Object') { diff --git a/src/PhpWord/Element/Image.php b/src/PhpWord/Element/Image.php index c38fda2c..de859ad2 100644 --- a/src/PhpWord/Element/Image.php +++ b/src/PhpWord/Element/Image.php @@ -284,6 +284,7 @@ class Image extends AbstractElement * * @param bool $base64 * @return string|null + * @since 0.11.0 */ public function getImageStringData($base64 = false) { @@ -291,6 +292,7 @@ class Image extends AbstractElement $actualSource = null; $imageBinary = null; $imageData = null; + $isTemp = false; // Get actual source from archive image or other source // Return null if not found @@ -301,6 +303,7 @@ class Image extends AbstractElement $zip = new ZipArchive(); if ($zip->open($zipFilename) !== false) { if ($zip->locateName($imageFilename)) { + $isTemp = true; $zip->extractTo(sys_get_temp_dir(), $imageFilename); $actualSource = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $imageFilename; } @@ -313,7 +316,7 @@ class Image extends AbstractElement return null; } - // Read image binary data and convert to hex + // Read image binary data and convert to hex/base64 string if ($this->sourceType == self::SOURCE_GD) { $imageResource = call_user_func($this->imageCreateFunc, $actualSource); ob_start(); @@ -335,6 +338,11 @@ class Image extends AbstractElement } } + // Delete temporary file if necessary + if ($isTemp === true) { + @unlink($actualSource); + } + return $imageData; } diff --git a/src/PhpWord/Media.php b/src/PhpWord/Media.php index 8cb82013..7f35841c 100644 --- a/src/PhpWord/Media.php +++ b/src/PhpWord/Media.php @@ -139,37 +139,53 @@ class Media * Get media elements * * @param string $container section|headerx|footerx|footnote|endnote - * @param string $mediaType image|object|link + * @param string $type image|object|link * @return array * @since 0.10.0 */ - public static function getElements($container, $mediaType = null) + public static function getElements($container, $type = null) { - $mediaElements = array(); + $elements = array(); // If header/footer, search for headerx and footerx where x is number if ($container == 'header' || $container == 'footer') { foreach (self::$elements as $key => $val) { if (substr($key, 0, 6) == $container) { - $mediaElements[$key] = $val; + $elements[$key] = $val; } } + return $elements; } else { if (!array_key_exists($container, self::$elements)) { - return $mediaElements; + return $elements; } - foreach (self::$elements[$container] as $mediaKey => $mediaData) { - if (!is_null($mediaType)) { - if ($mediaType == $mediaData['type']) { - $mediaElements[$mediaKey] = $mediaData; - } - } else { - $mediaElements[$mediaKey] = $mediaData; + return self::getElementsByType($container, $type); + } + } + + /** + * Get elements by media type + * + * @param string $container section|footnote|endnote + * @param string $type image|object|link + * @return array + * @since 0.11.0 Splitted from `getElements` to reduce complexity + */ + private static function getElementsByType($container, $type = null) + { + $elements = array(); + + foreach (self::$elements[$container] as $key => $data) { + if ($type !== null) { + if ($type == $data['type']) { + $elements[$key] = $data; } + } else { + $elements[$key] = $data; } } - return $mediaElements; + return $elements; } /** diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 2936884f..8f14daed 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -453,25 +453,41 @@ abstract class AbstractPart $attribute = ($attribute === null) ? 'w:val' : $attribute; $attributeValue = $xmlReader->getAttribute($attribute, $node); - // Assign style value based on conversion model - if ($method == self::READ_VALUE) { - $styles[$styleProp] = $attributeValue; - } elseif ($method == self::READ_SIZE) { - $styles[$styleProp] = $attributeValue / 2; - } elseif ($method == self::READ_TRUE) { - $styles[$styleProp] = true; - } elseif ($method == self::READ_FALSE) { - $styles[$styleProp] = false; - } elseif ($method == self::READ_EQUAL && $attributeValue == $expected) { - $styles[$styleProp] = true; + $styleValue = $this->readStyleDef($method, $attributeValue, $expected); + if ($styleValue !== null) { + $styles[$styleProp] = $styleValue; } } } - /** @var array $styles Type hint */ return $styles; } + /** + * Return style definition based on conversion method + * + * @param string $method + * @param mixed $attributeValue + * @param mixed $expected + * @return mixed + */ + private function readStyleDef($method, $attributeValue, $expected) + { + $style = $attributeValue; + + if ($method == self::READ_SIZE) { + $style = $attributeValue / 2; + } elseif ($method == self::READ_TRUE) { + $style = true; + } elseif ($method == self::READ_FALSE) { + $style = false; + } elseif ($method == self::READ_EQUAL && $attributeValue == $expected) { + $style = true; + } + + return $style; + } + /** * Returns the target of image, object, or link as stored in ::readMainRels * diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 501d2404..4faacfb1 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -214,7 +214,7 @@ class Html */ case 'li': $cNodes = $node->childNodes; - if (count($cNodes) > 0) { + if ($cNodes->length > 0) { $text = ''; foreach ($cNodes as $cNode) { if ($cNode->nodeName == '#text') { @@ -240,7 +240,7 @@ class Html */ if ($node->nodeName != 'li') { $cNodes = $node->childNodes; - if (count($cNodes) > 0) { + if ($cNodes->length > 0) { foreach ($cNodes as $cNode) { self::parseNode($cNode, $newobject, $styles, $data); } diff --git a/src/PhpWord/Writer/Word2007/Element/TextBreak.php b/src/PhpWord/Writer/Word2007/Element/TextBreak.php index 227b1b30..5f4bd3ce 100644 --- a/src/PhpWord/Writer/Word2007/Element/TextBreak.php +++ b/src/PhpWord/Writer/Word2007/Element/TextBreak.php @@ -37,15 +37,13 @@ class TextBreak extends Text if (!$this->withoutP) { $hasStyle = $element->hasStyle(); + $this->writeOpeningWP(); if ($hasStyle) { - $this->writeOpeningWP(); $xmlWriter->startElement('w:pPr'); $this->writeFontStyle(); $xmlWriter->endElement(); // w:pPr - $this->writeClosingWP(); - } else { - $xmlWriter->writeElement('w:p'); } + $this->writeClosingWP(); } else { $xmlWriter->writeElement('w:br'); } diff --git a/src/PhpWord/Writer/Word2007/Part/Numbering.php b/src/PhpWord/Writer/Word2007/Part/Numbering.php index 2678ac55..df5abd9b 100644 --- a/src/PhpWord/Writer/Word2007/Part/Numbering.php +++ b/src/PhpWord/Writer/Word2007/Part/Numbering.php @@ -24,6 +24,8 @@ use PhpOffice\PhpWord\Style\NumberingLevel; /** * Word2007 numbering part writer: word/numbering.xml + * + * @since 0.10.0 */ class Numbering extends AbstractPart { @@ -97,12 +99,6 @@ class Numbering extends AbstractPart */ private function writeLevel(XMLWriter $xmlWriter, NumberingLevel $level) { - $tabPos = $level->getTabPos(); - $left = $level->getLeft(); - $hanging = $level->getHanging(); - $font = $level->getFont(); - $hint = $level->getHint(); - $xmlWriter->startElement('w:lvl'); $xmlWriter->writeAttribute('w:ilvl', $level->getLevel()); @@ -124,48 +120,64 @@ class Numbering extends AbstractPart } } - // Paragraph styles - if (!is_null($tabPos) || !is_null($left) || !is_null($hanging)) { - $xmlWriter->startElement('w:pPr'); - if (!is_null($tabPos)) { - $xmlWriter->startElement('w:tabs'); - $xmlWriter->startElement('w:tab'); - $xmlWriter->writeAttribute('w:val', 'num'); - $xmlWriter->writeAttribute('w:pos', $tabPos); - $xmlWriter->endElement(); // w:tab - $xmlWriter->endElement(); // w:tabs - } - if (!is_null($left) || !is_null($hanging)) { - $xmlWriter->startElement('w:ind'); - if (!is_null($left)) { - $xmlWriter->writeAttribute('w:left', $left); - } - if (!is_null($hanging)) { - $xmlWriter->writeAttribute('w:hanging', $hanging); - } - $xmlWriter->endElement(); // w:ind - } - $xmlWriter->endElement(); // w:pPr - } + // Paragraph & font styles + $this->writeParagraph($xmlWriter, $level); + $this->writeFont($xmlWriter, $level); - // Font styles - if (!is_null($font) || !is_null($hint)) { - $xmlWriter->startElement('w:rPr'); - $xmlWriter->startElement('w:rFonts'); - if (!is_null($font)) { - $xmlWriter->writeAttribute('w:ascii', $font); - $xmlWriter->writeAttribute('w:hAnsi', $font); - $xmlWriter->writeAttribute('w:cs', $font); - } - if (!is_null($hint)) { - $xmlWriter->writeAttribute('w:hint', $hint); - } - $xmlWriter->endElement(); // w:rFonts - $xmlWriter->endElement(); // w:rPr - } $xmlWriter->endElement(); // w:lvl } + /** + * Write level paragraph + * + * @since 0.11.0 + * @todo Use paragraph style writer + */ + private function writeParagraph(XMLWriter $xmlWriter, NumberingLevel $level) + { + $tabPos = $level->getTabPos(); + $left = $level->getLeft(); + $hanging = $level->getHanging(); + + $xmlWriter->startElement('w:pPr'); + + $xmlWriter->startElement('w:tabs'); + $xmlWriter->startElement('w:tab'); + $xmlWriter->writeAttribute('w:val', 'num'); + $xmlWriter->writeAttributeIf($tabPos !== null, 'w:pos', $tabPos); + $xmlWriter->writeAttribute('w:pos', $tabPos); + $xmlWriter->endElement(); // w:tab + $xmlWriter->endElement(); // w:tabs + + $xmlWriter->startElement('w:ind'); + $xmlWriter->writeAttributeIf($left !== null, 'w:left', $left); + $xmlWriter->writeAttributeIf($hanging !== null, 'w:hanging', $hanging); + $xmlWriter->endElement(); // w:ind + + $xmlWriter->endElement(); // w:pPr + } + + /** + * Write level font + * + * @since 0.11.0 + * @todo Use font style writer + */ + private function writeFont(XMLWriter $xmlWriter, NumberingLevel $level) + { + $font = $level->getFont(); + $hint = $level->getHint(); + + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:rFonts'); + $xmlWriter->writeAttributeIf($font !== null, 'w:ascii', $font); + $xmlWriter->writeAttributeIf($font !== null, 'w:hAnsi', $font); + $xmlWriter->writeAttributeIf($font !== null, 'w:cs', $font); + $xmlWriter->writeAttributeIf($hint !== null, 'w:hint', $hint); + $xmlWriter->endElement(); // w:rFonts + $xmlWriter->endElement(); // w:rPr + } + /** * Get random hexadecimal number value * diff --git a/src/PhpWord/Writer/Word2007/Part/Rels.php b/src/PhpWord/Writer/Word2007/Part/Rels.php index c1405258..562deb35 100644 --- a/src/PhpWord/Writer/Word2007/Part/Rels.php +++ b/src/PhpWord/Writer/Word2007/Part/Rels.php @@ -50,43 +50,51 @@ class Rels extends AbstractPart * Write relationships * * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param null|array $xmlRels - * @param null|array $mediaRels - * @param integer $relId + * @param array $xmlRels + * @param array $mediaRels + * @param int $relId */ - protected function writeRels(XMLWriter $xmlWriter, $xmlRels = null, $mediaRels = null, $relId = 1) + protected function writeRels(XMLWriter $xmlWriter, $xmlRels = array(), $mediaRels = array(), $relId = 1) { $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); $xmlWriter->startElement('Relationships'); $xmlWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); // XML files relationships - if (is_array($xmlRels)) { - foreach ($xmlRels as $target => $type) { - $this->writeRel($xmlWriter, $relId++, $type, $target); - } + foreach ($xmlRels as $target => $type) { + $this->writeRel($xmlWriter, $relId++, $type, $target); } // Media relationships - if (is_array($mediaRels)) { - $typePrefix = 'officeDocument/2006/relationships/'; - $typeMapping = array('image' => 'image', 'object' => 'oleObject', 'link' => 'hyperlink'); - $targetPaths = array('image' => 'media/', 'object' => 'embeddings/'); - - foreach ($mediaRels as $mediaRel) { - $mediaType = $mediaRel['type']; - $type = array_key_exists($mediaType, $typeMapping) ? $typeMapping[$mediaType] : $mediaType; - $target = array_key_exists($mediaType, $targetPaths) ? $targetPaths[$mediaType] : ''; - $target .= $mediaRel['target']; - $targetMode = ($type == 'hyperlink') ? 'External' : ''; - - $this->writeRel($xmlWriter, $relId++, $typePrefix . $type, $target, $targetMode); - } + foreach ($mediaRels as $mediaRel) { + $this->writeMediaRel($xmlWriter, $relId++, $mediaRel); } $xmlWriter->endElement(); // Relationships } + /** + * Write media relationships + * + * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter + * @param int $relId + * @param array $mediaRel + */ + private function writeMediaRel(XMLWriter $xmlWriter, $relId, $mediaRel) + { + $typePrefix = 'officeDocument/2006/relationships/'; + $typeMapping = array('image' => 'image', 'object' => 'oleObject', 'link' => 'hyperlink'); + $targetMapping = array('image' => 'media/', 'object' => 'embeddings/'); + + $mediaType = $mediaRel['type']; + $type = array_key_exists($mediaType, $typeMapping) ? $typeMapping[$mediaType] : $mediaType; + $targetPrefix = array_key_exists($mediaType, $targetMapping) ? $targetMapping[$mediaType] : ''; + $target = $mediaRel['target']; + $targetMode = ($type == 'hyperlink') ? 'External' : ''; + + $this->writeRel($xmlWriter, $relId, $typePrefix . $type, $targetPrefix . $target, $targetMode); + } + /** * Write individual rels entry * diff --git a/src/PhpWord/Writer/Word2007/Part/RelsPart.php b/src/PhpWord/Writer/Word2007/Part/RelsPart.php index a3697834..627a2bcd 100644 --- a/src/PhpWord/Writer/Word2007/Part/RelsPart.php +++ b/src/PhpWord/Writer/Word2007/Part/RelsPart.php @@ -39,7 +39,7 @@ class RelsPart extends Rels public function write() { $xmlWriter = $this->getXmlWriter(); - $this->writeRels($xmlWriter, null, $this->media); + $this->writeRels($xmlWriter, array(), $this->media); return $xmlWriter->getData(); } From dc6c487cd02afbf135463db364f61f768afa6597 Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Sat, 24 May 2014 23:07:34 +0700 Subject: [PATCH 20/21] Fix test error --- src/PhpWord/Element/Image.php | 10 +++++++--- src/PhpWord/Settings.php | 2 +- src/PhpWord/Shared/Html.php | 4 ++-- src/PhpWord/Writer/Word2007/Part/Numbering.php | 1 - tests/PhpWord/Tests/Element/ImageTest.php | 1 + tests/PhpWord/Tests/Element/SectionTest.php | 2 +- tests/PhpWord/Tests/SettingsTest.php | 2 +- 7 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/PhpWord/Element/Image.php b/src/PhpWord/Element/Image.php index de859ad2..a1cb8250 100644 --- a/src/PhpWord/Element/Image.php +++ b/src/PhpWord/Element/Image.php @@ -312,9 +312,13 @@ class Image extends AbstractElement } else { $actualSource = $source; } - if ($actualSource === null) { - return null; - } + + // Can't find any case where $actualSource = null hasn't captured by + // preceding exceptions. Please uncomment when you find the case and + // put the case into Element\ImageTest. + // if ($actualSource === null) { + // return null; + // } // Read image binary data and convert to hex/base64 string if ($this->sourceType == self::SOURCE_GD) { diff --git a/src/PhpWord/Settings.php b/src/PhpWord/Settings.php index 8d9e2ace..cb74389a 100644 --- a/src/PhpWord/Settings.php +++ b/src/PhpWord/Settings.php @@ -344,7 +344,7 @@ class Settings // Parse config file $config = array(); if ($configFile !== null) { - $config = parse_ini_file($configFile); + $config = @parse_ini_file($configFile); if ($config === false) { return $config; } diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 4faacfb1..501d2404 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -214,7 +214,7 @@ class Html */ case 'li': $cNodes = $node->childNodes; - if ($cNodes->length > 0) { + if (count($cNodes) > 0) { $text = ''; foreach ($cNodes as $cNode) { if ($cNode->nodeName == '#text') { @@ -240,7 +240,7 @@ class Html */ if ($node->nodeName != 'li') { $cNodes = $node->childNodes; - if ($cNodes->length > 0) { + if (count($cNodes) > 0) { foreach ($cNodes as $cNode) { self::parseNode($cNode, $newobject, $styles, $data); } diff --git a/src/PhpWord/Writer/Word2007/Part/Numbering.php b/src/PhpWord/Writer/Word2007/Part/Numbering.php index df5abd9b..05cbf7b9 100644 --- a/src/PhpWord/Writer/Word2007/Part/Numbering.php +++ b/src/PhpWord/Writer/Word2007/Part/Numbering.php @@ -145,7 +145,6 @@ class Numbering extends AbstractPart $xmlWriter->startElement('w:tab'); $xmlWriter->writeAttribute('w:val', 'num'); $xmlWriter->writeAttributeIf($tabPos !== null, 'w:pos', $tabPos); - $xmlWriter->writeAttribute('w:pos', $tabPos); $xmlWriter->endElement(); // w:tab $xmlWriter->endElement(); // w:tabs diff --git a/tests/PhpWord/Tests/Element/ImageTest.php b/tests/PhpWord/Tests/Element/ImageTest.php index b04b5fe6..11b33d87 100644 --- a/tests/PhpWord/Tests/Element/ImageTest.php +++ b/tests/PhpWord/Tests/Element/ImageTest.php @@ -38,6 +38,7 @@ class ImageTest extends \PHPUnit_Framework_TestCase $this->assertEquals($oImage->getSource(), $src); $this->assertEquals($oImage->getMediaId(), md5($src)); $this->assertEquals($oImage->isWatermark(), false); + $this->assertEquals($oImage->getSourceType(), Image::SOURCE_LOCAL); $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Image', $oImage->getStyle()); } diff --git a/tests/PhpWord/Tests/Element/SectionTest.php b/tests/PhpWord/Tests/Element/SectionTest.php index 271e81e3..af7595e2 100644 --- a/tests/PhpWord/Tests/Element/SectionTest.php +++ b/tests/PhpWord/Tests/Element/SectionTest.php @@ -73,7 +73,7 @@ class SectionTest extends \PHPUnit_Framework_TestCase { $expected = 'landscape'; $object = new Section(0); - $object->setSettings(array('orientation' => $expected)); + $object->setSettings(array('orientation' => $expected, 'foo' => null)); $this->assertEquals($expected, $object->getSettings()->getOrientation()); } diff --git a/tests/PhpWord/Tests/SettingsTest.php b/tests/PhpWord/Tests/SettingsTest.php index 36565eb1..61364034 100644 --- a/tests/PhpWord/Tests/SettingsTest.php +++ b/tests/PhpWord/Tests/SettingsTest.php @@ -111,6 +111,6 @@ class SettingsTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expected, Settings::loadConfig(__DIR__ . '/../../../phpword.ini.dist')); // Test with invalid file - $this->assertEmpty(Settings::loadConfig(__DIR__ . '/files/xsl/passthrough.xsl')); + $this->assertEmpty(Settings::loadConfig(__DIR__ . '/../../../phpunit.xml.dist')); } } From 92c7a24c38ebeb1bf90c38d0fe989b0bde2f12a7 Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Sun, 25 May 2014 22:51:14 +0700 Subject: [PATCH 21/21] QA: Additional unit tests and docblock fixes --- CHANGELOG.md | 1 + src/PhpWord/Autoloader.php | 1 + src/PhpWord/DocumentProperties.php | 2 +- src/PhpWord/Shared/XMLWriter.php | 10 +- src/PhpWord/Style/AbstractStyle.php | 2 +- src/PhpWord/Style/Cell.php | 2 +- src/PhpWord/Style/Font.php | 2 +- src/PhpWord/Style/LineNumbering.php | 3 +- src/PhpWord/Style/ListItem.php | 4 +- src/PhpWord/Style/Paragraph.php | 10 +- src/PhpWord/Style/Table.php | 2 +- src/PhpWord/Style/TextBox.php | 2 +- src/PhpWord/Writer/AbstractWriter.php | 37 +++- src/PhpWord/Writer/HTML.php | 30 ++- src/PhpWord/Writer/PDF.php | 8 +- src/PhpWord/Writer/PDF/DomPDF.php | 2 +- src/PhpWord/Writer/PDF/MPDF.php | 2 +- src/PhpWord/Writer/PDF/TCPDF.php | 2 +- src/PhpWord/Writer/RTF.php | 38 ++-- src/PhpWord/Writer/Word2007/Part/Styles.php | 205 ++++++++++-------- tests/PhpWord/Tests/Shared/XMLWriterTest.php | 40 ++++ .../PhpWord/Tests/Style/AbstractStyleTest.php | 1 + tests/PhpWord/Tests/Style/CellTest.php | 4 + tests/PhpWord/Tests/Style/FontTest.php | 1 + tests/PhpWord/Tests/Style/ParagraphTest.php | 14 ++ tests/PhpWord/Tests/Style/TableTest.php | 4 + .../Tests/_files/documents/reader.docx | Bin 74101 -> 74121 bytes 27 files changed, 290 insertions(+), 139 deletions(-) create mode 100644 tests/PhpWord/Tests/Shared/XMLWriterTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 84cc2cb1..fcaf1388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ This release marked the change of PHPWord license from LGPL 2.1 to LGPL 3; new r - `Writer\Word2007\Part`: `Numbering::writeNumbering()`, `Settings::writeSettings()`, `WebSettings::writeWebSettings()`, `ContentTypes::writeContentTypes()`, `Styles::writeStyles()`, `Document::writeDocument()` all changed into `write()` - `Writer\Word2007\Part\DocProps`: Split into `Writer\Word2007\Part\DocPropsCore` and `Writer\Word2007\Part\DocPropsApp` - `Element\Title::getBookmarkId()` replaced by `Element\Title::getRelationId()` +- `Writer\HTML::writeDocument`: Replaced by `Writer\HTML::getContent` ### Miscellaneous diff --git a/src/PhpWord/Autoloader.php b/src/PhpWord/Autoloader.php index c60ae9a6..c467f836 100644 --- a/src/PhpWord/Autoloader.php +++ b/src/PhpWord/Autoloader.php @@ -22,6 +22,7 @@ namespace PhpOffice\PhpWord; */ class Autoloader { + /** @const string */ const NAMESPACE_PREFIX = 'PhpOffice\\PhpWord\\'; /** diff --git a/src/PhpWord/DocumentProperties.php b/src/PhpWord/DocumentProperties.php index 95159ec0..5644c3d9 100644 --- a/src/PhpWord/DocumentProperties.php +++ b/src/PhpWord/DocumentProperties.php @@ -22,7 +22,7 @@ namespace PhpOffice\PhpWord; */ class DocumentProperties { - /** Constants */ + /** @const string Property type constants */ const PROPERTY_TYPE_BOOLEAN = 'b'; const PROPERTY_TYPE_INTEGER = 'i'; const PROPERTY_TYPE_FLOAT = 'f'; diff --git a/src/PhpWord/Shared/XMLWriter.php b/src/PhpWord/Shared/XMLWriter.php index dc85bfc1..81e8e286 100644 --- a/src/PhpWord/Shared/XMLWriter.php +++ b/src/PhpWord/Shared/XMLWriter.php @@ -68,9 +68,8 @@ class XMLWriter // Create temporary filename $this->tempFile = @tempnam($tempFolder, 'xml'); - // Open storage + // Fallback to memory when temporary file cannot be used if ($this->xmlWriter->openUri($this->tempFile) === false) { - // Fallback to memory... $this->xmlWriter->openMemory(); } } @@ -105,9 +104,16 @@ class XMLWriter * * @param mixed $function * @param mixed $args + * @throws \BadMethodCallException */ public function __call($function, $args) { + // Catch exception + if (method_exists($this->xmlWriter, $function) === false) { + throw new \BadMethodCallException("Method '{$function}' does not exists."); + } + + // Run method try { @call_user_func_array(array($this->xmlWriter, $function), $args); } catch (\Exception $ex) { diff --git a/src/PhpWord/Style/AbstractStyle.php b/src/PhpWord/Style/AbstractStyle.php index 1b916934..8167c4d2 100644 --- a/src/PhpWord/Style/AbstractStyle.php +++ b/src/PhpWord/Style/AbstractStyle.php @@ -265,7 +265,7 @@ abstract class AbstractStyle { if ($value != null && trim($value) != '' && !empty($enum) && !in_array($value, $enum)) { throw new \InvalidArgumentException('Invalid style value.'); - } elseif (is_null($value) || trim($value) == '') { + } elseif ($value === null || trim($value) == '') { $value = $default; } diff --git a/src/PhpWord/Style/Cell.php b/src/PhpWord/Style/Cell.php index 95ed13b4..2d1b88d0 100644 --- a/src/PhpWord/Style/Cell.php +++ b/src/PhpWord/Style/Cell.php @@ -144,7 +144,7 @@ class Cell extends Border */ public function getBgColor() { - if (!is_null($this->shading)) { + if ($this->shading !== null) { return $this->shading->getFill(); } else { return null; diff --git a/src/PhpWord/Style/Font.php b/src/PhpWord/Style/Font.php index 07eebdb2..0775b8b3 100644 --- a/src/PhpWord/Style/Font.php +++ b/src/PhpWord/Style/Font.php @@ -540,7 +540,7 @@ class Font extends AbstractStyle */ public function getBgColor() { - if (!is_null($this->shading)) { + if ($this->shading !== null) { return $this->shading->getFill(); } else { return null; diff --git a/src/PhpWord/Style/LineNumbering.php b/src/PhpWord/Style/LineNumbering.php index d49c7f4e..b93ce03f 100644 --- a/src/PhpWord/Style/LineNumbering.php +++ b/src/PhpWord/Style/LineNumbering.php @@ -20,11 +20,12 @@ namespace PhpOffice\PhpWord\Style; /** * Line numbering style * - * @link http://www.schemacentral.com/sc/ooxml/e-w_lnNumType-1.html + * @link http://www.schemacentral.com/sc/ooxml/t-w_CT_LineNumber.html * @since 0.10.0 */ class LineNumbering extends AbstractStyle { + /** @const string Line numbering restart setting http://www.schemacentral.com/sc/ooxml/a-w_restart-1.html */ const LINE_NUMBERING_CONTINUOUS = 'continuous'; const LINE_NUMBERING_NEW_PAGE = 'newPage'; const LINE_NUMBERING_NEW_SECTION = 'newSection'; diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index 554d75b0..a689c691 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -65,7 +65,7 @@ class ListItem extends AbstractStyle */ public function __construct($numStyle = null) { - if (!is_null($numStyle)) { + if ($numStyle !== null) { $this->setNumStyle($numStyle); } else { $this->setListType(); @@ -149,7 +149,7 @@ class ListItem extends AbstractStyle { // Check if legacy style already registered in global Style collection $numStyle = "PHPWordList{$this->listType}"; - if (!is_null(Style::getStyle($numStyle))) { + if (Style::getStyle($numStyle) !== null) { $this->setNumStyle($numStyle); return; } diff --git a/src/PhpWord/Style/Paragraph.php b/src/PhpWord/Style/Paragraph.php index 8609b5ab..e70833ca 100644 --- a/src/PhpWord/Style/Paragraph.php +++ b/src/PhpWord/Style/Paragraph.php @@ -171,7 +171,7 @@ class Paragraph extends AbstractStyle */ public function getSpaceBefore() { - if (!is_null($this->spacing)) { + if ($this->spacing !== null) { return $this->spacing->getBefore(); } else { return null; @@ -196,7 +196,7 @@ class Paragraph extends AbstractStyle */ public function getSpaceAfter() { - if (!is_null($this->spacing)) { + if ($this->spacing !== null) { return $this->spacing->getAfter(); } else { return null; @@ -221,7 +221,7 @@ class Paragraph extends AbstractStyle */ public function getSpacing() { - if (!is_null($this->spacing)) { + if ($this->spacing !== null) { return $this->spacing->getLine(); } else { return null; @@ -278,7 +278,7 @@ class Paragraph extends AbstractStyle */ public function getIndent() { - if (!is_null($this->indentation)) { + if ($this->indentation !== null) { return $this->indentation->getLeft(); } else { return null; @@ -303,7 +303,7 @@ class Paragraph extends AbstractStyle */ public function getHanging() { - if (!is_null($this->indentation)) { + if ($this->indentation !== null) { return $this->indentation->getHanging(); } else { return null; diff --git a/src/PhpWord/Style/Table.php b/src/PhpWord/Style/Table.php index 9875cc26..24f50667 100644 --- a/src/PhpWord/Style/Table.php +++ b/src/PhpWord/Style/Table.php @@ -169,7 +169,7 @@ class Table extends Border */ public function getBgColor() { - if (!is_null($this->shading)) { + if ($this->shading !== null) { return $this->shading->getFill(); } diff --git a/src/PhpWord/Style/TextBox.php b/src/PhpWord/Style/TextBox.php index eb215c06..9f0a1dde 100644 --- a/src/PhpWord/Style/TextBox.php +++ b/src/PhpWord/Style/TextBox.php @@ -179,7 +179,7 @@ class TextBox extends Image $hasInnerMargins = false; $margins = $this->getInnerMargin(); for ($i = 0; $i < count($margins); $i++) { - if (!is_null($margins[$i])) { + if ($margins[$i] !== null) { $hasInnerMargins = true; } } diff --git a/src/PhpWord/Writer/AbstractWriter.php b/src/PhpWord/Writer/AbstractWriter.php index 52a1a28c..367b7729 100644 --- a/src/PhpWord/Writer/AbstractWriter.php +++ b/src/PhpWord/Writer/AbstractWriter.php @@ -258,7 +258,7 @@ abstract class AbstractWriter implements WriterInterface * * @param string $filename * @return \PhpOffice\PhpWord\Shared\ZipArchive - * @throws \PhpOffice\PhpWord\Exception\Exception + * @throws \Exception */ protected function getZipArchive($filename) { @@ -271,13 +271,46 @@ abstract class AbstractWriter implements WriterInterface $zip = new ZipArchive(); if ($zip->open($filename, ZipArchive::OVERWRITE) !== true) { if ($zip->open($filename, ZipArchive::CREATE) !== true) { - throw new Exception("Could not open " . $filename . " for writing."); + throw new \Exception("Could not open '{$filename}' for writing."); } } return $zip; } + /** + * Open file for writing + * + * @param string $filename + * @return resource + * @throws \Exception + * @since 0.11.0 + */ + protected function openFile($filename) + { + $filename = $this->getTempFile($filename); + $fileHandle = fopen($filename, 'w'); + if ($fileHandle === false) { + throw new \Exception("Could not open '{$filename}' for writing."); + } + + return $fileHandle; + } + + /** + * Write content to file + * + * @param resource $fileHandle + * @param string $content + * @since 0.11.0 + */ + protected function writeFile(&$fileHandle, $content) + { + fwrite($fileHandle, $content); + fclose($fileHandle); + $this->cleanupTempFile(); + } + /** * Add files to package * diff --git a/src/PhpWord/Writer/HTML.php b/src/PhpWord/Writer/HTML.php index 88e7658d..29173ff2 100644 --- a/src/PhpWord/Writer/HTML.php +++ b/src/PhpWord/Writer/HTML.php @@ -17,7 +17,6 @@ namespace PhpOffice\PhpWord\Writer; -use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\PhpWord; /** @@ -69,25 +68,20 @@ class HTML extends AbstractWriter implements WriterInterface */ public function save($filename = null) { - $this->setTempDir(sys_get_temp_dir() . '/PHPWordWriter/'); - $hFile = fopen($filename, 'w'); - if ($hFile !== false) { - fwrite($hFile, $this->writeDocument()); - fclose($hFile); - } else { - throw new Exception("Can't open file"); - } - $this->clearTempDir(); + $fileHandle = $this->openFile($filename); + $this->writeFile($fileHandle, $this->getContent()); } /** - * Get phpWord data + * Get content * * @return string + * @since 0.11.0 */ - public function writeDocument() + public function getContent() { $content = ''; + $content .= '' . PHP_EOL; $content .= '' . PHP_EOL; $content .= '' . PHP_EOL; @@ -128,4 +122,16 @@ class HTML extends AbstractWriter implements WriterInterface { $this->notes[$noteId] = $noteMark; } + + /** + * Write document + * + * @return string + * @deprecated 0.11.0 + * @codeCoverageIgnore + */ + public function writeDocument() + { + return $this->getContent(); + } } diff --git a/src/PhpWord/Writer/PDF.php b/src/PhpWord/Writer/PDF.php index 98dc1220..17865563 100644 --- a/src/PhpWord/Writer/PDF.php +++ b/src/PhpWord/Writer/PDF.php @@ -65,13 +65,13 @@ class PDF * @param string $name Renderer library method name * @param mixed[] $arguments Array of arguments to pass to the renderer method * @return mixed Returned data from the PDF renderer wrapper method - * @throws \PhpOffice\PhpWord\Exception\Exception */ public function __call($name, $arguments) { - if ($this->renderer === null) { - throw new Exception("PDF Rendering library has not been defined."); - } + // Note: Commented because all exceptions should already be catched by `__construct` + // if ($this->renderer === null) { + // throw new Exception("PDF Rendering library has not been defined."); + // } return call_user_func_array(array($this->renderer, $name), $arguments); } diff --git a/src/PhpWord/Writer/PDF/DomPDF.php b/src/PhpWord/Writer/PDF/DomPDF.php index 4effc154..a40e2cea 100644 --- a/src/PhpWord/Writer/PDF/DomPDF.php +++ b/src/PhpWord/Writer/PDF/DomPDF.php @@ -50,7 +50,7 @@ class DomPDF extends AbstractRenderer implements WriterInterface // Create PDF $pdf = new \DOMPDF(); $pdf->set_paper(strtolower($paperSize), $orientation); - $pdf->load_html($this->writeDocument()); + $pdf->load_html($this->getContent()); $pdf->render(); // Write to file diff --git a/src/PhpWord/Writer/PDF/MPDF.php b/src/PhpWord/Writer/PDF/MPDF.php index d38d5b66..9d4e050c 100644 --- a/src/PhpWord/Writer/PDF/MPDF.php +++ b/src/PhpWord/Writer/PDF/MPDF.php @@ -61,7 +61,7 @@ class MPDF extends AbstractRenderer implements WriterInterface $pdf->setKeywords($docProps->getKeywords()); $pdf->setCreator($docProps->getCreator()); - $pdf->writeHTML($this->writeDocument()); + $pdf->writeHTML($this->getContent()); // Write to file fwrite($fileHandle, $pdf->output($filename, 'S')); diff --git a/src/PhpWord/Writer/PDF/TCPDF.php b/src/PhpWord/Writer/PDF/TCPDF.php index 05f02756..669a2cdd 100644 --- a/src/PhpWord/Writer/PDF/TCPDF.php +++ b/src/PhpWord/Writer/PDF/TCPDF.php @@ -54,7 +54,7 @@ class TCPDF extends AbstractRenderer implements WriterInterface $pdf->setPrintFooter(false); $pdf->addPage(); $pdf->setFont($this->getFont()); - $pdf->writeHTML($this->writeDocument()); + $pdf->writeHTML($this->getContent()); // Write document properties $phpWord = $this->getPhpWord(); diff --git a/src/PhpWord/Writer/RTF.php b/src/PhpWord/Writer/RTF.php index ef94b264..d7fed942 100644 --- a/src/PhpWord/Writer/RTF.php +++ b/src/PhpWord/Writer/RTF.php @@ -17,7 +17,6 @@ namespace PhpOffice\PhpWord\Writer; -use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\PhpWord; /** @@ -56,29 +55,34 @@ class RTF extends AbstractWriter implements WriterInterface } /** - * Save PhpWord to file + * Save content to file * * @param string $filename * @throws \PhpOffice\PhpWord\Exception\Exception */ public function save($filename = null) { - $content = ''; - $filename = $this->getTempFile($filename); - $hFile = fopen($filename, 'w'); - if ($hFile !== false) { - $content .= '{'; - $content .= '\rtf1' . PHP_EOL; - $content .= $this->getWriterPart('Header')->write(); - $content .= $this->getWriterPart('Document')->write(); - $content .= '}'; + $fileHandle = $this->openFile($filename); + $this->writeFile($fileHandle, $this->getContent()); + } - fwrite($hFile, $content); - fclose($hFile); - } else { - throw new Exception("Can't open file"); - } - $this->cleanupTempFile(); + /** + * Get content + * + * @return string + * @since 0.11.0 + */ + private function getContent() + { + $content = ''; + + $content .= '{'; + $content .= '\rtf1' . PHP_EOL; + $content .= $this->getWriterPart('Header')->write(); + $content .= $this->getWriterPart('Document')->write(); + $content .= '}'; + + return $content; } /** diff --git a/src/PhpWord/Writer/Word2007/Part/Styles.php b/src/PhpWord/Writer/Word2007/Part/Styles.php index a0a6317a..0d688e36 100644 --- a/src/PhpWord/Writer/Word2007/Part/Styles.php +++ b/src/PhpWord/Writer/Word2007/Part/Styles.php @@ -20,9 +20,9 @@ namespace PhpOffice\PhpWord\Writer\Word2007\Part; use PhpOffice\PhpWord\Settings as PhpWordSettings; use PhpOffice\PhpWord\Shared\XMLWriter; use PhpOffice\PhpWord\Style; -use PhpOffice\PhpWord\Style\Font; -use PhpOffice\PhpWord\Style\Paragraph; -use PhpOffice\PhpWord\Style\Table; +use PhpOffice\PhpWord\Style\Font as FontStyle; +use PhpOffice\PhpWord\Style\Paragraph as ParagraphStyle; +use PhpOffice\PhpWord\Style\Table as TableStyle; use PhpOffice\PhpWord\Writer\Word2007\Style\Font as FontStyleWriter; use PhpOffice\PhpWord\Writer\Word2007\Style\Paragraph as ParagraphStyleWriter; use PhpOffice\PhpWord\Writer\Word2007\Style\Table as TableStyleWriter; @@ -31,6 +31,7 @@ use PhpOffice\PhpWord\Writer\Word2007\Style\Table as TableStyleWriter; * Word2007 styles part writer: word/styles.xml * * @todo Do something with the numbering style introduced in 0.10.0 + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) For writeFontStyle, writeParagraphStyle, and writeTableStyle */ class Styles extends AbstractPart { @@ -59,88 +60,11 @@ class Styles extends AbstractPart continue; } - // Font style - if ($style instanceof Font) { - $paragraphStyle = $style->getParagraph(); - $styleType = $style->getStyleType(); - $type = ($styleType == 'title') ? 'paragraph' : 'character'; - if (!is_null($paragraphStyle)) { - $type = 'paragraph'; - } - - $xmlWriter->startElement('w:style'); - $xmlWriter->writeAttribute('w:type', $type); - if ($styleType == 'title') { - $arrStyle = explode('_', $styleName); - $styleId = 'Heading' . $arrStyle[1]; - $styleName = 'heading ' . $arrStyle[1]; - $styleLink = 'Heading' . $arrStyle[1] . 'Char'; - $xmlWriter->writeAttribute('w:styleId', $styleId); - - $xmlWriter->startElement('w:link'); - $xmlWriter->writeAttribute('w:val', $styleLink); - $xmlWriter->endElement(); - } - $xmlWriter->startElement('w:name'); - $xmlWriter->writeAttribute('w:val', $styleName); - $xmlWriter->endElement(); - - // Parent style - $xmlWriter->writeElementIf(!is_null($paragraphStyle), 'w:basedOn', 'w:val', 'Normal'); - - // w:pPr - if (!is_null($paragraphStyle)) { - $styleWriter = new ParagraphStyleWriter($xmlWriter, $paragraphStyle); - $styleWriter->write(); - } - - // w:rPr - $styleWriter = new FontStyleWriter($xmlWriter, $style); - $styleWriter->write(); - - $xmlWriter->endElement(); - - // Paragraph style - } elseif ($style instanceof Paragraph) { - $xmlWriter->startElement('w:style'); - $xmlWriter->writeAttribute('w:type', 'paragraph'); - $xmlWriter->writeAttribute('w:customStyle', '1'); - $xmlWriter->writeAttribute('w:styleId', $styleName); - $xmlWriter->startElement('w:name'); - $xmlWriter->writeAttribute('w:val', $styleName); - $xmlWriter->endElement(); - - // Parent style - $basedOn = $style->getBasedOn(); - $xmlWriter->writeElementIf(!is_null($basedOn), 'w:basedOn', 'w:val', $basedOn); - - // Next paragraph style - $next = $style->getNext(); - $xmlWriter->writeElementIf(!is_null($next), 'w:next', 'w:val', $next); - - // w:pPr - $styleWriter = new ParagraphStyleWriter($xmlWriter, $style); - $styleWriter->write(); - - $xmlWriter->endElement(); - - // Table style - } elseif ($style instanceof Table) { - $xmlWriter->startElement('w:style'); - $xmlWriter->writeAttribute('w:type', 'table'); - $xmlWriter->writeAttribute('w:customStyle', '1'); - $xmlWriter->writeAttribute('w:styleId', $styleName); - $xmlWriter->startElement('w:name'); - $xmlWriter->writeAttribute('w:val', $styleName); - $xmlWriter->endElement(); - $xmlWriter->startElement('w:uiPriority'); - $xmlWriter->writeAttribute('w:val', '99'); - $xmlWriter->endElement(); - - $styleWriter = new TableStyleWriter($xmlWriter, $style); - $styleWriter->write(); - - $xmlWriter->endElement(); // w:style + // Get style class and execute if the private method exists + $styleClass = substr(get_class($style), strrpos(get_class($style), '\\') + 1); + $method = "write{$styleClass}Style"; + if (method_exists($this, $method)) { + $this->$method($xmlWriter, $styleName, $style); } } } @@ -213,4 +137,115 @@ class Styles extends AbstractPart $xmlWriter->endElement(); // w:style } } + + /** + * Write font style + * + * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter + * @param string $styleName + * @param \PhpOffice\PhpWord\Style\Font $style + */ + private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $style) + { + $paragraphStyle = $style->getParagraph(); + $styleType = $style->getStyleType(); + $type = ($styleType == 'title') ? 'paragraph' : 'character'; + if (!is_null($paragraphStyle)) { + $type = 'paragraph'; + } + + $xmlWriter->startElement('w:style'); + $xmlWriter->writeAttribute('w:type', $type); + + // Heading style + if ($styleType == 'title') { + $arrStyle = explode('_', $styleName); + $styleId = 'Heading' . $arrStyle[1]; + $styleName = 'heading ' . $arrStyle[1]; + $styleLink = 'Heading' . $arrStyle[1] . 'Char'; + $xmlWriter->writeAttribute('w:styleId', $styleId); + + $xmlWriter->startElement('w:link'); + $xmlWriter->writeAttribute('w:val', $styleLink); + $xmlWriter->endElement(); + } + + // Style name + $xmlWriter->startElement('w:name'); + $xmlWriter->writeAttribute('w:val', $styleName); + $xmlWriter->endElement(); + + // Parent style + $xmlWriter->writeElementIf(!is_null($paragraphStyle), 'w:basedOn', 'w:val', 'Normal'); + + // w:pPr + if (!is_null($paragraphStyle)) { + $styleWriter = new ParagraphStyleWriter($xmlWriter, $paragraphStyle); + $styleWriter->write(); + } + + // w:rPr + $styleWriter = new FontStyleWriter($xmlWriter, $style); + $styleWriter->write(); + + $xmlWriter->endElement(); + } + + /** + * Write paragraph style + * + * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter + * @param string $styleName + * @param \PhpOffice\PhpWord\Style\Paragraph $style + */ + private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, ParagraphStyle $style) + { + $xmlWriter->startElement('w:style'); + $xmlWriter->writeAttribute('w:type', 'paragraph'); + $xmlWriter->writeAttribute('w:customStyle', '1'); + $xmlWriter->writeAttribute('w:styleId', $styleName); + $xmlWriter->startElement('w:name'); + $xmlWriter->writeAttribute('w:val', $styleName); + $xmlWriter->endElement(); + + // Parent style + $basedOn = $style->getBasedOn(); + $xmlWriter->writeElementIf(!is_null($basedOn), 'w:basedOn', 'w:val', $basedOn); + + // Next paragraph style + $next = $style->getNext(); + $xmlWriter->writeElementIf(!is_null($next), 'w:next', 'w:val', $next); + + // w:pPr + $styleWriter = new ParagraphStyleWriter($xmlWriter, $style); + $styleWriter->write(); + + $xmlWriter->endElement(); + } + + /** + * Write table style + * + * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter + * @param string $styleName + * @param \PhpOffice\PhpWord\Style\Table $style + */ + private function writeTableStyle(XMLWriter $xmlWriter, $styleName, TableStyle $style) + { + $xmlWriter->startElement('w:style'); + $xmlWriter->writeAttribute('w:type', 'table'); + $xmlWriter->writeAttribute('w:customStyle', '1'); + $xmlWriter->writeAttribute('w:styleId', $styleName); + $xmlWriter->startElement('w:name'); + $xmlWriter->writeAttribute('w:val', $styleName); + $xmlWriter->endElement(); + $xmlWriter->startElement('w:uiPriority'); + $xmlWriter->writeAttribute('w:val', '99'); + $xmlWriter->endElement(); + + $styleWriter = new TableStyleWriter($xmlWriter, $style); + $styleWriter->write(); + + $xmlWriter->endElement(); // w:style + } } diff --git a/tests/PhpWord/Tests/Shared/XMLWriterTest.php b/tests/PhpWord/Tests/Shared/XMLWriterTest.php new file mode 100644 index 00000000..08db3918 --- /dev/null +++ b/tests/PhpWord/Tests/Shared/XMLWriterTest.php @@ -0,0 +1,40 @@ +foo(); + } +} diff --git a/tests/PhpWord/Tests/Style/AbstractStyleTest.php b/tests/PhpWord/Tests/Style/AbstractStyleTest.php index 15ff8fac..d35e1090 100644 --- a/tests/PhpWord/Tests/Style/AbstractStyleTest.php +++ b/tests/PhpWord/Tests/Style/AbstractStyleTest.php @@ -45,6 +45,7 @@ class AbstractStyleTest extends \PHPUnit_Framework_TestCase $this->assertEquals(true, self::callProtectedMethod($stub, 'setBoolVal', array(true, false))); $this->assertEquals(12, self::callProtectedMethod($stub, 'setIntVal', array(12, 200))); $this->assertEquals(871.1, self::callProtectedMethod($stub, 'setFloatVal', array(871.1, 2.1))); + $this->assertEquals(871.1, self::callProtectedMethod($stub, 'setFloatVal', array('871.1', 2.1))); $this->assertEquals('a', self::callProtectedMethod($stub, 'setEnumVal', array('a', array('a', 'b'), 'b'))); } diff --git a/tests/PhpWord/Tests/Style/CellTest.php b/tests/PhpWord/Tests/Style/CellTest.php index 1a026710..f9131728 100644 --- a/tests/PhpWord/Tests/Style/CellTest.php +++ b/tests/PhpWord/Tests/Style/CellTest.php @@ -52,7 +52,11 @@ class CellTest extends \PHPUnit_Framework_TestCase foreach ($attributes as $key => $value) { $set = "set{$key}"; $get = "get{$key}"; + + $this->assertNull($object->$get()); // Init with null value + $object->$set($value); + $this->assertEquals($value, $object->$get()); } } diff --git a/tests/PhpWord/Tests/Style/FontTest.php b/tests/PhpWord/Tests/Style/FontTest.php index ca2105fb..432b29fb 100644 --- a/tests/PhpWord/Tests/Style/FontTest.php +++ b/tests/PhpWord/Tests/Style/FontTest.php @@ -74,6 +74,7 @@ class FontTest extends \PHPUnit_Framework_TestCase ); foreach ($attributes as $key => $default) { $get = is_bool($default) ? "is{$key}" : "get{$key}"; + $this->assertEquals($default, $object->$get()); $object->setStyleValue("$key", null); $this->assertEquals($default, $object->$get()); $object->setStyleValue("$key", ''); diff --git a/tests/PhpWord/Tests/Style/ParagraphTest.php b/tests/PhpWord/Tests/Style/ParagraphTest.php index 357371c6..32e46985 100644 --- a/tests/PhpWord/Tests/Style/ParagraphTest.php +++ b/tests/PhpWord/Tests/Style/ParagraphTest.php @@ -96,6 +96,20 @@ class ParagraphTest extends \PHPUnit_Framework_TestCase } } + /** + * Test get null style value + */ + public function testGetNullStyleValue() + { + $object = new Paragraph(); + + $attributes = array('spacing', 'indent', 'hanging', 'spaceBefore', 'spaceAfter'); + foreach ($attributes as $key) { + $get = "get{$key}"; + $this->assertNull($object->$get()); + } + } + /** * Test tabs */ diff --git a/tests/PhpWord/Tests/Style/TableTest.php b/tests/PhpWord/Tests/Style/TableTest.php index a7b46c1f..2afbab74 100644 --- a/tests/PhpWord/Tests/Style/TableTest.php +++ b/tests/PhpWord/Tests/Style/TableTest.php @@ -74,6 +74,9 @@ class TableTest extends \PHPUnit_Framework_TestCase 'cellMarginLeft' => 240, 'cellMarginRight' => 240, 'cellMarginBottom' => 240, + 'align' => 'center', + 'width' => 100, + 'unit' => 'pct', ); foreach ($attributes as $key => $value) { $set = "set{$key}"; @@ -146,6 +149,7 @@ class TableTest extends \PHPUnit_Framework_TestCase $this->assertEquals($value, $object->$get()); } $this->assertEquals($values, $object->getCellMargin()); + $this->assertTrue($object->hasMargin()); } /** diff --git a/tests/PhpWord/Tests/_files/documents/reader.docx b/tests/PhpWord/Tests/_files/documents/reader.docx index 5f37ef4b17d05dc992c92922b5591eb978a429a8..d09091b11983772d56172c519b702ede44df8989 100644 GIT binary patch delta 2647 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