Compare commits

...

59 Commits

Author SHA1 Message Date
Ahmad Syamim
08064480ea
Update recipes.rst (#2367) 2023-01-08 09:39:08 +01:00
Erik Hazington
aac3a601e1
Add support for fractional font sizes (#2363)
* Add support for fractional font sizes

Word supports fractional font sizes like 8.5pt, but PHPWord doesn't.

* Add background color support for textboxes

* Add background color support for text boxes for Word writer

* Added period to the comments to match the cs-fixer configuration

* Fixed cs-fixer issues.

* Added type hints and matching PHPDoc. Also added @return void.

* Added bgcolor unit test coverage

* Removed @return void as not compatible with projects coding standard.

* Fixed several CS-Fixer issues

* Replaced deprecated "assertRegExp" test method by assertMatchesRegularExpression.

* Address the CI coverage issue for the regex match assertion.

* Add support for fractional font sizes

Word supports fractional font sizes like 8.5pt, but PHPWord doesn't.

* The default font size method now supports int and float font sizes. Added type hinting to settings class and unit test coverage for fractional (float) font size.

* The default font size method now supports int and float font sizes. Added type hinting to settings class and unit test coverage for fractional (float) font size.

Co-authored-by: hazington <hazington@eweso.de>
Co-authored-by: Progi1984 <progi1984@gmail.com>
2023-01-06 21:04:39 +01:00
Yohann Tilotti
ef99fac8e1
Add support table row height when importing HTML (#2350)
* Add support table row height when importing HTML

* fix space

* fix phpcsfixer

* Add test table row height

* Fix xpath test table row height

* Fix attribute first test table row height

* Fix $cValue

Co-authored-by: Progi1984 <progi1984@gmail.com>

* Fix css

* Fix test on pt

Co-authored-by: Progi1984 <progi1984@gmail.com>
2023-01-06 16:26:34 +01:00
Progi1984
60734dd7f1
Merge pull request #2277 from amir9480/patch-1
Add softhyphen support to Word2007 Reader
2023-01-05 22:04:27 +01:00
Progi1984
ed8d9ff420
Fixed some stylelint 2023-01-05 21:53:50 +01:00
Progi1984
a379577ac9
Merge pull request #2364 from hazington/patch-5
Add background color support for text box element for Word writer
2023-01-04 09:23:26 +01:00
hazington
53d34fd049 Address the CI coverage issue for the regex match assertion. 2023-01-04 00:07:39 +01:00
hazington
679a738c42 Replaced deprecated "assertRegExp" test method by assertMatchesRegularExpression. 2023-01-03 23:56:04 +01:00
hazington
0f6fbf2b11 Fixed several CS-Fixer issues 2023-01-03 23:44:51 +01:00
hazington
050802bdb5 Removed @return void as not compatible with projects coding standard. 2023-01-03 23:38:08 +01:00
hazington
a215501cc9 Added bgcolor unit test coverage 2023-01-03 23:35:37 +01:00
hazington
1a80aacb4f Added type hints and matching PHPDoc. Also added @return void. 2023-01-03 23:26:02 +01:00
Erik Hazington
40d5770651
Fixed cs-fixer issues. 2022-12-30 21:50:28 +01:00
Erik Hazington
143e01b29b
Added period to the comments to match the cs-fixer configuration 2022-12-30 21:46:44 +01:00
Erik Hazington
25575c80ca
Add background color support for text boxes for Word writer 2022-12-30 21:34:47 +01:00
Erik Hazington
1dc3dc6ce5
Add background color support for textboxes 2022-12-30 21:32:48 +01:00
Progi1984
b453cf00ff
Merge pull request #2261 from ismail1432/customize-macro
Allow to customize macro delimiters in TemplateProcessor
2022-12-23 19:36:07 +01:00
Progi1984
f195d282d0
Merge pull request #2111 from brammeleman/patch-1
Fixed code example
2022-11-29 22:04:08 +01:00
Progi1984
b2dfa65698
Merge pull request #1848 from rikvdlooi/patch-1
Fixed setting width of Cell Style
2022-11-29 21:46:07 +01:00
Progi1984
be499388da
Merge pull request #2336 from kernusr/read-empty-vmerge
Word2007 : Read empty vmerge
2022-11-29 21:40:09 +01:00
Adrien Crivelli
20f3a3e370
Use pcov instead of xdebug because it is much faster 2022-11-29 11:09:02 +01:00
Adrien Crivelli
803cdee4f3
Use local install of friendsofphp/php-cs-fixer
Otherwise, `composer check` and `composer fix` are broken and contributing
becomes much harder than it should be. Also using the same version
everywhere is a benefit to get consistent result locally and on CI.
2022-11-29 10:50:45 +01:00
Progi1984
25de4b819c Fixed PHPCSFixer error 2022-11-23 20:36:39 +00:00
Progi1984
6041bb3fb5
Merge pull request #2339 from Progi1984/className
HTML Reader : Set style name from the CSS class (and if not CSS is loaded)
2022-11-23 13:47:30 +01:00
Progi1984
249e049894
Merge pull request #2171 from amgad-naiem/patch-1
Added Swedish language
2022-11-23 13:44:11 +01:00
Progi1984
9658c12084
Merge pull request #2308 from fbclol/develop
Call static instead of self on protected method
2022-11-23 13:41:37 +01:00
Progi1984
83f6b020b3
Merge pull request #2329 from nicoder/improve-phpdoc
improve TemplateProcessor#setValueForPart parameter phpdoc
2022-11-23 13:34:28 +01:00
Progi1984
d721b5eda7 HTML Reader : Set style name from the CSS class (and if not CSS is loaded) 2022-11-23 13:32:03 +01:00
Progi1984
02e3aa96b6
Merge pull request #2338 from Progi1984/className
HTML Reader : Set style name from the CSS class
2022-11-22 21:53:53 +01:00
Progi1984
02a92c8924 HTML Reader : Set style name from the CSS class 2022-11-22 21:38:41 +01:00
Artem Vasilev
b457ff5f7f set default value for vMerge 2022-11-22 13:45:55 +03:00
Artem Vasilev
573f1c3ea1 set default value on empty value attribute 2022-11-22 13:43:03 +03:00
Nicolas Dermine
0189977743 improve phpdoc
The `tempDocumentHeaders` and `tempDocumentFooters` array attributes are
passed to this method parameter
2022-11-20 07:56:42 +01:00
Franck Boué
9afd53d622
remove - 2022-11-18 13:44:35 +01:00
Franck Boué
a075740e32
remove espaces 2022-11-18 13:38:18 +01:00
Franck Boué
ecd2fcbda8
add blank lines 2022-11-18 13:34:21 +01:00
Franck Boué
9ed82d1f5a
delete blank lines 2022-11-18 13:31:04 +01:00
Progi1984
0af4ca1f6e
Merge pull request #2327 from Progi1984/htmlReaderExtractCssStyle
HTML Reader : Add basic support for CSS Style Tag
2022-11-18 10:42:04 +01:00
Progi1984
228bada972
Remove stale Github Actions 2022-11-18 10:37:07 +01:00
Adrien Crivelli
6deea59905
Update GitHub Actions 2022-11-17 14:39:30 +01:00
Adrien Crivelli
95ce998651
Drop obsolete coverall references
Instead, we've been using Scrutinizer for a while
2022-11-17 09:53:27 +01:00
Adrien Crivelli
c37be288a5
Consume half of the 1000 operations per hour limit of GitHub API 2022-11-17 09:45:21 +01:00
Adrien Crivelli
28ca75eca4
Close oldest stale issues first 2022-11-16 22:55:44 +01:00
Adrien Crivelli
0b9f30fc77
Remove all traces of Travis 2022-11-16 22:35:01 +01:00
Adrien Crivelli
d2c6b9f790
master is the new default branch
`master` is now the only permanent branch. Features and fixes should be merged
into `master` when stable. Pull requests should be forked from `master`.

`develop` branch disappear entirely in favor of temporary features/fixes branches.
2022-11-16 22:27:10 +01:00
Adrien Crivelli
a771de75b9
Keep things as private as possible 2022-11-16 22:02:21 +01:00
Progi1984
ab3a2c0418 HTML Reader : Add basic support for CSS Style Tag 2022-11-16 22:02:07 +01:00
Adrien Crivelli
14c6e6f370
Merge pull request #2104 from simivar/feature/delete-row
Introduce deleteRow() method for TemplateProcessor
2022-11-16 21:58:25 +01:00
simivar
f482f2600b CS Fixer 2022-11-07 22:38:19 +01:00
Krystian Marcisz
46e61d4c18 Apply suggestions from code review
Co-authored-by: Progi1984 <progi1984@gmail.com>
2022-11-06 19:19:25 +01:00
Krystian Marcisz
4b7e7e4612 Update src/PhpWord/TemplateProcessor.php
Co-authored-by: Progi1984 <progi1984@gmail.com>
2022-11-06 19:19:25 +01:00
Krystian Marcisz
9a6eb6970d Update src/PhpWord/TemplateProcessor.php
Co-authored-by: Progi1984 <progi1984@gmail.com>
2022-11-06 19:19:25 +01:00
simivar
91504dfddc Introduce deleteRow() method for TemplateProcessor 2022-11-06 19:19:23 +01:00
Franck
024fdf9c1a call static instead of self on protected method 2022-10-07 16:04:39 +02:00
Smaine Milianni
c4bcba9741 allow to customize macro in TemplateProcessor 2022-09-30 11:51:05 +01:00
amir
26757c29bb
Add softhyphen support to word reader 2022-08-12 14:38:49 +04:00
Amgad Naiem
d9c1daa89c
Added Swedish language 2021-12-06 22:39:26 +02:00
brammeleman
5bd4ad39a5
fixed code example
classes not found and missing dollar sign
2021-06-29 11:57:41 +02:00
rikvdlooi
04b224caa5
Fixed setting width of Cell Style
The setWidth method did not change the width in the style, seems to be a small mistake.
Now the width is correctly for the Cell Style.
2020-04-15 22:34:19 +02:00
32 changed files with 1276 additions and 322 deletions

4
.gitattributes vendored
View File

@ -1,6 +1,6 @@
# build config
/.scrutinizer.yml export-ignore
/.travis.yml export-ignore
/.github export-ignore
/php_cs.dist export-ignore
/phpmd.xml.dist export-ignore
/phpstan.neon export-ignore
@ -18,4 +18,4 @@
# tests
/phpunit.xml.dist export-ignore
/tests export-ignore
/tests export-ignore

View File

@ -9,6 +9,6 @@ labels: WontFix
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).
Sample code is in the [`/samples/` directory](https://github.com/PHPOffice/PHPWord/tree/master/samples).
Usage questions belong on [Stack Overflow](https://stackoverflow.com/questions/tagged/phpword).

View File

@ -19,7 +19,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
@ -33,7 +33,7 @@ jobs:
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
@ -58,7 +58,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
@ -73,40 +73,37 @@ jobs:
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Composer Install
run: composer global require friendsofphp/php-cs-fixer
- name: Add environment path
run: export PATH="$PATH:$HOME/.composer/vendor/bin"
- name: Install dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Code style with PHP-CS-Fixer
run: php-cs-fixer fix --dry-run --diff
run: ./vendor/bin/php-cs-fixer fix --dry-run --diff
coverage:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: 7.4
extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib
coverage: xdebug
coverage: pcov
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}

View File

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2

View File

@ -1,32 +0,0 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '30 1 * * *'
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v5
with:
days-before-stale: 90
days-before-close: 60
exempt-issue-labels: 'pinned,security'
exempt-pr-labels: 'pinned,security'
stale-issue-message: 'This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs.
If this is still an issue for you, please try to help by debugging it
further and sharing your results.
Thank you for your contributions.'
stale-pr-message: 'This PR has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs.
If this is still an issue for you, please try to complete the PR by adding tests and making sure that the CI is green.
Thank you for your contributions.'

View File

@ -1,23 +1,16 @@
# ![PHPWord](https://rawgit.com/PHPOffice/PHPWord/develop/docs/images/phpword.svg "PHPWord")
Master:
[![Latest Stable Version](https://poser.pugx.org/phpoffice/phpword/v/stable.png)](https://packagist.org/packages/phpoffice/phpword)
[![Build Status](https://travis-ci.org/PHPOffice/PHPWord.svg?branch=master)](https://travis-ci.org/PHPOffice/PHPWord)
[![CI](https://github.com/PHPOffice/PHPWord/actions/workflows/ci.yml/badge.svg)](https://github.com/PHPOffice/PHPWord/actions/workflows/ci.yml)
[![Code Quality](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/)
[![Coverage Status](https://coveralls.io/repos/github/PHPOffice/PHPWord/badge.svg?branch=master)](https://coveralls.io/github/PHPOffice/PHPWord?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/)
[![Total Downloads](https://poser.pugx.org/phpoffice/phpword/downloads.png)](https://packagist.org/packages/phpoffice/phpword)
[![License](https://poser.pugx.org/phpoffice/phpword/license.png)](https://packagist.org/packages/phpoffice/phpword)
[![Join the chat at https://gitter.im/PHPOffice/PHPWord](https://img.shields.io/badge/GITTER-join%20chat-green.svg)](https://gitter.im/PHPOffice/PHPWord)
Develop:
[![Latest Development Version](https://img.shields.io/badge/unstable-dev--develop-orange.svg)](https://packagist.org/packages/phpoffice/phpword#dev-develop)
[![Build Status](https://travis-ci.org/PHPOffice/PHPWord.svg?branch=develop)](https://travis-ci.org/PHPOffice/PHPWord/branches)
[![Code Quality](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/?branch=develop)
[![Coverage Status](https://coveralls.io/repos/github/PHPOffice/PHPWord/badge.svg?branch=develop)](https://coveralls.io/github/PHPOffice/PHPWord?branch=develop)
PHPWord is a library written in pure PHP that provides a set of classes to write to and read from different document file formats. The current version of PHPWord supports Microsoft [Office Open XML](http://en.wikipedia.org/wiki/Office_Open_XML) (OOXML or OpenXML), OASIS [Open Document Format for Office Applications](http://en.wikipedia.org/wiki/OpenDocument) (OpenDocument or ODF), [Rich Text Format](http://en.wikipedia.org/wiki/Rich_Text_Format) (RTF), HTML, and PDF.
PHPWord is an open source project licensed under the terms of [LGPL version 3](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://github.com/PHPOffice/PHPWord/actions) and unit testing. 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)
@ -78,9 +71,9 @@ Run the following to use the latest stable version
```sh
composer require phpoffice/phpword
```
or if you want the latest develop version
or if you want the latest unreleased version
```sh
composer require phpoffice/phpword:dev-develop
composer require phpoffice/phpword:dev-master
```
## Getting started
@ -165,6 +158,6 @@ 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.
- 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 [master](https://github.com/PHPOffice/PHPWord/tree/master) branch.
- 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.

View File

@ -70,11 +70,11 @@
"ext-libxml": "*",
"dompdf/dompdf": "^2.0",
"mpdf/mpdf": "^8.1",
"php-coveralls/php-coveralls": "^2.5",
"phpmd/phpmd": "^2.13",
"phpunit/phpunit": ">=7.0",
"tecnickcom/tcpdf": "^6.5",
"symfony/process": "^4.4"
"symfony/process": "^4.4",
"friendsofphp/php-cs-fixer": "^3.3"
},
"suggest": {
"ext-zip": "Allows writing OOXML and ODF",
@ -92,10 +92,5 @@
"psr-4": {
"PhpOffice\\PhpWordTests\\": "tests/PhpWordTests"
}
},
"extra": {
"branch-alias": {
"dev-develop": "0.19-dev"
}
}
}

View File

@ -192,11 +192,11 @@ You can also specify the status of the spell and grammar checks, marking spellin
.. code-block:: php
$proofState = new ProofState();
$proofState->setGrammar(ProofState::CLEAN);
$proofState->setSpelling(ProofState::DIRTY);
$proofState = new \PhpOffice\PhpWord\ComplexType\ProofState();
$proofState->setGrammar(\PhpOffice\PhpWord\ComplexType\ProofState::CLEAN);
$proofState->setSpelling(\PhpOffice\PhpWord\ComplexType\ProofState::DIRTY);
$phpWord->getSettings()->setProofState(proofState);
$phpWord->getSettings()->setProofState($proofState);
Track Revisions
~~~~~~~~~~~~~~~

View File

@ -39,7 +39,7 @@ Example:
.. code-block:: bash
composer require phpoffice/phpword:dev-develop
composer require phpoffice/phpword:dev-master
Using samples
-------------

View File

@ -13,13 +13,10 @@ Applications <http://en.wikipedia.org/wiki/OpenDocument>`__
Format <http://en.wikipedia.org/wiki/Rich_Text_Format>`__ (RTF).
PHPWord is an open source project licensed under the terms of `LGPL
version 3 <https://github.com/PHPOffice/PHPWord/blob/develop/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/>`__.
version 3 <https://github.com/PHPOffice/PHPWord/blob/master/COPYING.LESSER>`__.
PHPWord is aimed to be a high quality software product.
You can learn more about PHPWord by reading this Developers'
Documentation and the `API
Documentation <http://phpoffice.github.io/PHPWord/docs/develop/>`__.
Documentation.
Features
--------
@ -191,7 +188,7 @@ things that you can do to contribute.
guide <https://github.com/PHPOffice/PHPWord/blob/master/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>`__
`master <https://github.com/PHPOffice/PHPWord/tree/master>`__
branch.
- Submit `bug reports or feature
requests <https://github.com/PHPOffice/PHPWord/issues>`__ to GitHub.

View File

@ -29,7 +29,7 @@ Use ``php://output`` as the filename.
.. code-block:: php
$phpWord = new \PhpOffice\PhpWord\PhpWord();
$section = $phpWord->createSection();
$section = $phpWord->addSection();
$section->addText('Hello World!');
$file = 'HelloWorld.docx';
header("Content-Description: File Transfer");

View File

@ -4,7 +4,7 @@ Templates processing
====================
You can create an OOXML document template with included search-patterns (macros) which can be replaced by any value you wish. Only single-line values can be replaced.
Macros are defined like this: ``${search-pattern}``.
By default Macros are defined like this: ``${search-pattern}`` but you can define custom macros.
To load a template file, create a new instance of the TemplateProcessor.
.. code-block:: php
@ -35,6 +35,30 @@ You can also set multiple values by passing all of them in an array.
$templateProcessor->setValues(array('firstname' => 'John', 'lastname' => 'Doe'));
setMacroOpeningChars
""""""""
You can define a custom opening macro. The following will set ``{#`` as the opening search pattern.
.. code-block:: php
$templateProcessor->setMacroOpeningChars('{#');
setMacroClosingChars
""""""""
You can define a custom closing macro. The following will set ``#}`` as the closing search pattern.
.. code-block:: php
$templateProcessor->setMacroClosingChars('#}');
setMacroChars
""""""""
You can define a custom opening and closing macro at the same time . The following will set the search-pattern like this: ``{#search-pattern#}`` .
.. code-block:: php
$templateProcessor->setMacroChars('{#', '#}');
setImageValue
"""""""""""""
The search-pattern model for images can be like:

View File

@ -323,6 +323,8 @@ abstract class AbstractPart
$element->setChangeInfo($type, $author, $date);
}
}
} elseif ($node->nodeName == 'w:softHyphen') {
$element = $parent->addText("\u{200c}", $fontStyle, $paragraphStyle);
}
}
@ -558,7 +560,7 @@ abstract class AbstractPart
'valign' => [self::READ_VALUE, 'w:vAlign'],
'textDirection' => [self::READ_VALUE, 'w:textDirection'],
'gridSpan' => [self::READ_VALUE, 'w:gridSpan'],
'vMerge' => [self::READ_VALUE, 'w:vMerge'],
'vMerge' => [self::READ_VALUE, 'w:vMerge', null, null, 'continue'],
'bgColor' => [self::READ_VALUE, 'w:shd', 'w:fill'],
];
@ -626,7 +628,7 @@ abstract class AbstractPart
$styles = [];
foreach ($styleDefs as $styleProp => $styleVal) {
[$method, $element, $attribute, $expected] = array_pad($styleVal, 4, null);
[$method, $element, $attribute, $expected, $default] = array_pad($styleVal, 5, null);
$element = $this->findPossibleElement($xmlReader, $parentNode, $element);
if ($element === null) {
@ -640,7 +642,7 @@ abstract class AbstractPart
// Use w:val as default if no attribute assigned
$attribute = ($attribute === null) ? 'w:val' : $attribute;
$attributeValue = $xmlReader->getAttribute($attribute, $node);
$attributeValue = $xmlReader->getAttribute($attribute, $node) ?? $default;
$styleValue = $this->readStyleDef($method, $attributeValue, $expected);
if ($styleValue !== null) {

View File

@ -2,10 +2,8 @@
/**
* 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.
@ -29,48 +27,46 @@ class Settings
*
* @const string
*/
const ZIPARCHIVE = 'ZipArchive';
const PCLZIP = 'PclZip';
const OLD_LIB = \PhpOffice\PhpWord\Shared\ZipArchive::class; // @deprecated 0.11
public const ZIPARCHIVE = 'ZipArchive';
public const PCLZIP = 'PclZip';
public const OLD_LIB = \PhpOffice\PhpWord\Shared\ZipArchive::class; // @deprecated 0.11
/**
* PDF rendering libraries.
*
* @const string
*/
const PDF_RENDERER_DOMPDF = 'DomPDF';
const PDF_RENDERER_TCPDF = 'TCPDF';
const PDF_RENDERER_MPDF = 'MPDF';
public const PDF_RENDERER_DOMPDF = 'DomPDF';
public const PDF_RENDERER_TCPDF = 'TCPDF';
public const PDF_RENDERER_MPDF = 'MPDF';
/**
* Measurement units multiplication factor.
*
* Applied to:
* - Section: margins, header/footer height, gutter, column spacing
* - Tab: position
* - Indentation: left, right, firstLine, hanging
* - Spacing: before, after
* - Spacing: before, after.
*
* @const string
*/
const UNIT_TWIP = 'twip'; // = 1/20 point
const UNIT_CM = 'cm';
const UNIT_MM = 'mm';
const UNIT_INCH = 'inch';
const UNIT_POINT = 'point'; // = 1/72 inch
const UNIT_PICA = 'pica'; // = 1/6 inch = 12 points
public const UNIT_TWIP = 'twip'; // = 1/20 point
public const UNIT_CM = 'cm';
public const UNIT_MM = 'mm';
public const UNIT_INCH = 'inch';
public const UNIT_POINT = 'point'; // = 1/72 inch
public const UNIT_PICA = 'pica'; // = 1/6 inch = 12 points
/**
* Default font settings.
*
* OOXML defined font size values in halfpoints, i.e. twice of what PhpWord
* use, and the conversion will be conducted during XML writing.
*/
const DEFAULT_FONT_NAME = 'Arial';
const DEFAULT_FONT_SIZE = 10;
const DEFAULT_FONT_COLOR = '000000';
const DEFAULT_FONT_CONTENT_TYPE = 'default'; // default|eastAsia|cs
const DEFAULT_PAPER = 'A4';
public const DEFAULT_FONT_NAME = 'Arial';
public const DEFAULT_FONT_SIZE = 10;
public const DEFAULT_FONT_COLOR = '000000';
public const DEFAULT_FONT_CONTENT_TYPE = 'default'; // default|eastAsia|cs
public const DEFAULT_PAPER = 'A4';
/**
* Compatibility option for XMLWriter.
@ -89,21 +85,21 @@ class Settings
/**
* Name of the external Library used for rendering PDF files.
*
* @var string
* @var null|string
*/
private static $pdfRendererName;
/**
* Directory Path to the external Library used for rendering PDF files.
*
* @var string
* @var null|string
*/
private static $pdfRendererPath;
/**
* Measurement unit.
*
* @var float|int
* @var string
*/
private static $measurementUnit = self::UNIT_TWIP;
@ -117,7 +113,7 @@ class Settings
/**
* Default font size.
*
* @var int
* @var float|int
*/
private static $defaultFontSize = self::DEFAULT_FONT_SIZE;
@ -148,23 +144,17 @@ class Settings
*
* @return bool Compatibility
*/
public static function hasCompatibility()
public static function hasCompatibility(): bool
{
return self::$xmlWriterCompatibility;
}
/**
* Set the compatibility option used by the XMLWriter.
*
* This sets the setIndent and setIndentString for better compatibility
*
* @param bool $compatibility
*
* @return bool
* This sets the setIndent and setIndentString for better compatibility.
*/
public static function setCompatibility($compatibility)
public static function setCompatibility(bool $compatibility): bool
{
$compatibility = (bool) $compatibility;
self::$xmlWriterCompatibility = $compatibility;
return true;
@ -172,22 +162,16 @@ class Settings
/**
* Get zip handler class.
*
* @return string
*/
public static function getZipClass()
public static function getZipClass(): string
{
return self::$zipClass;
}
/**
* Set zip handler class.
*
* @param string $zipClass
*
* @return bool
*/
public static function setZipClass($zipClass)
public static function setZipClass(string $zipClass): bool
{
if (in_array($zipClass, [self::PCLZIP, self::ZIPARCHIVE, self::OLD_LIB])) {
self::$zipClass = $zipClass;
@ -201,12 +185,9 @@ class Settings
/**
* Set details of the external library for rendering PDF files.
*
* @param string $libraryName
* @param string $libraryBaseDir
*
* @return bool Success or failure
*/
public static function setPdfRenderer($libraryName, $libraryBaseDir)
public static function setPdfRenderer(string $libraryName, string $libraryBaseDir): bool
{
if (!self::setPdfRendererName($libraryName)) {
return false;
@ -217,22 +198,16 @@ class Settings
/**
* Return the PDF Rendering Library.
*
* @return string
*/
public static function getPdfRendererName()
public static function getPdfRendererName(): ?string
{
return self::$pdfRendererName;
}
/**
* Identify the external library to use for rendering PDF files.
*
* @param string $libraryName
*
* @return bool
*/
public static function setPdfRendererName($libraryName)
public static function setPdfRendererName(?string $libraryName): bool
{
$pdfRenderers = [self::PDF_RENDERER_DOMPDF, self::PDF_RENDERER_TCPDF, self::PDF_RENDERER_MPDF];
if (!in_array($libraryName, $pdfRenderers)) {
@ -245,10 +220,8 @@ class Settings
/**
* Return the directory path to the PDF Rendering Library.
*
* @return string
*/
public static function getPdfRendererPath()
public static function getPdfRendererPath(): ?string
{
return self::$pdfRendererPath;
}
@ -256,11 +229,11 @@ class Settings
/**
* Location of external library to use for rendering PDF files.
*
* @param string $libraryBaseDir Directory path to the library's base folder
* @param null|string $libraryBaseDir Directory path to the library's base folder
*
* @return bool Success or failure
*/
public static function setPdfRendererPath($libraryBaseDir)
public static function setPdfRendererPath(?string $libraryBaseDir): bool
{
if (!$libraryBaseDir || false === file_exists($libraryBaseDir) || false === is_readable($libraryBaseDir)) {
return false;
@ -272,25 +245,25 @@ class Settings
/**
* Get measurement unit.
*
* @return string
*/
public static function getMeasurementUnit()
public static function getMeasurementUnit(): string
{
return self::$measurementUnit;
}
/**
* Set measurement unit.
*
* @param string $value
*
* @return bool
*/
public static function setMeasurementUnit($value)
public static function setMeasurementUnit(string $value): bool
{
$units = [self::UNIT_TWIP, self::UNIT_CM, self::UNIT_MM, self::UNIT_INCH,
self::UNIT_POINT, self::UNIT_PICA, ];
$units = [
self::UNIT_TWIP,
self::UNIT_CM,
self::UNIT_MM,
self::UNIT_INCH,
self::UNIT_POINT,
self::UNIT_PICA,
];
if (!in_array($value, $units)) {
return false;
}
@ -302,11 +275,11 @@ class Settings
/**
* Sets the user defined path to temporary directory.
*
* @since 0.12.0
*
* @param string $tempDir The user defined path to temporary directory
*
* @since 0.12.0
*/
public static function setTempDir($tempDir): void
public static function setTempDir(string $tempDir): void
{
self::$tempDir = $tempDir;
}
@ -315,10 +288,8 @@ class Settings
* Returns path to temporary directory.
*
* @since 0.12.0
*
* @return string
*/
public static function getTempDir()
public static function getTempDir(): string
{
if (!empty(self::$tempDir)) {
$tempDir = self::$tempDir;
@ -331,44 +302,34 @@ class Settings
/**
* @since 0.13.0
*
* @return bool
*/
public static function isOutputEscapingEnabled()
public static function isOutputEscapingEnabled(): bool
{
return self::$outputEscapingEnabled;
}
/**
* @since 0.13.0
*
* @param bool $outputEscapingEnabled
*/
public static function setOutputEscapingEnabled($outputEscapingEnabled): void
public static function setOutputEscapingEnabled(bool $outputEscapingEnabled): void
{
self::$outputEscapingEnabled = $outputEscapingEnabled;
}
/**
* Get default font name.
*
* @return string
*/
public static function getDefaultFontName()
public static function getDefaultFontName(): string
{
return self::$defaultFontName;
}
/**
* Set default font name.
*
* @param string $value
*
* @return bool
*/
public static function setDefaultFontName($value)
public static function setDefaultFontName(string $value): bool
{
if (is_string($value) && trim($value) !== '') {
if (trim($value) !== '') {
self::$defaultFontName = $value;
return true;
@ -380,7 +341,7 @@ class Settings
/**
* Get default font size.
*
* @return int
* @return float|int
*/
public static function getDefaultFontSize()
{
@ -390,14 +351,11 @@ class Settings
/**
* Set default font size.
*
* @param int $value
*
* @return bool
* @param null|float|int $value
*/
public static function setDefaultFontSize($value)
public static function setDefaultFontSize($value): bool
{
$value = (int) $value;
if ($value > 0) {
if ((is_int($value) || is_float($value)) && (int) $value > 0) {
self::$defaultFontSize = $value;
return true;
@ -408,12 +366,8 @@ class Settings
/**
* Load setting from phpword.yml or phpword.yml.dist.
*
* @param string $filename
*
* @return array
*/
public static function loadConfig($filename = null)
public static function loadConfig(?string $filename = null): array
{
// Get config file
$configFile = null;
@ -455,24 +409,18 @@ class Settings
/**
* Get default paper.
*
* @return string
*/
public static function getDefaultPaper()
public static function getDefaultPaper(): string
{
return self::$defaultPaper;
}
/**
* Set default paper.
*
* @param string $value
*
* @return bool
*/
public static function setDefaultPaper($value)
public static function setDefaultPaper(string $value): bool
{
if (is_string($value) && trim($value) !== '') {
if (trim($value) !== '') {
self::$defaultPaper = $value;
return true;

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
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
declare(strict_types=1);
namespace PhpOffice\PhpWord\Shared;
class Css
{
/**
* @var string
*/
private $cssContent;
/**
* @var array<string, array<string, string>>
*/
private $styles = [];
public function __construct(string $cssContent)
{
$this->cssContent = $cssContent;
}
public function process(): void
{
$cssContent = str_replace(["\r", "\n"], '', $this->cssContent);
preg_match_all('/(.+?)\s?\{\s?(.+?)\s?\}/', $cssContent, $cssExtracted);
// Check the number of extracted
if (count($cssExtracted) != 3) {
return;
}
// Check if there are x selectors and x rules
if (count($cssExtracted[1]) != count($cssExtracted[2])) {
return;
}
foreach ($cssExtracted[1] as $key => $selector) {
$rules = trim($cssExtracted[2][$key]);
$rules = explode(';', $rules);
foreach ($rules as $rule) {
if (empty($rule)) {
continue;
}
[$key, $value] = explode(':', trim($rule));
$this->styles[$this->sanitize($selector)][$this->sanitize($key)] = $this->sanitize($value);
}
}
}
public function getStyles(): array
{
return $this->styles;
}
public function getStyle(string $selector): array
{
$selector = $this->sanitize($selector);
return $this->styles[$selector] ?? [];
}
private function sanitize(string $value): string
{
return addslashes(trim($value));
}
}

View File

@ -43,6 +43,11 @@ class Html
protected static $options;
/**
* @var Css
*/
protected static $css;
/**
* Add HTML parts.
*
@ -61,7 +66,7 @@ class Html
* @todo parse $stylesheet for default styles. Should result in an array based on id, class and element,
* which could be applied when such an element occurs in the parseNode function.
*/
self::$options = $options;
static::$options = $options;
// Preprocess: remove all line ends, decode HTML entity,
// fix ampersand and angle brackets and add body tag for HTML fragments
@ -82,10 +87,10 @@ class Html
$dom = new DOMDocument();
$dom->preserveWhiteSpace = $preserveWhiteSpace;
$dom->loadXML($html);
self::$xpath = new DOMXPath($dom);
static::$xpath = new DOMXPath($dom);
$node = $dom->getElementsByTagName('body');
self::parseNode($node->item(0), $element);
static::parseNode($node->item(0), $element);
if (\PHP_VERSION_ID < 80000) {
libxml_disable_entity_loader($orignalLibEntityLoader);
}
@ -149,6 +154,19 @@ class Html
}
}
$attributeIdentifier = $attributes->getNamedItem('id');
if ($attributeIdentifier && self::$css) {
$styles = self::parseStyleDeclarations(self::$css->getStyle('#' . $attributeIdentifier->value), $styles);
}
$attributeClass = $attributes->getNamedItem('class');
if ($attributeClass) {
if (self::$css) {
$styles = self::parseStyleDeclarations(self::$css->getStyle('.' . $attributeClass->value), $styles);
}
$styles['className'] = $attributeClass->value;
}
$attributeStyle = $attributes->getNamedItem('style');
if ($attributeStyle) {
$styles = self::parseStyle($attributeStyle, $styles);
@ -168,6 +186,13 @@ class Html
*/
protected static function parseNode($node, $element, $styles = [], $data = []): void
{
if ($node->nodeName == 'style') {
self::$css = new Css($node->textContent);
self::$css->process();
return;
}
// Populate styles array
$styleTypes = ['font', 'paragraph', 'list', 'table', 'row', 'cell'];
foreach ($styleTypes as $styleType) {
@ -389,6 +414,11 @@ class Html
$newElement = $element->addTable($elementStyles);
// Add style name from CSS Class
if (isset($elementStyles['className'])) {
$newElement->getStyle()->setStyleName($elementStyles['className']);
}
$attributes = $node->attributes;
if ($attributes->getNamedItem('border') !== null) {
$border = (int) $attributes->getNamedItem('border')->value;
@ -414,7 +444,11 @@ class Html
$rowStyles['tblHeader'] = true;
}
return $element->addRow(null, $rowStyles);
// set cell height to control row heights
$height = $rowStyles['height'] ?? null;
unset($rowStyles['height']); // would not apply
return $element->addRow($height, $rowStyles);
}
/**
@ -635,13 +669,21 @@ class Html
{
$properties = explode(';', trim($attribute->value, " \t\n\r\0\x0B;"));
$selectors = [];
foreach ($properties as $property) {
[$cKey, $cValue] = array_pad(explode(':', $property, 2), 2, null);
$cValue = trim($cValue ?? '');
$cKey = strtolower(trim($cKey));
switch ($cKey) {
$selectors[strtolower(trim($cKey))] = trim($cValue ?? '');
}
return self::parseStyleDeclarations($selectors, $styles);
}
protected static function parseStyleDeclarations(array $selectors, array $styles)
{
foreach ($selectors as $property => $value) {
switch ($property) {
case 'text-decoration':
switch ($cValue) {
switch ($value) {
case 'underline':
$styles['underline'] = 'single';
@ -654,44 +696,44 @@ class Html
break;
case 'text-align':
$styles['alignment'] = self::mapAlign($cValue);
$styles['alignment'] = self::mapAlign($value);
break;
case 'display':
$styles['hidden'] = $cValue === 'none' || $cValue === 'hidden';
$styles['hidden'] = $value === 'none' || $value === 'hidden';
break;
case 'direction':
$styles['rtl'] = $cValue === 'rtl';
$styles['rtl'] = $value === 'rtl';
break;
case 'font-size':
$styles['size'] = Converter::cssToPoint($cValue);
$styles['size'] = Converter::cssToPoint($value);
break;
case 'font-family':
$cValue = array_map('trim', explode(',', $cValue));
$styles['name'] = ucwords($cValue[0]);
$value = array_map('trim', explode(',', $value));
$styles['name'] = ucwords($value[0]);
break;
case 'color':
$styles['color'] = trim($cValue, '#');
$styles['color'] = trim($value, '#');
break;
case 'background-color':
$styles['bgColor'] = trim($cValue, '#');
$styles['bgColor'] = trim($value, '#');
break;
case 'line-height':
$matches = [];
if ($cValue === 'normal') {
if ($value === 'normal') {
$spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO;
$spacing = 0;
} elseif (preg_match('/([0-9]+\.?[0-9]*[a-z]+)/', $cValue, $matches)) {
} elseif (preg_match('/([0-9]+\.?[0-9]*[a-z]+)/', $value, $matches)) {
//matches number with a unit, e.g. 12px, 15pt, 20mm, ...
$spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::EXACT;
$spacing = Converter::cssToTwip($matches[1]);
} elseif (preg_match('/([0-9]+)%/', $cValue, $matches)) {
} elseif (preg_match('/([0-9]+)%/', $value, $matches)) {
//matches percentages
$spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO;
//we are subtracting 1 line height because the Spacing writer is adding one line
@ -700,23 +742,23 @@ class Html
//any other, wich is a multiplier. E.g. 1.2
$spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO;
//we are subtracting 1 line height because the Spacing writer is adding one line
$spacing = ($cValue * Paragraph::LINE_HEIGHT) - Paragraph::LINE_HEIGHT;
$spacing = ($value * Paragraph::LINE_HEIGHT) - Paragraph::LINE_HEIGHT;
}
$styles['spacingLineRule'] = $spacingLineRule;
$styles['line-spacing'] = $spacing;
break;
case 'letter-spacing':
$styles['letter-spacing'] = Converter::cssToTwip($cValue);
$styles['letter-spacing'] = Converter::cssToTwip($value);
break;
case 'text-indent':
$styles['indentation']['firstLine'] = Converter::cssToTwip($cValue);
$styles['indentation']['firstLine'] = Converter::cssToTwip($value);
break;
case 'font-weight':
$tValue = false;
if (preg_match('#bold#', $cValue)) {
if (preg_match('#bold#', $value)) {
$tValue = true; // also match bolder
}
$styles['bold'] = $tValue;
@ -724,52 +766,57 @@ class Html
break;
case 'font-style':
$tValue = false;
if (preg_match('#(?:italic|oblique)#', $cValue)) {
if (preg_match('#(?:italic|oblique)#', $value)) {
$tValue = true;
}
$styles['italic'] = $tValue;
break;
case 'margin':
$cValue = Converter::cssToTwip($cValue);
$styles['spaceBefore'] = $cValue;
$styles['spaceAfter'] = $cValue;
$value = Converter::cssToTwip($value);
$styles['spaceBefore'] = $value;
$styles['spaceAfter'] = $value;
break;
case 'margin-top':
// BC change: up to ver. 0.17.0 incorrectly converted to points - Converter::cssToPoint($cValue)
$styles['spaceBefore'] = Converter::cssToTwip($cValue);
// BC change: up to ver. 0.17.0 incorrectly converted to points - Converter::cssToPoint($value)
$styles['spaceBefore'] = Converter::cssToTwip($value);
break;
case 'margin-bottom':
// BC change: up to ver. 0.17.0 incorrectly converted to points - Converter::cssToPoint($cValue)
$styles['spaceAfter'] = Converter::cssToTwip($cValue);
// BC change: up to ver. 0.17.0 incorrectly converted to points - Converter::cssToPoint($value)
$styles['spaceAfter'] = Converter::cssToTwip($value);
break;
case 'border-color':
self::mapBorderColor($styles, $cValue);
self::mapBorderColor($styles, $value);
break;
case 'border-width':
$styles['borderSize'] = Converter::cssToPoint($cValue);
$styles['borderSize'] = Converter::cssToPoint($value);
break;
case 'border-style':
$styles['borderStyle'] = self::mapBorderStyle($cValue);
$styles['borderStyle'] = self::mapBorderStyle($value);
break;
case 'width':
if (preg_match('/([0-9]+[a-z]+)/', $cValue, $matches)) {
if (preg_match('/([0-9]+[a-z]+)/', $value, $matches)) {
$styles['width'] = Converter::cssToTwip($matches[1]);
$styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::TWIP;
} elseif (preg_match('/([0-9]+)%/', $cValue, $matches)) {
} elseif (preg_match('/([0-9]+)%/', $value, $matches)) {
$styles['width'] = $matches[1] * 50;
$styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::PERCENT;
} elseif (preg_match('/([0-9]+)/', $cValue, $matches)) {
} elseif (preg_match('/([0-9]+)/', $value, $matches)) {
$styles['width'] = $matches[1];
$styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::AUTO;
}
break;
case 'height':
$styles['height'] = Converter::cssToTwip($value);
$styles['exactHeight'] = true;
break;
case 'border':
case 'border-top':
@ -778,9 +825,9 @@ class Html
case 'border-left':
// must have exact order [width color style], e.g. "1px #0011CC solid" or "2pt green solid"
// Word does not accept shortened hex colors e.g. #CCC, only full e.g. #CCCCCC
if (preg_match('/([0-9]+[^0-9]*)\s+(\#[a-fA-F0-9]+|[a-zA-Z]+)\s+([a-z]+)/', $cValue, $matches)) {
if (false !== strpos($cKey, '-')) {
$tmp = explode('-', $cKey);
if (preg_match('/([0-9]+[^0-9]*)\s+(\#[a-fA-F0-9]+|[a-zA-Z]+)\s+([a-z]+)/', $value, $matches)) {
if (false !== strpos($property, '-')) {
$tmp = explode('-', $property);
$which = $tmp[1];
$which = ucfirst($which); // e.g. bottom -> Bottom
} else {
@ -803,13 +850,13 @@ class Html
break;
case 'vertical-align':
// https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align
if (preg_match('#(?:top|bottom|middle|sub|baseline)#i', $cValue, $matches)) {
if (preg_match('#(?:top|bottom|middle|sub|baseline)#i', $value, $matches)) {
$styles['valign'] = self::mapAlignVertical($matches[0]);
}
break;
case 'page-break-after':
if ($cValue == 'always') {
if ($value == 'always') {
$styles['isPageBreak'] = true;
}

View File

@ -286,7 +286,7 @@ class Cell extends Border
*/
public function setWidth($value)
{
$this->setIntVal($value);
$this->width = $this->setIntVal($value);
return $this;
}

View File

@ -70,6 +70,9 @@ final class Language extends AbstractStyle
const NL_NL = 'nl-NL';
const NL_NL_ID = 1043;
const SV_SE = 'sv-SE';
const SV_SE_ID = 1053;
const UK_UA = 'uk-UA';
const UK_UA_ID = 1058;

View File

@ -2,10 +2,8 @@
/**
* 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.
@ -27,121 +25,128 @@ class TextBox extends Image
/**
* margin top.
*
* @var int
* @var null|int
*/
private $innerMarginTop;
/**
* margin left.
*
* @var int
* @var null|int
*/
private $innerMarginLeft;
/**
* margin right.
*
* @var int
* @var null|int
*/
private $innerMarginRight;
/**
* Cell margin bottom.
*
* @var int
* @var null|int
*/
private $innerMarginBottom;
/**
* border size.
*
* @var int
* @var null|int
*/
private $borderSize;
/**
* border color.
*
* @var string
* @var null|string
*/
private $borderColor;
/**
* Set margin top.
* background color.
*
* @param int $value
* @var null|string
*/
public function setInnerMarginTop($value = null): void
private $bgColor;
/**
* Set background color.
*/
public function setBgColor(?string $value = null): void
{
$this->bgColor = $value;
}
/**
* Get background color.
*/
public function getBgColor(): ?string
{
return $this->bgColor;
}
/**
* Set margin top.
*/
public function setInnerMarginTop(?int $value = null): void
{
$this->innerMarginTop = $value;
}
/**
* Get margin top.
*
* @return int
*/
public function getInnerMarginTop()
public function getInnerMarginTop(): ?int
{
return $this->innerMarginTop;
}
/**
* Set margin left.
*
* @param int $value
*/
public function setInnerMarginLeft($value = null): void
public function setInnerMarginLeft(?int $value = null): void
{
$this->innerMarginLeft = $value;
}
/**
* Get margin left.
*
* @return int
*/
public function getInnerMarginLeft()
public function getInnerMarginLeft(): ?int
{
return $this->innerMarginLeft;
}
/**
* Set margin right.
*
* @param int $value
*/
public function setInnerMarginRight($value = null): void
public function setInnerMarginRight(?int $value = null): void
{
$this->innerMarginRight = $value;
}
/**
* Get margin right.
*
* @return int
*/
public function getInnerMarginRight()
public function getInnerMarginRight(): ?int
{
return $this->innerMarginRight;
}
/**
* Set margin bottom.
*
* @param int $value
*/
public function setInnerMarginBottom($value = null): void
public function setInnerMarginBottom(?int $value = null): void
{
$this->innerMarginBottom = $value;
}
/**
* Get margin bottom.
*
* @return int
*/
public function getInnerMarginBottom()
public function getInnerMarginBottom(): ?int
{
return $this->innerMarginBottom;
}
@ -149,9 +154,9 @@ class TextBox extends Image
/**
* Set TLRB cell margin.
*
* @param int $value Margin in twips
* @param null|int $value Margin in twips
*/
public function setInnerMargin($value = null): void
public function setInnerMargin(?int $value = null): void
{
$this->setInnerMarginTop($value);
$this->setInnerMarginLeft($value);
@ -164,17 +169,15 @@ class TextBox extends Image
*
* @return int[]
*/
public function getInnerMargin()
public function getInnerMargin(): array
{
return [$this->innerMarginLeft, $this->innerMarginTop, $this->innerMarginRight, $this->innerMarginBottom];
}
/**
* Has inner margin?
*
* @return bool
*/
public function hasInnerMargins()
public function hasInnerMargins(): bool
{
$hasInnerMargins = false;
$margins = $this->getInnerMargin();
@ -191,39 +194,33 @@ class TextBox extends Image
/**
* Set border size.
*
* @param int $value Size in points
* @param null|int $value Size in points
*/
public function setBorderSize($value = null): void
public function setBorderSize(?int $value = null): void
{
$this->borderSize = $value;
}
/**
* Get border size.
*
* @return int
*/
public function getBorderSize()
public function getBorderSize(): ?int
{
return $this->borderSize;
}
/**
* Set border color.
*
* @param string $value
*/
public function setBorderColor($value = null): void
public function setBorderColor(?string $value = null): void
{
$this->borderColor = $value;
}
/**
* Get border color.
*
* @return string
*/
public function getBorderColor()
public function getBorderColor(): ?string
{
return $this->borderColor;
}

View File

@ -94,6 +94,10 @@ class TemplateProcessor
*/
protected $tempDocumentNewImages = [];
protected static $macroOpeningChars = '${';
protected static $macroClosingChars = '}';
/**
* @since 0.12.0 Throws CreateTemporaryFileException and CopyFileException instead of Exception
*
@ -238,8 +242,8 @@ class TemplateProcessor
*/
protected static function ensureMacroCompleted($macro)
{
if (substr($macro, 0, 2) !== '${' && substr($macro, -1) !== '}') {
$macro = '${' . $macro . '}';
if (substr($macro, 0, 2) !== self::$macroOpeningChars && substr($macro, -1) !== self::$macroClosingChars) {
$macro = self::$macroOpeningChars . $macro . self::$macroClosingChars;
}
return $macro;
@ -759,6 +763,70 @@ class TemplateProcessor
$this->tempDocumentMainPart = $result;
}
/**
* Delete a table row in a template document.
*/
public function deleteRow(string $search): void
{
if ('${' !== substr($search, 0, 2) && '}' !== substr($search, -1)) {
$search = '${' . $search . '}';
}
$tagPos = strpos($this->tempDocumentMainPart, $search);
if (!$tagPos) {
throw new Exception(sprintf('Can not delete row %s, template variable not found or variable contains markup.', $search));
}
$tableStart = $this->findTableStart($tagPos);
$tableEnd = $this->findTableEnd($tagPos);
$xmlTable = $this->getSlice($tableStart, $tableEnd);
if (substr_count($xmlTable, '<w:tr') === 1) {
$this->tempDocumentMainPart = $this->getSlice(0, $tableStart) . $this->getSlice($tableEnd);
return;
}
$rowStart = $this->findRowStart($tagPos);
$rowEnd = $this->findRowEnd($tagPos);
$xmlRow = $this->getSlice($rowStart, $rowEnd);
$this->tempDocumentMainPart = $this->getSlice(0, $rowStart) . $this->getSlice($rowEnd);
// Check if there's a cell spanning multiple rows.
if (preg_match('#<w:vMerge w:val="restart"/>#', $xmlRow)) {
$extraRowStart = $rowStart;
while (true) {
$extraRowStart = $this->findRowStart($extraRowStart + 1);
$extraRowEnd = $this->findRowEnd($extraRowStart + 1);
// If extraRowEnd is lower then 7, there was no next row found.
if ($extraRowEnd < 7) {
break;
}
// If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row.
$tmpXmlRow = $this->getSlice($extraRowStart, $extraRowEnd);
if (!preg_match('#<w:vMerge/>#', $tmpXmlRow) &&
!preg_match('#<w:vMerge w:val="continue" />#', $tmpXmlRow)
) {
break;
}
$tableStart = $this->findTableStart($extraRowEnd + 1);
$tableEnd = $this->findTableEnd($extraRowEnd + 1);
$xmlTable = $this->getSlice($tableStart, $tableEnd);
if (substr_count($xmlTable, '<w:tr') === 1) {
$this->tempDocumentMainPart = $this->getSlice(0, $tableStart) . $this->getSlice($tableEnd);
return;
}
$this->tempDocumentMainPart = $this->getSlice(0, $extraRowStart) . $this->getSlice($extraRowEnd);
}
}
}
/**
* Clones a table row and populates it's values from a two-dimensional array in a template document.
*
@ -792,8 +860,12 @@ class TemplateProcessor
{
$xmlBlock = null;
$matches = [];
$escapedMacroOpeningChars = self::$macroOpeningChars;
$escapedMacroClosingChars = self::$macroClosingChars;
preg_match(
'/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\${' . $blockname . '}<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\${\/' . $blockname . '}<\/w:.*?p>)/is',
//'/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\{{' . $blockname . '}<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\{{\/' . $blockname . '}<\/w:.*?p>)/is',
'/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\\' . $escapedMacroOpeningChars . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\\' . $escapedMacroOpeningChars . '\/' . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>)/is',
//'/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\\'. $escapedMacroOpeningChars . $blockname . '}<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\\'.$escapedMacroOpeningChars.'\/' . $blockname . '}<\/w:.*?p>)/is',
$this->tempDocumentMainPart,
$matches
);
@ -832,8 +904,10 @@ class TemplateProcessor
public function replaceBlock($blockname, $replacement): void
{
$matches = [];
$escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars);
$escapedMacroClosingChars = preg_quote(self::$macroClosingChars);
preg_match(
'/(<\?xml.*)(<w:p.*>\${' . $blockname . '}<\/w:.*?p>)(.*)(<w:p.*\${\/' . $blockname . '}<\/w:.*?p>)/is',
'/(<\?xml.*)(<w:p.*>' . $escapedMacroOpeningChars . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>)(.*)(<w:p.*' . $escapedMacroOpeningChars . '\/' . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>)/is',
$this->tempDocumentMainPart,
$matches
);
@ -949,8 +1023,12 @@ class TemplateProcessor
*/
protected function fixBrokenMacros($documentPart)
{
$brokenMacroOpeningChars = substr(self::$macroOpeningChars, 0, 1);
$endMacroOpeningChars = substr(self::$macroOpeningChars, 1);
$macroClosingChars = self::$macroClosingChars;
return preg_replace_callback(
'/\$(?:\{|[^{$]*\>\{)[^}$]*\}/U',
'/\\' . $brokenMacroOpeningChars . '(?:\\' . $endMacroOpeningChars . '|[^{$]*\>\{)[^' . $macroClosingChars . '$]*\}/U',
function ($match) {
return strip_tags($match[0]);
},
@ -963,7 +1041,7 @@ class TemplateProcessor
*
* @param mixed $search
* @param mixed $replace
* @param string $documentPartXML
* @param array<int, string>|string $documentPartXML
* @param int $limit
*
* @return string
@ -989,7 +1067,10 @@ class TemplateProcessor
protected function getVariablesForPart($documentPartXML)
{
$matches = [];
preg_match_all('/\$\{(.*?)}/i', $documentPartXML, $matches);
$escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars);
$escapedMacroClosingChars = preg_quote(self::$macroClosingChars);
preg_match_all("/$escapedMacroOpeningChars(.*?)$escapedMacroClosingChars/i", $documentPartXML, $matches);
return $matches[1];
}
@ -1079,6 +1160,39 @@ class TemplateProcessor
return '[Content_Types].xml';
}
/**
* Find the start position of the nearest table before $offset.
*/
private function findTableStart(int $offset): int
{
$rowStart = strrpos(
$this->tempDocumentMainPart,
'<w:tbl ',
((strlen($this->tempDocumentMainPart) - $offset) * -1)
);
if (!$rowStart) {
$rowStart = strrpos(
$this->tempDocumentMainPart,
'<w:tbl>',
((strlen($this->tempDocumentMainPart) - $offset) * -1)
);
}
if (!$rowStart) {
throw new Exception('Can not find the start position of the table.');
}
return $rowStart;
}
/**
* Find the end position of the nearest table row after $offset.
*/
private function findTableEnd(int $offset): int
{
return strpos($this->tempDocumentMainPart, '</w:tbl>', $offset) + 7;
}
/**
* Find the start position of the nearest table row before $offset.
*
@ -1141,8 +1255,11 @@ class TemplateProcessor
protected function indexClonedVariables($count, $xmlBlock)
{
$results = [];
$escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars);
$escapedMacroClosingChars = preg_quote(self::$macroClosingChars);
for ($i = 1; $i <= $count; ++$i) {
$results[] = preg_replace('/\$\{([^:]*?)(:.*?)?\}/', '\${\1#' . $i . '\2}', $xmlBlock);
$results[] = preg_replace("/$escapedMacroOpeningChars([^:]*?)(:.*?)?$escapedMacroClosingChars/", self::$macroOpeningChars . '\1#' . $i . '\2' . self::$macroClosingChars, $xmlBlock);
}
return $results;
@ -1297,7 +1414,7 @@ class TemplateProcessor
}
$unformattedText = preg_replace('/>\s+</', '><', $text);
$result = str_replace(['${', '}'], ['</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);
$result = str_replace([self::$macroOpeningChars, self::$macroClosingChars], ['</w:t></w:r><w:r>' . $extractedStyle . '<w:t xml:space="preserve">' . self::$macroOpeningChars, self::$macroClosingChars . '</w:t></w:r><w:r>' . $extractedStyle . '<w:t xml:space="preserve">'], $unformattedText);
return str_replace(['<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>'], ['', '', '<w:t xml:space="preserve">'], $result);
}
@ -1311,6 +1428,25 @@ class TemplateProcessor
*/
protected function textNeedsSplitting($text)
{
return preg_match('/[^>]\${|}[^<]/i', $text) == 1;
$escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars);
$escapedMacroClosingChars = preg_quote(self::$macroClosingChars);
return 1 === preg_match('/[^>]' . $escapedMacroOpeningChars . '|' . $escapedMacroClosingChars . '[^<]/i', $text);
}
public function setMacroOpeningChars(string $macroOpeningChars): void
{
self::$macroOpeningChars = $macroOpeningChars;
}
public function setMacroClosingChars(string $macroClosingChars): void
{
self::$macroClosingChars = $macroClosingChars;
}
public function setMacroChars(string $macroOpeningChars, string $macroClosingChars): void
{
self::$macroOpeningChars = $macroOpeningChars;
self::$macroClosingChars = $macroClosingChars;
}
}

View File

@ -2,10 +2,8 @@
/**
* 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.
@ -50,6 +48,10 @@ class TextBox extends Image
$xmlWriter->startElement('v:shape');
$xmlWriter->writeAttribute('type', '#_x0000_t0202');
if ($style->getBgColor()) {
$xmlWriter->writeAttribute('fillcolor', $style->getBgColor());
}
$styleWriter->write();
$styleWriter->writeBorder();

View File

@ -2,10 +2,8 @@
/**
* 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.

View File

@ -2,10 +2,8 @@
/**
* 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.
@ -19,15 +17,15 @@ namespace PhpOffice\PhpWordTests;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Settings;
use PHPUnit\Framework\TestCase;
/**
* Test class for PhpOffice\PhpWord\Settings.
*
* @coversDefaultClass \PhpOffice\PhpWord\Settings
*
* @runTestsInSeparateProcesses
*/
class SettingsTest extends \PHPUnit\Framework\TestCase
class SettingsTest extends TestCase
{
private $compatibility;
@ -151,7 +149,6 @@ class SettingsTest extends \PHPUnit\Framework\TestCase
/**
* @covers ::getTempDir
* @covers ::setTempDir
*
* @depends testPhpTempDirIsUsedByDefault
*/
public function testTempDirCanBeSet(): void
@ -189,6 +186,12 @@ class SettingsTest extends \PHPUnit\Framework\TestCase
self::assertEquals(12, Settings::getDefaultFontSize());
self::assertFalse(Settings::setDefaultFontSize(null));
self::assertEquals(12, Settings::getDefaultFontSize());
self::assertTrue(Settings::setDefaultFontSize(12.5));
self::assertEquals(12.5, Settings::getDefaultFontSize());
self::assertFalse(Settings::setDefaultFontSize(0.5));
self::assertEquals(12.5, Settings::getDefaultFontSize());
self::assertFalse(Settings::setDefaultFontSize(0));
self::assertEquals(12.5, Settings::getDefaultFontSize());
}
/**

View File

@ -0,0 +1,54 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWordTests\Shared;
use PhpOffice\PhpWord\Shared\Css;
use PHPUnit\Framework\TestCase;
/**
* Test class for PhpOffice\PhpWord\Shared\Css.
*/
class CssTest extends TestCase
{
public function testEmptyCss(): void
{
$css = new Css('');
$css->process();
self::assertEquals([], $css->getStyles());
}
public function testBasicCss(): void
{
$cssContent = '.pStyle {
font-size:15px;
}';
$css = new Css($cssContent);
$css->process();
self::assertEquals([
'.pStyle' => [
'font-size' => '15px',
],
], $css->getStyles());
self::assertEquals([
'font-size' => '15px',
], $css->getStyle('.pStyle'));
}
}

View File

@ -19,6 +19,7 @@ namespace PhpOffice\PhpWordTests\Shared;
use Exception;
use PhpOffice\PhpWord\Element\Section;
use PhpOffice\PhpWord\Element\Table;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Shared\Html;
use PhpOffice\PhpWord\SimpleType\Jc;
@ -107,6 +108,44 @@ class HtmlTest extends AbstractWebServerEmbeddedTest
self::assertEquals('text with entities <my text>', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->nodeValue);
}
public function testParseStyle(): void
{
$html = '<style type="text/css">
.pStyle {
font-size:15px;
}
.tableStyle {
width:100%;
background-color:red;
}
</style>
<p class="pStyle">Calculator</p>';
$phpWord = new PhpWord();
$section = $phpWord->addSection();
Html::addHtml($section, $html);
$doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]'));
self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r'));
self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:t'));
self::assertEquals('Calculator', $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:t')->nodeValue);
self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:rPr'));
self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:rPr/w:sz'));
self::assertEquals('22.5', $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:r/w:rPr/w:sz', 'w:val'));
}
public function testParseStyleTableClassName(): void
{
$html = '<style type="text/css">.pStyle { font-size:15px; }</style><table class="pStyle"><tr><td></td></tr></table>';
$phpWord = new PhpWord();
$section = $phpWord->addSection();
Html::addHtml($section, $html);
self::assertInstanceOf(Table::class, $section->getElement(0));
self::assertEquals('pStyle', $section->getElement(0)->getStyle()->getStyleName());
}
/**
* Test underline.
*/
@ -425,6 +464,58 @@ HTML;
self::assertEquals('dxa', $doc->getElement($xpath)->getAttribute('w:type'));
}
/**
* Parse heights in rows, which also allows for controlling column height.
*/
public function testParseTableRowHeight(): void
{
$phpWord = new PhpWord();
$section = $phpWord->addSection([
'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE,
]);
$html = <<<HTML
<table>
<tr style="height: 100px;">
<td>100px</td>
</tr>
<tr style="height: 200pt;">
<td>200pt</td>
</tr>
<tr>
<td>
<table>
<tr style="height: 300px;">
<td>300px</td>
</tr>
</table>
</td>
</tr>
</table>
HTML;
Html::addHtml($section, $html);
$doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
// <tr style="height: 100; ... 100px = 1500 twips (100 / 96 * 1440)
$xpath = '/w:document/w:body/w:tbl/w:tr/w:trPr/w:trHeight';
self::assertTrue($doc->elementExists($xpath));
self::assertEquals(1500, $doc->getElement($xpath)->getAttribute('w:val'));
self::assertEquals('exact', $doc->getElement($xpath)->getAttribute('w:hRule'));
// <tr style="height: 200pt; ... 200pt = 4000 twips (200 / 72 * 1440)
$xpath = '/w:document/w:body/w:tbl/w:tr[2]/w:trPr/w:trHeight';
self::assertTrue($doc->elementExists($xpath));
self::assertEquals(4000, $doc->getElement($xpath)->getAttribute('w:val'));
self::assertEquals('exact', $doc->getElement($xpath)->getAttribute('w:hRule'));
// <tr style="width: 300; .. 300px = 4500 twips (300 / 72 * 1440)
$xpath = '/w:document/w:body/w:tbl/w:tr[3]/w:tc/w:tbl/w:tr/w:trPr/w:trHeight';
self::assertTrue($doc->elementExists($xpath));
self::assertEquals(4500, $doc->getElement($xpath)->getAttribute('w:val'));
self::assertEquals('exact', $doc->getElement($xpath)->getAttribute('w:hRule'));
}
/**
* Test parsing table (attribute border).
*/

View File

@ -2,10 +2,8 @@
/**
* 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.
@ -20,15 +18,15 @@ namespace PhpOffice\PhpWordTests\Style;
use InvalidArgumentException;
use PhpOffice\PhpWord\SimpleType\Jc;
use PhpOffice\PhpWord\Style\TextBox;
use PHPUnit\Framework\TestCase;
/**
* Test class for PhpOffice\PhpWord\Style\Image.
*
* @coversDefaultClass \PhpOffice\PhpWord\Style\Image
*
* @runTestsInSeparateProcesses
*/
class TextBoxTest extends \PHPUnit\Framework\TestCase
class TextBoxTest extends TestCase
{
/**
* Test setting style with normal value.
@ -55,6 +53,7 @@ class TextBoxTest extends \PHPUnit\Framework\TestCase
'innerMarginLeft' => '5',
'borderSize' => '2',
'borderColor' => 'red',
'bgColor' => 'blue',
];
foreach ($properties as $key => $value) {
$set = "set{$key}";
@ -89,6 +88,7 @@ class TextBoxTest extends \PHPUnit\Framework\TestCase
'innerMarginLeft' => '5',
'borderSize' => '2',
'borderColor' => 'red',
'bgColor' => 'blue',
];
foreach ($properties as $key => $value) {
$get = "get{$key}";
@ -305,4 +305,15 @@ class TextBoxTest extends \PHPUnit\Framework\TestCase
$object->setBorderColor($expected);
self::assertEquals($expected, $object->getBorderColor());
}
/**
* Test set/get bgColor.
*/
public function testSetGetBgColor(): void
{
$expected = 'blue';
$object = new TextBox();
$object->setBgColor($expected);
self::assertEquals($expected, $object->getBgColor());
}
}

View File

@ -182,6 +182,32 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
@$templateProcessor->applyXslStyleSheet($xslDomDocument);
}
/**
* @covers ::deleteRow
* @covers ::getVariables
* @covers ::saveAs
*/
public function testDeleteRow(): void
{
$templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/delete-row.docx');
self::assertEquals(
['deleteMe', 'deleteMeToo'],
$templateProcessor->getVariables()
);
$docName = 'delete-row-test-result.docx';
$templateProcessor->deleteRow('deleteMe');
self::assertEquals(
[],
$templateProcessor->getVariables()
);
$templateProcessor->saveAs($docName);
$docFound = file_exists($docName);
unlink($docName);
self::assertTrue($docFound);
}
/**
* @covers ::cloneRow
* @covers ::saveAs
@ -206,6 +232,33 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
self::assertTrue($docFound);
}
/**
* @covers ::cloneRow
* @covers ::saveAs
* @covers ::setValue
*/
public function testCloneRowWithCustomMacro(): void
{
$templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/clone-merge-with-custom-macro.docx');
$templateProcessor->setMacroOpeningChars('{#');
$templateProcessor->setMacroClosingChars('#}');
self::assertEquals(
['tableHeader', 'userId', 'userName', 'userLocation'],
$templateProcessor->getVariables()
);
$docName = 'clone-test-result.docx';
$templateProcessor->setValue('tableHeader', utf8_decode('ééé'));
$templateProcessor->cloneRow('userId', 1);
$templateProcessor->setValue('userId#1', 'Test');
$templateProcessor->saveAs($docName);
$docFound = file_exists($docName);
unlink($docName);
self::assertTrue($docFound);
}
/**
* @covers ::cloneRow
* @covers ::saveAs
@ -275,6 +328,68 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
$templateProcessor->cloneRow('fake_search', 2);
}
/**
* @covers ::cloneRow
* @covers ::saveAs
* @covers ::setValue
*/
public function testCloneRowAndSetValuesWithCustomMacro(): void
{
$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);
$templateProcessor->setMacroOpeningChars('{{');
$templateProcessor->setMacroClosingChars('}}');
self::assertEquals(
['userId', 'userName', 'userLocation'],
$templateProcessor->getVariables()
);
$values = [
['userId' => 1, 'userName' => 'Batman', 'userLocation' => 'Gotham City'],
['userId' => 2, 'userName' => 'Superman', 'userLocation' => 'Metropolis'],
];
$templateProcessor->setValue('tableHeader', 'My clonable table');
$templateProcessor->cloneRowAndSetValues('userId', $values);
self::assertStringContainsString('<w:t>Superman</w:t>', $templateProcessor->getMainPart());
self::assertStringContainsString('<w:t>Metropolis</w:t>', $templateProcessor->getMainPart());
}
/**
* @covers ::saveAs
* @covers ::setValue
@ -296,6 +411,29 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
self::assertTrue($docFound);
}
/**
* @covers ::saveAs
* @covers ::setValue
*/
public function testCustomMacrosCanBeReplacedInHeaderAndFooter(): void
{
$templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/header-footer-with-custom-macro.docx');
$templateProcessor->setMacroOpeningChars('{{');
$templateProcessor->setMacroClosingChars('}}');
self::assertEquals(['documentContent', 'headerValue:100:100', 'footerValue'], $templateProcessor->getVariables());
$macroNames = ['headerValue', 'documentContent', 'footerValue'];
$macroValues = ['Header Value', 'Document text.', 'Footer Value'];
$templateProcessor->setValue($macroNames, $macroValues);
$docName = 'header-footer-test-result.docx';
$templateProcessor->saveAs($docName);
$docFound = file_exists($docName);
unlink($docName);
self::assertTrue($docFound);
}
/**
* @covers ::setValue
*/
@ -311,6 +449,22 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
);
}
/**
* @covers ::setValue
*/
public function testSetValueWithCustomMacro(): void
{
$templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/clone-merge-with-custom-macro.docx');
$templateProcessor->setMacroChars('{#', '#}');
Settings::setOutputEscapingEnabled(true);
$helloworld = "hello\nworld";
$templateProcessor->setValue('userName', $helloworld);
self::assertEquals(
['tableHeader', 'userId', 'userLocation'],
$templateProcessor->getVariables()
);
}
public function testSetComplexValue(): void
{
$title = new TextRun();
@ -364,6 +518,60 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
self::assertEquals(preg_replace('/>\s+</', '><', $result), preg_replace('/>\s+</', '><', $templateProcessor->getMainPart()));
}
public function testSetComplexValueWithCustomMacro(): void
{
$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->setMacroChars('{{', '}}');
$templateProcessor->setComplexBlock('document-title', $title);
$templateProcessor->setComplexValue('firstname', $firstname);
$templateProcessor->setComplexValue('lastname', $lastname);
self::assertEquals(preg_replace('/>\s+</', '><', $result), preg_replace('/>\s+</', '><', $templateProcessor->getMainPart()));
}
/**
* @covers ::setValues
*/
@ -382,6 +590,25 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
self::assertStringContainsString('Hello John Doe', $templateProcessor->getMainPart());
}
/**
* @covers ::setValues
*/
public function testSetValuesWithCustomMacro(): void
{
$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->setMacroChars('{#', '#}');
$templateProcessor->setValues(['firstname' => 'John', 'lastname' => 'Doe']);
self::assertStringContainsString('Hello John Doe', $templateProcessor->getMainPart());
}
/**
* @covers ::setImageValue
*/
@ -521,6 +748,44 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
);
}
/**
* @covers ::getVariableCount
*/
public function testGetVariableCountCountsHowManyTimesEachPlaceholderIsPresentWithCustomMacro(): void
{
// create template with placeholders
$phpWord = new PhpWord();
$section = $phpWord->addSection();
$header = $section->addHeader();
$header->addText('{{a_field_that_is_present_three_times}}');
$footer = $section->addFooter();
$footer->addText('{{a_field_that_is_present_twice}}');
$section2 = $phpWord->addSection();
$section2->addText('
{{a_field_that_is_present_one_time}}
{{a_field_that_is_present_three_times}}
{{a_field_that_is_present_twice}}
{{a_field_that_is_present_three_times}}
');
$objWriter = IOFactory::createWriter($phpWord);
$templatePath = 'test.docx';
$objWriter->save($templatePath);
$templateProcessor = new TemplateProcessor($templatePath);
$templateProcessor->setMacroChars('{{', '}}');
$variableCount = $templateProcessor->getVariableCount();
unlink($templatePath);
self::assertEquals(
[
'a_field_that_is_present_three_times' => 3,
'a_field_that_is_present_twice' => 2,
'a_field_that_is_present_one_time' => 1,
],
$variableCount
);
}
/**
* @covers ::cloneBlock
*/
@ -574,6 +839,61 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
}
}
/**
* @covers ::cloneBlock
*/
public function testCloneBlockCanCloneABlockTwiceWithCustomMacro(): void
{
// create template with placeholders and block
$phpWord = new PhpWord();
$section = $phpWord->addSection();
$documentElements = [
'Title: {{title}}',
'{{subreport}}',
'{{subreport.id}}: {{subreport.text}}. ',
'{{/subreport}}',
];
foreach ($documentElements as $documentElement) {
$section->addText($documentElement);
}
$objWriter = IOFactory::createWriter($phpWord);
$templatePath = 'test.docx';
$objWriter->save($templatePath);
// replace placeholders and save the file
$templateProcessor = new TemplateProcessor($templatePath);
$templateProcessor->setMacroChars('{{', '}}');
$templateProcessor->setValue('title', 'Some title');
$templateProcessor->cloneBlock('subreport', 2);
$templateProcessor->setValue('subreport.id', '123', 1);
$templateProcessor->setValue('subreport.text', 'Some text', 1);
$templateProcessor->setValue('subreport.id', '456', 1);
$templateProcessor->setValue('subreport.text', 'Some other text', 1);
$templateProcessor->saveAs($templatePath);
// assert the block has been cloned twice
// and the placeholders have been replaced correctly
$phpWord = IOFactory::load($templatePath);
$sections = $phpWord->getSections();
/** @var \PhpOffice\PhpWord\Element\TextRun[] $actualElements */
$actualElements = $sections[0]->getElements();
unlink($templatePath);
$expectedElements = [
'Title: Some title',
'123: Some text. ',
'456: Some other text. ',
];
self::assertCount(count($expectedElements), $actualElements);
foreach ($expectedElements as $i => $expectedElement) {
self::assertEquals(
$expectedElement,
$actualElements[$i]->getElement(0)->getText()
);
}
}
/**
* @covers ::cloneBlock
*/
@ -603,6 +923,36 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
self::assertEquals(3, substr_count($templateProcessor->getMainPart(), 'This block will be cloned with ${variable}'));
}
/**
* @covers ::cloneBlock
*/
public function testCloneBlockWithCustomMacro(): void
{
$mainPart = '<?xml version="1.0" encoding="UTF-8"?>
<w:p>
<w:r>
<w:rPr></w:rPr>
<w:t>{{CLONEME}}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t xml:space="preserve">This block will be cloned with {{variable}}</w:t>
</w:r>
</w:p>
<w:p>
<w:r w:rsidRPr="00204FED">
<w:t>{{/CLONEME}}</w:t>
</w:r>
</w:p>';
$templateProcessor = new TestableTemplateProcesor($mainPart);
$templateProcessor->setMacroChars('{{', '}}');
$templateProcessor->cloneBlock('CLONEME', 3);
self::assertEquals(3, substr_count($templateProcessor->getMainPart(), 'This block will be cloned with {{variable}}'));
}
/**
* @covers ::cloneBlock
*/
@ -634,6 +984,38 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
self::assertStringContainsString('Address ${address#3}, Street ${street#3}', $templateProcessor->getMainPart());
}
/**
* @covers ::cloneBlock
*/
public function testCloneBlockWithVariablesAndCustomMacro(): void
{
$mainPart = '<?xml version="1.0" encoding="UTF-8"?>
<w:p>
<w:r>
<w:rPr></w:rPr>
<w:t>{{CLONEME}}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t xml:space="preserve">Address {{address}}, Street {{street}}</w:t>
</w:r>
</w:p>
<w:p>
<w:r w:rsidRPr="00204FED">
<w:t>{{/CLONEME}}</w:t>
</w:r>
</w:p>';
$templateProcessor = new TestableTemplateProcesor($mainPart);
$templateProcessor->setMacroChars('{{', '}}');
$templateProcessor->cloneBlock('CLONEME', 3, true, true);
self::assertStringContainsString('Address {{address#1}}, Street {{street#1}}', $templateProcessor->getMainPart());
self::assertStringContainsString('Address {{address#2}}, Street {{street#2}}', $templateProcessor->getMainPart());
self::assertStringContainsString('Address {{address#3}}, Street {{street#3}}', $templateProcessor->getMainPart());
}
public function testCloneBlockWithVariableReplacements(): void
{
$mainPart = '<?xml version="1.0" encoding="UTF-8"?>
@ -667,6 +1049,40 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
self::assertStringContainsString('City: Rome, Street: Via della Conciliazione', $templateProcessor->getMainPart());
}
public function testCloneBlockWithVariableReplacementsAndCustomMacro(): void
{
$mainPart = '<?xml version="1.0" encoding="UTF-8"?>
<w:p>
<w:r>
<w:rPr></w:rPr>
<w:t>{{CLONEME}}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t xml:space="preserve">City: {{city}}, Street: {{street}}</w:t>
</w:r>
</w:p>
<w:p>
<w:r w:rsidRPr="00204FED">
<w:t>{{/CLONEME}}</w:t>
</w:r>
</w:p>';
$replacements = [
['city' => 'London', 'street' => 'Baker Street'],
['city' => 'New York', 'street' => '5th Avenue'],
['city' => 'Rome', 'street' => 'Via della Conciliazione'],
];
$templateProcessor = new TestableTemplateProcesor($mainPart);
$templateProcessor->setMacroChars('{{', '}}');
$templateProcessor->cloneBlock('CLONEME', 0, true, false, $replacements);
self::assertStringContainsString('City: London, Street: Baker Street', $templateProcessor->getMainPart());
self::assertStringContainsString('City: New York, Street: 5th Avenue', $templateProcessor->getMainPart());
self::assertStringContainsString('City: Rome, Street: Via della Conciliazione', $templateProcessor->getMainPart());
}
/**
* Template macros can be fixed.
*
@ -698,6 +1114,38 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
self::assertEquals('<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>${variable_name}</w:t></w:r>', $fixed);
}
/**
* Template macros can be fixed even with cutome macro.
*
* @covers ::fixBrokenMacros
*/
public function testFixBrokenMacrosWithCustomMacro(): void
{
$templateProcessor = new TestableTemplateProcesor();
$templateProcessor->setMacroChars('{{', '}}');
$fixed = $templateProcessor->fixBrokenMacros('<w:r><w:t>normal text</w:t></w:r>');
self::assertEquals('<w:r><w:t>normal text</w:t></w:r>', $fixed);
$fixed = $templateProcessor->fixBrokenMacros('<w:r><w:t>{{documentContent}}</w:t></w:r>');
self::assertEquals('<w:r><w:t>{{documentContent}}</w:t></w:r>', $fixed);
$fixed = $templateProcessor->fixBrokenMacros('<w:r><w:t>{</w:t><w:t>{documentContent}}</w:t></w:r>');
self::assertEquals('<w:r><w:t>{{documentContent}}</w:t></w:r>', $fixed);
$fixed = $templateProcessor->fixBrokenMacros('<w:r><w:t>$1500</w:t><w:t>{{documentContent}}</w:t></w:r>');
self::assertEquals('<w:r><w:t>$1500</w:t><w:t>{{documentContent}}</w:t></w:r>', $fixed);
$fixed = $templateProcessor->fixBrokenMacros('<w:r><w:t>$1500</w:t><w:t>{</w:t><w:t>{documentContent}}</w:t></w:r>');
self::assertEquals('<w:r><w:t>$1500</w:t><w:t>{{documentContent}}</w:t></w:r>', $fixed);
$fixed = $templateProcessor->fixBrokenMacros('<w:r><w:t>25$ plus some info {hint}</w:t></w:r>');
self::assertEquals('<w:r><w:t>25$ plus some info {hint}</w:t></w:r>', $fixed);
$fixed = $templateProcessor->fixBrokenMacros('<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>');
self::assertEquals('<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>{{variable_name}}</w:t></w:r>', $fixed);
}
/**
* @covers ::getMainPartName
*/
@ -710,6 +1158,19 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
self::assertEquals($variables, $templateProcessor->getVariables());
}
/**
* @covers ::getMainPartName
*/
public function testMainPartNameDetectionWithCustomMacro(): void
{
$templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/document22-with-custom-macro-xml.docx');
$templateProcessor->setMacroOpeningChars('{#');
$templateProcessor->setMacroClosingChars('#}');
$variables = ['test'];
self::assertEquals($variables, $templateProcessor->getVariables());
}
/**
* @covers ::getVariables
*/
@ -727,6 +1188,25 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
self::assertEquals(['variable_name'], $variables);
}
/**
* @covers ::getVariables
*/
public function testGetVariablesWithCustomMacro(): void
{
$templateProcessor = new TestableTemplateProcesor();
$templateProcessor->setMacroOpeningChars('{{');
$templateProcessor->setMacroClosingChars('}}');
$variables = $templateProcessor->getVariablesForPart('<w:r><w:t>normal text</w:t></w:r>');
self::assertEquals([], $variables);
$variables = $templateProcessor->getVariablesForPart('<w:r><w:t>{{documentContent}}</w:t></w:r>');
self::assertEquals(['documentContent'], $variables);
$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>');
self::assertEquals(['variable_name'], $variables);
}
/**
* @covers ::textNeedsSplitting
*/
@ -742,6 +1222,22 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
self::assertFalse($templateProcessor->textNeedsSplitting($splitText));
}
/**
* @covers ::textNeedsSplitting
*/
public function testTextNeedsSplittingWithCustomMacro(): void
{
$templateProcessor = new TestableTemplateProcesor();
$templateProcessor->setMacroChars('{{', '}}');
self::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>';
self::assertTrue($templateProcessor->textNeedsSplitting($text));
$splitText = $templateProcessor->splitTextIntoTexts($text);
self::assertFalse($templateProcessor->textNeedsSplitting($splitText));
}
/**
* @covers ::splitTextIntoTexts
*/
@ -756,6 +1252,21 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
self::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);
}
/**
* @covers ::splitTextIntoTexts
*/
public function testSplitTextIntoTextsWithCustomMacro(): void
{
$templateProcessor = new TestableTemplateProcesor();
$templateProcessor->setMacroChars('{{', '}}');
$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>');
self::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>');
self::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(): void
{
$toFind = '<w:r>
@ -799,6 +1310,50 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
self::assertEquals($toFind, $templateProcessor->getSlice($position['start'], $position['end']));
}
public function testFindXmlBlockStartWithCustomMacro(): void
{
$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);
$templateProcessor->setMacroChars('{{', '}}');
$position = $templateProcessor->findContainingXmlBlockForMacro('{{title}}', 'w:r');
self::assertEquals($toFind, $templateProcessor->getSlice($position['start'], $position['end']));
}
public function testShouldReturnFalseIfXmlBlockNotFound(): void
{
$mainPart = '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
@ -826,6 +1381,34 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
self::assertFalse($result);
}
public function testShouldReturnFalseIfXmlBlockNotFoundWithCustomMacro(): void
{
$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);
$templateProcessor->setMacroChars('{{', '}}');
//non-existing macro
$result = $templateProcessor->findContainingXmlBlockForMacro('{{fake-macro}}', 'w:p');
self::assertFalse($result);
//existing macro but not inside node looked for
$result = $templateProcessor->findContainingXmlBlockForMacro('{{macro}}', 'w:fake-node');
self::assertFalse($result);
//existing macro but end tag not found after macro
$result = $templateProcessor->findContainingXmlBlockForMacro('{{macro}}', 'w:rPr');
self::assertFalse($result);
}
public function testShouldMakeFieldsUpdateOnOpen(): void
{
$settingsPart = '<w:settings xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
@ -839,4 +1422,19 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
$templateProcessor->setUpdateFields(false);
self::assertStringContainsString('<w:updateFields w:val="false"/>', $templateProcessor->getSettingsPart());
}
public function testShouldMakeFieldsUpdateOnOpenWithCustomMacro(): void
{
$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->setMacroChars('{{', '}}');
$templateProcessor->setUpdateFields(true);
self::assertStringContainsString('<w:updateFields w:val="true"/>', $templateProcessor->getSettingsPart());
$templateProcessor->setUpdateFields(false);
self::assertStringContainsString('<w:updateFields w:val="false"/>', $templateProcessor->getSettingsPart());
}
}

View File

@ -2,10 +2,8 @@
/**
* 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.
@ -61,11 +59,11 @@ class DocumentTest extends \PHPUnit\Framework\TestCase
$doc = TestHelperDOCX::getDocument($phpWord);
self::assertNotNull($doc);
// $this->assertTrue($doc->elementExists('/Properties/property[name="key1"]/vt:lpwstr'));
// $this->assertTrue($doc->elementExists('/Properties/property[name="key2"]/vt:bool'));
// $this->assertTrue($doc->elementExists('/Properties/property[name="key3"]/vt:i4'));
// $this->assertTrue($doc->elementExists('/Properties/property[name="key4"]/vt:r8'));
// $this->assertTrue($doc->elementExists('/Properties/property[name="key5"]/vt:lpwstr'));
// $this->assertTrue($doc->elementExists('/Properties/property[name="key1"]/vt:lpwstr'));
// $this->assertTrue($doc->elementExists('/Properties/property[name="key2"]/vt:bool'));
// $this->assertTrue($doc->elementExists('/Properties/property[name="key3"]/vt:i4'));
// $this->assertTrue($doc->elementExists('/Properties/property[name="key4"]/vt:r8'));
// $this->assertTrue($doc->elementExists('/Properties/property[name="key5"]/vt:lpwstr'));
}
/**
@ -408,7 +406,13 @@ class DocumentTest extends \PHPUnit\Framework\TestCase
// behind
$element = $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:pict/v:shape');
$style = $element->getAttribute('style');
self::assertRegExp('/z\-index:\-[0-9]*/', $style);
// Try to address CI coverage issue for PHP 7.1 and 7.2 when using regex match assertions
if (method_exists(static::class, 'assertRegExp')) {
self::assertRegExp('/z\-index:\-[0-9]*/', $style);
} else {
self::assertMatchesRegularExpression('/z\-index:\-[0-9]*/', $style);
}
// square
$element = $doc->getElement('/w:document/w:body/w:p[4]/w:r/w:pict/v:shape/w10:wrap');
@ -551,7 +555,13 @@ class DocumentTest extends \PHPUnit\Framework\TestCase
$cell->addText('Test');
$doc = TestHelperDOCX::getDocument($phpWord);
self::assertEquals(Cell::DEFAULT_BORDER_COLOR, $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr/w:tcBorders/w:top', 'w:color'));
self::assertEquals(
Cell::DEFAULT_BORDER_COLOR,
$doc->getElementAttribute(
'/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr/w:tcBorders/w:top',
'w:color'
)
);
}
/**

Binary file not shown.