Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08064480ea | ||
|
|
aac3a601e1 | ||
|
|
ef99fac8e1 | ||
|
|
60734dd7f1 | ||
|
|
ed8d9ff420 | ||
|
|
a379577ac9 | ||
|
|
53d34fd049 | ||
|
|
679a738c42 | ||
|
|
0f6fbf2b11 | ||
|
|
050802bdb5 | ||
|
|
a215501cc9 | ||
|
|
1a80aacb4f | ||
|
|
40d5770651 | ||
|
|
143e01b29b | ||
|
|
25575c80ca | ||
|
|
1dc3dc6ce5 | ||
|
|
b453cf00ff | ||
|
|
f195d282d0 | ||
|
|
b2dfa65698 | ||
|
|
be499388da | ||
|
|
20f3a3e370 | ||
|
|
803cdee4f3 | ||
|
|
25de4b819c | ||
|
|
6041bb3fb5 | ||
|
|
249e049894 | ||
|
|
9658c12084 | ||
|
|
83f6b020b3 | ||
|
|
d721b5eda7 | ||
|
|
02e3aa96b6 | ||
|
|
02a92c8924 | ||
|
|
b457ff5f7f | ||
|
|
573f1c3ea1 | ||
|
|
0189977743 | ||
|
|
9afd53d622 | ||
|
|
a075740e32 | ||
|
|
ecd2fcbda8 | ||
|
|
9ed82d1f5a | ||
|
|
0af4ca1f6e | ||
|
|
228bada972 | ||
|
|
6deea59905 | ||
|
|
95ce998651 | ||
|
|
c37be288a5 | ||
|
|
28ca75eca4 | ||
|
|
0b9f30fc77 | ||
|
|
d2c6b9f790 | ||
|
|
a771de75b9 | ||
|
|
ab3a2c0418 | ||
|
|
14c6e6f370 | ||
|
|
f482f2600b | ||
|
|
46e61d4c18 | ||
|
|
4b7e7e4612 | ||
|
|
9a6eb6970d | ||
|
|
91504dfddc | ||
|
|
024fdf9c1a | ||
|
|
c4bcba9741 | ||
|
|
26757c29bb | ||
|
|
d9c1daa89c | ||
|
|
5bd4ad39a5 | ||
|
|
04b224caa5 |
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -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
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/how-to-use.md
vendored
2
.github/ISSUE_TEMPLATE/how-to-use.md
vendored
@ -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).
|
||||
|
||||
23
.github/workflows/ci.yml
vendored
23
.github/workflows/ci.yml
vendored
@ -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') }}
|
||||
|
||||
2
.github/workflows/github-pages.yml
vendored
2
.github/workflows/github-pages.yml
vendored
@ -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
|
||||
|
||||
32
.github/workflows/stale.yml
vendored
32
.github/workflows/stale.yml
vendored
@ -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.'
|
||||
19
README.md
19
README.md
@ -1,23 +1,16 @@
|
||||
# 
|
||||
|
||||
Master:
|
||||
[](https://packagist.org/packages/phpoffice/phpword)
|
||||
[](https://travis-ci.org/PHPOffice/PHPWord)
|
||||
[](https://github.com/PHPOffice/PHPWord/actions/workflows/ci.yml)
|
||||
[](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/)
|
||||
[](https://coveralls.io/github/PHPOffice/PHPWord?branch=master)
|
||||
[](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/)
|
||||
[](https://packagist.org/packages/phpoffice/phpword)
|
||||
[](https://packagist.org/packages/phpoffice/phpword)
|
||||
[](https://gitter.im/PHPOffice/PHPWord)
|
||||
|
||||
Develop:
|
||||
[](https://packagist.org/packages/phpoffice/phpword#dev-develop)
|
||||
[](https://travis-ci.org/PHPOffice/PHPWord/branches)
|
||||
[](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/?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.
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
@ -39,7 +39,7 @@ Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
composer require phpoffice/phpword:dev-develop
|
||||
composer require phpoffice/phpword:dev-master
|
||||
|
||||
Using samples
|
||||
-------------
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
80
src/PhpWord/Shared/Css.php
Normal file
80
src/PhpWord/Shared/Css.php
Normal 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));
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -286,7 +286,7 @@ class Cell extends Border
|
||||
*/
|
||||
public function setWidth($value)
|
||||
{
|
||||
$this->setIntVal($value);
|
||||
$this->width = $this->setIntVal($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
54
tests/PhpWordTests/Shared/CssTest.php
Normal file
54
tests/PhpWordTests/Shared/CssTest.php
Normal 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'));
|
||||
}
|
||||
}
|
||||
@ -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).
|
||||
*/
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
BIN
tests/PhpWordTests/_files/templates/delete-row.docx
Normal file
BIN
tests/PhpWordTests/_files/templates/delete-row.docx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user