Merge branch 'master' into develop_v1.0

Conflicts:
	.travis.yml
	composer.json
This commit is contained in:
troosan 2019-10-01 23:55:29 +02:00
commit 847520f380
65 changed files with 1525 additions and 176 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help improve PHPWord
labels: Bug Report
---
### Describe the Bug
A clear and concise description of what the bug is.
### Steps to Reproduce
Please provide a code sample that reproduces the issue.
```php
<?php
require __DIR__ . '/vendor/autoload.php';
$phpWord = new \PhpOffice\PhpWord\PhpWord();
$section = $phpWord->addSection();
$section->...
```
### Expected Behavior
A clear and concise description of what you expected to happen.
### Current Behavior
What is the current behavior?
### Context
Please fill in your environment information:
- PHP Version:
- PHPWord Version:

View File

@ -0,0 +1,22 @@
---
name: Feature request
about: Suggest an idea for this project
labels: Change Request
---
### Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
### Describe the solution you'd like
A clear and concise description of what you want to happen.
### Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
### Additional context
Add any other context or screenshots about the feature request here.

14
.github/ISSUE_TEMPLATE/how-to-use.md vendored Normal file
View File

@ -0,0 +1,14 @@
---
name: How to Use PHPWord
about: Find out how to use PHPWord
labels: WontFix
---
***Please do not use the issue tracker to ask how to use PHPWord.***
Documentation is available on [Read the Docs](https://phpword.readthedocs.io/en/latest/).
Sample code is in the [`/samples/` directory](https://github.com/PHPOffice/PHPWord/tree/develop/samples).
Usage questions belong on [Stack Overflow](https://stackoverflow.com/questions/tagged/phpword).

1
.gitignore vendored
View File

@ -13,7 +13,6 @@ composer.phar
vendor vendor
/report /report
/build /build
/samples/resources
/samples/results /samples/results
/.settings /.settings
phpword.ini phpword.ini

View File

@ -1,13 +1,12 @@
language: php language: php
dist: precise dist: xenial
php: php:
- 5.6
- 7.1 - 7.1
- 7.2 - 7.2
- 7.3 - 7.3
- nightly - 7.4snapshot
matrix: matrix:
include: include:
@ -15,14 +14,14 @@ matrix:
env: COVERAGE=1 env: COVERAGE=1
- php: 7.3 - php: 7.3
env: DEPENDENCIES="--ignore-platform-reqs" env: DEPENDENCIES="--ignore-platform-reqs"
- php: nightly - php: 7.4snapshot
env: DEPENDENCIES="--ignore-platform-reqs" env: DEPENDENCIES="--ignore-platform-reqs"
exclude: exclude:
- php: 7.2 - php: 7.2
- php: 7.3 - php: 7.3
- php: nightly - php: 7.4snapshot
allow_failures: allow_failures:
- php: nightly - php: 7.4snapshot
cache: cache:
directories: directories:
@ -35,6 +34,7 @@ env:
before_install: before_install:
## Packages ## Packages
- sudo rm -f /etc/apt/sources.list.d/mongodb.list # Makes apt crash on Precise, and we don't need MongoDB
- sudo apt-get update -qq - sudo apt-get update -qq
- sudo apt-get install -y graphviz - sudo apt-get install -y graphviz

View File

@ -3,6 +3,34 @@ Change Log
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
v0.17.0 (01 oct 2019)
----------------------
### Added
- Add methods setValuesFromArray and cloneRowFromArray to the TemplateProcessor @geraldb-nicat #670
- Set complex type in template @troosan #1565
- implement support for section vAlign @troosan #1569
- ParseStyle for border-color @Gllrm0 #1551
- Html writer auto invert text color @SailorMax #1387
- Add RightToLeft table presentation. @troosan #1550
- Add support for page vertical alignment. @troosan #672 #1569
- Adding setNumId method for ListItem style @eweso #1329
- Add support for basic fields in RTF writer. @Samuel-BF #1717
### Fixed
- Fix HTML border-color parsing. @troosan #1551 #1570
- Language::validateLocale should pass with locale 'zxx'. @efpapado #1558
- can't align center vertically with the text @ter987 #672
- fix parsing of border-color and add test @troosan #1570
- TrackChange doesn't handle all return types of \DateTime::createFromFormat(...) @superhaggis #1584
- To support PreserveText inside sub container @bhattnishant #1637
- No nested w:pPr elements in ListItemRun. @waltertamboer #1628
- Ensure that entity_loader disable variable is re-set back to the original setting @seamuslee001 #1585
### Miscellaneous
- Use embedded http server to test loading of remote images @troosan #1544
- Change private to protected to be able extending class Html @SpinyMan #1646
- Fix apt-get crash in Travis CI for PHP 5.3 @mdupont #1707
v0.16.0 (30 dec 2018) v0.16.0 (30 dec 2018)
---------------------- ----------------------
### Added ### Added
@ -27,7 +55,7 @@ v0.16.0 (30 dec 2018)
- For RTF writers, sizes should should never have decimals @Samuel-BF #1536 - 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 - Style Name Parsing fails if document generated by a non-english word version @begnini #1434
### Miscelaneous ### Miscellaneous
- Get rid of duplicated code in TemplateProcessor @abcdmitry #1161 - Get rid of duplicated code in TemplateProcessor @abcdmitry #1161
v0.15.0 (14 Jul 2018) v0.15.0 (14 Jul 2018)
@ -73,7 +101,7 @@ v0.15.0 (14 Jul 2018)
- Remove zend-stdlib dependency @Trainmaster #1284 - Remove zend-stdlib dependency @Trainmaster #1284
- The default unit for `\PhpOffice\PhpWord\Style\Image` changed from `px` to `pt`. - The default unit for `\PhpOffice\PhpWord\Style\Image` changed from `px` to `pt`.
### Miscelaneous ### Miscellaneous
- Drop GitHub pages, switch to coveralls for code coverage analysis @czosel #1360 - Drop GitHub pages, switch to coveralls for code coverage analysis @czosel #1360
v0.14.0 (29 Dec 2017) v0.14.0 (29 Dec 2017)

View File

@ -1,13 +1,30 @@
# Contributing to PHPWord # Contributing to PHPWord
PHPWord is built by the crowd and for the crowd. Every contribution is welcome; either by [submitting](https://github.com/PHPOffice/PHPWord/issues) bug issues or suggesting improvements, or in a more active form like [requesting](https://github.com/PHPOffice/PHPWord/pulls) a pull. PHPWord is built by the crowd and for the crowd. Every contribution is welcome; either by [reporting a bug](https://github.com/PHPOffice/PHPWord/issues/new?labels=Bug+Report&template=bug_report.md) or [suggesting improvements](https://github.com/PHPOffice/PHPWord/issues/new?labels=Change+Request&template=feature_request.md), or in a more active form like [requesting a pull](https://github.com/PHPOffice/PHPWord/pulls).
We want to create a high quality document writer and reader library that people can use with more confidence and less bugs. We want to collaborate happily, code joyfully, and get alive merrily. Thus, below are some guidelines, that we expect to be followed by each contributor. We want to create a high quality document writer and reader library that people can use with more confidence and fewer bugs. We want to collaborate happily, code joyfully, and live merrily. Thus, below are some guidelines that we expect to be followed by each contributor:
- **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. - **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 them 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. - **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, [familiarize yourself now](https://github.com/php-fig/fig-standards). Also, please run `composer fix` to automatically fix your code to match these recommendations.
- **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**. No one knows your code better than you, so we depend on you to test the changes you make before pull request submission. We use [PHPUnit](https://phpunit.de/) for our testing purposes and request that you use this tool too. Tests can be ran with `composer test`. [Documentation for writing tests with PHPUnit is available on Read the Docs.](https://phpunit.readthedocs.io)
- **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. - **Use best practices when submitting pull requests**. Create a separate branch named specifically for the issue that you are addressing. Read the [GitHub manual](https://help.github.com/articles/about-pull-requests) to learn more about pull requests and GitHub. 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 fork on GitHub with the upstream branch from PHPWord.
## Getting Started
1. [Clone](https://help.github.com/en/articles/cloning-a-repository) [PHPWord](https://github.com/PHPOffice/PHPWord/)
2. [Install Composer](https://getcomposer.org/download/) if you don't already have it
3. Open your terminal and:
1. Switch to the directory PHPWord was cloned to (e.g., `cd ~/Projects/PHPWord/`)
2. Run `composer install` to install the dependencies
You're ready to start working on PHPWord! Tests belong in the `/tests/PhpWord/` directory, the source code is in `/src/PhpWord/`, and any documentation should go in `/docs/`. Familiarize yourself with the codebase and try your hand at fixing [one of our outstanding issues](https://github.com/PHPOffice/PHPWord/issues). Before you get started, check the [existing pull requests](https://github.com/PHPOffice/PHPWord/pulls) to make sure no one else is already working on it.
Once you have an issue you want to start working on, you'll need to write tests for it, and then you can start implementing the changes necessary to pass the new tests. To run the tests, you can run one of the following commands in your terminal:
- `composer test-no-coverage` to run all of the tests
- `composer test` to run all of the tests and generate test coverage reports
When you're ready to submit your new (and fully tested) feature, ensure `composer check` passes and [submit a pull request to PHPWord](https://github.com/PHPOffice/PHPWord/issues/new).
That's it. Thank you for your interest in PHPWord, and welcome! That's it. Thank you for your interest in PHPWord, and welcome!

View File

@ -17,7 +17,7 @@ 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 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/). PHPWord is an open source project licensed under the terms of [LGPL version 3](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/).
If you have any questions, please ask on [StackOverFlow](https://stackoverflow.com/questions/tagged/phpword) If you have any questions, please ask on [StackOverFlow](https://stackoverflow.com/questions/tagged/phpword)
@ -73,7 +73,7 @@ PHPWord requires the following:
## Installation ## Installation
PHPWord is installed via [Composer](https://getcomposer.org/). PHPWord is installed via [Composer](https://getcomposer.org/).
To [add a dependency](https://getcomposer.org/doc/04-schema.md#package-links>) to PHPWord in your project, either To [add a dependency](https://getcomposer.org/doc/04-schema.md#package-links) to PHPWord in your project, either
Run the following to use the latest stable version Run the following to use the latest stable version
```sh ```sh
@ -174,7 +174,7 @@ You can also read the [Developers' Documentation](http://phpword.readthedocs.org
We welcome everyone to contribute to PHPWord. Below are some of the things that you can do to contribute. We welcome everyone to contribute to PHPWord. Below are some of the things that you can do to contribute.
- Read [our contributing guide](https://github.com/PHPOffice/PHPWord/blob/master/CONTRIBUTING.md). - Read [our contributing guide](CONTRIBUTING.md).
- [Fork us](https://github.com/PHPOffice/PHPWord/fork) and [request a pull](https://github.com/PHPOffice/PHPWord/pulls) to the [develop](https://github.com/PHPOffice/PHPWord/tree/develop) branch. - [Fork us](https://github.com/PHPOffice/PHPWord/fork) and [request a pull](https://github.com/PHPOffice/PHPWord/pulls) to the [develop](https://github.com/PHPOffice/PHPWord/tree/develop) branch.
- Submit [bug reports or feature requests](https://github.com/PHPOffice/PHPWord/issues) to GitHub. - Submit [bug reports or feature requests](https://github.com/PHPOffice/PHPWord/issues) to GitHub.
- Follow [@PHPWord](https://twitter.com/PHPWord) and [@PHPOffice](https://twitter.com/PHPOffice) on Twitter. - Follow [@PHPWord](https://twitter.com/PHPWord) and [@PHPOffice](https://twitter.com/PHPOffice) on Twitter.

View File

@ -58,7 +58,7 @@
"fix": "Fixes issues found by PHP-CS" "fix": "Fixes issues found by PHP-CS"
}, },
"require": { "require": {
"php": "^5.6 || ^7.0", "php": "^7.0",
"ext-xml": "*", "ext-xml": "*",
"zendframework/zend-escaper": "^2.2", "zendframework/zend-escaper": "^2.2",
"phpoffice/common": "^0.2.9" "phpoffice/common": "^0.2.9"
@ -66,9 +66,9 @@
"require-dev": { "require-dev": {
"ext-zip": "*", "ext-zip": "*",
"ext-gd": "*", "ext-gd": "*",
"phpunit/phpunit": "^5.7 || ^7.0", "phpunit/phpunit": "^7.0",
"squizlabs/php_codesniffer": "^3.0", "squizlabs/php_codesniffer": "^3.0",
"friendsofphp/php-cs-fixer": "^2.3", "friendsofphp/php-cs-fixer": "^2.14",
"phpmd/phpmd": "2.*", "phpmd/phpmd": "2.*",
"phploc/phploc": "^4.0", "phploc/phploc": "^4.0",
"dompdf/dompdf":"0.8.*", "dompdf/dompdf":"0.8.*",
@ -90,7 +90,7 @@
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-develop": "0.17-dev", "dev-develop": "0.18-dev",
"develop_v1.0": "1.0-dev" "develop_v1.0": "1.0-dev"
} }
} }

View File

@ -1,35 +0,0 @@
This is:
- [ ] a bug report
- [ ] a feature request
- [ ] **not** a usage question (ask them on https://stackoverflow.com/questions/tagged/phpword)
### Expected Behavior
Please describe the behavior you are expecting.
### Current Behavior
What is the current behavior?
### Failure Information
Please help provide information about the failure.
### How to Reproduce
Please provide a code sample that reproduces the issue.
```php
<?php
require __DIR__ . '/vendor/autoload.php';
$phpWord = new \PhpOffice\PhpWord\PhpWord();
$section = $phpWord->addSection();
$section->...
```
### Context
* PHP version:
* PHPWord version: 0.14

View File

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

View File

@ -34,7 +34,7 @@ Example:
{ {
"require": { "require": {
"phpoffice/phpword": "v0.14.*" "phpoffice/phpword": "v0.17.*"
} }
} }

View File

@ -32,6 +32,8 @@ Available Section style options:
See ``\PhpOffice\PhpWord\Style\Section::ORIENTATION_...`` class constants for possible values 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. - ``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. - ``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: .. _font-style:
@ -108,11 +110,12 @@ Available Table style options:
- ``border(Top|Right|Bottom|Left)Size``. Border size in *twip*. - ``border(Top|Right|Bottom|Left)Size``. Border size in *twip*.
- ``cellMargin(Top|Right|Bottom|Left)``. Cell margin 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``. - ``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*. - ``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. - ``layout``. Table layout, either *fixed* or *autofit* See ``\PhpOffice\PhpWord\Style\Table`` for constants.
- ``cellSpacing`` Cell spacing in *twip* - ``cellSpacing`` Cell spacing in *twip*
- ``position`` Floating Table Positioning, see below for options - ``position`` Floating Table Positioning, see below for options
- ``bidiVisual`` Present table as Right-To-Left
Floating Table Positioning options: Floating Table Positioning options:

View File

@ -17,13 +17,23 @@ Given a template containing
.. code-block:: clean .. code-block:: clean
Hello ${name}! Hello ${firstname} ${lastname}!
The following will replace ``${name}`` with ``World``. The resulting document will now contain ``Hello World!`` The following will replace ``${firstname}`` with ``John``, and ``${lastname}`` with ``Doe`` .
The resulting document will now contain ``Hello John Doe!``
.. code-block:: php .. code-block:: php
$templateProcessor->setValue('name', 'World'); $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 setImageValue
""""""""""""" """""""""""""
@ -138,11 +148,11 @@ See ``Sample_07_TemplateCloneRow.php`` for an example.
.. code-block:: clean .. code-block:: clean
------------------------------ +-----------+----------------+
| ${userId} | ${userName} | | ${userId} | ${userName} |
| |----------------| | |----------------+
| | ${userAddress} | | | ${userAddress} |
------------------------------ +-----------+----------------+
.. code-block:: php .. code-block:: php
@ -152,15 +162,49 @@ Will result in
.. code-block:: clean .. code-block:: clean
---------------------------------- +-------------+------------------+
| ${userId#1} | ${userName#1} | | ${userId#1} | ${userName#1} |
| |------------------| | |------------------+
| | ${userAddress#1} | | | ${userAddress#1} |
---------------------------------| +-------------+------------------+
| ${userId#2} | ${userName#2} | | ${userId#2} | ${userName#2} |
| |------------------| | |------------------+
| | ${userAddress#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 applyXslStyleSheet
"""""""""""""""""" """"""""""""""""""
@ -171,3 +215,32 @@ Applies the XSL stylesheet passed to header part, footer part and main part
$xslDomDocument = new \DOMDocument(); $xslDomDocument = new \DOMDocument();
$xslDomDocument->load('/path/to/my/stylesheet.xsl'); $xslDomDocument->load('/path/to/my/stylesheet.xsl');
$templateProcessor->applyXslStyleSheet($xslDomDocument); $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

@ -19,7 +19,7 @@
<rule ref="rulesets/design.xml/CouplingBetweenObjects"> <rule ref="rulesets/design.xml/CouplingBetweenObjects">
<!-- AbstractContainer needs more coupling (default: 13) --> <!-- AbstractContainer needs more coupling (default: 13) -->
<properties> <properties>
<property name="minimum" value="20" /> <property name="maximum" value="20" />
</properties> </properties>
</rule> </rule>
<rule ref="rulesets/design.xml/NumberOfChildren"> <rule ref="rulesets/design.xml/NumberOfChildren">
@ -28,6 +28,6 @@
<property name="minimum" value="30" /> <property name="minimum" value="30" />
</properties> </properties>
</rule> </rule>
<rule ref="rulesets/unusedcode.xml" /> <!--rule ref="rulesets/unusedcode.xml" /-->
<rule ref="rulesets/controversial.xml" /> <rule ref="rulesets/controversial.xml" />
</ruleset> </ruleset>

View File

@ -1,4 +1,6 @@
<?php <?php
use PhpOffice\PhpWord\SimpleType\VerticalJc;
include_once 'Sample_Header.php'; include_once 'Sample_Header.php';
// New Word Document // New Word Document
@ -21,6 +23,12 @@ $section = $phpWord->addSection(
); );
$section->addText('This section uses other margins with folio papersize.'); $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 // New portrait section with Header & Footer
$section = $phpWord->addSection( $section = $phpWord->addSection(
array( array(

View File

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

View File

@ -74,7 +74,7 @@ $html .= '<table align="center" style="width: 50%; border: 6px #0000FF double;">
</tr> </tr>
</thead> </thead>
<tbody> <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> <tr><td>This is <b>bold</b> text</td><td></td><td>6</td></tr>
</tbody> </tbody>
</table>'; </table>';

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 = $section->addTextRun(array('alignment' => \PhpOffice\PhpWord\SimpleType\Jc::END));
$textrun->addText('سلام این یک پاراگراف راست به چپ است', array('rtl' => true)); $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 // Save file
echo write($phpWord, basename(__FILE__, '.php'), $writers); echo write($phpWord, basename(__FILE__, '.php'), $writers);
if (!CLI) { 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> <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> </p>
</div> </div>
<?php <?php
} }
if (!CLI) { if (!CLI) {
echo '<h3>Requirement check:</h3>'; echo '<h3>Requirement check:</h3>';

View File

@ -31,7 +31,7 @@ namespace PhpOffice\PhpWord\Element;
* @method Footnote addFootnote(mixed $pStyle = null) * @method Footnote addFootnote(mixed $pStyle = null)
* @method Endnote addEndnote(mixed $pStyle = null) * @method Endnote addEndnote(mixed $pStyle = null)
* @method CheckBox addCheckBox(string $name, $text, mixed $fStyle = null, 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 TOC addTOC(mixed $fontStyle = null, mixed $tocStyle = null, int $minDepth = 1, int $maxDepth = 9)
* @method PageBreak addPageBreak() * @method PageBreak addPageBreak()
* @method Table addTable(mixed $style = null) * @method Table addTable(mixed $style = null)
@ -41,7 +41,7 @@ namespace PhpOffice\PhpWord\Element;
* @method Field addField(string $type = null, array $properties = array(), array $options = array(), mixed $text = null) * @method Field addField(string $type = null, array $properties = array(), array $options = array(), mixed $text = null)
* @method Line addLine(mixed $lineStyle = null) * @method Line addLine(mixed $lineStyle = null)
* @method Shape addShape(string $type, mixed $style = null) * @method Shape addShape(string $type, mixed $style = null)
* @method Chart addChart(string $type, array $categories, array $values, array $style = null) * @method Chart addChart(string $type, array $categories, array $values, array $style = null, $seriesName = null)
* @method FormField addFormField(string $type, mixed $fStyle = null, mixed $pStyle = null) * @method FormField addFormField(string $type, mixed $fStyle = null, mixed $pStyle = null)
* @method SDT addSDT(string $type) * @method SDT addSDT(string $type)
* *
@ -254,7 +254,7 @@ abstract class AbstractContainer extends AbstractElement
// Special condition, e.g. preservetext can only exists in cell when // Special condition, e.g. preservetext can only exists in cell when
// the cell is located in header or footer // the cell is located in header or footer
$validSubcontainers = array( $validSubcontainers = array(
'PreserveText' => array(array('Cell'), array('Header', 'Footer')), 'PreserveText' => array(array('Cell'), array('Header', 'Footer', 'Section')),
'Footnote' => array(array('Cell', 'TextRun'), array('Section')), 'Footnote' => array(array('Cell', 'TextRun'), array('Section')),
'Endnote' => array(array('Cell', 'TextRun'), array('Section')), 'Endnote' => array(array('Cell', 'TextRun'), array('Section')),
); );

View File

@ -96,7 +96,7 @@ abstract class AbstractElement
/** /**
* A reference to the parent * A reference to the parent
* *
* @var \PhpOffice\PhpWord\Element\AbstractElement * @var AbstractElement|null
*/ */
private $parent; private $parent;
@ -335,6 +335,11 @@ abstract class AbstractElement
$this->commentRangeEnd->setEndElement($this); $this->commentRangeEnd->setEndElement($this);
} }
/**
* Get parent element
*
* @return AbstractElement|null
*/
public function getParent() public function getParent()
{ {
return $this->parent; return $this->parent;

View File

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

View File

@ -483,6 +483,7 @@ abstract class AbstractPart
$styleDefs["border{$ucfSide}Style"] = array(self::READ_VALUE, "w:tblBorders/w:$side", 'w:val'); $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['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'); $styleDefs['cellSpacing'] = array(self::READ_VALUE, 'w:tblCellSpacing', 'w:w');
$style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs);

View File

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

View File

@ -32,9 +32,9 @@ use PhpOffice\PhpWord\Style\Paragraph;
*/ */
class Html class Html
{ {
private static $listIndex = 0; protected static $listIndex = 0;
private static $xpath; protected static $xpath;
private static $options; protected static $options;
/** /**
* Add HTML parts. * Add HTML parts.
@ -75,7 +75,7 @@ class Html
$html = preg_replace('/(\>)\s*(\<)/m', '$1$2', $html); $html = preg_replace('/(\>)\s*(\<)/m', '$1$2', $html);
// Load DOM // Load DOM
libxml_disable_entity_loader(true); $orignalLibEntityLoader = libxml_disable_entity_loader(true);
$dom = new \DOMDocument(); $dom = new \DOMDocument();
$dom->preserveWhiteSpace = $preserveWhiteSpace; $dom->preserveWhiteSpace = $preserveWhiteSpace;
$dom->loadHTML($html, LIBXML_NOWARNING); $dom->loadHTML($html, LIBXML_NOWARNING);
@ -83,6 +83,7 @@ class Html
$node = $dom->getElementsByTagName('body'); $node = $dom->getElementsByTagName('body');
self::parseNode($node->item(0), $element); self::parseNode($node->item(0), $element);
libxml_disable_entity_loader($orignalLibEntityLoader);
} }
/** /**
@ -194,7 +195,7 @@ class Html
$newElement = $element; $newElement = $element;
} }
self::parseChildNodes($node, $newElement, $styles, $data); static::parseChildNodes($node, $newElement, $styles, $data);
} }
/** /**
@ -205,7 +206,7 @@ class Html
* @param array $styles * @param array $styles
* @param array $data * @param array $data
*/ */
private static function parseChildNodes($node, $element, $styles, $data) protected static function parseChildNodes($node, $element, $styles, $data)
{ {
if ('li' != $node->nodeName) { if ('li' != $node->nodeName) {
$cNodes = $node->childNodes; $cNodes = $node->childNodes;
@ -227,7 +228,7 @@ class Html
* @param array &$styles * @param array &$styles
* @return \PhpOffice\PhpWord\Element\TextRun * @return \PhpOffice\PhpWord\Element\TextRun
*/ */
private static function parseParagraph($node, $element, &$styles) protected static function parseParagraph($node, $element, &$styles)
{ {
$styles['paragraph'] = self::recursiveParseStylesInHierarchy($node, $styles['paragraph']); $styles['paragraph'] = self::recursiveParseStylesInHierarchy($node, $styles['paragraph']);
$newElement = $element->addTextRun($styles['paragraph']); $newElement = $element->addTextRun($styles['paragraph']);
@ -246,7 +247,7 @@ class Html
* @todo Think of a clever way of defining header styles, now it is only based on the assumption, that * @todo Think of a clever way of defining header styles, now it is only based on the assumption, that
* Heading1 - Heading6 are already defined somewhere * Heading1 - Heading6 are already defined somewhere
*/ */
private static function parseHeading($element, &$styles, $argument1) protected static function parseHeading($element, &$styles, $argument1)
{ {
$styles['paragraph'] = $argument1; $styles['paragraph'] = $argument1;
$newElement = $element->addTextRun($styles['paragraph']); $newElement = $element->addTextRun($styles['paragraph']);
@ -261,7 +262,7 @@ class Html
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param \PhpOffice\PhpWord\Element\AbstractContainer $element
* @param array &$styles * @param array &$styles
*/ */
private static function parseText($node, $element, &$styles) protected static function parseText($node, $element, &$styles)
{ {
$styles['font'] = self::recursiveParseStylesInHierarchy($node, $styles['font']); $styles['font'] = self::recursiveParseStylesInHierarchy($node, $styles['font']);
@ -282,7 +283,7 @@ class Html
* @param string $argument1 Style name * @param string $argument1 Style name
* @param string $argument2 Style value * @param string $argument2 Style value
*/ */
private static function parseProperty(&$styles, $argument1, $argument2) protected static function parseProperty(&$styles, $argument1, $argument2)
{ {
$styles['font'][$argument1] = $argument2; $styles['font'][$argument1] = $argument2;
} }
@ -293,7 +294,7 @@ class Html
* @param \DOMNode $node * @param \DOMNode $node
* @param array &$styles * @param array &$styles
*/ */
private static function parseSpan($node, &$styles) protected static function parseSpan($node, &$styles)
{ {
self::parseInlineStyle($node, $styles['font']); self::parseInlineStyle($node, $styles['font']);
} }
@ -308,7 +309,7 @@ class Html
* *
* @todo As soon as TableItem, RowItem and CellItem support relative width and height * @todo As soon as TableItem, RowItem and CellItem support relative width and height
*/ */
private static function parseTable($node, $element, &$styles) protected static function parseTable($node, $element, &$styles)
{ {
$elementStyles = self::parseInlineStyle($node, $styles['table']); $elementStyles = self::parseInlineStyle($node, $styles['table']);
@ -337,7 +338,7 @@ class Html
* @param array &$styles * @param array &$styles
* @return Row $element * @return Row $element
*/ */
private static function parseRow($node, $element, &$styles) protected static function parseRow($node, $element, &$styles)
{ {
$rowStyles = self::parseInlineStyle($node, $styles['row']); $rowStyles = self::parseInlineStyle($node, $styles['row']);
if ($node->parentNode->nodeName == 'thead') { if ($node->parentNode->nodeName == 'thead') {
@ -355,7 +356,7 @@ class Html
* @param array &$styles * @param array &$styles
* @return \PhpOffice\PhpWord\Element\Cell|\PhpOffice\PhpWord\Element\TextRun $element * @return \PhpOffice\PhpWord\Element\Cell|\PhpOffice\PhpWord\Element\TextRun $element
*/ */
private static function parseCell($node, $element, &$styles) protected static function parseCell($node, $element, &$styles)
{ {
$cellStyles = self::recursiveParseStylesInHierarchy($node, $styles['cell']); $cellStyles = self::recursiveParseStylesInHierarchy($node, $styles['cell']);
@ -378,7 +379,7 @@ class Html
* @param \DOMNode $node * @param \DOMNode $node
* @return bool Returns true if the node contains an HTML element that cannot be added to TextRun * @return bool Returns true if the node contains an HTML element that cannot be added to TextRun
*/ */
private static function shouldAddTextRun(\DOMNode $node) protected static function shouldAddTextRun(\DOMNode $node)
{ {
$containsBlockElement = self::$xpath->query('.//table|./p|./ul|./ol', $node)->length > 0; $containsBlockElement = self::$xpath->query('.//table|./p|./ul|./ol', $node)->length > 0;
if ($containsBlockElement) { if ($containsBlockElement) {
@ -395,7 +396,7 @@ class Html
* @param \DOMNode $node * @param \DOMNode $node
* @param array &$styles * @param array &$styles
*/ */
private static function recursiveParseStylesInHierarchy(\DOMNode $node, array $style) protected static function recursiveParseStylesInHierarchy(\DOMNode $node, array $style)
{ {
$parentStyle = self::parseInlineStyle($node, array()); $parentStyle = self::parseInlineStyle($node, array());
$style = array_merge($parentStyle, $style); $style = array_merge($parentStyle, $style);
@ -414,7 +415,7 @@ class Html
* @param array &$styles * @param array &$styles
* @param array &$data * @param array &$data
*/ */
private static function parseList($node, $element, &$styles, &$data) protected static function parseList($node, $element, &$styles, &$data)
{ {
$isOrderedList = $node->nodeName === 'ol'; $isOrderedList = $node->nodeName === 'ol';
if (isset($data['listdepth'])) { if (isset($data['listdepth'])) {
@ -433,7 +434,7 @@ class Html
* @param bool $isOrderedList * @param bool $isOrderedList
* @return array * @return array
*/ */
private static function getListStyle($isOrderedList) protected static function getListStyle($isOrderedList)
{ {
if ($isOrderedList) { if ($isOrderedList) {
return array( return array(
@ -479,7 +480,7 @@ class Html
* @todo This function is almost the same like `parseChildNodes`. Merged? * @todo This function is almost the same like `parseChildNodes`. Merged?
* @todo As soon as ListItem inherits from AbstractContainer or TextRun delete parsing part of childNodes * @todo As soon as ListItem inherits from AbstractContainer or TextRun delete parsing part of childNodes
*/ */
private static function parseListItem($node, $element, &$styles, $data) protected static function parseListItem($node, $element, &$styles, $data)
{ {
$cNodes = $node->childNodes; $cNodes = $node->childNodes;
if (!empty($cNodes)) { if (!empty($cNodes)) {
@ -497,7 +498,7 @@ class Html
* @param array $styles * @param array $styles
* @return array * @return array
*/ */
private static function parseStyle($attribute, $styles) protected static function parseStyle($attribute, $styles)
{ {
$properties = explode(';', trim($attribute->value, " \t\n\r\0\x0B;")); $properties = explode(';', trim($attribute->value, " \t\n\r\0\x0B;"));
@ -519,7 +520,7 @@ class Html
$styles['alignment'] = self::mapAlign($cValue); $styles['alignment'] = self::mapAlign($cValue);
break; break;
case 'display': case 'display':
$styles['hidden'] = $cValue === 'none'; $styles['hidden'] = $cValue === 'none' || $cValue === 'hidden';
break; break;
case 'direction': case 'direction':
$styles['rtl'] = $cValue === 'rtl'; $styles['rtl'] = $cValue === 'rtl';
@ -584,7 +585,7 @@ class Html
$styles['spaceAfter'] = Converter::cssToPoint($cValue); $styles['spaceAfter'] = Converter::cssToPoint($cValue);
break; break;
case 'border-color': case 'border-color':
$styles['color'] = trim($cValue, '#'); self::mapBorderColor($styles, $cValue);
break; break;
case 'border-width': case 'border-width':
$styles['borderSize'] = Converter::cssToPoint($cValue); $styles['borderSize'] = Converter::cssToPoint($cValue);
@ -625,7 +626,7 @@ class Html
* *
* @return \PhpOffice\PhpWord\Element\Image * @return \PhpOffice\PhpWord\Element\Image
**/ **/
private static function parseImage($node, $element) protected static function parseImage($node, $element)
{ {
$style = array(); $style = array();
$src = null; $src = null;
@ -728,7 +729,7 @@ class Html
* @param string $cssBorderStyle * @param string $cssBorderStyle
* @return null|string * @return null|string
*/ */
private static function mapBorderStyle($cssBorderStyle) protected static function mapBorderStyle($cssBorderStyle)
{ {
switch ($cssBorderStyle) { switch ($cssBorderStyle) {
case 'none': case 'none':
@ -741,13 +742,27 @@ class Html
} }
} }
protected 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 * Transforms a HTML/CSS alignment into a \PhpOffice\PhpWord\SimpleType\Jc
* *
* @param string $cssAlignment * @param string $cssAlignment
* @return string|null * @return string|null
*/ */
private static function mapAlign($cssAlignment) protected static function mapAlign($cssAlignment)
{ {
switch ($cssAlignment) { switch ($cssAlignment) {
case 'right': case 'right':
@ -766,7 +781,7 @@ class Html
* *
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param \PhpOffice\PhpWord\Element\AbstractContainer $element
*/ */
private static function parseLineBreak($element) protected static function parseLineBreak($element)
{ {
$element->addTextBreak(); $element->addTextBreak();
} }
@ -778,7 +793,7 @@ class Html
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param \PhpOffice\PhpWord\Element\AbstractContainer $element
* @param array $styles * @param array $styles
*/ */
private static function parseLink($node, $element, &$styles) protected static function parseLink($node, $element, &$styles)
{ {
$target = null; $target = null;
foreach ($node->attributes as $attribute) { foreach ($node->attributes as $attribute) {

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

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

View File

@ -71,6 +71,9 @@ final class Language extends AbstractStyle
const UK_UA = 'uk-UA'; const UK_UA = 'uk-UA';
const UK_UA_ID = 1058; const UK_UA_ID = 1058;
const RU_RU = 'ru-RU';
const RU_RU_ID = 1049;
/** /**
* Language ID, used for RTF document generation * Language ID, used for RTF document generation
* *
@ -229,7 +232,7 @@ final class Language extends AbstractStyle
return strtolower($locale) . '-' . strtoupper($locale); return strtolower($locale) . '-' . strtoupper($locale);
} }
if ($locale !== null && strstr($locale, '-') === false) { if ($locale !== null && $locale !== 'zxx' && strstr($locale, '-') === false) {
throw new \InvalidArgumentException($locale . ' is not a valid language code'); throw new \InvalidArgumentException($locale . ' is not a valid language code');
} }

View File

@ -139,6 +139,16 @@ class ListItem extends AbstractStyle
return $this->numId; return $this->numId;
} }
/**
* Set numbering Id. Same numId means same list
* @param mixed $numInt
*/
public function setNumId($numInt)
{
$this->numId = $numInt;
$this->getListTypeStyle();
}
/** /**
* Get legacy numbering definition * Get legacy numbering definition
* *
@ -148,7 +158,12 @@ class ListItem extends AbstractStyle
private function getListTypeStyle() private function getListTypeStyle()
{ {
// Check if legacy style already registered in global Style collection // Check if legacy style already registered in global Style collection
$numStyle = "PHPWordList{$this->listType}"; $numStyle = 'PHPWordListType' . $this->listType;
if ($this->numId) {
$numStyle .= 'NumId' . $this->numId;
}
if (Style::getStyle($numStyle) !== null) { if (Style::getStyle($numStyle) !== null) {
$this->setNumStyle($numStyle); $this->setNumStyle($numStyle);

View File

@ -17,6 +17,8 @@
namespace PhpOffice\PhpWord\Style; namespace PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\SimpleType\VerticalJc;
/** /**
* Section settings * Section settings
*/ */
@ -166,6 +168,14 @@ class Section extends Border
*/ */
private $lineNumbering; private $lineNumbering;
/**
* Vertical Text Alignment on Page
* One of \PhpOffice\PhpWord\SimpleType\VerticalJc
*
* @var string
*/
private $vAlign;
/** /**
* Create new instance * Create new instance
*/ */
@ -599,4 +609,28 @@ class Section extends Border
return $this; 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

@ -170,6 +170,14 @@ class Table extends Border
*/ */
private $columnWidths; 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 * Create new table style
* *
@ -775,4 +783,28 @@ class Table extends Border
{ {
$this->columnWidths = $value; $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; namespace PhpOffice\PhpWord;
use PhpOffice\Common\Text; use PhpOffice\Common\Text;
use PhpOffice\Common\XMLWriter;
use PhpOffice\PhpWord\Escaper\RegExp; use PhpOffice\PhpWord\Escaper\RegExp;
use PhpOffice\PhpWord\Escaper\Xml; use PhpOffice\PhpWord\Escaper\Xml;
use PhpOffice\PhpWord\Exception\CopyFileException; use PhpOffice\PhpWord\Exception\CopyFileException;
@ -48,6 +49,13 @@ class TemplateProcessor
*/ */
protected $tempDocumentMainPart; 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 * Content of headers (in XML format) of the temporary document
* *
@ -119,6 +127,7 @@ class TemplateProcessor
} }
$this->tempDocumentMainPart = $this->readPartWithRels($this->getMainPartName()); $this->tempDocumentMainPart = $this->readPartWithRels($this->getMainPartName());
$this->tempDocumentSettingsPart = $this->readPartWithRels($this->getSettingsPartName());
$this->tempDocumentContentTypes = $this->zipClass->getFromName($this->getDocumentContentTypesName()); $this->tempDocumentContentTypes = $this->zipClass->getFromName($this->getDocumentContentTypesName());
} }
@ -161,7 +170,7 @@ class TemplateProcessor
*/ */
protected function transformSingleXml($xml, $xsltProcessor) protected function transformSingleXml($xml, $xsltProcessor)
{ {
libxml_disable_entity_loader(true); $orignalLibEntityLoader = libxml_disable_entity_loader(true);
$domDocument = new \DOMDocument(); $domDocument = new \DOMDocument();
if (false === $domDocument->loadXML($xml)) { if (false === $domDocument->loadXML($xml)) {
throw new Exception('Could not load the given XML document.'); throw new Exception('Could not load the given XML document.');
@ -171,6 +180,7 @@ class TemplateProcessor
if (false === $transformedXml) { if (false === $transformedXml) {
throw new Exception('Could not transform the given XML document.'); throw new Exception('Could not transform the given XML document.');
} }
libxml_disable_entity_loader($orignalLibEntityLoader);
return $transformedXml; return $transformedXml;
} }
@ -249,6 +259,46 @@ class TemplateProcessor
return $subject; 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 $search
* @param mixed $replace * @param mixed $replace
@ -284,6 +334,18 @@ class TemplateProcessor
$this->tempDocumentFooters = $this->setValueForPart($search, $replace, $this->tempDocumentFooters, $limit); $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) private function getImageArgs($varNameWithArgs)
{ {
$varElements = explode(':', $varNameWithArgs); $varElements = explode(':', $varNameWithArgs);
@ -641,6 +703,24 @@ class TemplateProcessor
$this->tempDocumentMainPart = $result; $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. * Clone a block.
* *
@ -655,6 +735,7 @@ class TemplateProcessor
public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVariables = false, $variableReplacements = null) public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVariables = false, $variableReplacements = null)
{ {
$xmlBlock = null; $xmlBlock = null;
$matches = array();
preg_match( preg_match(
'/(<\?xml.*)(<w:p\b.*>\${' . $blockname . '}<\/w:.*?p>)(.*)(<w:p\b.*\${\/' . $blockname . '}<\/w:.*?p>)/is', '/(<\?xml.*)(<w:p\b.*>\${' . $blockname . '}<\/w:.*?p>)(.*)(<w:p\b.*\${\/' . $blockname . '}<\/w:.*?p>)/is',
$this->tempDocumentMainPart, $this->tempDocumentMainPart,
@ -694,6 +775,7 @@ class TemplateProcessor
*/ */
public function replaceBlock($blockname, $replacement) public function replaceBlock($blockname, $replacement)
{ {
$matches = array();
preg_match( preg_match(
'/(<\?xml.*)(<w:p.*>\${' . $blockname . '}<\/w:.*?p>)(.*)(<w:p.*\${\/' . $blockname . '}<\/w:.*?p>)/is', '/(<\?xml.*)(<w:p.*>\${' . $blockname . '}<\/w:.*?p>)(.*)(<w:p.*\${\/' . $blockname . '}<\/w:.*?p>)/is',
$this->tempDocumentMainPart, $this->tempDocumentMainPart,
@ -719,6 +801,22 @@ class TemplateProcessor
$this->replaceBlock($blockname, ''); $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. * Saves the result document.
* *
@ -733,6 +831,7 @@ class TemplateProcessor
} }
$this->savePartWithRels($this->getMainPartName(), $this->tempDocumentMainPart); $this->savePartWithRels($this->getMainPartName(), $this->tempDocumentMainPart);
$this->savePartWithRels($this->getSettingsPartName(), $this->tempDocumentSettingsPart);
foreach ($this->tempDocumentFooters as $index => $xml) { foreach ($this->tempDocumentFooters as $index => $xml) {
$this->savePartWithRels($this->getFooterName($index), $xml); $this->savePartWithRels($this->getFooterName($index), $xml);
@ -835,6 +934,7 @@ class TemplateProcessor
*/ */
protected function getVariablesForPart($documentPartXML) protected function getVariablesForPart($documentPartXML)
{ {
$matches = array();
preg_match_all('/\$\{(.*?)}/i', $documentPartXML, $matches); preg_match_all('/\$\{(.*?)}/i', $documentPartXML, $matches);
return $matches[1]; return $matches[1];
@ -863,11 +963,22 @@ class TemplateProcessor
$pattern = '~PartName="\/(word\/document.*?\.xml)" ContentType="application\/vnd\.openxmlformats-officedocument\.wordprocessingml\.document\.main\+xml"~'; $pattern = '~PartName="\/(word\/document.*?\.xml)" ContentType="application\/vnd\.openxmlformats-officedocument\.wordprocessingml\.document\.main\+xml"~';
$matches = array();
preg_match($pattern, $contentTypes, $matches); preg_match($pattern, $contentTypes, $matches);
return array_key_exists(1, $matches) ? $matches[1] : 'word/document.xml'; 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';
}
/** /**
* Get the name of the footer file for $index. * Get the name of the footer file for $index.
* *
@ -1001,4 +1112,141 @@ class TemplateProcessor
return $results; 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 // Temporary file
$this->originalFilename = $filename; $this->originalFilename = $filename;
if (strtolower($filename) == 'php://output' || strtolower($filename) == 'php://stdout') { if (strpos(strtolower($filename), 'php://') === 0) {
$filename = tempnam(Settings::getTempDir(), 'PhpWord'); $filename = tempnam(Settings::getTempDir(), 'PhpWord');
if (false === $filename) { if (false === $filename) {
$filename = $this->originalFilename; // @codeCoverageIgnore $filename = $this->originalFilename; // @codeCoverageIgnore

View File

@ -51,6 +51,14 @@ class Table extends AbstractElement
$rowCellCount = count($rowCells); $rowCellCount = count($rowCells);
for ($j = 0; $j < $rowCellCount; $j++) { for ($j = 0; $j < $rowCellCount; $j++) {
$cellStyle = $rowCells[$j]->getStyle(); $cellStyle = $rowCells[$j]->getStyle();
$cellBgColor = $cellStyle->getBgColor();
$cellFgColor = null;
if ($cellBgColor) {
$red = hexdec(substr($cellBgColor, 0, 2));
$green = hexdec(substr($cellBgColor, 2, 2));
$blue = hexdec(substr($cellBgColor, 4, 2));
$cellFgColor = (($red * 0.299 + $green * 0.587 + $blue * 0.114) > 186) ? null : 'ffffff';
}
$cellColSpan = $cellStyle->getGridSpan(); $cellColSpan = $cellStyle->getGridSpan();
$cellRowSpan = 1; $cellRowSpan = 1;
$cellVMerge = $cellStyle->getVMerge(); $cellVMerge = $cellStyle->getVMerge();
@ -74,7 +82,9 @@ class Table extends AbstractElement
$cellTag = $tblHeader ? 'th' : 'td'; $cellTag = $tblHeader ? 'th' : 'td';
$cellColSpanAttr = (is_numeric($cellColSpan) && ($cellColSpan > 1) ? " colspan=\"{$cellColSpan}\"" : ''); $cellColSpanAttr = (is_numeric($cellColSpan) && ($cellColSpan > 1) ? " colspan=\"{$cellColSpan}\"" : '');
$cellRowSpanAttr = ($cellRowSpan > 1 ? " rowspan=\"{$cellRowSpan}\"" : ''); $cellRowSpanAttr = ($cellRowSpan > 1 ? " rowspan=\"{$cellRowSpan}\"" : '');
$content .= "<{$cellTag}{$cellColSpanAttr}{$cellRowSpanAttr}>" . PHP_EOL; $cellBgColorAttr = (is_null($cellBgColor) ? '' : " bgcolor=\"#{$cellBgColor}\"");
$cellFgColorAttr = (is_null($cellFgColor) ? '' : " color=\"#{$cellFgColor}\"");
$content .= "<{$cellTag}{$cellColSpanAttr}{$cellRowSpanAttr}{$cellBgColorAttr}{$cellFgColorAttr}>" . PHP_EOL;
$writer = new Container($this->parentWriter, $rowCells[$j]); $writer = new Container($this->parentWriter, $rowCells[$j]);
$content .= $writer->write(); $content .= $writer->write();
if ($cellRowSpan > 1) { if ($cellRowSpan > 1) {

View File

@ -54,6 +54,10 @@ class Paragraph extends AbstractStyle
$xmlWriter->writeAttribute('fo:margin-bottom', $marginBottom . 'cm'); $xmlWriter->writeAttribute('fo:margin-bottom', $marginBottom . 'cm');
$xmlWriter->writeAttribute('fo:text-align', $style->getAlignment()); $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:paragraph-properties
$xmlWriter->endElement(); //style:style $xmlWriter->endElement(); //style:style

View File

@ -43,6 +43,7 @@ class Table extends AbstractStyle
//$xmlWriter->writeAttribute('style:width', 'table'); //$xmlWriter->writeAttribute('style:width', 'table');
$xmlWriter->writeAttribute('style:rel-width', 100); $xmlWriter->writeAttribute('style:rel-width', 100);
$xmlWriter->writeAttribute('table:align', 'center'); $xmlWriter->writeAttribute('table:align', 'center');
$xmlWriter->writeAttributeIf($style->isBidiVisual(), 'style:writing-mode', 'rl-tb');
$xmlWriter->endElement(); // style:table-properties $xmlWriter->endElement(); // style:table-properties
$xmlWriter->endElement(); // style:style $xmlWriter->endElement(); // style:style

View File

@ -0,0 +1,80 @@
<?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 2019 PHPWord contributors
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Writer\RTF\Element;
/**
* Field element writer
*
* Note: for now, only date, page and numpages fields are implemented for RTF.
*/
class Field extends Text
{
/**
* Write field element.
*/
public function write()
{
$element = $this->element;
if (!$element instanceof \PhpOffice\PhpWord\Element\Field) {
return;
}
$this->getStyles();
$content = '';
$content .= $this->writeOpening();
$content .= '{';
$content .= $this->writeFontStyle();
$methodName = 'write' . ucfirst(strtolower($element->getType()));
if (!method_exists($this, $methodName)) {
// Unsupported field
$content .= '';
} else {
$content .= '\\field{\\*\\fldinst ';
$content .= $this->$methodName($element);
$content .= '}{\\fldrslt}';
}
$content .= '}';
$content .= $this->writeClosing();
return $content;
}
protected function writePage()
{
return 'PAGE';
}
protected function writeNumpages()
{
return 'NUMPAGES';
}
protected function writeDate(\PhpOffice\PhpWord\Element\Field $element)
{
$content = '';
$content .= 'DATE';
$properties = $element->getProperties();
if (isset($properties['dateformat'])) {
$content .= ' \\\\@ "' . $properties['dateformat'] . '"';
}
return $content;
}
}

View File

@ -17,6 +17,7 @@
namespace PhpOffice\PhpWord\Writer\Word2007\Element; namespace PhpOffice\PhpWord\Writer\Word2007\Element;
use PhpOffice\PhpWord\Element\ListItemRun as ListItemRunElement;
use PhpOffice\PhpWord\Writer\Word2007\Style\Paragraph as ParagraphStyleWriter; use PhpOffice\PhpWord\Writer\Word2007\Style\Paragraph as ParagraphStyleWriter;
/** /**
@ -31,34 +32,56 @@ class ListItemRun extends AbstractElement
*/ */
public function write() public function write()
{ {
$xmlWriter = $this->getXmlWriter();
$element = $this->getElement(); $element = $this->getElement();
if (!$element instanceof \PhpOffice\PhpWord\Element\ListItemRun) {
if (!$element instanceof ListItemRunElement) {
return; return;
} }
$this->writeParagraph($element);
}
private function writeParagraph(ListItemRunElement $element)
{
$xmlWriter = $this->getXmlWriter();
$xmlWriter->startElement('w:p'); $xmlWriter->startElement('w:p');
$xmlWriter->startElement('w:pPr'); $this->writeParagraphProperties($element);
$paragraphStyle = $element->getParagraphStyle();
$styleWriter = new ParagraphStyleWriter($xmlWriter, $paragraphStyle);
$styleWriter->setIsInline(true);
$styleWriter->write();
$xmlWriter->startElement('w:numPr');
$xmlWriter->startElement('w:ilvl');
$xmlWriter->writeAttribute('w:val', $element->getDepth());
$xmlWriter->endElement(); // w:ilvl
$xmlWriter->startElement('w:numId');
$xmlWriter->writeAttribute('w:val', $element->getStyle()->getNumId());
$xmlWriter->endElement(); // w:numId
$xmlWriter->endElement(); // w:numPr
$xmlWriter->endElement(); // w:pPr
$containerWriter = new Container($xmlWriter, $element); $containerWriter = new Container($xmlWriter, $element);
$containerWriter->write(); $containerWriter->write();
$xmlWriter->endElement(); // w:p $xmlWriter->endElement(); // w:p
} }
private function writeParagraphProperties(ListItemRunElement $element)
{
$xmlWriter = $this->getXmlWriter();
$xmlWriter->startElement('w:pPr');
$styleWriter = new ParagraphStyleWriter($xmlWriter, $element->getParagraphStyle());
$styleWriter->setIsInline(true);
$styleWriter->setWithoutPPR(true);
$styleWriter->write();
$this->writeParagraphPropertiesNumbering($element);
$xmlWriter->endElement(); // w:pPr
}
private function writeParagraphPropertiesNumbering(ListItemRunElement $element)
{
$xmlWriter = $this->getXmlWriter();
$xmlWriter->startElement('w:numPr');
$xmlWriter->writeElementBlock('w:ilvl', array(
'w:val' => $element->getDepth(),
));
$xmlWriter->writeElementBlock('w:numId', array(
'w:val' => $element->getStyle()->getNumId(),
));
$xmlWriter->endElement(); // w:numPr
}
} }

View File

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

View File

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

View File

@ -17,12 +17,14 @@
namespace PhpOffice\PhpWord\Element; namespace PhpOffice\PhpWord\Element;
use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest;
/** /**
* Test class for PhpOffice\PhpWord\Element\Cell * Test class for PhpOffice\PhpWord\Element\Cell
* *
* @runTestsInSeparateProcesses * @runTestsInSeparateProcesses
*/ */
class CellTest extends \PHPUnit\Framework\TestCase class CellTest extends AbstractWebServerEmbeddedTest
{ {
/** /**
* New instance * New instance
@ -165,7 +167,7 @@ class CellTest extends \PHPUnit\Framework\TestCase
public function testAddImageSectionByUrl() public function testAddImageSectionByUrl()
{ {
$oCell = new Cell(); $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->assertCount(1, $oCell->getElements());
$this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element);
@ -231,7 +233,7 @@ class CellTest extends \PHPUnit\Framework\TestCase
public function testAddPreserveTextException() public function testAddPreserveTextException()
{ {
$oCell = new Cell(); $oCell = new Cell();
$oCell->setDocPart('Section', 1); $oCell->setDocPart('TextRun', 1);
$oCell->addPreserveText('text'); $oCell->addPreserveText('text');
} }

View File

@ -17,12 +17,14 @@
namespace PhpOffice\PhpWord\Element; namespace PhpOffice\PhpWord\Element;
use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest;
/** /**
* Test class for PhpOffice\PhpWord\Element\Footer * Test class for PhpOffice\PhpWord\Element\Footer
* *
* @runTestsInSeparateProcesses * @runTestsInSeparateProcesses
*/ */
class FooterTest extends \PHPUnit\Framework\TestCase class FooterTest extends AbstractWebServerEmbeddedTest
{ {
/** /**
* New instance * New instance
@ -116,7 +118,7 @@ class FooterTest extends \PHPUnit\Framework\TestCase
public function testAddImageByUrl() public function testAddImageByUrl()
{ {
$oFooter = new Footer(1); $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->assertCount(1, $oFooter->getElements());
$this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element);

View File

@ -17,12 +17,14 @@
namespace PhpOffice\PhpWord\Element; namespace PhpOffice\PhpWord\Element;
use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest;
/** /**
* Test class for PhpOffice\PhpWord\Element\Header * Test class for PhpOffice\PhpWord\Element\Header
* *
* @runTestsInSeparateProcesses * @runTestsInSeparateProcesses
*/ */
class HeaderTest extends \PHPUnit\Framework\TestCase class HeaderTest extends AbstractWebServerEmbeddedTest
{ {
/** /**
* New instance * New instance
@ -125,7 +127,7 @@ class HeaderTest extends \PHPUnit\Framework\TestCase
public function testAddImageByUrl() public function testAddImageByUrl()
{ {
$oHeader = new Header(1); $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->assertCount(1, $oHeader->getElements());
$this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element);

View File

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

View File

@ -41,4 +41,22 @@ class TrackChangeTest extends \PHPUnit\Framework\TestCase
$this->assertEquals($date, $oTrackChange->getDate()); $this->assertEquals($date, $oTrackChange->getDate());
$this->assertEquals(TrackChange::INSERTED, $oTrackChange->getChangeType()); $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 * @runTestsInSeparateProcesses
*/ */
class MediaTest extends \PHPUnit\Framework\TestCase class MediaTest extends AbstractWebServerEmbeddedTest
{ {
/** /**
* Get section media elements * Get section media elements
@ -49,7 +49,7 @@ class MediaTest extends \PHPUnit\Framework\TestCase
{ {
$local = __DIR__ . '/_files/images/mars.jpg'; $local = __DIR__ . '/_files/images/mars.jpg';
$object = __DIR__ . '/_files/documents/sheet.xls'; $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', $local, new Image($local)); Media::addElement('section', 'image', $local, new Image($local));
Media::addElement('section', 'image', $remote, new Image($local)); Media::addElement('section', 'image', $remote, new Image($local));
@ -77,7 +77,7 @@ class MediaTest extends \PHPUnit\Framework\TestCase
public function testAddHeaderMediaElement() public function testAddHeaderMediaElement()
{ {
$local = __DIR__ . '/_files/images/mars.jpg'; $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', $local, new Image($local)); Media::addElement('header1', 'image', $local, new Image($local));
Media::addElement('header1', 'image', $remote, new Image($remote)); Media::addElement('header1', 'image', $remote, new Image($remote));
@ -92,7 +92,7 @@ class MediaTest extends \PHPUnit\Framework\TestCase
public function testAddFooterMediaElement() public function testAddFooterMediaElement()
{ {
$local = __DIR__ . '/_files/images/mars.jpg'; $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', $local, new Image($local)); Media::addElement('footer1', 'image', $local, new Image($local));
Media::addElement('footer1', 'image', $remote, new Image($remote)); Media::addElement('footer1', 'image', $remote, new Image($remote));

View File

@ -19,6 +19,7 @@ namespace PhpOffice\PhpWord\Reader\Word2007;
use PhpOffice\PhpWord\AbstractTestReader; use PhpOffice\PhpWord\AbstractTestReader;
use PhpOffice\PhpWord\SimpleType\TblWidth; use PhpOffice\PhpWord\SimpleType\TblWidth;
use PhpOffice\PhpWord\SimpleType\VerticalJc;
use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\Style\Table; use PhpOffice\PhpWord\Style\Table;
use PhpOffice\PhpWord\Style\TablePosition; use PhpOffice\PhpWord\Style\TablePosition;
@ -147,6 +148,24 @@ class StyleTest extends AbstractTestReader
$this->assertSame(2160, $tableStyle->getIndent()->getValue()); $this->assertSame(2160, $tableStyle->getIndent()->getValue());
} }
public function testReadTableRTL()
{
$documentXml = '<w:tbl>
<w:tblPr>
<w:bidiVisual w:val="1"/>
</w:tblPr>
</w:tbl>';
$phpWord = $this->getDocumentFromString(array('document' => $documentXml));
$elements = $phpWord->getSection(0)->getElements();
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Table', $elements[0]);
$this->assertInstanceOf('PhpOffice\PhpWord\Style\Table', $elements[0]->getStyle());
/** @var \PhpOffice\PhpWord\Style\Table $tableStyle */
$tableStyle = $elements[0]->getStyle();
$this->assertTrue($tableStyle->isBidiVisual());
}
public function testReadHidden() public function testReadHidden()
{ {
$documentXml = '<w:p> $documentXml = '<w:p>
@ -195,4 +214,16 @@ class StyleTest extends AbstractTestReader
$this->getDocumentFromString(array('styles' => $documentXml)); $this->getDocumentFromString(array('styles' => $documentXml));
$this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', Style::getStyle($name)); $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', Style::getStyle($name));
} }
public function testPageVerticalAlign()
{
$documentXml = '<w:sectPr>
<w:vAlign w:val="center"/>
</w:sectPr>';
$phpWord = $this->getDocumentFromString(array('document' => $documentXml));
$sectionStyle = $phpWord->getSection(0)->getStyle();
$this->assertEquals(VerticalJc::CENTER, $sectionStyle->getVAlign());
}
} }

View File

@ -17,6 +17,7 @@
namespace PhpOffice\PhpWord\Shared; namespace PhpOffice\PhpWord\Shared;
use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest;
use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\Element\Section;
use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\Jc;
use PhpOffice\PhpWord\SimpleType\LineSpacingRule; use PhpOffice\PhpWord\SimpleType\LineSpacingRule;
@ -27,7 +28,7 @@ use PhpOffice\PhpWord\TestHelperDOCX;
* Test class for PhpOffice\PhpWord\Shared\Html * Test class for PhpOffice\PhpWord\Shared\Html
* @coversDefaultClass \PhpOffice\PhpWord\Shared\Html * @coversDefaultClass \PhpOffice\PhpWord\Shared\Html
*/ */
class HtmlTest extends \PHPUnit\Framework\TestCase class HtmlTest extends AbstractWebServerEmbeddedTest
{ {
/** /**
* Test unit conversion functions with various numbers * Test unit conversion functions with various numbers
@ -296,8 +297,8 @@ class HtmlTest extends \PHPUnit\Framework\TestCase
<thead> <thead>
<tr style="background-color: #FF0000; text-align: center; color: #FFFFFF; font-weight: bold; "> <tr style="background-color: #FF0000; text-align: center; color: #FFFFFF; font-weight: bold; ">
<th style="width: 50pt">header a</th> <th style="width: 50pt">header a</th>
<th style="width: 50">header b</th> <th style="width: 50; border-color: #00EE00">header b</th>
<th style="border-color: #00FF00; border-width: 3px">header c</th> <th style="border-color: #00AA00 #00BB00 #00CC00 #00DD00; border-width: 3px">header c</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -312,6 +313,17 @@ class HtmlTest extends \PHPUnit\Framework\TestCase
$this->assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr/w:tc')); $this->assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr/w:tc'));
$this->assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tblPr/w:jc')); $this->assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tblPr/w:jc'));
$this->assertEquals(Jc::START, $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tblPr/w:jc', 'w:val')); $this->assertEquals(Jc::START, $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tblPr/w:jc', 'w:val'));
//check border colors
$this->assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:top', 'w:color'));
$this->assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:right', 'w:color'));
$this->assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:bottom', 'w:color'));
$this->assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:left', 'w:color'));
$this->assertEquals('00AA00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:top', 'w:color'));
$this->assertEquals('00BB00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:right', 'w:color'));
$this->assertEquals('00CC00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:bottom', 'w:color'));
$this->assertEquals('00DD00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:left', 'w:color'));
} }
/** /**
@ -487,7 +499,7 @@ class HtmlTest extends \PHPUnit\Framework\TestCase
*/ */
public function testParseRemoteImage() public function testParseRemoteImage()
{ {
$src = 'https://phpword.readthedocs.io/en/latest/_images/phpword.png'; $src = self::getRemoteImageUrl();
$phpWord = new \PhpOffice\PhpWord\PhpWord(); $phpWord = new \PhpOffice\PhpWord\PhpWord();
$section = $phpWord->addSection(); $section = $phpWord->addSection();
@ -589,4 +601,35 @@ class HtmlTest extends \PHPUnit\Framework\TestCase
$doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
$this->assertFalse($doc->elementExists('/w:document/w:body/w:p[1]/w:pPr/w:jc')); $this->assertFalse($doc->elementExists('/w:document/w:body/w:p[1]/w:pPr/w:jc'));
} }
/**
* Tests parsing hidden text
*/
public function testParseHiddenText()
{
$phpWord = new \PhpOffice\PhpWord\PhpWord();
$section = $phpWord->addSection();
$html = '<p style="display: hidden">This is some hidden text.</p>';
Html::addHtml($section, $html);
$doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:vanish'));
}
/**
* Tests parsing letter spacing
*/
public function testParseLetterSpacing()
{
$phpWord = new \PhpOffice\PhpWord\PhpWord();
$section = $phpWord->addSection();
$html = '<p style="letter-spacing: 150px">This is some text with letter spacing.</p>';
Html::addHtml($section, $html);
$doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:spacing'));
$this->assertEquals(150 * 15, $doc->getElement('/w:document/w:body/w:p/w:r/w:rPr/w:spacing')->getAttribute('w:val'));
}
} }

View File

@ -17,6 +17,8 @@
namespace PhpOffice\PhpWord\Style; namespace PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\SimpleType\VerticalJc;
/** /**
* Test class for PhpOffice\PhpWord\Style\Cell * Test class for PhpOffice\PhpWord\Style\Cell
* *
@ -33,7 +35,7 @@ class CellTest extends \PHPUnit\Framework\TestCase
$object = new Cell(); $object = new Cell();
$attributes = array( $attributes = array(
'valign' => Cell::VALIGN_TOP, 'valign' => VerticalJc::TOP,
'textDirection' => Cell::TEXT_DIR_BTLR, 'textDirection' => Cell::TEXT_DIR_BTLR,
'bgColor' => 'FFFF00', 'bgColor' => 'FFFF00',
'borderTopSize' => 120, 'borderTopSize' => 120,

View File

@ -17,6 +17,8 @@
namespace PhpOffice\PhpWord\Style; namespace PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\SimpleType\VerticalJc;
/** /**
* Test class for PhpOffice\PhpWord\Style\Section * Test class for PhpOffice\PhpWord\Style\Section
* *
@ -328,4 +330,18 @@ class SectionTest extends \PHPUnit\Framework\TestCase
$oSettings->setBreakType(); $oSettings->setBreakType();
$this->assertNull($oSettings->getBreakType()); $this->assertNull($oSettings->getBreakType());
} }
/**
* Vertical page alignment
*/
public function testVerticalAlign()
{
// Section Settings
$oSettings = new Section();
$this->assertNull($oSettings->getVAlign());
$oSettings->setVAlign(VerticalJc::BOTH);
$this->assertEquals('both', $oSettings->getVAlign());
}
} }

View File

@ -17,6 +17,9 @@
namespace PhpOffice\PhpWord; namespace PhpOffice\PhpWord;
use PhpOffice\PhpWord\Element\Text;
use PhpOffice\PhpWord\Element\TextRun;
/** /**
* @covers \PhpOffice\PhpWord\TemplateProcessor * @covers \PhpOffice\PhpWord\TemplateProcessor
* @coversDefaultClass \PhpOffice\PhpWord\TemplateProcessor * @coversDefaultClass \PhpOffice\PhpWord\TemplateProcessor
@ -196,6 +199,67 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
$this->assertTrue($docFound); $this->assertTrue($docFound);
} }
/**
* @covers ::setValue
* @covers ::cloneRow
* @covers ::saveAs
* @test
*/
public function testCloneRowAndSetValues()
{
$mainPart = '<w:tbl>
<w:tr>
<w:tc>
<w:tcPr>
<w:vMerge w:val="restart"/>
</w:tcPr>
<w:p>
<w:r>
<w:t>${userId}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>${userName}</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
<w:tr>
<w:tc>
<w:tcPr>
<w:vMerge/>
</w:tcPr>
<w:p/>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>${userLocation}</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
</w:tbl>';
$templateProcessor = new TestableTemplateProcesor($mainPart);
$this->assertEquals(
array('userId', 'userName', 'userLocation'),
$templateProcessor->getVariables()
);
$values = array(
array('userId' => 1, 'userName' => 'Batman', 'userLocation' => 'Gotham City'),
array('userId' => 2, 'userName' => 'Superman', 'userLocation' => 'Metropolis'),
);
$templateProcessor->setValue('tableHeader', 'My clonable table');
$templateProcessor->cloneRowAndSetValues('userId', $values);
$this->assertContains('<w:t>Superman</w:t>', $templateProcessor->getMainPart());
$this->assertContains('<w:t>Metropolis</w:t>', $templateProcessor->getMainPart());
}
/** /**
* @expectedException \Exception * @expectedException \Exception
* @test * @test
@ -246,6 +310,78 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
); );
} }
public function testSetComplexValue()
{
$title = new TextRun();
$title->addText('This is my title');
$firstname = new Text('Donald');
$lastname = new Text('Duck');
$mainPart = '<?xml version="1.0" encoding="UTF-8"?>
<w:p>
<w:r>
<w:t xml:space="preserve">Hello ${document-title}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t xml:space="preserve">Hello ${firstname} ${lastname}</w:t>
</w:r>
</w:p>';
$result = '<?xml version="1.0" encoding="UTF-8"?>
<w:p>
<w:pPr/>
<w:r>
<w:rPr/>
<w:t xml:space="preserve">This is my title</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t xml:space="preserve">Hello </w:t>
</w:r>
<w:r>
<w:rPr/>
<w:t xml:space="preserve">Donald</w:t>
</w:r>
<w:r>
<w:t xml:space="preserve"> </w:t>
</w:r>
<w:r>
<w:rPr/>
<w:t xml:space="preserve">Duck</w:t>
</w:r>
</w:p>';
$templateProcessor = new TestableTemplateProcesor($mainPart);
$templateProcessor->setComplexBlock('document-title', $title);
$templateProcessor->setComplexValue('firstname', $firstname);
$templateProcessor->setComplexValue('lastname', $lastname);
$this->assertEquals(preg_replace('/>\s+</', '><', $result), preg_replace('/>\s+</', '><', $templateProcessor->getMainPart()));
}
/**
* @covers ::setValues
* @test
*/
public function testSetValues()
{
$mainPart = '<?xml version="1.0" encoding="UTF-8"?>
<w:p>
<w:r>
<w:t xml:space="preserve">Hello ${firstname} ${lastname}</w:t>
</w:r>
</w:p>';
$templateProcessor = new TestableTemplateProcesor($mainPart);
$templateProcessor->setValues(array('firstname' => 'John', 'lastname' => 'Doe'));
$this->assertContains('Hello John Doe', $templateProcessor->getMainPart());
}
/** /**
* @covers ::setImageValue * @covers ::setImageValue
* @test * @test
@ -595,4 +731,117 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
$variables = $templateProcessor->getVariablesForPart('<w:t>$</w:t></w:r><w:bookmarkStart w:id="0" w:name="_GoBack"/><w:bookmarkEnd w:id="0"/><w:r><w:t xml:space="preserve">15,000.00. </w:t></w:r><w:r w:rsidR="0056499B"><w:t>$</w:t></w:r><w:r w:rsidR="00573DFD" w:rsidRPr="00573DFD"><w:rPr><w:iCs/></w:rPr><w:t>{</w:t></w:r><w:proofErr w:type="spellStart"/><w:r w:rsidR="00573DFD" w:rsidRPr="00573DFD"><w:rPr><w:iCs/></w:rPr><w:t>variable_name</w:t></w:r><w:proofErr w:type="spellEnd"/><w:r w:rsidR="00573DFD" w:rsidRPr="00573DFD"><w:rPr><w:iCs/></w:rPr><w:t>}</w:t></w:r>'); $variables = $templateProcessor->getVariablesForPart('<w:t>$</w:t></w:r><w:bookmarkStart w:id="0" w:name="_GoBack"/><w:bookmarkEnd w:id="0"/><w:r><w:t xml:space="preserve">15,000.00. </w:t></w:r><w:r w:rsidR="0056499B"><w:t>$</w:t></w:r><w:r w:rsidR="00573DFD" w:rsidRPr="00573DFD"><w:rPr><w:iCs/></w:rPr><w:t>{</w:t></w:r><w:proofErr w:type="spellStart"/><w:r w:rsidR="00573DFD" w:rsidRPr="00573DFD"><w:rPr><w:iCs/></w:rPr><w:t>variable_name</w:t></w:r><w:proofErr w:type="spellEnd"/><w:r w:rsidR="00573DFD" w:rsidRPr="00573DFD"><w:rPr><w:iCs/></w:rPr><w:t>}</w:t></w:r>');
$this->assertEquals(array('variable_name'), $variables); $this->assertEquals(array('variable_name'), $variables);
} }
/**
* @covers ::textNeedsSplitting
*/
public function testTextNeedsSplitting()
{
$templateProcessor = new TestableTemplateProcesor();
$this->assertFalse($templateProcessor->textNeedsSplitting('<w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">${nothing-to-replace}</w:t></w:r>'));
$text = '<w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">Hello ${firstname} ${lastname}</w:t></w:r>';
$this->assertTrue($templateProcessor->textNeedsSplitting($text));
$splitText = $templateProcessor->splitTextIntoTexts($text);
$this->assertFalse($templateProcessor->textNeedsSplitting($splitText));
}
/**
* @covers ::splitTextIntoTexts
*/
public function testSplitTextIntoTexts()
{
$templateProcessor = new TestableTemplateProcesor();
$splitText = $templateProcessor->splitTextIntoTexts('<w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">${nothing-to-replace}</w:t></w:r>');
$this->assertEquals('<w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">${nothing-to-replace}</w:t></w:r>', $splitText);
$splitText = $templateProcessor->splitTextIntoTexts('<w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">Hello ${firstname} ${lastname}</w:t></w:r>');
$this->assertEquals('<w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">Hello </w:t></w:r><w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">${firstname}</w:t></w:r><w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve"> </w:t></w:r><w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">${lastname}</w:t></w:r>', $splitText);
}
public function testFindXmlBlockStart()
{
$toFind = '<w:r>
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:cs="Calibri"/>
<w:lang w:val="en-GB"/>
</w:rPr>
<w:t>This whole paragraph will be replaced with my ${title}</w:t>
</w:r>';
$mainPart = '<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid wp14">
<w:p w14:paraId="165D45AF" w14:textId="7FEC9B41" w:rsidR="005B1098" w:rsidRDefault="005B1098">
<w:r w:rsidR="00A045B2">
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:cs="Calibri"/>
<w:lang w:val="en-GB"/>
</w:rPr>
<w:t xml:space="preserve"> ${value1} ${value2}</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:cs="Calibri"/>
<w:lang w:val="en-GB"/>
</w:rPr>
<w:t>.</w:t>
</w:r>
</w:p>
<w:p w14:paraId="330D1954" w14:textId="0AB1D347" w:rsidR="00156568" w:rsidRDefault="00156568">
<w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:cs="Calibri"/>
<w:lang w:val="en-GB"/>
</w:rPr>
</w:pPr>
' . $toFind . '
</w:p>
</w:document>';
$templateProcessor = new TestableTemplateProcesor($mainPart);
$position = $templateProcessor->findContainingXmlBlockForMacro('${title}', 'w:r');
$this->assertEquals($toFind, $templateProcessor->getSlice($position['start'], $position['end']));
}
public function testShouldReturnFalseIfXmlBlockNotFound()
{
$mainPart = '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:p>
<w:r>
<w:rPr>
<w:lang w:val="en-GB"/>
</w:rPr>
<w:t xml:space="preserve">this is my text containing a ${macro}</w:t>
</w:r>
</w:p>
</w:document>';
$templateProcessor = new TestableTemplateProcesor($mainPart);
//non-existing macro
$result = $templateProcessor->findContainingXmlBlockForMacro('${fake-macro}', 'w:p');
$this->assertFalse($result);
//existing macro but not inside node looked for
$result = $templateProcessor->findContainingXmlBlockForMacro('${macro}', 'w:fake-node');
$this->assertFalse($result);
//existing macro but end tag not found after macro
$result = $templateProcessor->findContainingXmlBlockForMacro('${macro}', 'w:rPr');
$this->assertFalse($result);
}
public function testShouldMakeFieldsUpdateOnOpen()
{
$settingsPart = '<w:settings xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:zoom w:percent="100"/>
</w:settings>';
$templateProcessor = new TestableTemplateProcesor(null, $settingsPart);
$templateProcessor->setUpdateFields(true);
$this->assertContains('<w:updateFields w:val="true"/>', $templateProcessor->getSettingsPart());
$templateProcessor->setUpdateFields(false);
$this->assertContains('<w:updateFields w:val="false"/>', $templateProcessor->getSettingsPart());
}
} }

View File

@ -86,10 +86,10 @@ class ElementTest extends \PHPUnit\Framework\TestCase
$section = $phpWord->addSection(); $section = $phpWord->addSection();
$table = $section->addTable(); $table = $section->addTable();
$row1 = $table->addRow(); $row1 = $table->addRow();
$cell11 = $row1->addCell(1000, array('gridSpan' => 2)); $cell11 = $row1->addCell(1000, array('gridSpan' => 2, 'bgColor' => '6086B8'));
$cell11->addText('cell spanning 2 bellow'); $cell11->addText('cell spanning 2 bellow');
$row2 = $table->addRow(); $row2 = $table->addRow();
$cell21 = $row2->addCell(500); $cell21 = $row2->addCell(500, array('bgColor' => 'ffffff'));
$cell21->addText('first cell'); $cell21->addText('first cell');
$cell22 = $row2->addCell(500); $cell22 = $row2->addCell(500);
$cell22->addText('second cell'); $cell22->addText('second cell');
@ -100,6 +100,11 @@ class ElementTest extends \PHPUnit\Framework\TestCase
$this->assertEquals(1, $xpath->query('/html/body/table/tr[1]/td')->length); $this->assertEquals(1, $xpath->query('/html/body/table/tr[1]/td')->length);
$this->assertEquals('2', $xpath->query('/html/body/table/tr/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); $this->assertEquals('2', $xpath->query('/html/body/table/tr/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent);
$this->assertEquals(2, $xpath->query('/html/body/table/tr[2]/td')->length); $this->assertEquals(2, $xpath->query('/html/body/table/tr[2]/td')->length);
$this->assertEquals('#6086B8', $xpath->query('/html/body/table/tr[1]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent);
$this->assertEquals('#ffffff', $xpath->query('/html/body/table/tr[1]/td')->item(0)->attributes->getNamedItem('color')->textContent);
$this->assertEquals('#ffffff', $xpath->query('/html/body/table/tr[2]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent);
$this->assertNull($xpath->query('/html/body/table/tr[2]/td')->item(0)->attributes->getNamedItem('color'));
} }
/** /**

View File

@ -17,6 +17,7 @@
namespace PhpOffice\PhpWord\Writer; namespace PhpOffice\PhpWord\Writer;
use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest;
use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Settings;
use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\Jc;
@ -26,7 +27,7 @@ use PhpOffice\PhpWord\SimpleType\Jc;
* *
* @runTestsInSeparateProcesses * @runTestsInSeparateProcesses
*/ */
class HTMLTest extends \PHPUnit\Framework\TestCase class HTMLTest extends AbstractWebServerEmbeddedTest
{ {
/** /**
* Construct * Construct
@ -57,7 +58,7 @@ class HTMLTest extends \PHPUnit\Framework\TestCase
{ {
$localImage = __DIR__ . '/../_files/images/PhpWord.png'; $localImage = __DIR__ . '/../_files/images/PhpWord.png';
$archiveImage = 'zip://' . __DIR__ . '/../_files/documents/reader.docx#word/media/image1.jpeg'; $archiveImage = 'zip://' . __DIR__ . '/../_files/documents/reader.docx#word/media/image1.jpeg';
$gdImage = 'http://php.net/images/logos/php-med-trans-light.gif'; $gdImage = self::getRemoteGifImageUrl();
$objectSrc = __DIR__ . '/../_files/documents/sheet.xls'; $objectSrc = __DIR__ . '/../_files/documents/sheet.xls';
$file = __DIR__ . '/../_files/temp.html'; $file = __DIR__ . '/../_files/temp.html';

View File

@ -29,7 +29,7 @@ class ElementTest extends \PHPUnit\Framework\TestCase
*/ */
public function testUnmatchedElements() public function testUnmatchedElements()
{ {
$elements = array('Container', 'Text', 'Title', 'Link', 'Image', 'Table'); $elements = array('Container', 'Text', 'Title', 'Link', 'Image', 'Table', 'Field');
foreach ($elements as $element) { foreach ($elements as $element) {
$objectClass = 'PhpOffice\\PhpWord\\Writer\\RTF\\Element\\' . $element; $objectClass = 'PhpOffice\\PhpWord\\Writer\\RTF\\Element\\' . $element;
$parentWriter = new RTF(); $parentWriter = new RTF();
@ -39,4 +39,40 @@ class ElementTest extends \PHPUnit\Framework\TestCase
$this->assertEquals('', $object->write()); $this->assertEquals('', $object->write());
} }
} }
public function testPageField()
{
$parentWriter = new RTF();
$element = new \PhpOffice\PhpWord\Element\Field('PAGE');
$field = new \PhpOffice\PhpWord\Writer\RTF\Element\Field($parentWriter, $element);
$this->assertEquals("{\\field{\\*\\fldinst PAGE}{\\fldrslt}}\\par\n", $field->write());
}
public function testNumpageField()
{
$parentWriter = new RTF();
$element = new \PhpOffice\PhpWord\Element\Field('NUMPAGES');
$field = new \PhpOffice\PhpWord\Writer\RTF\Element\Field($parentWriter, $element);
$this->assertEquals("{\\field{\\*\\fldinst NUMPAGES}{\\fldrslt}}\\par\n", $field->write());
}
public function testDateField()
{
$parentWriter = new RTF();
$element = new \PhpOffice\PhpWord\Element\Field('DATE', array('dateformat' => 'd MM yyyy H:mm:ss'));
$field = new \PhpOffice\PhpWord\Writer\RTF\Element\Field($parentWriter, $element);
$this->assertEquals("{\\field{\\*\\fldinst DATE \\\\@ \"d MM yyyy H:mm:ss\"}{\\fldrslt}}\\par\n", $field->write());
}
public function testIndexField()
{
$parentWriter = new RTF();
$element = new \PhpOffice\PhpWord\Element\Field('INDEX');
$field = new \PhpOffice\PhpWord\Writer\RTF\Element\Field($parentWriter, $element);
$this->assertEquals("{}\\par\n", $field->write());
}
} }

View File

@ -510,4 +510,25 @@ class ElementTest extends \PHPUnit\Framework\TestCase
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t')); $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t'));
$this->assertEquals('this text contains an & (ampersant)', $doc->getElement('/w:document/w:body/w:p/w:r/w:t')->nodeValue); $this->assertEquals('this text contains an & (ampersant)', $doc->getElement('/w:document/w:body/w:p/w:r/w:t')->nodeValue);
} }
/**
* Test ListItemRun paragraph style writing
*/
public function testListItemRunStyleWriting()
{
$phpWord = new PhpWord();
$phpWord->addParagraphStyle('MyParagraphStyle', array('spaceBefore' => 400));
$section = $phpWord->addSection();
$listItemRun = $section->addListItemRun(0, null, 'MyParagraphStyle');
$listItemRun->addText('List item');
$listItemRun->addText(' in bold', array('bold' => true));
$doc = TestHelperDOCX::getDocument($phpWord);
$this->assertFalse($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:pPr'));
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:pStyle'));
$this->assertEquals('List item', $doc->getElement('/w:document/w:body/w:p/w:r[1]/w:t')->nodeValue);
$this->assertEquals(' in bold', $doc->getElement('/w:document/w:body/w:p/w:r[2]/w:t')->nodeValue);
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r[2]/w:rPr/w:b'));
}
} }

View File

@ -141,4 +141,21 @@ class TableTest extends \PHPUnit\Framework\TestCase
$this->assertSame($value, (int) $doc->getElementAttribute($path, 'w:w')); $this->assertSame($value, (int) $doc->getElementAttribute($path, 'w:w'));
$this->assertSame($type, $doc->getElementAttribute($path, 'w:type')); $this->assertSame($type, $doc->getElementAttribute($path, 'w:type'));
} }
public function testRigthToLeft()
{
$tableStyle = new Table();
$tableStyle->setBidiVisual(true);
$phpWord = new \PhpOffice\PhpWord\PhpWord();
$section = $phpWord->addSection();
$table = $section->addTable($tableStyle);
$table->addRow();
$doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
$path = '/w:document/w:body/w:tbl/w:tblPr/w:bidiVisual';
$this->assertTrue($doc->elementExists($path));
$this->assertEquals('1', $doc->getElementAttribute($path, 'w:val'));
}
} }

View File

@ -17,6 +17,7 @@
namespace PhpOffice\PhpWord\Writer; namespace PhpOffice\PhpWord\Writer;
use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest;
use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\Jc;
use PhpOffice\PhpWord\TestHelperDOCX; use PhpOffice\PhpWord\TestHelperDOCX;
@ -26,7 +27,7 @@ use PhpOffice\PhpWord\TestHelperDOCX;
* *
* @runTestsInSeparateProcesses * @runTestsInSeparateProcesses
*/ */
class Word2007Test extends \PHPUnit\Framework\TestCase class Word2007Test extends AbstractWebServerEmbeddedTest
{ {
/** /**
* Tear down after each test * Tear down after each test
@ -75,7 +76,7 @@ class Word2007Test extends \PHPUnit\Framework\TestCase
public function testSave() public function testSave()
{ {
$localImage = __DIR__ . '/../_files/images/earth.jpg'; $localImage = __DIR__ . '/../_files/images/earth.jpg';
$remoteImage = 'http://php.net/images/logos/new-php-logo.png'; $remoteImage = self::getRemoteGifImageUrl();
$phpWord = new PhpWord(); $phpWord = new PhpWord();
$phpWord->addFontStyle('Font', array('size' => 11)); $phpWord->addFontStyle('Font', array('size' => 11));
$phpWord->addParagraphStyle('Paragraph', array('alignment' => Jc::CENTER)); $phpWord->addParagraphStyle('Paragraph', array('alignment' => Jc::CENTER));

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,80 @@
<?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;
use Symfony\Component\Process\Process;
abstract class AbstractWebServerEmbeddedTest extends \PHPUnit\Framework\TestCase
{
private static $httpServer;
public static function setUpBeforeClass()
{
if (self::isBuiltinServerSupported()) {
self::$httpServer = new Process('php -S localhost:8080 -t tests/PhpWord/_files');
self::$httpServer->start();
while (!self::$httpServer->isRunning()) {
usleep(1000);
}
}
}
public static function tearDownAfterClass()
{
if (self::isBuiltinServerSupported()) {
self::$httpServer->stop();
}
}
protected static function getBaseUrl()
{
return 'http://localhost:8080';
}
protected static function getRemoteImageUrl()
{
if (self::$httpServer) {
return self::getBaseUrl() . '/images/new-php-logo.png';
}
return 'http://php.net/images/logos/new-php-logo.png';
}
protected static function getRemoteGifImageUrl()
{
if (self::$httpServer) {
return self::getBaseUrl() . '/images/mario.gif';
}
return 'http://php.net/images/logos/php-med-trans-light.gif';
}
protected static function getRemoteBmpImageUrl()
{
if (self::$httpServer) {
return self::getBaseUrl() . '/images/duke_nukem.bmp';
}
return 'https://samples.libav.org/image-samples/RACECAR.BMP';
}
private static function isBuiltinServerSupported()
{
return version_compare(PHP_VERSION, '5.4.0', '>=');
}
}

View File

@ -25,9 +25,10 @@ namespace PhpOffice\PhpWord;
*/ */
class TestableTemplateProcesor extends TemplateProcessor class TestableTemplateProcesor extends TemplateProcessor
{ {
public function __construct($mainPart = null) public function __construct($mainPart = null, $settingsPart = null)
{ {
$this->tempDocumentMainPart = $mainPart; $this->tempDocumentMainPart = $mainPart;
$this->tempDocumentSettingsPart = $settingsPart;
} }
public function fixBrokenMacros($documentPart) public function fixBrokenMacros($documentPart)
@ -35,6 +36,16 @@ class TestableTemplateProcesor extends TemplateProcessor
return parent::fixBrokenMacros($documentPart); return parent::fixBrokenMacros($documentPart);
} }
public function splitTextIntoTexts($text)
{
return parent::splitTextIntoTexts($text);
}
public function textNeedsSplitting($text)
{
return parent::textNeedsSplitting($text);
}
public function getVariablesForPart($documentPartXML) public function getVariablesForPart($documentPartXML)
{ {
$documentPartXML = parent::fixBrokenMacros($documentPartXML); $documentPartXML = parent::fixBrokenMacros($documentPartXML);
@ -42,8 +53,34 @@ class TestableTemplateProcesor extends TemplateProcessor
return parent::getVariablesForPart($documentPartXML); return parent::getVariablesForPart($documentPartXML);
} }
public function findXmlBlockStart($offset, $blockType)
{
return parent::findXmlBlockStart($offset, $blockType);
}
public function findContainingXmlBlockForMacro($macro, $blockType = 'w:p')
{
return parent::findContainingXmlBlockForMacro($macro, $blockType);
}
public function getSlice($startPosition, $endPosition = 0)
{
return parent::getSlice($startPosition, $endPosition);
}
/**
* @return string
*/
public function getMainPart() public function getMainPart()
{ {
return $this->tempDocumentMainPart; return $this->tempDocumentMainPart;
} }
/**
* @return string
*/
public function getSettingsPart()
{
return $this->tempDocumentSettingsPart;
}
} }

View File

@ -76,10 +76,10 @@ class XmlDocument
$this->file = $file; $this->file = $file;
$file = $this->path . '/' . $file; $file = $this->path . '/' . $file;
libxml_disable_entity_loader(false); $orignalLibEntityLoader = libxml_disable_entity_loader(false);
$this->dom = new \DOMDocument(); $this->dom = new \DOMDocument();
$this->dom->load($file); $this->dom->load($file);
libxml_disable_entity_loader(true); libxml_disable_entity_loader($orignalLibEntityLoader);
return $this->dom; return $this->dom;
} }