Merge branch 'develop' into html_writer_auto_invert_text_color

This commit is contained in:
Maxim 2019-03-01 16:33:50 +02:00 committed by GitHub
commit 8931ab12b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
129 changed files with 3580 additions and 663 deletions

21
.gitattributes vendored Normal file
View File

@ -0,0 +1,21 @@
# build config
/.scrutinizer.yml export-ignore
/.travis.yml export-ignore
/php_cs.dist export-ignore
/phpmd.xml.dist export-ignore
/phpstan.neon export-ignore
/composer.lock export-ignore
# git files
/.gitignore export-ignore
/.gitattributes export-ignore
# project directories
/build export-ignore
/docs export-ignore
/samples export-ignore
# tests
/phpunit.xml.dist export-ignore
/tests export-ignore

2
.gitignore vendored
View File

@ -13,11 +13,11 @@ composer.phar
vendor
/report
/build
/samples/resources
/samples/results
/.settings
phpword.ini
/.buildpath
/.scannerwork
/.project
/nbproject
/.php_cs.cache

View File

@ -10,11 +10,20 @@ php:
- 7.0
- 7.1
- 7.2
- 7.3
matrix:
include:
- php: 5.6
- php: 7.0
env: COVERAGE=1
- php: 5.3
env: COMPOSER_MEMORY_LIMIT=2G
- php: 7.3
env: DEPENDENCIES="--ignore-platform-reqs"
exclude:
- php: 5.3
- php: 7.0
- php: 7.3
cache:
directories:
@ -32,12 +41,12 @@ before_install:
before_script:
## Deactivate xdebug if we don't do code coverage
- if [ -z "$COVERAGE" ]; then phpenv config-rm xdebug.ini ; fi
- if [ -z "$COVERAGE" ]; then phpenv config-rm xdebug.ini || echo "xdebug not available" ; fi
## Composer
- composer self-update
- travis_wait composer install --prefer-source
- travis_wait composer install --prefer-source $(if [ -n "$DEPENDENCIES" ]; then echo $DEPENDENCIES; fi)
## PHPDocumentor
- mkdir -p build/docs
##- mkdir -p build/docs
- mkdir -p build/coverage
script:
@ -52,7 +61,7 @@ script:
## PHPLOC
- if [ -z "$COVERAGE" ]; then ./vendor/bin/phploc src/ ; fi
## PHPDocumentor
- if [ -z "$COVERAGE" ]; then ./vendor/bin/phpdoc -q -d ./src -t ./build/docs --ignore "*/src/PhpWord/Shared/*/*" --template="responsive-twig" ; fi
##- if [ -z "$COVERAGE" ]; then ./vendor/bin/phpdoc -q -d ./src -t ./build/docs --ignore "*/src/PhpWord/Shared/*/*" --template="responsive-twig" ; fi
after_success:
## Coveralls

View File

@ -3,7 +3,47 @@ Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
v0.15.0 (?? ??? 2018)
v0.17.0 (?? ??? 2019)
----------------------
### Added
- Add RightToLeft table presentation. @troosan #1550
- Set complex type in template @troosan #1565
- Add support for page vertical alignment. @troosan #672 #1569
### Fixed
- Fix HTML border-color parsing. @troosan #1551 #1570
### Miscelaneous
- Use embedded http server to test loading of remote images @troosan #
v0.16.0 (30 dec 2018)
----------------------
### Added
- Add getVariableCount method in TemplateProcessor. @nicoder #1272
- Add setting Chart Title and Legend visibility @Tom-Magill #1433
- Add ability to pass a Style object in Section constructor @ndench #1416
- Add support for hidden text @Alexmg86 #1527
- Add support for setting images in TemplateProcessor @SailorMax #1170
- Add "Plain Text" type to SDT (Structured Document Tags) @morrisdj #1541
- Added possibility to index variables inside cloned block in TemplateProcessor @JPBetley #817
- Added possibility to replace variables inside cloned block with values in TemplateProcessor @DIDoS #1392
### Fixed
- Fix regex in `cloneBlock` function @nicoder #1269
- HTML Title Writer loses text when Title contains a TextRun instead a string. @begnini #1436
- Fix regex in fixBrokenMacros, make it less greedy @MuriloSo @brainwood @yurii-sio2 #1502 #1345
- 240 twips are being added to line spacing, should not happen when using lineRule fixed @troosan #1509 #1505
- Adding table layout to the generated HTML @aarangara #1441
- Fix loading of Sharepoint document @Garrcomm #1498
- RTF writer: Round getPageSizeW and getPageSizeH to avoid decimals @Patrick64 #1493
- Fix parsing of Office 365 documents @Timanx #1485
- For RTF writers, sizes should should never have decimals @Samuel-BF #1536
- Style Name Parsing fails if document generated by a non-english word version @begnini #1434
### Miscelaneous
- Get rid of duplicated code in TemplateProcessor @abcdmitry #1161
v0.15.0 (14 Jul 2018)
----------------------
### Added
- Parsing of `align` HTML attribute - @troosan #1231
@ -23,6 +63,9 @@ v0.15.0 (?? ??? 2018)
- Add support for table indent (tblInd) @Trainmaster #1343
- Added parsing of internal links in HTML reader @lalop #1336
- Several improvements to charts @JAEK-S #1332
- Add parsing of html image in base64 format @jgpATs2w #1382
- Added Support for Indentation & Tabs on RTF Writer. @smaug1985 #1405
- Allows decimal numbers in HTML line-height style @jgpATs2w #1413
### Fixed
- Fix reading of docx default style - @troosan #1238
@ -35,6 +78,9 @@ v0.15.0 (?? ??? 2018)
- Fix colspan and rowspan for tables in HTML Writer @mattbolt #1292
- Fix parsing of Heading and Title formating @troosan @gthomas2 #465
- Fix Dateformat typo, fix hours casing, add Month-Day-Year formats @ComputerTinker #591
- Support reading of w:drawing for documents produced by word 2011+ @gthomas2 #464 #1324
- Fix missing column width in ODText writer @potofcoffee #413
- Disable entity loader before parsing XML to avoid XXE injection @Tom4t0 #1427
### Changed
- Remove zend-stdlib dependency @Trainmaster #1284

View File

@ -6,7 +6,7 @@ We want to create a high quality document writer and reader library that people
- **Be brief, but be bold**. State your issues briefly. But speak out your ideas loudly, even if you can't or don't know how to implement it right away. The world will be better with limitless innovations.
- **Follow PHP-FIG standards**. We follow PHP Standards Recommendations (PSRs) by [PHP Framework Interoperability Group](http://www.php-fig.org/). If you're not familiar with these standards, please, [familiarize yourself now](https://github.com/php-fig/fig-standards). Also, please, use [PHPCodeSniffer](http://pear.php.net/package/PHP_CodeSniffer/) to validate your code against PSRs.
- **Test your code**. Nobody else knows your code better than you. So, it's completely your mission to test the changes you made before pull request submission. We use [PHPUnit](https://phpunit.de/) for our testing purposes and recommend you using this tool too. [Here](https://phpunit.de/presentations.html) you can find PHPUnit best practices and additional information on effective unit testing, which helps us making PHPWord better day to day. Do not hesitate to smoke it carefully. It's a great investment in quality of your work, and it saves you years of life.
- **Test your code**. Nobody else knows your code better than you. So, it's completely your mission to test the changes you made before pull request submission. We use [PHPUnit](https://phpunit.de/) for our testing purposes and recommend you using this tool too. [Here](https://phpunit.readthedocs.io) you can find documentation on how to write tests with PHPUnit, which helps us making PHPWord better day to day. Do not hesitate to smoke it carefully. It's a great investment in quality of your work, and it saves you years of life.
- **Request pull in separate branch**. Do not submit your request to the master branch. But create a separate branch named specifically for the issue that you addressed. Read [GitHub manual](https://help.github.com/articles/using-pull-requests) to find out more about this. If you are new to GitHub, read [this short manual](https://help.github.com/articles/fork-a-repo) to get yourself familiar with forks and how git works in general. [This video](http://www.youtube.com/watch?v=-zvHQXnBO6c) explains how to synchronize your Github Fork with the Branch of PHPWord.
That's it. Thank you for your interest in PHPWord, and welcome!

View File

@ -1,13 +1,20 @@
# ![PHPWord](https://rawgit.com/PHPOffice/PHPWord/develop/docs/images/phpword.svg "PHPWord")
Master:
[![Latest Stable Version](https://poser.pugx.org/phpoffice/phpword/v/stable.png)](https://packagist.org/packages/phpoffice/phpword)
[![Build Status](https://travis-ci.org/PHPOffice/PHPWord.svg?branch=master)](https://travis-ci.org/PHPOffice/PHPWord)
[![Code Quality](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/badges/quality-score.png?s=b5997ce59ac2816b4514f3a38de9900f6d492c1d)](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/)
[![Coverage Status](https://coveralls.io/repos/github/PHPOffice/PHPWord/badge.svg?branch=develop)](https://coveralls.io/github/PHPOffice/PHPWord?branch=develop)
[![Code Quality](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/)
[![Coverage Status](https://coveralls.io/repos/github/PHPOffice/PHPWord/badge.svg?branch=master)](https://coveralls.io/github/PHPOffice/PHPWord?branch=master)
[![Total Downloads](https://poser.pugx.org/phpoffice/phpword/downloads.png)](https://packagist.org/packages/phpoffice/phpword)
[![License](https://poser.pugx.org/phpoffice/phpword/license.png)](https://packagist.org/packages/phpoffice/phpword)
[![Join the chat at https://gitter.im/PHPOffice/PHPWord](https://img.shields.io/badge/GITTER-join%20chat-green.svg)](https://gitter.im/PHPOffice/PHPWord)
Develop:
[![Latest Development Version](https://img.shields.io/badge/unstable-dev--develop-orange.svg)](https://packagist.org/packages/phpoffice/phpword#dev-develop)
[![Build Status](https://travis-ci.org/PHPOffice/PHPWord.svg?branch=develop)](https://travis-ci.org/PHPOffice/PHPWord/branches)
[![Code Quality](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/?branch=develop)
[![Coverage Status](https://coveralls.io/repos/github/PHPOffice/PHPWord/badge.svg?branch=develop)](https://coveralls.io/github/PHPOffice/PHPWord?branch=develop)
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/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/).
@ -81,7 +88,7 @@ You can of course also manually edit your composer.json file
```json
{
"require": {
"phpoffice/phpword": "v0.14.*"
"phpoffice/phpword": "v0.16.*"
}
}
```
@ -161,7 +168,7 @@ $objWriter->save('helloWorld.html');
```
More examples are provided in the [samples folder](samples/). For an easy access to those samples launch `php -S localhost:8000` in the samples directory then browse to [http://localhost:8000](http://localhost:8000) to view the samples.
You can also read the [Developers' Documentation](http://phpword.readthedocs.org/) and the [API Documentation](http://phpoffice.github.io/PHPWord/docs/master/) for more detail.
You can also read the [Developers' Documentation](http://phpword.readthedocs.org/) for more detail.
## Contributing

View File

@ -45,7 +45,7 @@
"php-cs-fixer fix --ansi --dry-run --diff",
"phpcs --report-width=200 --report-summary --report-full samples/ src/ tests/ --ignore=src/PhpWord/Shared/PCLZip --standard=PSR2 -n",
"phpmd src/,tests/ text ./phpmd.xml.dist --exclude pclzip.lib.php",
"@test"
"@test-no-coverage"
],
"fix": [
"php-cs-fixer fix --ansi"
@ -61,18 +61,19 @@
"php": "^5.3.3 || ^7.0",
"ext-xml": "*",
"zendframework/zend-escaper": "^2.2",
"phpoffice/common": "^0.2"
"phpoffice/common": "^0.2.9"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36 || ^5.0",
"phpdocumentor/phpdocumentor":"2.*",
"squizlabs/php_codesniffer": "^2.7",
"friendsofphp/php-cs-fixer": "^2.0",
"ext-zip": "*",
"ext-gd": "*",
"phpunit/phpunit": "^4.8.36 || ^7.0",
"squizlabs/php_codesniffer": "^2.9",
"friendsofphp/php-cs-fixer": "^2.2",
"phpmd/phpmd": "2.*",
"phploc/phploc": "2.* || 3.* || 4.*",
"dompdf/dompdf":"0.8.*",
"tecnickcom/tcpdf": "6.*",
"mpdf/mpdf": "5.* || 6.* || 7.*",
"mpdf/mpdf": "5.7.4 || 6.* || 7.*",
"php-coveralls/php-coveralls": "1.1.0 || ^2.0"
},
"suggest": {
@ -89,7 +90,7 @@
},
"extra": {
"branch-alias": {
"dev-develop": "0.15-dev"
"dev-develop": "0.17-dev"
}
}
}

View File

@ -8,4 +8,4 @@ Fixes # (issue)
- [ ] I have run `composer run-script check --timeout=0` and no errors were reported
- [ ] The new code is covered by unit tests (check build/coverage for coverage report)
- [ ] I have update the documentation to describe the changes
- [ ] I have updated the documentation to describe the changes

View File

@ -48,7 +48,7 @@ copyright = u'2014-2017, PHPWord Contributors'
# built documents.
#
# The short X.Y version.
version = '0.14.0'
version = '0.16.0'
# The full version, including alpha/beta/rc tags.
release = version

View File

@ -104,6 +104,12 @@ You can pass an optional parameter to specify where the header/footer should be
- ``Footer::FIRST`` each first page of the section
- ``Footer::EVEN`` each even page of the section. Will only be applied if the evenAndOddHeaders is set to true in phpWord->settings
To change the evenAndOddHeaders use the ``getSettings`` method to return the Settings object, and then call the ``setEvenAndOddHeaders`` method:
.. code-block:: php
$phpWord->getSettings()->setEvenAndOddHeaders(true);
Footers
-------

View File

@ -61,7 +61,7 @@ Legend:
Texts
-----
Text can be added by using ``addText`` and ``addTextRun`` method.
Text can be added by using ``addText`` and ``addTextRun`` methods.
``addText`` is used for creating simple paragraphs that only contain texts with the same style.
``addTextRun`` is used for creating complex paragraphs that contain text with different style (some bold, other
italics, etc) or other elements, e.g. images or links. The syntaxes are as follow:
@ -155,13 +155,18 @@ method or using the ``pageBreakBefore`` style of paragraph.
Lists
-----
To add a list item use the function ``addListItem``.
Lists can be added by using ``addListItem`` and ``addListItemRun`` methods.
``addListItem`` is used for creating lists that only contain plain text.
``addListItemRun`` is used for creating complex list items that contains texts
with different style (some bold, other italics, etc) or other elements, e.g.
images or links. The syntaxes are as follow:
Basic usage:
.. code-block:: php
$section->addListItem($text, [$depth], [$fontStyle], [$listStyle], [$paragraphStyle]);
$listItemRun = $section->addListItemRun([$depth], [$listStyle], [$paragraphStyle])
Parameters:
@ -172,6 +177,8 @@ Parameters:
TYPE\_ALPHANUM, TYPE\_BULLET\_FILLED, etc. See list of constants in PHPWord\\Style\\ListItem.
- ``$paragraphStyle``. See :ref:`paragraph-style`.
See ``Sample_09_Tables.php`` for more code sample.
Advanced usage:
You can also create your own numbering style by changing the ``$listStyle`` parameter with the name of your numbering style.

View File

@ -29,8 +29,11 @@ Available Section style options:
- ``marginRight``. Page margin right in *twip*.
- ``marginBottom``. Page margin bottom in *twip*.
- ``orientation``. Page orientation (``portrait``, which is default, or ``landscape``).
See ``\PhpOffice\PhpWord\Style\Section::ORIENTATION_...`` class constants for possible values
- ``pageSizeH``. Page height in *twip*. Implicitly defined by ``orientation`` option. Any changes are discouraged.
- ``pageSizeW``. Page width in *twip*. Implicitly defined by ``orientation`` option. Any changes are discouraged.
- ``vAlign``. Vertical Page Alignment
See ``\PhpOffice\PhpWord\SimpleType\VerticalJc`` for possible values
.. _font-style:
@ -45,7 +48,7 @@ Available Font style options:
- ``color``. Font color, e.g. *FF0000*.
- ``doubleStrikethrough``. Double strikethrough, *true* or *false*.
- ``fgColor``. Font highlight color, e.g. *yellow*, *green*, *blue*.
See ``\PhpOffice\PhpWord\Style\Font::FGCOLOR_...`` constants for more values
See ``\PhpOffice\PhpWord\Style\Font::FGCOLOR_...`` class constants for possible values
- ``hint``. Font content type, *default*, *eastAsia*, or *cs*.
- ``italic``. Italic, *true* or *false*.
- ``name``. Font name, e.g. *Arial*.
@ -56,10 +59,11 @@ Available Font style options:
- ``subScript``. Subscript, *true* or *false*.
- ``superScript``. Superscript, *true* or *false*.
- ``underline``. Underline, *single*, *dash*, *dotted*, etc.
See ``\PhpOffice\PhpWord\Style\Font::UNDERLINE_...`` constants for more values
See ``\PhpOffice\PhpWord\Style\Font::UNDERLINE_...`` class constants for possible values
- ``lang``. Language, either a language code like *en-US*, *fr-BE*, etc. or an object (or as an array) if you need to set eastAsian or bidirectional languages
See ``\PhpOffice\PhpWord\Style\Language`` class for some language codes.
- ``position``. The text position, raised or lowered, in half points
- ``hidden``. Hidden text, *true* or *false*.
.. _paragraph-style:
@ -69,7 +73,7 @@ Paragraph
Available Paragraph style options:
- ``alignment``. Supports all alignment modes since 1st Edition of ECMA-376 standard up till ISO/IEC 29500:2012.
See ``\PhpOffice\PhpWord\SimpleType\Jc`` class for the details.
See ``\PhpOffice\PhpWord\SimpleType\Jc`` class constants for possible values.
- ``basedOn``. Parent style.
- ``hanging``. Hanging in *twip*.
- ``indent``. Indent in *twip*.
@ -80,8 +84,9 @@ Available Paragraph style options:
- ``pageBreakBefore``. Start paragraph on next page, *true* or *false*.
- ``spaceBefore``. Space before paragraph in *twip*.
- ``spaceAfter``. Space after paragraph in *twip*.
- ``spacing``. Space between lines.
- ``spacing``. Space between lines in *twip*. If spacingLineRule is auto, 240 (height of 1 line) will be added, so if you want a double line height, set this to 240.
- ``spacingLineRule``. Line Spacing Rule. *auto*, *exact*, *atLeast*
See ``\PhpOffice\PhpWord\SimpleType\LineSpacingRule`` class constants for possible values.
- ``suppressAutoHyphens``. Hyphenation for paragraph, *true* or *false*.
- ``tabs``. Set of custom tab stops.
- ``widowControl``. Allow first/last line to display on a separate page, *true* or *false*.
@ -89,7 +94,7 @@ Available Paragraph style options:
- ``bidi``. Right to Left Paragraph Layout, *true* or *false*.
- ``shading``. Paragraph Shading.
- ``textAlignment``. Vertical Character Alignment on Line.
See ``\PhpOffice\PhpWord\SimpleType\TextAlignment`` class for possible values.
See ``\PhpOffice\PhpWord\SimpleType\TextAlignment`` class constants for possible values.
.. _table-style:
@ -99,17 +104,18 @@ Table
Available Table style options:
- ``alignment``. Supports all alignment modes since 1st Edition of ECMA-376 standard up till ISO/IEC 29500:2012.
See ``\PhpOffice\PhpWord\SimpleType\JcTable`` and ``\PhpOffice\PhpWord\SimpleType\Jc`` classes for the details.
See ``\PhpOffice\PhpWord\SimpleType\JcTable`` and ``\PhpOffice\PhpWord\SimpleType\Jc`` class constants for possible values.
- ``bgColor``. Background color, e.g. '9966CC'.
- ``border(Top|Right|Bottom|Left)Color``. Border color, e.g. '9966CC'.
- ``border(Top|Right|Bottom|Left)Size``. Border size in *twip*.
- ``cellMargin(Top|Right|Bottom|Left)``. Cell margin in *twip*.
- ``indent``. Table indent from leading margin. Must be an instance of ``\PhpOffice\PhpWord\ComplexType\TblWidth``.
- ``width``. Table width in percent.
- ``width``. Table width in Fiftieths of a Percent or Twentieths of a Point.
- ``unit``. The unit to use for the width. One of ``\PhpOffice\PhpWord\SimpleType\TblWidth``. Defaults to *auto*.
- ``layout``. Table layout, either *fixed* or *autofit* See ``\PhpOffice\PhpWord\Style\Table`` for constants.
- ``cellSpacing`` Cell spacing in *twip*
- ``position`` Floating Table Positioning, see below for options
- ``bidiVisual`` Present table as Right-To-Left
Floating Table Positioning options:
@ -168,7 +174,7 @@ Numbering level
Available NumberingLevel style options:
- ``alignment``. Supports all alignment modes since 1st Edition of ECMA-376 standard up till ISO/IEC 29500:2012.
See ``\PhpOffice\PhpWord\SimpleType\Jc`` class for the details.
See ``\PhpOffice\PhpWord\SimpleType\Jc`` class constants for possible values.
- ``font``. Font name.
- ``format``. Numbering format bullet\|decimal\|upperRoman\|lowerRoman\|upperLetter\|lowerLetter.
- ``hanging``. See paragraph style.
@ -190,6 +196,14 @@ Available Chart style options:
- ``width``. Width (in EMU).
- ``height``. Height (in EMU).
- ``3d``. Is 3D; applies to pie, bar, line, area, *true* or *false*.
- ``colors``. A list of colors to use in the chart.
- ``title``. The title for the chart.
- ``showLegend``. Show legend, *true* or *false*.
- ``categoryLabelPosition``. Label position for categories, *nextTo* (default), *low* or *high*.
- ``valueLabelPosition``. Label position for values, *nextTo* (default), *low* or *high*.
- ``categoryAxisTitle``. The title for the category axis.
- ``valueAxisTitle``. The title for the values axis.
- ``majorTickMarkPos``. The position for major tick marks, *in*, *out*, *cross*, *none* (default).
- ``showAxisLabels``. Show labels for axis, *true* or *false*.
- ``gridX``. Show Gridlines for X-Axis, *true* or *false*.
- ``gridY``. Show Gridlines for Y-Axis, *true* or *false*.

View File

@ -4,22 +4,243 @@ Templates processing
====================
You can create an OOXML document template with included search-patterns (macros) which can be replaced by any value you wish. Only single-line values can be replaced.
Macros are defined like this: ``${search-pattern}``.
To load a template file, create a new instance of the TemplateProcessor.
To deal with a template file, use ``new TemplateProcessor`` statement. After TemplateProcessor instance creation the document template is copied into the temporary directory. Then you can use ``TemplateProcessor::setValue`` method to change the value of a search pattern. The search-pattern model is: ``${search-pattern}``.
.. code-block:: php
$templateProcessor = new TemplateProcessor('Template.docx');
setValue
""""""""
Given a template containing
.. code-block:: clean
Hello ${firstname} ${lastname}!
The following will replace ``${firstname}`` with ``John``, and ``${lastname}`` with ``Doe`` .
The resulting document will now contain ``Hello John Doe!``
.. code-block:: php
$templateProcessor->setValue('firstname', 'John');
$templateProcessor->setValue('lastname', 'Doe');
setValues
"""""""""
You can also set multiple values by passing all of them in an array.
.. code-block:: php
$templateProcessor->setValues(array('firstname' => 'John', 'lastname' => 'Doe'));
setImageValue
"""""""""""""
The search-pattern model for images can be like:
- ``${search-image-pattern}``
- ``${search-image-pattern:[width]:[height]:[ratio]}``
- ``${search-image-pattern:[width]x[height]}``
- ``${search-image-pattern:size=[width]x[height]}``
- ``${search-image-pattern:width=[width]:height=[height]:ratio=false}``
Where:
- [width] and [height] can be just numbers or numbers with measure, which supported by Word (cm, mm, in, pt, pc, px, %, em, ex)
- [ratio] uses only for ``false``, ``-`` or ``f`` to turn off respect aspect ration of image. By default template image size uses as 'container' size.
Example:
.. code-block:: clean
${CompanyLogo}
${UserLogo:50:50} ${Name} - ${City} - ${Street}
.. code-block:: php
$templateProcessor = new TemplateProcessor('Template.docx');
$templateProcessor->setValue('Name', 'John Doe');
$templateProcessor->setValue(array('City', 'Street'), array('Detroit', '12th Street'));
It is not possible to directly add new OOXML elements to the template file being processed, but it is possible to transform headers, main document part, and footers of the template using XSLT (see ``TemplateProcessor::applyXslStyleSheet``).
$templateProcessor->setImageValue('CompanyLogo', 'path/to/company/logo.png');
$templateProcessor->setImageValue('UserLogo', array('path' => 'path/to/logo.png', 'width' => 100, 'height' => 100, 'ratio' => false));
See ``Sample_07_TemplateCloneRow.php`` for example on how to create
multirow from a single row in a template by using ``TemplateProcessor::cloneRow``.
cloneBlock
""""""""""
Given a template containing
See ``Sample_23_TemplateBlock.php`` for an example.
See ``Sample_23_TemplateBlock.php`` for example on how to clone a block
of text using ``TemplateProcessor::cloneBlock`` and delete a block of text using
``TemplateProcessor::deleteBlock``.
.. code-block:: clean
${block_name}
Customer: ${customer_name}
Address: ${customer_address}
${/block_name}
The following will duplicate everything between ``${block_name}`` and ``${/block_name}`` 3 times.
.. code-block:: php
$templateProcessor->cloneBlock('block_name', 3, true, true);
The last parameter will rename any macro defined inside the block and add #1, #2, #3 ... to the macro name.
The result will be
.. code-block:: clean
Customer: ${customer_name#1}
Address: ${customer_address#1}
Customer: ${customer_name#2}
Address: ${customer_address#2}
Customer: ${customer_name#3}
Address: ${customer_address#3}
It is also possible to pass an array with the values to replace the marcros with.
If an array with replacements is passed, the ``count`` argument is ignored, it is the size of the array that counts.
.. code-block:: php
$replacements = array(
array('customer_name' => 'Batman', 'customer_address' => 'Gotham City'),
array('customer_name' => 'Superman', 'customer_address' => 'Metropolis'),
);
$templateProcessor->cloneBlock('block_name', 0, true, false, $replacements);
The result will then be
.. code-block:: clean
Customer: Batman
Address: Gotham City
Customer: Superman
Address: Metropolis
replaceBlock
""""""""""""
Given a template containing
.. code-block:: clean
${block_name}
This block content will be replaced
${/block_name}
The following will replace everything between``${block_name}`` and ``${/block_name}`` with the value passed.
.. code-block:: php
$templateProcessor->replaceBlock('block_name', 'This is the replacement text.');
deleteBlock
"""""""""""
Same as previous, but it deletes the block
.. code-block:: php
$templateProcessor->deleteBlock('block_name');
cloneRow
""""""""
Clones a table row in a template document.
See ``Sample_07_TemplateCloneRow.php`` for an example.
.. code-block:: clean
+-----------+----------------+
| ${userId} | ${userName} |
| |----------------+
| | ${userAddress} |
+-----------+----------------+
.. code-block:: php
$templateProcessor->cloneRow('userId', 2);
Will result in
.. code-block:: clean
+-------------+------------------+
| ${userId#1} | ${userName#1} |
| |------------------+
| | ${userAddress#1} |
+-------------+------------------+
| ${userId#2} | ${userName#2} |
| |------------------+
| | ${userAddress#2} |
+-------------+------------------+
cloneRowAndSetValues
""""""""""""""""""""
Finds a row in a table row identified by `$search` param and clones it as many times as there are entries in `$values`.
.. code-block:: clean
+-----------+----------------+
| ${userId} | ${userName} |
| |----------------+
| | ${userAddress} |
+-----------+----------------+
.. code-block:: php
$values = [
['userId' => 1, 'userName' => 'Batman', 'userAddress' => 'Gotham City'],
['userId' => 2, 'userName' => 'Superman', 'userAddress' => 'Metropolis'],
];
$templateProcessor->cloneRowAndSetValues('userId', );
Will result in
.. code-block:: clean
+---+-------------+
| 1 | Batman |
| |-------------+
| | Gotham City |
+---+-------------+
| 2 | Superman |
| |-------------+
| | Metropolis |
+---+-------------+
applyXslStyleSheet
""""""""""""""""""
Applies the XSL stylesheet passed to header part, footer part and main part
.. code-block:: php
$xslDomDocument = new \DOMDocument();
$xslDomDocument->load('/path/to/my/stylesheet.xsl');
$templateProcessor->applyXslStyleSheet($xslDomDocument);
setComplexValue
"""""""""""""""
Raplaces a ${macro} with the ComplexType passed.
See ``Sample_40_TemplateSetComplexValue.php`` for examples.
.. code-block:: php
$inline = new TextRun();
$inline->addText('by a red italic text', array('italic' => true, 'color' => 'red'));
$templateProcessor->setComplexValue('inline', $inline);
setComplexBlock
"""""""""""""""
Raplaces a ${macro} with the ComplexType passed.
See ``Sample_40_TemplateSetComplexValue.php`` for examples.
.. code-block:: php
$table = new Table(array('borderSize' => 12, 'borderColor' => 'green', 'width' => 6000, 'unit' => TblWidth::TWIP));
$table->addRow();
$table->addCell(150)->addText('Cell A1');
$table->addCell(150)->addText('Cell A2');
$table->addCell(150)->addText('Cell A3');
$table->addRow();
$table->addCell(150)->addText('Cell B1');
$table->addCell(150)->addText('Cell B2');
$table->addCell(150)->addText('Cell B3');
$templateProcessor->setComplexBlock('table', $table);

View File

@ -1,7 +1,7 @@
includes:
- vendor/phpstan/phpstan/conf/config.level1.neon
parameters:
memory-limit: 200000
memory-limit: 20000000
autoload_directories:
- tests
autoload_files:

View File

@ -6,8 +6,7 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false">
stopOnFailure="false">
<testsuites>
<testsuite name="PhpWord Test Suite">
<directory>./tests/PhpWord</directory>
@ -22,7 +21,8 @@
</whitelist>
</filter>
<logging>
<log type="coverage-html" target="./build/coverage" charset="UTF-8" highlight="true" />
<log type="coverage-html" target="./build/coverage" />
<log type="coverage-clover" target="./build/logs/clover.xml" />
<log type="junit" target="./build/logs/logfile.xml"/>
</logging>
</phpunit>

View File

@ -1,6 +1,5 @@
<?php
use PhpOffice\PhpWord\Style\Font;
use PhpOffice\PhpWord\Style\Paragraph;
include_once 'Sample_Header.php';

View File

@ -1,4 +1,6 @@
<?php
use PhpOffice\PhpWord\SimpleType\VerticalJc;
include_once 'Sample_Header.php';
// New Word Document
@ -21,6 +23,12 @@ $section = $phpWord->addSection(
);
$section->addText('This section uses other margins with folio papersize.');
// The text of this section is vertically centered
$section = $phpWord->addSection(
array('vAlign' => VerticalJc::CENTER)
);
$section->addText('This section is vertically centered.');
// New portrait section with Header & Footer
$section = $phpWord->addSection(
array(

View File

@ -39,6 +39,9 @@ $textrun->addText(' Sample Object: ');
$textrun->addObject('resources/_sheet.xls');
$textrun->addText(' Here is some more text. ');
$textrun = $section->addTextRun();
$textrun->addText('This text is not visible.', array('hidden' => true));
// Save file
echo write($phpWord, basename(__FILE__, '.php'), $writers);
if (!CLI) {

View File

@ -36,22 +36,46 @@ $templateProcessor->setValue('rowNumber#9', '9');
$templateProcessor->setValue('rowNumber#10', '10');
// Table with a spanned cell
$templateProcessor->cloneRow('userId', 3);
$values = array(
array(
'userId' => 1,
'userFirstName' => 'James',
'userName' => 'Taylor',
'userPhone' => '+1 428 889 773',
),
array(
'userId' => 2,
'userFirstName' => 'Robert',
'userName' => 'Bell',
'userPhone' => '+1 428 889 774',
),
array(
'userId' => 3,
'userFirstName' => 'Michael',
'userName' => 'Ray',
'userPhone' => '+1 428 889 775',
),
);
$templateProcessor->setValue('userId#1', '1');
$templateProcessor->setValue('userFirstName#1', 'James');
$templateProcessor->setValue('userName#1', 'Taylor');
$templateProcessor->setValue('userPhone#1', '+1 428 889 773');
$templateProcessor->cloneRowAndSetValues('userId', $values);
$templateProcessor->setValue('userId#2', '2');
$templateProcessor->setValue('userFirstName#2', 'Robert');
$templateProcessor->setValue('userName#2', 'Bell');
$templateProcessor->setValue('userPhone#2', '+1 428 889 774');
//this is equivalent to cloning and settings values with cloneRowAndSetValues
// $templateProcessor->cloneRow('userId', 3);
$templateProcessor->setValue('userId#3', '3');
$templateProcessor->setValue('userFirstName#3', 'Michael');
$templateProcessor->setValue('userName#3', 'Ray');
$templateProcessor->setValue('userPhone#3', '+1 428 889 775');
// $templateProcessor->setValue('userId#1', '1');
// $templateProcessor->setValue('userFirstName#1', 'James');
// $templateProcessor->setValue('userName#1', 'Taylor');
// $templateProcessor->setValue('userPhone#1', '+1 428 889 773');
// $templateProcessor->setValue('userId#2', '2');
// $templateProcessor->setValue('userFirstName#2', 'Robert');
// $templateProcessor->setValue('userName#2', 'Bell');
// $templateProcessor->setValue('userPhone#2', '+1 428 889 774');
// $templateProcessor->setValue('userId#3', '3');
// $templateProcessor->setValue('userFirstName#3', 'Michael');
// $templateProcessor->setValue('userName#3', 'Ray');
// $templateProcessor->setValue('userPhone#3', '+1 428 889 775');
echo date('H:i:s'), ' Saving the result document...', EOL;
$templateProcessor->saveAs('results/Sample_07_TemplateCloneRow.docx');

View File

@ -7,13 +7,8 @@ $source = "resources/{$name}.doc";
echo date('H:i:s'), " Reading contents from `{$source}`", EOL;
$phpWord = \PhpOffice\PhpWord\IOFactory::load($source, 'MsDoc');
// (Re)write contents
$writers = array('Word2007' => 'docx', 'ODText' => 'odt', 'RTF' => 'rtf');
foreach ($writers as $writer => $extension) {
echo date('H:i:s'), " Write to {$writer} format", EOL;
$xmlWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, $writer);
$xmlWriter->save("{$name}.{$extension}");
rename("{$name}.{$extension}", "results/{$name}.{$extension}");
// Save file
echo write($phpWord, basename(__FILE__, '.php'), $writers);
if (!CLI) {
include_once 'Sample_Footer.php';
}
include_once 'Sample_Footer.php';

View File

@ -64,7 +64,7 @@ $section->addText('List with inline formatting.');
$listItemRun = $section->addListItemRun();
$listItemRun->addText('List item 1');
$listItemRun->addText(' in bold', array('bold' => true));
$listItemRun = $section->addListItemRun();
$listItemRun = $section->addListItemRun(1, $predefinedMultilevelStyle, $paragraphStyleName);
$listItemRun->addText('List item 2');
$listItemRun->addText(' in italic', array('italic' => true));
$footnote = $listItemRun->addFootnote();

View File

@ -14,7 +14,7 @@ $templateProcessor->deleteBlock('DELETEME');
echo date('H:i:s'), ' Saving the result document...', EOL;
$templateProcessor->saveAs('results/Sample_23_TemplateBlock.docx');
echo getEndingNotes(array('Word2007' => 'docx'), 'results/Sample_23_TemplateBlock.docx');
echo getEndingNotes(array('Word2007' => 'docx'), 'Sample_23_TemplateBlock');
if (!CLI) {
include_once 'Sample_Footer.php';
}

View File

@ -74,7 +74,7 @@ $html .= '<table align="center" style="width: 50%; border: 6px #0000FF double;">
</tr>
</thead>
<tbody>
<tr><td style="border-style: dotted;">1</td><td colspan="2">2</td></tr>
<tr><td style="border-style: dotted; border-color: #FF0000">1</td><td colspan="2">2</td></tr>
<tr><td>This is <b>bold</b> text</td><td></td><td>6</td></tr>
</tbody>
</table>';
@ -89,6 +89,9 @@ $html .= '<table align="center" style="width: 80%; border: 6px #0000FF double;">
<tr><td style="text-align: center;">Cell in parent table</td></tr>
</table>';
$html .= '<p style="margin-top: 240pt;">The text below is not visible, click on show/hide to reveil it:</p>';
$html .= '<p style="display: none">This is hidden text</p>';
\PhpOffice\PhpWord\Shared\Html::addHtml($section, $html, false, false);
// Save file

View File

@ -14,6 +14,29 @@ $textrun->addText('This is a Left to Right paragraph.');
$textrun = $section->addTextRun(array('alignment' => \PhpOffice\PhpWord\SimpleType\Jc::END));
$textrun->addText('سلام این یک پاراگراف راست به چپ است', array('rtl' => true));
$section->addText('Table visually presented as RTL');
$style = array('rtl' => true, 'size' => 12);
$tableStyle = array('borderSize' => 6, 'borderColor' => '000000', 'width' => 5000, 'unit' => \PhpOffice\PhpWord\SimpleType\TblWidth::PERCENT, 'bidiVisual' => true);
$table = $section->addTable($tableStyle);
$cellHCentered = array('alignment' => \PhpOffice\PhpWord\SimpleType\Jc::CENTER);
$cellHEnd = array('alignment' => \PhpOffice\PhpWord\SimpleType\Jc::END);
$cellVCentered = array('valign' => \PhpOffice\PhpWord\Style\Cell::VALIGN_CENTER);
//Vidually bidirectinal table
$table->addRow();
$cell = $table->addCell(500, $cellVCentered);
$textrun = $cell->addTextRun($cellHCentered);
$textrun->addText('ردیف', $style);
$cell = $table->addCell(11000);
$textrun = $cell->addTextRun($cellHEnd);
$textrun->addText('سوالات', $style);
$cell = $table->addCell(500, $cellVCentered);
$textrun = $cell->addTextRun($cellHCentered);
$textrun->addText('بارم', $style);
// Save file
echo write($phpWord, basename(__FILE__, '.php'), $writers);
if (!CLI) {

View File

@ -0,0 +1,45 @@
<?php
use PhpOffice\PhpWord\Element\Field;
use PhpOffice\PhpWord\Element\Table;
use PhpOffice\PhpWord\Element\TextRun;
use PhpOffice\PhpWord\SimpleType\TblWidth;
include_once 'Sample_Header.php';
// Template processor instance creation
echo date('H:i:s'), ' Creating new TemplateProcessor instance...', EOL;
$templateProcessor = new \PhpOffice\PhpWord\TemplateProcessor('resources/Sample_40_TemplateSetComplexValue.docx');
$title = new TextRun();
$title->addText('This title has been set ', array('bold' => true, 'italic' => true, 'color' => 'blue'));
$title->addText('dynamically', array('bold' => true, 'italic' => true, 'color' => 'red', 'underline' => 'single'));
$templateProcessor->setComplexBlock('title', $title);
$inline = new TextRun();
$inline->addText('by a red italic text', array('italic' => true, 'color' => 'red'));
$templateProcessor->setComplexValue('inline', $inline);
$table = new Table(array('borderSize' => 12, 'borderColor' => 'green', 'width' => 6000, 'unit' => TblWidth::TWIP));
$table->addRow();
$table->addCell(150)->addText('Cell A1');
$table->addCell(150)->addText('Cell A2');
$table->addCell(150)->addText('Cell A3');
$table->addRow();
$table->addCell(150)->addText('Cell B1');
$table->addCell(150)->addText('Cell B2');
$table->addCell(150)->addText('Cell B3');
$templateProcessor->setComplexBlock('table', $table);
$field = new Field('DATE', array('dateformat' => 'dddd d MMMM yyyy H:mm:ss'), array('PreserveFormat'));
$templateProcessor->setComplexValue('field', $field);
// $link = new Link('https://github.com/PHPOffice/PHPWord');
// $templateProcessor->setComplexValue('link', $link);
echo date('H:i:s'), ' Saving the result document...', EOL;
$templateProcessor->saveAs('results/Sample_40_TemplateSetComplexValue.docx');
echo getEndingNotes(array('Word2007' => 'docx'), 'results/Sample_40_TemplateSetComplexValue.docx');
if (!CLI) {
include_once 'Sample_Footer.php';
}

View File

@ -22,7 +22,7 @@ if (!CLI) {
<a class="btn btn-lg btn-primary" href="http://phpword.readthedocs.org/" role="button"><i class="fa fa-book fa-lg" title="Docs"></i> Read the Docs</a>
</p>
</div>
<?php
<?php
}
if (!CLI) {
echo '<h3>Requirement check:</h3>';

View File

@ -11,5 +11,15 @@
<ul><li>Item 1</li><li>Item 2</li><ul><li>Item 2.1</li><li>Item 2.1</li></ul></ul>
<p>Ordered (numbered) list:</p>
<ol><li>Item 1</li><li>Item 2</li></ol>
<p style="line-height:2">Double height</p>
<h2>Includes images</h2>
<img src="https://phpword.readthedocs.io/en/latest/_images/phpword.png" alt=""/>
<img src="http://php.net/images/logos/php-med-trans-light.gif" name="Imagen 12" align="bottom" width="208" height="183" border="0"/>
<img src="http://php.net/images/logos/php-icon.png" name="Imagen 13" align="bottom" width="143" height="202" border="0"/>
<img src="http://php.net/images/logos/php-med-trans-light.gif" name="Imagen 14" align="bottom" width="194" height="188" border="0"/>
</body>
</html>

0
samples/results/.gitignore vendored Normal file → Executable file
View File

17
sonar-project.properties Normal file
View File

@ -0,0 +1,17 @@
# must be unique in a given SonarQube instance
sonar.projectKey=phpoffice:phpword
# this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.
sonar.projectName=PHPWord
sonar.projectVersion=0.16
# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
# This property is optional if sonar.modules is set.
sonar.sources=src
sonar.tests=tests
sonar.php.coverage.reportPaths=build/logs/clover.xml
sonar.php.tests.reportPath=build/logs/logfile.xml
# Encoding of the source code. Default is default system encoding
#sonar.sourceEncoding=UTF-8
sonar.host.url=http://localhost:9000

View File

@ -31,11 +31,11 @@ namespace PhpOffice\PhpWord\Element;
* @method Footnote addFootnote(mixed $pStyle = null)
* @method Endnote addEndnote(mixed $pStyle = null)
* @method CheckBox addCheckBox(string $name, $text, mixed $fStyle = null, mixed $pStyle = null)
* @method Title addTitle(string $text, int $depth = 1)
* @method Title addTitle(mixed $text, int $depth = 1)
* @method TOC addTOC(mixed $fontStyle = null, mixed $tocStyle = null, int $minDepth = 1, int $maxDepth = 9)
* @method PageBreak addPageBreak()
* @method Table addTable(mixed $style = null)
* @method Image addImage(string $source, mixed $style = null, bool $isWatermark = false)
* @method Image addImage(string $source, mixed $style = null, bool $isWatermark = false, $name = null)
* @method OLEObject addOLEObject(string $source, mixed $style = null)
* @method TextBox addTextBox(mixed $style = null)
* @method Field addField(string $type = null, array $properties = array(), array $options = array(), mixed $text = null)

View File

@ -347,7 +347,7 @@ abstract class AbstractElement
*
* @param \PhpOffice\PhpWord\Element\AbstractElement $container
*/
public function setParentContainer(AbstractElement $container)
public function setParentContainer(self $container)
{
$this->parentContainer = substr(get_class($container), strrpos(get_class($container), '\\') + 1);
$this->parent = $container;

View File

@ -65,6 +65,13 @@ class Image extends AbstractElement
*/
private $watermark;
/**
* Name of image
*
* @var string
*/
private $name;
/**
* Image type
*
@ -127,15 +134,17 @@ class Image extends AbstractElement
* @param string $source
* @param mixed $style
* @param bool $watermark
* @param string $name
*
* @throws \PhpOffice\PhpWord\Exception\InvalidImageException
* @throws \PhpOffice\PhpWord\Exception\UnsupportedImageTypeException
*/
public function __construct($source, $style = null, $watermark = false)
public function __construct($source, $style = null, $watermark = false, $name = null)
{
$this->source = $source;
$this->setIsWatermark($watermark);
$this->style = $this->setNewStyle(new ImageStyle(), $style, true);
$this->setIsWatermark($watermark);
$this->setName($name);
$this->checkImage();
}
@ -170,6 +179,26 @@ class Image extends AbstractElement
return $this->sourceType;
}
/**
* Sets the image name
*
* @param string $value
*/
public function setName($value)
{
$this->name = $value;
}
/**
* Get image name
*
* @return null|string
*/
public function getName()
{
return $this->name;
}
/**
* Get image media ID
*

View File

@ -83,7 +83,7 @@ class OLEObject extends AbstractElement
$this->style = $this->setNewStyle(new ImageStyle(), $style, true);
$this->icon = realpath(__DIR__ . "/../resources/{$ext}.png");
return $this;
return;
}
throw new InvalidObjectException();

View File

@ -29,7 +29,7 @@ class PreserveText extends AbstractElement
/**
* Text content
*
* @var string
* @var string|array
*/
private $text;
@ -64,8 +64,6 @@ class PreserveText extends AbstractElement
if (isset($matches[0])) {
$this->text = $matches;
}
return $this;
}
/**
@ -91,7 +89,7 @@ class PreserveText extends AbstractElement
/**
* Get Text content
*
* @return string
* @return string|array
*/
public function getText()
{

View File

@ -90,7 +90,7 @@ class SDT extends Text
*/
public function setType($value)
{
$enum = array('comboBox', 'dropDownList', 'date');
$enum = array('plainText', 'comboBox', 'dropDownList', 'date');
$this->type = $this->setEnumVal($value, $enum, 'comboBox');
return $this;

View File

@ -59,14 +59,16 @@ class Section extends AbstractContainer
* Create new instance
*
* @param int $sectionCount
* @param array $style
* @param null|array|\PhpOffice\PhpWord\Style $style
*/
public function __construct($sectionCount, $style = null)
{
$this->sectionId = $sectionCount;
$this->setDocPart($this->container, $this->sectionId);
$this->style = new SectionStyle();
$this->setStyle($style);
if (null === $style) {
$style = new SectionStyle();
}
$this->style = $this->setNewStyle(new SectionStyle(), $style);
}
/**

View File

@ -135,18 +135,40 @@ class Table extends AbstractElement
public function countColumns()
{
$columnCount = 0;
if (is_array($this->rows)) {
$rowCount = count($this->rows);
for ($i = 0; $i < $rowCount; $i++) {
/** @var \PhpOffice\PhpWord\Element\Row $row Type hint */
$row = $this->rows[$i];
$cellCount = count($row->getCells());
if ($columnCount < $cellCount) {
$columnCount = $cellCount;
}
$rowCount = count($this->rows);
for ($i = 0; $i < $rowCount; $i++) {
/** @var \PhpOffice\PhpWord\Element\Row $row Type hint */
$row = $this->rows[$i];
$cellCount = count($row->getCells());
if ($columnCount < $cellCount) {
$columnCount = $cellCount;
}
}
return $columnCount;
}
/**
* The first declared cell width for each column
*
* @return int[]
*/
public function findFirstDefinedCellWidths()
{
$cellWidths = array();
foreach ($this->rows as $row) {
$cells = $row->getCells();
if (count($cells) <= count($cellWidths)) {
continue;
}
$cellWidths = array();
foreach ($cells as $cell) {
$cellWidths[] = $cell->getWidth();
}
}
return $cellWidths;
}
}

View File

@ -61,14 +61,12 @@ class Title extends AbstractElement
*/
public function __construct($text, $depth = 1)
{
if (isset($text)) {
if (is_string($text)) {
$this->text = CommonText::toUTF8($text);
} elseif ($text instanceof TextRun) {
$this->text = $text;
} else {
throw new \InvalidArgumentException('Invalid text, should be a string or a TextRun');
}
if (is_string($text)) {
$this->text = CommonText::toUTF8($text);
} elseif ($text instanceof TextRun) {
$this->text = $text;
} else {
throw new \InvalidArgumentException('Invalid text, should be a string or a TextRun');
}
$this->depth = $depth;
@ -76,8 +74,6 @@ class Title extends AbstractElement
if (array_key_exists($styleName, Style::getStyles())) {
$this->style = str_replace('_', '', $styleName);
}
return $this;
}
/**

View File

@ -58,13 +58,13 @@ class TrackChange extends AbstractContainer
*
* @param string $changeType
* @param string $author
* @param null|int|\DateTime $date
* @param null|int|bool|\DateTime $date
*/
public function __construct($changeType = null, $author = null, $date = null)
{
$this->changeType = $changeType;
$this->author = $author;
if ($date !== null) {
if ($date !== null && $date !== false) {
$this->date = ($date instanceof \DateTime) ? $date : new \DateTime('@' . $date);
}
}

View File

@ -507,7 +507,7 @@ class DocInfo
case 'date': // Date
return strtotime($propertyValue);
case 'bool': // Boolean
return ($propertyValue == 'true') ? true : false;
return $propertyValue == 'true';
}
return $propertyValue;

View File

@ -17,7 +17,7 @@
namespace PhpOffice\PhpWord\Metadata;
use PhpOffice\PhpWord\Shared\Microsoft\PasswordEncoder;
use PhpOffice\Common\Microsoft\PasswordEncoder;
use PhpOffice\PhpWord\SimpleType\DocProtect;
/**
@ -113,7 +113,7 @@ class Protection
/**
* Set password
*
* @param $password
* @param string $password
* @return self
*/
public function setPassword($password)
@ -136,7 +136,7 @@ class Protection
/**
* Set count for hash iterations
*
* @param $spinCount
* @param int $spinCount
* @return self
*/
public function setSpinCount($spinCount)
@ -159,7 +159,7 @@ class Protection
/**
* Set algorithm
*
* @param $algorithm
* @param string $algorithm
* @return self
*/
public function setAlgorithm($algorithm)

View File

@ -35,10 +35,10 @@ use PhpOffice\PhpWord\Exception\Exception;
* @method int addChart(Element\Chart $chart)
* @method int addComment(Element\Comment $comment)
*
* @method Style\Paragraph addParagraphStyle(string $styleName, array $styles)
* @method Style\Paragraph addParagraphStyle(string $styleName, mixed $styles)
* @method Style\Font addFontStyle(string $styleName, mixed $fontStyle, mixed $paragraphStyle = null)
* @method Style\Font addLinkStyle(string $styleName, mixed $styles)
* @method Style\Font addTitleStyle(int $depth, mixed $fontStyle, mixed $paragraphStyle = null)
* @method Style\Font addTitleStyle(mixed $depth, mixed $fontStyle, mixed $paragraphStyle = null)
* @method Style\Table addTableStyle(string $styleName, mixed $styleTable, mixed $styleFirstRow = null)
* @method Style\Numbering addNumberingStyle(string $styleName, mixed $styles)
*/

View File

@ -1619,7 +1619,7 @@ class MsDoc extends AbstractReader implements ReaderInterface
break;
// sprmCFData
case 0x06:
$sprmCFData = dechex($operand) == 0x00 ? false : true;
$sprmCFData = dechex($operand) != 0x00;
break;
// sprmCFItalic
case 0x36:
@ -2185,6 +2185,8 @@ class MsDoc extends AbstractReader implements ReaderInterface
$sprmCPicLocation += $embeddedBlipRH['recLen'];
break;
case self::OFFICEARTBLIPPNG:
break;
default:
// print_r(dechex($embeddedBlipRH['recType']));
}

View File

@ -62,6 +62,9 @@ class Word2007 extends AbstractReader implements ReaderInterface
foreach ($steps as $step) {
$stepPart = $step['stepPart'];
$stepItems = $step['stepItems'];
if (!isset($relationships[$stepPart])) {
continue;
}
foreach ($relationships[$stepPart] as $relItem) {
$relType = $relItem['type'];
if (isset($stepItems[$relType])) {

View File

@ -261,6 +261,20 @@ abstract class AbstractPart
}
$parent->addImage($imageSource);
}
} elseif ($node->nodeName == 'w:drawing') {
// Office 2011 Image
$xmlReader->registerNamespace('wp', 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing');
$xmlReader->registerNamespace('r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
$xmlReader->registerNamespace('pic', 'http://schemas.openxmlformats.org/drawingml/2006/picture');
$xmlReader->registerNamespace('a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
$name = $xmlReader->getAttribute('name', $node, 'wp:inline/a:graphic/a:graphicData/pic:pic/pic:nvPicPr/pic:cNvPr');
$embedId = $xmlReader->getAttribute('r:embed', $node, 'wp:inline/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip');
$target = $this->getMediaTarget($docPart, $embedId);
if (!is_null($target)) {
$imageSource = "zip://{$this->docFile}#{$target}";
$parent->addImage($imageSource, null, false, $name);
}
} elseif ($node->nodeName == 'w:object') {
// Object
$rId = $xmlReader->getAttribute('r:id', $node, 'o:OLEObject');
@ -283,6 +297,8 @@ abstract class AbstractPart
$target = $this->getMediaTarget($docPart, $rId);
if (!is_null($target)) {
$parent->addLink($target, $textContent, $fontStyle, $paragraphStyle);
} else {
$parent->addText($textContent, $fontStyle, $paragraphStyle);
}
} else {
/** @var AbstractElement $element */
@ -322,7 +338,7 @@ abstract class AbstractPart
} elseif ('w:tr' == $tblNode->nodeName) { // Row
$rowHeight = $xmlReader->getAttribute('w:val', $tblNode, 'w:trPr/w:trHeight');
$rowHRule = $xmlReader->getAttribute('w:hRule', $tblNode, 'w:trPr/w:trHeight');
$rowHRule = $rowHRule == 'exact' ? true : false;
$rowHRule = $rowHRule == 'exact';
$rowStyle = array(
'tblHeader' => $xmlReader->elementExists('w:trPr/w:tblHeader', $tblNode),
'cantSplit' => $xmlReader->elementExists('w:trPr/w:cantSplit', $tblNode),
@ -430,6 +446,7 @@ abstract class AbstractPart
'rtl' => array(self::READ_TRUE, 'w:rtl'),
'lang' => array(self::READ_VALUE, 'w:lang'),
'position' => array(self::READ_VALUE, 'w:position'),
'hidden' => array(self::READ_TRUE, 'w:vanish'),
);
return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs);
@ -466,6 +483,7 @@ abstract class AbstractPart
$styleDefs["border{$ucfSide}Style"] = array(self::READ_VALUE, "w:tblBorders/w:$side", 'w:val');
}
$styleDefs['layout'] = array(self::READ_VALUE, 'w:tblLayout', 'w:type');
$styleDefs['bidiVisual'] = array(self::READ_TRUE, 'w:bidiVisual');
$styleDefs['cellSpacing'] = array(self::READ_VALUE, 'w:tblCellSpacing', 'w:w');
$style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs);

View File

@ -106,6 +106,7 @@ class Document extends AbstractPart
{
$styleDefs = array(
'breakType' => array(self::READ_VALUE, 'w:type'),
'vAlign' => array(self::READ_VALUE, 'w:vAlign'),
'pageSizeW' => array(self::READ_VALUE, 'w:pgSz', 'w:w'),
'pageSizeH' => array(self::READ_VALUE, 'w:pgSz', 'w:h'),
'orientation' => array(self::READ_VALUE, 'w:pgSz', 'w:orient'),

View File

@ -81,7 +81,7 @@ class Settings extends AbstractPart
*
* @param XMLReader $xmlReader
* @param PhpWord $phpWord
* @param \DOMNode $node
* @param \DOMElement $node
*/
protected function setThemeFontLang(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node)
{
@ -102,7 +102,7 @@ class Settings extends AbstractPart
*
* @param XMLReader $xmlReader
* @param PhpWord $phpWord
* @param \DOMNode $node
* @param \DOMElement $node
*/
protected function setDocumentProtection(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node)
{
@ -119,7 +119,7 @@ class Settings extends AbstractPart
*
* @param XMLReader $xmlReader
* @param PhpWord $phpWord
* @param \DOMNode $node
* @param \DOMElement $node
*/
protected function setProofState(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node)
{
@ -141,7 +141,7 @@ class Settings extends AbstractPart
*
* @param XMLReader $xmlReader
* @param PhpWord $phpWord
* @param \DOMNode $node
* @param \DOMElement $node
*/
protected function setZoom(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node)
{
@ -158,7 +158,7 @@ class Settings extends AbstractPart
*
* @param XMLReader $xmlReader
* @param PhpWord $phpWord
* @param \DOMNode $node
* @param \DOMElement $node
*/
protected function setRevisionView(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node)
{

View File

@ -64,11 +64,12 @@ class Styles extends AbstractPart
if ($nodes->length > 0) {
foreach ($nodes as $node) {
$type = $xmlReader->getAttribute('w:type', $node);
$name = $xmlReader->getAttribute('w:styleId', $node);
$name = $xmlReader->getAttribute('w:val', $node, 'w:name');
if (is_null($name)) {
$name = $xmlReader->getAttribute('w:val', $node, 'w:name');
$name = $xmlReader->getAttribute('w:styleId', $node);
}
preg_match('/Heading(\d)/', $name, $headingMatches);
$headingMatches = array();
preg_match('/Heading\s*(\d)/i', $name, $headingMatches);
// $default = ($xmlReader->getAttribute('w:default', $node) == 1);
switch ($type) {
case 'paragraph':

View File

@ -198,7 +198,7 @@ class Converter
/**
* Convert point to pixel
*
* @param int $point
* @param float $point
* @return float
*/
public static function pointToPixel($point = 1)
@ -217,6 +217,17 @@ class Converter
return round($point / self::INCH_TO_POINT * self::INCH_TO_PIXEL * self::PIXEL_TO_EMU);
}
/**
* Convert point to cm
*
* @param float $point
* @return float
*/
public static function pointToCm($point = 1)
{
return $point / self::INCH_TO_POINT * self::INCH_TO_CM;
}
/**
* Convert EMU to pixel
*
@ -299,6 +310,7 @@ class Converter
if ($value == '0') {
return 0;
}
$matches = array();
if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(px|em|ex|%|in|cm|mm|pt|pc)$/i', $value, $matches)) {
$size = $matches[1];
$unit = $matches[2];
@ -332,4 +344,37 @@ class Converter
{
return self::pointToTwip(self::cssToPoint($value));
}
/**
* Transforms a size in CSS format (eg. 10px, 10px, ...) to pixel
*
* @param string $value
* @return float
*/
public static function cssToPixel($value)
{
return self::pointToPixel(self::cssToPoint($value));
}
/**
* Transforms a size in CSS format (eg. 10px, 10px, ...) to cm
*
* @param string $value
* @return float
*/
public static function cssToCm($value)
{
return self::pointToCm(self::cssToPoint($value));
}
/**
* Transforms a size in CSS format (eg. 10px, 10px, ...) to emu
*
* @param string $value
* @return float
*/
public static function cssToEmu($value)
{
return self::pointToEmu(self::cssToPoint($value));
}
}

View File

@ -20,8 +20,10 @@ namespace PhpOffice\PhpWord\Shared;
use PhpOffice\PhpWord\Element\AbstractContainer;
use PhpOffice\PhpWord\Element\Row;
use PhpOffice\PhpWord\Element\Table;
use PhpOffice\PhpWord\Settings;
use PhpOffice\PhpWord\SimpleType\Jc;
use PhpOffice\PhpWord\SimpleType\NumberFormat;
use PhpOffice\PhpWord\Style\Paragraph;
/**
* Common Html functions
@ -32,6 +34,7 @@ class Html
{
private static $listIndex = 0;
private static $xpath;
private static $options;
/**
* Add HTML parts.
@ -44,13 +47,17 @@ class Html
* @param string $html The code to parse
* @param bool $fullHTML If it's a full HTML, no need to add 'body' tag
* @param bool $preserveWhiteSpace If false, the whitespaces between nodes will be removed
* @param array $options:
* + IMG_SRC_SEARCH: optional to speed up images loading from remote url when files can be found locally
* + IMG_SRC_REPLACE: optional to speed up images loading from remote url when files can be found locally
*/
public static function addHtml($element, $html, $fullHTML = false, $preserveWhiteSpace = true)
public static function addHtml($element, $html, $fullHTML = false, $preserveWhiteSpace = true, $options = null)
{
/*
* @todo parse $stylesheet for default styles. Should result in an array based on id, class and element,
* which could be applied when such an element occurs in the parseNode function.
*/
self::$options = $options;
// Preprocess: remove all line ends, decode HTML entity,
// fix ampersand and angle brackets and add body tag for HTML fragments
@ -65,10 +72,11 @@ class Html
}
// Load DOM
libxml_disable_entity_loader(true);
$dom = new \DOMDocument();
$dom->preserveWhiteSpace = $preserveWhiteSpace;
$dom->loadXML($html);
self::$xpath = new \DOMXpath($dom);
self::$xpath = new \DOMXPath($dom);
$node = $dom->getElementsByTagName('body');
self::parseNode($node->item(0), $element);
@ -141,6 +149,7 @@ class Html
'sup' => array('Property', null, null, $styles, null, 'superScript', true),
'sub' => array('Property', null, null, $styles, null, 'subScript', true),
'span' => array('Span', $node, null, $styles, null, null, null),
'font' => array('Span', $node, null, $styles, null, null, null),
'table' => array('Table', $node, $element, $styles, null, null, null),
'tr' => array('Row', $node, $element, $styles, null, null, null),
'td' => array('Cell', $node, $element, $styles, null, null, null),
@ -506,6 +515,9 @@ class Html
case 'text-align':
$styles['alignment'] = self::mapAlign($cValue);
break;
case 'display':
$styles['hidden'] = $cValue === 'none' || $cValue === 'hidden';
break;
case 'direction':
$styles['rtl'] = $cValue === 'rtl';
break;
@ -523,18 +535,27 @@ class Html
$styles['bgColor'] = trim($cValue, '#');
break;
case 'line-height':
if (preg_match('/([0-9]+[a-z]+)/', $cValue, $matches)) {
$matches = array();
if (preg_match('/([0-9]+\.?[0-9]*[a-z]+)/', $cValue, $matches)) {
//matches number with a unit, e.g. 12px, 15pt, 20mm, ...
$spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::EXACT;
$spacing = Converter::cssToTwip($matches[1]) / \PhpOffice\PhpWord\Style\Paragraph::LINE_HEIGHT;
$spacing = Converter::cssToTwip($matches[1]);
} elseif (preg_match('/([0-9]+)%/', $cValue, $matches)) {
//matches percentages
$spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO;
$spacing = ((int) $matches[1]) / 100;
//we are subtracting 1 line height because the Spacing writer is adding one line
$spacing = ((((int) $matches[1]) / 100) * Paragraph::LINE_HEIGHT) - Paragraph::LINE_HEIGHT;
} else {
//any other, wich is a multiplier. E.g. 1.2
$spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO;
$spacing = $cValue;
//we are subtracting 1 line height because the Spacing writer is adding one line
$spacing = ($cValue * Paragraph::LINE_HEIGHT) - Paragraph::LINE_HEIGHT;
}
$styles['spacingLineRule'] = $spacingLineRule;
$styles['lineHeight'] = $spacing;
$styles['line-spacing'] = $spacing;
break;
case 'letter-spacing':
$styles['letter-spacing'] = Converter::cssToTwip($cValue);
break;
case 'text-indent':
$styles['indentation']['firstLine'] = Converter::cssToTwip($cValue);
@ -560,7 +581,7 @@ class Html
$styles['spaceAfter'] = Converter::cssToPoint($cValue);
break;
case 'border-color':
$styles['color'] = trim($cValue, '#');
self::mapBorderColor($styles, $cValue);
break;
case 'border-width':
$styles['borderSize'] = Converter::cssToPoint($cValue);
@ -648,7 +669,52 @@ class Html
break;
}
}
$newElement = $element->addImage($src, $style);
$originSrc = $src;
if (strpos($src, 'data:image') !== false) {
$tmpDir = Settings::getTempDir() . '/';
$match = array();
preg_match('/data:image\/(\w+);base64,(.+)/', $src, $match);
$src = $imgFile = $tmpDir . uniqid() . '.' . $match[1];
$ifp = fopen($imgFile, 'wb');
if ($ifp !== false) {
fwrite($ifp, base64_decode($match[2]));
fclose($ifp);
}
}
$src = urldecode($src);
if (!is_file($src)
&& !is_null(self::$options)
&& isset(self::$options['IMG_SRC_SEARCH'])
&& isset(self::$options['IMG_SRC_REPLACE'])) {
$src = str_replace(self::$options['IMG_SRC_SEARCH'], self::$options['IMG_SRC_REPLACE'], $src);
}
if (!is_file($src)) {
if ($imgBlob = @file_get_contents($src)) {
$tmpDir = Settings::getTempDir() . '/';
$match = array();
preg_match('/.+\.(\w+)$/', $src, $match);
$src = $tmpDir . uniqid() . '.' . $match[1];
$ifp = fopen($src, 'wb');
if ($ifp !== false) {
fwrite($ifp, $imgBlob);
fclose($ifp);
}
}
}
if (is_file($src)) {
$newElement = $element->addImage($src, $style);
} else {
throw new \Exception("Could not load image $originSrc");
}
return $newElement;
}
@ -672,6 +738,20 @@ class Html
}
}
private static function mapBorderColor(&$styles, $cssBorderColor)
{
$numColors = substr_count($cssBorderColor, '#');
if ($numColors === 1) {
$styles['borderColor'] = trim($cssBorderColor, '#');
} elseif ($numColors > 1) {
$colors = explode(' ', $cssBorderColor);
$borders = array('borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor');
for ($i = 0; $i < min(4, $numColors, count($colors)); $i++) {
$styles[$borders[$i]] = trim($colors[$i], '#');
}
}
}
/**
* Transforms a HTML/CSS alignment into a \PhpOffice\PhpWord\SimpleType\Jc
*
@ -690,8 +770,6 @@ class Html
default:
return Jc::START;
}
return null;
}
/**

View File

@ -1,235 +0,0 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
* @copyright 2010-2018 PHPWord contributors
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Shared\Microsoft;
/**
* Password encoder for microsoft office applications
*/
class PasswordEncoder
{
const ALGORITHM_MD2 = 'MD2';
const ALGORITHM_MD4 = 'MD4';
const ALGORITHM_MD5 = 'MD5';
const ALGORITHM_SHA_1 = 'SHA-1';
const ALGORITHM_SHA_256 = 'SHA-256';
const ALGORITHM_SHA_384 = 'SHA-384';
const ALGORITHM_SHA_512 = 'SHA-512';
const ALGORITHM_RIPEMD = 'RIPEMD';
const ALGORITHM_RIPEMD_160 = 'RIPEMD-160';
const ALGORITHM_MAC = 'MAC';
const ALGORITHM_HMAC = 'HMAC';
/**
* Mapping between algorithm name and algorithm ID
*
* @var array
* @see https://msdn.microsoft.com/en-us/library/documentformat.openxml.wordprocessing.writeprotection.cryptographicalgorithmsid(v=office.14).aspx
*/
private static $algorithmMapping = array(
self::ALGORITHM_MD2 => array(1, 'md2'),
self::ALGORITHM_MD4 => array(2, 'md4'),
self::ALGORITHM_MD5 => array(3, 'md5'),
self::ALGORITHM_SHA_1 => array(4, 'sha1'),
self::ALGORITHM_MAC => array(5, ''), // 'mac' -> not possible with hash()
self::ALGORITHM_RIPEMD => array(6, 'ripemd'),
self::ALGORITHM_RIPEMD_160 => array(7, 'ripemd160'),
self::ALGORITHM_HMAC => array(9, ''), //'hmac' -> not possible with hash()
self::ALGORITHM_SHA_256 => array(12, 'sha256'),
self::ALGORITHM_SHA_384 => array(13, 'sha384'),
self::ALGORITHM_SHA_512 => array(14, 'sha512'),
);
private static $initialCodeArray = array(
0xE1F0,
0x1D0F,
0xCC9C,
0x84C0,
0x110C,
0x0E10,
0xF1CE,
0x313E,
0x1872,
0xE139,
0xD40F,
0x84F9,
0x280C,
0xA96A,
0x4EC3,
);
private static $encryptionMatrix = array(
array(0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09),
array(0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF),
array(0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0),
array(0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40),
array(0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5),
array(0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A),
array(0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9),
array(0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0),
array(0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC),
array(0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10),
array(0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168),
array(0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C),
array(0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD),
array(0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC),
array(0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4),
);
private static $passwordMaxLength = 15;
/**
* Create a hashed password that MS Word will be able to work with
* @see https://blogs.msdn.microsoft.com/vsod/2010/04/05/how-to-set-the-editing-restrictions-in-word-using-open-xml-sdk-2-0/
*
* @param string $password
* @param string $algorithmName
* @param string $salt
* @param int $spinCount
* @return string
*/
public static function hashPassword($password, $algorithmName = self::ALGORITHM_SHA_1, $salt = null, $spinCount = 10000)
{
$origEncoding = mb_internal_encoding();
mb_internal_encoding('UTF-8');
$password = mb_substr($password, 0, min(self::$passwordMaxLength, mb_strlen($password)));
// Get the single-byte values by iterating through the Unicode characters of the truncated password.
// For each character, if the low byte is not equal to 0, take it. Otherwise, take the high byte.
$passUtf8 = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8');
$byteChars = array();
for ($i = 0; $i < mb_strlen($password); $i++) {
$byteChars[$i] = ord(substr($passUtf8, $i * 2, 1));
if ($byteChars[$i] == 0) {
$byteChars[$i] = ord(substr($passUtf8, $i * 2 + 1, 1));
}
}
// build low-order word and hig-order word and combine them
$combinedKey = self::buildCombinedKey($byteChars);
// build reversed hexadecimal string
$hex = str_pad(strtoupper(dechex($combinedKey & 0xFFFFFFFF)), 8, '0', \STR_PAD_LEFT);
$reversedHex = $hex[6] . $hex[7] . $hex[4] . $hex[5] . $hex[2] . $hex[3] . $hex[0] . $hex[1];
$generatedKey = mb_convert_encoding($reversedHex, 'UCS-2LE', 'UTF-8');
// Implementation Notes List:
// Word requires that the initial hash of the password with the salt not be considered in the count.
// The initial hash of salt + key is not included in the iteration count.
$algorithm = self::getAlgorithm($algorithmName);
$generatedKey = hash($algorithm, $salt . $generatedKey, true);
for ($i = 0; $i < $spinCount; $i++) {
$generatedKey = hash($algorithm, $generatedKey . pack('CCCC', $i, $i >> 8, $i >> 16, $i >> 24), true);
}
$generatedKey = base64_encode($generatedKey);
mb_internal_encoding($origEncoding);
return $generatedKey;
}
/**
* Get algorithm from self::$algorithmMapping
*
* @param string $algorithmName
* @return string
*/
private static function getAlgorithm($algorithmName)
{
$algorithm = self::$algorithmMapping[$algorithmName][1];
if ($algorithm == '') {
$algorithm = 'sha1';
}
return $algorithm;
}
/**
* Returns the algorithm ID
*
* @param string $algorithmName
* @return int
*/
public static function getAlgorithmId($algorithmName)
{
return self::$algorithmMapping[$algorithmName][0];
}
/**
* Build combined key from low-order word and high-order word
*
* @param array $byteChars byte array representation of password
* @return int
*/
private static function buildCombinedKey($byteChars)
{
$byteCharsLength = count($byteChars);
// Compute the high-order word
// Initialize from the initial code array (see above), depending on the passwords length.
$highOrderWord = self::$initialCodeArray[$byteCharsLength - 1];
// For each character in the password:
// For every bit in the character, starting with the least significant and progressing to (but excluding)
// the most significant, if the bit is set, XOR the keys high-order word with the corresponding word from
// the Encryption Matrix
for ($i = 0; $i < $byteCharsLength; $i++) {
$tmp = self::$passwordMaxLength - $byteCharsLength + $i;
$matrixRow = self::$encryptionMatrix[$tmp];
for ($intBit = 0; $intBit < 7; $intBit++) {
if (($byteChars[$i] & (0x0001 << $intBit)) != 0) {
$highOrderWord = ($highOrderWord ^ $matrixRow[$intBit]);
}
}
}
// Compute low-order word
// Initialize with 0
$lowOrderWord = 0;
// For each character in the password, going backwards
for ($i = $byteCharsLength - 1; $i >= 0; $i--) {
// low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR character
$lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteChars[$i]);
}
// Lastly, low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR strPassword length XOR 0xCE4B.
$lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteCharsLength ^ 0xCE4B);
// Combine the Low and High Order Word
return self::int32(($highOrderWord << 16) + $lowOrderWord);
}
/**
* Simulate behaviour of (signed) int32
*
* @codeCoverageIgnore
* @param int $value
* @return int
*/
private static function int32($value)
{
$value = ($value & 0xFFFFFFFF);
if ($value & 0x80000000) {
$value = -((~$value & 0xFFFFFFFF) + 1);
}
return $value;
}
}

View File

@ -129,6 +129,7 @@ class ZipArchive
{
$result = true;
$this->filename = $filename;
$this->tempDir = Settings::getTempDir();
if (!$this->usePclzip) {
$zip = new \ZipArchive();
@ -139,7 +140,6 @@ class ZipArchive
$this->numFiles = $zip->numFiles;
} else {
$zip = new \PclZip($this->filename);
$this->tempDir = Settings::getTempDir();
$zipContent = $zip->listContent();
$this->numFiles = is_array($zipContent) ? count($zipContent) : 0;
}
@ -245,14 +245,20 @@ class ZipArchive
$pathRemoved = $filenameParts['dirname'];
$pathAdded = $localnameParts['dirname'];
$res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded);
if (!$this->usePclzip) {
$pathAdded = $pathAdded . '/' . ltrim(str_replace('\\', '/', substr($filename, strlen($pathRemoved))), '/');
//$res = $zip->addFile($filename, $pathAdded);
$res = $zip->addFromString($pathAdded, file_get_contents($filename)); // addFile can't use subfolders in some cases
} else {
$res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded);
}
if ($tempFile) {
// Remove temp file, if created
unlink($this->tempDir . DIRECTORY_SEPARATOR . $localnameParts['basename']);
}
return ($res == 0) ? false : true;
return $res != 0;
}
/**
@ -283,7 +289,7 @@ class ZipArchive
// Remove temp file
@unlink($this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename']);
return ($res == 0) ? false : true;
return $res != 0;
}
/**
@ -303,7 +309,7 @@ class ZipArchive
if (is_null($entries)) {
$result = $zip->extract(PCLZIP_OPT_PATH, $destination);
return ($result > 0) ? true : false;
return $result > 0;
}
// Extract by entries

View File

@ -0,0 +1,36 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
* @copyright 2010-2018 PHPWord contributors
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\SimpleType;
use PhpOffice\PhpWord\Shared\AbstractEnum;
/**
* Vertical Alignment Type.
*
* Introduced in ISO/IEC-29500:2008.
*
* @see http://www.datypic.com/sc/ooxml/t-w_ST_VerticalJc.html
* @since 0.17.0
*/
final class VerticalJc extends AbstractEnum
{
const TOP = 'top';
const CENTER = 'center';
const BOTH = 'both';
const BOTTOM = 'bottom';
}

View File

@ -39,7 +39,7 @@ class Style
* Add paragraph style
*
* @param string $styleName
* @param array $styles
* @param array|\PhpOffice\PhpWord\Style\AbstractStyle $styles
* @return \PhpOffice\PhpWord\Style\Paragraph
*/
public static function addParagraphStyle($styleName, $styles)
@ -51,8 +51,8 @@ class Style
* Add font style
*
* @param string $styleName
* @param array $fontStyle
* @param array $paragraphStyle
* @param array|\PhpOffice\PhpWord\Style\AbstractStyle $fontStyle
* @param array|\PhpOffice\PhpWord\Style\AbstractStyle $paragraphStyle
* @return \PhpOffice\PhpWord\Style\Font
*/
public static function addFontStyle($styleName, $fontStyle, $paragraphStyle = null)
@ -64,7 +64,7 @@ class Style
* Add link style
*
* @param string $styleName
* @param array $styles
* @param array|\PhpOffice\PhpWord\Style\AbstractStyle $styles
* @return \PhpOffice\PhpWord\Style\Font
*/
public static function addLinkStyle($styleName, $styles)
@ -76,7 +76,7 @@ class Style
* Add numbering style
*
* @param string $styleName
* @param array $styleValues
* @param array|\PhpOffice\PhpWord\Style\AbstractStyle $styleValues
* @return \PhpOffice\PhpWord\Style\Numbering
* @since 0.10.0
*/
@ -88,14 +88,14 @@ class Style
/**
* Add title style
*
* @param int $depth
* @param array $fontStyle
* @param array $paragraphStyle
* @param int|null $depth Provide null to set title font
* @param array|\PhpOffice\PhpWord\Style\AbstractStyle $fontStyle
* @param array|\PhpOffice\PhpWord\Style\AbstractStyle $paragraphStyle
* @return \PhpOffice\PhpWord\Style\Font
*/
public static function addTitleStyle($depth, $fontStyle, $paragraphStyle = null)
{
if ($depth == null) {
if (empty($depth)) {
$styleName = 'Title';
} else {
$styleName = "Heading_{$depth}";
@ -141,7 +141,7 @@ class Style
/**
* Set default paragraph style
*
* @param array $styles Paragraph style definition
* @param array|\PhpOffice\PhpWord\Style\AbstractStyle $styles Paragraph style definition
* @return \PhpOffice\PhpWord\Style\Paragraph
*/
public static function setDefaultParagraphStyle($styles)

View File

@ -18,6 +18,7 @@
namespace PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\SimpleType\TblWidth;
use PhpOffice\PhpWord\SimpleType\VerticalJc;
/**
* Table cell style
@ -28,10 +29,20 @@ class Cell extends Border
* Vertical alignment constants
*
* @const string
* @deprecated Use \PhpOffice\PhpWord\SimpleType\VerticalJc::TOP instead
*/
const VALIGN_TOP = 'top';
/**
* @deprecated Use \PhpOffice\PhpWord\SimpleType\VerticalJc::CENTER instead
*/
const VALIGN_CENTER = 'center';
/**
* @deprecated Use \PhpOffice\PhpWord\SimpleType\VerticalJc::BOTTOM instead
*/
const VALIGN_BOTTOM = 'bottom';
/**
* @deprecated Use \PhpOffice\PhpWord\SimpleType\VerticalJc::BOTH instead
*/
const VALIGN_BOTH = 'both';
//Text direction constants
@ -145,8 +156,8 @@ class Cell extends Border
*/
public function setVAlign($value = null)
{
$enum = array(self::VALIGN_TOP, self::VALIGN_CENTER, self::VALIGN_BOTTOM, self::VALIGN_BOTH);
$this->vAlign = $this->setEnumVal($value, $enum, $this->vAlign);
VerticalJc::validate($value);
$this->vAlign = $this->setEnumVal($value, VerticalJc::values(), $this->vAlign);
return $this;
}

View File

@ -52,6 +52,20 @@ class Chart extends AbstractStyle
*/
private $colors = array();
/**
* Chart title
*
* @var string
*/
private $title = null;
/**
* Chart legend visibility
*
* @var bool
*/
private $showLegend = false;
/**
* A list of display options for data labels
*
@ -97,9 +111,15 @@ class Chart extends AbstractStyle
*/
private $valueAxisTitle;
/**
* The position for major tick marks
* Possible values are 'in', 'out', 'cross', 'none'
*
* @var string
*/
private $majorTickMarkPos = 'none';
/*
/**
* Show labels for axis
*
* @var bool
@ -221,6 +241,50 @@ class Chart extends AbstractStyle
return $this;
}
/**
* Get the chart title
*
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set the chart title
*
* @param string $value
*/
public function setTitle($value = null)
{
$this->title = $value;
return $this;
}
/**
* Get chart legend visibility
*
* @return bool
*/
public function isShowLegend()
{
return $this->showLegend;
}
/**
* Set chart legend visibility
*
* @param bool $value
*/
public function setShowLegend($value = false)
{
$this->showLegend = $value;
return $this;
}
/*
* Show labels for axis
*
@ -394,8 +458,8 @@ class Chart extends AbstractStyle
}
/**
* set the position for major tick marks
* @param string $position [description]
* Set the position for major tick marks
* @param string $position
*/
public function setMajorTickPosition($position)
{
@ -403,7 +467,7 @@ class Chart extends AbstractStyle
$this->majorTickMarkPos = $this->setEnumVal($position, $enum, $this->majorTickMarkPos);
}
/*
/**
* Show Gridlines for X-Axis
*
* @return bool

View File

@ -80,7 +80,7 @@ class Font extends AbstractStyle
*
* @var array
*/
protected $aliases = array('line-height' => 'lineHeight');
protected $aliases = array('line-height' => 'lineHeight', 'letter-spacing' => 'spacing');
/**
* Font style type
@ -122,14 +122,14 @@ class Font extends AbstractStyle
*
* @var bool
*/
private $bold = false;
private $bold;
/**
* Italic
*
* @var bool
*/
private $italic = false;
private $italic;
/**
* Undeline
@ -157,14 +157,14 @@ class Font extends AbstractStyle
*
* @var bool
*/
private $strikethrough = false;
private $strikethrough;
/**
* Double strikethrough
*
* @var bool
*/
private $doubleStrikethrough = false;
private $doubleStrikethrough;
/**
* Small caps
@ -172,7 +172,7 @@ class Font extends AbstractStyle
* @var bool
* @see http://www.schemacentral.com/sc/ooxml/e-w_smallCaps-1.html
*/
private $smallCaps = false;
private $smallCaps;
/**
* All caps
@ -180,7 +180,7 @@ class Font extends AbstractStyle
* @var bool
* @see http://www.schemacentral.com/sc/ooxml/e-w_caps-1.html
*/
private $allCaps = false;
private $allCaps;
/**
* Foreground/highlight
@ -235,7 +235,7 @@ class Font extends AbstractStyle
*
* @var bool
*/
private $rtl = false;
private $rtl;
/**
* noProof (disables AutoCorrect)
@ -243,7 +243,7 @@ class Font extends AbstractStyle
* @var bool
* http://www.datypic.com/sc/ooxml/e-w_noProof-1.html
*/
private $noProof = false;
private $noProof;
/**
* Languages
@ -252,6 +252,14 @@ class Font extends AbstractStyle
*/
private $lang;
/**
* Hidden text
*
* @var bool
* @see http://www.datypic.com/sc/ooxml/e-w_vanish-1.html
*/
private $hidden;
/**
* Vertically Raised or Lowered Text
*
@ -264,7 +272,7 @@ class Font extends AbstractStyle
* Create new font style
*
* @param string $type Type of font
* @param array $paragraph Paragraph styles definition
* @param array|string|\PhpOffice\PhpWord\Style\AbstractStyle $paragraph Paragraph styles definition
*/
public function __construct($type = 'text', $paragraph = null)
{
@ -299,6 +307,7 @@ class Font extends AbstractStyle
'smallCaps' => $this->isSmallCaps(),
'allCaps' => $this->isAllCaps(),
'fgColor' => $this->getFgColor(),
'hidden' => $this->isHidden(),
),
'spacing' => array(
'scale' => $this->getScale(),
@ -938,6 +947,29 @@ class Font extends AbstractStyle
return $this->getParagraph();
}
/**
* Get hidden text
*
* @return bool
*/
public function isHidden()
{
return $this->hidden;
}
/**
* Set hidden text
*
* @param bool $value
* @return self
*/
public function setHidden($value = true)
{
$this->hidden = $this->setBoolVal($value, $this->hidden);
return $this;
}
/**
* Get position
*

View File

@ -47,6 +47,9 @@ final class Language extends AbstractStyle
const HE_IL = 'he-IL';
const HE_IL_ID = 1037;
const IT_IT = 'it-IT';
const IT_IT_ID = 1040;
const JA_JP = 'ja-JP';
const JA_JP_ID = 1041;
@ -62,6 +65,12 @@ final class Language extends AbstractStyle
const PT_BR = 'pt-BR';
const PT_BR_ID = 1046;
const NL_NL = 'nl-NL';
const NL_NL_ID = 1043;
const UK_UA = 'uk-UA';
const UK_UA_ID = 1058;
/**
* Language ID, used for RTF document generation
*
@ -120,8 +129,7 @@ final class Language extends AbstractStyle
*/
public function setLatin($latin)
{
$this->validateLocale($latin);
$this->latin = $latin;
$this->latin = $this->validateLocale($latin);
return $this;
}
@ -170,8 +178,7 @@ final class Language extends AbstractStyle
*/
public function setEastAsia($eastAsia)
{
$this->validateLocale($eastAsia);
$this->eastAsia = $eastAsia;
$this->eastAsia = $this->validateLocale($eastAsia);
return $this;
}
@ -195,8 +202,7 @@ final class Language extends AbstractStyle
*/
public function setBidirectional($bidirectional)
{
$this->validateLocale($bidirectional);
$this->bidirectional = $bidirectional;
$this->bidirectional = $this->validateLocale($bidirectional);
return $this;
}
@ -215,12 +221,18 @@ final class Language extends AbstractStyle
* Validates that the language passed is in the format xx-xx
*
* @param string $locale
* @return bool
* @return string
*/
private function validateLocale($locale)
{
if ($locale !== null && strstr($locale, '-') === false) {
if (strlen($locale) === 2) {
return strtolower($locale) . '-' . strtoupper($locale);
}
if ($locale !== null && $locale !== 'zxx' && strstr($locale, '-') === false) {
throw new \InvalidArgumentException($locale . ' is not a valid language code');
}
return $locale;
}
}

View File

@ -61,7 +61,7 @@ class Paragraph extends Border
*
* @var array
*/
protected $aliases = array('line-height' => 'lineHeight');
protected $aliases = array('line-height' => 'lineHeight', 'line-spacing' => 'spacing');
/**
* Parent style
@ -199,8 +199,6 @@ class Paragraph extends Border
$key = Text::removeUnderscorePrefix($key);
if ('indent' == $key || 'hanging' == $key) {
$value = $value * 720;
} elseif ('spacing' == $key) {
$value += 240; // because line height of 1 matches 240 twips
}
return parent::setStyleValue($key, $value);
@ -479,7 +477,7 @@ class Paragraph extends Border
/**
* Get spacing between lines
*
* @return int
* @return int|float
*/
public function getSpacing()
{
@ -489,7 +487,7 @@ class Paragraph extends Border
/**
* Set spacing between lines
*
* @param int $value
* @param int|float $value
* @return self
*/
public function setSpacing($value = null)
@ -547,7 +545,8 @@ class Paragraph extends Border
}
$this->lineHeight = $lineHeight;
$this->setSpacing($lineHeight * self::LINE_HEIGHT);
$this->setSpacing(($lineHeight - 1) * self::LINE_HEIGHT);
$this->setSpacingLineRule(\PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO);
return $this;
}

View File

@ -17,6 +17,8 @@
namespace PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\SimpleType\VerticalJc;
/**
* Section settings
*/
@ -166,6 +168,14 @@ class Section extends Border
*/
private $lineNumbering;
/**
* Vertical Text Alignment on Page
* One of \PhpOffice\PhpWord\SimpleType\VerticalJc
*
* @var string
*/
private $vAlign;
/**
* Create new instance
*/
@ -599,4 +609,28 @@ class Section extends Border
return $this;
}
/**
* Get vertical alignment
*
* @return string
*/
public function getVAlign()
{
return $this->vAlign;
}
/**
* Set vertical alignment
*
* @param string $value
* @return self
*/
public function setVAlign($value = null)
{
VerticalJc::validate($value);
$this->vAlign = $value;
return $this;
}
}

View File

@ -163,6 +163,21 @@ class Table extends Border
/** @var TblWidthComplexType|null */
private $indent;
/**
* The width of each column, computed based on the max cell width of each column
*
* @var int[]
*/
private $columnWidths;
/**
* Visually Right to Left Table
*
* @see http://www.datypic.com/sc/ooxml/e-w_bidiVisual-1.html
* @var bool
*/
private $bidiVisual = false;
/**
* Create new table style
*
@ -748,4 +763,48 @@ class Table extends Border
return $this;
}
/**
* Get the columnWidths
*
* @return null|int[]
*/
public function getColumnWidths()
{
return $this->columnWidths;
}
/**
* The column widths
*
* @param int[] $value
*/
public function setColumnWidths(array $value = null)
{
$this->columnWidths = $value;
}
/**
* Get bidiVisual
*
* @return bool
*/
public function isBidiVisual()
{
return $this->bidiVisual;
}
/**
* Set bidiVisual
*
* @param bool $bidi
* Set to true to visually present table as Right to Left
* @return self
*/
public function setBidiVisual($bidi)
{
$this->bidiVisual = $bidi;
return $this;
}
}

View File

@ -18,6 +18,7 @@
namespace PhpOffice\PhpWord;
use PhpOffice\Common\Text;
use PhpOffice\Common\XMLWriter;
use PhpOffice\PhpWord\Escaper\RegExp;
use PhpOffice\PhpWord\Escaper\Xml;
use PhpOffice\PhpWord\Exception\CopyFileException;
@ -48,6 +49,13 @@ class TemplateProcessor
*/
protected $tempDocumentMainPart;
/**
* Content of settings part (in XML format) of the temporary document
*
* @var string
*/
protected $tempDocumentSettingsPart;
/**
* Content of headers (in XML format) of the temporary document
*
@ -62,6 +70,27 @@ class TemplateProcessor
*/
protected $tempDocumentFooters = array();
/**
* Document relations (in XML format) of the temporary document.
*
* @var string[]
*/
protected $tempDocumentRelations = array();
/**
* Document content types (in XML format) of the temporary document.
*
* @var string
*/
protected $tempDocumentContentTypes = '';
/**
* new inserted images list
*
* @var string[]
*/
protected $tempDocumentNewImages = array();
/**
* @since 0.12.0 Throws CreateTemporaryFileException and CopyFileException instead of Exception
*
@ -75,12 +104,12 @@ class TemplateProcessor
// Temporary document filename initialization
$this->tempDocumentFilename = tempnam(Settings::getTempDir(), 'PhpWord');
if (false === $this->tempDocumentFilename) {
throw new CreateTemporaryFileException();
throw new CreateTemporaryFileException(); // @codeCoverageIgnore
}
// Template file cloning
if (false === copy($documentTemplate, $this->tempDocumentFilename)) {
throw new CopyFileException($documentTemplate, $this->tempDocumentFilename);
throw new CopyFileException($documentTemplate, $this->tempDocumentFilename); // @codeCoverageIgnore
}
// Temporary document content extraction
@ -88,19 +117,47 @@ class TemplateProcessor
$this->zipClass->open($this->tempDocumentFilename);
$index = 1;
while (false !== $this->zipClass->locateName($this->getHeaderName($index))) {
$this->tempDocumentHeaders[$index] = $this->fixBrokenMacros(
$this->zipClass->getFromName($this->getHeaderName($index))
);
$this->tempDocumentHeaders[$index] = $this->readPartWithRels($this->getHeaderName($index));
$index++;
}
$index = 1;
while (false !== $this->zipClass->locateName($this->getFooterName($index))) {
$this->tempDocumentFooters[$index] = $this->fixBrokenMacros(
$this->zipClass->getFromName($this->getFooterName($index))
);
$this->tempDocumentFooters[$index] = $this->readPartWithRels($this->getFooterName($index));
$index++;
}
$this->tempDocumentMainPart = $this->fixBrokenMacros($this->zipClass->getFromName($this->getMainPartName()));
$this->tempDocumentMainPart = $this->readPartWithRels($this->getMainPartName());
$this->tempDocumentSettingsPart = $this->readPartWithRels($this->getSettingsPartName());
$this->tempDocumentContentTypes = $this->zipClass->getFromName($this->getDocumentContentTypesName());
}
/**
* Expose zip class
*
* To replace an image: $templateProcessor->zip()->AddFromString("word/media/image1.jpg", file_get_contents($file));<br>
* To read a file: $templateProcessor->zip()->getFromName("word/media/image1.jpg");
*
* @return \PhpOffice\PhpWord\Shared\ZipArchive
*/
public function zip()
{
return $this->zipClass;
}
/**
* @param string $fileName
*
* @return string
*/
protected function readPartWithRels($fileName)
{
$relsFileName = $this->getRelationsName($fileName);
$partRelations = $this->zipClass->getFromName($relsFileName);
if ($partRelations !== false) {
$this->tempDocumentRelations[$fileName] = $partRelations;
}
return $this->fixBrokenMacros($this->zipClass->getFromName($fileName));
}
/**
@ -113,6 +170,7 @@ class TemplateProcessor
*/
protected function transformSingleXml($xml, $xsltProcessor)
{
libxml_disable_entity_loader(true);
$domDocument = new \DOMDocument();
if (false === $domDocument->loadXML($xml)) {
throw new Exception('Could not load the given XML document.');
@ -138,6 +196,7 @@ class TemplateProcessor
foreach ($xml as &$item) {
$item = $this->transformSingleXml($item, $xsltProcessor);
}
unset($item);
} else {
$xml = $this->transformSingleXml($xml, $xsltProcessor);
}
@ -199,6 +258,46 @@ class TemplateProcessor
return $subject;
}
/**
* @param string $search
* @param \PhpOffice\PhpWord\Element\AbstractElement $complexType
*/
public function setComplexValue($search, \PhpOffice\PhpWord\Element\AbstractElement $complexType)
{
$elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1);
$objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName;
$xmlWriter = new XMLWriter();
/** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $elementWriter */
$elementWriter = new $objectClass($xmlWriter, $complexType, true);
$elementWriter->write();
$where = $this->findContainingXmlBlockForMacro($search, 'w:r');
$block = $this->getSlice($where['start'], $where['end']);
$textParts = $this->splitTextIntoTexts($block);
$this->replaceXmlBlock($search, $textParts, 'w:r');
$search = static::ensureMacroCompleted($search);
$this->replaceXmlBlock($search, $xmlWriter->getData(), 'w:r');
}
/**
* @param string $search
* @param \PhpOffice\PhpWord\Element\AbstractElement $complexType
*/
public function setComplexBlock($search, \PhpOffice\PhpWord\Element\AbstractElement $complexType)
{
$elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1);
$objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName;
$xmlWriter = new XMLWriter();
/** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $elementWriter */
$elementWriter = new $objectClass($xmlWriter, $complexType, false);
$elementWriter->write();
$this->replaceXmlBlock($search, $xmlWriter->getData(), 'w:p');
}
/**
* @param mixed $search
* @param mixed $replace
@ -208,18 +307,20 @@ class TemplateProcessor
{
if (is_array($search)) {
foreach ($search as &$item) {
$item = self::ensureMacroCompleted($item);
$item = static::ensureMacroCompleted($item);
}
unset($item);
} else {
$search = self::ensureMacroCompleted($search);
$search = static::ensureMacroCompleted($search);
}
if (is_array($replace)) {
foreach ($replace as &$item) {
$item = self::ensureUtf8Encoded($item);
$item = static::ensureUtf8Encoded($item);
}
unset($item);
} else {
$replace = self::ensureUtf8Encoded($replace);
$replace = static::ensureUtf8Encoded($replace);
}
if (Settings::isOutputEscapingEnabled()) {
@ -232,6 +333,312 @@ class TemplateProcessor
$this->tempDocumentFooters = $this->setValueForPart($search, $replace, $this->tempDocumentFooters, $limit);
}
/**
* Set values from a one-dimensional array of "variable => value"-pairs.
*
* @param array $values
*/
public function setValues(array $values)
{
foreach ($values as $macro => $replace) {
$this->setValue($macro, $replace);
}
}
private function getImageArgs($varNameWithArgs)
{
$varElements = explode(':', $varNameWithArgs);
array_shift($varElements); // first element is name of variable => remove it
$varInlineArgs = array();
// size format documentation: https://msdn.microsoft.com/en-us/library/documentformat.openxml.vml.shape%28v=office.14%29.aspx?f=255&MSPPError=-2147217396
foreach ($varElements as $argIdx => $varArg) {
if (strpos($varArg, '=')) { // arg=value
list($argName, $argValue) = explode('=', $varArg, 2);
$argName = strtolower($argName);
if ($argName == 'size') {
list($varInlineArgs['width'], $varInlineArgs['height']) = explode('x', $argValue, 2);
} else {
$varInlineArgs[strtolower($argName)] = $argValue;
}
} elseif (preg_match('/^([0-9]*[a-z%]{0,2}|auto)x([0-9]*[a-z%]{0,2}|auto)$/i', $varArg)) { // 60x40
list($varInlineArgs['width'], $varInlineArgs['height']) = explode('x', $varArg, 2);
} else { // :60:40:f
switch ($argIdx) {
case 0:
$varInlineArgs['width'] = $varArg;
break;
case 1:
$varInlineArgs['height'] = $varArg;
break;
case 2:
$varInlineArgs['ratio'] = $varArg;
break;
}
}
}
return $varInlineArgs;
}
private function chooseImageDimension($baseValue, $inlineValue, $defaultValue)
{
$value = $baseValue;
if (is_null($value) && isset($inlineValue)) {
$value = $inlineValue;
}
if (!preg_match('/^([0-9]*(cm|mm|in|pt|pc|px|%|em|ex|)|auto)$/i', $value)) {
$value = null;
}
if (is_null($value)) {
$value = $defaultValue;
}
if (is_numeric($value)) {
$value .= 'px';
}
return $value;
}
private function fixImageWidthHeightRatio(&$width, &$height, $actualWidth, $actualHeight)
{
$imageRatio = $actualWidth / $actualHeight;
if (($width === '') && ($height === '')) { // defined size are empty
$width = $actualWidth . 'px';
$height = $actualHeight . 'px';
} elseif ($width === '') { // defined width is empty
$heightFloat = (float) $height;
$widthFloat = $heightFloat * $imageRatio;
$matches = array();
preg_match("/\d([a-z%]+)$/", $height, $matches);
$width = $widthFloat . $matches[1];
} elseif ($height === '') { // defined height is empty
$widthFloat = (float) $width;
$heightFloat = $widthFloat / $imageRatio;
$matches = array();
preg_match("/\d([a-z%]+)$/", $width, $matches);
$height = $heightFloat . $matches[1];
} else { // we have defined size, but we need also check it aspect ratio
$widthMatches = array();
preg_match("/\d([a-z%]+)$/", $width, $widthMatches);
$heightMatches = array();
preg_match("/\d([a-z%]+)$/", $height, $heightMatches);
// try to fix only if dimensions are same
if ($widthMatches[1] == $heightMatches[1]) {
$dimention = $widthMatches[1];
$widthFloat = (float) $width;
$heightFloat = (float) $height;
$definedRatio = $widthFloat / $heightFloat;
if ($imageRatio > $definedRatio) { // image wider than defined box
$height = ($widthFloat / $imageRatio) . $dimention;
} elseif ($imageRatio < $definedRatio) { // image higher than defined box
$width = ($heightFloat * $imageRatio) . $dimention;
}
}
}
}
private function prepareImageAttrs($replaceImage, $varInlineArgs)
{
// get image path and size
$width = null;
$height = null;
$ratio = null;
if (is_array($replaceImage) && isset($replaceImage['path'])) {
$imgPath = $replaceImage['path'];
if (isset($replaceImage['width'])) {
$width = $replaceImage['width'];
}
if (isset($replaceImage['height'])) {
$height = $replaceImage['height'];
}
if (isset($replaceImage['ratio'])) {
$ratio = $replaceImage['ratio'];
}
} else {
$imgPath = $replaceImage;
}
$width = $this->chooseImageDimension($width, isset($varInlineArgs['width']) ? $varInlineArgs['width'] : null, 115);
$height = $this->chooseImageDimension($height, isset($varInlineArgs['height']) ? $varInlineArgs['height'] : null, 70);
$imageData = @getimagesize($imgPath);
if (!is_array($imageData)) {
throw new Exception(sprintf('Invalid image: %s', $imgPath));
}
list($actualWidth, $actualHeight, $imageType) = $imageData;
// fix aspect ratio (by default)
if (is_null($ratio) && isset($varInlineArgs['ratio'])) {
$ratio = $varInlineArgs['ratio'];
}
if (is_null($ratio) || !in_array(strtolower($ratio), array('', '-', 'f', 'false'))) {
$this->fixImageWidthHeightRatio($width, $height, $actualWidth, $actualHeight);
}
$imageAttrs = array(
'src' => $imgPath,
'mime' => image_type_to_mime_type($imageType),
'width' => $width,
'height' => $height,
);
return $imageAttrs;
}
private function addImageToRelations($partFileName, $rid, $imgPath, $imageMimeType)
{
// define templates
$typeTpl = '<Override PartName="/word/media/{IMG}" ContentType="image/{EXT}"/>';
$relationTpl = '<Relationship Id="{RID}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/{IMG}"/>';
$newRelationsTpl = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n" . '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>';
$newRelationsTypeTpl = '<Override PartName="/{RELS}" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
$extTransform = array(
'image/jpeg' => 'jpeg',
'image/png' => 'png',
'image/bmp' => 'bmp',
'image/gif' => 'gif',
);
// get image embed name
if (isset($this->tempDocumentNewImages[$imgPath])) {
$imgName = $this->tempDocumentNewImages[$imgPath];
} else {
// transform extension
if (isset($extTransform[$imageMimeType])) {
$imgExt = $extTransform[$imageMimeType];
} else {
throw new Exception("Unsupported image type $imageMimeType");
}
// add image to document
$imgName = 'image_' . $rid . '_' . pathinfo($partFileName, PATHINFO_FILENAME) . '.' . $imgExt;
$this->zipClass->pclzipAddFile($imgPath, 'word/media/' . $imgName);
$this->tempDocumentNewImages[$imgPath] = $imgName;
// setup type for image
$xmlImageType = str_replace(array('{IMG}', '{EXT}'), array($imgName, $imgExt), $typeTpl);
$this->tempDocumentContentTypes = str_replace('</Types>', $xmlImageType, $this->tempDocumentContentTypes) . '</Types>';
}
$xmlImageRelation = str_replace(array('{RID}', '{IMG}'), array($rid, $imgName), $relationTpl);
if (!isset($this->tempDocumentRelations[$partFileName])) {
// create new relations file
$this->tempDocumentRelations[$partFileName] = $newRelationsTpl;
// and add it to content types
$xmlRelationsType = str_replace('{RELS}', $this->getRelationsName($partFileName), $newRelationsTypeTpl);
$this->tempDocumentContentTypes = str_replace('</Types>', $xmlRelationsType, $this->tempDocumentContentTypes) . '</Types>';
}
// add image to relations
$this->tempDocumentRelations[$partFileName] = str_replace('</Relationships>', $xmlImageRelation, $this->tempDocumentRelations[$partFileName]) . '</Relationships>';
}
/**
* @param mixed $search
* @param mixed $replace Path to image, or array("path" => xx, "width" => yy, "height" => zz)
* @param int $limit
*/
public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_DEFAULT)
{
// prepare $search_replace
if (!is_array($search)) {
$search = array($search);
}
$replacesList = array();
if (!is_array($replace) || isset($replace['path'])) {
$replacesList[] = $replace;
} else {
$replacesList = array_values($replace);
}
$searchReplace = array();
foreach ($search as $searchIdx => $searchString) {
$searchReplace[$searchString] = isset($replacesList[$searchIdx]) ? $replacesList[$searchIdx] : $replacesList[0];
}
// collect document parts
$searchParts = array(
$this->getMainPartName() => &$this->tempDocumentMainPart,
);
foreach (array_keys($this->tempDocumentHeaders) as $headerIndex) {
$searchParts[$this->getHeaderName($headerIndex)] = &$this->tempDocumentHeaders[$headerIndex];
}
foreach (array_keys($this->tempDocumentFooters) as $headerIndex) {
$searchParts[$this->getFooterName($headerIndex)] = &$this->tempDocumentFooters[$headerIndex];
}
// define templates
// result can be verified via "Open XML SDK 2.5 Productivity Tool" (http://www.microsoft.com/en-us/download/details.aspx?id=30425)
$imgTpl = '<w:pict><v:shape type="#_x0000_t75" style="width:{WIDTH};height:{HEIGHT}"><v:imagedata r:id="{RID}" o:title=""/></v:shape></w:pict>';
foreach ($searchParts as $partFileName => &$partContent) {
$partVariables = $this->getVariablesForPart($partContent);
foreach ($searchReplace as $searchString => $replaceImage) {
$varsToReplace = array_filter($partVariables, function ($partVar) use ($searchString) {
return ($partVar == $searchString) || preg_match('/^' . preg_quote($searchString) . ':/', $partVar);
});
foreach ($varsToReplace as $varNameWithArgs) {
$varInlineArgs = $this->getImageArgs($varNameWithArgs);
$preparedImageAttrs = $this->prepareImageAttrs($replaceImage, $varInlineArgs);
$imgPath = $preparedImageAttrs['src'];
// get image index
$imgIndex = $this->getNextRelationsIndex($partFileName);
$rid = 'rId' . $imgIndex;
// replace preparations
$this->addImageToRelations($partFileName, $rid, $imgPath, $preparedImageAttrs['mime']);
$xmlImage = str_replace(array('{RID}', '{WIDTH}', '{HEIGHT}'), array($rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']), $imgTpl);
// replace variable
$varNameWithArgsFixed = static::ensureMacroCompleted($varNameWithArgs);
$matches = array();
if (preg_match('/(<[^<]+>)([^<]*)(' . preg_quote($varNameWithArgsFixed) . ')([^>]*)(<[^>]+>)/Uu', $partContent, $matches)) {
$wholeTag = $matches[0];
array_shift($matches);
list($openTag, $prefix, , $postfix, $closeTag) = $matches;
$replaceXml = $openTag . $prefix . $closeTag . $xmlImage . $openTag . $postfix . $closeTag;
// replace on each iteration, because in one tag we can have 2+ inline variables => before proceed next variable we need to change $partContent
$partContent = $this->setValueForPart($wholeTag, $replaceXml, $partContent, $limit);
}
}
}
}
}
/**
* Returns count of all variables in template.
*
* @return array
*/
public function getVariableCount()
{
$variables = $this->getVariablesForPart($this->tempDocumentMainPart);
foreach ($this->tempDocumentHeaders as $headerXML) {
$variables = array_merge(
$variables,
$this->getVariablesForPart($headerXML)
);
}
foreach ($this->tempDocumentFooters as $footerXML) {
$variables = array_merge(
$variables,
$this->getVariablesForPart($footerXML)
);
}
return array_count_values($variables);
}
/**
* Returns array of all variables in template.
*
@ -239,17 +646,7 @@ class TemplateProcessor
*/
public function getVariables()
{
$variables = $this->getVariablesForPart($this->tempDocumentMainPart);
foreach ($this->tempDocumentHeaders as $headerXML) {
$variables = array_merge($variables, $this->getVariablesForPart($headerXML));
}
foreach ($this->tempDocumentFooters as $footerXML) {
$variables = array_merge($variables, $this->getVariablesForPart($footerXML));
}
return array_unique($variables);
return array_keys($this->getVariableCount());
}
/**
@ -262,9 +659,7 @@ class TemplateProcessor
*/
public function cloneRow($search, $numberOfClones)
{
if ('${' !== substr($search, 0, 2) && '}' !== substr($search, -1)) {
$search = '${' . $search . '}';
}
$search = static::ensureMacroCompleted($search);
$tagPos = strpos($this->tempDocumentMainPart, $search);
if (!$tagPos) {
@ -291,7 +686,7 @@ class TemplateProcessor
// If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row.
$tmpXmlRow = $this->getSlice($extraRowStart, $extraRowEnd);
if (!preg_match('#<w:vMerge/>#', $tmpXmlRow) &&
!preg_match('#<w:vMerge w:val="continue" />#', $tmpXmlRow)) {
!preg_match('#<w:vMerge w:val="continue"\s*/>#', $tmpXmlRow)) {
break;
}
// This row was a spanned row, update $rowEnd and search for the next row.
@ -301,37 +696,62 @@ class TemplateProcessor
}
$result = $this->getSlice(0, $rowStart);
for ($i = 1; $i <= $numberOfClones; $i++) {
$result .= preg_replace('/\$\{(.*?)\}/', '\${\\1#' . $i . '}', $xmlRow);
}
$result .= implode($this->indexClonedVariables($numberOfClones, $xmlRow));
$result .= $this->getSlice($rowEnd);
$this->tempDocumentMainPart = $result;
}
/**
* Clones a table row and populates it's values from a two-dimensional array in a template document.
*
* @param string $search
* @param array $values
*/
public function cloneRowAndSetValues($search, $values)
{
$this->cloneRow($search, count($values));
foreach ($values as $rowKey => $rowData) {
$rowNumber = $rowKey + 1;
foreach ($rowData as $macro => $replace) {
$this->setValue($macro . '#' . $rowNumber, $replace);
}
}
}
/**
* Clone a block.
*
* @param string $blockname
* @param int $clones
* @param int $clones How many time the block should be cloned
* @param bool $replace
* @param bool $indexVariables If true, any variables inside the block will be indexed (postfixed with #1, #2, ...)
* @param array $variableReplacements Array containing replacements for macros found inside the block to clone
*
* @return string|null
*/
public function cloneBlock($blockname, $clones = 1, $replace = true)
public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVariables = false, $variableReplacements = null)
{
$xmlBlock = null;
$matches = array();
preg_match(
'/(<\?xml.*)(<w:p.*>\${' . $blockname . '}<\/w:.*?p>)(.*)(<w:p.*\${\/' . $blockname . '}<\/w:.*?p>)/is',
'/(<\?xml.*)(<w:p\b.*>\${' . $blockname . '}<\/w:.*?p>)(.*)(<w:p\b.*\${\/' . $blockname . '}<\/w:.*?p>)/is',
$this->tempDocumentMainPart,
$matches
);
if (isset($matches[3])) {
$xmlBlock = $matches[3];
$cloned = array();
for ($i = 1; $i <= $clones; $i++) {
$cloned[] = $xmlBlock;
if ($indexVariables) {
$cloned = $this->indexClonedVariables($clones, $xmlBlock);
} elseif ($variableReplacements !== null && is_array($variableReplacements)) {
$cloned = $this->replaceClonedVariables($variableReplacements, $xmlBlock);
} else {
$cloned = array();
for ($i = 1; $i <= $clones; $i++) {
$cloned[] = $xmlBlock;
}
}
if ($replace) {
@ -354,6 +774,7 @@ class TemplateProcessor
*/
public function replaceBlock($blockname, $replacement)
{
$matches = array();
preg_match(
'/(<\?xml.*)(<w:p.*>\${' . $blockname . '}<\/w:.*?p>)(.*)(<w:p.*\${\/' . $blockname . '}<\/w:.*?p>)/is',
$this->tempDocumentMainPart,
@ -379,6 +800,22 @@ class TemplateProcessor
$this->replaceBlock($blockname, '');
}
/**
* Automatically Recalculate Fields on Open
*
* @param bool $update
*/
public function setUpdateFields($update = true)
{
$string = $update ? 'true' : 'false';
$matches = array();
if (preg_match('/<w:updateFields w:val=\"(true|false|1|0|on|off)\"\/>/', $this->tempDocumentSettingsPart, $matches)) {
$this->tempDocumentSettingsPart = str_replace($matches[0], '<w:updateFields w:val="' . $string . '"/>', $this->tempDocumentSettingsPart);
} else {
$this->tempDocumentSettingsPart = str_replace('</w:settings>', '<w:updateFields w:val="' . $string . '"/></w:settings>', $this->tempDocumentSettingsPart);
}
}
/**
* Saves the result document.
*
@ -389,23 +826,39 @@ class TemplateProcessor
public function save()
{
foreach ($this->tempDocumentHeaders as $index => $xml) {
$this->zipClass->addFromString($this->getHeaderName($index), $xml);
$this->savePartWithRels($this->getHeaderName($index), $xml);
}
$this->zipClass->addFromString($this->getMainPartName(), $this->tempDocumentMainPart);
$this->savePartWithRels($this->getMainPartName(), $this->tempDocumentMainPart);
$this->savePartWithRels($this->getSettingsPartName(), $this->tempDocumentSettingsPart);
foreach ($this->tempDocumentFooters as $index => $xml) {
$this->zipClass->addFromString($this->getFooterName($index), $xml);
$this->savePartWithRels($this->getFooterName($index), $xml);
}
$this->zipClass->addFromString($this->getDocumentContentTypesName(), $this->tempDocumentContentTypes);
// Close zip file
if (false === $this->zipClass->close()) {
throw new Exception('Could not close zip file.');
throw new Exception('Could not close zip file.'); // @codeCoverageIgnore
}
return $this->tempDocumentFilename;
}
/**
* @param string $fileName
* @param string $xml
*/
protected function savePartWithRels($fileName, $xml)
{
$this->zipClass->addFromString($fileName, $xml);
if (isset($this->tempDocumentRelations[$fileName])) {
$relsFileName = $this->getRelationsName($fileName);
$this->zipClass->addFromString($relsFileName, $this->tempDocumentRelations[$fileName]);
}
}
/**
* Saves the result document to the user defined file.
*
@ -441,17 +894,13 @@ class TemplateProcessor
*/
protected function fixBrokenMacros($documentPart)
{
$fixedDocumentPart = $documentPart;
$fixedDocumentPart = preg_replace_callback(
'|\$[^{]*\{[^}]*\}|U',
return preg_replace_callback(
'/\$(?:\{|[^{$]*\>\{)[^}$]*\}/U',
function ($match) {
return strip_tags($match[0]);
},
$fixedDocumentPart
$documentPart
);
return $fixedDocumentPart;
}
/**
@ -484,6 +933,7 @@ class TemplateProcessor
*/
protected function getVariablesForPart($documentPartXML)
{
$matches = array();
preg_match_all('/\$\{(.*?)}/i', $documentPartXML, $matches);
return $matches[1];
@ -502,11 +952,30 @@ class TemplateProcessor
}
/**
* Usually, the name of main part document will be 'document.xml'. However, some .docx files (possibly those from Office 365, experienced also on documents from Word Online created from blank templates) have file 'document22.xml' in their zip archive instead of 'document.xml'. This method searches content types file to correctly determine the file name.
*
* @return string
*/
protected function getMainPartName()
{
return 'word/document.xml';
$contentTypes = $this->zipClass->getFromName('[Content_Types].xml');
$pattern = '~PartName="\/(word\/document.*?\.xml)" ContentType="application\/vnd\.openxmlformats-officedocument\.wordprocessingml\.document\.main\+xml"~';
$matches = array();
preg_match($pattern, $contentTypes, $matches);
return array_key_exists(1, $matches) ? $matches[1] : 'word/document.xml';
}
/**
* The name of the file containing the Settings part
*
* @return string
*/
protected function getSettingsPartName()
{
return 'word/settings.xml';
}
/**
@ -521,6 +990,35 @@ class TemplateProcessor
return sprintf('word/footer%d.xml', $index);
}
/**
* Get the name of the relations file for document part.
*
* @param string $documentPartName
*
* @return string
*/
protected function getRelationsName($documentPartName)
{
return 'word/_rels/' . pathinfo($documentPartName, PATHINFO_BASENAME) . '.rels';
}
protected function getNextRelationsIndex($documentPartName)
{
if (isset($this->tempDocumentRelations[$documentPartName])) {
return substr_count($this->tempDocumentRelations[$documentPartName], '<Relationship');
}
return 1;
}
/**
* @return string
*/
protected function getDocumentContentTypesName()
{
return '[Content_Types].xml';
}
/**
* Find the start position of the nearest table row before $offset.
*
@ -572,4 +1070,182 @@ class TemplateProcessor
return substr($this->tempDocumentMainPart, $startPosition, ($endPosition - $startPosition));
}
/**
* Replaces variable names in cloned
* rows/blocks with indexed names
*
* @param int $count
* @param string $xmlBlock
*
* @return string
*/
protected function indexClonedVariables($count, $xmlBlock)
{
$results = array();
for ($i = 1; $i <= $count; $i++) {
$results[] = preg_replace('/\$\{(.*?)\}/', '\${\\1#' . $i . '}', $xmlBlock);
}
return $results;
}
/**
* Raplaces variables with values from array, array keys are the variable names
*
* @param array $variableReplacements
* @param string $xmlBlock
*
* @return string[]
*/
protected function replaceClonedVariables($variableReplacements, $xmlBlock)
{
$results = array();
foreach ($variableReplacements as $replacementArray) {
$localXmlBlock = $xmlBlock;
foreach ($replacementArray as $search => $replacement) {
$localXmlBlock = $this->setValueForPart(self::ensureMacroCompleted($search), $replacement, $localXmlBlock, self::MAXIMUM_REPLACEMENTS_DEFAULT);
}
$results[] = $localXmlBlock;
}
return $results;
}
/**
* Replace an XML block surrounding a macro with a new block
*
* @param string $macro Name of macro
* @param string $block New block content
* @param string $blockType XML tag type of block
* @return \PhpOffice\PhpWord\TemplateProcessor Fluent interface
*/
protected function replaceXmlBlock($macro, $block, $blockType = 'w:p')
{
$where = $this->findContainingXmlBlockForMacro($macro, $blockType);
if (is_array($where)) {
$this->tempDocumentMainPart = $this->getSlice(0, $where['start']) . $block . $this->getSlice($where['end']);
}
return $this;
}
/**
* Find start and end of XML block containing the given macro
* e.g. <w:p>...${macro}...</w:p>
*
* Note that only the first instance of the macro will be found
*
* @param string $macro Name of macro
* @param string $blockType XML tag for block
* @return bool|int[] FALSE if not found, otherwise array with start and end
*/
protected function findContainingXmlBlockForMacro($macro, $blockType = 'w:p')
{
$macroPos = $this->findMacro($macro);
if (0 > $macroPos) {
return false;
}
$start = $this->findXmlBlockStart($macroPos, $blockType);
if (0 > $start) {
return false;
}
$end = $this->findXmlBlockEnd($start, $blockType);
//if not found or if resulting string does not contain the macro we are searching for
if (0 > $end || strstr($this->getSlice($start, $end), $macro) === false) {
return false;
}
return array('start' => $start, 'end' => $end);
}
/**
* Find the position of (the start of) a macro
*
* Returns -1 if not found, otherwise position of opening $
*
* Note that only the first instance of the macro will be found
*
* @param string $search Macro name
* @param int $offset Offset from which to start searching
* @return int -1 if macro not found
*/
protected function findMacro($search, $offset = 0)
{
$search = static::ensureMacroCompleted($search);
$pos = strpos($this->tempDocumentMainPart, $search, $offset);
return ($pos === false) ? -1 : $pos;
}
/**
* Find the start position of the nearest XML block start before $offset
*
* @param int $offset Search position
* @param string $blockType XML Block tag
* @return int -1 if block start not found
*/
protected function findXmlBlockStart($offset, $blockType)
{
$reverseOffset = (strlen($this->tempDocumentMainPart) - $offset) * -1;
// first try XML tag with attributes
$blockStart = strrpos($this->tempDocumentMainPart, '<' . $blockType . ' ', $reverseOffset);
// if not found, or if found but contains the XML tag without attribute
if (false === $blockStart || strrpos($this->getSlice($blockStart, $offset), '<' . $blockType . '>')) {
// also try XML tag without attributes
$blockStart = strrpos($this->tempDocumentMainPart, '<' . $blockType . '>', $reverseOffset);
}
return ($blockStart === false) ? -1 : $blockStart;
}
/**
* Find the nearest block end position after $offset
*
* @param int $offset Search position
* @param string $blockType XML Block tag
* @return int -1 if block end not found
*/
protected function findXmlBlockEnd($offset, $blockType)
{
$blockEndStart = strpos($this->tempDocumentMainPart, '</' . $blockType . '>', $offset);
// return position of end of tag if found, otherwise -1
return ($blockEndStart === false) ? -1 : $blockEndStart + 3 + strlen($blockType);
}
/**
* Splits a w:r/w:t into a list of w:r where each ${macro} is in a separate w:r
*
* @param string $text
* @return string
*/
protected function splitTextIntoTexts($text)
{
if (!$this->textNeedsSplitting($text)) {
return $text;
}
$matches = array();
if (preg_match('/(<w:rPr.*<\/w:rPr>)/i', $text, $matches)) {
$extractedStyle = $matches[0];
} else {
$extractedStyle = '';
}
$unformattedText = preg_replace('/>\s+</', '><', $text);
$result = str_replace(array('${', '}'), array('</w:t></w:r><w:r>' . $extractedStyle . '<w:t xml:space="preserve">${', '}</w:t></w:r><w:r>' . $extractedStyle . '<w:t xml:space="preserve">'), $unformattedText);
return str_replace(array('<w:r>' . $extractedStyle . '<w:t xml:space="preserve"></w:t></w:r>', '<w:r><w:t xml:space="preserve"></w:t></w:r>', '<w:t>'), array('', '', '<w:t xml:space="preserve">'), $result);
}
/**
* Returns true if string contains a macro that is not in it's own w:r
*
* @param string $text
* @return bool
*/
protected function textNeedsSplitting($text)
{
return preg_match('/[^>]\${|}[^<]/i', $text) == 1;
}
}

View File

@ -220,7 +220,7 @@ abstract class AbstractWriter implements WriterInterface
// Temporary file
$this->originalFilename = $filename;
if (strtolower($filename) == 'php://output' || strtolower($filename) == 'php://stdout') {
if (strpos(strtolower($filename), 'php://') === 0) {
$filename = tempnam(Settings::getTempDir(), 'PhpWord');
if (false === $filename) {
$filename = $this->originalFilename; // @codeCoverageIgnore

View File

@ -39,7 +39,8 @@ class Table extends AbstractElement
$rows = $this->element->getRows();
$rowCount = count($rows);
if ($rowCount > 0) {
$content .= '<table>' . PHP_EOL;
$content .= '<table' . self::getTableStyle($this->element->getStyle()) . '>' . PHP_EOL;
for ($i = 0; $i < $rowCount; $i++) {
/** @var $row \PhpOffice\PhpWord\Element\Row Type hint */
$rowStyle = $rows[$i]->getStyle();
@ -112,4 +113,29 @@ class Table extends AbstractElement
return $content;
}
/**
* Translates Table style in CSS equivalent
*
* @param string|\PhpOffice\PhpWord\Style\Table|null $tableStyle
* @return string
*/
private function getTableStyle($tableStyle = null)
{
if ($tableStyle == null) {
return '';
}
if (is_string($tableStyle)) {
$style = ' class="' . $tableStyle;
} else {
$style = ' style="';
if ($tableStyle->getLayout() == \PhpOffice\PhpWord\Style\Table::LAYOUT_FIXED) {
$style .= 'table-layout: fixed;';
} elseif ($tableStyle->getLayout() == \PhpOffice\PhpWord\Style\Table::LAYOUT_AUTO) {
$style .= 'table-layout: auto;';
}
}
return $style . '"';
}
}

View File

@ -45,7 +45,7 @@ class Title extends AbstractElement
$text = $this->escaper->escapeHtml($text);
}
} elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) {
$writer = new Container($this->parentWriter, $this->element);
$writer = new Container($this->parentWriter, $text);
$text = $writer->write();
}

View File

@ -60,6 +60,7 @@ class Font extends AbstractStyle
$css['text-decoration'] .= $this->getValueIf($lineThrough, 'line-through ');
$css['text-transform'] = $this->getValueIf($style->isAllCaps(), 'uppercase');
$css['font-variant'] = $this->getValueIf($style->isSmallCaps(), 'small-caps');
$css['display'] = $this->getValueIf($style->isHidden(), 'none');
$spacing = $style->getSpacing();
$css['letter-spacing'] = $this->getValueIf(!is_null($spacing), ($spacing / 20) . 'pt');

View File

@ -17,6 +17,10 @@
namespace PhpOffice\PhpWord\Writer\ODText\Element;
use PhpOffice\Common\XMLWriter;
use PhpOffice\PhpWord\Element\Row as RowElement;
use PhpOffice\PhpWord\Element\Table as TableElement;
/**
* Table element writer
*
@ -36,32 +40,59 @@ class Table extends AbstractElement
}
$rows = $element->getRows();
$rowCount = count($rows);
$colCount = $element->countColumns();
if ($rowCount > 0) {
$xmlWriter->startElement('table:table');
$xmlWriter->writeAttribute('table:name', $element->getElementId());
$xmlWriter->writeAttribute('table:style', $element->getElementId());
$xmlWriter->startElement('table:table-column');
$xmlWriter->writeAttribute('table:number-columns-repeated', $colCount);
$xmlWriter->endElement(); // table:table-column
// Write columns
$this->writeColumns($xmlWriter, $element);
// Write rows
foreach ($rows as $row) {
$xmlWriter->startElement('table:table-row');
/** @var $row \PhpOffice\PhpWord\Element\Row Type hint */
foreach ($row->getCells() as $cell) {
$xmlWriter->startElement('table:table-cell');
$xmlWriter->writeAttribute('office:value-type', 'string');
$containerWriter = new Container($xmlWriter, $cell);
$containerWriter->write();
$xmlWriter->endElement(); // table:table-cell
}
$xmlWriter->endElement(); // table:table-row
$this->writeRow($xmlWriter, $row);
}
$xmlWriter->endElement(); // table:table
}
}
/**
* Write column.
*
* @param \PhpOffice\Common\XMLWriter $xmlWriter
* @param \PhpOffice\PhpWord\Element\Table $element
*/
private function writeColumns(XMLWriter $xmlWriter, TableElement $element)
{
$colCount = $element->countColumns();
for ($i = 0; $i < $colCount; $i++) {
$xmlWriter->startElement('table:table-column');
$xmlWriter->writeAttribute('table:style-name', $element->getElementId() . '.' . $i);
$xmlWriter->endElement();
}
}
/**
* Write row.
*
* @param \PhpOffice\Common\XMLWriter $xmlWriter
* @param \PhpOffice\PhpWord\Element\Row $row
*/
private function writeRow(XMLWriter $xmlWriter, RowElement $row)
{
$xmlWriter->startElement('table:table-row');
/** @var $row \PhpOffice\PhpWord\Element\Row Type hint */
foreach ($row->getCells() as $cell) {
$xmlWriter->startElement('table:table-cell');
$xmlWriter->writeAttribute('office:value-type', 'string');
$containerWriter = new Container($xmlWriter, $cell);
$containerWriter->write();
$xmlWriter->endElement(); // table:table-cell
}
$xmlWriter->endElement(); // table:table-row
}
}

View File

@ -239,6 +239,7 @@ class Content extends AbstractPart
$style->setStyleName('fr' . $element->getMediaIndex());
$this->autoStyles['Image'][] = $style;
} elseif ($element instanceof Table) {
/** @var \PhpOffice\PhpWord\Style\Table $style */
$style = $element->getStyle();
if ($style === null) {
$style = new TableStyle();
@ -246,6 +247,7 @@ class Content extends AbstractPart
$style = Style::getStyle($style);
}
$style->setStyleName($element->getElementId());
$style->setColumnWidths($element->findFirstDefinedCellWidths());
$this->autoStyles['Table'][] = $style;
}
}

View File

@ -75,6 +75,9 @@ class Font extends AbstractStyle
$xmlWriter->writeAttributeIf($style->isSmallCaps(), 'fo:font-variant', 'small-caps');
$xmlWriter->writeAttributeIf($style->isAllCaps(), 'fo:text-transform', 'uppercase');
//Hidden text
$xmlWriter->writeAttributeIf($style->isHidden(), 'text:display', 'none');
// Superscript/subscript
$xmlWriter->writeAttributeIf($style->isSuperScript(), 'style:text-position', 'super');
$xmlWriter->writeAttributeIf($style->isSubScript(), 'style:text-position', 'sub');

View File

@ -54,6 +54,10 @@ class Paragraph extends AbstractStyle
$xmlWriter->writeAttribute('fo:margin-bottom', $marginBottom . 'cm');
$xmlWriter->writeAttribute('fo:text-align', $style->getAlignment());
}
//Right to left
$xmlWriter->writeAttributeIf($style->isBidi(), 'style:writing-mode', 'rl-tb');
$xmlWriter->endElement(); //style:paragraph-properties
$xmlWriter->endElement(); //style:style

View File

@ -43,7 +43,22 @@ class Table extends AbstractStyle
//$xmlWriter->writeAttribute('style:width', 'table');
$xmlWriter->writeAttribute('style:rel-width', 100);
$xmlWriter->writeAttribute('table:align', 'center');
$xmlWriter->writeAttributeIf($style->isBidiVisual(), 'style:writing-mode', 'rl-tb');
$xmlWriter->endElement(); // style:table-properties
$xmlWriter->endElement(); // style:style
$cellWidths = $style->getColumnWidths();
$countCellWidths = $cellWidths === null ? 0 : count($cellWidths);
for ($i = 0; $i < $countCellWidths; $i++) {
$width = $cellWidths[$i];
$xmlWriter->startElement('style:style');
$xmlWriter->writeAttribute('style:name', $style->getStyleName() . '.' . $i);
$xmlWriter->writeAttribute('style:family', 'table-column');
$xmlWriter->startElement('style:table-column-properties');
$xmlWriter->writeAttribute('style:column-width', number_format($width * 0.0017638889, 2, '.', '') . 'cm');
$xmlWriter->endElement(); // style:table-column-properties
$xmlWriter->endElement(); // style:style
}
}
}

View File

@ -92,7 +92,7 @@ class Border extends AbstractStyle
$content .= '\pgbrdr' . substr($side, 0, 1);
$content .= '\brdrs'; // Single-thickness border; @todo Get other type of border
$content .= '\brdrw' . $width; // Width
$content .= '\brdrw' . round($width); // Width
$content .= '\brdrcf' . $colorIndex; // Color
$content .= '\brsp480'; // Space in twips between borders and the paragraph (24pt, following OOXML)
$content .= ' ';

View File

@ -53,7 +53,7 @@ class Font extends AbstractStyle
$content .= '\f' . $this->nameIndex;
$size = $style->getSize();
$content .= $this->getValueIf(is_numeric($size), '\fs' . ($size * 2));
$content .= $this->getValueIf(is_numeric($size), '\fs' . round($size * 2));
$content .= $this->getValueIf($style->isBold(), '\b');
$content .= $this->getValueIf($style->isItalic(), '\i');

View File

@ -0,0 +1,45 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
* @copyright 2010-2018 PHPWord contributors
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Writer\RTF\Style;
/**
* RTF indentation style writer
*
* @since 0.11.0
*/
class Indentation extends AbstractStyle
{
/**
* Write style
*
* @return string
*/
public function write()
{
$style = $this->getStyle();
if (!$style instanceof \PhpOffice\PhpWord\Style\Indentation) {
return '';
}
$content = '\fi' . round($style->getFirstLine());
$content .= '\li' . round($style->getLeft());
$content .= '\ri' . round($style->getRight());
return $content . ' ';
}
}

View File

@ -64,8 +64,48 @@ class Paragraph extends AbstractStyle
if (isset($alignments[$style->getAlignment()])) {
$content .= $alignments[$style->getAlignment()];
}
$content .= $this->getValueIf($spaceBefore !== null, '\sb' . $spaceBefore);
$content .= $this->getValueIf($spaceAfter !== null, '\sa' . $spaceAfter);
$content .= $this->writeIndentation($style->getIndentation());
$content .= $this->getValueIf($spaceBefore !== null, '\sb' . round($spaceBefore));
$content .= $this->getValueIf($spaceAfter !== null, '\sa' . round($spaceAfter));
$styles = $style->getStyleValues();
$content .= $this->writeTabs($styles['tabs']);
return $content;
}
/**
* Writes an \PhpOffice\PhpWord\Style\Indentation
*
* @param null|\PhpOffice\PhpWord\Style\Indentation $indent
* @return string
*/
private function writeIndentation($indent = null)
{
if (isset($indent) && $indent instanceof \PhpOffice\PhpWord\Style\Indentation) {
$writer = new Indentation($indent);
return $writer->write();
}
return '';
}
/**
* Writes tabs
*
* @param \PhpOffice\PhpWord\Style\Tab[] $tabs
* @return string
*/
private function writeTabs($tabs = null)
{
$content = '';
if (!empty($tabs)) {
foreach ($tabs as $tab) {
$styleWriter = new Tab($tab);
$content .= $styleWriter->write();
}
}
return $content;
}

View File

@ -43,16 +43,16 @@ class Section extends AbstractStyle
$content .= '\sectd ';
// Size & margin
$content .= $this->getValueIf($style->getPageSizeW() !== null, '\pgwsxn' . $style->getPageSizeW());
$content .= $this->getValueIf($style->getPageSizeH() !== null, '\pghsxn' . $style->getPageSizeH());
$content .= $this->getValueIf($style->getPageSizeW() !== null, '\pgwsxn' . round($style->getPageSizeW()));
$content .= $this->getValueIf($style->getPageSizeH() !== null, '\pghsxn' . round($style->getPageSizeH()));
$content .= ' ';
$content .= $this->getValueIf($style->getMarginTop() !== null, '\margtsxn' . $style->getMarginTop());
$content .= $this->getValueIf($style->getMarginRight() !== null, '\margrsxn' . $style->getMarginRight());
$content .= $this->getValueIf($style->getMarginBottom() !== null, '\margbsxn' . $style->getMarginBottom());
$content .= $this->getValueIf($style->getMarginLeft() !== null, '\marglsxn' . $style->getMarginLeft());
$content .= $this->getValueIf($style->getHeaderHeight() !== null, '\headery' . $style->getHeaderHeight());
$content .= $this->getValueIf($style->getFooterHeight() !== null, '\footery' . $style->getFooterHeight());
$content .= $this->getValueIf($style->getGutter() !== null, '\guttersxn' . $style->getGutter());
$content .= $this->getValueIf($style->getMarginTop() !== null, '\margtsxn' . round($style->getMarginTop()));
$content .= $this->getValueIf($style->getMarginRight() !== null, '\margrsxn' . round($style->getMarginRight()));
$content .= $this->getValueIf($style->getMarginBottom() !== null, '\margbsxn' . round($style->getMarginBottom()));
$content .= $this->getValueIf($style->getMarginLeft() !== null, '\marglsxn' . round($style->getMarginLeft()));
$content .= $this->getValueIf($style->getHeaderHeight() !== null, '\headery' . round($style->getHeaderHeight()));
$content .= $this->getValueIf($style->getFooterHeight() !== null, '\footery' . round($style->getFooterHeight()));
$content .= $this->getValueIf($style->getGutter() !== null, '\guttersxn' . round($style->getGutter()));
$content .= ' ';
// Borders

View File

@ -0,0 +1,49 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
* @copyright 2010-2018 PHPWord contributors
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Writer\RTF\Style;
/**
* Line numbering style writer
*
* @since 0.10.0
*/
class Tab extends AbstractStyle
{
/**
* Write style.
*/
public function write()
{
$style = $this->getStyle();
if (!$style instanceof \PhpOffice\PhpWord\Style\Tab) {
return;
}
$tabs = array(
\PhpOffice\PhpWord\Style\Tab::TAB_STOP_RIGHT => '\tqr',
\PhpOffice\PhpWord\Style\Tab::TAB_STOP_CENTER => '\tqc',
\PhpOffice\PhpWord\Style\Tab::TAB_STOP_DECIMAL => '\tqdec',
);
$content = '';
if (isset($tabs[$style->getType()])) {
$content .= $tabs[$style->getType()];
}
$content .= '\tx' . round($style->getPosition());
return $content;
}
}

View File

@ -103,7 +103,9 @@ class Image extends AbstractElement
$style->setPositioning('absolute');
$styleWriter = new ImageStyleWriter($xmlWriter, $style);
$xmlWriter->startElement('w:p');
if (!$this->withoutP) {
$xmlWriter->startElement('w:p');
}
$xmlWriter->startElement('w:r');
$xmlWriter->startElement('w:pict');
$xmlWriter->startElement('v:shape');
@ -118,6 +120,8 @@ class Image extends AbstractElement
$xmlWriter->endElement(); // v:shape
$xmlWriter->endElement(); // w:pict
$xmlWriter->endElement(); // w:r
$xmlWriter->endElement(); // w:p
if (!$this->withoutP) {
$xmlWriter->endElement(); // w:p
}
}
}

View File

@ -73,6 +73,18 @@ class SDT extends Text
$this->endElementP(); // w:p
}
/**
* Write text.
*
* @see http://www.datypic.com/sc/ooxml/t-w_CT_SdtText.html
* @param \PhpOffice\Common\XMLWriter $xmlWriter
*/
private function writePlainText(XMLWriter $xmlWriter)
{
$xmlWriter->startElement('w:text');
$xmlWriter->endElement(); // w:text
}
/**
* Write combo box.
*

View File

@ -76,21 +76,7 @@ class Table extends AbstractElement
*/
private function writeColumns(XMLWriter $xmlWriter, TableElement $element)
{
$rows = $element->getRows();
$rowCount = count($rows);
$cellWidths = array();
for ($i = 0; $i < $rowCount; $i++) {
$row = $rows[$i];
$cells = $row->getCells();
if (count($cells) <= count($cellWidths)) {
continue;
}
$cellWidths = array();
foreach ($cells as $cell) {
$cellWidths[] = $cell->getWidth();
}
}
$cellWidths = $element->findFirstDefinedCellWidths();
$xmlWriter->startElement('w:tblGrid');
foreach ($cellWidths as $width) {

View File

@ -47,6 +47,7 @@ class Title extends AbstractElement
$xmlWriter->endElement();
}
$bookmarkRId = null;
if ($element->getDepth() !== 0) {
$rId = $element->getRelationId();
$bookmarkRId = $element->getPhpWord()->addBookmark();

View File

@ -105,8 +105,6 @@ class Chart extends AbstractPart
{
$xmlWriter->startElement('c:chart');
$xmlWriter->writeElementBlock('c:autoTitleDeleted', 'val', 1);
$this->writePlotArea($xmlWriter);
$xmlWriter->endElement(); // c:chart
@ -131,6 +129,34 @@ class Chart extends AbstractPart
$style = $this->element->getStyle();
$this->options = $this->types[$type];
$title = $style->getTitle();
$showLegend = $style->isShowLegend();
//Chart title
if ($title) {
$xmlWriter->startElement('c:title');
$xmlWriter->startElement('c:tx');
$xmlWriter->startElement('c:rich');
$xmlWriter->writeRaw('
<a:bodyPr/>
<a:lstStyle/>
<a:p>
<a:pPr>
<a:defRPr/></a:pPr><a:r><a:rPr/><a:t>' . $title . '</a:t></a:r>
<a:endParaRPr/>
</a:p>');
$xmlWriter->endElement(); // c:rich
$xmlWriter->endElement(); // c:tx
$xmlWriter->endElement(); // c:title
} else {
$xmlWriter->writeElementBlock('c:autoTitleDeleted', 'val', 1);
}
//Chart legend
if ($showLegend) {
$xmlWriter->writeRaw('<c:legend><c:legendPos val="r"/></c:legend>');
}
$xmlWriter->startElement('c:plotArea');
$xmlWriter->writeElement('c:layout');
@ -330,11 +356,11 @@ class Chart extends AbstractPart
$valueAxisTitle = $style->getValueAxisTitle();
if ($axisType == 'c:catAx') {
if (isset($categoryAxisTitle)) {
if (!is_null($categoryAxisTitle)) {
$this->writeAxisTitle($xmlWriter, $categoryAxisTitle);
}
} elseif ($axisType == 'c:valAx') {
if (isset($valueAxisTitle)) {
if (!is_null($valueAxisTitle)) {
$this->writeAxisTitle($xmlWriter, $valueAxisTitle);
}
}

View File

@ -17,9 +17,9 @@
namespace PhpOffice\PhpWord\Writer\Word2007\Part;
use PhpOffice\Common\Microsoft\PasswordEncoder;
use PhpOffice\PhpWord\ComplexType\ProofState;
use PhpOffice\PhpWord\ComplexType\TrackChangesView;
use PhpOffice\PhpWord\Shared\Microsoft\PasswordEncoder;
use PhpOffice\PhpWord\Style\Language;
/**

View File

@ -121,6 +121,21 @@ abstract class AbstractStyle
}
}
/**
* Writes boolean as 0 or 1
*
* @param bool $value
* @return null|string
*/
protected function writeOnOf($value = null)
{
if ($value === null) {
return null;
}
return $value ? '1' : '0';
}
/**
* Assemble style array into style string
*

View File

@ -107,18 +107,21 @@ class Font extends AbstractStyle
$xmlWriter->writeElementIf($size !== null, 'w:szCs', 'w:val', $size * 2);
// Bold, italic
$xmlWriter->writeElementIf($style->isBold(), 'w:b');
$xmlWriter->writeElementIf($style->isBold(), 'w:bCs');
$xmlWriter->writeElementIf($style->isItalic(), 'w:i');
$xmlWriter->writeElementIf($style->isItalic(), 'w:iCs');
$xmlWriter->writeElementIf($style->isBold() !== null, 'w:b', 'w:val', $this->writeOnOf($style->isBold()));
$xmlWriter->writeElementIf($style->isBold() !== null, 'w:bCs', 'w:val', $this->writeOnOf($style->isBold()));
$xmlWriter->writeElementIf($style->isItalic() !== null, 'w:i', 'w:val', $this->writeOnOf($style->isItalic()));
$xmlWriter->writeElementIf($style->isItalic() !== null, 'w:iCs', 'w:val', $this->writeOnOf($style->isItalic()));
// Strikethrough, double strikethrough
$xmlWriter->writeElementIf($style->isStrikethrough(), 'w:strike');
$xmlWriter->writeElementIf($style->isDoubleStrikethrough(), 'w:dstrike');
$xmlWriter->writeElementIf($style->isStrikethrough() !== null, 'w:strike', 'w:val', $this->writeOnOf($style->isStrikethrough()));
$xmlWriter->writeElementIf($style->isDoubleStrikethrough() !== null, 'w:dstrike', 'w:val', $this->writeOnOf($style->isDoubleStrikethrough()));
// Small caps, all caps
$xmlWriter->writeElementIf($style->isSmallCaps(), 'w:smallCaps');
$xmlWriter->writeElementIf($style->isAllCaps(), 'w:caps');
$xmlWriter->writeElementIf($style->isSmallCaps() !== null, 'w:smallCaps', 'w:val', $this->writeOnOf($style->isSmallCaps()));
$xmlWriter->writeElementIf($style->isAllCaps() !== null, 'w:caps', 'w:val', $this->writeOnOf($style->isAllCaps()));
//Hidden text
$xmlWriter->writeElementIf($style->isHidden(), 'w:vanish', 'w:val', $this->writeOnOf($style->isHidden()));
// Underline
$xmlWriter->writeElementIf($style->getUnderline() != 'none', 'w:u', 'w:val', $style->getUnderline());
@ -136,7 +139,7 @@ class Font extends AbstractStyle
$xmlWriter->writeElementIf($style->getKerning() !== null, 'w:kern', 'w:val', $style->getKerning() * 2);
// noProof
$xmlWriter->writeElementIf($style->isNoProof() !== false, 'w:noProof');
$xmlWriter->writeElementIf($style->isNoProof() !== null, 'w:noProof', $this->writeOnOf($style->isNoProof()));
// Background-Color
$shading = $style->getShading();

View File

@ -61,6 +61,7 @@ class Frame extends AbstractStyle
'hPos' => 'mso-position-horizontal',
'vPos' => 'mso-position-vertical',
'hPosRelTo' => 'mso-position-horizontal-relative',
'vPosRelTo' => 'mso-position-vertical-relative',
);
$posStyles = $this->getStyles($style, $properties);

View File

@ -48,6 +48,10 @@ class Section extends AbstractStyle
$xmlWriter->writeAttribute('w:h', $style->getPageSizeH());
$xmlWriter->endElement(); // w:pgSz
// Vertical alignment
$vAlign = $style->getVAlign();
$xmlWriter->writeElementIf(!is_null($vAlign), 'w:vAlign', 'w:val', $vAlign);
// Margins
$margins = array(
'w:top' => array('getMarginTop', SectionStyle::DEFAULT_MARGIN),

View File

@ -44,6 +44,10 @@ class Spacing extends AbstractStyle
$xmlWriter->writeAttributeIf(!is_null($after), 'w:after', $this->convertTwip($after));
$line = $style->getLine();
//if linerule is auto, the spacing is supposed to include the height of the line itself, which is 240 twips
if (null !== $line && 'auto' === $style->getLineRule()) {
$line += \PhpOffice\PhpWord\Style\Paragraph::LINE_HEIGHT;
}
$xmlWriter->writeAttributeIf(!is_null($line), 'w:line', $line);
$xmlWriter->writeAttributeIf(!is_null($line), 'w:lineRule', $style->getLineRule());

View File

@ -86,6 +86,9 @@ class Table extends AbstractStyle
$styleWriter = new TablePosition($xmlWriter, $style->getPosition());
$styleWriter->write();
//Right to left
$xmlWriter->writeElementIf($style->isBidiVisual() !== null, 'w:bidiVisual', 'w:val', $this->writeOnOf($style->isBidiVisual()));
$this->writeMargin($xmlWriter, $style);
$this->writeBorder($xmlWriter, $style);

View File

@ -17,12 +17,14 @@
namespace PhpOffice\PhpWord\Element;
use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest;
/**
* Test class for PhpOffice\PhpWord\Element\Cell
*
* @runTestsInSeparateProcesses
*/
class CellTest extends \PHPUnit\Framework\TestCase
class CellTest extends AbstractWebServerEmbeddedTest
{
/**
* New instance
@ -165,7 +167,7 @@ class CellTest extends \PHPUnit\Framework\TestCase
public function testAddImageSectionByUrl()
{
$oCell = new Cell();
$element = $oCell->addImage('http://php.net/images/logos/php-med-trans-light.gif');
$element = $oCell->addImage(self::getRemoteGifImageUrl());
$this->assertCount(1, $oCell->getElements());
$this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element);

View File

@ -17,12 +17,14 @@
namespace PhpOffice\PhpWord\Element;
use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest;
/**
* Test class for PhpOffice\PhpWord\Element\Footer
*
* @runTestsInSeparateProcesses
*/
class FooterTest extends \PHPUnit\Framework\TestCase
class FooterTest extends AbstractWebServerEmbeddedTest
{
/**
* New instance
@ -116,7 +118,7 @@ class FooterTest extends \PHPUnit\Framework\TestCase
public function testAddImageByUrl()
{
$oFooter = new Footer(1);
$element = $oFooter->addImage('http://php.net/images/logos/php-med-trans-light.gif');
$element = $oFooter->addImage(self::getRemoteGifImageUrl());
$this->assertCount(1, $oFooter->getElements());
$this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element);

View File

@ -17,12 +17,14 @@
namespace PhpOffice\PhpWord\Element;
use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest;
/**
* Test class for PhpOffice\PhpWord\Element\Header
*
* @runTestsInSeparateProcesses
*/
class HeaderTest extends \PHPUnit\Framework\TestCase
class HeaderTest extends AbstractWebServerEmbeddedTest
{
/**
* New instance
@ -125,7 +127,7 @@ class HeaderTest extends \PHPUnit\Framework\TestCase
public function testAddImageByUrl()
{
$oHeader = new Header(1);
$element = $oHeader->addImage('http://php.net/images/logos/php-med-trans-light.gif');
$element = $oHeader->addImage(self::getRemoteGifImageUrl());
$this->assertCount(1, $oHeader->getElements());
$this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element);

View File

@ -17,6 +17,7 @@
namespace PhpOffice\PhpWord\Element;
use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest;
use PhpOffice\PhpWord\SimpleType\Jc;
/**
@ -24,7 +25,7 @@ use PhpOffice\PhpWord\SimpleType\Jc;
*
* @runTestsInSeparateProcesses
*/
class ImageTest extends \PHPUnit\Framework\TestCase
class ImageTest extends AbstractWebServerEmbeddedTest
{
/**
* New instance
@ -131,7 +132,7 @@ class ImageTest extends \PHPUnit\Framework\TestCase
*/
public function testUnsupportedImage()
{
//disable ssl verification, never do this in real application, you should pass the certiciate instead!!!
//disable ssl verification, never do this in real application, you should pass the certificiate instead!!!
$arrContextOptions = array(
'ssl' => array(
'verify_peer' => false,
@ -139,7 +140,7 @@ class ImageTest extends \PHPUnit\Framework\TestCase
),
);
stream_context_set_default($arrContextOptions);
$object = new Image('https://samples.libav.org/image-samples/RACECAR.BMP');
$object = new Image(self::getRemoteBmpImageUrl());
$object->getSource();
}
@ -215,7 +216,7 @@ class ImageTest extends \PHPUnit\Framework\TestCase
*/
public function testConstructFromGd()
{
$source = 'http://php.net/images/logos/php-icon.png';
$source = self::getRemoteImageUrl();
$image = new Image($source);
$this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $image);

View File

@ -29,8 +29,8 @@ class SDTTest extends \PHPUnit\Framework\TestCase
*/
public function testConstruct()
{
$types = array('comboBox', 'dropDownList', 'date');
$type = $types[rand(0, 2)];
$types = array('plainText', 'comboBox', 'dropDownList', 'date');
$type = $types[rand(0, 3)];
$value = rand(0, 100);
$alias = 'alias';
$tag = 'my_tag';

View File

@ -19,6 +19,7 @@ namespace PhpOffice\PhpWord\Element;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\Style\Section as SectionStyle;
/**
* @covers \PhpOffice\PhpWord\Element\Section
@ -27,6 +28,27 @@ use PhpOffice\PhpWord\Style;
*/
class SectionTest extends \PHPUnit\Framework\TestCase
{
public function testConstructorWithDefaultStyle()
{
$section = new Section(0);
$this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Section', $section->getStyle());
}
public function testConstructorWithArrayStyle()
{
$section = new Section(0, array('orientation' => 'landscape'));
$style = $section->getStyle();
$this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Section', $style);
$this->assertEquals('landscape', $style->getOrientation());
}
public function testConstructorWithObjectStyle()
{
$style = new SectionStyle();
$section = new Section(0, $style);
$this->assertSame($style, $section->getStyle());
}
/**
* @covers ::setStyle
*/

View File

@ -41,4 +41,22 @@ class TrackChangeTest extends \PHPUnit\Framework\TestCase
$this->assertEquals($date, $oTrackChange->getDate());
$this->assertEquals(TrackChange::INSERTED, $oTrackChange->getChangeType());
}
/**
* New instance with invalid \DateTime (produced by \DateTime::createFromFormat(...))
*/
public function testConstructDefaultWithInvalidDate()
{
$author = 'Test User';
$date = false;
$oTrackChange = new TrackChange(TrackChange::INSERTED, $author, $date);
$oText = new Text('dummy text');
$oText->setTrackChange($oTrackChange);
$this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\TrackChange', $oTrackChange);
$this->assertEquals($author, $oTrackChange->getAuthor());
$this->assertEquals($date, null);
$this->assertEquals(TrackChange::INSERTED, $oTrackChange->getChangeType());
}
}

View File

@ -24,7 +24,7 @@ use PhpOffice\PhpWord\Element\Image;
*
* @runTestsInSeparateProcesses
*/
class MediaTest extends \PHPUnit\Framework\TestCase
class MediaTest extends AbstractWebServerEmbeddedTest
{
/**
* Get section media elements
@ -49,7 +49,7 @@ class MediaTest extends \PHPUnit\Framework\TestCase
{
$local = __DIR__ . '/_files/images/mars.jpg';
$object = __DIR__ . '/_files/documents/sheet.xls';
$remote = 'http://php.net/images/logos/php-med-trans-light.gif';
$remote = self::getRemoteImageUrl();
Media::addElement('section', 'image', $local, new Image($local));
Media::addElement('section', 'image', $local, new Image($local));
Media::addElement('section', 'image', $remote, new Image($local));
@ -77,7 +77,7 @@ class MediaTest extends \PHPUnit\Framework\TestCase
public function testAddHeaderMediaElement()
{
$local = __DIR__ . '/_files/images/mars.jpg';
$remote = 'http://php.net/images/logos/php-med-trans-light.gif';
$remote = self::getRemoteImageUrl();
Media::addElement('header1', 'image', $local, new Image($local));
Media::addElement('header1', 'image', $local, new Image($local));
Media::addElement('header1', 'image', $remote, new Image($remote));
@ -92,7 +92,7 @@ class MediaTest extends \PHPUnit\Framework\TestCase
public function testAddFooterMediaElement()
{
$local = __DIR__ . '/_files/images/mars.jpg';
$remote = 'http://php.net/images/logos/php-med-trans-light.gif';
$remote = self::getRemoteImageUrl();
Media::addElement('footer1', 'image', $local, new Image($local));
Media::addElement('footer1', 'image', $local, new Image($local));
Media::addElement('footer1', 'image', $remote, new Image($remote));

View File

@ -236,4 +236,40 @@ class ElementTest extends AbstractTestReader
$this->assertEquals('Title', $formattedTitle->getStyle());
$this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $formattedTitle->getText());
}
/**
* Test reading Drawing
*/
public function testReadDrawing()
{
$documentXml = '<w:p>
<w:r>
<w:drawing xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing">
<wp:inline distT="0" distB="0" distL="0" distR="0">
<wp:extent cx="5727700" cy="6621145"/>
<wp:docPr id="1" name="Picture 1"/>
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:nvPicPr>
<pic:cNvPr id="1" name="file_name.jpg"/>
<pic:cNvPicPr/>
</pic:nvPicPr>
<pic:blipFill>
<a:blip r:embed="rId4" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
</a:blip>
</pic:blipFill>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</w:r>
</w:p>';
$phpWord = $this->getDocumentFromString(array('document' => $documentXml));
$elements = $phpWord->getSection(0)->getElements();
$this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]);
}
}

Some files were not shown because too many files have changed in this diff Show More