diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 0e2e9ea96d1..81f75d8e042 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -48,7 +48,7 @@ jobs: fail-fast: false matrix: # Disabled PHPStan in '7.1' - php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] steps: - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -60,65 +60,88 @@ jobs: - name: Composer Install run: composer install --ansi --prefer-dist --no-interaction --no-progress - - name: Run phpstan + - name: Run phpstan Php7.2/Php7.3 + if: matrix.php == '7.2' || matrix.php == '7.3' + run: ./vendor/bin/phpstan analyse -c phpstan.neon.php73.dist + + - name: Run phpstan Php8/Php7.4 + if: matrix.php == '7.4' || matrix.php == '8.0' || matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.3' || matrix.php == '8.4' || matrix.php == '8.5' run: ./vendor/bin/phpstan analyse -c phpstan.neon.dist phpunit: - name: PHPUnit ${{ matrix.php }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: - php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + experimental: + - false + php: + - '7.1' + - '7.2' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + - '8.2' + - '8.3' + - '8.4' + - '8.5' + + include: + - php: 'nightly' + experimental: true + + name: PHPUnit ${{ matrix.php }} + steps: - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: gd, xml, zip - coverage: ${{ (matrix.php == '7.3') && 'xdebug' || 'none' }} + extensions: dom, gd, mbstring, xml, zip + coverage: ${{ (matrix.php == '8.3') && 'xdebug' || 'none' }} - uses: actions/checkout@v2 + - name: Composer Config nightly + if: matrix.php == 'nightly' + run: composer config platform.php 8.5.99 + - name: Composer Install run: composer install --ansi --prefer-dist --no-interaction --no-progress - - name: Run phpunit - if: matrix.php != '7.3' - run: ./vendor/bin/phpunit -c phpunit.xml.dist --no-coverage + - name: Run phpunit 7.1 7.2 + if: matrix.php == '7.1' || matrix.php == '7.2' + run: ./vendor/bin/phpunit -c phpunit.7.8.xml.dist --no-coverage - - name: Run phpunit - if: matrix.php == '7.3' - run: ./vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover build/clover.xml + - name: Run phpunit 7.3 7.4 8.0 + if: matrix.php == '7.3' || matrix.php == '7.4' || matrix.php == '8.0' + run: ./vendor/bin/phpunit -c phpunit9.xml.dist --no-coverage - - name: Upload coverage results to Coveralls - if: matrix.php == '7.3' + - name: Run phpunit 8.1 8.2 8.4 8.5 + if: matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.4' || matrix.php == '8.5' + run: ./vendor/bin/phpunit -c phpunit10.xml.dist --no-coverage + + - name: Run phpunit nightly experimental + if: matrix.php == 'nightly' env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar - chmod +x php-coveralls.phar - php php-coveralls.phar --coverage_clover=build/clover.xml --json_path=build/coveralls-upload.json -vvv - - samples: - name: Check samples - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] - steps: - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: gd, xml, zip - coverage: xdebug + FAILURE_ACTION: "${{ matrix.experimental == true }}" + run: ./vendor/bin/phpunit -c phpunit10.xml.dist --no-coverage || $FAILURE_ACTION - - uses: actions/checkout@v2 + - name: Test non-Composer Autoloader + env: + FAILURE_ACTION: "${{ matrix.experimental == true }}" + run: php samples/Sample_45_Autoloader.php - - name: Composer Install - run: composer install --ansi --prefer-dist --no-interaction --no-progress + - name: Run phpunit 8.3 + if: matrix.php == '8.3' + run: ./vendor/bin/phpunit -c phpunit10.xml.dist --coverage-clover build/clover.xml - - name: Generate samples files - run: composer run samples + - name: Upload coverage results to Coveralls + if: matrix.php == '8.3' + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + file: build/clover.xml + format: clover + fail-on-error: false diff --git a/.gitignore b/.gitignore index 6918df72e6b..5ccb032d249 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ Thumbs.db Desktop.ini .idea +/analysis _build /build phpunit.xml @@ -15,7 +16,6 @@ composer.phar composer.lock vendor /report -/build /samples/results /.settings phpword.ini @@ -25,4 +25,5 @@ phpword.ini /nbproject /.php_cs.cache /.phpunit.result.cache -/public \ No newline at end of file +/.phpunit.cache +/public diff --git a/README.md b/README.md index e080d32da80..4a2e461f93f 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,6 @@ PHPWord requires the following: - PHP 7.1+ - [XML Parser extension](http://www.php.net/manual/en/xml.installation.php) -- [Laminas Escaper component](https://docs.laminas.dev/laminas-escaper/intro/) - [Zip extension](http://php.net/manual/en/book.zip.php) (optional, used to write OOXML and ODF) - [GD extension](http://php.net/manual/en/book.image.php) (optional, used to add images) - [XMLWriter extension](http://php.net/manual/en/book.xmlwriter.php) (optional, used to write OOXML and ODF) @@ -129,7 +128,7 @@ $fontStyle = new \PhpOffice\PhpWord\Style\Font(); $fontStyle->setBold(true); $fontStyle->setName('Tahoma'); $fontStyle->setSize(13); -$myTextElement = $section->addText('"Believe you can and you\'re halfway there." (Theodor Roosevelt)'); +$myTextElement = $section->addText('"Believe you can and you\'re halfway there." (Theodore Roosevelt)'); $myTextElement->setFontStyle($fontStyle); // Saving the document as OOXML file... diff --git a/composer.json b/composer.json index 08ef4ec9277..15d3598d1cc 100644 --- a/composer.json +++ b/composer.json @@ -96,7 +96,9 @@ "php samples/Sample_42_TemplateSetCheckbox.php", "php samples/Sample_43_RTLDefault.php", "php samples/Sample_44_ExtractVariablesFromReaderWord2007.php", - "php samples/Sample_45_Autoloader.php" + "php samples/Sample_45_Autoloader.php", + "php samples/Sample_46_RubyPhoneticGuide.php", + "php samples/Sample_47_RTLTitles.php" ] }, "scripts-descriptions": { @@ -108,11 +110,12 @@ "require": { "php": "^7.1|^8.0", "ext-dom": "*", - "ext-gd": "*", - "ext-zip": "*", - "ext-json": "*", + "ext-gd": "*", + "ext-mbstring": "*", "ext-xml": "*", - "phpoffice/math": "^0.3" + "ext-zip": "*", + "phpoffice/math": "^0.3", + "symfony/polyfill-php72": "^1.30" }, "require-dev": { "ext-libxml": "*", @@ -120,10 +123,10 @@ "friendsofphp/php-cs-fixer": "^3.3", "mpdf/mpdf": "^7.0 || ^8.0", "phpmd/phpmd": "^2.13", - "phpstan/phpstan": "^0.12.88 || ^1.0.0 || ^2.0.0", + "phpstan/phpstan": "<=2.1.32", "phpstan/phpstan-phpunit": "^1.0 || ^2.0", - "phpunit/phpunit": ">=7.0", - "symfony/process": "^4.4 || ^5.0", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0 || ^10.0", + "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0 || 7.4.x-dev", "tecnickcom/tcpdf": "^6.5" }, "suggest": { diff --git a/docs/usage/introduction.md b/docs/usage/introduction.md index 19d6aff51f1..228aff47953 100644 --- a/docs/usage/introduction.md +++ b/docs/usage/introduction.md @@ -55,7 +55,7 @@ $fontStyle = new \PhpOffice\PhpWord\Style\Font(); $fontStyle->setBold(true); $fontStyle->setName('Tahoma'); $fontStyle->setSize(13); -$myTextElement = $section->addText('"Believe you can and you\'re halfway there." (Theodor Roosevelt)'); +$myTextElement = $section->addText('"Believe you can and you\'re halfway there." (Theodore Roosevelt)'); $myTextElement->setFontStyle($fontStyle); // Saving the document as OOXML file... diff --git a/docs/usage/template.md b/docs/usage/template.md index a0c885e75ef..05f9abe6ea9 100644 --- a/docs/usage/template.md +++ b/docs/usage/template.md @@ -166,7 +166,7 @@ Customer: ${customer_name#3} Address: ${customer_address#3} ``` -It is also possible to pass an array with the values to replace the marcros with. +It is also possible to pass an array with the values to replace the macros with. If an array with replacements is passed, the ``count`` argument is ignored, it is the size of the array that counts. ``` php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ea75a4bd910..db563fa1215 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -10,41 +10,11 @@ parameters: count: 1 path: src/PhpWord/Element/AbstractContainer.php - - - message: "#^Parameter \\#1 \\$string of function md5 expects string, int\\<0, max\\> given\\.$#" - count: 1 - path: src/PhpWord/Element/AbstractElement.php - - - - message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\|null given\\.$#" - count: 1 - path: src/PhpWord/Element/Cell.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:setOptions\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Field but returns array\\.$#" - count: 1 - path: src/PhpWord/Element/Field.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:setProperties\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Field but returns array\\.$#" - count: 1 - path: src/PhpWord/Element/Field.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\) does not accept null\\.$#" count: 1 path: src/PhpWord/Element/Field.php - - - message: "#^Result of \\|\\| is always true\\.$#" - count: 1 - path: src/PhpWord/Element/Field.php - - - - message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\|null given\\.$#" - count: 1 - path: src/PhpWord/Element/Footnote.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:getArchiveImageSize\\(\\) should return array\\|null but returns array\\|false\\|null\\.$#" count: 1 @@ -55,21 +25,11 @@ parameters: count: 1 path: src/PhpWord/Element/Image.php - - - message: "#^Parameter \\#1 \\$callback of function call_user_func expects callable\\(\\)\\: mixed, string given\\.$#" - count: 1 - path: src/PhpWord/Element/Image.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:\\$source \\(string\\) does not accept string\\|false\\.$#" count: 1 path: src/PhpWord/Element/Image.php - - - message: "#^Offset 'extension' does not exist on array\\{dirname\\?\\: string, basename\\: string, extension\\?\\: string, filename\\: string\\}\\.$#" - count: 2 - path: src/PhpWord/Element/OLEObject.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\OLEObject\\:\\:\\$icon \\(string\\) does not accept string\\|false\\.$#" count: 1 @@ -85,21 +45,6 @@ parameters: count: 1 path: src/PhpWord/Element/Section.php - - - message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\|PhpOffice\\\\PhpWord\\\\Style\\\\Section\\|string given\\.$#" - count: 1 - path: src/PhpWord/Element/Section.php - - - - message: "#^Parameter \\#3 \\$length of function substr expects int\\|null, int\\|false given\\.$#" - count: 1 - path: src/PhpWord/Element/Section.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeAsciiCharacter\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Escaper/Rtf.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeAsciiCharacter\\(\\) has parameter \\$code with no type specified\\.$#" count: 1 @@ -115,11 +60,6 @@ parameters: count: 1 path: src/PhpWord/Escaper/Rtf.php - - - message: "#^Cannot instantiate interface PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface\\.$#" - count: 1 - path: src/PhpWord/IOFactory.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createObject\\(\\) should return PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface but returns object\\.$#" count: 1 @@ -140,16 +80,6 @@ parameters: count: 1 path: src/PhpWord/IOFactory.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:getDefaultFontSize\\(\\) should return int but returns float\\|int\\.$#" - count: 1 - path: src/PhpWord/PhpWord.php - - - - message: "#^Parameter \\#1 \\$callback of function forward_static_call_array expects callable\\(\\)\\: mixed, array\\{'PhpOffice\\\\\\\\PhpWord…', string\\} given\\.$#" - count: 1 - path: src/PhpWord/PhpWord.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\AbstractReader\\:\\:openFile\\(\\) should return resource but return statement is missing\\.$#" count: 1 @@ -160,11 +90,6 @@ parameters: count: 1 path: src/PhpWord/Reader/HTML.php - - - message: "#^Offset 'textNodes' on array\\{changed\\: PhpOffice\\\\PhpWord\\\\Element\\\\TrackChange, textNodes\\: DOMNodeList\\\\} in isset\\(\\) always exists and is not nullable\\.$#" - count: 1 - path: src/PhpWord/Reader/ODText/Content.php - - message: "#^Parameter \\#2 \\$contextNode of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLReader\\:\\:getElements\\(\\) expects DOMElement\\|null, DOMNode\\|null given\\.$#" count: 2 @@ -175,41 +100,6 @@ parameters: count: 1 path: src/PhpWord/Reader/ODText/Content.php - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$rtf \\(string\\) does not accept string\\|false\\.$#" - count: 1 - path: src/PhpWord/Reader/RTF.php - - - - message: "#^Cannot call method setStyleByArray\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" - count: 1 - path: src/PhpWord/Reader/RTF/Document.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$phpWord is never read, only written\\.$#" - count: 1 - path: src/PhpWord/Reader/RTF/Document.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\:\\:load\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Reader/ReaderInterface.php - - - - message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|false given\\.$#" - count: 2 - path: src/PhpWord/Reader/Word2007.php - - - - message: "#^Parameter \\#2 \\$xmlFile of method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\:\\:getRels\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007.php - - - - message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|false given\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007.php - - message: "#^Binary operation \"/\" between string\\|null and 2 results in an error\\.$#" count: 1 @@ -380,21 +270,11 @@ parameters: count: 1 path: src/PhpWord/Shared/Html.php - - - message: "#^Cannot call method setBorderSize\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - message: "#^Cannot call method setStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" count: 1 path: src/PhpWord/Shared/Html.php - - - message: "#^If condition is always true\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:addHtml\\(\\) has parameter \\$options with no type specified\\.$#" count: 1 @@ -435,21 +315,11 @@ parameters: count: 1 path: src/PhpWord/Shared/Html.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseStyleDeclarations\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:recursiveParseStylesInHierarchy\\(\\) has no return type specified\\.$#" count: 1 path: src/PhpWord/Shared/Html.php - - - message: "#^Parameter \\#1 \\$attribute of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseStyle\\(\\) expects DOMAttr, DOMNode given\\.$#" - count: 3 - path: src/PhpWord/Shared/Html.php - - message: "#^Parameter \\#2 \\$element of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseNode\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\|PhpOffice\\\\PhpWord\\\\Element\\\\Row\\|PhpOffice\\\\PhpWord\\\\Element\\\\Table given\\.$#" count: 1 @@ -470,21 +340,6 @@ parameters: count: 1 path: src/PhpWord/Shared/Html.php - - - message: "#^Result of \\|\\| is always true\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Right side of && is always true\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Variable \\$cNodes in empty\\(\\) always exists and is not falsy\\.$#" - count: 2 - path: src/PhpWord/Shared/Html.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$encryptionMatrix has no type specified\\.$#" count: 1 @@ -500,21 +355,11 @@ parameters: count: 1 path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:getData\\(\\) should return string but returns string\\|false\\.$#" - count: 1 - path: src/PhpWord/Shared/XMLWriter.php - - message: "#^Call to method add\\(\\) on an unknown class PclZip\\.$#" count: 2 path: src/PhpWord/Shared/ZipArchive.php - - - message: "#^Call to method addFromString\\(\\) on an unknown class PclZip\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - message: "#^Call to method close\\(\\) on an unknown class PclZip\\.$#" count: 1 @@ -545,11 +390,6 @@ parameters: count: 3 path: src/PhpWord/Shared/ZipArchive.php - - - message: "#^Comparison operation \"\\!\\=\" between array and 0 results in an error\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - message: "#^Constant PCLZIP_OPT_ADD_PATH not found\\.$#" count: 2 @@ -575,41 +415,16 @@ parameters: count: 1 path: src/PhpWord/Shared/ZipArchive.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:getFromName\\(\\) should return string but returns string\\|false\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:open\\(\\) should return bool but returns int\\|true\\.$#" count: 1 path: src/PhpWord/Shared/ZipArchive.php - - - message: "#^Offset 'dirname' does not exist on array\\{dirname\\?\\: string, basename\\: string, extension\\?\\: string, filename\\: string\\}\\.$#" - count: 3 - path: src/PhpWord/Shared/ZipArchive.php - - message: "#^PHPDoc tag @var for variable \\$zip contains unknown class PclZip\\.$#" count: 6 path: src/PhpWord/Shared/ZipArchive.php - - - message: "#^Parameter \\#1 \\$callback of function call_user_func_array expects callable\\(\\)\\: mixed, array\\{\\$this\\(PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\)\\|PclZip\\|ZipArchive, mixed\\} given\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Parameter \\#1 \\$stream of function fclose expects resource, resource\\|false given\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Parameter \\#1 \\$stream of function fwrite expects resource, resource\\|false given\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:\\$zip has unknown class PclZip as its type\\.$#" count: 1 @@ -660,21 +475,6 @@ parameters: count: 1 path: src/PhpWord/Style/AbstractStyle.php - - - message: "#^Parameter \\#3 \\$length of function substr expects int\\|null, int\\|false given\\.$#" - count: 1 - path: src/PhpWord/Style/AbstractStyle.php - - - - message: "#^Unreachable statement \\- code above always terminates\\.$#" - count: 1 - path: src/PhpWord/Style/AbstractStyle.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Border\\:\\:getBorderSize\\(\\) should return array\\ but returns array\\\\.$#" - count: 1 - path: src/PhpWord/Style/Border.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\:\\:getBgColor\\(\\) should return string but returns null\\.$#" count: 1 @@ -715,11 +515,6 @@ parameters: count: 1 path: src/PhpWord/Style/Chart.php - - - message: "#^PHPDoc tag @param has invalid value \\(string\\)\\: Unexpected token \"\\\\n \\* \", expected variable at offset 250$#" - count: 1 - path: src/PhpWord/Style/Chart.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setAllCaps\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" count: 1 @@ -775,11 +570,6 @@ parameters: count: 1 path: src/PhpWord/Style/Paragraph.php - - - message: "#^Result of && is always false\\.$#" - count: 1 - path: src/PhpWord/Style/Paragraph.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Section\\:\\:setSettingValue\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Section but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" count: 1 @@ -965,11 +755,6 @@ parameters: count: 1 path: src/PhpWord/TemplateProcessor.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:indexClonedVariables\\(\\) should return string but returns array\\, string\\|null\\>\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has no return type specified\\.$#" count: 1 @@ -985,31 +770,11 @@ parameters: count: 1 path: src/PhpWord/TemplateProcessor.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:setValueForPart\\(\\) should return string but returns array\\\\|string\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:setValueForPart\\(\\) should return string but returns array\\\\|string\\|null\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - message: "#^Parameter \\#1 \\$element of method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\Chart\\:\\:setElement\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\Chart, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement given\\.$#" count: 1 path: src/PhpWord/TemplateProcessor.php - - - message: "#^Parameter \\#2 \\$array of function implode expects array\\|null, array\\\\|string given\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Parameter \\#2 \\$array of function implode expects array\\|null, string given\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$macroClosingChars has no type specified\\.$#" count: 1 @@ -1020,21 +785,6 @@ parameters: count: 1 path: src/PhpWord/TemplateProcessor.php - - - message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$tempDocumentFooters \\(array\\\\) does not accept string\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$tempDocumentHeaders \\(array\\\\) does not accept string\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: src/PhpWord/Writer/AbstractWriter.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\HTML\\\\Element\\\\AbstractElement\\:\\:write\\(\\) has no return type specified\\.$#" count: 1 @@ -1100,11 +850,6 @@ parameters: count: 1 path: src/PhpWord/Writer/ODText/Part/Styles.php - - - message: "#^Parameter \\#1 \\$callback of function call_user_func_array expects callable\\(\\)\\: mixed, array\\{PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\AbstractRenderer, string\\} given\\.$#" - count: 1 - path: src/PhpWord/Writer/PDF.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:\\$renderer \\(PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\AbstractRenderer\\) does not accept object\\.$#" count: 1 @@ -1140,41 +885,6 @@ parameters: count: 1 path: src/PhpWord/Writer/PDF/DomPDF.php - - - message: "#^Binary operation \"\\+\" between int\\|string and 1 results in an error\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Style\\\\Font\\:\\:setNameIndex\\(\\) expects int, int\\|string given\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|null\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|null\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept null\\.$#" - count: 2 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:write\\(\\) should return string but empty return statement found\\.$#" count: 1 @@ -1300,11 +1010,6 @@ parameters: count: 4 path: src/PhpWord/Writer/Word2007/Part/Chart.php - - - message: "#^Parameter \\#1 \\$string of function md5 expects string, int\\<0, max\\> given\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Part/Numbering.php - - message: "#^Parameter \\#1 \\$haystack of function strpos expects string, int given\\.$#" count: 1 @@ -1315,11 +1020,6 @@ parameters: count: 1 path: src/PhpWord/Writer/Word2007/Style/AbstractStyle.php - - - message: "#^Parameter \\#1 \\$styleName of static method PhpOffice\\\\PhpWord\\\\Style\\:\\:getStyle\\(\\) expects string, PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|string given\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Style/Font.php - - message: "#^Call to an undefined method object\\:\\:read\\(\\)\\.$#" count: 1 @@ -1365,11 +1065,6 @@ parameters: count: 2 path: tests/PhpWordTests/Element/CellTest.php - - - message: "#^Parameter \\#1 \\$text of method PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:setText\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string\\|null, array given\\.$#" - count: 1 - path: tests/PhpWordTests/Element/FieldTest.php - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$createFunction with no type specified\\.$#" count: 1 @@ -1405,26 +1100,11 @@ parameters: count: 1 path: tests/PhpWordTests/Element/ImageTest.php - - - message: "#^Parameter \\#1 \\$string of function md5 expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Element/ImageTest.php - - - - message: "#^Parameter \\#1 \\$string of function ucfirst expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Element/ImageTest.php - - message: "#^Parameter \\#3 \\$watermark of class PhpOffice\\\\PhpWord\\\\Element\\\\Image constructor expects bool, null given\\.$#" count: 1 path: tests/PhpWordTests/Element/ImageTest.php - - - message: "#^Parameter \\#2 \\$style of class PhpOffice\\\\PhpWord\\\\Element\\\\Section constructor expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, PhpOffice\\\\PhpWord\\\\Style\\\\Section given\\.$#" - count: 1 - path: tests/PhpWordTests/Element/SectionTest.php - - message: "#^Parameter \\#1 \\$text of class PhpOffice\\\\PhpWord\\\\Element\\\\Title constructor expects PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string, PhpOffice\\\\PhpWord\\\\Element\\\\PageBreak given\\.$#" count: 1 @@ -1510,66 +1190,6 @@ parameters: count: 2 path: tests/PhpWordTests/Reader/Word2007/StyleTest.php - - - message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$compatibility has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultFontName has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultFontSize has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultPaper has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$measurementUnit has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$pdfRendererName has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$pdfRendererPath has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$tempDir has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$zipClass has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - message: "#^Cannot call method getStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" count: 1 @@ -1590,16 +1210,6 @@ parameters: count: 1 path: tests/PhpWordTests/Shared/XMLWriterTest.php - - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: tests/PhpWordTests/Shared/ZipArchiveTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Style\\\\AbstractStyleTest\\:\\:callProtectedMethod\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Style/AbstractStyleTest.php - - message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|object, class\\-string\\|false given\\.$#" count: 1 @@ -1640,11 +1250,6 @@ parameters: count: 2 path: tests/PhpWordTests/TemplateProcessorTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:AddFromString\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/TemplateProcessorTest.php - - message: "#^Cannot access offset 'end' on array\\\\|bool\\.$#" count: 2 @@ -1675,11 +1280,6 @@ parameters: count: 1 path: tests/PhpWordTests/TemplateProcessorTest.php - - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: tests/PhpWordTests/TestHelperDOCX.php - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\TestableTemplateProcesor\\:\\:__construct\\(\\) has parameter \\$mainPart with no type specified\\.$#" count: 1 @@ -1690,21 +1290,6 @@ parameters: count: 1 path: tests/PhpWordTests/TestableTemplateProcesor.php - - - message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" - count: 9 - path: tests/PhpWordTests/Writer/HTML/ElementTest.php - - - - message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" - count: 2 - path: tests/PhpWordTests/Writer/HTML/Element/RubyTest.php - - - - message: "#^Cannot call method item\\(\\) on DOMNodeList\\\\|false\\.$#" - count: 11 - path: tests/PhpWordTests/Writer/HTML/ElementTest.php - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" count: 3 @@ -1715,121 +1300,16 @@ parameters: count: 1 path: tests/PhpWordTests/Writer/ODText/Style/FontTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" - count: 2 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getOrientation\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getPaperSize\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getTempDir\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setFont\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setOrientation\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setPaperSize\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setTempDir\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 3 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/MPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/MPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 2 - path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDFTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/ElementTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/ElementTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/StyleTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/StyleTest.php - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\Writer\\\\Word2007\\\\Element\\\\ChartTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" count: 1 path: tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php - - - message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/Word2007/ElementTest.php - - - - message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/Word2007/StyleTest.php - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getElement\\(\\) should return DOMElement\\|null but returns DOMNode\\|null\\.$#" count: 1 path: tests/PhpWordTests/XmlDocument.php - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getNodeList\\(\\) should return DOMNodeList\\ but returns DOMNodeList\\\\|false\\.$#" - count: 1 - path: tests/PhpWordTests/XmlDocument.php - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$path \\(string\\) does not accept string\\|false\\.$#" count: 1 @@ -1839,39 +1319,3 @@ parameters: message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$xpath \\(DOMXPath\\) does not accept null\\.$#" count: 1 path: tests/PhpWordTests/XmlDocument.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\HTML\\\\Part\\\\AbstractPart is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:|]+$#" - count: 1 - path: src/PhpWord/Writer/HTML.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 2 - path: tests/PhpWordTests/Element/AbstractElementTest.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 4 - path: tests/PhpWordTests/Style/AbstractStyleTest.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\EPub3\\\\Style\\\\AbstractStyle is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 2 - path: tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\AbstractPart is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 2 - path: tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\AbstractPart is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 2 - path: tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php diff --git a/phpstan-baseline.php73.neon b/phpstan-baseline.php73.neon new file mode 100644 index 00000000000..683d13703fb --- /dev/null +++ b/phpstan-baseline.php73.neon @@ -0,0 +1,1316 @@ +parameters: + ignoreErrors: + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:__call\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement but returns null\\.$#" + count: 1 + path: src/PhpWord/Element/AbstractContainer.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\) does not accept null\\.$#" + count: 1 + path: src/PhpWord/Element/Field.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:getArchiveImageSize\\(\\) should return array\\|null but returns array\\|false\\|null\\.$#" + count: 1 + path: src/PhpWord/Element/Image.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:getImageString\\(\\) should return string\\|null but returns string\\|false\\|null\\.$#" + count: 1 + path: src/PhpWord/Element/Image.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:\\$source \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: src/PhpWord/Element/Image.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\OLEObject\\:\\:\\$icon \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: src/PhpWord/Element/OLEObject.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Section\\:\\:addHeader\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Header but returns PhpOffice\\\\PhpWord\\\\Element\\\\Footer\\.$#" + count: 1 + path: src/PhpWord/Element/Section.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Section\\:\\:addHeaderFooter\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Footer but returns PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\.$#" + count: 1 + path: src/PhpWord/Element/Section.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeAsciiCharacter\\(\\) has parameter \\$code with no type specified\\.$#" + count: 1 + path: src/PhpWord/Escaper/Rtf.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeMultibyteCharacter\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Escaper/Rtf.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeMultibyteCharacter\\(\\) has parameter \\$code with no type specified\\.$#" + count: 1 + path: src/PhpWord/Escaper/Rtf.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createObject\\(\\) should return PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface but returns object\\.$#" + count: 1 + path: src/PhpWord/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createReader\\(\\) should return PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface but returns PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface\\.$#" + count: 1 + path: src/PhpWord/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createWriter\\(\\) should return PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface but returns PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface\\.$#" + count: 1 + path: src/PhpWord/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\AbstractReader\\:\\:openFile\\(\\) should return resource but return statement is missing\\.$#" + count: 1 + path: src/PhpWord/Reader/AbstractReader.php + + - + message: "#^Parameter \\#2 \\$html of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:addHtml\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpWord/Reader/HTML.php + + - + message: "#^Parameter \\#2 \\$contextNode of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLReader\\:\\:getElements\\(\\) expects DOMElement\\|null, DOMNode\\|null given\\.$#" + count: 2 + path: src/PhpWord/Reader/ODText/Content.php + + - + message: "#^Parameter \\#2 \\$depth of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addTitle\\(\\) expects int, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/ODText/Content.php + + - + message: "#^Binary operation \"/\" between string\\|null and 2 results in an error\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Call to an undefined method DOMNode\\:\\:getAttribute\\(\\)\\.$#" + count: 2 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:getHeadingDepth\\(\\) never returns float so it can be removed from the return type\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:getHeadingDepth\\(\\) should return float\\|int\\|null but returns string\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:read\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#1 \\$depth of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addListItemRun\\(\\) expects int, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#1 \\$height of method PhpOffice\\\\PhpWord\\\\Element\\\\Table\\:\\:addRow\\(\\) expects int\\|null, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setRelationId\\(\\) expects int, string\\|null given\\.$#" + count: 2 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#1 \\$width of method PhpOffice\\\\PhpWord\\\\Element\\\\Row\\:\\:addCell\\(\\) expects int\\|null, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#2 \\$contextNode of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLReader\\:\\:getAttribute\\(\\) expects DOMElement\\|null, DOMNode\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#2 \\$depth of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addTitle\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and DOMElement will always evaluate to false\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#2 \\$relationId of method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\Footnotes\\:\\:getElement\\(\\) expects int, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Footnotes.php + + - + message: "#^Parameter \\#3 \\$levelId of method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\Numbering\\:\\:readLevel\\(\\) expects int, string\\|null given\\.$#" + count: 2 + path: src/PhpWord/Reader/Word2007/Numbering.php + + - + message: "#^Parameter \\#1 \\$comments of method PhpOffice\\\\PhpWord\\\\ComplexType\\\\TrackChangesView\\:\\:setComments\\(\\) expects bool\\|null, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Settings.php + + - + message: "#^Parameter \\#1 \\$consecutiveHyphenLimit of method PhpOffice\\\\PhpWord\\\\Metadata\\\\Settings\\:\\:setConsecutiveHyphenLimit\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Settings.php + + - + message: "#^Parameter \\#1 \\$hyphenationZone of method PhpOffice\\\\PhpWord\\\\Metadata\\\\Settings\\:\\:setHyphenationZone\\(\\) expects float\\|int\\|null, string given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Settings.php + + - + message: "#^Parameter \\#1 \\$filename of function parse_ini_file expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpWord/Settings.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\AbstractEnum\\:\\:getConstants\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/AbstractEnum.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\AbstractEnum\\:\\:\\$constCacheArray has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/AbstractEnum.php + + - + message: "#^Binary operation \"/\" between string and 10 results in an error\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:angleToDegree\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:cssToPoint\\(\\) should return float\\|null but returns string\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:htmlToRgb\\(\\) should return array but returns false\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:inchToEmu\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:pixelToEmu\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$centimeter of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:cmToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$inch of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:inchToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$pica of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:picaToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$pixel of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:pixelToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:angleToDegrees\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Drawing.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:emuToPixels\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Drawing.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:pixelsToEmu\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Drawing.php + + - + message: "#^Call to an undefined method DOMNode\\:\\:getAttribute\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Cannot call method setStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:addHtml\\(\\) has parameter \\$options with no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:filterOutNonInheritedStyles\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapBorderColor\\(\\) has parameter \\$cssBorderColor with no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapBorderColor\\(\\) has parameter \\$styles with no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapListType\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseLink\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseList\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseRuby\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:recursiveParseStylesInHierarchy\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Parameter \\#2 \\$element of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseNode\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\|PhpOffice\\\\PhpWord\\\\Element\\\\Row\\|PhpOffice\\\\PhpWord\\\\Element\\\\Table given\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$listIndex has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$options has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$xpath has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$encryptionMatrix has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$initialCodeArray has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$passwordMaxLength has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php + + - + message: "#^Call to method add\\(\\) on an unknown class PclZip\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method close\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method extract\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method extractByIndex\\(\\) on an unknown class PclZip\\.$#" + count: 3 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method extractTo\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method getFromName\\(\\) on an unknown class PclZip\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method listContent\\(\\) on an unknown class PclZip\\.$#" + count: 3 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_ADD_PATH not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_EXTRACT_AS_STRING not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_PATH not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_REMOVE_PATH not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Instantiated class PclZip not found\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:open\\(\\) should return bool but returns int\\|true\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^PHPDoc tag @var for variable \\$zip contains unknown class PclZip\\.$#" + count: 6 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:\\$zip has unknown class PclZip as its type\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addFontStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addLinkStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addNumberingStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Numbering but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addParagraphStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addTableStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Table but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addTitleStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Call to an undefined method object\\:\\:setStyleByArray\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Style/AbstractStyle.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setFloatVal\\(\\) should return float\\|null but returns float\\|int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/AbstractStyle.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setNumericVal\\(\\) should return float\\|int\\|null but returns float\\|int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/AbstractStyle.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\:\\:getBgColor\\(\\) should return string but returns null\\.$#" + count: 1 + path: src/PhpWord/Style/Cell.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\:\\:setUnit\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Cell.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:getMajorTickPosition\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setCategoryAxisTitle\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setValueAxisTitle\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setValueLabelPosition\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:showAxisLabels\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:showGridY\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setAllCaps\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setBgColor\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Table but return statement is missing\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setDoubleStrikethrough\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSmallCaps\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setStrikethrough\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSubScript\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSuperScript\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Line\\:\\:\\$weight \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Line.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\ListItem\\:\\:getListTypeStyle\\(\\) should return array but empty return statement found\\.$#" + count: 1 + path: src/PhpWord/Style/ListItem.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\ListItem\\:\\:getListTypeStyle\\(\\) should return array but return statement is missing\\.$#" + count: 1 + path: src/PhpWord/Style/ListItem.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\:\\:setStyleValue\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Paragraph.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Section\\:\\:setSettingValue\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Section but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Section.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\TOC\\:\\:setTabLeader\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\TOC but returns PhpOffice\\\\PhpWord\\\\Style\\\\Tab\\.$#" + count: 1 + path: src/PhpWord/Style/TOC.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\TOC\\:\\:setTabPos\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\TOC but returns PhpOffice\\\\PhpWord\\\\Style\\\\Tab\\.$#" + count: 1 + path: src/PhpWord/Style/TOC.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideHColor\\(\\) should return string but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideHSize\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideVColor\\(\\) should return string but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideVSize\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderSize\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginBottom\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginLeft\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginRight\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginTop\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$bottomFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$leftFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$rightFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$tblpX \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$tblpY \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$topFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Cannot access offset 'end' on array\\\\|true\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Cannot access offset 'start' on array\\\\|true\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$imageMimeType with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$imgPath with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$partFileName with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$rid with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$baseValue with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$defaultValue with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$inlineValue with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$actualHeight with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$actualWidth with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$height with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$width with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getImageArgs\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getImageArgs\\(\\) has parameter \\$varNameWithArgs with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getNextRelationsIndex\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getNextRelationsIndex\\(\\) has parameter \\$documentPartName with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has parameter \\$replaceImage with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has parameter \\$varInlineArgs with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Parameter \\#1 \\$element of method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\Chart\\:\\:setElement\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\Chart, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement given\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$macroClosingChars has no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$macroOpeningChars has no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\HTML\\\\Element\\\\AbstractElement\\:\\:write\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/HTML/Element/AbstractElement.php + + - + message: "#^Variable \\$row in PHPDoc tag @var does not match assigned variable \\$rowStyle\\.$#" + count: 1 + path: src/PhpWord/Writer/HTML/Element/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Field\\:\\:writeDefault\\(\\) has parameter \\$type with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Field.php + + - + message: "#^Variable \\$row in PHPDoc tag @var does not match any variable in the foreach loop\\: \\$cell$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\AbstractElement\\:\\:replaceTabs\\(\\) has parameter \\$text with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\AbstractElement\\:\\:replaceTabs\\(\\) has parameter \\$xmlWriter with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Text\\:\\:writeChangeInsertion\\(\\) has parameter \\$start with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Text.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getParagraphStyle\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/TextRun.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setColumnWidths\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Parameter \\#1 \\$container of method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Content\\:\\:collectTrackedChanges\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement given\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Content\\:\\:\\$imageParagraphStyles has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 2 + path: src/PhpWord/Writer/ODText/Part/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Styles\\:\\:cvttwiptostr\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Styles.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:\\$renderer \\(PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\AbstractRenderer\\) does not accept object\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:loadHtml\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:output\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:render\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:setPaper\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Class PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF referenced with incorrect case\\: PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\Dompdf\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:createExternalWriterInstance\\(\\) should return PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF but returns Dompdf\\\\Dompdf\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:write\\(\\) should return string but empty return statement found\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writeDate\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writeNumpages\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writePage\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getImageStringData\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Image.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getStyle\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Image.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getSource\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Link.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Link.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 2 + path: src/PhpWord/Writer/RTF/Part/Document.php + + - + message: "#^Binary operation \"\\+\" between int\\|string and 1 results in an error\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Style/Border.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\AbstractElement\\:\\:write\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\Field\\:\\:buildPropertiesAndOptions\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/Field.php + + - + message: "#^Parameter \\#1 \\$content of method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\AbstractElement\\:\\:writeText\\(\\) expects string, bool\\|int\\|string given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/FormField.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, bool\\|int\\|string given\\.$#" + count: 3 + path: src/PhpWord/Writer/Word2007/Element/FormField.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int given\\.$#" + count: 4 + path: src/PhpWord/Writer/Word2007/Element/FormField.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\ParagraphAlignment\\:\\:\\$attributes has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\ParagraphAlignment\\:\\:\\$name has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php + + - + message: "#^Parameter \\#2 \\$content of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, bool\\|int\\|string given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/SDT.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int\\<100000000, 999999999\\> given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/SDT.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\TableAlignment\\:\\:\\$attributes has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/TableAlignment.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\TableAlignment\\:\\:\\$name has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/TableAlignment.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:addBookmark\\(\\) invoked with 0 parameters, 1 required\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/Title.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\Chart\\:\\:writeAxisTitle\\(\\) has parameter \\$title with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Part/Chart.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int given\\.$#" + count: 9 + path: src/PhpWord/Writer/Word2007/Part/Chart.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int\\<0, max\\> given\\.$#" + count: 4 + path: src/PhpWord/Writer/Word2007/Part/Chart.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, int given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Part/Rels.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Style\\\\AbstractStyle\\:\\:write\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Style/AbstractStyle.php + + - + message: "#^Call to an undefined method object\\:\\:read\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractTestReader.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\AbstractTestReader\\:\\:\\$parts has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractTestReader.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getBaseUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteBmpImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteGifImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:\\$httpServer has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Parameter \\#1 \\$width of class PhpOffice\\\\PhpWord\\\\Element\\\\Cell constructor expects int\\|null, string given\\.$#" + count: 2 + path: tests/PhpWordTests/Element/CellTest.php + + - + message: "#^Parameter \\#2 \\$style of class PhpOffice\\\\PhpWord\\\\Element\\\\Cell constructor expects array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\|null, int given\\.$#" + count: 2 + path: tests/PhpWordTests/Element/CellTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$createFunction with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$extension with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$imageFunction with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$imageQuality with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$source with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$type with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Parameter \\#1 \\$source of class PhpOffice\\\\PhpWord\\\\Element\\\\Image constructor expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Parameter \\#3 \\$watermark of class PhpOffice\\\\PhpWord\\\\Element\\\\Image constructor expects bool, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Parameter \\#1 \\$text of class PhpOffice\\\\PhpWord\\\\Element\\\\Title constructor expects PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string, PhpOffice\\\\PhpWord\\\\Element\\\\PageBreak given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/TitleTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:escapestring\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:escapestring\\(\\) has parameter \\$str with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:expect\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:expect\\(\\) has parameter \\$str with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Parameter \\#1 \\$expected of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) expects class\\-string\\, string given\\.$#" + count: 1 + path: tests/PhpWordTests/IOFactoryTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/IOFactoryTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:undefinedMethod\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/PhpWordTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getRows\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/ElementTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/ElementTest.php + + - + message: "#^Cannot access offset 0 on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/ElementTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getElement\\(\\)\\.$#" + count: 2 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Cannot call method getElement\\(\\) on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string\\.$#" + count: 3 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Cannot call method isBold\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Variable \\$endnote in PHPDoc tag @var does not match assigned variable \\$documentEndnote\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Variable \\$footnote in PHPDoc tag @var does not match assigned variable \\$documentFootnote\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Cannot access offset 0 on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\.$#" + count: 2 + path: tests/PhpWordTests/Reader/Word2007/StyleTest.php + + - + message: "#^Cannot call method getStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Shared/HtmlTest.php + + - + message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Text\\:\\:numberFormat\\(\\) expects float, string given\\.$#" + count: 2 + path: tests/PhpWordTests/Shared/TextTest.php + + - + message: "#^Parameter \\#2 \\$locale of function setlocale expects array\\|string\\|null, int given\\.$#" + count: 1 + path: tests/PhpWordTests/Shared/XMLWriterTest.php + + - + message: "#^Parameter \\#2 \\$locale of function setlocale expects string\\|null, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Shared/XMLWriterTest.php + + - + message: "#^Cannot call method setLineHeight\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Style/FontTest.php + + - + message: "#^Parameter \\#1 \\$type of class PhpOffice\\\\PhpWord\\\\Style\\\\Font constructor expects string, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/FontTest.php + + - + message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setStyleValue\\(\\) expects array\\|int\\|string, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/FontTest.php + + - + message: "#^Cannot call method setLineHeight\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Style/ParagraphTest.php + + - + message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setStyleValue\\(\\) expects array\\|int\\|string, bool given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/RowTest.php + + - + message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\Section\\:\\:setSettingValue\\(\\) expects array\\|int\\|string, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/SectionTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" + count: 2 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Cannot access offset 'end' on array\\\\|bool\\.$#" + count: 2 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Cannot access offset 'start' on array\\\\|bool\\.$#" + count: 2 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\) expects string, string\\|false given\\.$#" + count: 6 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringNotContainsString\\(\\) expects string, string\\|false given\\.$#" + count: 4 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Part \\$documentZip \\(ZipArchive\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Part \\$templateZip \\(ZipArchive\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\TestableTemplateProcesor\\:\\:__construct\\(\\) has parameter \\$mainPart with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/TestableTemplateProcesor.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\TestableTemplateProcesor\\:\\:__construct\\(\\) has parameter \\$settingsPart with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/TestableTemplateProcesor.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 3 + path: tests/PhpWordTests/Writer/HTML/Element/PageBreakTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\ODText\\\\Style\\\\FontTest\\:\\:providerAllNamedColors\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/ODText/Style/FontTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\Writer\\\\Word2007\\\\Element\\\\ChartTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/ElementTest.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/StyleTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getElement\\(\\) should return DOMElement\\|null but returns DOMNode\\|null\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$path \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$xpath \\(DOMXPath\\) does not accept null\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index cf26b6956bc..295b623f512 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -9,37 +9,9 @@ parameters: - tests/ excludePaths: - */pclzip.lib.php - - src/PhpWord/Shared/OLERead.php - - src/PhpWord/Reader/MsDoc.php - - src/PhpWord/Writer/PDF/MPDF.php bootstrapFiles: - tests/bootstrap.php - ## <=PHP7.4 - reportUnmatchedIgnoredErrors: false treatPhpDocTypesAsCertain: false ignoreErrors: - identifier: missingType.iterableValue - - ## <=PHP7.4 - - - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' - path: src/PhpWord/Element/AbstractContainer.php - - - message: '#Parameter \#1 \$function of function call_user_func expects callable\(\): mixed, string given.#' - path: src/PhpWord/Element/Image.php - - - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' - path: src/PhpWord/IOFactory.php - - - message: '#Parameter \#1 \$function of function forward_static_call_array expects callable\(\): mixed, array{.+, string} given.#' - path: src/PhpWord/PhpWord.php - - - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{\$this\(PhpOffice\\PhpWord\\Shared\\ZipArchive\)\|PclZip\|ZipArchive, mixed} given.#' - path: src/PhpWord/Shared/ZipArchive.php - - - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{PhpOffice\\PhpWord\\Writer\\PDF\\AbstractRenderer, string} given.#' - path: src/PhpWord/Writer/PDF.php - - - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' - path: tests/PhpWordTests/Style/AbstractStyleTest.php diff --git a/phpstan.neon.php73.dist b/phpstan.neon.php73.dist new file mode 100644 index 00000000000..f68f9b95d45 --- /dev/null +++ b/phpstan.neon.php73.dist @@ -0,0 +1,53 @@ +includes: + - phpstan-baseline.php73.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon +parameters: + level: 7 + paths: + - src/ + - tests/ + excludePaths: + - */pclzip.lib.php + bootstrapFiles: + - tests/bootstrap.php + reportUnmatchedIgnoredErrors: true + treatPhpDocTypesAsCertain: false + ignoreErrors: + - + identifier: missingType.iterableValue + + ## <=PHP7.4 + - + message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' + path: src/PhpWord/Element/AbstractContainer.php + - + message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' + path: src/PhpWord/IOFactory.php + - + message: '#Cannot instantiate interface#' + path: src/PhpWord/IOFactory.php + - + message: '#only iterables are supported#' + path: src/PhpWord/Writer/AbstractWriter.php + - + message: '#Cannot call method getText#' + path: src/PhpWord/Writer/ODText/Part/Content.php + - + message: '#only iterables are supported#' + path: tests/PhpWordTests/Shared/ZipArchiveTest.php + - + message: '#only iterables are supported#' + path: tests/PhpWordTests/TestHelperDOCX.php + - + message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' + path: tests/PhpWordTests/Style/AbstractStyleTest.php + - + message: '#always exists and is not nullable#' + path: tests/PhpWordTests/Writer/HTML/Element/TableTest.php + - + message: '#always exists and is not nullable#' + path: tests/PhpWordTests/Writer/HTML/FontTest.php + - + message: '#always exists and is not nullable#' + path: tests/PhpWordTests/Writer/HTML/ParagraphTest.php diff --git a/phpunit.7.8.xml.dist b/phpunit.7.8.xml.dist new file mode 100644 index 00000000000..22e77a0ea68 --- /dev/null +++ b/phpunit.7.8.xml.dist @@ -0,0 +1,11 @@ + + + + + + + + ./tests/PhpWordTests + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6f1f5445ab6..fd357d9ade7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,18 +1,7 @@ - - - - ./src - - - ./src/PhpWord/Shared/PCLZip - - - - - - + + @@ -21,4 +10,12 @@ + + + ./src + + + ./src/PhpWord/Shared/PCLZip + + diff --git a/phpunit10.xml.dist b/phpunit10.xml.dist new file mode 100644 index 00000000000..b97fbee3fb2 --- /dev/null +++ b/phpunit10.xml.dist @@ -0,0 +1,20 @@ + + + + + + + + ./tests/PhpWordTests + + + + + + ./src + + + ./src/PhpWord/Shared/PCLZip + + + diff --git a/phpunit9.xml.dist b/phpunit9.xml.dist new file mode 100644 index 00000000000..ff0c676fad3 --- /dev/null +++ b/phpunit9.xml.dist @@ -0,0 +1,24 @@ + + + + + ./src + + + ./src/PhpWord/Shared/PCLZip + + + + + + + + + ./tests/PhpWordTests + + + + diff --git a/samples/Sample_09_Tables.php b/samples/Sample_09_Tables.php index 3a887468b05..4c4f68f83de 100644 --- a/samples/Sample_09_Tables.php +++ b/samples/Sample_09_Tables.php @@ -125,8 +125,8 @@ $row = $table->addRow(); $row->addCell(1000, ['vMerge' => 'continue']); -$row->addCell(1000)->addText('C'); -$row->addCell(1000)->addText('D'); +$row->addCell(500)->addText('C'); +$row->addCell(500)->addText('D'); $row->addCell(1000)->addText('3'); // 5. Nested table diff --git a/samples/Sample_14_ListItem.php b/samples/Sample_14_ListItem.php index f82c3ec0642..98c3042faf1 100644 --- a/samples/Sample_14_ListItem.php +++ b/samples/Sample_14_ListItem.php @@ -95,6 +95,34 @@ $section->addTitle('Heading 2', 2); $section->addTitle('Heading 3', 3); +$html = ' +

Loading from html, including type on nested list.

+
    +
  1. Items in the first level of the list +
      +
    1. Sub-item 1 (lcroman)
    2. +
    +
  2. +
  3.  Another item at the first level
  4. +
  5. Yet another item at the first level
  6. +
      +
    1. Sub-item 1 (lcalpha)
    2. +
    3. Sub-item 2
    4. +
    +
+

End of html load.

+'; +PhpOffice\PhpWord\Shared\Html::addHtml($section, $html, false, false); +$section->addText('Testing setNumId for new numbering start (there are better ways to do this).'); +$predefinedStyle = ['listType' => PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER]; +$section->addText('First List'); +$section->addListItem('First List Item 1', 0, null, $predefinedStyle); +$section->addListItem('First List Item 2', 0, null, $predefinedStyle); +$section->addText('Second List'); +$temp = $section->addListItem('Second List Item 1', 0, null, $predefinedStyle); +$temp->getStyle()->setNumId(0); +$section->addText('End of setNumId test.'); + // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); if (!CLI) { diff --git a/samples/Sample_20_BGColor.php b/samples/Sample_20_BGColor.php index e0ce4d64361..c72ae85a3ff 100644 --- a/samples/Sample_20_BGColor.php +++ b/samples/Sample_20_BGColor.php @@ -11,7 +11,7 @@ $section->addText( 'This is some text highlighted using fgColor (limited to 15 colors)', - ['fgColor' => PhpOffice\PhpWord\Style\Font::FGCOLOR_YELLOW] + ['fgColor' => PhpOffice\PhpWord\SimpleType\Color::YELLOW] ); $section->addText('This one uses bgColor and is using hex value (0xfbbb10)', ['bgColor' => 'fbbb10']); $section->addText('Compatible with font colors', ['color' => '0000ff', 'bgColor' => 'fbbb10']); diff --git a/samples/Sample_45_Autoloader.php b/samples/Sample_45_Autoloader.php index 71f443f7703..b6a375d04b6 100644 --- a/samples/Sample_45_Autoloader.php +++ b/samples/Sample_45_Autoloader.php @@ -2,7 +2,10 @@ use PhpOffice\PhpWord\Style\Font; -define('USE_AUTOLOADER', true); +// When following define is TRUE, +// this script will use PhpWord autoloader rather than Composer, +// and will not be able to find Pdf renderer. +define('USE_AUTOLOADER', getenv('DONTUSEAUTOLOADER') !== '1'); include_once 'Sample_Header.php'; diff --git a/samples/Sample_47_RTLTitles.php b/samples/Sample_47_RTLTitles.php new file mode 100644 index 00000000000..f17e146ad0c --- /dev/null +++ b/samples/Sample_47_RTLTitles.php @@ -0,0 +1,34 @@ +setDefaultFontName('DejaVu Sans'); // for good rendition of PDF +$rendererName = Settings::PDF_RENDERER_MPDF; +$rendererLibraryPath = $vendorDirPath . '/mpdf/mpdf'; +Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + +// Define styles for headers +$phpWord->addTitleStyle(1, ['bold' => true, 'name' => 'Arial', 'size' => 16], []); +$phpWord->addTitleStyle(2, ['bold' => true, 'name' => 'Arial', 'size' => 14], []); +$phpWord->addTitleStyle(3, ['bold' => true, 'name' => 'Arial', 'size' => 12], []); +$phpWord->addTitleStyle(4, ['bold' => true, 'name' => 'Arial', 'size' => 10], []); + +// New section +$section = $phpWord->addSection(); +$htmlContent = '

مرحبا 1

تجربة 2

تجربة تجربة

هناك hello هنا 4

مرحبا here كلمة انجليزي.

'; +SharedHtml::addHtml($section, $htmlContent, false, false); + +// Save file +echo write($phpWord, basename(__FILE__, '.php'), $writers); +if (!CLI) { + include_once 'Sample_Footer.php'; +} +Settings::setDefaultRtl(false); diff --git a/samples/Sample_47_SVG.php b/samples/Sample_47_SVG.php new file mode 100644 index 00000000000..30d6d4c3fa9 --- /dev/null +++ b/samples/Sample_47_SVG.php @@ -0,0 +1,47 @@ +addSection(); +$section->addText('SVG image without any styles:'); +$svg = $section->addImage(__DIR__ . '/resources/sample.svg'); + +printSeparator($section); + +$section->addText('SVG image with styles:'); +$svg = $section->addImage( + __DIR__ . '/resources/sample.svg', + [ + 'width' => 200, + 'height' => 200, + 'align' => 'center', + 'wrappingStyle' => PhpOffice\PhpWord\Style\Image::WRAPPING_STYLE_BEHIND, + ] +); + +function printSeparator(Section $section): void +{ + $section->addTextBreak(); + $lineStyle = ['weight' => 0.2, 'width' => 150, 'height' => 0, 'align' => 'center']; + $section->addLine($lineStyle); + $section->addTextBreak(2); +} + +// Save file +echo write($phpWord, basename(__FILE__, '.php'), $writers); +if (!CLI) { + include_once 'Sample_Footer.php'; +} diff --git a/samples/Sample_Header.php b/samples/Sample_Header.php index 57bb10a4c69..641220c0b8f 100644 --- a/samples/Sample_Header.php +++ b/samples/Sample_Header.php @@ -31,7 +31,14 @@ } // Set writers -$writers = ['Word2007' => 'docx', 'ODText' => 'odt', 'RTF' => 'rtf', 'HTML' => 'html', 'PDF' => 'pdf', 'EPub3' => 'epub']; +$writers = [ + 'Word2007' => 'docx', + 'RTF' => 'rtf', + 'HTML' => 'html', + 'PDF' => 'pdf', + 'EPub3' => 'epub', + 'ODText' => 'odt', // creates a lot of extra style - do it last +]; // Set PDF renderer if (null === Settings::getPdfRendererPath()) { diff --git a/samples/resources/sample.svg b/samples/resources/sample.svg new file mode 100644 index 00000000000..8a800c4a2c6 --- /dev/null +++ b/samples/resources/sample.svg @@ -0,0 +1,96 @@ + + + Official PHP Logo + + + + image/svg+xml + + Official PHP Logo + + + Colin Viebrock + + + + + + + + + + + + Copyright Colin Viebrock 1997 - All rights reserved. + + + 1997 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/PhpWord/Autoloader.php b/src/PhpWord/Autoloader.php index 122d9c05fd9..5bd0176d30a 100644 --- a/src/PhpWord/Autoloader.php +++ b/src/PhpWord/Autoloader.php @@ -38,10 +38,14 @@ public static function autoload(string $class): void if (!$file) { return; } + // We can't get here testing with Phpunit, which has + // already autoloaded everything relevant. + // @codeCoverageIgnoreStart if (file_exists($file)) { /** @noinspection PhpIncludeInspection Dynamic includes */ require_once $file; } + // @codeCoverageIgnoreEnd } } } diff --git a/src/PhpWord/Element/AbstractElement.php b/src/PhpWord/Element/AbstractElement.php index 3a29b686732..2413a4c1964 100644 --- a/src/PhpWord/Element/AbstractElement.php +++ b/src/PhpWord/Element/AbstractElement.php @@ -24,6 +24,7 @@ use PhpOffice\PhpWord\Media; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\Style\AbstractStyle; /** * Element abstract class. @@ -298,6 +299,10 @@ public function getCommentsRangeStart(): ?Comments /** * Get comment start. + * This code just plain doesn't work, but it's not clear what is should do. + * Unsurprisingly untested. + * + * @codeCoverageIgnore */ public function getCommentRangeStart(): ?Comment { @@ -342,6 +347,10 @@ public function getCommentsRangeEnd(): ?Comments /** * Get comment end. + * This code just plain doesn't work, but it's not clear what is should do. + * Unsurprisingly untested. + * + * @codeCoverageIgnore */ public function getCommentRangeEnd(): ?Comment { @@ -470,24 +479,26 @@ public function isInSection() } /** - * Set new style value. + * Set new style value. PR #2685. * * @param mixed $styleObject Style object - * @param null|array|string|Style $styleValue Style value + * @param null|AbstractStyle|array|string $styleValue Style value * @param bool $returnObject Always return object * * @return mixed */ protected function setNewStyle($styleObject, $styleValue = null, $returnObject = false) { - if (null !== $styleValue && is_array($styleValue)) { + if ($styleValue instanceof AbstractStyle && get_class($styleValue) === get_class($styleObject)) { + return $styleValue; + } + if (is_array($styleValue)) { $styleObject->setStyleByArray($styleValue); - $style = $styleObject; - } else { - $style = $returnObject ? $styleObject : $styleValue; + + return $styleObject; } - return $style; + return $returnObject ? $styleObject : $styleValue; } /** diff --git a/src/PhpWord/Element/Field.php b/src/PhpWord/Element/Field.php index 592d6e421ca..eba067e165b 100644 --- a/src/PhpWord/Element/Field.php +++ b/src/PhpWord/Element/Field.php @@ -94,7 +94,12 @@ class Field extends AbstractElement ], 'REF' => [ 'properties' => ['name' => ''], - 'options' => ['f', 'h', 'n', 'p', 'r', 't', 'w'], + 'options' => [ + 'f', 'h', 'n', 'p', 'r', 't', 'w', + 'd' => [',', '.', ''], + 'NumberSeperatorSequence' => [',', '.', ''], // grandfather typo + 'NumberSeparatorSequence' => [',', '.', ''], + ], ], ]; @@ -249,7 +254,8 @@ public function getProperties() public function setOptions(array $options = []) { foreach (array_keys($options) as $optionkey) { - if (!(isset($this->fieldsArray[$this->type]['options'][$optionkey])) && substr($optionkey, 0, 1) !== '\\') { + $optionkey = preg_replace('/^[\\\\](.)$/', '$1', $optionkey) ?? $optionkey; + if (!(isset($this->fieldsArray[$this->type]['options'][$optionkey]))) { throw new InvalidArgumentException("Invalid option '$optionkey', possible values are " . implode(', ', $this->fieldsArray[$this->type]['options'])); } } diff --git a/src/PhpWord/Element/Image.php b/src/PhpWord/Element/Image.php index c47e5effa5d..bc9039c4373 100644 --- a/src/PhpWord/Element/Image.php +++ b/src/PhpWord/Element/Image.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Element; +use DOMDocument; use PhpOffice\PhpWord\Exception\CreateTemporaryFileException; use PhpOffice\PhpWord\Exception\InvalidImageException; use PhpOffice\PhpWord\Exception\UnsupportedImageTypeException; @@ -373,7 +374,9 @@ public function getImageString(): ?string // Read image binary data and convert to hex/base64 string if ($this->sourceType == self::SOURCE_GD) { - $imageResource = call_user_func($this->imageCreateFunc, $actualSource); + /** @var callable */ + $callable = $this->imageCreateFunc; + $imageResource = call_user_func($callable, $actualSource); if ($this->imageType === 'image/png') { // PNG images need to preserve alpha channel information imagesavealpha($imageResource, true); @@ -432,6 +435,20 @@ private function checkImage(): void { $this->setSourceType(); + $ext = strtolower(pathinfo($this->source, PATHINFO_EXTENSION)); + if ($ext === 'svg') { + [$actualWidth, $actualHeight] = $this->getSvgDimensions($this->source); + $this->imageType = 'image/svg+xml'; + $this->imageExtension = 'svg'; + $this->imageFunc = null; + $this->imageQuality = null; + $this->memoryImage = false; + $this->sourceType = self::SOURCE_LOCAL; + $this->setProportionalSize($actualWidth, $actualHeight); + + return; + } + // Check image data if ($this->sourceType == self::SOURCE_ARCHIVE) { $imageData = $this->getArchiveImageSize($this->source); @@ -598,4 +615,38 @@ private function setProportionalSize($actualWidth, $actualHeight): void } } } + + public function getSvgDimensions(string $file): array + { + $xml = @file_get_contents($file); + if ($xml === false) { + throw new InvalidImageException("Impossible de lire le fichier SVG: $file"); + } + libxml_use_internal_errors(true); + $dom = new DOMDocument(); + if (!$dom->loadXML($xml)) { + throw new InvalidImageException('SVG invalide ou mal formé'); + } + $svg = $dom->documentElement; + + $wAttr = round((float) $svg->getAttribute('width')); + $hAttr = round((float) $svg->getAttribute('height')); + + $w = (int) filter_var($wAttr, FILTER_SANITIZE_NUMBER_INT); + $h = (int) filter_var($hAttr, FILTER_SANITIZE_NUMBER_INT); + + if ($w <= 0 || $h <= 0) { + $vb = $svg->getAttribute('viewBox'); + if (preg_match('/^\s*[\d.+-]+[\s,]+[\d.+-]+[\s,]+([\d.+-]+)[\s,]+([\d.+-]+)\s*$/', $vb, $m)) { + $w = (int) round((float) $m[1]); + $h = (int) round((float) $m[2]); + } + } + + if ($w <= 0 || $h <= 0) { + throw new InvalidImageException('Impossible de déterminer width/height ou viewBox valides pour le SVG'); + } + + return [$w, $h]; + } } diff --git a/src/PhpWord/Element/Section.php b/src/PhpWord/Element/Section.php index 0ae00aa9f6e..dc5d4a5177e 100644 --- a/src/PhpWord/Element/Section.php +++ b/src/PhpWord/Element/Section.php @@ -61,7 +61,7 @@ class Section extends AbstractContainer * Create new instance. * * @param int $sectionCount - * @param null|array|\PhpOffice\PhpWord\Style|string $style + * @param null|array|SectionStyle|string $style */ public function __construct($sectionCount, $style = null) { diff --git a/src/PhpWord/Element/TextRun.php b/src/PhpWord/Element/TextRun.php index 0c9a2322a71..7b9e3055a03 100644 --- a/src/PhpWord/Element/TextRun.php +++ b/src/PhpWord/Element/TextRun.php @@ -40,7 +40,7 @@ class TextRun extends AbstractContainer /** * Create new instance. * - * @param array|Paragraph|string $paragraphStyle + * @param null|array|Paragraph|string $paragraphStyle */ public function __construct($paragraphStyle = null) { @@ -60,7 +60,7 @@ public function getParagraphStyle() /** * Set Paragraph style. * - * @param array|Paragraph|string $style + * @param null|array|Paragraph|string $style * * @return Paragraph|string */ diff --git a/src/PhpWord/Escaper/Rtf.php b/src/PhpWord/Escaper/Rtf.php index 815e3a27517..4e5b833b3d0 100644 --- a/src/PhpWord/Escaper/Rtf.php +++ b/src/PhpWord/Escaper/Rtf.php @@ -25,11 +25,15 @@ */ class Rtf extends AbstractEscaper { + /** @return string */ protected function escapeAsciiCharacter($code) { if ($code == 9) { return '{\\tab}'; } + if ($code === 10) { + return ''; // or maybe '\par' + } if (0x20 > $code || $code >= 0x80) { return '{\\u' . $code . '}'; } @@ -42,55 +46,27 @@ protected function escapeAsciiCharacter($code) protected function escapeMultibyteCharacter($code) { - return '\\uc0{\\u' . $code . '}'; + if ($code > 32767) { + return '\\uc0\\u' . ((int) $code - 65536) . ' '; + } + + return '\\uc0\\u' . $code . ' '; } /** - * @see http://www.randomchaos.com/documents/?source=php_and_unicode - * * @param ?string $input */ protected function escapeSingleValue($input) { $escapedValue = ''; - - $numberOfBytes = 1; - $bytes = []; - for ($i = 0; $i < strlen($input); ++$i) { - $character = $input[$i]; - $asciiCode = ord($character); - - if ($asciiCode < 128) { - $escapedValue .= $this->escapeAsciiCharacter($asciiCode); + $utf16 = (string) mb_convert_encoding($input, 'UTF-16BE', 'UTF-8'); + $utf16len = strlen($utf16); + for ($i = 0; $i < $utf16len; $i += 2) { + $code = (ord($utf16[$i]) << 8) | ord($utf16[$i + 1]); + if ($code <= 0x7f) { + $escapedValue .= $this->escapeAsciiCharacter($code); } else { - if (0 == count($bytes)) { - if ($asciiCode < 224) { - $numberOfBytes = 2; - } elseif ($asciiCode < 240) { - $numberOfBytes = 3; - } elseif ($asciiCode < 248) { - $numberOfBytes = 4; - } - } - - $bytes[] = $asciiCode; - - if ($numberOfBytes == count($bytes)) { - if (4 == $numberOfBytes) { - $multibyteCode = ($bytes[0] % 8) * 262144 + ($bytes[1] % 64) * 4096 + ($bytes[2] % 64) * 64 + ($bytes[3] % 64); - } elseif (3 == $numberOfBytes) { - $multibyteCode = ($bytes[0] % 16) * 4096 + ($bytes[1] % 64) * 64 + ($bytes[2] % 64); - } else { - $multibyteCode = ($bytes[0] % 32) * 64 + ($bytes[1] % 64); - } - - if (65279 != $multibyteCode) { - $escapedValue .= $multibyteCode < 128 ? $this->escapeAsciiCharacter($multibyteCode) : $this->escapeMultibyteCharacter($multibyteCode); - } - - $numberOfBytes = 1; - $bytes = []; - } + $escapedValue .= $this->escapeMultibyteCharacter($code); } } diff --git a/src/PhpWord/Helper/Handler.php b/src/PhpWord/Helper/Handler.php new file mode 100644 index 00000000000..e95d871e00a --- /dev/null +++ b/src/PhpWord/Helper/Handler.php @@ -0,0 +1,47 @@ +rtfWidowControl; + } + + public function setRtfWidowControl(bool $rtfWidowControl): self + { + $this->rtfWidowControl = $rtfWidowControl; + + return $this; + } } diff --git a/src/PhpWord/PhpWord.php b/src/PhpWord/PhpWord.php index c3200fe8573..de7f3e08748 100644 --- a/src/PhpWord/PhpWord.php +++ b/src/PhpWord/PhpWord.php @@ -21,6 +21,8 @@ use BadMethodCallException; use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\Exception\Exception; +use PhpOffice\PhpWord\Style\Font; +use PhpOffice\PhpWord\Style\Section as StyleSection; /** * PHPWord main class. @@ -212,7 +214,7 @@ public function getSection($index) /** * Create new section. * - * @param null|array|string $style + * @param null|array|string|StyleSection $style * * @return Section */ @@ -294,7 +296,7 @@ public function getDefaultFontColor(): string /** * Get default font size. * - * @return int + * @return float|int */ public function getDefaultFontSize() { @@ -318,9 +320,9 @@ public function setDefaultFontSize($fontSize): void * * @return Style\Paragraph */ - public function setDefaultParagraphStyle($styles) + public function setDefaultParagraphStyle($styles, ?Font $fontStyles = null) { - return Style::setDefaultParagraphStyle($styles); + return Style::setDefaultParagraphStyle($styles, $fontStyles); } /** @@ -361,6 +363,16 @@ public function save($filename, $format = 'Word2007', $download = false) return true; } + public static function noPhar(string $path): void + { + $dontUse = false; + if (filter_var($path, FILTER_VALIDATE_URL) || (preg_match('/^([\w\s\x00-\x1f]+):/u', $path) && !preg_match('/^([\w]+):/u', $path))) { + if (!preg_match('~^((s3):)|(php://(output|memory|temp)$)~', $path)) { + throw new Exception('Invalid protocol used in filename'); + } + } + } + /** * Create new section. * diff --git a/src/PhpWord/Reader/MsDoc.php b/src/PhpWord/Reader/MsDoc.php index 7cbeb1a8ac4..3db4e5a7217 100644 --- a/src/PhpWord/Reader/MsDoc.php +++ b/src/PhpWord/Reader/MsDoc.php @@ -40,23 +40,31 @@ class MsDoc extends AbstractReader implements ReaderInterface /** * WordDocument Stream. + * + * @var mixed */ - private $dataWorkDocument; + protected $dataWorkDocument; /** * 1Table Stream. + * + * @var mixed */ - private $data1Table; + protected $data1Table; /** * Data Stream. + * + * @var mixed */ - private $dataData; + protected $dataData; /** * Object Pool Stream. + * + * @var mixed */ - private $dataObjectPool; + protected $dataObjectPool; /** * @var stdClass[] @@ -69,7 +77,7 @@ class MsDoc extends AbstractReader implements ReaderInterface private $arrayFib = []; /** - * @var string[] + * @var array> */ private $arrayFonts = []; @@ -84,10 +92,10 @@ class MsDoc extends AbstractReader implements ReaderInterface private $arraySections = []; /** @var string */ - private $summaryInformation; + protected $summaryInformation; /** @var string */ - private $documentSummaryInformation; + protected $documentSummaryInformation; const VERSION_97 = '97'; const VERSION_2000 = '2000'; @@ -162,11 +170,24 @@ private function loadOLE($filename): void $this->documentSummaryInformation = $ole->getStream($ole->docSummaryInfos); } + /** + * @param int $lcb + * @param int $iSize + * + * @return int + */ private function getNumInLcb($lcb, $iSize) { return ($lcb - 4) / (4 + $iSize); } + /** + * @param string $data + * @param int $posMem + * @param int $iNum + * + * @return array + */ private function getArrayCP($data, $posMem, $iNum) { $arrayCP = []; @@ -183,6 +204,8 @@ private function getArrayCP($data, $posMem, $iNum) * @see https://igor.io/2012/09/24/binary-parsing.html * * @param string $data + * + * @return int */ private function readFib($data) { @@ -368,6 +391,13 @@ private function readFib($data) return $pos; } + /** + * @param string $data + * @param int $pos + * @param string $version + * + * @return int + */ private function readBlockFibRgFcLcb($data, $pos, $version) { if ($version == self::VERSION_97) { @@ -1218,7 +1248,7 @@ private function readRecordSttbfFfn(): void $char = self::getInt2d($this->data1Table, $posMem); $posMem += 2; if ($char > 0) { - $xszFfn .= chr($char); + $xszFfn .= mb_chr($char, 'UTF-8'); } } while ($char != 0); // xszAlt @@ -1230,7 +1260,7 @@ private function readRecordSttbfFfn(): void if ($char == 0) { break; } - $xszAlt .= chr($char); + $xszAlt .= mb_chr($char, 'UTF-8'); } while ($char != 0); } $this->arrayFonts[] = [ @@ -1514,6 +1544,8 @@ private function readRecordPlcfBteChpx(): void } /** + * @param int $sprm + * * @return stdClass */ private function readSprm($sprm) @@ -1598,9 +1630,9 @@ private function readSprmSpra($data, $pos, $oSprm) } /** - * @param $data int - * @param $pos int - * @param $cbNum int + * @param string $data + * @param int $pos + * @param int $cbNum * * @return stdClass * diff --git a/src/PhpWord/Reader/RTF.php b/src/PhpWord/Reader/RTF.php index dbf1ebd7ad8..eb7317fb5a2 100644 --- a/src/PhpWord/Reader/RTF.php +++ b/src/PhpWord/Reader/RTF.php @@ -42,7 +42,7 @@ public function load($docFile) if ($this->canRead($docFile)) { $doc = new Document(); - $doc->rtf = file_get_contents($docFile); + $doc->rtf = (string) file_get_contents($docFile); $doc->read($phpWord); } else { throw new Exception("Cannot read {$docFile}."); diff --git a/src/PhpWord/Reader/RTF/Document.php b/src/PhpWord/Reader/RTF/Document.php index 59f0a7dd59c..e282ebcd102 100644 --- a/src/PhpWord/Reader/RTF/Document.php +++ b/src/PhpWord/Reader/RTF/Document.php @@ -344,6 +344,8 @@ private function parseControl($control, $parameter): void array_shift($directives); // remove the function variable; we won't need it $this->$function($directives); } + } elseif ($control === 'widowctrl') { + $this->phpWord->getSettings()->setRtfWidowControl(true); } } @@ -389,7 +391,9 @@ private function readText(): void { $text = $this->textrun->addText($this->text); if (isset($this->flags['styles']['font'])) { - $text->getFontStyle()->setStyleByArray($this->flags['styles']['font']); + /** @var \PhpOffice\PhpWord\Style\Font */ + $temp = $text->getFontStyle(); + $temp->setStyleByArray($this->flags['styles']['font']); } } } diff --git a/src/PhpWord/Reader/ReaderInterface.php b/src/PhpWord/Reader/ReaderInterface.php index 1dced12aba4..f1f4fb9e903 100644 --- a/src/PhpWord/Reader/ReaderInterface.php +++ b/src/PhpWord/Reader/ReaderInterface.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Reader; +use PhpOffice\PhpWord\PhpWord; + /** * Reader interface. * @@ -38,6 +40,8 @@ public function canRead($filename); * Loads PhpWord from file. * * @param string $filename + * + * @return PhpWord */ public function load($filename); } diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 9d49573d690..b59970cf54f 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -369,7 +369,11 @@ private function readFormField(XMLReader $xmlReader, array $domNodes, $parent, $ } $formField->setEntries($listEntries); if (null !== $formField->getValue()) { - $formField->setText($listEntries[$formField->getValue()]); + /** @var int */ + $temp = $formField->getValue(); + $formField->setText( + $listEntries[$temp] + ); } break; @@ -571,11 +575,7 @@ protected function readRunChild(XMLReader $xmlReader, DOMElement $node, Abstract if ($runParent->nodeName == 'w:hyperlink') { $rId = $xmlReader->getAttribute('r:id', $runParent); $target = $this->getMediaTarget($docPart, $rId); - if (null !== $target) { - $parent->addLink($target, $textContent, $fontStyle, $paragraphStyle); - } else { - $parent->addText($textContent, $fontStyle, $paragraphStyle); - } + $parent->addLink($target ?? 'http://example.com', $textContent, $fontStyle, $paragraphStyle); } else { /** @var AbstractElement $element */ $element = $parent->addText($textContent, $fontStyle, $paragraphStyle); @@ -793,35 +793,46 @@ protected function readTableStyle(XMLReader $xmlReader, DOMElement $domNode) $borders = array_merge($margins, ['insideH', 'insideV']); if ($xmlReader->elementExists('w:tblPr', $domNode)) { + $tblStyleName = ''; if ($xmlReader->elementExists('w:tblPr/w:tblStyle', $domNode)) { - $style = $xmlReader->getAttribute('w:val', $domNode, 'w:tblPr/w:tblStyle'); - } else { - $styleNode = $xmlReader->getElement('w:tblPr', $domNode); - $styleDefs = []; - foreach ($margins as $side) { - $ucfSide = ucfirst($side); - $styleDefs["cellMargin$ucfSide"] = [self::READ_VALUE, "w:tblCellMar/w:$side", 'w:w']; - } - foreach ($borders as $side) { - $ucfSide = ucfirst($side); - $styleDefs["border{$ucfSide}Size"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:sz']; - $styleDefs["border{$ucfSide}Color"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:color']; - $styleDefs["border{$ucfSide}Style"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:val']; - } - $styleDefs['layout'] = [self::READ_VALUE, 'w:tblLayout', 'w:type']; - $styleDefs['bidiVisual'] = [self::READ_TRUE, 'w:bidiVisual']; - $styleDefs['cellSpacing'] = [self::READ_VALUE, 'w:tblCellSpacing', 'w:w']; - $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); - - $tablePositionNode = $xmlReader->getElement('w:tblpPr', $styleNode); - if ($tablePositionNode !== null) { - $style['position'] = $this->readTablePosition($xmlReader, $tablePositionNode); - } + $tblStyleName = $xmlReader->getAttribute('w:val', $domNode, 'w:tblPr/w:tblStyle'); + } + $styleNode = $xmlReader->getElement('w:tblPr', $domNode); + $styleDefs = []; - $indentNode = $xmlReader->getElement('w:tblInd', $styleNode); - if ($indentNode !== null) { - $style['indent'] = $this->readTableIndent($xmlReader, $indentNode); - } + foreach ($margins as $side) { + $ucfSide = ucfirst($side); + $styleDefs["cellMargin$ucfSide"] = [self::READ_VALUE, "w:tblCellMar/w:$side", 'w:w']; + } + foreach ($borders as $side) { + $ucfSide = ucfirst($side); + $styleDefs["border{$ucfSide}Size"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:sz']; + $styleDefs["border{$ucfSide}Color"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:color']; + $styleDefs["border{$ucfSide}Style"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:val']; + } + $styleDefs['layout'] = [self::READ_VALUE, 'w:tblLayout', 'w:type']; + $styleDefs['bidiVisual'] = [self::READ_TRUE, 'w:bidiVisual']; + $styleDefs['cellSpacing'] = [self::READ_VALUE, 'w:tblCellSpacing', 'w:w']; + $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); + + $tablePositionNode = $xmlReader->getElement('w:tblpPr', $styleNode); + if ($tablePositionNode !== null) { + $style['position'] = $this->readTablePosition($xmlReader, $tablePositionNode); + } + + $indentNode = $xmlReader->getElement('w:tblInd', $styleNode); + if ($indentNode !== null) { + $style['indent'] = $this->readTableIndent($xmlReader, $indentNode); + } + if ($xmlReader->elementExists('w:basedOn', $domNode)) { + $style['basedOn'] = $xmlReader->getAttribute('w:val', $domNode, 'w:basedOn'); + } + if ($tblStyleName !== '') { + $style['tblStyle'] = $tblStyleName; + } + // this may be unneeded + if ($xmlReader->elementExists('w:name', $domNode)) { + $style['styleName'] = $xmlReader->getAttribute('w:val', $domNode, 'w:name'); } } diff --git a/src/PhpWord/Reader/Word2007/Footnotes.php b/src/PhpWord/Reader/Word2007/Footnotes.php index 0b259e926a4..ee2e182e108 100644 --- a/src/PhpWord/Reader/Word2007/Footnotes.php +++ b/src/PhpWord/Reader/Word2007/Footnotes.php @@ -81,16 +81,19 @@ public function read(PhpWord $phpWord): void */ private function getElement(PhpWord $phpWord, $relationId) { + $returnVal = null; $getMethod = "get{$this->collection}"; $collection = $phpWord->$getMethod()->getItems(); //not found by key, looping to search by relationId foreach ($collection as $collectionElement) { if ($collectionElement->getRelationId() == $relationId) { - return $collectionElement; + $returnVal = $collectionElement; + + break; } } - return null; + return $returnVal; } } diff --git a/src/PhpWord/Reader/Word2007/Styles.php b/src/PhpWord/Reader/Word2007/Styles.php index d0777c30269..0bcc96b75e3 100644 --- a/src/PhpWord/Reader/Word2007/Styles.php +++ b/src/PhpWord/Reader/Word2007/Styles.php @@ -68,10 +68,9 @@ public function read(PhpWord $phpWord): void if ($nodes->length > 0) { foreach ($nodes as $node) { $type = $xmlReader->getAttribute('w:type', $node); - $name = $xmlReader->getAttribute('w:val', $node, 'w:name'); - if (null === $name) { - $name = $xmlReader->getAttribute('w:styleId', $node); - } + $namex = $xmlReader->getAttribute('w:val', $node, 'w:name'); + $styleId = $xmlReader->getAttribute('w:styleId', $node); + $name = ($namex !== null) ? $namex : $styleId; $headingMatches = []; preg_match('/Heading\s*(\d)/i', $name, $headingMatches); // $default = ($xmlReader->getAttribute('w:default', $node) == 1); @@ -102,7 +101,8 @@ public function read(PhpWord $phpWord): void case 'table': $tStyle = $this->readTableStyle($xmlReader, $node); if (!empty($tStyle)) { - $phpWord->addTableStyle($name, $tStyle); + $newTable = $phpWord->addTableStyle($styleId, $tStyle); + $newTable->setStyleName($name); } break; diff --git a/src/PhpWord/Settings.php b/src/PhpWord/Settings.php index 16f49166fa8..89f8c488847 100644 --- a/src/PhpWord/Settings.php +++ b/src/PhpWord/Settings.php @@ -16,6 +16,8 @@ namespace PhpOffice\PhpWord; +use PhpOffice\PhpWord\SimpleType\TextDirection; + /** * PHPWord settings class. * @@ -453,6 +455,9 @@ public static function setDefaultFontSize($value): bool public static function setDefaultRtl(?bool $defaultRtl): void { self::$defaultRtl = $defaultRtl; + if ($defaultRtl === true && Style::getStyle('Normal') === null) { + Style::setDefaultParagraphStyle(['bidi' => true, 'textDirection' => TextDirection::RLTB], ['rtl' => true]); + } } public static function isDefaultRtl(): ?bool @@ -524,4 +529,22 @@ public static function setDefaultPaper(string $value): bool return false; } + + public static function restoreDefaults(): void + { + self::$defaultAsianFontName = self::DEFAULT_FONT_NAME; + self::$defaultFontColor = self::DEFAULT_FONT_COLOR; + self::$defaultFontName = self::DEFAULT_FONT_NAME; + self::$defaultFontSize = self::DEFAULT_FONT_SIZE; + self::$defaultPaper = self::DEFAULT_PAPER; + self::$defaultRtl = null; + self::$measurementUnit = self::UNIT_TWIP; + self::$outputEscapingEnabled = false; + self::$pdfRendererName = null; + self::$pdfRendererOptions = []; + self::$pdfRendererPath = null; + self::$tempDir = ''; + self::$xmlWriterCompatibility = true; + self::$zipClass = self::ZIPARCHIVE; + } } diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php index 17d2e1a05dc..5a53885c404 100644 --- a/src/PhpWord/Shared/Converter.php +++ b/src/PhpWord/Shared/Converter.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Shared; +use PhpOffice\PhpWord\SimpleType\Color; + /** * Common converter functions. */ @@ -40,7 +42,7 @@ class Converter */ public static function cmToTwip($centimeter = 1) { - return $centimeter / self::INCH_TO_CM * self::INCH_TO_TWIP; + return $centimeter * self::INCH_TO_TWIP / self::INCH_TO_CM; } /** @@ -64,7 +66,7 @@ public static function cmToInch($centimeter = 1) */ public static function cmToPixel($centimeter = 1) { - return $centimeter / self::INCH_TO_CM * self::INCH_TO_PIXEL; + return $centimeter * self::INCH_TO_PIXEL / self::INCH_TO_CM; } /** @@ -76,7 +78,7 @@ public static function cmToPixel($centimeter = 1) */ public static function cmToPoint($centimeter = 1) { - return $centimeter / self::INCH_TO_CM * self::INCH_TO_POINT; + return $centimeter * self::INCH_TO_POINT / self::INCH_TO_CM; } /** @@ -88,7 +90,7 @@ public static function cmToPoint($centimeter = 1) */ public static function cmToEmu($centimeter = 1) { - return round($centimeter / self::INCH_TO_CM * self::INCH_TO_PIXEL * self::PIXEL_TO_EMU); + return round($centimeter * self::INCH_TO_PIXEL * self::PIXEL_TO_EMU / self::INCH_TO_CM); } /** @@ -160,7 +162,7 @@ public static function inchToEmu($inch = 1) */ public static function pixelToTwip($pixel = 1) { - return $pixel / self::INCH_TO_PIXEL * self::INCH_TO_TWIP; + return $pixel * self::INCH_TO_TWIP / self::INCH_TO_PIXEL; } /** @@ -172,7 +174,7 @@ public static function pixelToTwip($pixel = 1) */ public static function pixelToCm($pixel = 1) { - return $pixel / self::INCH_TO_PIXEL * self::INCH_TO_CM; + return $pixel * self::INCH_TO_CM / self::INCH_TO_PIXEL; } /** @@ -184,7 +186,7 @@ public static function pixelToCm($pixel = 1) */ public static function pixelToPoint($pixel = 1) { - return $pixel / self::INCH_TO_PIXEL * self::INCH_TO_POINT; + return $pixel * self::INCH_TO_POINT / self::INCH_TO_PIXEL; } /** @@ -208,7 +210,7 @@ public static function pixelToEmu($pixel = 1) */ public static function pointToTwip($point = 1) { - return $point / self::INCH_TO_POINT * self::INCH_TO_TWIP; + return $point * self::INCH_TO_TWIP / self::INCH_TO_POINT; } /** @@ -220,7 +222,7 @@ public static function pointToTwip($point = 1) */ public static function pointToPixel($point = 1) { - return $point / self::INCH_TO_POINT * self::INCH_TO_PIXEL; + return $point * self::INCH_TO_PIXEL / self::INCH_TO_POINT; } /** @@ -232,7 +234,7 @@ public static function pointToPixel($point = 1) */ public static function pointToEmu($point = 1) { - return round($point / self::INCH_TO_POINT * self::INCH_TO_PIXEL * self::PIXEL_TO_EMU); + return round($point * self::INCH_TO_PIXEL * self::PIXEL_TO_EMU / self::INCH_TO_POINT); } /** @@ -244,7 +246,7 @@ public static function pointToEmu($point = 1) */ public static function pointToCm($point = 1) { - return $point / self::INCH_TO_POINT * self::INCH_TO_CM; + return $point * self::INCH_TO_CM / self::INCH_TO_POINT; } /** @@ -268,7 +270,7 @@ public static function emuToPixel($emu = 1) */ public static function picaToPoint($pica = 1) { - return $pica / self::INCH_TO_PICA * self::INCH_TO_POINT; + return $pica * self::INCH_TO_POINT / self::INCH_TO_PICA; } /** @@ -295,6 +297,54 @@ public static function angleToDegree($angle = 1) return round($angle / self::DEGREE_TO_ANGLE); } + private const STRING_TO_RGB = [ + Color::AQUA => '00FFFF', + Color::BLACK => '000000', + Color::BLUE => '0000FF', + Color::BROWN => 'A52A2A', + Color::CYAN => '00FFFF', + Color::DARKBLUE => '00008B', + Color::DARKCYAN => '008B8B', + Color::DARKGRAY => 'A9A9A9', + Color::DARKGREEN => '006400', + Color::DARKMAGENTA => '8B008B', + Color::DARKORANGE => 'FF8C00', + Color::DARKRED => '8B0000', + Color::DARKVIOLET => '9400D3', + Color::DARKYELLOW => '808000', + Color::FUCHSIA => 'FF00FF', + Color::GOLD => 'FFD700', + Color::GRAY => '808080', + Color::GREEN => '008000', + Color::LIGHTBLUE => 'ADD8E6', + Color::LIGHTCYAN => 'E0FFFF', + Color::LIGHTGRAY => 'D3D3D3', + Color::LIGHTGREEN => '90EE90', + Color::LIGHTPINK => 'FFB6C1', + Color::LIGHTYELLOW => 'FFFFE0', + Color::LIME => '00FF00', + Color::MAGENTA => 'FF00FF', + Color::MAROON => '800000', + Color::NAVY => '000080', + Color::OLIVE => '808000', + Color::ORANGE => 'FFA500', + Color::PINK => 'FFC0CB', + Color::PURPLE => '800080', + Color::RED => 'FF0000', + Color::SILVER => 'C0C0C0', + Color::TAN => 'D2B48C', + Color::TEAL => '008080', + Color::TURQUOISE => '40E0D0', + Color::VIOLET => 'EE82EE', + Color::WHITE => 'FFFFFF', + Color::YELLOW => 'FFFF00', + ]; + + public static function validStringColor(string $value): bool + { + return (self::STRING_TO_RGB[$value] ?? null) !== null; + } + /** * Convert colorname as string to RGB. * @@ -304,40 +354,7 @@ public static function angleToDegree($angle = 1) */ public static function stringToRgb($value) { - switch ($value) { - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_YELLOW: - return 'FFFF00'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_LIGHTGREEN: - return '90EE90'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_CYAN: - return '00FFFF'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_MAGENTA: - return 'FF00FF'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_BLUE: - return '0000FF'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_RED: - return 'FF0000'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKBLUE: - return '00008B'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKCYAN: - return '008B8B'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKGREEN: - return '006400'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKMAGENTA: - return '8B008B'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKRED: - return '8B0000'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKYELLOW: - return '8B8B00'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKGRAY: - return 'A9A9A9'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_LIGHTGRAY: - return 'D3D3D3'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_BLACK: - return '000000'; - } - - return $value; + return self::STRING_TO_RGB[$value] ?? $value; } /** @@ -447,10 +464,12 @@ public static function cssToCm($value) * * @param string $value * - * @return float + * @return ?float */ public static function cssToEmu($value) { - return self::pointToEmu(self::cssToPoint($value)); + $point = self::cssToPoint($value); + + return ($point === null) ? null : self::pointToEmu($point); } } diff --git a/src/PhpWord/Shared/Css.php b/src/PhpWord/Shared/Css.php index 93f69000b45..c26940b1a6f 100644 --- a/src/PhpWord/Shared/Css.php +++ b/src/PhpWord/Shared/Css.php @@ -41,19 +41,17 @@ public function process(): void $cssContent = str_replace(["\r", "\n"], '', $this->cssContent); preg_match_all('/(.+?)\s?\{\s?(.+?)\s?\}/', $cssContent, $cssExtracted); // 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; + if (count($cssExtracted[1]) === count($cssExtracted[2])) { + 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); } - [$key, $value] = explode(':', trim($rule)); - $this->styles[$this->sanitize($selector)][$this->sanitize($key)] = $this->sanitize($value); } } } diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 6ee6399fd60..ce4e922fa64 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -27,10 +27,15 @@ use PhpOffice\PhpWord\Element\Row; use PhpOffice\PhpWord\Element\Table; use PhpOffice\PhpWord\Element\TextRun; +use PhpOffice\PhpWord\Metadata\DocInfo; +use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\SimpleType\Border; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\NumberFormat; +use PhpOffice\PhpWord\SimpleType\TextDirection; use PhpOffice\PhpWord\Style\Paragraph; +use Throwable; /** * Common Html functions. @@ -39,14 +44,24 @@ */ class Html { + private const SPECIAL_BORDER_WIDTHS = ['thin' => '0.5pt', 'thick' => '3.5pt', 'medium' => '2.0pt']; + private const RGB_REGEXP = '/^\s*rgb\s*[(]\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*[)]\s*$/'; + private const DECLARES_CHARSET = '/ charset=/i'; + protected static $listIndex = 0; protected static $xpath; protected static $options; + /** @var ?DocInfo */ + protected static $docInfo; + + /** @var bool */ + private static $addbody = false; + /** * @var Css */ @@ -71,51 +86,111 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit * which could be applied when such an element occurs in the parseNode function. */ static::$options = $options; + static::$docInfo = null; + if (method_exists($element, 'getPhpWord')) { + /** @var ?PhpWord */ + $phpWord = $element->getPhpWord(); + if ($phpWord !== null) { + static::$docInfo = $phpWord->getDocInfo(); + } + } - // Preprocess: remove all line ends, decode HTML entity, - // fix ampersand and angle brackets and add body tag for HTML fragments - $html = str_replace(["\n", "\r"], '', $html); - $html = str_replace(['<', '>', '&', '"'], ['_lt_', '_gt_', '_amp_', '_quot_'], $html); - $html = html_entity_decode($html, ENT_QUOTES, 'UTF-8'); - $html = str_replace('&', '&', $html); - $html = str_replace(['_lt_', '_gt_', '_amp_', '_quot_'], ['<', '>', '&', '"'], $html); - - if (false === $fullHTML) { - $html = '' . $html . ''; + if (substr($html, 0, 2) === "\xfe\xff" || substr($html, 0, 2) === "\xff\xfe") { + $html = (string) mb_convert_encoding($html, 'UTF-8', 'UTF-16'); + } + if (substr($html, 0, 3) === "\xEF\xBB\xBF") { + $html = substr($html, 3); + } + if (self::$addbody && false === $fullHTML) { + $html = '' . $html . ''; // @codeCoverageIgnore } // Load DOM if (\PHP_VERSION_ID < 80000) { - $orignalLibEntityLoader = libxml_disable_entity_loader(true); + $orignalLibEntityLoader = libxml_disable_entity_loader(true); // @codeCoverageIgnore } $dom = new DOMDocument(); + $html = self::replaceNonAsciiIfNeeded($html); $dom->preserveWhiteSpace = $preserveWhiteSpace; - $dom->loadXML($html); + + try { + $result = @$dom->loadHTML($html); + $exceptionMessage = 'DOM loadHTML failed'; + } catch (Throwable $e) { + $result = false; + $exceptionMessage = $e->getMessage(); + } + if ($result === false) { + throw new Exception($exceptionMessage); + } + self::removeAnnoyingWhitespaceTextNodes($dom); static::$xpath = new DOMXPath($dom); - $node = $dom->getElementsByTagName('body'); + $node = $dom->getElementsByTagName('html'); + if (count($node) === 0 || $node->item(0) === null) { + $node = $dom->getElementsByTagName('body'); // @codeCoverageIgnore + } static::parseNode($node->item(0), $element); if (\PHP_VERSION_ID < 80000) { - libxml_disable_entity_loader($orignalLibEntityLoader); + libxml_disable_entity_loader($orignalLibEntityLoader); // @codeCoverageIgnore + } + } + + // https://www.php.net/manual/en/domdocument.loadhtml.php + private static function removeAnnoyingWhitespaceTextNodes(DOMNode $node): void + { + if ($node->hasChildNodes()) { + for ($i = $node->childNodes->length - 1; $i >= 0; --$i) { + self::removeAnnoyingWhitespaceTextNodes($node->childNodes->item($i)); + } } + if ($node->nodeType === XML_TEXT_NODE && !$node->hasChildNodes() && !$node->hasAttributes() && empty(trim($node->textContent))) { + $node->parentNode->removeChild($node); + } + } + + private static function replaceNonAscii(array $matches): string + { + return '&#' . mb_ord($matches[0], 'UTF-8') . ';'; + } + + private static function replaceNonAsciiIfNeeded(string $convert): ?string + { + if (preg_match(self::DECLARES_CHARSET, $convert) !== 1) { + $lowend = "\u{80}"; + $highend = "\u{10ffff}"; + $regexp = "/[$lowend-$highend]/u"; + /** @var callable $callback */ + $callback = [self::class, 'replaceNonAscii']; + $convert = preg_replace_callback($regexp, $callback, $convert); + } + + return $convert; } /** * parse Inline style of a node. * * @param DOMNode $node Node to check on attributes and to compile a style array - * @param array $styles is supplied, the inline style attributes are added to the already existing style + * @param array $styles is supplied, the inline style attributes are added to the already existing style * * @return array */ - protected static function parseInlineStyle($node, $styles = []) + protected static function parseInlineStyle($node, &$styles) { if (XML_ELEMENT_NODE == $node->nodeType) { $attributes = $node->attributes; // get all the attributes(eg: id, class) - $attributeDir = $attributes->getNamedItem('dir'); - $attributeDirValue = $attributeDir ? $attributeDir->nodeValue : ''; - $bidi = $attributeDirValue === 'rtl'; + $bidi = false; + $attrDir = $attributes->getNamedItem('dir'); + $direction = isset($attrDir) ? $attrDir->nodeValue : ''; + if ($direction === 'rtl') { + $bidi = $styles['bidi'] = $styles['rtl'] = true; + $styles['textDirection'] = TextDirection::RLTB; + } elseif ($direction === 'ltr') { + $bidi = $styles['bidi'] = $styles['rtl'] = false; + $styles['textDirection'] = TextDirection::LRTB; + } foreach ($attributes as $attribute) { $val = $attribute->value; switch (strtolower($attribute->name)) { @@ -148,7 +223,7 @@ protected static function parseInlineStyle($node, $styles = []) break; case 'bgcolor': // tables, rows, cells e.g. - $styles['bgColor'] = self::convertRgb($val); + HtmlColours::setArrayColour($styles, 'bgColor', self::convertRgb($val)); break; case 'valign': @@ -199,6 +274,46 @@ protected static function parseNode($node, $element, $styles = [], $data = []): return; } + if ($node->nodeName === 'title') { + if (self::$docInfo !== null) { + $docTitle = $node->nodeValue; + if ($docTitle !== 'PHPWord' && trim($docTitle) !== '') { // default + self::$docInfo->setTitle($node->nodeValue); + } + } + + return; + } + if ($node->nodeName === 'meta') { + if (self::$docInfo !== null) { + $attributes = $node->attributes; + $name = $attributes->getNamedItem('name'); + $content = $attributes->getNamedItem('content'); + if ($name !== null && $content !== null) { + $mapArray = ['author' => 'creator']; + $others = [ + 'title', + 'description', + 'subject', + 'keywords', + 'category', + 'company', + 'manager', + ]; + $nameValue = $name->nodeValue; + $propertyName = $mapArray[$nameValue] ?? (in_array($nameValue, $others, true) ? $nameValue : ''); + $method = 'set' . ucfirst($propertyName); + if (method_exists(self::$docInfo, $method)) { + self::$docInfo->$method($content->nodeValue); + } + } + } + + return; + } + if ($node->nodeName === 'script') { + return; + } // Populate styles array $styleTypes = ['font', 'paragraph', 'list', 'table', 'row', 'cell']; @@ -313,7 +428,12 @@ protected static function parseParagraph($node, $element, &$styles) return $element->addPageBreak(); } - return $element->addTextRun($styles['paragraph']); + $newElement = $element->addTextRun($styles['paragraph']); + if (isset($styles['paragraph']['className']) && $newElement->getParagraphStyle() instanceof Paragraph) { + $newElement->getParagraphStyle()->setStyleName($styles['paragraph']['className']); + } + + return $newElement; } /** @@ -344,18 +464,22 @@ protected static function parseInput($node, $element, &$styles): void /** * Parse heading node. * - * @param string $argument1 Name of heading style - * * @todo Think of a clever way of defining header styles, now it is only based on the assumption, that * Heading1 - Heading6 are already defined somewhere */ - protected static function parseHeading(DOMNode $node, AbstractContainer $element, array &$styles, string $argument1): TextRun + protected static function parseHeading(DOMNode $node, AbstractContainer $element, array &$styles, string $headingStyle): TextRun { $style = new Paragraph(); - $style->setStyleName($argument1); + $style->setStyleName($headingStyle); $style->setStyleByArray(self::parseInlineStyle($node, $styles['paragraph'])); + $textRun = new TextRun($style); - return $element->addTextRun($style); + // Create a title with level corresponding to number in heading style + // (Eg, Heading1 = 1) + $element->addTitle($textRun, (int) ltrim($headingStyle, 'Heading')); + + // Return TextRun so children are parsed + return $textRun; } /** @@ -375,7 +499,11 @@ protected static function parseText($node, $element, &$styles): void } if (is_callable([$element, 'addText'])) { - $element->addText($node->nodeValue, $styles['font'], $styles['paragraph']); + $font = $styles['font']; + if (isset($font['className']) && count($font) === 1) { + $font = $styles['font']['className']; + } + $element->addText($node->nodeValue, $font, $styles['paragraph']); } } @@ -425,9 +553,10 @@ protected static function parseTable($node, $element, &$styles) } $attributes = $node->attributes; - if ($attributes->getNamedItem('border')) { + if ($attributes->getNamedItem('border') !== null && is_object($newElement->getStyle())) { $border = (int) $attributes->getNamedItem('border')->nodeValue; - $newElement->getStyle()->setBorderSize(Converter::pixelToTwip($border)); + $newElement->getStyle()->setBorderSize((int) Converter::pixelToTwip($border)); + $newElement->getStyle()->setBorderStyle(($border === 0) ? 'none' : 'single'); } return $newElement; @@ -565,35 +694,35 @@ protected static function parseList($node, $element, &$styles, &$data) ++$data['listdepth']; } else { $data['listdepth'] = 0; - $styles['list'] = 'listStyle_' . self::$listIndex++; - $style = $element->getPhpWord()->addNumberingStyle($styles['list'], self::getListStyle($isOrderedList)); + } + $styles['list'] = 'listStyle_' . self::$listIndex++; + $style = $element->getPhpWord()->addNumberingStyle($styles['list'], self::getListStyle($isOrderedList)); - // extract attributes start & type e.g.
    - $start = 0; - $type = ''; - foreach ($node->attributes as $attribute) { - switch ($attribute->name) { - case 'start': - $start = (int) $attribute->value; + // extract attributes start & type e.g.
      + $start = 0; + $type = ''; + foreach ($node->attributes as $attribute) { + switch ($attribute->name) { + case 'start': + $start = (int) $attribute->value; - break; - case 'type': - $type = $attribute->value; + break; + case 'type': + $type = $attribute->value; - break; - } + break; } + } - $levels = $style->getLevels(); - /** @var \PhpOffice\PhpWord\Style\NumberingLevel */ - $level = $levels[0]; - if ($start > 0) { - $level->setStart($start); - } - $type = $type ? self::mapListType($type) : null; - if ($type) { - $level->setFormat($type); - } + $levels = $style->getLevels(); + /** @var \PhpOffice\PhpWord\Style\NumberingLevel */ + $level = $levels[$data['listdepth']]; + if ($start > 0) { + $level->setStart($start); + } + $type = $type ? self::mapListType($type) : null; + if ($type) { + $level->setFormat($type); } if ($node->parentNode->nodeName === 'li') { return $element->getParent(); @@ -713,6 +842,7 @@ protected static function parseStyleDeclarations(array $selectors, array $styles case 'direction': $styles['rtl'] = $value === 'rtl'; $styles['bidi'] = $value === 'rtl'; + $styles['textDirection'] = ($value === 'rtl') ? TextDirection::RLTB : TextDirection::LRTB; break; case 'font-size': @@ -725,11 +855,11 @@ protected static function parseStyleDeclarations(array $selectors, array $styles break; case 'color': - $styles['color'] = self::convertRgb($value); + HtmlColours::setArrayColour($styles, 'color', self::convertRgb($value)); break; case 'background-color': - $styles['bgColor'] = self::convertRgb($value); + HtmlColours::setArrayColour($styles, 'bgColor', self::convertRgb($value)); break; case 'line-height': @@ -861,7 +991,7 @@ protected static function parseStyleDeclarations(array $selectors, array $styles break; case 'border-width': - $styles['borderSize'] = Converter::cssToPoint($value); + $styles['borderSize'] = Converter::cssToPoint(self::SPECIAL_BORDER_WIDTHS[$value] ?? $value); break; case 'border-style': @@ -891,29 +1021,46 @@ protected static function parseStyleDeclarations(array $selectors, array $styles case 'border-bottom': case 'border-right': 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]+)/', $value, $matches)) { - if (false !== strpos($property, '-')) { - $tmp = explode('-', $property); - $which = $tmp[1]; - $which = ucfirst($which); // e.g. bottom -> Bottom - } else { - $which = ''; - } - // Note - border width normalization: - // Width of border in Word is calculated differently than HTML borders, usually showing up too bold. - // Smallest 1px (or 1pt) appears in Word like 2-3px/pt in HTML once converted to twips. - // Therefore we need to normalize converted twip value to cca 1/2 of value. - // This may be adjusted, if better ratio or formula found. - // BC change: up to ver. 0.17.0 was $size converted to points - Converter::cssToPoint($size) - $size = Converter::cssToTwip($matches[1]); + $stylePattern = '/(^|\\s)(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)(\\s|$)/'; + if (!preg_match($stylePattern, $value, $matches)) { + break; + } + $borderStyle = $matches[2]; + $value = preg_replace($stylePattern, ' ', $value) ?? ''; + $borderSize = $borderColor = null; + $sizePattern = '/(^|\\s)([0-9]+([.][0-9]+)?+(%|[a-z]*)|thick|thin|medium)(\\s|$)/'; + if (preg_match($sizePattern, $value, $matches)) { + $borderSize = $matches[2]; + $borderSize = self::SPECIAL_BORDER_WIDTHS[$borderSize] ?? $borderSize; + $value = preg_replace($sizePattern, ' ', $value) ?? ''; + } + $colorPattern = '/(^|\\s)([#][a-fA-F0-9]{6}|[#][a-fA-F0-9]{3}|[a-z][a-z0-9]+)(\\s|$)/'; + if (preg_match($colorPattern, $value, $matches)) { + $borderColor = HtmlColours::convertColour($matches[2]); + } + if (false !== strpos($property, '-')) { + $tmp = explode('-', $property); + $which = $tmp[1]; + $which = ucfirst($which); // e.g. bottom -> Bottom + } else { + $which = ''; + } + // Note - border width normalization: + // Width of border in Word is calculated differently than HTML borders, usually showing up too bold. + // Smallest 1px (or 1pt) appears in Word like 2-3px/pt in HTML once converted to twips. + // Therefore we need to normalize converted twip value to cca 1/2 of value. + // This may be adjusted, if better ratio or formula found. + // BC change: up to ver. 0.17.0 was $size converted to points - Converter::cssToPoint($size) + if ($borderSize !== null) { + $size = Converter::cssToTwip($borderSize); $size = (int) ($size / 2); // valid variants may be e.g. borderSize, borderTopSize, borderLeftColor, etc .. $styles["border{$which}Size"] = $size; // twips - $styles["border{$which}Color"] = trim($matches[2], '#'); - $styles["border{$which}Style"] = self::mapBorderStyle($matches[3]); } + if (!empty($borderColor)) { + $styles["border{$which}Color"] = $borderColor; + } + $styles["border{$which}Style"] = self::mapBorderStyle($borderStyle); break; case 'vertical-align': @@ -1062,6 +1209,8 @@ protected static function mapBorderStyle($cssBorderStyle) case 'dotted': case 'double': return $cssBorderStyle; + case 'hidden': + return 'none'; default: return 'single'; } @@ -1069,14 +1218,14 @@ protected static function mapBorderStyle($cssBorderStyle) protected static function mapBorderColor(&$styles, $cssBorderColor): void { - $numColors = substr_count($cssBorderColor, '#'); + $colors = explode(' ', $cssBorderColor); + $numColors = count($colors); if ($numColors === 1) { - $styles['borderColor'] = trim($cssBorderColor, '#'); - } elseif ($numColors > 1) { - $colors = explode(' ', $cssBorderColor); + HtmlColours::setArrayColour($styles, 'borderColor', $cssBorderColor); + } else { $borders = ['borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor']; for ($i = 0; $i < min(4, $numColors, count($colors)); ++$i) { - $styles[$borders[$i]] = trim($colors[$i], '#'); + HtmlColours::setArrayColour($styles, $borders[$i], $colors[$i]); } } } @@ -1167,7 +1316,6 @@ protected static function mapListType($cssListType) return NumberFormat::LOWER_ROMAN; // i, ii, iii, iv, .. case 'I': return NumberFormat::UPPER_ROMAN; // I, II, III, IV, .. - case '1': default: return NumberFormat::DECIMAL; // 1, 2, 3, .. } @@ -1223,7 +1371,8 @@ protected static function parseLink($node, $element, &$styles) */ protected static function parseHorizRule($node, $element): void { - $styles = self::parseInlineStyle($node); + $unusedStyle = []; + $styles = self::parseInlineStyle($node, $unusedStyle); //
      is implemented as an empty paragraph - extending 100% inside the section // Some properties may be controlled, e.g.
      @@ -1324,6 +1473,12 @@ protected static function convertHtmlSize(string $size): float if (false !== strpos($size, 'px')) { return (float) str_replace('px', '', $size); } + if (false !== strpos($size, 'cm')) { + return Converter::cmToPixel((float) str_replace('cm', '', $size)); + } + if (false !== strpos($size, 'in')) { + return Converter::inchToPixel((float) str_replace('in', '', $size)); + } return (float) $size; } diff --git a/src/PhpWord/Shared/HtmlColours.php b/src/PhpWord/Shared/HtmlColours.php new file mode 100644 index 00000000000..c50acfd5da8 --- /dev/null +++ b/src/PhpWord/Shared/HtmlColours.php @@ -0,0 +1,544 @@ + 'f0f8ff', + 'antiquewhite' => 'faebd7', + 'antiquewhite1' => 'ffefdb', + 'antiquewhite2' => 'eedfcc', + 'antiquewhite3' => 'cdc0b0', + 'antiquewhite4' => '8b8378', + 'aqua' => '00ffff', + 'aquamarine1' => '7fffd4', + 'aquamarine2' => '76eec6', + 'aquamarine4' => '458b74', + 'azure1' => 'f0ffff', + 'azure2' => 'e0eeee', + 'azure3' => 'c1cdcd', + 'azure4' => '838b8b', + 'beige' => 'f5f5dc', + 'bisque1' => 'ffe4c4', + 'bisque2' => 'eed5b7', + 'bisque3' => 'cdb79e', + 'bisque4' => '8b7d6b', + 'black' => '000000', + 'blanchedalmond' => 'ffebcd', + 'blue' => '0000ff', + 'blue1' => '0000ff', + 'blue2' => '0000ee', + 'blue4' => '00008b', + 'blueviolet' => '8a2be2', + 'brown' => 'a52a2a', + 'brown1' => 'ff4040', + 'brown2' => 'ee3b3b', + 'brown3' => 'cd3333', + 'brown4' => '8b2323', + 'burlywood' => 'deb887', + 'burlywood1' => 'ffd39b', + 'burlywood2' => 'eec591', + 'burlywood3' => 'cdaa7d', + 'burlywood4' => '8b7355', + 'cadetblue' => '5f9ea0', + 'cadetblue1' => '98f5ff', + 'cadetblue2' => '8ee5ee', + 'cadetblue3' => '7ac5cd', + 'cadetblue4' => '53868b', + 'chartreuse1' => '7fff00', + 'chartreuse2' => '76ee00', + 'chartreuse3' => '66cd00', + 'chartreuse4' => '458b00', + 'chocolate' => 'd2691e', + 'chocolate1' => 'ff7f24', + 'chocolate2' => 'ee7621', + 'chocolate3' => 'cd661d', + 'coral' => 'ff7f50', + 'coral1' => 'ff7256', + 'coral2' => 'ee6a50', + 'coral3' => 'cd5b45', + 'coral4' => '8b3e2f', + 'cornflowerblue' => '6495ed', + 'cornsilk1' => 'fff8dc', + 'cornsilk2' => 'eee8cd', + 'cornsilk3' => 'cdc8b1', + 'cornsilk4' => '8b8878', + 'cyan1' => '00ffff', + 'cyan2' => '00eeee', + 'cyan3' => '00cdcd', + 'cyan4' => '008b8b', + 'darkgoldenrod' => 'b8860b', + 'darkgoldenrod1' => 'ffb90f', + 'darkgoldenrod2' => 'eead0e', + 'darkgoldenrod3' => 'cd950c', + 'darkgoldenrod4' => '8b6508', + 'darkgreen' => '006400', + 'darkkhaki' => 'bdb76b', + 'darkolivegreen' => '556b2f', + 'darkolivegreen1' => 'caff70', + 'darkolivegreen2' => 'bcee68', + 'darkolivegreen3' => 'a2cd5a', + 'darkolivegreen4' => '6e8b3d', + 'darkorange' => 'ff8c00', + 'darkorange1' => 'ff7f00', + 'darkorange2' => 'ee7600', + 'darkorange3' => 'cd6600', + 'darkorange4' => '8b4500', + 'darkorchid' => '9932cc', + 'darkorchid1' => 'bf3eff', + 'darkorchid2' => 'b23aee', + 'darkorchid3' => '9a32cd', + 'darkorchid4' => '68228b', + 'darksalmon' => 'e9967a', + 'darkseagreen' => '8fbc8f', + 'darkseagreen1' => 'c1ffc1', + 'darkseagreen2' => 'b4eeb4', + 'darkseagreen3' => '9bcd9b', + 'darkseagreen4' => '698b69', + 'darkslateblue' => '483d8b', + 'darkslategray' => '2f4f4f', + 'darkslategray1' => '97ffff', + 'darkslategray2' => '8deeee', + 'darkslategray3' => '79cdcd', + 'darkslategray4' => '528b8b', + 'darkturquoise' => '00ced1', + 'darkviolet' => '9400d3', + 'deeppink1' => 'ff1493', + 'deeppink2' => 'ee1289', + 'deeppink3' => 'cd1076', + 'deeppink4' => '8b0a50', + 'deepskyblue1' => '00bfff', + 'deepskyblue2' => '00b2ee', + 'deepskyblue3' => '009acd', + 'deepskyblue4' => '00688b', + 'dimgray' => '696969', + 'dodgerblue1' => '1e90ff', + 'dodgerblue2' => '1c86ee', + 'dodgerblue3' => '1874cd', + 'dodgerblue4' => '104e8b', + 'firebrick' => 'b22222', + 'firebrick1' => 'ff3030', + 'firebrick2' => 'ee2c2c', + 'firebrick3' => 'cd2626', + 'firebrick4' => '8b1a1a', + 'floralwhite' => 'fffaf0', + 'forestgreen' => '228b22', + 'fuchsia' => 'ff00ff', + 'gainsboro' => 'dcdcdc', + 'ghostwhite' => 'f8f8ff', + 'gold1' => 'ffd700', + 'gold2' => 'eec900', + 'gold3' => 'cdad00', + 'gold4' => '8b7500', + 'goldenrod' => 'daa520', + 'goldenrod1' => 'ffc125', + 'goldenrod2' => 'eeb422', + 'goldenrod3' => 'cd9b1d', + 'goldenrod4' => '8b6914', + 'gray' => 'bebebe', + 'gray1' => '030303', + 'gray10' => '1a1a1a', + 'gray11' => '1c1c1c', + 'gray12' => '1f1f1f', + 'gray13' => '212121', + 'gray14' => '242424', + 'gray15' => '262626', + 'gray16' => '292929', + 'gray17' => '2b2b2b', + 'gray18' => '2e2e2e', + 'gray19' => '303030', + 'gray2' => '050505', + 'gray20' => '333333', + 'gray21' => '363636', + 'gray22' => '383838', + 'gray23' => '3b3b3b', + 'gray24' => '3d3d3d', + 'gray25' => '404040', + 'gray26' => '424242', + 'gray27' => '454545', + 'gray28' => '474747', + 'gray29' => '4a4a4a', + 'gray3' => '080808', + 'gray30' => '4d4d4d', + 'gray31' => '4f4f4f', + 'gray32' => '525252', + 'gray33' => '545454', + 'gray34' => '575757', + 'gray35' => '595959', + 'gray36' => '5c5c5c', + 'gray37' => '5e5e5e', + 'gray38' => '616161', + 'gray39' => '636363', + 'gray4' => '0a0a0a', + 'gray40' => '666666', + 'gray41' => '696969', + 'gray42' => '6b6b6b', + 'gray43' => '6e6e6e', + 'gray44' => '707070', + 'gray45' => '737373', + 'gray46' => '757575', + 'gray47' => '787878', + 'gray48' => '7a7a7a', + 'gray49' => '7d7d7d', + 'gray5' => '0d0d0d', + 'gray50' => '7f7f7f', + 'gray51' => '828282', + 'gray52' => '858585', + 'gray53' => '878787', + 'gray54' => '8a8a8a', + 'gray55' => '8c8c8c', + 'gray56' => '8f8f8f', + 'gray57' => '919191', + 'gray58' => '949494', + 'gray59' => '969696', + 'gray6' => '0f0f0f', + 'gray60' => '999999', + 'gray61' => '9c9c9c', + 'gray62' => '9e9e9e', + 'gray63' => 'a1a1a1', + 'gray64' => 'a3a3a3', + 'gray65' => 'a6a6a6', + 'gray66' => 'a8a8a8', + 'gray67' => 'ababab', + 'gray68' => 'adadad', + 'gray69' => 'b0b0b0', + 'gray7' => '121212', + 'gray70' => 'b3b3b3', + 'gray71' => 'b5b5b5', + 'gray72' => 'b8b8b8', + 'gray73' => 'bababa', + 'gray74' => 'bdbdbd', + 'gray75' => 'bfbfbf', + 'gray76' => 'c2c2c2', + 'gray77' => 'c4c4c4', + 'gray78' => 'c7c7c7', + 'gray79' => 'c9c9c9', + 'gray8' => '141414', + 'gray80' => 'cccccc', + 'gray81' => 'cfcfcf', + 'gray82' => 'd1d1d1', + 'gray83' => 'd4d4d4', + 'gray84' => 'd6d6d6', + 'gray85' => 'd9d9d9', + 'gray86' => 'dbdbdb', + 'gray87' => 'dedede', + 'gray88' => 'e0e0e0', + 'gray89' => 'e3e3e3', + 'gray9' => '171717', + 'gray90' => 'e5e5e5', + 'gray91' => 'e8e8e8', + 'gray92' => 'ebebeb', + 'gray93' => 'ededed', + 'gray94' => 'f0f0f0', + 'gray95' => 'f2f2f2', + 'gray97' => 'f7f7f7', + 'gray98' => 'fafafa', + 'gray99' => 'fcfcfc', + 'green' => '00ff00', + 'green1' => '00ff00', + 'green2' => '00ee00', + 'green3' => '00cd00', + 'green4' => '008b00', + 'greenyellow' => 'adff2f', + 'honeydew1' => 'f0fff0', + 'honeydew2' => 'e0eee0', + 'honeydew3' => 'c1cdc1', + 'honeydew4' => '838b83', + 'hotpink' => 'ff69b4', + 'hotpink1' => 'ff6eb4', + 'hotpink2' => 'ee6aa7', + 'hotpink3' => 'cd6090', + 'hotpink4' => '8b3a62', + 'indianred' => 'cd5c5c', + 'indianred1' => 'ff6a6a', + 'indianred2' => 'ee6363', + 'indianred3' => 'cd5555', + 'indianred4' => '8b3a3a', + 'ivory1' => 'fffff0', + 'ivory2' => 'eeeee0', + 'ivory3' => 'cdcdc1', + 'ivory4' => '8b8b83', + 'khaki' => 'f0e68c', + 'khaki1' => 'fff68f', + 'khaki2' => 'eee685', + 'khaki3' => 'cdc673', + 'khaki4' => '8b864e', + 'lavender' => 'e6e6fa', + 'lavenderblush1' => 'fff0f5', + 'lavenderblush2' => 'eee0e5', + 'lavenderblush3' => 'cdc1c5', + 'lavenderblush4' => '8b8386', + 'lawngreen' => '7cfc00', + 'lemonchiffon1' => 'fffacd', + 'lemonchiffon2' => 'eee9bf', + 'lemonchiffon3' => 'cdc9a5', + 'lemonchiffon4' => '8b8970', + 'light' => 'eedd82', + 'lightblue' => 'add8e6', + 'lightblue1' => 'bfefff', + 'lightblue2' => 'b2dfee', + 'lightblue3' => '9ac0cd', + 'lightblue4' => '68838b', + 'lightcoral' => 'f08080', + 'lightcyan1' => 'e0ffff', + 'lightcyan2' => 'd1eeee', + 'lightcyan3' => 'b4cdcd', + 'lightcyan4' => '7a8b8b', + 'lightgoldenrod1' => 'ffec8b', + 'lightgoldenrod2' => 'eedc82', + 'lightgoldenrod3' => 'cdbe70', + 'lightgoldenrod4' => '8b814c', + 'lightgoldenrodyellow' => 'fafad2', + 'lightgray' => 'd3d3d3', + 'lightpink' => 'ffb6c1', + 'lightpink1' => 'ffaeb9', + 'lightpink2' => 'eea2ad', + 'lightpink3' => 'cd8c95', + 'lightpink4' => '8b5f65', + 'lightsalmon1' => 'ffa07a', + 'lightsalmon2' => 'ee9572', + 'lightsalmon3' => 'cd8162', + 'lightsalmon4' => '8b5742', + 'lightseagreen' => '20b2aa', + 'lightskyblue' => '87cefa', + 'lightskyblue1' => 'b0e2ff', + 'lightskyblue2' => 'a4d3ee', + 'lightskyblue3' => '8db6cd', + 'lightskyblue4' => '607b8b', + 'lightslateblue' => '8470ff', + 'lightslategray' => '778899', + 'lightsteelblue' => 'b0c4de', + 'lightsteelblue1' => 'cae1ff', + 'lightsteelblue2' => 'bcd2ee', + 'lightsteelblue3' => 'a2b5cd', + 'lightsteelblue4' => '6e7b8b', + 'lightyellow1' => 'ffffe0', + 'lightyellow2' => 'eeeed1', + 'lightyellow3' => 'cdcdb4', + 'lightyellow4' => '8b8b7a', + 'lime' => '00ff00', + 'limegreen' => '32cd32', + 'linen' => 'faf0e6', + 'magenta' => 'ff00ff', + 'magenta2' => 'ee00ee', + 'magenta3' => 'cd00cd', + 'magenta4' => '8b008b', + 'maroon' => 'b03060', + 'maroon1' => 'ff34b3', + 'maroon2' => 'ee30a7', + 'maroon3' => 'cd2990', + 'maroon4' => '8b1c62', + 'medium' => '66cdaa', + 'mediumaquamarine' => '66cdaa', + 'mediumblue' => '0000cd', + 'mediumorchid' => 'ba55d3', + 'mediumorchid1' => 'e066ff', + 'mediumorchid2' => 'd15fee', + 'mediumorchid3' => 'b452cd', + 'mediumorchid4' => '7a378b', + 'mediumpurple' => '9370db', + 'mediumpurple1' => 'ab82ff', + 'mediumpurple2' => '9f79ee', + 'mediumpurple3' => '8968cd', + 'mediumpurple4' => '5d478b', + 'mediumseagreen' => '3cb371', + 'mediumslateblue' => '7b68ee', + 'mediumspringgreen' => '00fa9a', + 'mediumturquoise' => '48d1cc', + 'mediumvioletred' => 'c71585', + 'midnightblue' => '191970', + 'mintcream' => 'f5fffa', + 'mistyrose1' => 'ffe4e1', + 'mistyrose2' => 'eed5d2', + 'mistyrose3' => 'cdb7b5', + 'mistyrose4' => '8b7d7b', + 'moccasin' => 'ffe4b5', + 'navajowhite1' => 'ffdead', + 'navajowhite2' => 'eecfa1', + 'navajowhite3' => 'cdb38b', + 'navajowhite4' => '8b795e', + 'navy' => '000080', + 'navyblue' => '000080', + 'oldlace' => 'fdf5e6', + 'olive' => '808000', + 'olivedrab' => '6b8e23', + 'olivedrab1' => 'c0ff3e', + 'olivedrab2' => 'b3ee3a', + 'olivedrab4' => '698b22', + 'orange' => 'ffa500', + 'orange1' => 'ffa500', + 'orange2' => 'ee9a00', + 'orange3' => 'cd8500', + 'orange4' => '8b5a00', + 'orangered1' => 'ff4500', + 'orangered2' => 'ee4000', + 'orangered3' => 'cd3700', + 'orangered4' => '8b2500', + 'orchid' => 'da70d6', + 'orchid1' => 'ff83fa', + 'orchid2' => 'ee7ae9', + 'orchid3' => 'cd69c9', + 'orchid4' => '8b4789', + 'pale' => 'db7093', + 'palegoldenrod' => 'eee8aa', + 'palegreen' => '98fb98', + 'palegreen1' => '9aff9a', + 'palegreen2' => '90ee90', + 'palegreen3' => '7ccd7c', + 'palegreen4' => '548b54', + 'paleturquoise' => 'afeeee', + 'paleturquoise1' => 'bbffff', + 'paleturquoise2' => 'aeeeee', + 'paleturquoise3' => '96cdcd', + 'paleturquoise4' => '668b8b', + 'palevioletred' => 'db7093', + 'palevioletred1' => 'ff82ab', + 'palevioletred2' => 'ee799f', + 'palevioletred3' => 'cd6889', + 'palevioletred4' => '8b475d', + 'papayawhip' => 'ffefd5', + 'peachpuff1' => 'ffdab9', + 'peachpuff2' => 'eecbad', + 'peachpuff3' => 'cdaf95', + 'peachpuff4' => '8b7765', + 'pink' => 'ffc0cb', + 'pink1' => 'ffb5c5', + 'pink2' => 'eea9b8', + 'pink3' => 'cd919e', + 'pink4' => '8b636c', + 'plum' => 'dda0dd', + 'plum1' => 'ffbbff', + 'plum2' => 'eeaeee', + 'plum3' => 'cd96cd', + 'plum4' => '8b668b', + 'powderblue' => 'b0e0e6', + 'purple' => 'a020f0', + 'rebeccapurple' => '663399', + 'purple1' => '9b30ff', + 'purple2' => '912cee', + 'purple3' => '7d26cd', + 'purple4' => '551a8b', + 'red' => 'ff0000', + 'red1' => 'ff0000', + 'red2' => 'ee0000', + 'red3' => 'cd0000', + 'red4' => '8b0000', + 'rosybrown' => 'bc8f8f', + 'rosybrown1' => 'ffc1c1', + 'rosybrown2' => 'eeb4b4', + 'rosybrown3' => 'cd9b9b', + 'rosybrown4' => '8b6969', + 'royalblue' => '4169e1', + 'royalblue1' => '4876ff', + 'royalblue2' => '436eee', + 'royalblue3' => '3a5fcd', + 'royalblue4' => '27408b', + 'saddlebrown' => '8b4513', + 'salmon' => 'fa8072', + 'salmon1' => 'ff8c69', + 'salmon2' => 'ee8262', + 'salmon3' => 'cd7054', + 'salmon4' => '8b4c39', + 'sandybrown' => 'f4a460', + 'seagreen1' => '54ff9f', + 'seagreen2' => '4eee94', + 'seagreen3' => '43cd80', + 'seagreen4' => '2e8b57', + 'seashell1' => 'fff5ee', + 'seashell2' => 'eee5de', + 'seashell3' => 'cdc5bf', + 'seashell4' => '8b8682', + 'sienna' => 'a0522d', + 'sienna1' => 'ff8247', + 'sienna2' => 'ee7942', + 'sienna3' => 'cd6839', + 'sienna4' => '8b4726', + 'silver' => 'c0c0c0', + 'skyblue' => '87ceeb', + 'skyblue1' => '87ceff', + 'skyblue2' => '7ec0ee', + 'skyblue3' => '6ca6cd', + 'skyblue4' => '4a708b', + 'slateblue' => '6a5acd', + 'slateblue1' => '836fff', + 'slateblue2' => '7a67ee', + 'slateblue3' => '6959cd', + 'slateblue4' => '473c8b', + 'slategray' => '708090', + 'slategray1' => 'c6e2ff', + 'slategray2' => 'b9d3ee', + 'slategray3' => '9fb6cd', + 'slategray4' => '6c7b8b', + 'snow1' => 'fffafa', + 'snow2' => 'eee9e9', + 'snow3' => 'cdc9c9', + 'snow4' => '8b8989', + 'springgreen1' => '00ff7f', + 'springgreen2' => '00ee76', + 'springgreen3' => '00cd66', + 'springgreen4' => '008b45', + 'steelblue' => '4682b4', + 'steelblue1' => '63b8ff', + 'steelblue2' => '5cacee', + 'steelblue3' => '4f94cd', + 'steelblue4' => '36648b', + 'tan' => 'd2b48c', + 'tan1' => 'ffa54f', + 'tan2' => 'ee9a49', + 'tan3' => 'cd853f', + 'tan4' => '8b5a2b', + 'teal' => '008080', + 'thistle' => 'd8bfd8', + 'thistle1' => 'ffe1ff', + 'thistle2' => 'eed2ee', + 'thistle3' => 'cdb5cd', + 'thistle4' => '8b7b8b', + 'tomato1' => 'ff6347', + 'tomato2' => 'ee5c42', + 'tomato3' => 'cd4f39', + 'tomato4' => '8b3626', + 'turquoise' => '40e0d0', + 'turquoise1' => '00f5ff', + 'turquoise2' => '00e5ee', + 'turquoise3' => '00c5cd', + 'turquoise4' => '00868b', + 'violet' => 'ee82ee', + 'violetred' => 'd02090', + 'violetred1' => 'ff3e96', + 'violetred2' => 'ee3a8c', + 'violetred3' => 'cd3278', + 'violetred4' => '8b2252', + 'wheat' => 'f5deb3', + 'wheat1' => 'ffe7ba', + 'wheat2' => 'eed8ae', + 'wheat3' => 'cdba96', + 'wheat4' => '8b7e66', + 'white' => 'ffffff', + 'whitesmoke' => 'f5f5f5', + 'yellow' => 'ffff00', + 'yellow1' => 'ffff00', + 'yellow2' => 'eeee00', + 'yellow3' => 'cdcd00', + 'yellow4' => '8b8b00', + 'yellowgreen' => '9acd32', + ]; + + public static function convertColour(string $colorName): string + { + $colorName = trim($colorName); + if (preg_match('/^[#][a-fA-F0-9]{6}$/', $colorName) === 1) { + return substr($colorName, 1); + } + if (preg_match('/^[#][a-fA-F0-9]{3}$/', $colorName) === 1) { + return "{$colorName[1]}{$colorName[1]}{$colorName[2]}{$colorName[2]}{$colorName[3]}{$colorName[3]}"; + } + + return self::COLOUR_MAP[$colorName] ?? $colorName; + } + + public static function setArrayColour(array &$array, string $index, string $colorName): void + { + $array[$index] = self::convertColour($colorName); + } +} diff --git a/src/PhpWord/Shared/Microsoft/PasswordEncoder.php b/src/PhpWord/Shared/Microsoft/PasswordEncoder.php index 4762cc71048..e493cc11bb4 100644 --- a/src/PhpWord/Shared/Microsoft/PasswordEncoder.php +++ b/src/PhpWord/Shared/Microsoft/PasswordEncoder.php @@ -99,6 +99,18 @@ class PasswordEncoder private static $passwordMaxLength = 15; + /** + * @param string $password + * @param string $to + * @param string $from + * + * @return false|string + */ + protected static function mbConvertEncoding($password, $to, $from) + { + return mb_convert_encoding($password, $to, $from); + } + /** * Create a hashed password that MS Word will be able to work with. * @@ -120,7 +132,7 @@ public static function hashPassword($password, $algorithmName = self::ALGORITHM_ // Get the single-byte values by iterating through the Unicode characters of the truncated password. // For each character, if the low byte is not equal to 0, take it. Otherwise, take the high byte. - $passUtf8 = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8'); + $passUtf8 = static::mbConvertEncoding($password, 'UCS-2LE', 'UTF-8'); if (!is_string($passUtf8)) { throw new Exception('Failed to convert password to UCS-2LE'); } diff --git a/src/PhpWord/Shared/OLERead.php b/src/PhpWord/Shared/OLERead.php index d4399d6fb77..7285256906d 100644 --- a/src/PhpWord/Shared/OLERead.php +++ b/src/PhpWord/Shared/OLERead.php @@ -24,6 +24,7 @@ class OLERead { + /** @var string */ private $data = ''; // OLE identifier @@ -55,32 +56,49 @@ class OLERead const START_BLOCK_POS = 0x74; const SIZE_POS = 0x78; + /** @var mixed */ public $wrkdocument = null; + /** @var mixed */ public $wrk1Table = null; + /** @var mixed */ public $wrkData = null; + /** @var mixed */ public $wrkObjectPool = null; + /** @var mixed */ public $summaryInformation = null; + /** @var mixed */ public $docSummaryInfos = null; + /** @var mixed */ public $numBigBlockDepotBlocks = null; + /** @var mixed */ public $rootStartBlock = null; + /** @var mixed */ public $sbdStartBlock = null; + /** @var mixed */ public $extensionBlock = null; + /** @var mixed */ public $numExtensionBlocks = null; + /** @var mixed */ public $bigBlockChain = null; - public $smallBlockChain = null; - public $entry = null; + /** @var mixed */ + public $smallBlockChain = null; + /** @var mixed */ + public $entry = null; + /** @var mixed */ public $rootentry = null; + /** @var mixed */ public $wrkObjectPoolelseif = null; + /** @var array */ public $props = array(); /** * Read the file * - * @param $sFileName string Filename + * @param string $sFileName Filename * * @throws Exception */ - public function read($sFileName) + public function read($sFileName): void { // Check if file exists and is readable if (!is_readable($sFileName)) { @@ -89,7 +107,7 @@ public function read($sFileName) // Get the file identifier // Don't bother reading the whole file until we know it's a valid OLE file - $this->data = file_get_contents($sFileName, false, null, 0, 8); + $this->data = (string) file_get_contents($sFileName, false, null, 0, 8); // Check OLE identifier if ($this->data != self::IDENTIFIER_OLE) { @@ -97,7 +115,7 @@ public function read($sFileName) } // Get the file data - $this->data = file_get_contents($sFileName); + $this->data = (string) file_get_contents($sFileName); // Total number of sectors used for the SAT $this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS); @@ -180,7 +198,7 @@ public function read($sFileName) * Extract binary stream data * * @param mixed $stream - * @return string + * @return ?string */ public function getStream($stream) { @@ -248,7 +266,7 @@ private function readData($blSectorId) /** * Read entries in the directory stream. */ - private function readPropertySets() + private function readPropertySets(): void { $offset = 0; diff --git a/src/PhpWord/Shared/Text.php b/src/PhpWord/Shared/Text.php index 251764b3dda..eeaaa5ad9fa 100644 --- a/src/PhpWord/Shared/Text.php +++ b/src/PhpWord/Shared/Text.php @@ -18,11 +18,8 @@ namespace PhpOffice\PhpWord\Shared; -use PhpOffice\PhpWord\Exception\Exception; +use PhpOffice\PhpWord\Exception\Exception as WordException; -/** - * Text. - */ class Text { /** @@ -37,6 +34,9 @@ class Text */ private static function buildControlCharacters(): void { + if (!empty(self::$controlCharacters)) { + return; + } for ($i = 0; $i <= 19; ++$i) { if ($i != 9 && $i != 10 && $i != 13) { $find = '_x' . sprintf('%04s', strtoupper(dechex($i))) . '_'; @@ -63,9 +63,7 @@ private static function buildControlCharacters(): void */ public static function controlCharacterPHP2OOXML($value = '') { - if (empty(self::$controlCharacters)) { - self::buildControlCharacters(); - } + self::buildControlCharacters(); return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $value); } @@ -119,9 +117,7 @@ public static function chr($dec) */ public static function controlCharacterOOXML2PHP($value = '') { - if (empty(self::$controlCharacters)) { - self::buildControlCharacters(); - } + self::buildControlCharacters(); return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $value); } @@ -148,16 +144,25 @@ public static function isUTF8($value = '') public static function toUTF8($value = '') { if (null !== $value && !self::isUTF8($value)) { - // PHP8.2 : utf8_encode is deprecated, but mb_convert_encoding always usable - $value = (function_exists('mb_convert_encoding')) ? mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1') : utf8_encode($value); + $value = static::mbConvertEncoding($value); if ($value === false) { - throw new Exception('Unable to convert text to UTF-8'); + throw new WordException('Unable to convert text to UTF-8'); } } return $value; } + /** + * @param string $value + * + * @return false|string + */ + protected static function mbConvertEncoding($value) + { + return mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1'); + } + /** * Returns unicode from UTF8 text. * diff --git a/src/PhpWord/Shared/XMLReader.php b/src/PhpWord/Shared/XMLReader.php index ec41f2d6747..819a7552860 100644 --- a/src/PhpWord/Shared/XMLReader.php +++ b/src/PhpWord/Shared/XMLReader.php @@ -91,12 +91,12 @@ public function getDomFromZip($zipFile, $xmlFile) public function getDomFromString($content) { if (\PHP_VERSION_ID < 80000) { - $originalLibXMLEntityValue = libxml_disable_entity_loader(true); + $originalLibXMLEntityValue = libxml_disable_entity_loader(true); // @codeCoverageIgnore } $this->dom = new DOMDocument(); $this->dom->loadXML($content); if (\PHP_VERSION_ID < 80000) { - libxml_disable_entity_loader($originalLibXMLEntityValue); + libxml_disable_entity_loader($originalLibXMLEntityValue); // @codeCoverageIgnore } return $this->dom; diff --git a/src/PhpWord/Shared/XMLWriter.php b/src/PhpWord/Shared/XMLWriter.php index 441eac05f0b..4bade2fb2d0 100644 --- a/src/PhpWord/Shared/XMLWriter.php +++ b/src/PhpWord/Shared/XMLWriter.php @@ -18,7 +18,7 @@ namespace PhpOffice\PhpWord\Shared; -use Exception; +use PhpOffice\PhpWord\Exception\Exception as WordException; /** * XMLWriter. @@ -35,6 +35,8 @@ * @method bool writeComment(string $content) * @method bool writeElement(string $name, string $content = null) * @method bool writeRaw(string $content) + * + * @codeCoverageIgnore */ class XMLWriter extends \XMLWriter { @@ -49,6 +51,9 @@ class XMLWriter extends \XMLWriter */ private $tempFileName = ''; + /** @var string */ + private $hash; + /** * Create a new \PhpOffice\PhpWord\Shared\XMLWriter instance. * @@ -58,6 +63,7 @@ class XMLWriter extends \XMLWriter */ public function __construct($pTemporaryStorage = self::STORAGE_MEMORY, $pTemporaryStorageDir = null, $compatibility = false) { + $this->hash = spl_object_hash($this); // Open temporary storage if ($pTemporaryStorage == self::STORAGE_MEMORY) { $this->openMemory(); @@ -86,13 +92,30 @@ public function __construct($pTemporaryStorage = self::STORAGE_MEMORY, $pTempora */ public function __destruct() { + if ($this->hash !== spl_object_hash($this)) { + throw new WordException('Unserialize not permitted1'); + } // Unlink temporary files if (empty($this->tempFileName)) { return; } - if (PHP_OS != 'WINNT' && @unlink($this->tempFileName) === false) { - throw new Exception('The file ' . $this->tempFileName . ' could not be deleted.'); - } + @unlink($this->tempFileName); + } + + /** @codeCoverageIgnore */ + public function __wakeup(): void + { + $this->tempFileName = ''; + + throw new WordException('Unserialize not permitted2'); + } + + /** @param mixed[] $data */ + public function __unserialize(array $data): void + { + $this->tempFileName = ''; + + throw new WordException('Unserialize not permitted3'); } /** @@ -108,7 +131,7 @@ public function getData() $this->flush(); - return file_get_contents($this->tempFileName); + return (string) file_get_contents($this->tempFileName); } /** @@ -176,7 +199,7 @@ public function writeAttributeIf($condition, $attribute, $value): void public function writeAttribute($name, $value): bool { if (is_float($value)) { - $value = json_encode($value); + $value = (string) $value; } return parent::writeAttribute($name, $value ?? ''); diff --git a/src/PhpWord/Shared/ZipArchive.php b/src/PhpWord/Shared/ZipArchive.php index 462206c7f56..2c612b12783 100644 --- a/src/PhpWord/Shared/ZipArchive.php +++ b/src/PhpWord/Shared/ZipArchive.php @@ -116,7 +116,9 @@ public function __call($function, $args) // Run function $result = false; if (method_exists($zipObject, $zipFunction)) { - $result = @call_user_func_array([$zipObject, $zipFunction], $args); + /** @var callable */ + $callable = [$zipObject, $zipFunction]; + $result = @call_user_func_array($callable, $args); } return $result; @@ -236,7 +238,6 @@ public function getFromName($filename) */ public function pclzipAddFile($filename, $localname = null) { - /** @var PclZip $zip Type hint */ $zip = $this->zip; // Bugfix GH-261 https://github.com/PHPOffice/PHPWord/pull/261 @@ -267,8 +268,10 @@ public function pclzipAddFile($filename, $localname = null) if (!$this->usePclzip) { $pathAdded = $pathAdded . '/' . ltrim(str_replace('\\', '/', substr($filename, strlen($pathRemoved))), '/'); //$res = $zip->addFile($filename, $pathAdded); - $res = $zip->addFromString($pathAdded, file_get_contents($filename)); // addFile can't use subfolders in some cases + /** @var \ZipArchive $zip */ + $res = $zip->addFromString($pathAdded, (string) file_get_contents($filename)); // addFile can't use subfolders in some cases } else { + /** @var PclZip $zip */ $res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded); } diff --git a/src/PhpWord/SimpleType/Border.php b/src/PhpWord/SimpleType/Border.php index acd1c1a1b1f..b3298fb7eda 100644 --- a/src/PhpWord/SimpleType/Border.php +++ b/src/PhpWord/SimpleType/Border.php @@ -48,7 +48,9 @@ final class Border extends AbstractEnum const THIN_THICK_LARGE_GAP = 'thinThickLargeGap'; //A thin line contained within a thick line with a large-sized intermediate gap const THIN_THICK_MEDIUM_GAP = 'thinThickMediumGap'; //A thick line contained within a thin line with a medium-sized intermediate gap const THIN_THICK_SMALL_GAP = 'thinThickSmallGap'; //A thick line contained within a thin line with a small intermediate gap + /** @deprecated 2.0 use THIN_THICK_THIN_LARGE_GAP */ const THIN_THICK_THINLARGE_GAP = 'thinThickThinLargeGap'; //A thin-thick-thin line with a large gap + const THIN_THICK_THIN_LARGE_GAP = 'thinThickThinLargeGap'; //A thin-thick-thin line with a large gap const THIN_THICK_THIN_MEDIUM_GAP = 'thinThickThinMediumGap'; //A thin-thick-thin line with a medium gap const THIN_THICK_THIN_SMALL_GAP = 'thinThickThinSmallGap'; //A thin-thick-thin line with a small gap const THREE_D_EMBOSS = 'threeDEmboss'; //A three-staged gradient line, getting darker towards the paragraph diff --git a/src/PhpWord/SimpleType/Color.php b/src/PhpWord/SimpleType/Color.php new file mode 100644 index 00000000000..09e985ad681 --- /dev/null +++ b/src/PhpWord/SimpleType/Color.php @@ -0,0 +1,77 @@ +getParagraph(); } /** @@ -172,15 +178,24 @@ public static function getStyles() /** * Get style by name. * - * @param string $styleName + * @param ?string $styleName * * @return ?AbstractStyle Paragraph|Font|Table|Numbering */ public static function getStyle($styleName) { + if ($styleName === null) { + return null; + } if (isset(self::$styles[$styleName])) { return self::$styles[$styleName]; } + foreach (self::$styles as $key => $value) { + $styleId = self::alternateName($key); + if ($styleId === $styleName) { + return $value; + } + } return null; } @@ -215,4 +230,24 @@ private static function setStyleValues($name, $style, $value = null) return self::getStyle($name); } + + /** + * Get alternate name for style names with embedded spaces. + * + * @param string $name + * + * @return string + */ + public static function alternateName($name) + { + $explode = explode(' ', $name); + if (count($explode) > 1) { + $name = ''; + foreach ($explode as $explodeItem) { + $name .= ucfirst($explodeItem); + } + } + + return $name; + } } diff --git a/src/PhpWord/Style/AbstractStyle.php b/src/PhpWord/Style/AbstractStyle.php index 338ae6a5eff..5a553c43c4f 100644 --- a/src/PhpWord/Style/AbstractStyle.php +++ b/src/PhpWord/Style/AbstractStyle.php @@ -51,6 +51,9 @@ abstract class AbstractStyle */ protected $aliases = []; + /** @var string */ + protected $basedOn = ''; + /** * Is this an automatic style? (Used primarily in OpenDocument driver). * @@ -84,6 +87,18 @@ public function setStyleName($value) return $this; } + public function getBasedOn(): string + { + return $this->basedOn; + } + + public function setBasedOn(string $value): self + { + $this->basedOn = $value; + + return $this; + } + /** * Get index number. * diff --git a/src/PhpWord/Style/Border.php b/src/PhpWord/Style/Border.php index 722e09931a7..aa92ad386f8 100644 --- a/src/PhpWord/Style/Border.php +++ b/src/PhpWord/Style/Border.php @@ -46,6 +46,13 @@ class Border extends AbstractStyle */ protected $borderTopStyle; + /** + * Border Top Space. For section and paragraph borders only. + * + * @var float|int + */ + protected $borderTopSpace; + /** * Border Left Size. * @@ -67,6 +74,13 @@ class Border extends AbstractStyle */ protected $borderLeftStyle; + /** + * Border Left Space. For section and paragraph borders only. + * + * @var float|int + */ + protected $borderLeftSpace; + /** * Border Right Size. * @@ -88,6 +102,13 @@ class Border extends AbstractStyle */ protected $borderRightStyle; + /** + * Border Right Space. For section and paragraph borders only. + * + * @var float|int + */ + protected $borderRightSpace; + /** * Border Bottom Size. * @@ -109,6 +130,13 @@ class Border extends AbstractStyle */ protected $borderBottomStyle; + /** + * Border Bottom Space. For section and paragraph borders only. + * + * @var float|int + */ + protected $borderBottomSpace; + /** * Top margin spacing. * @@ -140,7 +168,7 @@ class Border extends AbstractStyle /** * Get border size. * - * @return int[] + * @return array */ public function getBorderSize() { @@ -233,6 +261,38 @@ public function setBorderStyle($value = null) return $this; } + /** + * Get border space. + * + * @return array + */ + public function getBorderSpace() + { + return [ + $this->getBorderTopSpace(), + $this->getBorderLeftSpace(), + $this->getBorderRightSpace(), + $this->getBorderBottomSpace(), + ]; + } + + /** + * Set border space. + * + * @param float|int $value + * + * @return self + */ + public function setBorderSpace($value = null) + { + $this->setBorderTopSpace($value); + $this->setBorderLeftSpace($value); + $this->setBorderRightSpace($value); + $this->setBorderBottomSpace($value); + + return $this; + } + /** * Get border top size. * @@ -292,7 +352,7 @@ public function getBorderTopStyle() } /** - * Set border top Style. + * Set border top style. * * @param string $value * @@ -305,6 +365,30 @@ public function setBorderTopStyle($value = null) return $this; } + /** + * Get border top space. + * + * @return float|int + */ + public function getBorderTopSpace() + { + return $this->borderTopSpace; + } + + /** + * Set border top space. + * + * @param float|int $value + * + * @return self + */ + public function setBorderTopSpace($value = null) + { + $this->borderTopSpace = $value; + + return $this; + } + /** * Get border left size. * @@ -377,6 +461,30 @@ public function setBorderLeftStyle($value = null) return $this; } + /** + * Get border left space. + * + * @return float|int + */ + public function getBorderLeftSpace() + { + return $this->borderLeftSpace; + } + + /** + * Set border left space. + * + * @param float|int $value + * + * @return self + */ + public function setBorderLeftSpace($value = null) + { + $this->borderLeftSpace = $value; + + return $this; + } + /** * Get border right size. * @@ -449,6 +557,30 @@ public function setBorderRightStyle($value = null) return $this; } + /** + * Get border right space. + * + * @return float|int + */ + public function getBorderRightSpace() + { + return $this->borderRightSpace; + } + + /** + * Set border right space. + * + * @param float|int $value + * + * @return self + */ + public function setBorderRightSpace($value = null) + { + $this->borderRightSpace = $value; + + return $this; + } + /** * Get border bottom size. * @@ -521,6 +653,30 @@ public function setBorderBottomStyle($value = null) return $this; } + /** + * Get border bottom space. + * + * @return float|int + */ + public function getBorderBottomSpace() + { + return $this->borderBottomSpace; + } + + /** + * Set border bottom space. + * + * @param float|int $value + * + * @return self + */ + public function setBorderBottomSpace($value = null) + { + $this->borderBottomSpace = $value; + + return $this; + } + /** * Check if any of the border is not null. * @@ -528,7 +684,7 @@ public function setBorderBottomStyle($value = null) */ public function hasBorder() { - $borders = $this->getBorderSize(); + $borders = array_merge($this->getBorderSize(), $this->getBorderColor(), $this->getBorderStyle(), $this->getBorderSpace()); return $borders !== array_filter($borders, 'is_null'); } diff --git a/src/PhpWord/Style/Font.php b/src/PhpWord/Style/Font.php index f03e8899d15..02fb47837c7 100644 --- a/src/PhpWord/Style/Font.php +++ b/src/PhpWord/Style/Font.php @@ -33,42 +33,59 @@ class Font extends AbstractStyle */ const UNDERLINE_NONE = 'none'; const UNDERLINE_DASH = 'dash'; - const UNDERLINE_DASHHEAVY = 'dashHeavy'; + const UNDERLINE_DASHDOTHEAVY = 'dashDotHeavy'; + const UNDERLINE_DASHDOTDOTHEAVY = 'dashDotDotHeavy'; + const UNDERLINE_DASHEDHEAVY = 'dashedHeavy'; const UNDERLINE_DASHLONG = 'dashLong'; const UNDERLINE_DASHLONGHEAVY = 'dashLongHeavy'; - const UNDERLINE_DOUBLE = 'dbl'; + const UNDERLINE_DOUBLE = 'double'; const UNDERLINE_DOTDASH = 'dotDash'; - const UNDERLINE_DOTDASHHEAVY = 'dotDashHeavy'; const UNDERLINE_DOTDOTDASH = 'dotDotDash'; - const UNDERLINE_DOTDOTDASHHEAVY = 'dotDotDashHeavy'; const UNDERLINE_DOTTED = 'dotted'; const UNDERLINE_DOTTEDHEAVY = 'dottedHeavy'; - const UNDERLINE_HEAVY = 'heavy'; + const UNDERLINE_HEAVY = 'thick'; const UNDERLINE_SINGLE = 'single'; - const UNDERLINE_WAVY = 'wavy'; - const UNDERLINE_WAVYDOUBLE = 'wavyDbl'; + const UNDERLINE_WAVY = 'wave'; + const UNDERLINE_WAVYDOUBLE = 'wavyDouble'; const UNDERLINE_WAVYHEAVY = 'wavyHeavy'; const UNDERLINE_WORDS = 'words'; - - /** - * Foreground colors. - * - * @const string - */ + /** @deprecated 2.0 use UNDERLINE_DASHEDHEAVY */ + const UNDERLINE_DASHHEAVY = self::UNDERLINE_DASHEDHEAVY; + /** @deprecated 2.0 use UNDERLINE_DASHDOTHEAVY */ + const UNDERLINE_DOTDASHHEAVY = self::UNDERLINE_DASHDOTHEAVY; + /** @deprecated 2.0 use UNDERLINE_DASHDOTHEAVY */ + const UNDERLINE_DOTDOTDASHHEAVY = self::UNDERLINE_DASHDOTDOTHEAVY; + + // Foreground colors. + /** @deprecated 2.0 use SimpleType\Color::YELLOW */ const FGCOLOR_YELLOW = 'yellow'; - const FGCOLOR_LIGHTGREEN = 'green'; + /** @deprecated 2.0 use SimpleType\Color::LIGHTGREEN */ + const FGCOLOR_LIGHTGREEN = 'lightGreen'; + /** @deprecated 2.0 use SimpleType\Color::CYAN */ const FGCOLOR_CYAN = 'cyan'; + /** @deprecated 2.0 use SimpleType\Color::MAGENTA */ const FGCOLOR_MAGENTA = 'magenta'; + /** @deprecated 2.0 use SimpleType\Color::BLUE */ const FGCOLOR_BLUE = 'blue'; + /** @deprecated 2.0 use SimpleType\Color::RED */ const FGCOLOR_RED = 'red'; + /** @deprecated 2.0 use SimpleType\Color::DARKBLUE */ const FGCOLOR_DARKBLUE = 'darkBlue'; + /** @deprecated 2.0 use SimpleType\Color::DARKCYAN */ const FGCOLOR_DARKCYAN = 'darkCyan'; + /** @deprecated 2.0 use SimpleType\Color::DARKGREEN */ const FGCOLOR_DARKGREEN = 'darkGreen'; + /** @deprecated 2.0 use SimpleType\Color::DARKMAGENTA */ const FGCOLOR_DARKMAGENTA = 'darkMagenta'; + /** @deprecated 2.0 use SimpleType\Color::DARKRED */ const FGCOLOR_DARKRED = 'darkRed'; + /** @deprecated 2.0 use SimpleType\Color::DARKYELLOW */ const FGCOLOR_DARKYELLOW = 'darkYellow'; + /** @deprecated 2.0 use SimpleType\Color::DARKGRAY */ const FGCOLOR_DARKGRAY = 'darkGray'; + /** @deprecated 2.0 use SimpleType\Color::LIGHTGRAY */ const FGCOLOR_LIGHTGRAY = 'lightGray'; + /** @deprecated 2.0 use SimpleType\Color::BLACK */ const FGCOLOR_BLACK = 'black'; /** diff --git a/src/PhpWord/Style/Language.php b/src/PhpWord/Style/Language.php index 641ed7b41e3..b4887e27575 100644 --- a/src/PhpWord/Style/Language.php +++ b/src/PhpWord/Style/Language.php @@ -29,14 +29,32 @@ */ final class Language extends AbstractStyle { - const EN_US = 'en-US'; - const EN_US_ID = 1033; + const AR_SA = 'ar-SA'; + const AR_SA_ID = 1025; + + const BG_BG = 'bg-BG'; + const BG_BG_ID = 1026; + + const CS_CZ = 'cs-CZ'; + const CS_CZ_ID = 1029; + + const DA_DK = 'da-DK'; + const DA_DK_ID = 1030; + + const DE_CH = 'de-CH'; + const DE_CH_ID = 2055; + + const DE_DE = 'de-DE'; + const DE_DE_ID = 1031; const EN_GB = 'en-GB'; const EN_GB_ID = 2057; - const FR_FR = 'fr-FR'; - const FR_FR_ID = 1036; + const EN_US = 'en-US'; + const EN_US_ID = 1033; + + const ES_ES = 'es-ES'; + const ES_ES_ID = 3082; const FR_BE = 'fr-BE'; const FR_BE_ID = 2060; @@ -44,51 +62,99 @@ final class Language extends AbstractStyle const FR_CH = 'fr-CH'; const FR_CH_ID = 4108; - const ES_ES = 'es-ES'; - const ES_ES_ID = 3082; - - const DE_DE = 'de-DE'; - const DE_DE_ID = 1031; - - const DE_CH = 'de-CH'; - const DE_CH_ID = 2055; + const FR_FR = 'fr-FR'; + const FR_FR_ID = 1036; const HE_IL = 'he-IL'; const HE_IL_ID = 1037; - const IT_IT = 'it-IT'; - const IT_IT_ID = 1040; + const HI_IN = 'hi-IN'; + const HI_IN_ID = 1081; + + const HR_HR = 'hr-HR'; + const HR_HR_ID = 1050; + + const HU_HU = 'hu-HU'; + const HU_HU_ID = 1038; + + const ID_ID = 'id-ID'; + const ID_ID_ID = 1057; const IT_CH = 'it-CH'; const IT_CH_ID = 2064; + const IT_IT = 'it-IT'; + const IT_IT_ID = 1040; + const JA_JP = 'ja-JP'; const JA_JP_ID = 1041; + const KK_KZ = 'kk-KZ'; + const KK_KK_ID = 1087; + const KO_KR = 'ko-KR'; const KO_KR_ID = 1042; - const ZH_CN = 'zh-CN'; - const ZH_CN_ID = 2052; + const LT_LT = 'lt-LT'; + const LT_LT_ID = 1063; - const HI_IN = 'hi-IN'; - const HI_IN_ID = 1081; + const LV_LV = 'lv-LV'; + const LV_LV_ID = 1062; - const PT_BR = 'pt-BR'; - const PT_BR_ID = 1046; + const MS_MY = 'ms-MY'; + const MS_MY_ID = 1086; + + const NB_NO = 'nb-NO'; + const NB_NO_ID = 1044; const NL_NL = 'nl-NL'; const NL_NL_ID = 1043; + const PL_PL = 'pl-PL'; + const PL_PL_ID = 1045; + + const PT_BR = 'pt-BR'; + const PT_BR_ID = 1046; + + const PT_PT = 'pt-PT'; + const PT_PT_ID = 2070; + + const RO_RO = 'ro-RO'; + const RO_RO_ID = 1048; + + const SL_SI = 'sl-SI'; + const SL_SI_ID = 1046; + + const SK_SK = 'sk-SK'; + const SK_SK_ID = 1051; + + const SR_LATN_RS = 'sr-latn-RS'; + const SR_LATN_RS_ID = 2074; + const SV_SE = 'sv-SE'; const SV_SE_ID = 1053; + const TH_TH = 'th-TH'; + const TH_TH_ID = 1054; + + const TR_TR = 'tr-TR'; + const TR_TR_ID = 1055; + const UK_UA = 'uk-UA'; const UK_UA_ID = 1058; const RU_RU = 'ru-RU'; const RU_RU_ID = 1049; + const VI_VN = 'vi-VN'; + const VI_VN_ID = 1066; + + const ZH_CN = 'zh-CN'; + const ZH_CN_ID = 2052; + + const ZH_TW = 'zh-TW'; + const ZH_TW_ID = 1028; + /** * Language ID, used for RTF document generation. * @@ -122,16 +188,20 @@ final class Language extends AbstractStyle /** * Constructor. */ - public function __construct(?string $latin = null, ?string $eastAsia = null, ?string $bidirectional = null) + public function __construct(string $latin = '', string $eastAsia = '', string $bidirectional = '', int $langId = 0) { + $this->langId = $langId; if (!empty($latin)) { $this->setLatin($latin); + $this->convertLangId($latin); } if (!empty($eastAsia)) { $this->setEastAsia($eastAsia); + $this->convertLangId($eastAsia); } if (!empty($bidirectional)) { $this->setBidirectional($bidirectional); + $this->convertLangId($bidirectional); } } @@ -141,7 +211,7 @@ public function __construct(?string $latin = null, ?string $eastAsia = null, ?st * @param string $latin * The value for the latin language */ - public function setLatin(?string $latin): self + public function setLatin(string $latin): self { $this->latin = $this->validateLocale($latin); @@ -236,26 +306,39 @@ public function getBidirectional() /** * Validates that the language passed is in the format xx-xx. * - * @param string $locale + * @param ?string $locale + * @param bool $throw * - * @return string + * @return ?string */ - private function validateLocale($locale) + private function validateLocale($locale, $throw = true) { - if ($locale !== null) { - $locale = str_replace('_', '-', $locale); + if ($locale === null) { + return null; } + $locale = str_replace('_', '-', $locale); - if ($locale !== null && strlen($locale) === 2) { + if (strlen($locale) === 2) { return strtolower($locale) . '-' . strtoupper($locale); } if ($locale === 'und') { - return 'en-EN'; + return 'en-GB'; } - if ($locale !== null && $locale !== 'zxx' && strstr($locale, '-') === false) { + if ($throw && $locale !== 'zxx' && strstr($locale, '-') === false) { throw new InvalidArgumentException($locale . ' is not a valid language code'); } return $locale; } + + private function convertLangId(string $locale): void + { + if ($this->langId === 0 && $locale !== '') { + $locale = $this->validateLocale($locale, false); + $locale = strtoupper(str_replace('-', '_', $locale)) . '_ID'; + if (defined("self::$locale")) { + $this->langId = constant("self::$locale"); + } + } + } } diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index e34aeb7c7c5..2c579938e43 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -115,6 +115,18 @@ public function getNumStyle() return $this->numStyle; } + /** + * Get numbering style. + * + * @return ?Numbering + */ + public function getNumbering() + { + $numStyleObject = Style::getStyle($this->numStyle); + + return ($numStyleObject instanceof Numbering) ? $numStyleObject : null; + } + /** * Set numbering style name. * diff --git a/src/PhpWord/Style/Paper.php b/src/PhpWord/Style/Paper.php index c59ea42d7bc..daecdec436a 100644 --- a/src/PhpWord/Style/Paper.php +++ b/src/PhpWord/Style/Paper.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Style; +use InvalidArgumentException; use PhpOffice\PhpWord\Shared\Converter; /** @@ -97,16 +98,21 @@ class Paper extends AbstractStyle /** * Paper sizes. * - * @var array + * @var array */ private $sizes = [ 'A3' => [297, 420, 'mm'], 'A4' => [210, 297, 'mm'], 'A5' => [148, 210, 'mm'], + 'B4' => [250, 353, 'mm'], 'B5' => [176, 250, 'mm'], + 'Executive' => [7.25, 10.5, 'in'], 'Folio' => [8.5, 13, 'in'], + 'Ledger' => [17, 11, 'in'], 'Legal' => [8.5, 14, 'in'], 'Letter' => [8.5, 11, 'in'], + 'Statement' => [5.5, 8.5, 'in'], + 'Tabloid' => [11, 17, 'in'], ]; /** @@ -151,24 +157,37 @@ public function getSize() } /** - * Set size. + * Set size. Normally called with 1 parameter, corresponding to a key + * in the $sizes array. However, for sizes not in that table, + * it can be called with explicit width, height, and unit. * * @param string $size + * @param float|int $width + * @param float|int $height + * @param string $unit * * @return self */ - public function setSize($size) + public function setSize($size, $width = 0, $height = 0, $unit = '') { - $this->size = $this->setEnumVal($size, array_keys($this->sizes), $this->size); - - [$width, $height, $unit] = $this->sizes[$this->size]; + if ($width != 0 || $height != 0 || $unit !== '') { + $this->size = $size; + } else { + $this->size = $this->setEnumVal($size, array_keys($this->sizes), $this->size); + [$width, $height, $unit] = $this->sizes[$this->size]; + } - if ($unit == 'mm') { + if ($width <= 0 || $height <= 0) { + throw new InvalidArgumentException('width and height must be positive'); + } + if ($unit === 'mm') { $this->width = Converter::cmToTwip($width / 10); $this->height = Converter::cmToTwip($height / 10); - } else { + } elseif ($unit === 'in') { $this->width = Converter::inchToTwip($width); $this->height = Converter::inchToTwip($height); + } else { + throw new InvalidArgumentException('unit must be mm or in'); } return $this; diff --git a/src/PhpWord/Style/Paragraph.php b/src/PhpWord/Style/Paragraph.php index 5ab7ade673d..3f27398f16f 100644 --- a/src/PhpWord/Style/Paragraph.php +++ b/src/PhpWord/Style/Paragraph.php @@ -23,6 +23,7 @@ use PhpOffice\PhpWord\Shared\Text; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\TextAlignment; +use PhpOffice\PhpWord\SimpleType\TextDirection; /** * Paragraph style. @@ -70,7 +71,7 @@ class Paragraph extends Border * * @var string */ - private $basedOn = 'Normal'; + protected $basedOn = 'Normal'; /** * Style for next paragraph. @@ -182,6 +183,13 @@ class Paragraph extends Border */ private $textAlignment; + /** + * Text direction right or left, top or bottom. + * + * @var string + */ + private $textDirection = ''; + /** * Suppress hyphenation for paragraph. * @@ -242,6 +250,7 @@ public function getStyleValues() 'contextualSpacing' => $this->hasContextualSpacing(), 'bidi' => $this->isBidi(), 'textAlignment' => $this->getTextAlignment(), + 'textDirection' => $this->getTextDirection(), 'suppressAutoHyphens' => $this->hasSuppressAutoHyphens(), ]; @@ -274,30 +283,6 @@ public function setAlignment($value) return $this; } - /** - * Get parent style ID. - * - * @return string - */ - public function getBasedOn() - { - return $this->basedOn; - } - - /** - * Set parent style ID. - * - * @param string $value - * - * @return self - */ - public function setBasedOn($value = 'Normal') - { - $this->basedOn = $value; - - return $this; - } - /** * Get style for next paragraph. * @@ -874,6 +859,19 @@ public function setTextAlignment($textAlignment) return $this; } + public function getTextDirection(): string + { + return ($this->textDirection === '' && $this->isBidi()) ? TextDirection::TBRL : $this->textDirection; + } + + public function setTextDirection(string $textDirection): self + { + TextDirection::validate($textDirection); + $this->textDirection = $textDirection; + + return $this; + } + /** * @return bool */ diff --git a/src/PhpWord/Style/Table.php b/src/PhpWord/Style/Table.php index 2510a3fa72e..5cb170fcf62 100644 --- a/src/PhpWord/Style/Table.php +++ b/src/PhpWord/Style/Table.php @@ -110,6 +110,20 @@ class Table extends Border */ private $borderInsideVColor; + /** + * Border style inside horizontal. + * + * @var string + */ + protected $borderInsideHStyle = ''; + + /** + * Border style inside vertical. + * + * @var string + */ + protected $borderInsideVStyle = ''; + /** * Shading. * @@ -168,6 +182,9 @@ class Table extends Border */ private $bidiVisual; + /** @var string */ + private $tblStyle = ''; + /** * Create new table style. */ @@ -270,6 +287,42 @@ public function getBorderSize() ]; } + /** + * Get border style. + * + * @return string[] + */ + public function getBorderStyle() + { + return [ + $this->getBorderTopStyle(), + $this->getBorderLeftStyle(), + $this->getBorderRightStyle(), + $this->getBorderBottomStyle(), + $this->getBorderInsideHStyle(), + $this->getBorderInsideVStyle(), + ]; + } + + /** + * Set border style. + * + * @param string $value + * + * @return self + */ + public function setBorderStyle($value = null) + { + $this->setBorderTopStyle($value); + $this->setBorderLeftStyle($value); + $this->setBorderRightStyle($value); + $this->setBorderBottomStyle($value); + $this->setBorderInsideHStyle($value); + $this->setBorderInsideVStyle($value); + + return $this; + } + /** * Set TLRBHV Border Size. * @@ -325,6 +378,26 @@ public function setBorderColor($value = null) return $this; } + /** + * Get border style inside horizontal. + * + * @return string + */ + public function getBorderInsideHStyle() + { + return (string) $this->getTableOnlyProperty('borderInsideHStyle'); + } + + /** + * Get border style inside vertical. + * + * @return string + */ + public function getBorderInsideVStyle() + { + return (string) $this->getTableOnlyProperty('borderInsideVStyle'); + } + /** * Get border size inside horizontal. * @@ -347,6 +420,34 @@ public function setBorderInsideHSize($value = null) return $this->setTableOnlyProperty('borderInsideHSize', $value); } + /** + * Set border style inside horizontal. + * + * @param string $value + * + * @return self + */ + public function setBorderInsideHStyle($value = '') + { + $this->setTableOnlyProperty('borderInsideHStyle', $value, false); + + return $this; + } + + /** + * Set border style inside horizontal. + * + * @param ?string $value + * + * @return self + */ + public function setBorderInsideVStyle($value = null) + { + $this->setTableOnlyProperty('borderInsideVStyle', $value, false); + + return $this; + } + /** * Get border color inside horizontal. * @@ -801,4 +902,16 @@ public function setBidiVisual($bidi) return $this; } + + public function getTblStyle(): string + { + return $this->tblStyle; + } + + public function setTblStyle(string $tblStyle): self + { + $this->tblStyle = $tblStyle; + + return $this; + } } diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 073393ffc47..cbe54a18ce6 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -24,6 +24,7 @@ use PhpOffice\PhpWord\Exception\CopyFileException; use PhpOffice\PhpWord\Exception\CreateTemporaryFileException; use PhpOffice\PhpWord\Exception\Exception; +use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\Shared\Text; use PhpOffice\PhpWord\Shared\XMLWriter; use PhpOffice\PhpWord\Shared\ZipArchive; @@ -189,7 +190,7 @@ protected function readPartWithRels($fileName) protected function transformSingleXml($xml, $xsltProcessor) { if (\PHP_VERSION_ID < 80000) { - $orignalLibEntityLoader = libxml_disable_entity_loader(true); + $orignalLibEntityLoader = libxml_disable_entity_loader(true); // @codeCoverageIgnore } $domDocument = new DOMDocument(); if (false === $domDocument->loadXML($xml)) { @@ -201,7 +202,7 @@ protected function transformSingleXml($xml, $xsltProcessor) throw new Exception('Could not transform the given XML document.'); } if (\PHP_VERSION_ID < 80000) { - libxml_disable_entity_loader($orignalLibEntityLoader); + libxml_disable_entity_loader($orignalLibEntityLoader); // @codeCoverageIgnore } return $transformedXml; @@ -243,7 +244,7 @@ public function applyXslStyleSheet($xslDomDocument, $xslOptions = [], $xslOption $xsltProcessor->importStylesheet($xslDomDocument); if (false === $xsltProcessor->setParameter($xslOptionsUri, $xslOptions)) { - throw new Exception('Could not set values for the given XSL style sheet parameters.'); + throw new Exception('Could not set values for the given XSL style sheet parameters.'); // @codeCoverageIgnore } $this->tempDocumentHeaders = $this->transformXml($this->tempDocumentHeaders, $xsltProcessor); @@ -278,8 +279,9 @@ protected static function ensureUtf8Encoded($subject) /** * @param string $search */ - public function setComplexValue($search, Element\AbstractElement $complexType): void + public function setComplexValue($search, Element\AbstractElement $complexType, bool $multiple = false): void { + $originalSearch = $search; $elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1); $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; @@ -300,6 +302,9 @@ public function setComplexValue($search, Element\AbstractElement $complexType): $search = static::ensureMacroCompleted($search); $this->replaceXmlBlock($search, $xmlWriter->getData(), 'w:r'); + if ($multiple === true) { + $this->setComplexValue($originalSearch, $complexType, true); + } } /** @@ -308,6 +313,9 @@ public function setComplexValue($search, Element\AbstractElement $complexType): public function setComplexBlock($search, Element\AbstractElement $complexType): void { $elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1); + if ($elementName === 'Section') { + $elementName = 'Container'; + } $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; $xmlWriter = new XMLWriter(); @@ -563,11 +571,28 @@ private function prepareImageAttrs($replaceImage, $varInlineArgs) $width = $this->chooseImageDimension($width, $varInlineArgs['width'] ?? null, 115); $height = $this->chooseImageDimension($height, $varInlineArgs['height'] ?? null, 70); - $imageData = @getimagesize($imgPath); - if (!is_array($imageData)) { - throw new Exception(sprintf('Invalid image: %s', $imgPath)); + $mime = mime_content_type($imgPath); + if ($mime !== 'image/svg+xml') { + $imageData = @getimagesize($imgPath); + if (!is_array($imageData)) { + throw new Exception(sprintf('Invalid image: %s', $imgPath)); // @codeCoverageIgnore + } + [$actualWidth, $actualHeight, $imageType] = $imageData; + } else { + $content = file_get_contents($imgPath); + if (!$content) { + throw new Exception(sprintf('Invalid image: %s', $imgPath)); // @codeCoverageIgnore + } + $svgXml = simplexml_load_string($content); + if (!$svgXml) { + throw new Exception(sprintf('Invalid image: %s', $imgPath)); // @codeCoverageIgnore + } + $svgAttributes = $svgXml->attributes(); + $actualWidth = $svgAttributes->width; + $actualHeight = $svgAttributes->height; + $actualWidth = is_numeric($actualWidth) ? $actualWidth . 'px' : $actualWidth; + $actualHeight = is_numeric($actualHeight) ? $actualHeight . 'px' : $actualHeight; } - [$actualWidth, $actualHeight, $imageType] = $imageData; // fix aspect ratio (by default) if (null === $ratio && isset($varInlineArgs['ratio'])) { @@ -579,9 +604,11 @@ private function prepareImageAttrs($replaceImage, $varInlineArgs) $imageAttrs = [ 'src' => $imgPath, - 'mime' => image_type_to_mime_type($imageType), + 'mime' => $mime, 'width' => $width, 'height' => $height, + 'originalWidth' => $actualWidth, + 'originalHeight' => $actualHeight, ]; return $imageAttrs; @@ -599,6 +626,7 @@ private function addImageToRelations($partFileName, $rid, $imgPath, $imageMimeTy 'image/png' => 'png', 'image/bmp' => 'bmp', 'image/gif' => 'gif', + 'image/svg+xml' => 'svg', ]; // get image embed name @@ -609,7 +637,7 @@ private function addImageToRelations($partFileName, $rid, $imgPath, $imageMimeTy if (isset($extTransform[$imageMimeType])) { $imgExt = $extTransform[$imageMimeType]; } else { - throw new Exception("Unsupported image type $imageMimeType"); + throw new Exception("Unsupported image type $imageMimeType"); // @codeCoverageIgnore } // add image to document @@ -674,6 +702,48 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM // define templates // result can be verified via "Open XML SDK 2.5 Productivity Tool" (http://www.microsoft.com/en-us/download/details.aspx?id=30425) $imgTpl = ''; + // use drawing for svg, see https://www.datypic.com/sc/ooxml/e-w_drawing-1.html + $svgTpl = ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '; $i = 0; foreach ($searchParts as $partFileName => &$partContent) { @@ -695,7 +765,29 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM // replace preparations $this->addImageToRelations($partFileName, $rid, $imgPath, $preparedImageAttrs['mime']); - $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']], $imgTpl); + if ($preparedImageAttrs['mime'] !== 'image/svg+xml') { + $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']], $imgTpl); + } else { + $width = Converter::cssToEmu($preparedImageAttrs['width']); + $height = Converter::cssToEmu($preparedImageAttrs['height']); + if ($width === null) { + $width = Converter::cssToEmu($preparedImageAttrs['originalWidth']); + if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $preparedImageAttrs['width'], $matches)) { + $unit = $matches[2]; + $size = (float) ($matches[1]) * (($unit === 'ex') ? 2 : 1); + $width = ($unit === '%') ? (Converter::cssToEmu($preparedImageAttrs['originalWidth']) * $size) : ($size * 152400); + } + } + if ($height === null) { + $height = Converter::cssToEmu($preparedImageAttrs['originalHeight']); + if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $preparedImageAttrs['height'], $matches)) { + $unit = $matches[2]; + $size = (float) ($matches[1]) * (($unit === 'ex') ? 2 : 1); + $height = ($unit === '%') ? (Converter::cssToEmu($preparedImageAttrs['originalHeight']) * $size) : ($size * 152400); + } + } + $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}', '{ID}', '{NAME}'], [$rid, (string) $width, (string) $height, $imgIndex, 'graphic'], $svgTpl); + } // replace variable $varNameWithArgsFixed = static::ensureMacroCompleted($varNameWithArgs); @@ -1039,6 +1131,7 @@ protected function savePartWithRels($fileName, $xml): void */ public function saveAs($fileName): void { + PhpWord::noPhar($fileName); $tempFileName = $this->save(); if (file_exists($fileName)) { diff --git a/src/PhpWord/Writer/EPub3.php b/src/PhpWord/Writer/EPub3.php index b2ed9700d13..7ad32bd435a 100644 --- a/src/PhpWord/Writer/EPub3.php +++ b/src/PhpWord/Writer/EPub3.php @@ -63,6 +63,7 @@ public function __construct(?PhpWord $phpWord = null) */ public function save(string $filename): void { + PhpWord::noPhar($filename); $filename = $this->getTempFile($filename); $zip = $this->getZipArchive($filename); @@ -72,14 +73,12 @@ public function save(string $filename): void // Add other files foreach ($this->parts as $partName => $fileName) { - if ($fileName === '') { - continue; + if ($fileName !== '') { + $part = $this->getWriterPart($partName); + if ($part instanceof AbstractPart) { + $zip->addFromString($fileName, $part->write()); + } } - $part = $this->getWriterPart($partName); - if (!$part instanceof AbstractPart) { - continue; - } - $zip->addFromString($fileName, $part->write()); } // Close zip archive diff --git a/src/PhpWord/Writer/HTML.php b/src/PhpWord/Writer/HTML.php index acaa3c48006..b67a008e40e 100644 --- a/src/PhpWord/Writer/HTML.php +++ b/src/PhpWord/Writer/HTML.php @@ -21,7 +21,6 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Validate; -use PhpOffice\PhpWord\Writer\HTML\Part\AbstractPart; /** * HTML writer. @@ -77,8 +76,7 @@ public function __construct(?PhpWord $phpWord = null) $this->parts = ['Head', 'Body']; foreach ($this->parts as $partName) { $partClass = self::class . '\\Part\\' . $partName; - if (class_exists($partClass)) { - /** @var AbstractPart $part Type hint */ + if (class_exists($partClass) && method_exists($partClass, 'setParentWriter')) { $part = new $partClass(); $part->setParentWriter($this); $this->writerParts[strtolower($partName)] = $part; @@ -91,6 +89,7 @@ public function __construct(?PhpWord $phpWord = null) */ public function save(string $filename): void { + PhpWord::noPhar($filename); $this->writeFile($this->openFile($filename), $this->getContent()); } diff --git a/src/PhpWord/Writer/HTML/Element/PreserveText.php b/src/PhpWord/Writer/HTML/Element/PreserveText.php new file mode 100644 index 00000000000..7c7603927e6 --- /dev/null +++ b/src/PhpWord/Writer/HTML/Element/PreserveText.php @@ -0,0 +1,90 @@ +processFontStyle(); + + /** @var \PhpOffice\PhpWord\Element\PreserveText */ + $element = $this->element; + $text = $element->getText(); + if (is_array($text)) { + $text = implode('', $text); + } + + $text = $this->parentWriter->escapeHTML($text ?? ''); + if (!$this->withoutP && !trim($text)) { + $text = ' '; + } + + $content = ''; + $content .= $this->writeOpening(); + $content .= $this->openingTags; + $content .= $text; + $content .= $this->closingTags; + $content .= $this->writeClosing(); + + return $content; + } + + /** + * Write opening. + * + * @return string + */ + protected function writeOpening() + { + $content = ''; + if (!$this->withoutP) { + $style = $this->getParagraphStyle(); + $content .= ""; + } + + return $content; + } + + /** + * Write ending. + * + * @return string + */ + protected function writeClosing() + { + $content = ''; + + if (!$this->withoutP) { + $content .= '

      ' . PHP_EOL; + } + + return $content; + } +} diff --git a/src/PhpWord/Writer/HTML/Element/Ruby.php b/src/PhpWord/Writer/HTML/Element/Ruby.php index 5648d85e2fd..db8d2f9d320 100644 --- a/src/PhpWord/Writer/HTML/Element/Ruby.php +++ b/src/PhpWord/Writer/HTML/Element/Ruby.php @@ -58,27 +58,18 @@ public function write() return $content; } + private const MAP_ALIGNMENT = [ + RubyProperties::ALIGNMENT_CENTER => 'center', + RubyProperties::ALIGNMENT_LEFT => 'start', + ]; + /** * Get property CSS for the tag. */ private function getPropertyCssForRubyTag(RubyProperties $properties): string { // alignment CSS: https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-align - $alignment = 'space-between'; - switch ($properties->getAlignment()) { - case RubyProperties::ALIGNMENT_CENTER: - $alignment = 'center'; - - break; - case RubyProperties::ALIGNMENT_LEFT: - $alignment = 'start'; - - break; - default: - $alignment = 'space-between'; - - break; - } + $alignment = self::MAP_ALIGNMENT[$properties->getAlignment()] ?? 'space-between'; return 'font-size:' . $properties->getFontSizeForBaseText() . 'pt' . ';' . diff --git a/src/PhpWord/Writer/HTML/Element/Table.php b/src/PhpWord/Writer/HTML/Element/Table.php index b66e2f8c92c..398987e0b5b 100644 --- a/src/PhpWord/Writer/HTML/Element/Table.php +++ b/src/PhpWord/Writer/HTML/Element/Table.php @@ -42,7 +42,8 @@ public function write() $rows = $this->element->getRows(); $rowCount = count($rows); if ($rowCount > 0) { - $content .= 'getTableStyle($this->element->getStyle()) . '>' . PHP_EOL; + $tableCss = $this->getTableStyle($this->element->getStyle()); + $content .= '' . PHP_EOL; for ($i = 0; $i < $rowCount; ++$i) { /** @var \PhpOffice\PhpWord\Element\Row $row Type hint */ @@ -54,10 +55,13 @@ public function write() $rowCellCount = count($rowCells); for ($j = 0; $j < $rowCellCount; ++$j) { $cellStyle = $rowCells[$j]->getStyle(); - $cellStyleCss = $this->getTableStyle($cellStyle); + $cellStyleCss = $this->getTableStyle($cellStyle) ?: $tableCss; $cellBgColor = $cellStyle->getBgColor(); + if ($cellBgColor === 'auto') { + $cellBgColor = null; + } $cellFgColor = null; - if ($cellBgColor && $cellBgColor !== 'auto') { + if ($cellBgColor) { $red = hexdec(substr($cellBgColor, 0, 2)); $green = hexdec(substr($cellBgColor, 2, 2)); $blue = hexdec(substr($cellBgColor, 4, 2)); diff --git a/src/PhpWord/Writer/HTML/Element/Text.php b/src/PhpWord/Writer/HTML/Element/Text.php index 7be95a5c768..a53a53fa546 100644 --- a/src/PhpWord/Writer/HTML/Element/Text.php +++ b/src/PhpWord/Writer/HTML/Element/Text.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; +use PhpOffice\PhpWord\Element\PreserveText as PreserveTextElement; +use PhpOffice\PhpWord\Element\Text as TextElement; use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; @@ -52,14 +54,14 @@ class Text extends AbstractElement * * @var string */ - private $openingTags = ''; + protected $openingTags = ''; /** * Closing tag. * * @var string */ - private $closingTags = ''; + protected $closingTags = ''; /** * Write text. @@ -70,7 +72,7 @@ public function write() { $this->processFontStyle(); - /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + /** @var TextElement */ $element = $this->element; $text = $this->parentWriter->escapeHTML($element->getText() ?? ''); @@ -163,20 +165,22 @@ private function writeTrackChangeOpening() $content = ''; if (($changed->getChangeType() == TrackChange::INSERTED)) { - $content .= 'getChangeType() == TrackChange::DELETED) { - $content .= ' ['author' => $changed->getAuthor(), 'id' => $this->element->getElementId()]]; - if ($changed->getDate() != null) { - $changedProp['changed']['date'] = $changed->getDate()->format('Y-m-d\TH:i:s\Z'); + $author = htmlspecialchars($changed->getAuthor(), ENT_QUOTES); + $content .= " data-phpword-chg-author='$author'"; + $elementId = htmlspecialchars($this->element->getElementId(), ENT_QUOTES); + $content .= " data-phpword-chg-id='$elementId'"; + $date = $changed->getDate(); + if ($date !== null) { + $dateout = $date->format('Y-m-d\TH:i:s\Z'); + $content .= " data-phpword-chg-timestamp='$dateout'"; } - $content .= json_encode($changedProp); - $content .= '\' '; - $content .= 'title="' . $changed->getAuthor(); - if ($changed->getDate() != null) { - $dateUser = $changed->getDate()->format('Y-m-d H:i:s'); + $content .= ' title="' . $author; + if ($date !== null) { + $dateUser = $date->format('Y-m-d H:i:s'); $content .= ' - ' . $dateUser; } $content .= '">'; @@ -211,9 +215,9 @@ private function writeTrackChangeClosing() * * @return string */ - private function getParagraphStyle() + protected function getParagraphStyle() { - /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + /** @var PreserveTextElement|TextElement */ $element = $this->element; $style = ''; if (!method_exists($element, 'getParagraphStyle')) { @@ -240,9 +244,9 @@ private function getParagraphStyle() /** * Get font style. */ - private function processFontStyle(): void + protected function processFontStyle(): void { - /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + /** @var PreserveTextElement|TextElement */ $element = $this->element; $attributeStyle = $attributeLang = ''; @@ -255,6 +259,11 @@ private function processFontStyle(): void $fontCSS = $styleWriter->write(); if ($fontCSS) { $attributeStyle = ' style="' . $fontCSS . '"'; + } else { + $className = $fontStyle->getStyleName(); + if ($className) { + $attributeStyle = ' class="' . $className . '"'; + } } // Attribute Lang $lang = $fontStyle->getLang(); diff --git a/src/PhpWord/Writer/HTML/Element/Title.php b/src/PhpWord/Writer/HTML/Element/Title.php index 84b0b199fc9..edd114788cb 100644 --- a/src/PhpWord/Writer/HTML/Element/Title.php +++ b/src/PhpWord/Writer/HTML/Element/Title.php @@ -18,7 +18,11 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; +use PhpOffice\PhpWord\Element\Title as ElementTitle; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Writer\HTML; +use PhpOffice\PhpWord\Writer\HTML\Style\Font; +use PhpOffice\PhpWord\Writer\HTML\Style\Paragraph; /** * TextRun element HTML writer. @@ -34,21 +38,38 @@ class Title extends AbstractElement */ public function write() { - if (!$this->element instanceof \PhpOffice\PhpWord\Element\Title) { + if (!$this->element instanceof ElementTitle) { return ''; } $tag = 'h' . $this->element->getDepth(); $text = $this->element->getText(); + $paragraphStyle = null; if (is_string($text)) { $text = $this->parentWriter->escapeHTML($text); } else { + $paragraphStyle = $text->getParagraphStyle(); $writer = new Container($this->parentWriter, $text); $text = $writer->write(); } + $write1 = $write3 = ''; + $style = Style::getStyle('Heading_' . $this->element->getDepth()); + if ($style !== null) { + $styleWriter = new Font($style); + $write1 = $styleWriter->write(); + } + if (is_object($paragraphStyle)) { + $styleWriter = new Paragraph($paragraphStyle); + $write3 = $styleWriter->write(); + } + $write2 = ($write1 === '' || $write3 === '') ? '' : ' '; + $css = "$write1$write2$write3"; + if ($css !== '') { + $css = " style=\"$css\""; + } - $content = "<{$tag}>{$text}" . PHP_EOL; + $content = "<{$tag}{$css}>{$text}" . PHP_EOL; return $content; } diff --git a/src/PhpWord/Writer/HTML/Part/Head.php b/src/PhpWord/Writer/HTML/Part/Head.php index 79235e1c4ab..6c2165c0bea 100644 --- a/src/PhpWord/Writer/HTML/Part/Head.php +++ b/src/PhpWord/Writer/HTML/Part/Head.php @@ -88,21 +88,19 @@ private function writeStyles(): string $defaultFontColor = Settings::getDefaultFontColor(); // Default styles $astarray = [ - 'font-family' => $this->getFontFamily(Settings::getDefaultFontName(), $this->getParentWriter()->getDefaultGenericFont()), + 'font-family' => $this->getFontFamily(Settings::getDefaultFontName() ?: Settings::DEFAULT_FONT_NAME, $this->getParentWriter()->getDefaultGenericFont()), 'font-size' => Settings::getDefaultFontSize() . 'pt', 'color' => "#{$defaultFontColor}", ]; - // Mpdf sometimes needs separate tag for body; doesn't harm others. - $bodyarray = $astarray; $defaultWhiteSpace = $this->getParentWriter()->getDefaultWhiteSpace(); if ($defaultWhiteSpace) { $astarray['white-space'] = $defaultWhiteSpace; } + $bodyarray = $astarray; foreach ([ 'body' => $bodyarray, - '*' => $astarray, 'a.NoteRef' => [ 'text-decoration' => 'none', ], @@ -121,6 +119,9 @@ private function writeStyles(): string 'td' => [ 'border' => '1px solid black', ], + 'th' => [ + 'border' => '1px solid black', + ], ] as $selector => $style) { $styleWriter = new GenericStyleWriter($style); $css .= $selector . ' {' . $styleWriter->write() . '}' . PHP_EOL; @@ -129,18 +130,20 @@ private function writeStyles(): string // Custom styles $customStyles = Style::getStyles(); if (is_array($customStyles)) { - foreach ($customStyles as $name => $style) { + foreach ($customStyles as $namex => $style) { + $name = Style::alternateName($namex); $styleParagraph = null; if ($style instanceof Font) { $styleWriter = new FontStyleWriter($style); if ($style->getStyleType() == 'title') { $name = str_replace('Heading_', 'h', $name); + $css .= "{$name} {" . $styleWriter->write() . '}' . PHP_EOL; $styleParagraph = $style->getParagraph(); $style = $styleParagraph; } else { $name = '.' . $name; + $css .= "{$name} {" . $styleWriter->write() . '}' . PHP_EOL; } - $css .= "{$name} {" . $styleWriter->write() . '}' . PHP_EOL; } if ($style instanceof Paragraph) { $styleWriter = new ParagraphStyleWriter($style); @@ -197,9 +200,6 @@ private function writeStyles(): string */ private function getFontFamily(string $font, string $genericFont): string { - if (empty($font)) { - return ''; - } $fontfamily = "'" . htmlspecialchars($font, ENT_QUOTES, 'UTF-8') . "'"; if (!empty($genericFont)) { $fontfamily .= ", $genericFont"; diff --git a/src/PhpWord/Writer/HTML/Style/Font.php b/src/PhpWord/Writer/HTML/Style/Font.php index 1001b64d2e0..9e27e7d9828 100644 --- a/src/PhpWord/Writer/HTML/Style/Font.php +++ b/src/PhpWord/Writer/HTML/Style/Font.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Writer\HTML\Style; +use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\Style\Font as FontStyle; /** @@ -27,6 +28,26 @@ */ class Font extends AbstractStyle { + private const UNDERLINES = [ + FontStyle::UNDERLINE_DASH => 'underline dashed ', + FontStyle::UNDERLINE_DASHDOTDOTHEAVY => 'underline dotted 2px ', + FontStyle::UNDERLINE_DASHDOTHEAVY => 'underline dotted 2px ', + FontStyle::UNDERLINE_DASHEDHEAVY => 'underline dashed 2px ', + FontStyle::UNDERLINE_DASHLONG => 'underline dashed ', + FontStyle::UNDERLINE_DASHLONGHEAVY => 'underline dashed 2px ', + FontStyle::UNDERLINE_DOTDASH => 'underline dotted ', + FontStyle::UNDERLINE_DOTDOTDASH => 'underline dotted ', + FontStyle::UNDERLINE_DOTTED => 'underline dotted ', + FontStyle::UNDERLINE_DOTTEDHEAVY => 'underline dotted 2px ', + FontStyle::UNDERLINE_DOUBLE => 'underline double ', + FontStyle::UNDERLINE_HEAVY => 'underline solid 2px ', + FontStyle::UNDERLINE_SINGLE => 'underline solid ', + FontStyle::UNDERLINE_WAVY => 'underline wavy ', + FontStyle::UNDERLINE_WAVYDOUBLE => 'underline wavy ', + FontStyle::UNDERLINE_WAVYHEAVY => 'underline wavy 2px ', + FontStyle::UNDERLINE_WORDS => 'underline solid ', + ]; + /** * Write style. * @@ -49,7 +70,9 @@ public function write() $css['font-family'] = $this->getValueIf(!empty($font), $font); $css['font-size'] = $this->getValueIf($size !== null, "{$size}pt"); - $css['color'] = $this->getValueIf($color !== null, "#{$color}"); + if ($color !== null) { + $css['color'] = Converter::validStringColor($color) ? $color : "#$color"; + } $css['background'] = $this->getValueIf($fgColor != '', $fgColor); $css['font-weight'] = $this->getValueIf($style->isBold(), 'bold'); $css['font-style'] = $this->getValueIf($style->isItalic(), 'italic'); @@ -57,7 +80,9 @@ public function write() $css['vertical-align'] .= $this->getValueIf($style->isSuperScript(), 'super'); $css['vertical-align'] .= $this->getValueIf($style->isSubScript(), 'sub'); $css['text-decoration'] = ''; - $css['text-decoration'] .= $this->getValueIf($underline, 'underline '); + if (isset(self::UNDERLINES[$style->getUnderline()])) { + $css['text-decoration'] .= $this->getValueIf($underline, self::UNDERLINES[$style->getUnderline()]); + } $css['text-decoration'] .= $this->getValueIf($lineThrough, 'line-through '); $css['text-transform'] = $this->getValueIf($style->isAllCaps(), 'uppercase'); $css['font-variant'] = $this->getValueIf($style->isSmallCaps(), 'small-caps'); @@ -74,6 +99,13 @@ public function write() } elseif ($style->isRTL() === false) { $css['direction'] = 'ltr'; } + $shading = $style->getShading(); + if ($shading !== null) { + $fill = $shading->getFill(); + if (!empty($fill)) { + $css['background-color'] = preg_match('/^[0-9a-fA-F]{6}$/', $fill) ? "#$fill" : $fill; + } + } return $this->assembleCss($css); } diff --git a/src/PhpWord/Writer/HTML/Style/Paragraph.php b/src/PhpWord/Writer/HTML/Style/Paragraph.php index 22267e7b180..4e797c8dc9f 100644 --- a/src/PhpWord/Writer/HTML/Style/Paragraph.php +++ b/src/PhpWord/Writer/HTML/Style/Paragraph.php @@ -29,6 +29,19 @@ */ class Paragraph extends AbstractStyle { + const ALIGNMENT_MAP = [ + Jc::CENTER => 'center', + Jc::MEDIUM_KASHIDA => 'right', + Jc::HIGH_KASHIDA => 'right', + Jc::LOW_KASHIDA => 'right', + Jc::RIGHT => 'right', + Jc::BOTH => 'justify', + Jc::DISTRIBUTE => 'justify', + Jc::THAI_DISTRIBUTE => 'justify', + Jc::JUSTIFY => 'justify', + Jc::LEFT => 'left', + ]; + /** * Write style. * @@ -43,40 +56,12 @@ public function write() $css = []; // Alignment - if ('' !== $style->getAlignment()) { - $textAlign = ''; - - switch ($style->getAlignment()) { - case Jc::CENTER: - $textAlign = 'center'; - - break; - case Jc::END: - $textAlign = $style->isBidi() ? 'left' : 'right'; - - break; - case Jc::MEDIUM_KASHIDA: - case Jc::HIGH_KASHIDA: - case Jc::LOW_KASHIDA: - case Jc::RIGHT: - $textAlign = 'right'; - - break; - case Jc::BOTH: - case Jc::DISTRIBUTE: - case Jc::THAI_DISTRIBUTE: - case Jc::JUSTIFY: - $textAlign = 'justify'; - - break; - case Jc::LEFT: - $textAlign = 'left'; - - break; - default: //all others, including Jc::START - $textAlign = $style->isBidi() ? 'right' : 'left'; - - break; + $alignment = $style->getAlignment(); + if ('' !== $alignment) { + if (Jc::END === $alignment) { + $textAlign = $style->isBidi() ? 'left' : 'right'; + } else { + $textAlign = self::ALIGNMENT_MAP[$alignment] ?? ($style->isBidi() ? 'right' : 'left'); } $css['text-align'] = $textAlign; diff --git a/src/PhpWord/Writer/HTML/Style/Table.php b/src/PhpWord/Writer/HTML/Style/Table.php index 6d3e43e8128..15e50283d1e 100644 --- a/src/PhpWord/Writer/HTML/Style/Table.php +++ b/src/PhpWord/Writer/HTML/Style/Table.php @@ -58,7 +58,7 @@ public function write() if ($outval === 'single') { $outval = 'solid'; } - if (is_string($outval) && 1 == preg_match('/^[a-z]+$/', $outval)) { + if (is_string($outval) && 1 === preg_match('/^[a-z]+$/', $outval)) { $css['border-' . lcfirst($direction) . '-style'] = $outval; } } @@ -66,7 +66,9 @@ public function write() $method = 'getBorder' . $direction . 'Color'; if (method_exists($style, $method)) { $outval = $style->{$method}(); - if (is_string($outval) && 1 == preg_match('/^[a-z]+$/', $outval)) { + if (is_string($outval) && 1 === preg_match('/^[a-fA-F0-9]{6}$/', $outval)) { + $css['border-' . lcfirst($direction) . '-color'] = "#$outval"; + } elseif (is_string($outval) && 1 === preg_match('/^[a-z][a-z0-9]+$/', $outval)) { $css['border-' . lcfirst($direction) . '-color'] = $outval; } } diff --git a/src/PhpWord/Writer/ODText.php b/src/PhpWord/Writer/ODText.php index c9a524e8820..7b6dea7d69b 100644 --- a/src/PhpWord/Writer/ODText.php +++ b/src/PhpWord/Writer/ODText.php @@ -72,6 +72,7 @@ public function __construct(?PhpWord $phpWord = null) */ public function save(string $filename): void { + PhpWord::noPhar($filename); $filename = $this->getTempFile($filename); $zip = $this->getZipArchive($filename); @@ -83,19 +84,14 @@ public function save(string $filename): void // Write parts foreach ($this->parts as $partName => $fileName) { - if ($fileName === '') { - continue; - } - $part = $this->getWriterPart($partName); - if (!$part instanceof AbstractPart) { - continue; + if ($fileName !== '') { + $part = $this->getWriterPart($partName); + if ($part instanceof AbstractPart) { + $part->setObjects($this->objects); + $zip->addFromString($fileName, $part->write()); + $this->objects = $part->getObjects(); + } } - - $part->setObjects($this->objects); - - $zip->addFromString($fileName, $part->write()); - - $this->objects = $part->getObjects(); } // Write objects charts diff --git a/src/PhpWord/Writer/ODText/Element/Formula.php b/src/PhpWord/Writer/ODText/Element/Formula.php index 2c7ce3aaf19..d1444a093aa 100644 --- a/src/PhpWord/Writer/ODText/Element/Formula.php +++ b/src/PhpWord/Writer/ODText/Element/Formula.php @@ -35,15 +35,11 @@ class Formula extends AbstractElement public function write(): void { $xmlWriter = $this->getXmlWriter(); + /** @var ElementFormula */ $element = $this->getElement(); - if (!$element instanceof ElementFormula) { - return; - } + /** @var AbstractPart */ $part = $this->getPart(); - if (!$part instanceof AbstractPart) { - return; - } $objectIdx = $part->addObject($element); diff --git a/src/PhpWord/Writer/ODText/Element/ListItemRun.php b/src/PhpWord/Writer/ODText/Element/ListItemRun.php index 1319e48577a..a52093ddac2 100644 --- a/src/PhpWord/Writer/ODText/Element/ListItemRun.php +++ b/src/PhpWord/Writer/ODText/Element/ListItemRun.php @@ -32,10 +32,8 @@ class ListItemRun extends AbstractElement */ public function write(): void { + /** @var ListItemRunElement */ $element = $this->getElement(); - if (!$element instanceof ListItemRunElement) { - return; - } $depth = $element->getDepth() + 1; $xmlWriter = $this->getXmlWriter(); diff --git a/src/PhpWord/Writer/ODText/Element/Ruby.php b/src/PhpWord/Writer/ODText/Element/Ruby.php index 41a86776d4f..795f288df32 100644 --- a/src/PhpWord/Writer/ODText/Element/Ruby.php +++ b/src/PhpWord/Writer/ODText/Element/Ruby.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\ODText\Element; +use PhpOffice\PhpWord\Element\TextRun; + /** * Ruby element writer. * NOTE: This class will write out a Ruby element in the format {baseText} ({rubyText}) @@ -33,32 +35,39 @@ class Ruby extends AbstractElement public function write(): void { $xmlWriter = $this->getXmlWriter(); + /** @var \PhpOffice\PhpWord\Element\Ruby */ $element = $this->getElement(); - if (!$element instanceof \PhpOffice\PhpWord\Element\Ruby) { - return; - } - $paragraphStyle = $element->getBaseTextRun()->getParagraphStyle(); - if (!$this->withoutP) { $xmlWriter->startElement('text:p'); // text:p } - if (empty($paragraphStyle)) { - if (!$this->withoutP) { - $xmlWriter->writeAttribute('text:style-name', 'Normal'); - } - } elseif (is_string($paragraphStyle)) { - if (!$this->withoutP) { + $xmlWriter->startElement('text:ruby'); + $this->writeRuby($element->getBaseTextRun(), 'text:ruby-base'); + $this->writeRuby($element->getRubyTextRun(), 'text:ruby-text'); + $xmlWriter->endElement(); // text:ruby + if (!$this->withoutP) { + $xmlWriter->endElement(); // text:p + } + } + + /** + * @param TextRun $textRun + * @param string $tag + */ + private function writeRuby($textRun, $tag): void + { + $xmlWriter = $this->getXmlWriter(); + $paragraphStyle = $textRun->getParagraphStyle(); + + $xmlWriter->startElement($tag); // text:rubyBase or text:rubyText + if (is_string($paragraphStyle)) { + $paragraphStyle = trim($paragraphStyle); + if ($paragraphStyle !== '') { $xmlWriter->writeAttribute('text:style-name', $paragraphStyle); } } - $this->replaceTabs($element->getBaseTextRun()->getText(), $xmlWriter); - $this->writeText(' ('); - $this->replaceTabs($element->getRubyTextRun()->getText(), $xmlWriter); - $this->writeText(')'); + $this->replaceTabs($textRun->getText(), $xmlWriter); - if (!$this->withoutP) { - $xmlWriter->endElement(); // text:p - } + $xmlWriter->endElement(); // text:rubyBase or text:rubyText } } diff --git a/src/PhpWord/Writer/ODText/Part/Content.php b/src/PhpWord/Writer/ODText/Part/Content.php index b4958e84e60..6f423964712 100644 --- a/src/PhpWord/Writer/ODText/Part/Content.php +++ b/src/PhpWord/Writer/ODText/Part/Content.php @@ -107,7 +107,6 @@ public function write() } $xmlWriter->endElement(); // office:change-info if ($trackedChange->getChangeType() == TrackChange::DELETED && method_exists($trackedElement, 'getText')) { - // @phpstan-ignore-next-line $xmlWriter->writeElement('text:p', $trackedElement->getText()); } diff --git a/src/PhpWord/Writer/ODText/Style/Font.php b/src/PhpWord/Writer/ODText/Style/Font.php index 95582ec48b0..5551c2b6f27 100644 --- a/src/PhpWord/Writer/ODText/Style/Font.php +++ b/src/PhpWord/Writer/ODText/Style/Font.php @@ -18,6 +18,9 @@ namespace PhpOffice\PhpWord\Writer\ODText\Style; +use PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\Style\Font as FontStyle; + /** * Font style writer. * @@ -25,19 +28,43 @@ */ class Font extends AbstractStyle { + private const UNDERLINES = [ + FontStyle::UNDERLINE_DASH => 'dash', + FontStyle::UNDERLINE_DASHDOTDOTHEAVY => 'dot-dot-dash', + FontStyle::UNDERLINE_DASHDOTHEAVY => 'dot-dash', + FontStyle::UNDERLINE_DASHEDHEAVY => 'dash', + FontStyle::UNDERLINE_DASHLONG => 'long-dash', + FontStyle::UNDERLINE_DASHLONGHEAVY => 'long-dash', + FontStyle::UNDERLINE_DOTDASH => 'dot-dash', + FontStyle::UNDERLINE_DOTDOTDASH => 'dot-dot-dash', + FontStyle::UNDERLINE_DOTTED => 'dotted', + FontStyle::UNDERLINE_DOTTEDHEAVY => 'dotted', + FontStyle::UNDERLINE_DOUBLE => 'solid', + FontStyle::UNDERLINE_HEAVY => 'solid', + FontStyle::UNDERLINE_SINGLE => 'solid', + FontStyle::UNDERLINE_WAVY => 'wave', + FontStyle::UNDERLINE_WAVYDOUBLE => 'wave', + FontStyle::UNDERLINE_WAVYHEAVY => 'wave', + FontStyle::UNDERLINE_WORDS => 'solid', + ]; + /** * Write style. */ public function write(): void { $style = $this->getStyle(); - if (!$style instanceof \PhpOffice\PhpWord\Style\Font) { - return; + if ($style instanceof FontStyle) { + $this->writeStyle($style); } + } + + private function writeStyle(FontStyle $style): void + { $xmlWriter = $this->getXmlWriter(); $stylep = $style->getParagraph(); - if ($stylep instanceof \PhpOffice\PhpWord\Style\Paragraph) { + if ($stylep instanceof Style\Paragraph) { $temp1 = clone $stylep; $temp1->setStyleName($style->getStyleName()); $temp2 = new Paragraph($xmlWriter, $temp1); @@ -45,7 +72,7 @@ public function write(): void } $xmlWriter->startElement('style:style'); - $xmlWriter->writeAttribute('style:name', $style->getStyleName()); + $xmlWriter->writeAttribute('style:name', Style::alternateName($style->getStyleName())); $xmlWriter->writeAttribute('style:family', 'text'); $xmlWriter->startElement('style:text-properties'); @@ -61,8 +88,8 @@ public function write(): void $xmlWriter->writeAttributeIf(is_numeric($size), 'style:font-size-complex', $size . 'pt'); // Color - $color = $style->getColor(); - $xmlWriter->writeAttributeIf($color != '', 'fo:color', '#' . \PhpOffice\PhpWord\Shared\Converter::stringToRgb($color)); + $color = (string) $style->getColor(); + $xmlWriter->writeAttributeIf($color !== '', 'fo:color', '#' . \PhpOffice\PhpWord\Shared\Converter::stringToRgb($color)); // Bold & italic $xmlWriter->writeAttributeIf($style->isBold(), 'fo:font-weight', 'bold'); @@ -72,9 +99,14 @@ public function write(): void $xmlWriter->writeAttributeIf($style->isItalic(), 'style:font-style-complex', 'italic'); // Underline - // @todo Various mode of underline $underline = $style->getUnderline(); - $xmlWriter->writeAttributeIf($underline != 'none', 'style:text-underline-style', 'solid'); + if (isset(self::UNDERLINES[$underline])) { + $xmlWriter->writeAttribute('style:text-underline-style', self::UNDERLINES[$underline]); + $xmlWriter->writeAttributeIf(strpos(strtolower($underline), 'heavy') !== false, 'style:text-underline-width', 'bold'); + $xmlWriter->writeAttributeIf(strpos(strtolower($underline), 'thick') !== false, 'style:text-underline-width', 'bold'); + $xmlWriter->writeAttributeIf(strpos(strtolower($underline), 'double') !== false, 'style:text-underline-type', 'double'); + $xmlWriter->writeAttributeIf(strpos(strtolower($underline), 'words') !== false, 'style:text-underline-mode', 'skip-white-space'); + } // Strikethrough, double strikethrough $xmlWriter->writeAttributeIf($style->isStrikethrough(), 'style:text-line-through-type', 'single'); @@ -100,9 +132,9 @@ public function write(): void $xmlWriter->writeAttribute('style:country-complex', 'none'); } - // @todo Foreground-Color - - // @todo Background color + // Foreground-Color (which is really background color) + $fgColor = (string) $style->getFgColor(); + $xmlWriter->writeAttributeIf($fgColor !== '', 'fo:background-color', '#' . \PhpOffice\PhpWord\Shared\Converter::stringToRgb($fgColor)); $xmlWriter->endElement(); // style:text-properties $xmlWriter->endElement(); // style:style diff --git a/src/PhpWord/Writer/ODText/Style/Numbering.php b/src/PhpWord/Writer/ODText/Style/Numbering.php index 287b25b34a6..b13bea93fdd 100644 --- a/src/PhpWord/Writer/ODText/Style/Numbering.php +++ b/src/PhpWord/Writer/ODText/Style/Numbering.php @@ -31,11 +31,8 @@ class Numbering extends AbstractStyle */ public function write(): void { - /** @var StyleNumbering $style Type hint */ + /** @var StyleNumbering */ $style = $this->getStyle(); - if (!$style instanceof StyleNumbering) { - return; - } $xmlWriter = $this->getXmlWriter(); $xmlWriter->startElement('text:list-style'); diff --git a/src/PhpWord/Writer/PDF.php b/src/PhpWord/Writer/PDF.php index 4f7f1be99a8..bf493675f43 100644 --- a/src/PhpWord/Writer/PDF.php +++ b/src/PhpWord/Writer/PDF.php @@ -26,6 +26,10 @@ /** * PDF Writer. * + * @method string getFont() + * @method self setFont(string $font) + * @method string getContent() + * * @since 0.10.0 */ class PDF @@ -72,12 +76,15 @@ public function __call($name, $arguments) // if ($this->renderer === null) { // throw new Exception("PDF Rendering library has not been defined."); // } + /** @var callable */ + $callable = [$this->getRenderer(), $name]; - return call_user_func_array([$this->getRenderer(), $name], $arguments); + return call_user_func_array($callable, $arguments); } public function save(string $filename): void { + PhpWord::noPhar($filename); $this->getRenderer()->save($filename); } diff --git a/src/PhpWord/Writer/PDF/AbstractRenderer.php b/src/PhpWord/Writer/PDF/AbstractRenderer.php index 125bf4fa43b..8a15a606234 100644 --- a/src/PhpWord/Writer/PDF/AbstractRenderer.php +++ b/src/PhpWord/Writer/PDF/AbstractRenderer.php @@ -30,13 +30,6 @@ */ abstract class AbstractRenderer extends HTML { - /** - * Name of renderer include file. - * - * @var string - */ - protected $includeFile; - /** * Temporary storage directory. * @@ -83,18 +76,6 @@ public function __construct(PhpWord $phpWord) { parent::__construct($phpWord); $this->isPdf = true; - if ($this->includeFile != null) { - $includeFile = Settings::getPdfRendererPath() . '/' . $this->includeFile; - if (file_exists($includeFile)) { - /** @noinspection PhpIncludeInspection Dynamic includes */ - require_once $includeFile; - } else { - // @codeCoverageIgnoreStart - // Can't find any test case. Uncomment when found. - throw new Exception('Unable to load PDF Rendering library'); - // @codeCoverageIgnoreEnd - } - } // Configuration $options = Settings::getPdfRendererOptions(); diff --git a/src/PhpWord/Writer/PDF/DomPDF.php b/src/PhpWord/Writer/PDF/DomPDF.php index 464dbfa59f9..96b213f3816 100644 --- a/src/PhpWord/Writer/PDF/DomPDF.php +++ b/src/PhpWord/Writer/PDF/DomPDF.php @@ -20,6 +20,7 @@ use Dompdf\Dompdf as DompdfLib; use Dompdf\Options; +use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Writer\WriterInterface; /** @@ -30,13 +31,6 @@ */ class DomPDF extends AbstractRenderer implements WriterInterface { - /** - * Name of renderer include file. - * - * @var string - */ - protected $includeFile; - /** * Gets the implementation of external PDF library that should be used. * @@ -57,6 +51,7 @@ protected function createExternalWriterInstance() */ public function save(string $filename): void { + PhpWord::noPhar($filename); $fileHandle = parent::prepareForSave($filename); // PDF settings diff --git a/src/PhpWord/Writer/PDF/MPDF.php b/src/PhpWord/Writer/PDF/MPDF.php index 03ef1f3ad7c..4c24a70aa0e 100644 --- a/src/PhpWord/Writer/PDF/MPDF.php +++ b/src/PhpWord/Writer/PDF/MPDF.php @@ -33,20 +33,6 @@ class MPDF extends AbstractRenderer implements WriterInterface public const SIMULATED_BODY_START = ''; private const BODY_TAG = ''; - /** - * Overridden to set the correct includefile, only needed for MPDF 5. - * - * @codeCoverageIgnore - */ - public function __construct(PhpWord $phpWord) - { - if (file_exists(Settings::getPdfRendererPath() . '/mpdf.php')) { - // MPDF version 5.* needs this file to be included, later versions not - $this->includeFile = 'mpdf.php'; - } - parent::__construct($phpWord); - } - /** * Gets the implementation of external PDF library that should be used. * @@ -54,14 +40,12 @@ public function __construct(PhpWord $phpWord) */ protected function createExternalWriterInstance() { - $mPdfClass = $this->getMPdfClassName(); - $options = []; if ($this->getFont()) { $options['default_font'] = $this->getFont(); } - return new $mPdfClass($options); + return new \Mpdf\Mpdf($options); } /** @@ -69,6 +53,7 @@ protected function createExternalWriterInstance() */ public function save(string $filename): void { + PhpWord::noPhar($filename); $fileHandle = parent::prepareForSave($filename); // PDF settings @@ -113,22 +98,4 @@ public function save(string $filename): void parent::restoreStateAfterSave($fileHandle); } - - /** - * Return classname of MPDF to instantiate. - * - * @codeCoverageIgnore - * - * @return string - */ - private function getMPdfClassName() - { - if ($this->includeFile != null) { - // MPDF version 5.* - return '\mpdf'; - } - - // MPDF version > 6.* - return '\Mpdf\Mpdf'; - } } diff --git a/src/PhpWord/Writer/PDF/TCPDF.php b/src/PhpWord/Writer/PDF/TCPDF.php index 93bdd58528a..2b39acd025a 100644 --- a/src/PhpWord/Writer/PDF/TCPDF.php +++ b/src/PhpWord/Writer/PDF/TCPDF.php @@ -27,18 +27,14 @@ /** * TCPDF writer. * - * @deprecated 0.13.0 Use `DomPDF` or `MPDF` instead. * @see http://www.tcpdf.org/ * @since 0.11.0 */ class TCPDF extends AbstractRenderer implements WriterInterface { - /** - * Name of renderer include file. - * - * @var string - */ - protected $includeFile = 'tcpdf.php'; + protected function defines(): void + { + } /** * Gets the implementation of external PDF library that should be used. @@ -51,6 +47,7 @@ class TCPDF extends AbstractRenderer implements WriterInterface */ protected function createExternalWriterInstance($orientation, $unit, $paperSize) { + $this->defines(); $instance = new TCPDFBase($orientation, $unit, $paperSize); if ($this->getFont()) { @@ -93,6 +90,7 @@ protected function prepareToWrite(TCPDFBase $pdf): void */ public function save(string $filename): void { + PhpWord::noPhar($filename); $fileHandle = parent::prepareForSave($filename); // PDF settings diff --git a/src/PhpWord/Writer/PDF/TcpdfNoDie.php b/src/PhpWord/Writer/PDF/TcpdfNoDie.php new file mode 100644 index 00000000000..2e85d702f6a --- /dev/null +++ b/src/PhpWord/Writer/PDF/TcpdfNoDie.php @@ -0,0 +1,43 @@ +writeFile($this->openFile($filename), $this->getContent()); } @@ -68,7 +69,7 @@ public function save(string $filename): void * * @since 0.11.0 */ - private function getContent() + public function getContent() { $content = ''; @@ -101,6 +102,16 @@ public function getColorTable() return $this->getWriterPart('Header')->getColorTable(); } + /** + * Get list table. + * + * @return array + */ + public function getListTable() + { + return $this->getWriterPart('Header')->getListTable(); + } + /** * Get last paragraph style. * diff --git a/src/PhpWord/Writer/RTF/Element/AbstractElement.php b/src/PhpWord/Writer/RTF/Element/AbstractElement.php index e007e6aa268..5a07ed8eade 100644 --- a/src/PhpWord/Writer/RTF/Element/AbstractElement.php +++ b/src/PhpWord/Writer/RTF/Element/AbstractElement.php @@ -20,8 +20,6 @@ use PhpOffice\PhpWord\Element\AbstractElement as Element; use PhpOffice\PhpWord\Escaper\Rtf; -use PhpOffice\PhpWord\Settings; -use PhpOffice\PhpWord\Shared\Text as SharedText; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font as FontStyle; use PhpOffice\PhpWord\Style\Paragraph as ParagraphStyle; @@ -67,14 +65,14 @@ abstract public function write(); /** * Font style. * - * @var FontStyle + * @var null|FontStyle|string */ protected $fontStyle; /** * Paragraph style. * - * @var ParagraphStyle + * @var null|ParagraphStyle|string */ protected $paragraphStyle; @@ -106,7 +104,14 @@ protected function getStyles(): void if (method_exists($element, 'getFontStyle')) { $this->fontStyle = $element->getFontStyle(); if (is_string($this->fontStyle)) { - $this->fontStyle = Style::getStyle($this->fontStyle); + /** @var FontStyle */ + $temp = Style::getStyle($this->fontStyle); + $this->fontStyle = $temp; + } elseif ($this->fontStyle !== null && is_string($this->fontStyle->getStyleName())) { + $temp = Style::getStyle($this->fontStyle->getStyleName()); + if ($temp instanceof FontStyle) { + $this->fontStyle = $temp; + } } } @@ -114,7 +119,9 @@ protected function getStyles(): void if (method_exists($element, 'getParagraphStyle')) { $this->paragraphStyle = $element->getParagraphStyle(); if (is_string($this->paragraphStyle)) { - $this->paragraphStyle = Style::getStyle($this->paragraphStyle); + /** @var null|ParagraphStyle */ + $temp = Style::getStyle($this->paragraphStyle); + $this->paragraphStyle = $temp; } if ($this->paragraphStyle !== null && !$this->withoutP) { @@ -143,6 +150,7 @@ protected function writeOpening() } $styleWriter = new ParagraphStyleWriter($this->paragraphStyle); + $styleWriter->setParentWriter($this->parentWriter); $styleWriter->setNestedLevel($this->element->getNestedLevel()); return $styleWriter->write(); @@ -157,11 +165,7 @@ protected function writeOpening() */ protected function writeText($text) { - if (Settings::isOutputEscapingEnabled()) { - return $this->escaper->escape($text); - } - - return SharedText::toUnicode($text); // todo: replace with `return $text;` later. + return $this->escaper->escape($text); } /** @@ -194,16 +198,29 @@ protected function writeFontStyle() // Create style writer and set color/name index $styleWriter = new FontStyleWriter($this->fontStyle); + $styleWriter->setParentWriter($this->parentWriter); if ($this->fontStyle->getColor() != null) { $colorIndex = array_search($this->fontStyle->getColor(), $parentWriter->getColorTable()); if ($colorIndex !== false) { - $styleWriter->setColorIndex($colorIndex + 1); + $styleWriter->setColorIndex((int) $colorIndex + 1); + } + } + if ($this->fontStyle->getFgColor() != null) { + $colorIndex = array_search($this->fontStyle->getFgColor(), $parentWriter->getColorTable()); + if ($colorIndex !== false) { + $styleWriter->setFgColorIndex((int) $colorIndex + 1); + } + } + if ($this->fontStyle->getBgColor() != null) { + $colorIndex = array_search($this->fontStyle->getBgColor(), $parentWriter->getColorTable()); + if ($colorIndex !== false) { + $styleWriter->setBgColorIndex((int) $colorIndex + 1); } } if ($this->fontStyle->getName() != null) { $fontIndex = array_search($this->fontStyle->getName(), $parentWriter->getFontTable()); if ($fontIndex !== false) { - $styleWriter->setNameIndex($fontIndex); + $styleWriter->setNameIndex((int) $fontIndex); } } diff --git a/src/PhpWord/Writer/RTF/Element/Container.php b/src/PhpWord/Writer/RTF/Element/Container.php index dcac8ec071f..8a5ba87da86 100644 --- a/src/PhpWord/Writer/RTF/Element/Container.php +++ b/src/PhpWord/Writer/RTF/Element/Container.php @@ -46,7 +46,7 @@ public function write() return ''; } $containerClass = substr(get_class($container), strrpos(get_class($container), '\\') + 1); - $withoutP = in_array($containerClass, ['TextRun', 'Footnote', 'Endnote']) ? true : false; + $withoutP = in_array($containerClass, ['TextRun', 'Footnote', 'Endnote', 'ListItemRun', 'Field']) ? true : false; $content = ''; $elements = $container->getElements(); diff --git a/src/PhpWord/Writer/RTF/Element/ListItem.php b/src/PhpWord/Writer/RTF/Element/ListItem.php index 8ce7d0d063e..dab99e5f54b 100644 --- a/src/PhpWord/Writer/RTF/Element/ListItem.php +++ b/src/PhpWord/Writer/RTF/Element/ListItem.php @@ -18,6 +18,9 @@ namespace PhpOffice\PhpWord\Writer\RTF\Element; +use PhpOffice\PhpWord\Element\ListItem as Li; +use PhpOffice\PhpWord\Style; + /** * ListItem element RTF writer; extends from text. * @@ -25,4 +28,74 @@ */ class ListItem extends Text { + /** + * Write list item element. + */ + public function write() + { + $element = $this->element; + + return ($element instanceof Li) ? $this->writeElement($element) : ''; + } + + /** + * @return string + */ + private function writeElement(Li $element) + { + $this->getStyles(); + + $depth = (int) $element->getDepth(); + $style = $element->getStyle(); + $text = $element->getTextObject(); + + // Bullet List + $content = ''; + $content .= $this->writeOpening(); + if ($style instanceof Style\ListItem) { + $numStyle = $style->getNumbering(); + if ($numStyle->getType() == 'singleLevel') { + $depth = 0; + } + $levels = $numStyle->getLevels(); + $content .= '\ilvl' . $element->getDepth(); + $content .= '\ls' . $style->getNumId(); + $content .= '\tx' . $levels[$depth]->getTabPos(); + $content .= '\fi' . $levels[$depth]->getHanging() * -1; + $content .= '\li' . $levels[$depth]->getLeft(); + $content .= '\lin' . $levels[$depth]->getLeft(); + } + $content .= $this->writeFontStyle(); // ListItem Text has its own font style applied later. + $content .= PHP_EOL; + /* $content .= '{\listtext\f2 \\\'b7\tab }'; // Not sure if needed for listItemRun + $content .= PHP_EOL; */ + $content .= '{'; + + $textStart = $textStyle = $textEnd = ''; + $textFontStyle = null; + $textObject = $element->getTextObject(); + if ($textObject !== null) { + $textFontStyle = $textObject->getFontStyle(); + if (is_string($textFontStyle)) { + $textFontStyle = Style::getStyle($textFontStyle); + } + } + if ($textFontStyle instanceof Style\Font) { + $this->fontStyle = $textFontStyle; + $textStyle = $this->writeFontStyle(); + if ($textStyle !== '') { + $textStart = '{'; + $textEnd = '}'; + } + } + + $content .= $textStart . $textStyle . $this->writeText($element->getText()) . $textEnd; + + $content .= '}'; + + $content .= PHP_EOL; + $content .= $this->writeClosing(); + + return $content; + } } diff --git a/src/PhpWord/Writer/RTF/Element/ListItemRun.php b/src/PhpWord/Writer/RTF/Element/ListItemRun.php new file mode 100644 index 00000000000..dda67029a8c --- /dev/null +++ b/src/PhpWord/Writer/RTF/Element/ListItemRun.php @@ -0,0 +1,74 @@ +element; + + return ($element instanceof Lir) ? $this->writeElement($element) : ''; + } + + /** + * @return string + */ + private function writeElement(Lir $element) + { + $writer = new Container($this->parentWriter, $element); + $this->getStyles(); + + $depth = (int) $element->getDepth(); + $style = $element->getStyle(); + + $content = ''; + $content .= $this->writeOpening(); + if ($style instanceof \PhpOffice\PhpWord\Style\ListItem) { + $numStyle = $style->getNumbering(); + $levels = $numStyle->getLevels(); + $content .= '\ilvl' . $element->getDepth(); + $content .= '\ls' . $style->getNumId(); + $content .= '\tx' . $levels[$depth]->getTabPos(); + $hanging = $levels[$depth]->getLeft() + $levels[$depth]->getHanging(); + $left = 0 - $levels[$depth]->getHanging(); + $content .= '\fi' . $left; + $content .= '\li' . $hanging; + $content .= '\lin' . $hanging; + } + $content .= '{'; + $content .= $writer->write(); + $content .= '}'; + $content .= $this->writeClosing(); + + return $content; + } +} diff --git a/src/PhpWord/Writer/RTF/Element/PreserveText.php b/src/PhpWord/Writer/RTF/Element/PreserveText.php new file mode 100644 index 00000000000..4f3d6cdc910 --- /dev/null +++ b/src/PhpWord/Writer/RTF/Element/PreserveText.php @@ -0,0 +1,65 @@ +element instanceof PreserveTextElement) ? $this->writeElement($this->element) : ''; + } + + private function writeElement(PreserveTextElement $element): string + { + $this->getStyles(); + + $content = ''; + $content .= $this->writeOpening(); + $content .= '{'; + $content .= $this->writeFontStyle(); + if (is_array($element->getText())) { + foreach ($element->getText() as $text) { + if (preg_match('/[{}]/', $text) == 1) { + $text = str_replace(['{', '}'], '', $text); + $content .= '{\field {\*\fldinst {' . $text . '}}{\\fldrslt {}}}'; + } else { + $content .= $this->writeText($text); + } + } + } else { + $content .= $this->writeText($element->getText()); + } + $content .= '}'; + $content .= $this->writeClosing(); + + return $content; + } +} diff --git a/src/PhpWord/Writer/RTF/Element/Ruby.php b/src/PhpWord/Writer/RTF/Element/Ruby.php index e2b617c556d..c236c1e5a05 100644 --- a/src/PhpWord/Writer/RTF/Element/Ruby.php +++ b/src/PhpWord/Writer/RTF/Element/Ruby.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\RTF\Element; +use PhpOffice\PhpWord\Element\Ruby as RubyElement; + /** * Ruby element RTF writer. Writes {baseText} ({rubyText}) in current paragraph style * because RTF does not natively support ruby text. @@ -31,13 +33,16 @@ class Ruby extends AbstractElement */ public function write() { - /** @var \PhpOffice\PhpWord\Element\Ruby $element */ $element = $this->element; - $elementClass = str_replace('\\Writer\\RTF', '', static::class); - if (!$element instanceof $elementClass || !is_string($element->getBaseTextRun()->getText())) { - return ''; - } + return ($element instanceof RubyElement) ? $this->writeElement($element) : ''; + } + + /** + * @return string + */ + private function writeElement(RubyElement $element) + { $this->getStyles(); $content = ''; diff --git a/src/PhpWord/Writer/RTF/Element/Table.php b/src/PhpWord/Writer/RTF/Element/Table.php index 3b08a5db86e..7a4de347e96 100644 --- a/src/PhpWord/Writer/RTF/Element/Table.php +++ b/src/PhpWord/Writer/RTF/Element/Table.php @@ -175,6 +175,10 @@ private function writeCellStyle(CellStyle $cell, ?TableStyle $table): string return $content; } + const MAP_BORDER_TYPE = [ + Border::DOTTED => '\brdrdot', + ]; + private function writeCellBorder(string $prefix, ?string $borderStyle, int $borderSize, ?string $borderColor): string { if ($borderSize == 0) { @@ -211,17 +215,7 @@ private function writeCellBorder(string $prefix, ?string $borderStyle, int $bord * \brdremboss Emboss border. * \brdrengrave Engrave border. */ - switch ($borderStyle) { - case Border::DOTTED: - $content .= '\brdrdot'; - - break; - case Border::SINGLE: - default: - $content .= '\brdrs'; - - break; - } + $content .= self::MAP_BORDER_TYPE[$borderStyle ?? ''] ?? '\brdrs'; // \brdrwN N is the width in twips (1/20 pt) of the pen used to draw the paragraph border line. // N cannot be greater than 75. diff --git a/src/PhpWord/Writer/RTF/Element/Text.php b/src/PhpWord/Writer/RTF/Element/Text.php index 71d3a27ce84..4570a5d4a22 100644 --- a/src/PhpWord/Writer/RTF/Element/Text.php +++ b/src/PhpWord/Writer/RTF/Element/Text.php @@ -45,6 +45,12 @@ public function write() $content .= $this->writeOpening(); $content .= '{'; $content .= $this->writeFontStyle(); + $change = $element->getTrackChange(); + if ($change !== null) { + if ($change->getChangeType() === 'DELETED') { + $content .= '\strike '; + } + } $content .= $this->writeText($element->getText()); $content .= '}'; $content .= $this->writeClosing(); diff --git a/src/PhpWord/Writer/RTF/Element/TextBreak.php b/src/PhpWord/Writer/RTF/Element/TextBreak.php index d74bf23dcbe..c533a32f45f 100644 --- a/src/PhpWord/Writer/RTF/Element/TextBreak.php +++ b/src/PhpWord/Writer/RTF/Element/TextBreak.php @@ -36,6 +36,10 @@ public function write() $parentWriter = $this->parentWriter; $parentWriter->setLastParagraphStyle(); + if ($this->withoutP) { + return '\line' . PHP_EOL; + } + return '\pard\par' . PHP_EOL; } } diff --git a/src/PhpWord/Writer/RTF/Element/TextRun.php b/src/PhpWord/Writer/RTF/Element/TextRun.php index 6418de54a89..68606b1f3a9 100644 --- a/src/PhpWord/Writer/RTF/Element/TextRun.php +++ b/src/PhpWord/Writer/RTF/Element/TextRun.php @@ -34,6 +34,17 @@ public function write() { $writer = new Container($this->parentWriter, $this->element); $this->getStyles(); + if ($this->paragraphStyle instanceof \PhpOffice\PhpWord\Style\Paragraph && $this->paragraphStyle->hasPageBreakBefore()) { + $sect = $this->element->getParent(); + if ($sect instanceof \PhpOffice\PhpWord\Element\Section) { + $elems = $sect->getElements(); + if ($elems[0] === $this->element) { + $pStyle = clone $this->paragraphStyle; + $pStyle->setPageBreakBefore(false); + $this->paragraphStyle = $pStyle; + } + } + } $content = ''; $content .= $this->writeOpening(); diff --git a/src/PhpWord/Writer/RTF/Element/Title.php b/src/PhpWord/Writer/RTF/Element/Title.php index 06266897b37..b47f5d2cb8e 100644 --- a/src/PhpWord/Writer/RTF/Element/Title.php +++ b/src/PhpWord/Writer/RTF/Element/Title.php @@ -58,16 +58,16 @@ public function write() { /** @var \PhpOffice\PhpWord\Element\Title $element Type hint */ $element = $this->element; + $text = method_exists($element, 'getText') ? $element->getText() : null; + // check for text run + if (is_object($text) && method_exists($text, 'getText')) { + $text = $text->getText(); + } $elementClass = str_replace('\\Writer\\RTF', '', static::class); - if (!$element instanceof $elementClass) { + if (!$element instanceof $elementClass || !is_string($text)) { return ''; } - $textToWrite = $element->getText(); - if ($textToWrite instanceof \PhpOffice\PhpWord\Element\TextRun) { - $textToWrite = $textToWrite->getText(); // gets text from TextRun - } - $this->getStyles(); $content = ''; @@ -88,7 +88,7 @@ public function write() $content .= '{'; $content .= $this->writeFontStyle(); - $content .= $this->writeText($textToWrite); + $content .= $this->writeText($text); $content .= '}'; $content .= $this->writeClosing(); $content .= $endout; diff --git a/src/PhpWord/Writer/RTF/Part/Document.php b/src/PhpWord/Writer/RTF/Part/Document.php index 484393477d1..22a9049209e 100644 --- a/src/PhpWord/Writer/RTF/Part/Document.php +++ b/src/PhpWord/Writer/RTF/Part/Document.php @@ -78,12 +78,14 @@ private function writeInfo() $method = 'get' . $propertyMethod; $value = $docProps->$method(); - if (!in_array($property, $dateFields) && Settings::isOutputEscapingEnabled()) { + if (!in_array($property, $dateFields)) { $value = $this->escaper->escape($value); } $value = in_array($property, $dateFields) ? $this->getDateValue($value) : $value; - $content .= "{\\{$property} {$value}}"; + if ($value !== '') { + $content .= "{\\{$property} {$value}}"; + } } $content .= '}'; $content .= PHP_EOL; @@ -108,14 +110,21 @@ private function writeFormatting() $content .= '\viewkind1'; // Set the view mode of the document $content .= '\uc1'; // Set the numberof bytes that follows a unicode character - $content .= '\pard'; // Resets to default paragraph properties. - $content .= '\nowidctlpar'; // No widow/orphan control + if ($docSettings->hasRtfWidowControl()) { + $content .= '\widowctrl'; + } $content .= '\lang' . $langId; $content .= '\kerning1'; // Point size (in half-points) above which to kern character pairs $content .= '\fs' . (Settings::getDefaultFontSize() * 2); // Set the font size in half-points - if ($docSettings->hasEvenAndOddHeaders()) { + if ($docSettings->hasEvenAndOddHeaders() || $docSettings->hasMirrorMargins()) { $content .= '\\facingp'; } + if ($docSettings->hasMirrorMargins()) { + $content .= '\\margmirror'; + } + if ($docSettings->hasBookFoldPrinting()) { + $content .= '\\bookfold\\landscape'; + } $content .= PHP_EOL; return $content; diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index 97644fe4ac8..505bc65d99c 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -20,8 +20,11 @@ use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Converter; +use PhpOffice\PhpWord\SimpleType\NumberFormat; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; +use PhpOffice\PhpWord\Style\Numbering; +use PhpOffice\PhpWord\Style\Paragraph; use PhpOffice\PhpWord\Style\Table; /** @@ -53,6 +56,13 @@ class Header extends AbstractPart */ private $colorTable = []; + /** + * List table. + * + * @var array + */ + private $listTable = []; + /** * Get font table. * @@ -73,6 +83,16 @@ public function getColorTable() return $this->colorTable; } + /** + * Get list table. + * + * @return array + */ + public function getListTable() + { + return $this->listTable; + } + /** * Write part. * @@ -80,7 +100,7 @@ public function getColorTable() */ public function write() { - $this->registerFont(); + $this->registerHeader(); $content = ''; @@ -88,6 +108,7 @@ public function write() $content .= $this->writeDefaults(); $content .= $this->writeFontTable(); $content .= $this->writeColorTable(); + $content .= $this->writeListTable(); $content .= $this->writeGenerator(); $content .= PHP_EOL; @@ -157,7 +178,11 @@ private function writeColorTable() $content .= '{'; $content .= '\colortbl;'; foreach ($this->colorTable as $color) { - [$red, $green, $blue] = Converter::htmlToRgb($color); + $temp = Converter::htmlToRgb($color); + $red = $green = $blue = 0; + if (is_array($temp)) { + [$red, $green, $blue] = $temp; + } $content .= "\\red{$red}\\green{$green}\\blue{$blue};"; } $content .= '}'; @@ -166,6 +191,185 @@ private function writeColorTable() return $content; } + /** + * Write list table. + * + * @return string + */ + private function writeListTable() + { + $content = ''; + + $listType = [ + 'singleLevel' => '\listsimple1', + 'multilevel' => '\listsimple0', + 'hybridMultilevel' => '\listhybrid', + ]; + + $numberType = [ + NumberFormat::DECIMAL => '0', + NumberFormat::UPPER_ROMAN => '1', + NumberFormat::LOWER_ROMAN => '2', + NumberFormat::UPPER_LETTER => '3', + NumberFormat::LOWER_LETTER => '4', + NumberFormat::ORDINAL => '5', + NumberFormat::CARDINAL_TEXT => '6', + NumberFormat::ORDINAL_TEXT => '7', + /* NumberFormat::HEX => 'hex', + NumberFormat::CHICAGO => 'chicago', + NumberFormat::IDEOGRAPH_DIGITAL => 'ideographDigital', + NumberFormat::JAPANESE_COUNTING => 'japaneseCounting', */ + NumberFormat::AIUEO => '12', + NumberFormat::IROHA => '13', + /* NumberFormat::DECIMAL_FULL_WIDTH => 'decimalFullWidth', + NumberFormat::DECIMAL_HALF_WIDTH => 'decimalHalfWidth', + NumberFormat::JAPANESE_LEGAL => 'japaneseLegal', + NumberFormat::JAPANESE_DIGITAL_TEN_THOUSAND => 'japaneseDigitalTenThousand', + NumberFormat::DECIMAL_ENCLOSED_CIRCLE => 'decimalEnclosedCircle', + NumberFormat::DECIMAL_FULL_WIDTH2 => 'decimalFullWidth2', */ + NumberFormat::AIUEO_FULL_WIDTH => '20', + NumberFormat::IROHA_FULL_WIDTH => '21', + // NumberFormat::DECIMAL_ZERO => 'decimalZero', + NumberFormat::BULLET => '23', + /* NumberFormat::GANADA => 'ganada', + NumberFormat::CHOSUNG => 'chosung', + NumberFormat::DECIMAL_ENCLOSED_FULL_STOP => 'decimalEnclosedFullstop', + NumberFormat::DECIMAL_ENCLOSED_PAREN => 'decimalEnclosedParen', + NumberFormat::DECIMAL_ENCLOSED_CIRCLE_CHINESE => 'decimalEnclosedCircleChinese', + NumberFormat::IDEOGRAPHENCLOSEDCIRCLE => 'ideographEnclosedCircle', + NumberFormat::IDEOGRAPH_TRADITIONAL => 'ideographTraditional', + NumberFormat::IDEOGRAPH_ZODIAC => 'ideographZodiac', + NumberFormat::IDEOGRAPH_ZODIAC_TRADITIONAL => 'ideographZodiacTraditional', + NumberFormat::TAIWANESE_COUNTING => 'taiwaneseCounting', + NumberFormat::IDEOGRAPH_LEGAL_TRADITIONAL => 'ideographLegalTraditional', + NumberFormat::TAIWANESE_COUNTING_THOUSAND => 'taiwaneseCountingThousand', + NumberFormat::TAIWANESE_DIGITAL => 'taiwaneseDigital', + NumberFormat::CHINESE_COUNTING => 'chineseCounting', + NumberFormat::CHINESE_LEGAL_SIMPLIFIED => 'chineseLegalSimplified', + NumberFormat::CHINESE_COUNTING_THOUSAND => 'chineseCountingThousand', + NumberFormat::KOREAN_DIGITAL => 'koreanDigital', + NumberFormat::KOREAN_COUNTING => 'koreanCounting', + NumberFormat::KOREAN_LEGAL => 'koreanLegal', + NumberFormat::KOREAN_DIGITAL2 => 'koreanDigital2', */ + NumberFormat::VIETNAMESE_COUNTING => '56', + NumberFormat::RUSSIAN_LOWER => '58', + NumberFormat::RUSSIAN_UPPER => '59', + NumberFormat::NONE => '255', + /* NumberFormat::NUMBER_IN_DASH => 'numberInDash', + NumberFormat::HEBREW1 => 'hebrew1', + NumberFormat::HEBREW2 => 'hebrew2', + NumberFormat::ARABIC_ALPHA => 'arabicAlpha', */ + NumberFormat::ARABIC_ABJAD => '48', + NumberFormat::HINDI_VOWELS => '49', + NumberFormat::HINDI_CONSONANTS => '50', + NumberFormat::HINDI_NUMBERS => '51', + NumberFormat::HINDI_COUNTING => '52', + NumberFormat::THAI_LETTERS => '53', + NumberFormat::THAI_NUMBERS => '54', + NumberFormat::THAI_COUNTING => '55', + ]; + + $listAlignment = [ + 'left' => '0', + 'center' => '1', + 'right' => '2', + ]; + + $content .= '{'; + $content .= '\*\listtable' . PHP_EOL; + + foreach ($this->listTable as $list) { + $content .= '{'; + $content .= '\list\listtemplateid' . $list['numId']; + if (isset($listType[$list['type']])) { + $content .= $listType[$list['type']]; + } + $content .= PHP_EOL; + foreach ($list['listItems'] as $listItem) { + $content .= '{'; + $content .= '\listlevel'; + if (isset($numberType[$listItem['format']])) { + $content .= '\levelnfc' . $numberType[$listItem['format']]; + $content .= '\levelnfcn' . $numberType[$listItem['format']]; + } + if (isset($listAlignment[$listItem['alignment']])) { + $content .= '\leveljc' . $listAlignment[$listItem['alignment']]; + $content .= '\leveljcn' . $listAlignment[$listItem['alignment']]; + } + $content .= '\levelstartat' . $listItem['start']; + if (isset($listItem['restart'])) { + $content .= '\levelnorestart' . $listItem['restart']; + } + + // Level Text and Numbers + $positions = []; + $level = ''; + $strLength = ''; + $listText = (string) $listItem['text']; + if (strpos($listText, '%') !== false) { + $level = $this->lowerDigitsByOne(str_replace('%', '\\\'0', $listText)); + $levelNumbers = preg_replace('/\d/', 'X', str_replace('%', '', $listText)); + $offset = 0; + while (($pos = strpos($levelNumbers, 'X', $offset)) !== false) { + $positions[] = $pos; + $offset = $pos + 1; + } + $strLength = (string) sprintf('%02d', strlen($levelNumbers)); + } else { + $level = $this->escaper->escape($listText); + $strLength = (string) sprintf('%02d', mb_strlen($listText)); + } + + $content .= '{'; + $content .= '\leveltext \\\'' . $strLength . $level; + $content .= ';}'; + $content .= '{'; + $content .= '\levelnumbers '; + foreach ($positions as $position) { + ++$position; + $content .= '\\\'0' . $position; + } + $content .= ';}'; + + // Font settings for Level Numbers + if (isset($listItem['font']) && !empty($listItem['font'])) { + $fontKey = array_search($listItem['font'], $this->fontTable); + if ($fontKey !== false) { + $content .= '\f' . $fontKey; + } + } + + // Tabs, Hanging, and First Line + $content .= '\levelfollow' . '0'; + $content .= '\jclisttab'; + $content .= '\tx' . $listItem['tabPos']; + $content .= '\fi' . $listItem['hanging'] * -1; + $content .= '\li' . $listItem['left']; + $content .= '\lin' . $listItem['left']; + $content .= '}'; + $content .= PHP_EOL; + } + $content .= '\listid' . $list['numId'] . '}'; + $content .= PHP_EOL; + } + $content .= '}'; + $content .= PHP_EOL . PHP_EOL; + + $content .= '{'; + $content .= '\*\listoverridetable' . PHP_EOL; + foreach ($this->listTable as $list) { + $content .= '{'; + $content .= '\listoverride\listid' . $list['numId']; + $content .= '\listoverridecount0\ls' . $list['numId']; + $content .= '}'; + $content .= PHP_EOL; + } + $content .= '}'; + $content .= PHP_EOL . PHP_EOL; + + return $content; + } + /** * Write. * @@ -182,17 +386,18 @@ private function writeGenerator() } /** - * Register all fonts and colors in both named and inline styles to appropriate header table. + * Register all fonts, colors, and lists in both named and inline styles to appropriate header table. */ - private function registerFont(): void + private function registerHeader(): void { $phpWord = $this->getParentWriter()->getPhpWord(); $this->fontTable[] = Settings::getDefaultFontName(); + $this->colorTable[] = Settings::getDefaultFontColor(); // Search named styles $styles = Style::getStyles(); foreach ($styles as $style) { - $this->registerFontItems($style); + $this->registerHeaderItems($style); } // Search inline styles @@ -201,9 +406,22 @@ private function registerFont(): void $elements = $section->getElements(); $this->registerBorderColor($section->getStyle()); foreach ($elements as $element) { - if (method_exists($element, 'getFontStyle')) { - $style = $element->getFontStyle(); - $this->registerFontItems($style); + $this->anythingToRegister($element); + } + } + } + + /** @param mixed $element */ + private function anythingToRegister($element): void + { + if (is_object($element)) { + if (method_exists($element, 'getFontStyle')) { + $style = $element->getFontStyle(); + $this->registerHeaderItems($style); + } + if (method_exists($element, 'getElements')) { + foreach ($element->getElements() as $subElement) { + $this->anythingToRegister($subElement); } } } @@ -225,11 +443,11 @@ private function registerBorderColor($style): void } /** - * Register fonts and colors. + * Register both fonts and colors. * * @param Style\AbstractStyle $style */ - private function registerFontItems($style): void + private function registerHeaderItems($style): void { $defaultFont = Settings::getDefaultFontName(); $defaultColor = Settings::DEFAULT_FONT_COLOR; @@ -238,6 +456,7 @@ private function registerFontItems($style): void $this->registerTableItem($this->fontTable, $style->getName(), $defaultFont); $this->registerTableItem($this->colorTable, $style->getColor(), $defaultColor); $this->registerTableItem($this->colorTable, $style->getFgColor(), $defaultColor); + $this->registerTableItem($this->colorTable, $style->getBgColor(), $defaultColor); return; } @@ -247,6 +466,12 @@ private function registerFontItems($style): void $this->registerTableItem($this->colorTable, $style->getBorderLeftColor(), $defaultColor); $this->registerTableItem($this->colorTable, $style->getBorderBottomColor(), $defaultColor); } + if ($style instanceof Numbering) { + $this->registerList($this->listTable, $style, $defaultFont); + } + if ($style instanceof Paragraph) { + $this->registerBorderColor($style); + } } /** @@ -262,4 +487,60 @@ private function registerTableItem(&$table, $value, $default = null): void $table[] = $value; } } + + /** + * Register lists and fonts within lists. + * + * @param array &$table + * @param Numbering $style + * @param string $defaultFont + */ + private function registerList(&$table, $style, $defaultFont = null): void + { + $listItems = []; + + $levels = $style->getLevels(); + foreach ($levels as $level) { + $this->registerTableItem($this->fontTable, $level->getFont(), $defaultFont); + + $listItem = [ + 'level' => $level->getLevel(), + 'start' => $level->getStart(), + 'restart' => $level->getRestart(), + 'format' => $level->getFormat(), + 'pStyle' => $level->getPStyle(), + 'suffix' => $level->getSuffix(), + 'text' => $level->getText(), + 'alignment' => $level->getAlignment(), + 'left' => $level->getLeft(), + 'hanging' => $level->getHanging(), + 'tabPos' => $level->getTabPos(), + 'font' => $level->getFont(), + 'hint' => $level->getHint(), + ]; + array_push($listItems, $listItem); + } + + $list = [ + 'numId' => $style->getNumId(), + 'type' => $style->getType(), + 'listItems' => $listItems, + ]; + $table[] = $list; + } + + /** + * NumberingLevel->getText() returns levels a step higher than expected in RTF \leveltext, (1-9) instead of (0-8). + * Thus all the digits need to be reduced by 1. + * + * @param string $string + */ + private function lowerDigitsByOne($string): string + { + return preg_replace_callback('/\d/', function ($matches) { + $digit = (int) $matches[0]; + + return ($digit > 0) ? (string) ($digit - 1) : (string) $digit; + }, $string); + } } diff --git a/src/PhpWord/Writer/RTF/Style/AbstractStyle.php b/src/PhpWord/Writer/RTF/Style/AbstractStyle.php index 00e148dfe93..bd095df14ce 100644 --- a/src/PhpWord/Writer/RTF/Style/AbstractStyle.php +++ b/src/PhpWord/Writer/RTF/Style/AbstractStyle.php @@ -105,4 +105,29 @@ protected function getValueIf($condition, $value) { return $condition == true ? $value : ''; } + + /** + * Write child style. + * + * @param string $name + * @param mixed $style + * + * @return string + */ + protected function writeChildStyle($name, $style = null) + { + $stylePath = 'PhpOffice\\PhpWord\\Style\\' . ucfirst($name); + if (isset($style) && $style instanceof $stylePath) { + $writerPath = 'PhpOffice\\PhpWord\\Writer\\RTF\\Style\\' . ucfirst($name); + $writer = new $writerPath($style); + if (method_exists($writer, 'setParentWriter')) { + $writer->setParentWriter($this->parentWriter); + } + if (method_exists($writer, 'write')) { + return $writer->write(); + } + } + + return ''; + } } diff --git a/src/PhpWord/Writer/RTF/Style/Border.php b/src/PhpWord/Writer/RTF/Style/Border.php index c8dc943579f..6514010c177 100644 --- a/src/PhpWord/Writer/RTF/Style/Border.php +++ b/src/PhpWord/Writer/RTF/Style/Border.php @@ -18,6 +18,9 @@ namespace PhpOffice\PhpWord\Writer\RTF\Style; +use PhpOffice\PhpWord\SimpleType\Border as BorderType; +use PhpOffice\PhpWord\Style\Border as BorderStyle; + /** * Border style writer. * @@ -26,18 +29,11 @@ class Border extends AbstractStyle { /** - * Sizes. - * - * @var array - */ - private $sizes = []; - - /** - * Colors. + * Type. Can be section, paragraph, font, row, or cell. * - * @var array + * @var string */ - private $colors = []; + private $type = 'paragraph'; /** * Write style. @@ -45,24 +41,32 @@ class Border extends AbstractStyle * @return string */ public function write() + { + $style = $this->getStyle(); + + return ($style instanceof BorderStyle) ? $this->writeStyle($style) : ''; + } + + private function writeStyle(BorderStyle $style): string { $content = ''; + if ($this->type == 'section') { + // Page border measure + // 8 = from text, infront off; 32 = from edge, infront on; 40 = from edge, infront off + $content .= '\pgbrdropt32'; + } + $sides = ['top', 'left', 'right', 'bottom']; - $sizeCount = count($this->sizes); + $sizeCount = ($this->type === 'font') ? 1 : 4; - // Page border measure - // 8 = from text, infront off; 32 = from edge, infront on; 40 = from edge, infront off - $content .= '\pgbrdropt32'; + $sizes = $style->getBorderSize(); + $colors = $style->getBorderColor(); + $styles = $style->getBorderStyle(); + $spaces = $style->getBorderSpace(); for ($i = 0; $i < $sizeCount; ++$i) { - if ($this->sizes[$i] !== null) { - $color = null; - if (isset($this->colors[$i])) { - $color = $this->colors[$i]; - } - $content .= $this->writeSide($sides[$i], $this->sizes[$i], $color); - } + $content .= $this->writeSide($sides[$i], $sizes[$i], $colors[$i], $styles[$i], $spaces[$i]); } return $content; @@ -72,53 +76,111 @@ public function write() * Write side. * * @param string $side - * @param int $width + * @param float|int $width * @param string $color + * @param string $style + * @param float|int $space * * @return string */ - private function writeSide($side, $width, $color = '') + private function writeSide($side, $width, $color, $style, $space) { - /** @var \PhpOffice\PhpWord\Writer\RTF $rtfWriter */ - $rtfWriter = $this->getParentWriter(); + if ($width == null && $color === null && $style === null) { + if ($this->type == 'font' || $this->type == 'row' || $this->type == 'cell') { + return ''; + } elseif ($space === null) { + return ''; + } + } + + $types = [ + 'section' => '\pgbrdr', + 'paragraph' => '\brdr', + 'font' => '\chbrdr', + 'row' => '\trbrdr', + 'cell' => '\clbrdr', + ]; + + $styles = [ + BorderType::SINGLE => '\brdrs', + BorderType::DASH_DOT_STROKED => '\brdrdashdotstr', + BorderType::DASHED => '\brdrdash', + BorderType::DASH_SMALL_GAP => '\brdrdashsm', + BorderType::DOT_DASH => '\brdrdashd', + BorderType::DOT_DOT_DASH => '\brdrdashdd', + BorderType::DOTTED => '\brdrdot', + BorderType::DOUBLE => '\brdrdb', + BorderType::DOUBLE_WAVE => '\brdrwavydb', + BorderType::INSET => '\brdrinset', + BorderType::NIL => '\brdrnil', + BorderType::NONE => '\brdrnone', + BorderType::OUTSET => '\brdroutset', + BorderType::THICK => '\brdrth', + BorderType::THICK_THIN_LARGE_GAP => '\brdrtnthlg', + BorderType::THICK_THIN_MEDIUM_GAP => '\brdrtnthmg', + BorderType::THICK_THIN_SMALL_GAP => '\brdrtnthsg', + BorderType::THIN_THICK_LARGE_GAP => '\brdrthtnlg', + BorderType::THIN_THICK_MEDIUM_GAP => '\brdrthtnmg', + BorderType::THIN_THICK_SMALL_GAP => '\brdrthtnsg', + BorderType::THIN_THICK_THIN_LARGE_GAP => '\brdrtnthtnlg', + BorderType::THIN_THICK_THIN_MEDIUM_GAP => '\brdrtnthtnmg', + BorderType::THIN_THICK_THIN_SMALL_GAP => '\brdrtnthtnsg', + BorderType::THREE_D_EMBOSS => '\brdremboss', + BorderType::THREE_D_ENGRAVE => '\brdrengrave', + BorderType::TRIPLE => '\brdrtriple', + BorderType::WAVE => '\brdrwavy', + ]; + + /** @var \PhpOffice\PhpWord\Writer\RTF $parentWriter */ + $parentWriter = $this->getParentWriter(); $colorIndex = 0; - if ($rtfWriter !== null) { - $colorTable = $rtfWriter->getColorTable(); - $index = array_search($color, $colorTable); + if ($parentWriter !== null) { + $index = array_search($color, $parentWriter->getColorTable()); if ($index !== false) { $colorIndex = $index + 1; } } $content = ''; + if (isset($types[$this->type])) { + if ($this->type == 'font') { + $content .= $types[$this->type]; // character borders cannot vary by side + } else { + $content .= $types[$this->type] . substr($side, 0, 1); + } - $content .= '\pgbrdr' . substr($side, 0, 1); - $content .= '\brdrs'; // Single-thickness border; @todo Get other type of border - $content .= '\brdrw' . round($width); // Width - $content .= '\brdrcf' . $colorIndex; // Color - $content .= '\brsp480'; // Space in twips between borders and the paragraph (24pt, following OOXML) - $content .= ' '; + if (isset($style, $styles[$style])) { + $content .= $styles[$style]; + } else { + $content .= '\brdrs'; // default style + } + $content .= $this->getValueIf($width !== null, '\brdrw' . round($width ?? 0)); // Width + $content .= $this->getValueIf($color !== null, '\brdrcf' . $colorIndex); // Color - return $content; - } + // Space + if ($this->type == 'section') { + $space = $space !== null ? $space : '480'; // section default is 480 + } elseif ($this->type == 'paragraph') { + if ($side == 'top' || $side == 'bottom') { + $space = $space !== null ? $space : '20'; // paragraph top|bottom default is 20 + } elseif ($side == 'left' || $side == 'right') { + $space = $space !== null ? $space : '80'; // paragraph left|rigth default is 80 + } + } + $content .= $this->getValueIf($space !== null, '\brsp' . round($space ?? 0)); + $content .= ' '; + } - /** - * Set sizes. - * - * @param int[] $value - */ - public function setSizes($value): void - { - $this->sizes = $value; + return $content; } /** - * Set colors. + * Set type. * - * @param string[] $value + * @param string $value */ - public function setColors($value): void + public function setType($value = 'paragraph'): void { - $this->colors = $value; + $this->type = $value; } } diff --git a/src/PhpWord/Writer/RTF/Style/Font.php b/src/PhpWord/Writer/RTF/Style/Font.php index f343c0502f9..1fba3b2dea5 100644 --- a/src/PhpWord/Writer/RTF/Style/Font.php +++ b/src/PhpWord/Writer/RTF/Style/Font.php @@ -37,6 +37,16 @@ class Font extends AbstractStyle */ private $colorIndex = 0; + /** + * @var int Font foreground color index + */ + private $fgColorIndex = 0; + + /** + * @var int Font background color index + */ + private $bgColorIndex = 0; + /** * Write style. * @@ -49,22 +59,100 @@ public function write() return ''; } + // Underline Keywords + $underlines = [ + FontStyle::UNDERLINE_DASH => '\uldash', + FontStyle::UNDERLINE_DASHHEAVY => '\ulthdash', + FontStyle::UNDERLINE_DASHLONG => '\ulldash', + FontStyle::UNDERLINE_DASHLONGHEAVY => '\ulthldash', + FontStyle::UNDERLINE_DOUBLE => '\uldb', + FontStyle::UNDERLINE_DOTDASH => '\uldashd', + FontStyle::UNDERLINE_DOTDASHHEAVY => '\ulthdashd', + FontStyle::UNDERLINE_DOTDOTDASH => '\uldashdd', + FontStyle::UNDERLINE_DOTDOTDASHHEAVY => '\ulthdashdd', + FontStyle::UNDERLINE_DOTTED => '\uld', + FontStyle::UNDERLINE_DOTTEDHEAVY => '\ulthd', + FontStyle::UNDERLINE_HEAVY => '\ulth', + FontStyle::UNDERLINE_SINGLE => '\ul', + FontStyle::UNDERLINE_WAVY => '\ulwave', + FontStyle::UNDERLINE_WAVYDOUBLE => '\ululdbwave', + FontStyle::UNDERLINE_WAVYHEAVY => '\ulhwave', + FontStyle::UNDERLINE_WORDS => '\ulw', + ]; + $content = ''; - $content .= $this->getValueIf($style->isRTL(), '\rtlch'); - $content .= '\cf' . $this->colorIndex; - $content .= '\f' . $this->nameIndex; + // Font name/family + $content .= $this->getValueIf($style->getName() !== null, '\f' . $this->nameIndex); + + // Language + if ($style->getLang() !== null) { + if ($style->isNoProof()) { + $content .= $this->getValueIf($style->getLang()->getLangId() !== 0, '\langnp' . $style->getLang()->getLangId()); + } else { + $content .= $this->getValueIf($style->getLang()->getLangId() !== 0, '\lang' . $style->getLang()->getLangId()); + } + } - $size = $style->getSize(); - $content .= $this->getValueIf(is_numeric($size), '\fs' . round($size * 2)); + // Color + $content .= $this->getValueIf($style->getColor() !== null, '\cf' . $this->colorIndex); + // Size + $content .= $this->getValueIf($style->getSize() !== null, '\fs' . round($style->getSize() * 2)); + + // Bold, italic $content .= $this->getValueIf($style->isBold(), '\b'); + $content .= $this->getValueIf($style->isBold() === false, '\b0'); $content .= $this->getValueIf($style->isItalic(), '\i'); - $content .= $this->getValueIf($style->getUnderline() != FontStyle::UNDERLINE_NONE, '\ul'); + $content .= $this->getValueIf($style->isItalic() === false, '\i0'); + + // Strikethrough, double strikethrough $content .= $this->getValueIf($style->isStrikethrough(), '\strike'); + $content .= $this->getValueIf($style->isDoubleStrikethrough(), '\striked1'); + $content .= $this->getValueIf($style->isStrikethrough() === false && $style->isDoubleStrikethrough() === false, '\strike0'); + + // Small caps, all caps + $content .= $this->getValueIf($style->isSmallCaps(), '\scaps'); + $content .= $this->getValueIf($style->isAllCaps(), '\caps'); + + // Hidden text + $content .= $this->getValueIf($style->isHidden(), '\v'); + $content .= $this->getValueIf($style->isHidden() === false, '\v0'); + + // Underline + if (isset($underlines[$style->getUnderline()])) { + $content .= $underlines[$style->getUnderline()]; + } + + // Foreground color + $content .= $this->getValueIf($style->getFgColor() !== null, '\highlight' . $this->fgColorIndex); + + // Superscript/subscript $content .= $this->getValueIf($style->isSuperScript(), '\super'); $content .= $this->getValueIf($style->isSubScript(), '\sub'); - return $content . ' '; + // Spacing + $content .= $this->getValueIf($style->getScale() !== null, '\charscalex' . round($style->getScale() ?? 0)); + $content .= $this->getValueIf($style->getSpacing() !== null, '\expnd' . round($style->getSpacing() * 0.2)); + $content .= $this->getValueIf($style->getSpacing() !== null, '\expndtw' . round($style->getSpacing() ?? 0)); + $content .= $this->getValueIf($style->getKerning() !== null, '\kerning' . round($style->getKerning() * 2)); + + // noProof + // This is also specified above as \\langnp{$langId} + // RTF spec suggests using this for backwards compatibility. + // So perhaps earlier can be omitted, but it seems harmless regardless. + $content .= $this->getValueIf($style->isNoProof(), '\noproof\lang1024'); + + // Background-Color + $content .= $this->getValueIf($style->getBgColor() !== null, '\chshdng0\chcbpat' . $this->bgColorIndex . '\cb' . $this->bgColorIndex); + + // RTL + $content .= $this->getValueIf($style->isRTL(), '\rtlch'); + $content .= $this->getValueIf($style->isRTL() === false, '\ltrch'); + + // Position + $content .= $this->getValueIf($style->getPosition() !== null, '\up' . $style->getPosition()); + + return ($content === '') ? '' : ("$content "); } /** @@ -86,4 +174,24 @@ public function setColorIndex($value = 0): void { $this->colorIndex = $value; } + + /** + * Set font foreground color index. + * + * @param int $value + */ + public function setFgColorIndex($value = 0): void + { + $this->fgColorIndex = $value; + } + + /** + * Set font background color index. + * + * @param int $value + */ + public function setBgColorIndex($value = 0): void + { + $this->bgColorIndex = $value; + } } diff --git a/src/PhpWord/Writer/RTF/Style/Indentation.php b/src/PhpWord/Writer/RTF/Style/Indentation.php index 589125a26a4..74e84db1127 100644 --- a/src/PhpWord/Writer/RTF/Style/Indentation.php +++ b/src/PhpWord/Writer/RTF/Style/Indentation.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\RTF\Style; +use PhpOffice\PhpWord\Style\Indentation as IndentationStyle; + /** * RTF indentation style writer. * @@ -33,13 +35,19 @@ class Indentation extends AbstractStyle public function write() { $style = $this->getStyle(); - if (!$style instanceof \PhpOffice\PhpWord\Style\Indentation) { - return ''; - } - $content = '\fi' . round($style->getFirstLine()); - $content .= '\li' . round($style->getLeft()); - $content .= '\ri' . round($style->getRight()); + return ($style instanceof IndentationStyle) ? $this->writeStyle($style) : ''; + } + + private function writeStyle(IndentationStyle $style): string + { + $content = ''; + + $content .= $this->getValueIf($style->getFirstLine() != 0, '\fi' . round($style->getFirstLine())); + $content .= $this->getValueIf($style->getFirstLineChars() != 0, '\cufi' . round($style->getFirstLineChars())); + $content .= $this->getValueIf($style->getHanging() != 0, '\fi-' . round($style->getHanging())); + $content .= $this->getValueIf($style->getLeft() != 0, '\li' . round($style->getLeft())); + $content .= $this->getValueIf($style->getRight() != 0, '\ri' . round($style->getRight())); return $content . ' '; } diff --git a/src/PhpWord/Writer/RTF/Style/Paragraph.php b/src/PhpWord/Writer/RTF/Style/Paragraph.php index 040c60b5aa3..82e543d1fa9 100644 --- a/src/PhpWord/Writer/RTF/Style/Paragraph.php +++ b/src/PhpWord/Writer/RTF/Style/Paragraph.php @@ -36,10 +36,6 @@ class Paragraph extends AbstractStyle */ private $nestedLevel = 0; - private const LEFT = Jc::LEFT; - private const RIGHT = Jc::RIGHT; - private const JUSTIFY = Jc::JUSTIFY; - /** * Write style. * @@ -57,69 +53,80 @@ public function write() Jc::END => '\qr', Jc::CENTER => '\qc', Jc::BOTH => '\qj', - self::LEFT => '\ql', - self::RIGHT => '\qr', - self::JUSTIFY => '\qj', + Jc::LEFT => '\ql', + Jc::RIGHT => '\qr', + Jc::JUSTIFY => '\qj', + Jc::DISTRIBUTE => '\qd', + Jc::THAI_DISTRIBUTE => '\qt', + Jc::HIGH_KASHIDA => '\qk20', + Jc::MEDIUM_KASHIDA => '\qk10', + Jc::LOW_KASHIDA => '\qk0', ]; $bidiAlignments = [ Jc::START => '\qr', Jc::END => '\ql', Jc::CENTER => '\qc', Jc::BOTH => '\qj', - self::LEFT => '\ql', - self::RIGHT => '\qr', - self::JUSTIFY => '\qj', + Jc::LEFT => '\ql', + Jc::RIGHT => '\qr', + Jc::JUSTIFY => '\qj', + Jc::DISTRIBUTE => '\qd', + Jc::THAI_DISTRIBUTE => '\qt', + Jc::HIGH_KASHIDA => '\qk20', + Jc::MEDIUM_KASHIDA => '\qk10', + Jc::LOW_KASHIDA => '\qk0', ]; - $spaceAfter = $style->getSpaceAfter(); - $spaceBefore = $style->getSpaceBefore(); - $content = ''; if ($this->nestedLevel == 0) { - $content .= '\pard\nowidctlpar '; + $content .= '\pard'; } + + // Alignment $alignment = $style->getAlignment(); $bidi = $style->isBidi(); if ($alignment === '' && $bidi !== null) { $alignment = Jc::START; } + + // Right to left if (isset($alignments[$alignment])) { $content .= $bidi ? $bidiAlignments[$alignment] : $alignments[$alignment]; } - $content .= $this->writeIndentation($style->getIndentation()); - $content .= $this->getValueIf($spaceBefore !== null, '\sb' . round($spaceBefore ?? 0)); - $content .= $this->getValueIf($spaceAfter !== null, '\sa' . round($spaceAfter ?? 0)); - $lineHeight = $style->getLineHeight(); - if ($lineHeight) { - $lineHeightAdjusted = (int) ($lineHeight * 240); - $content .= "\\sl$lineHeightAdjusted\\slmult1"; - } - if ($style->hasPageBreakBefore()) { - $content .= '\\page'; - } + $content .= $this->getValueIf($style->isBidi(), '\rtlpar'); - $styles = $style->getStyleValues(); - $content .= $this->writeTabs($styles['tabs']); + // Indentation + $content .= $this->writeChildStyle('Indentation', $style->getIndentation()); + $content .= $this->writeChildStyle('Spacing', $style->getSpace()); + // Future: Add Shading - return $content; - } + // Contextual Spacing + $content .= $this->getValueIf($style->hasContextualSpacing(), '\contextualspace'); + // Future: Add Text Alignment - /** - * Writes an \PhpOffice\PhpWord\Style\Indentation. - * - * @param null|\PhpOffice\PhpWord\Style\Indentation $indent - * - * @return string - */ - private function writeIndentation($indent = null) - { - if (isset($indent) && $indent instanceof \PhpOffice\PhpWord\Style\Indentation) { - $writer = new Indentation($indent); + // Pagination + $content .= $this->getValueIf($style->hasWidowControl(), '\widctlpar'); + $content .= $this->getValueIf(!$style->hasWidowControl(), '\nowidctlpar'); + $content .= $this->getValueIf($style->isKeepNext(), '\keepn'); + $content .= $this->getValueIf($style->isKeepLines(), '\keep'); + $content .= $this->getValueIf($style->hasPageBreakBefore(), '\pagebb'); + + // Hyphenation + $content .= $this->getValueIf($style->hasSuppressAutoHyphens(), '\hyphpar0'); + + // Tabs + $styles = $style->getStyleValues(); + $content .= $this->writeTabs($styles['tabs']); - return $writer->write(); + // Borders + if ($style->hasBorder()) { + $styleWriter = new Border($style); + $styleWriter->setParentWriter($this->getParentWriter()); + $styleWriter->setType('paragraph'); + $content .= $styleWriter->write(); } - return ''; + return $content . ' '; } /** @@ -135,6 +142,7 @@ private function writeTabs($tabs = null) if (!empty($tabs)) { foreach ($tabs as $tab) { $styleWriter = new Tab($tab); + $styleWriter->setParentWriter($this->getParentWriter()); $content .= $styleWriter->write(); } } diff --git a/src/PhpWord/Writer/RTF/Style/Section.php b/src/PhpWord/Writer/RTF/Style/Section.php index 598015ed4d3..fd147366e3c 100644 --- a/src/PhpWord/Writer/RTF/Style/Section.php +++ b/src/PhpWord/Writer/RTF/Style/Section.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Writer\RTF\Style; +use PhpOffice\PhpWord\SimpleType\VerticalJc; use PhpOffice\PhpWord\Style\Section as SectionStyle; /** @@ -44,25 +45,53 @@ public function write() $content .= '\sectd '; // Size & margin - $content .= $this->getValueIf($style->getPageSizeW() !== null, '\pgwsxn' . round($style->getPageSizeW())); - $content .= $this->getValueIf($style->getPageSizeH() !== null, '\pghsxn' . round($style->getPageSizeH())); + if ($this->getParentWriter() !== null && $this->getParentWriter()->getPhpWord()->getSettings()->hasBookFoldPrinting()) { + $style->setOrientation(SectionStyle::ORIENTATION_LANDSCAPE); + $content .= $this->getValueIf($style->getPageSizeW() !== null, '\pgwsxn' . round($style->getPageSizeW()) / 2); + $content .= $this->getValueIf($style->getPageSizeH() !== null, '\pghsxn' . round($style->getPageSizeH())); + } else { + $content .= $this->getValueIf($style->getPageSizeW() !== null, '\pgwsxn' . round($style->getPageSizeW())); + $content .= $this->getValueIf($style->getPageSizeH() !== null, '\pghsxn' . round($style->getPageSizeH())); + } $content .= ' '; + $content .= $this->getValueIf($style->getMarginTop() !== null, '\margtsxn' . round($style->getMarginTop())); $content .= $this->getValueIf($style->getMarginRight() !== null, '\margrsxn' . round($style->getMarginRight())); $content .= $this->getValueIf($style->getMarginBottom() !== null, '\margbsxn' . round($style->getMarginBottom())); $content .= $this->getValueIf($style->getMarginLeft() !== null, '\marglsxn' . round($style->getMarginLeft())); $content .= $this->getValueIf($style->getHeaderHeight() !== null, '\headery' . round($style->getHeaderHeight())); $content .= $this->getValueIf($style->getFooterHeight() !== null, '\footery' . round($style->getFooterHeight())); - $content .= $this->getValueIf($style->getGutter() !== null, '\guttersxn' . round($style->getGutter())); + $content .= $this->getValueIf($style->getGutter() !== null && $style->getGutter() !== SectionStyle::DEFAULT_GUTTER, '\guttersxn' . round($style->getGutter())); $content .= $this->getValueIf($style->getPageNumberingStart() !== null, '\pgnstarts' . $style->getPageNumberingStart() . '\pgnrestart'); + + $content .= $this->getValueIf($style->getColsNum() !== null && $style->getColsNum() !== SectionStyle::DEFAULT_COLUMN_COUNT, '\cols' . $style->getColsNum()); + $content .= $this->getValueIf($style->getColsSpace() !== null && $style->getColsNum() !== SectionStyle::DEFAULT_COLUMN_COUNT, '\colsx' . round($style->getColsSpace())); + + // Break Type + $breakTypes = [ + 'nextPage' => '\sbkpage', + 'nextColumn' => '\sbkcol', + 'continuous' => '\sbknone', + 'evenPage' => '\sbkeven', + 'oddPage' => '\sbkodd', + ]; + $content .= $breakTypes[(string) $style->getBreakType()] ?? ''; + + // Vertical Align + $verticalAlign = [ + VerticalJc::TOP => '\vertalt', + VerticalJc::CENTER => '\vertalc', + VerticalJc::BOTH => '\vertalj', + VerticalJc::BOTTOM => '\vertalb', + ]; + $content .= $verticalAlign[(string) $style->getVAlign()] ?? ''; $content .= ' '; // Borders if ($style->hasBorder()) { $styleWriter = new Border($style); $styleWriter->setParentWriter($this->getParentWriter()); - $styleWriter->setSizes($style->getBorderSize()); - $styleWriter->setColors($style->getBorderColor()); + $styleWriter->setType('section'); $content .= $styleWriter->write(); } diff --git a/src/PhpWord/Writer/RTF/Style/Spacing.php b/src/PhpWord/Writer/RTF/Style/Spacing.php new file mode 100644 index 00000000000..6600976e4bd --- /dev/null +++ b/src/PhpWord/Writer/RTF/Style/Spacing.php @@ -0,0 +1,75 @@ +getStyle(); + + return ($style instanceof SpacingStyle) ? $this->writeStyle($style) : ''; + } + + private function writeStyle(SpacingStyle $style): string + { + $spacingRules = [ + LineSpacingRule::AUTO => '\slmult1', + LineSpacingRule::EXACT => '\slmult0', + LineSpacingRule::AT_LEAST => '\slmult0', + ]; + + $content = ''; + + // Space Before and After + $content .= $this->getValueIf($style->getBefore() !== null, '\sb' . round($style->getBefore() ?? 0)); + $content .= $this->getValueIf($style->getAfter() !== null, '\sa' . round($style->getAfter() ?? 0)); + + // Space Between Lines + $line = $style->getLine(); + if (null !== $line && $style->getLineRule() === LineSpacingRule::AUTO) { + $line += \PhpOffice\PhpWord\Style\Paragraph::LINE_HEIGHT; + } + // Exact is specified by using negative numbers + if ($style->getLineRule() === LineSpacingRule::EXACT && $line > 0) { + $line = $line * -1; + } + $content .= $this->getValueIf($line !== null, '\sl' . round($line ?? 0)); + + // Spacing Multiple + if ($line !== null) { + $content .= $this->getValueIf(isset($spacingRules[$style->getLineRule()]), $spacingRules[$style->getLineRule()]); + } + + return $content; + } +} diff --git a/src/PhpWord/Writer/RTF/Style/Tab.php b/src/PhpWord/Writer/RTF/Style/Tab.php index 95e1f10a5c4..bc914f223cb 100644 --- a/src/PhpWord/Writer/RTF/Style/Tab.php +++ b/src/PhpWord/Writer/RTF/Style/Tab.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\RTF\Style; +use PhpOffice\PhpWord\Style\Tab as TabStyle; + /** * Line numbering style writer. * @@ -31,19 +33,31 @@ class Tab extends AbstractStyle public function write() { $style = $this->getStyle(); - if (!$style instanceof \PhpOffice\PhpWord\Style\Tab) { + if (!$style instanceof TabStyle) { return; } $tabs = [ - \PhpOffice\PhpWord\Style\Tab::TAB_STOP_RIGHT => '\tqr', - \PhpOffice\PhpWord\Style\Tab::TAB_STOP_CENTER => '\tqc', - \PhpOffice\PhpWord\Style\Tab::TAB_STOP_DECIMAL => '\tqdec', + TabStyle::TAB_STOP_RIGHT => '\tqr', + TabStyle::TAB_STOP_CENTER => '\tqc', + TabStyle::TAB_STOP_DECIMAL => '\tqdec', + TabStyle::TAB_LEADER_DOT => '\tldot', + TabStyle::TAB_LEADER_HYPHEN => '\tlhyph', + TabStyle::TAB_LEADER_UNDERSCORE => '\tlul', + TabStyle::TAB_LEADER_HEAVY => '\tlth', + TabStyle::TAB_LEADER_MIDDLEDOT => '\tlmdot', ]; $content = ''; if (isset($tabs[$style->getType()])) { $content .= $tabs[$style->getType()]; } - $content .= '\tx' . round($style->getPosition()); + if (isset($tabs[$style->getLeader()]) && $style->getType() !== TabStyle::TAB_STOP_BAR) { + $content .= $tabs[$style->getLeader()]; + } + if ($style->getType() == TabStyle::TAB_STOP_BAR) { + $content .= '\tb' . round($style->getPosition()); + } else { + $content .= '\tx' . round($style->getPosition()); + } return $content; } diff --git a/src/PhpWord/Writer/Word2007.php b/src/PhpWord/Writer/Word2007.php index 9702c210bd4..ad38458d957 100644 --- a/src/PhpWord/Writer/Word2007.php +++ b/src/PhpWord/Writer/Word2007.php @@ -93,6 +93,7 @@ public function __construct(?PhpWord $phpWord = null) */ public function save(string $filename): void { + PhpWord::noPhar($filename); $filename = $this->getTempFile($filename); $zip = $this->getZipArchive($filename); $phpWord = $this->getPhpWord(); diff --git a/src/PhpWord/Writer/Word2007/Element/Field.php b/src/PhpWord/Writer/Word2007/Element/Field.php index c02bbdbcc93..2e1b4ed214a 100644 --- a/src/PhpWord/Writer/Word2007/Element/Field.php +++ b/src/PhpWord/Writer/Word2007/Element/Field.php @@ -224,6 +224,7 @@ private function buildPropertiesAndOptions(ElementField $element) break; default: + $option = preg_replace('/^(.)$/', '\\\\$1', $option) ?? $option; $propertiesAndOptions .= $option . ' '; } } @@ -262,6 +263,8 @@ protected function writeRef(ElementField $element): void $xmlWriter->endElement(); // w:instrText $xmlWriter->endElement(); // w:r + // I do not believe that REF allows for a text component. + /* if ($element->getText() != null) { if ($element->getText() instanceof TextRun) { $containerWriter = new Container($xmlWriter, $element->getText(), true); @@ -281,6 +284,7 @@ protected function writeRef(ElementField $element): void $xmlWriter->endElement(); // w:r } } + */ $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:fldChar'); @@ -305,29 +309,25 @@ protected function writeRef(ElementField $element): void $this->endElementP(); // w:p } + private const OPTION_VALUES = [ + 'IncrementAndInsertText' => '\\f', + 'CreateHyperLink' => '\\h', + 'NoTrailingPeriod' => '\\n', + 'IncludeAboveOrBelow' => '\\p', + 'InsertParagraphNumberRelativeContext' => '\\r', + 'SuppressNonDelimiterNonNumericalText' => '\\t', + 'InsertParagraphNumberFullContext' => '\\w', + ]; + + private const NUMBER_SEPARATOR_SEQUENCE = ['NumberSeperatorSequence', 'NumberSeparatorSequence', '\\d', 'd']; + private function convertRefOption(string $optionKey, string $optionValue): string { - if ($optionKey === 'NumberSeperatorSequence') { - return '\\d ' . $optionValue; + if (in_array($optionKey, self::NUMBER_SEPARATOR_SEQUENCE, true)) { + return ($optionValue === '') ? '' : ('\\d ' . $optionValue); } + $optionValue = preg_replace('/^(.)$/', '\\\\$1', $optionValue) ?? $optionValue; - switch ($optionValue) { - case 'IncrementAndInsertText': - return '\\f'; - case 'CreateHyperLink': - return '\\h'; - case 'NoTrailingPeriod': - return '\\n'; - case 'IncludeAboveOrBelow': - return '\\p'; - case 'InsertParagraphNumberRelativeContext': - return '\\r'; - case 'SuppressNonDelimiterNonNumericalText': - return '\\t'; - case 'InsertParagraphNumberFullContext': - return '\\w'; - default: - return ''; - } + return self::OPTION_VALUES[$optionValue] ?? (in_array($optionValue, self::OPTION_VALUES, true) ? $optionValue : ''); } } diff --git a/src/PhpWord/Writer/Word2007/Element/Formula.php b/src/PhpWord/Writer/Word2007/Element/Formula.php index 95567062216..63e3e7d227b 100644 --- a/src/PhpWord/Writer/Word2007/Element/Formula.php +++ b/src/PhpWord/Writer/Word2007/Element/Formula.php @@ -31,10 +31,8 @@ class Formula extends AbstractElement */ public function write(): void { + /** @var FormulaElement */ $element = $this->getElement(); - if (!$element instanceof FormulaElement) { - return; - } $this->startElementP(); diff --git a/src/PhpWord/Writer/Word2007/Element/Image.php b/src/PhpWord/Writer/Word2007/Element/Image.php index 7835f32ad50..813f1e7582e 100644 --- a/src/PhpWord/Writer/Word2007/Element/Image.php +++ b/src/PhpWord/Writer/Word2007/Element/Image.php @@ -42,6 +42,12 @@ public function write(): void if (!$element instanceof ImageElement) { return; } + $ext = strtolower(pathinfo($element->getSource(), PATHINFO_EXTENSION)); + if ($ext === 'svg') { + $this->writeSvgDrawing($xmlWriter, $element); + + return; + } if ($element->isWatermark()) { $this->writeWatermark($xmlWriter, $element); @@ -127,4 +133,136 @@ private function writeWatermark(XMLWriter $xmlWriter, ImageElement $element): vo $xmlWriter->endElement(); // w:p } } + + private function writeSvgDrawing(XMLWriter $xmlWriter, ImageElement $element): void + { + $rId = $element->getRelationId() + ($element->isInSection() ? 6 : 0); + + $style = $element->getStyle(); + // dimensions px, fallback sur getSvgDimensions() + $pxW = $style->getWidth() ?: 0; + $pxH = $style->getHeight() ?: 0; + if ($pxW <= 0 || $pxH <= 0) { + [$pxW, $pxH] = $element->getSvgDimensions($element->getSource()); + } + $cx = \PhpOffice\PhpWord\Shared\Drawing::pixelsToEmu($pxW); + $cy = \PhpOffice\PhpWord\Shared\Drawing::pixelsToEmu($pxH); + + // + align + if (!$this->withoutP) { + $xmlWriter->startElement('w:p'); + (new ImageStyleWriter($xmlWriter, $style))->writeAlignment(); + } + // + $xmlWriter->startElement('w:r'); + // + $xmlWriter->startElement('w:drawing'); + + // avec déclarations xmlns comme python-docx-oss + $xmlWriter->startElement('wp:inline'); + $xmlWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $xmlWriter->writeAttribute('xmlns:pic', 'http://schemas.openxmlformats.org/drawingml/2006/picture'); + $xmlWriter->writeAttribute('xmlns:asvg', 'http://schemas.microsoft.com/office/drawing/2016/SVG/main'); + + // + $xmlWriter->startElement('wp:extent'); + $xmlWriter->writeAttribute('cx', (string) $cx); + $xmlWriter->writeAttribute('cy', (string) $cy); + $xmlWriter->endElement(); + + // + $xmlWriter->startElement('wp:docPr'); + $xmlWriter->writeAttribute('id', '1'); + $xmlWriter->writeAttribute('name', 'Picture 1'); + $xmlWriter->endElement(); + + // + $xmlWriter->startElement('wp:cNvGraphicFramePr'); + $xmlWriter->startElement('a:graphicFrameLocks'); + $xmlWriter->writeAttribute('noChangeAspect', '1'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + // + $xmlWriter->startElement('a:graphic'); + // + $xmlWriter->startElement('a:graphicData'); + $xmlWriter->writeAttribute( + 'uri', + 'http://schemas.openxmlformats.org/drawingml/2006/picture' + ); + + // + $xmlWriter->startElement('pic:pic'); + + // + $xmlWriter->startElement('pic:nvPicPr'); + $xmlWriter->startElement('pic:cNvPr'); + $xmlWriter->writeAttribute('id', '0'); + $xmlWriter->writeAttribute('name', basename($element->getSource())); + $xmlWriter->endElement(); + $xmlWriter->startElement('pic:cNvPicPr'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + // + $xmlWriter->startElement('pic:blipFill'); + $xmlWriter->startElement('a:blip'); + // uniquement extLst avec svgBlip + $xmlWriter->startElement('a:extLst'); + $xmlWriter->startElement('a:ext'); + $xmlWriter->writeAttribute( + 'uri', + '{96DAC541-7B7A-43D3-8B79-37D633B846F1}' + ); + $xmlWriter->startElement('asvg:svgBlip'); + $xmlWriter->writeAttribute( + 'r:embed', + 'rId' . $rId + ); + $xmlWriter->endElement(); // asvg:svgBlip + $xmlWriter->endElement(); // a:ext + $xmlWriter->endElement(); // a:extLst + $xmlWriter->endElement(); // a:blip + + // + $xmlWriter->startElement('a:stretch'); + $xmlWriter->startElement('a:fillRect'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->endElement(); // pic:blipFill + + // + $xmlWriter->startElement('pic:spPr'); + $xmlWriter->startElement('a:xfrm'); + $xmlWriter->startElement('a:off'); + $xmlWriter->writeAttribute('x', '0'); + $xmlWriter->writeAttribute('y', '0'); + $xmlWriter->endElement(); + $xmlWriter->startElement('a:ext'); + $xmlWriter->writeAttribute('cx', (string) $cx); + $xmlWriter->writeAttribute('cy', (string) $cy); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + $xmlWriter->startElement('a:prstGeom'); + $xmlWriter->writeAttribute('prst', 'rect'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); // pic:spPr + + $xmlWriter->endElement(); // pic:pic + + $xmlWriter->endElement(); // a:graphicData + $xmlWriter->endElement(); // a:graphic + + $xmlWriter->endElement(); // wp:inline + + $xmlWriter->endElement(); // w:drawing + $xmlWriter->endElement(); // w:r + + // + if (!$this->withoutP) { + $xmlWriter->endElement(); + } + } } diff --git a/src/PhpWord/Writer/Word2007/Element/Ruby.php b/src/PhpWord/Writer/Word2007/Element/Ruby.php index f30a5f7e84d..f8871513567 100644 --- a/src/PhpWord/Writer/Word2007/Element/Ruby.php +++ b/src/PhpWord/Writer/Word2007/Element/Ruby.php @@ -29,11 +29,8 @@ class Ruby extends AbstractElement public function write(): void { $xmlWriter = $this->getXmlWriter(); + /** @var \PhpOffice\PhpWord\Element\Ruby */ $element = $this->getElement(); - if (!$element instanceof \PhpOffice\PhpWord\Element\Ruby) { - return; - } - /** @var \PhpOffice\PhpWord\Element\Ruby $element */ $this->startElementP(); $xmlWriter->startElement('w:r'); diff --git a/src/PhpWord/Writer/Word2007/Part/Styles.php b/src/PhpWord/Writer/Word2007/Part/Styles.php index 0ed9d6b6fb2..4d55845d384 100644 --- a/src/PhpWord/Writer/Word2007/Part/Styles.php +++ b/src/PhpWord/Writer/Word2007/Part/Styles.php @@ -60,12 +60,13 @@ public function write() if ($styleName == 'Normal') { continue; } + $name = $style->getStyleName(); // Get style class and execute if the private method exists $styleClass = substr(get_class($style), strrpos(get_class($style), '\\') + 1); $method = "write{$styleClass}Style"; if (method_exists($this, $method)) { - $this->$method($xmlWriter, $styleName, $style); + $this->$method($xmlWriter, $styleName, $style, $name); } } } @@ -169,7 +170,7 @@ private function writeDefaultStyles(XMLWriter $xmlWriter, $styles): void * * @param string $styleName */ - private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $style): void + private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $style, string $name): void { $paragraphStyle = $style->getParagraph(); $styleType = $style->getStyleType(); @@ -201,6 +202,11 @@ private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $sty } elseif (null !== $paragraphStyle) { // if type is 'paragraph' it should have a styleId $xmlWriter->writeAttribute('w:styleId', $styleName); + } else { + $styleId = Style::alternateName($styleName); + if ($styleName !== $styleId) { + $xmlWriter->writeAttribute('w:styleId', $styleId); + } } // Style name @@ -210,7 +216,7 @@ private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $sty // Parent style if (null !== $paragraphStyle) { - if ($paragraphStyle->getStyleName() != null) { + if (!empty($paragraphStyle->getStyleName())) { $xmlWriter->writeElementBlock('w:basedOn', 'w:val', $paragraphStyle->getStyleName()); } elseif ($paragraphStyle->getBasedOn() != null) { $xmlWriter->writeElementBlock('w:basedOn', 'w:val', $paragraphStyle->getBasedOn()); @@ -235,7 +241,7 @@ private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $sty * * @param string $styleName */ - private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, ParagraphStyle $style): void + private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, ParagraphStyle $style, string $name): void { $xmlWriter->startElement('w:style'); $xmlWriter->writeAttribute('w:type', 'paragraph'); @@ -247,7 +253,7 @@ private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, Paragraph // Parent style $basedOn = $style->getBasedOn(); - $xmlWriter->writeElementIf(null !== $basedOn, 'w:basedOn', 'w:val', $basedOn); + $xmlWriter->writeElementIf('' !== $basedOn, 'w:basedOn', 'w:val', $basedOn); // Next paragraph style $next = $style->getNext(); @@ -265,15 +271,17 @@ private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, Paragraph * * @param string $styleName */ - private function writeTableStyle(XMLWriter $xmlWriter, $styleName, TableStyle $style): void + private function writeTableStyle(XMLWriter $xmlWriter, $styleName, TableStyle $style, string $name): void { $xmlWriter->startElement('w:style'); $xmlWriter->writeAttribute('w:type', 'table'); $xmlWriter->writeAttribute('w:customStyle', '1'); $xmlWriter->writeAttribute('w:styleId', $styleName); $xmlWriter->startElement('w:name'); - $xmlWriter->writeAttribute('w:val', $styleName); + $xmlWriter->writeAttribute('w:val', $name ?: $styleName); $xmlWriter->endElement(); + $basedOn = $style->getBasedOn(); + $xmlWriter->writeElementIf('' !== $basedOn, 'w:basedOn', 'w:val', $basedOn); $xmlWriter->startElement('w:uiPriority'); $xmlWriter->writeAttribute('w:val', '99'); $xmlWriter->endElement(); diff --git a/src/PhpWord/Writer/Word2007/Style/Font.php b/src/PhpWord/Writer/Word2007/Style/Font.php index 623e8d5e728..52b57bc6b19 100644 --- a/src/PhpWord/Writer/Word2007/Style/Font.php +++ b/src/PhpWord/Writer/Word2007/Style/Font.php @@ -39,8 +39,7 @@ public function write(): void { $xmlWriter = $this->getXmlWriter(); - $isStyleName = $this->isInline && null !== $this->style && is_string($this->style); - if ($isStyleName) { + if ($this->isInline && null !== $this->style && is_string($this->style)) { $xmlWriter->startElement('w:rPr'); $xmlWriter->startElement('w:rStyle'); $xmlWriter->writeAttribute('w:val', $this->style); @@ -154,10 +153,7 @@ private function writeStyle(): void } // RTL - if ($this->isInline === true) { - $styleName = $style->getStyleName(); - $xmlWriter->writeElementIf($styleName === null && $style->isRTL(), 'w:rtl'); - } + $xmlWriter->writeElementIf($style->isRTL(), 'w:rtl'); // Position $xmlWriter->writeElementIf($style->getPosition() !== null, 'w:position', 'w:val', $style->getPosition()); diff --git a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php index b464e66ffb1..4705b2e776d 100644 --- a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php +++ b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php @@ -30,7 +30,7 @@ class MarginBorder extends AbstractStyle /** * Sizes. * - * @var int[] + * @var array */ private $sizes = []; @@ -65,14 +65,12 @@ public function write(): void $sides = ['top', 'left', 'right', 'bottom', 'insideH', 'insideV']; foreach ($this->sizes as $i => $size) { - if ($size !== null) { - $color = null; - if (isset($this->colors[$i])) { - $color = $this->colors[$i]; - } - $style = $this->styles[$i] ?? 'single'; - $this->writeSide($xmlWriter, $sides[$i], $this->sizes[$i], $color, $style); + $color = null; + if (isset($this->colors[$i])) { + $color = $this->colors[$i]; } + $style = $this->styles[$i] ?? 'single'; + $this->writeSide($xmlWriter, $sides[$i], $this->sizes[$i], $color, $style); } } @@ -80,8 +78,8 @@ public function write(): void * Write side. * * @param string $side - * @param int $width - * @param string $color + * @param float|int $width + * @param ?string $color * @param string $borderStyle */ private function writeSide(XMLWriter $xmlWriter, $side, $width, $color = null, $borderStyle = 'solid'): void @@ -94,7 +92,7 @@ private function writeSide(XMLWriter $xmlWriter, $side, $width, $color = null, $ } } $xmlWriter->writeAttribute('w:val', $borderStyle); - $xmlWriter->writeAttribute('w:sz', $width); + $xmlWriter->writeAttributeIf($width != null, 'w:sz', $width); $xmlWriter->writeAttributeIf($color != null, 'w:color', $color); if (!empty($this->attributes)) { if (isset($this->attributes['space'])) { @@ -111,7 +109,7 @@ private function writeSide(XMLWriter $xmlWriter, $side, $width, $color = null, $ /** * Set sizes. * - * @param int[] $value + * @param array $value */ public function setSizes($value): void { diff --git a/src/PhpWord/Writer/Word2007/Style/Paragraph.php b/src/PhpWord/Writer/Word2007/Style/Paragraph.php index 55f51a54d60..9c0c6143cf8 100644 --- a/src/PhpWord/Writer/Word2007/Style/Paragraph.php +++ b/src/PhpWord/Writer/Word2007/Style/Paragraph.php @@ -106,6 +106,11 @@ private function writeStyle(): void //Right to left $xmlWriter->writeElementIf($styles['bidi'] === true, 'w:bidi'); + if ($styles['textDirection'] !== '') { + $xmlWriter->startElement('w:textDirection'); + $xmlWriter->writeAttribute('w:val', $styles['textDirection']); + $xmlWriter->endElement(); // w:textDirection + } //Paragraph contextualSpacing $xmlWriter->writeElementIf($styles['contextualSpacing'] === true, 'w:contextualSpacing'); diff --git a/src/PhpWord/Writer/Word2007/Style/Table.php b/src/PhpWord/Writer/Word2007/Style/Table.php index 711f3ecde7f..1489c25866d 100644 --- a/src/PhpWord/Writer/Word2007/Style/Table.php +++ b/src/PhpWord/Writer/Word2007/Style/Table.php @@ -64,6 +64,8 @@ private function writeStyle(XMLWriter $xmlWriter, TableStyle $style): void { // w:tblPr $xmlWriter->startElement('w:tblPr'); + $tblStyle = $style->getTblStyle(); + $xmlWriter->writeElementIf($tblStyle !== '', 'w:tblStyle', 'w:val', $tblStyle); // Table alignment if ('' !== $style->getAlignment()) { @@ -140,6 +142,7 @@ private function writeBorder(XMLWriter $xmlWriter, TableStyle $style): void $styleWriter = new MarginBorder($xmlWriter); $styleWriter->setSizes($style->getBorderSize()); $styleWriter->setColors($style->getBorderColor()); + $styleWriter->setStyles($style->getBorderStyle()); $styleWriter->write(); $xmlWriter->endElement(); // w:tblBorders diff --git a/tests/PhpWordTests/AbstractWebServerEmbedded.php b/tests/PhpWordTests/AbstractWebServerEmbedded.php index fde7e1007c9..e55c5c57e75 100644 --- a/tests/PhpWordTests/AbstractWebServerEmbedded.php +++ b/tests/PhpWordTests/AbstractWebServerEmbedded.php @@ -51,7 +51,7 @@ protected static function getRemoteImageUrl() return self::getBaseUrl() . '/images/new-php-logo.png'; } - return 'http://php.net/images/logos/new-php-logo.png'; + return 'https://www.php.net/images/logos/new-php-logo.png'; } protected static function getRemoteImageUrlWithoutExtension(): string @@ -69,7 +69,7 @@ protected static function getRemoteGifImageUrl() return self::getBaseUrl() . '/images/mario.gif'; } - return 'http://php.net/images/logos/php-med-trans-light.gif'; + return 'https://www.php.net/images/logos/php-med-trans-light.gif'; } protected static function getRemoteBmpImageUrl() @@ -78,6 +78,6 @@ protected static function getRemoteBmpImageUrl() return self::getBaseUrl() . '/images/duke_nukem.bmp'; } - return 'https://samples.libav.org/image-samples/RACECAR.BMP'; + return 'https://examplefiles.org/files/images/bmp-example-file-download-500x500.bmp'; } } diff --git a/tests/PhpWordTests/AutoloaderTest.php b/tests/PhpWordTests/AutoloaderTest.php index 076bc8ebca9..6f1df1d71c4 100644 --- a/tests/PhpWordTests/AutoloaderTest.php +++ b/tests/PhpWordTests/AutoloaderTest.php @@ -30,8 +30,7 @@ public function testRegister(): void { Autoloader::register(); $splFunctions = spl_autoload_functions(); - // @phpstan-ignore-next-line spl_autoload_functions return false < PHP 8.0 - if ($splFunctions === false) { + if (empty($splFunctions)) { $splFunctions = []; } diff --git a/tests/PhpWordTests/ComplexType/FootnotePropertiesTest.php b/tests/PhpWordTests/ComplexType/FootnotePropertiesTest.php index 6472968d3bc..22b8edab270 100644 --- a/tests/PhpWordTests/ComplexType/FootnotePropertiesTest.php +++ b/tests/PhpWordTests/ComplexType/FootnotePropertiesTest.php @@ -26,8 +26,6 @@ * Test class for PhpOffice\PhpWord\ComplexType\FootnoteProperties. * * @coversDefaultClass \PhpOffice\PhpWord\ComplexType\FootnoteProperties - * - * @runTestsInSeparateProcesses */ class FootnotePropertiesTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/ComplexType/RubyPropertiesTest.php b/tests/PhpWordTests/ComplexType/RubyPropertiesTest.php index 62bc0b07390..d494b2ab0d7 100644 --- a/tests/PhpWordTests/ComplexType/RubyPropertiesTest.php +++ b/tests/PhpWordTests/ComplexType/RubyPropertiesTest.php @@ -23,8 +23,6 @@ /** * Test class for PhpOffice\PhpWord\ComplexType\RubyProperties. - * - * @runTestsInSeparateProcesses */ class RubyPropertiesTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/AbstractElementClass.php b/tests/PhpWordTests/Element/AbstractElementClass.php new file mode 100644 index 00000000000..407e8c934ce --- /dev/null +++ b/tests/PhpWordTests/Element/AbstractElementClass.php @@ -0,0 +1,25 @@ +getMockForAbstractClass(AbstractElement::class); - } else { - /** @var AbstractElement $stub */ - $stub = new class() extends AbstractElement { - }; - } + $stub = new AbstractElementClass(); $ival = mt_rand(0, 100); $stub->setElementIndex($ival); self::assertEquals($ival, $stub->getElementIndex()); @@ -48,14 +39,7 @@ public function testElementIndex(): void */ public function testElementId(): void { - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $stub = $this->getMockForAbstractClass(AbstractElement::class); - } else { - /** @var AbstractElement $stub */ - $stub = new class() extends AbstractElement { - }; - } + $stub = new AbstractElementClass(); $stub->setElementId(); self::assertEquals(6, strlen($stub->getElementId())); } diff --git a/tests/PhpWordTests/Element/BookmarkTest.php b/tests/PhpWordTests/Element/BookmarkTest.php index 097166736f3..5499b18a966 100644 --- a/tests/PhpWordTests/Element/BookmarkTest.php +++ b/tests/PhpWordTests/Element/BookmarkTest.php @@ -22,8 +22,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Footer. - * - * @runTestsInSeparateProcesses */ class BookmarkTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/CellTest.php b/tests/PhpWordTests/Element/CellTest.php index 2fedcafc24c..5417e0b457c 100644 --- a/tests/PhpWordTests/Element/CellTest.php +++ b/tests/PhpWordTests/Element/CellTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Cell. - * - * @runTestsInSeparateProcesses */ class CellTest extends AbstractWebServerEmbedded { @@ -50,6 +48,17 @@ public function testConstructWithStyleArray(): void self::assertNull($oCell->getWidth()); } + /** + * Test if the style object passed to the constructor is actually used. + */ + public function testConstructWithStyleObject(): void + { + $style = new \PhpOffice\PhpWord\Style\Cell(); + $oCell = new Cell(null, $style); + + self::assertSame($style, $oCell->getStyle()); + } + /** * Add text. */ @@ -165,6 +174,9 @@ public function testAddImageFooter(): void /** * Add image section by URL. + * Method getRemoteGifImageUrl seems to require separate process. + * + * @runInSeparateProcess */ public function testAddImageSectionByUrl(): void { diff --git a/tests/PhpWordTests/Element/CheckBoxTest.php b/tests/PhpWordTests/Element/CheckBoxTest.php index fbdbf36aa3f..1b808ee53c3 100644 --- a/tests/PhpWordTests/Element/CheckBoxTest.php +++ b/tests/PhpWordTests/Element/CheckBoxTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\CheckBox. - * - * @runTestsInSeparateProcesses */ class CheckBoxTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/CommentTest.php b/tests/PhpWordTests/Element/CommentTest.php index f76316d8903..5b5aaeacfec 100644 --- a/tests/PhpWordTests/Element/CommentTest.php +++ b/tests/PhpWordTests/Element/CommentTest.php @@ -26,8 +26,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Header. - * - * @runTestsInSeparateProcesses */ class CommentTest extends \PHPUnit\Framework\TestCase { @@ -85,6 +83,19 @@ public function testTwoCommentsOnSameText(): void self::assertEquals($text->getCommentsRangeEnd()->getItem(1)->getElementId(), $comment2->getElementId()); } + public function testElementId(): void + { + $section = new Section(0); + $text = $section->addText('Text'); + + $comment1 = new Comment('Author1', new DateTime(), 'A1'); + $comment1->addText('Comment1'); + $temp1 = $comment1->getElementId(); + self::assertNull($temp1); + $text->setCommentRangeEnd($comment1); + self::assertNotNull($comment1->getElementId()); + } + /** * Add text. */ diff --git a/tests/PhpWordTests/Element/FieldTest.php b/tests/PhpWordTests/Element/FieldTest.php index f624e9294d0..d0018f1e1c9 100644 --- a/tests/PhpWordTests/Element/FieldTest.php +++ b/tests/PhpWordTests/Element/FieldTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Field. - * - * @runTestsInSeparateProcesses */ class FieldTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/FooterTest.php b/tests/PhpWordTests/Element/FooterTest.php index f97b159cb92..1d368834b13 100644 --- a/tests/PhpWordTests/Element/FooterTest.php +++ b/tests/PhpWordTests/Element/FooterTest.php @@ -23,8 +23,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Footer. - * - * @runTestsInSeparateProcesses */ class FooterTest extends AbstractWebServerEmbedded { @@ -115,6 +113,9 @@ public function testAddImage(): void /** * Add image by URL. + * Method getRemoteGifImageUrl seems to require separate process. + * + * @runInSeparateProcess */ public function testAddImageByUrl(): void { diff --git a/tests/PhpWordTests/Element/FootnoteTest.php b/tests/PhpWordTests/Element/FootnoteTest.php index c6297cfc32d..b51130532f8 100644 --- a/tests/PhpWordTests/Element/FootnoteTest.php +++ b/tests/PhpWordTests/Element/FootnoteTest.php @@ -22,8 +22,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Footnote. - * - * @runTestsInSeparateProcesses */ class FootnoteTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/FormulaTest.php b/tests/PhpWordTests/Element/FormulaTest.php index dcb730e9cc9..6a3fe44570c 100644 --- a/tests/PhpWordTests/Element/FormulaTest.php +++ b/tests/PhpWordTests/Element/FormulaTest.php @@ -25,8 +25,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Formula. - * - * @runTestsInSeparateProcesses */ class FormulaTest extends AbstractWebServerEmbedded { diff --git a/tests/PhpWordTests/Element/HeaderTest.php b/tests/PhpWordTests/Element/HeaderTest.php index 6d659dbd7c9..8e3d7e500c5 100644 --- a/tests/PhpWordTests/Element/HeaderTest.php +++ b/tests/PhpWordTests/Element/HeaderTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Header. - * - * @runTestsInSeparateProcesses */ class HeaderTest extends AbstractWebServerEmbedded { @@ -125,6 +123,9 @@ public function testAddImage(): void /** * Add image by URL. + * Method getRemoteGifImageUrl seems to require separate process. + * + * @runInSeparateProcess */ public function testAddImageByUrl(): void { diff --git a/tests/PhpWordTests/Element/LineTest.php b/tests/PhpWordTests/Element/LineTest.php index f4a39b38d19..e9eac1743fc 100644 --- a/tests/PhpWordTests/Element/LineTest.php +++ b/tests/PhpWordTests/Element/LineTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Element\Line. * * @coversDefaultClass \PhpOffice\PhpWord\Element\Line - * - * @runTestsInSeparateProcesses */ class LineTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/LinkTest.php b/tests/PhpWordTests/Element/LinkTest.php index 5b5c6f77bfa..91e1b787ebc 100644 --- a/tests/PhpWordTests/Element/LinkTest.php +++ b/tests/PhpWordTests/Element/LinkTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Element\Link. * * @coversDefaultClass \PhpOffice\PhpWord\Element\Link - * - * @runTestsInSeparateProcesses */ class LinkTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/ListItemRunTest.php b/tests/PhpWordTests/Element/ListItemRunTest.php index 69b5f990b0e..8c9d0d6b14d 100644 --- a/tests/PhpWordTests/Element/ListItemRunTest.php +++ b/tests/PhpWordTests/Element/ListItemRunTest.php @@ -22,8 +22,6 @@ /** * Test class for PhpOffice\PhpWord\Element\ListItemRun. - * - * @runTestsInSeparateProcesses */ class ListItemRunTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/ListItemTest.php b/tests/PhpWordTests/Element/ListItemTest.php index 3da9a8d1017..f38040c3457 100644 --- a/tests/PhpWordTests/Element/ListItemTest.php +++ b/tests/PhpWordTests/Element/ListItemTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Element\ListItem. * * @coversDefaultClass \PhpOffice\PhpWord\Element\ListItem - * - * @runTestsInSeparateProcesses */ class ListItemTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/ObjectTest.php b/tests/PhpWordTests/Element/ObjectTest.php index 2ef05567bc5..c18bdaa3732 100644 --- a/tests/PhpWordTests/Element/ObjectTest.php +++ b/tests/PhpWordTests/Element/ObjectTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Element\OLEObject. * * @coversDefaultClass \PhpOffice\PhpWord\Element\OLEObject - * - * @runTestsInSeparateProcesses */ class ObjectTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/PreserveTextTest.php b/tests/PhpWordTests/Element/PreserveTextTest.php index 746db6aeee3..9bcb0b5b3bc 100644 --- a/tests/PhpWordTests/Element/PreserveTextTest.php +++ b/tests/PhpWordTests/Element/PreserveTextTest.php @@ -23,8 +23,6 @@ /** * Test class for PhpOffice\PhpWord\Element\PreserveText. - * - * @runTestsInSeparateProcesses */ class PreserveTextTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/RowTest.php b/tests/PhpWordTests/Element/RowTest.php index 6dc8335ffde..793914efdc2 100644 --- a/tests/PhpWordTests/Element/RowTest.php +++ b/tests/PhpWordTests/Element/RowTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Element\Row. * * @coversDefaultClass \PhpOffice\PhpWord\Element\Row - * - * @runTestsInSeparateProcesses */ class RowTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/RubyTest.php b/tests/PhpWordTests/Element/RubyTest.php index 44d2e5ba3cb..16cdea0c3e9 100644 --- a/tests/PhpWordTests/Element/RubyTest.php +++ b/tests/PhpWordTests/Element/RubyTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Text. - * - * @runTestsInSeparateProcesses */ class RubyTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/SectionTest.php b/tests/PhpWordTests/Element/SectionTest.php index bf38a333008..1929c6c186c 100644 --- a/tests/PhpWordTests/Element/SectionTest.php +++ b/tests/PhpWordTests/Element/SectionTest.php @@ -29,8 +29,6 @@ * @covers \PhpOffice\PhpWord\Element\Section * * @coversDefaultClass \PhpOffice\PhpWord\Element\Section - * - * @runTestsInSeparateProcesses */ class SectionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/SvgImageTest.php b/tests/PhpWordTests/Element/SvgImageTest.php new file mode 100644 index 00000000000..0e8bb4d32f1 --- /dev/null +++ b/tests/PhpWordTests/Element/SvgImageTest.php @@ -0,0 +1,70 @@ +addSection(); + $image = $section->addImage($svgPath); + + self::assertSame($svgPath, $image->getSource()); + self::assertSame('image/svg+xml', $image->getImageType()); + } + + public function testAddSvgImageWithStyles(): void + { + $svgPath = self::SVG_PATH; + if (!file_exists($svgPath)) { + self::markTestSkipped('SVG file not found: ' . $svgPath); + } + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $options = [ + 'width' => 200, + 'height' => 200, + 'wrappingStyle' => Image::WRAPPING_STYLE_BEHIND, + ]; + + $image = $section->addImage($svgPath, $options); + + self::assertSame(200, $image->getStyle()->getWidth()); + self::assertSame(200, $image->getStyle()->getHeight()); + self::assertSame(Image::WRAPPING_STYLE_BEHIND, $image->getStyle()->getWrappingStyle()); + } + + public function testNonExistentSvg(): void + { + $this->expectException(InvalidImageException::class); + $this->expectExceptionMessage('Impossible de lire'); + $element = new ImageElement('xyz.svg'); + } + + public function testInvalidSvg(): void + { + $this->expectException(InvalidImageException::class); + $this->expectExceptionMessage('SVG invalide'); + $element = new ImageElement(__DIR__ . '/invalid.svg'); + } + + public function testNoWidthSvg(): void + { + $this->expectException(InvalidImageException::class); + $this->expectExceptionMessage('width/height ou viewBox'); + $element = new ImageElement(__DIR__ . '/nowidth.svg'); + } +} diff --git a/tests/PhpWordTests/Element/TOCTest.php b/tests/PhpWordTests/Element/TOCTest.php index 3770823f5fb..c880ba6f779 100644 --- a/tests/PhpWordTests/Element/TOCTest.php +++ b/tests/PhpWordTests/Element/TOCTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\TOC. - * - * @runTestsInSeparateProcesses */ class TOCTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/TableTest.php b/tests/PhpWordTests/Element/TableTest.php index 0628d269e66..2643f7faeef 100644 --- a/tests/PhpWordTests/Element/TableTest.php +++ b/tests/PhpWordTests/Element/TableTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Element\Table. * * @coversDefaultClass \PhpOffice\PhpWord\Element\Table - * - * @runTestsInSeparateProcesses */ class TableTest extends \PHPUnit\Framework\TestCase { @@ -93,6 +91,8 @@ public function testCell(): void $oTable->addRow(); $element = $oTable->addCell(); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Cell', $element); + $element->getStyle()->setWidth(1236); + self::assertSame(1236, $element->getStyle()->getWidth()); } /** diff --git a/tests/PhpWordTests/Element/TextBoxTest.php b/tests/PhpWordTests/Element/TextBoxTest.php index 9289d1ad47c..3623cd3ef8c 100644 --- a/tests/PhpWordTests/Element/TextBoxTest.php +++ b/tests/PhpWordTests/Element/TextBoxTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Element\TextBox. * * @coversDefaultClass \PhpOffice\PhpWord\Element\TextBox - * - * @runTestsInSeparateProcesses */ class TextBoxTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/TextBreakTest.php b/tests/PhpWordTests/Element/TextBreakTest.php index 10ca0581808..bf99c2a652c 100644 --- a/tests/PhpWordTests/Element/TextBreakTest.php +++ b/tests/PhpWordTests/Element/TextBreakTest.php @@ -26,8 +26,6 @@ * Test class for PhpOffice\PhpWord\Element\TextBreak. * * @coversDefaultClass \PhpOffice\PhpWord\Element\TextBreak - * - * @runTestsInSeparateProcesses */ class TextBreakTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/TextRunTest.php b/tests/PhpWordTests/Element/TextRunTest.php index b18eca99e45..b18db03794e 100644 --- a/tests/PhpWordTests/Element/TextRunTest.php +++ b/tests/PhpWordTests/Element/TextRunTest.php @@ -26,8 +26,6 @@ /** * Test class for PhpOffice\PhpWord\Element\TextRun. - * - * @runTestsInSeparateProcesses */ class TextRunTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/TextTest.php b/tests/PhpWordTests/Element/TextTest.php index 693430a87f6..d8c5b02eef1 100644 --- a/tests/PhpWordTests/Element/TextTest.php +++ b/tests/PhpWordTests/Element/TextTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Text. - * - * @runTestsInSeparateProcesses */ class TextTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/TitleTest.php b/tests/PhpWordTests/Element/TitleTest.php index ab1e38102f7..aea0aa7974d 100644 --- a/tests/PhpWordTests/Element/TitleTest.php +++ b/tests/PhpWordTests/Element/TitleTest.php @@ -28,8 +28,6 @@ * Test class for PhpOffice\PhpWord\Element\Title. * * @coversDefaultClass \PhpOffice\PhpWord\Element\Title - * - * @runTestsInSeparateProcesses */ class TitleTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/TrackChangeTest.php b/tests/PhpWordTests/Element/TrackChangeTest.php index 1d37fb3dd44..63045786332 100644 --- a/tests/PhpWordTests/Element/TrackChangeTest.php +++ b/tests/PhpWordTests/Element/TrackChangeTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\TrackChange. - * - * @runTestsInSeparateProcesses */ class TrackChangeTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/invalid.svg b/tests/PhpWordTests/Element/invalid.svg new file mode 100644 index 00000000000..8e3dbcc520c --- /dev/null +++ b/tests/PhpWordTests/Element/invalid.svg @@ -0,0 +1 @@ +Deliberately invalid. diff --git a/tests/PhpWordTests/Element/nowidth.svg b/tests/PhpWordTests/Element/nowidth.svg new file mode 100644 index 00000000000..27c7814ae56 --- /dev/null +++ b/tests/PhpWordTests/Element/nowidth.svg @@ -0,0 +1,96 @@ + + + Official PHP Logo + + + + image/svg+xml + + Official PHP Logo + + + Colin Viebrock + + + + + + + + + + + + Copyright Colin Viebrock 1997 - All rights reserved. + + + 1997 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/PhpWordTests/Escaper/RtfEscaper2Test.php b/tests/PhpWordTests/Escaper/RtfEscaper2Test.php index d65b543f215..1863f93a16d 100644 --- a/tests/PhpWordTests/Escaper/RtfEscaper2Test.php +++ b/tests/PhpWordTests/Escaper/RtfEscaper2Test.php @@ -23,12 +23,11 @@ */ class RtfEscaper2Test extends \PHPUnit\Framework\TestCase { - const HEADER = '\\pard\\nowidctlpar {\\cf0\\f0 '; + const HEADER = '\\pard\\widctlpar {'; const TRAILER = '}\\par'; public function escapestring($str) { - \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); $parentWriter = new \PhpOffice\PhpWord\Writer\RTF(); $element = new \PhpOffice\PhpWord\Element\Text($str); $txt = new \PhpOffice\PhpWord\Writer\RTF\Element\Text($parentWriter, $element); @@ -58,7 +57,7 @@ public function testSpecial(): void public function testAccent(): void { $str = 'Voilà - string with accented char'; - $expect = $this->expect('Voil\\uc0{\\u224} - string with accented char'); + $expect = $this->expect('Voil\\uc0\\u224 - string with accented char'); self::assertEquals($expect, $this->escapestring($str)); } @@ -68,7 +67,27 @@ public function testAccent(): void public function testHebrew(): void { $str = 'Hebrew - שלום'; - $expect = $this->expect('Hebrew - \\uc0{\\u1513}\\uc0{\\u1500}\\uc0{\\u1493}\\uc0{\\u1501}'); + $expect = $this->expect('Hebrew - \\uc0\\u1513 \\uc0\\u1500 \\uc0\\u1493 \\uc0\\u1501 '); + self::assertEquals($expect, $this->escapestring($str)); + } + + /** + * Test Chinese. + */ + public function test3ByteCharacters(): void + { + $str = 'Chinese - 耀老耂'; + $expect = $this->expect('Chinese - \\uc0\\u-32768 \\uc0\\u-32767 \\uc0\\u-32766 '); + self::assertEquals($expect, $this->escapestring($str)); + } + + /** + * Test Osmanya, not in BMP. + */ + public function testBeyondBmp(): void + { + $str = 'Osmanya - 𐐀𐐁𐐂'; + $expect = $this->expect('Osmanya - \uc0\u-10239 \uc0\u-9216 \uc0\u-10239 \uc0\u-9215 \uc0\u-10239 \uc0\u-9214 '); self::assertEquals($expect, $this->escapestring($str)); } diff --git a/tests/PhpWordTests/Escaper/RtfEscaper3Test.php b/tests/PhpWordTests/Escaper/RtfEscaper3Test.php index 1aebea52f0e..fad9f3cd7e4 100644 --- a/tests/PhpWordTests/Escaper/RtfEscaper3Test.php +++ b/tests/PhpWordTests/Escaper/RtfEscaper3Test.php @@ -25,8 +25,8 @@ */ class RtfEscaper3Test extends \PHPUnit\Framework\TestCase { - const HEADER = '\\pard\\nowidctlpar \ql{\\cf0\\f0 '; - const HEADER_RTL = '\\pard\\nowidctlpar \qr{\\rtlch\\cf0\\f0 '; + const HEADER = '\\pard\\ql\\widctlpar {\\ltrch '; + const HEADER_RTL = '\\pard\\qr\\rtlpar\\widctlpar {\\rtlch '; const TRAILER = '}\\par'; protected function tearDown(): void @@ -36,7 +36,6 @@ protected function tearDown(): void public function escapestring(string $str): string { - Settings::setOutputEscapingEnabled(true); $parentWriter = new \PhpOffice\PhpWord\Writer\RTF(); $element = new \PhpOffice\PhpWord\Element\Text($str); $txt = new \PhpOffice\PhpWord\Writer\RTF\Element\Text($parentWriter, $element); @@ -68,7 +67,7 @@ public function testAccent(): void { Settings::setDefaultRtl(false); $str = 'Voilà - string with accented char'; - $expect = $this->expect('Voil\\uc0{\\u224} - string with accented char'); + $expect = $this->expect('Voil\\uc0\\u224 - string with accented char'); self::assertEquals($expect, $this->escapestring($str)); } @@ -79,7 +78,7 @@ public function testHebrew(): void { Settings::setDefaultRtl(true); $str = 'Hebrew - שלום'; - $expect = $this->expect('Hebrew - \\uc0{\\u1513}\\uc0{\\u1500}\\uc0{\\u1493}\\uc0{\\u1501}', true); + $expect = $this->expect('Hebrew - \\uc0\\u1513 \\uc0\\u1500 \\uc0\\u1493 \\uc0\\u1501 ', true); self::assertEquals($expect, $this->escapestring($str)); } diff --git a/tests/PhpWordTests/Exception/ExceptionTest.php b/tests/PhpWordTests/Exception/ExceptionTest.php index 09ee933c86c..8bbd03190a0 100644 --- a/tests/PhpWordTests/Exception/ExceptionTest.php +++ b/tests/PhpWordTests/Exception/ExceptionTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Exception\Exception. * * @coversDefaultClass \Exception - * - * @runTestsInSeparateProcesses */ class ExceptionTest extends TestCase { diff --git a/tests/PhpWordTests/Exception/InvalidImageExceptionTest.php b/tests/PhpWordTests/Exception/InvalidImageExceptionTest.php index dea167bb256..045d7124a0e 100644 --- a/tests/PhpWordTests/Exception/InvalidImageExceptionTest.php +++ b/tests/PhpWordTests/Exception/InvalidImageExceptionTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Exception\InvalidImageException. * * @coversDefaultClass \PhpOffice\PhpWord\Exception\InvalidImageException - * - * @runTestsInSeparateProcesses */ class InvalidImageExceptionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Exception/InvalidStyleExceptionTest.php b/tests/PhpWordTests/Exception/InvalidStyleExceptionTest.php index 7f2a1650e42..f38fc3cf44a 100644 --- a/tests/PhpWordTests/Exception/InvalidStyleExceptionTest.php +++ b/tests/PhpWordTests/Exception/InvalidStyleExceptionTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Exception\InvalidStyleException. * * @coversDefaultClass \PhpOffice\PhpWord\Exception\InvalidStyleException - * - * @runTestsInSeparateProcesses */ class InvalidStyleExceptionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Exception/UnsupportedImageTypeExceptionTest.php b/tests/PhpWordTests/Exception/UnsupportedImageTypeExceptionTest.php index 2252b874ef1..d40dc8ae9a5 100644 --- a/tests/PhpWordTests/Exception/UnsupportedImageTypeExceptionTest.php +++ b/tests/PhpWordTests/Exception/UnsupportedImageTypeExceptionTest.php @@ -22,8 +22,6 @@ /** * Test class for PhpOffice\PhpWord\Exception\UnsupportedImageTypeExceptionTest. - * - * @runTestsInSeparateProcesses */ class UnsupportedImageTypeExceptionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Helper/HandlerTest.php b/tests/PhpWordTests/Helper/HandlerTest.php new file mode 100644 index 00000000000..38a868614f0 --- /dev/null +++ b/tests/PhpWordTests/Helper/HandlerTest.php @@ -0,0 +1,80 @@ +getMessage()); + } + } + + public function testNotice(): void + { + try { + Handler::notice('invalidtz'); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('Timezone', $e->getMessage()); + } + } + + public function testWarning(): void + { + try { + Handler::warning(); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('ailed to open stream', $e->getMessage()); + } + } + + public function testUserDeprecated(): void + { + try { + Handler::userDeprecated(); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('hello', $e->getMessage()); + } + } + + public function testUserNotice(): void + { + try { + Handler::userNotice(); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('userNotice', $e->getMessage()); + } + } + + public function testUserWarning(): void + { + try { + Handler::userWarning(); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('userWarning', $e->getMessage()); + } + } +} diff --git a/tests/PhpWordTests/IOFactoryTest.php b/tests/PhpWordTests/IOFactoryTest.php index 6a8d746bd07..bf697033367 100644 --- a/tests/PhpWordTests/IOFactoryTest.php +++ b/tests/PhpWordTests/IOFactoryTest.php @@ -31,8 +31,6 @@ /** * Test class for PhpOffice\PhpWord\IOFactory. - * - * @runTestsInSeparateProcesses */ class IOFactoryTest extends TestCase { @@ -43,6 +41,11 @@ protected function setUp(): void Settings::setPdfRenderer($rendererName, $rendererLibraryPath); } + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Create all possible writers. * diff --git a/tests/PhpWordTests/MediaTest.php b/tests/PhpWordTests/MediaTest.php index 2eb60a858b1..2774e46e2a9 100644 --- a/tests/PhpWordTests/MediaTest.php +++ b/tests/PhpWordTests/MediaTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Media. - * - * @runTestsInSeparateProcesses */ class MediaTest extends AbstractWebServerEmbedded { diff --git a/tests/PhpWordTests/Metadata/DocInfoTest.php b/tests/PhpWordTests/Metadata/DocInfoTest.php index d5c9eb5124f..f6a9911fb41 100644 --- a/tests/PhpWordTests/Metadata/DocInfoTest.php +++ b/tests/PhpWordTests/Metadata/DocInfoTest.php @@ -22,8 +22,6 @@ /** * Test class for PhpOffice\PhpWord\Metadata\DocInfo. - * - * @runTestsInSeparateProcesses */ class DocInfoTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Metadata/SettingsTest.php b/tests/PhpWordTests/Metadata/SettingsTest.php index 0c5a6f1593c..9da794c685e 100644 --- a/tests/PhpWordTests/Metadata/SettingsTest.php +++ b/tests/PhpWordTests/Metadata/SettingsTest.php @@ -26,8 +26,6 @@ /** * Test class for PhpOffice\PhpWord\Metadata\Settings. - * - * @runTestsInSeparateProcesses */ class SettingsTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/PhpWordTest.php b/tests/PhpWordTests/PhpWordTest.php index 76919bbdf30..8726b936b9e 100644 --- a/tests/PhpWordTests/PhpWordTest.php +++ b/tests/PhpWordTests/PhpWordTest.php @@ -27,16 +27,24 @@ /** * Test class for PhpOffice\PhpWord\PhpWord. - * - * @runTestsInSeparateProcesses */ class PhpWordTest extends \PHPUnit\Framework\TestCase { + /** + * Executed after each method of the class. + */ + protected function tearDown(): void + { + Style::resetStyles(); + Settings::restoreDefaults(); + } + /** * Test object creation. */ public function testConstruct(): void { + Settings::restoreDefaults(); do { $dtStart = new DateTimeImmutable(); $startSecond = $dtStart->format('s'); @@ -150,7 +158,14 @@ public function testAddTitleStyle(): void } /** - * Test save. + * Weird test - tries to save as if it were a browser even though that + * is not the case. This causes a problem for Phpunit 9 (Php 7.* or 8.0) + * if not run in separate process. So let it run separate. No harm + * for Php8.1+, except for a slight performance penalty. + * + * @runInSeparateProcess + * + * @preserveGlobalState disabled */ public function testSave(): void { diff --git a/tests/PhpWordTests/Reader/HTMLTest.php b/tests/PhpWordTests/Reader/HTMLTest.php index 7a35a06f780..e9228cf8403 100644 --- a/tests/PhpWordTests/Reader/HTMLTest.php +++ b/tests/PhpWordTests/Reader/HTMLTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Reader\HTML. * * @coversDefaultClass \PhpOffice\PhpWord\Reader\HTML - * - * @runTestsInSeparateProcesses */ class HTMLTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Reader/Html/CharsetTest.php b/tests/PhpWordTests/Reader/Html/CharsetTest.php new file mode 100644 index 00000000000..575fa0d14e7 --- /dev/null +++ b/tests/PhpWordTests/Reader/Html/CharsetTest.php @@ -0,0 +1,64 @@ +expectException(Throwable::class); + $this->expectExceptionMessage('unknown encoding'); + } + $directory = 'tests/PhpWordTests/_files/html'; + $reader = new HTML(); + $doc = $reader->load("$directory/$filename"); + $sections = $doc->getSections(); + self::assertCount(1, $sections); + $section = $sections[0]; + $elements = $section->getElements(); + $element = $elements[0]; + self::assertInstanceOf(TextRun::class, $element); + self::assertSame($expectedResult, $element->getText()); + } + + public static function providerCharset(): array + { + return [ + ['charset.ISO-8859-1.html', 'À1'], + ['charset.ISO-8859-1.html4.html', 'À1'], + ['charset.ISO-8859-2.html', 'Ŕ1'], + ['charset.nocharset.html', 'À1'], + ['charset.UTF-8.html', 'À1'], + ['charset.UTF-8.bom.html', 'À1'], + ['charset.UTF-16.bebom.html', 'À1'], + ['charset.UTF-16.lebom.html', 'À1'], + ['charset.gb18030.html', '电视机'], + 'loadhtml gives its best shot' => ['charset.unknown.html', "Ã\u{80}1"], + ]; + } +} diff --git a/tests/PhpWordTests/Reader/MsDocTest.php b/tests/PhpWordTests/Reader/MsDocTest.php index 35522718239..32aae8fe336 100644 --- a/tests/PhpWordTests/Reader/MsDocTest.php +++ b/tests/PhpWordTests/Reader/MsDocTest.php @@ -28,8 +28,6 @@ * Test class for PhpOffice\PhpWord\Reader\MsDoc. * * @coversDefaultClass \PhpOffice\PhpWord\Reader\MsDoc - * - * @runTestsInSeparateProcesses */ class MsDocTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Reader/ODTextTest.php b/tests/PhpWordTests/Reader/ODTextTest.php index c05705a4394..61c597cecfb 100644 --- a/tests/PhpWordTests/Reader/ODTextTest.php +++ b/tests/PhpWordTests/Reader/ODTextTest.php @@ -28,8 +28,6 @@ * Test class for PhpOffice\PhpWord\Reader\ODText. * * @coversDefaultClass \PhpOffice\PhpWord\Reader\ODText - * - * @runTestsInSeparateProcesses */ class ODTextTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Reader/RTFTest.php b/tests/PhpWordTests/Reader/RTFTest.php index a8f472571bf..4b9e9000334 100644 --- a/tests/PhpWordTests/Reader/RTFTest.php +++ b/tests/PhpWordTests/Reader/RTFTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Reader\RTF. * * @coversDefaultClass \PhpOffice\PhpWord\Reader\RTF - * - * @runTestsInSeparateProcesses */ class RTFTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Reader/Word2007/StyleTableTest.php b/tests/PhpWordTests/Reader/Word2007/StyleTableTest.php new file mode 100644 index 00000000000..932cc3b5959 --- /dev/null +++ b/tests/PhpWordTests/Reader/Word2007/StyleTableTest.php @@ -0,0 +1,60 @@ +load($file); + self::assertSame('Times New Roman', $phpWord->getDefaultFontName()); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf(Table::class, $elements[2]); + $style = $elements[2]->getStyle(); + self::assertIsObject($style); + self::assertSame('Tablaconcuadrcula', $style->getTblStyle()); + self::assertSame('none', $style->getBorderTopStyle()); + $baseStyle = Style::getStyle('Tablaconcuadrcula'); + self::assertInstanceOf(TableStyle::class, $baseStyle); + self::assertSame('Table Grid', $baseStyle->getStyleName()); + self::assertSame('Tablanormal', $baseStyle->getBasedOn()); + self::assertSame('single', $baseStyle->getBorderTopStyle()); + } +} diff --git a/tests/PhpWordTests/Reader/Word2007Test.php b/tests/PhpWordTests/Reader/Word2007Test.php index 65c8a4a71b6..cdc8cbc1a82 100644 --- a/tests/PhpWordTests/Reader/Word2007Test.php +++ b/tests/PhpWordTests/Reader/Word2007Test.php @@ -37,8 +37,6 @@ * Test class for PhpOffice\PhpWord\Reader\Word2007. * * @coversDefaultClass \PhpOffice\PhpWord\Reader\Word2007 - * - * @runTestsInSeparateProcesses */ class Word2007Test extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/SettingsRtlTest.php b/tests/PhpWordTests/SettingsRtlTest.php new file mode 100644 index 00000000000..a68c8e04590 --- /dev/null +++ b/tests/PhpWordTests/SettingsRtlTest.php @@ -0,0 +1,73 @@ +isRtl()); + $paragraph = $style->getParagraph(); + self::assertTrue($paragraph->isBidi()); + self::assertSame(TextDirection::RLTB, $paragraph->getTextDirection()); + } + + public function testNormalStyleNotReplaced(): void + { + $phpWord = new PhpWord(); + $phpWord->setDefaultParagraphStyle([]); + $style = Style::getStyle('Normal'); + self::assertInstanceOf(Paragraph::class, $style); + self::assertNotTrue($style->isBidi()); + self::assertSame(TextDirection::NONE, $style->getTextDirection()); + } +} diff --git a/tests/PhpWordTests/SettingsTest.php b/tests/PhpWordTests/SettingsTest.php index f8b9af661d1..fb51bc0b119 100644 --- a/tests/PhpWordTests/SettingsTest.php +++ b/tests/PhpWordTests/SettingsTest.php @@ -24,74 +24,12 @@ * Test class for PhpOffice\PhpWord\Settings. * * @coversDefaultClass \PhpOffice\PhpWord\Settings - * - * @runTestsInSeparateProcesses */ class SettingsTest extends TestCase { - private $compatibility; - - /** @var string */ - private $defaultFontColor; - - private $defaultFontSize; - - private $defaultFontName; - - private $defaultPaper; - - private $measurementUnit; - - private $outputEscapingEnabled; - - private $pdfRendererName; - - /** - * @var array - */ - private $pdfRendererOptions; - - private $pdfRendererPath; - - private $tempDir; - - private $zipClass; - - /** @var bool */ - private $defaultRtl; - - protected function setUp(): void - { - $this->compatibility = Settings::hasCompatibility(); - $this->defaultFontColor = Settings::getDefaultFontColor(); - $this->defaultFontSize = Settings::getDefaultFontSize(); - $this->defaultFontName = Settings::getDefaultFontName(); - $this->defaultPaper = Settings::getDefaultPaper(); - $this->measurementUnit = Settings::getMeasurementUnit(); - $this->outputEscapingEnabled = Settings::isOutputEscapingEnabled(); - $this->pdfRendererName = Settings::getPdfRendererName(); - $this->pdfRendererOptions = Settings::getPdfRendererOptions(); - $this->pdfRendererPath = Settings::getPdfRendererPath(); - $this->tempDir = Settings::getTempDir(); - $this->zipClass = Settings::getZipClass(); - $this->defaultRtl = Settings::isDefaultRtl(); - } - protected function tearDown(): void { - Settings::setCompatibility($this->compatibility); - Settings::setDefaultFontColor($this->defaultFontColor); - Settings::setDefaultFontSize($this->defaultFontSize); - Settings::setDefaultFontName($this->defaultFontName); - Settings::setDefaultPaper($this->defaultPaper); - Settings::setMeasurementUnit($this->measurementUnit); - Settings::setOutputEscapingEnabled($this->outputEscapingEnabled); - Settings::setPdfRendererName($this->pdfRendererName); - Settings::setPdfRendererOptions($this->pdfRendererOptions); - Settings::setPdfRendererPath($this->pdfRendererPath); - Settings::setTempDir($this->tempDir); - Settings::setZipClass($this->zipClass); - Settings::setDefaultRtl($this->defaultRtl); + Settings::restoreDefaults(); } /** @@ -99,6 +37,7 @@ protected function tearDown(): void */ public function testSetGetCompatibility(): void { + Settings::restoreDefaults(); self::assertTrue(Settings::hasCompatibility()); self::assertTrue(Settings::setCompatibility(false)); self::assertFalse(Settings::hasCompatibility()); @@ -114,17 +53,6 @@ public function testSetGetOutputEscapingEnabled(): void self::assertTrue(Settings::isOutputEscapingEnabled()); } - public function testSetGetDefaultRtl(): void - { - self::assertNull(Settings::isDefaultRtl()); - Settings::setDefaultRtl(true); - self::assertTrue(Settings::isDefaultRtl()); - Settings::setDefaultRtl(false); - self::assertFalse(Settings::isDefaultRtl()); - Settings::setDefaultRtl(null); - self::assertNull(Settings::isDefaultRtl()); - } - /** * Test set/get zip class. */ @@ -145,6 +73,7 @@ public function testSetGetZipClass(): void public function testSetGetPdfRenderer(): void { $domPdfPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); + self::assertNotFalse($domPdfPath); self::assertFalse(Settings::setPdfRenderer('FOO', 'dummy/path')); self::assertTrue(Settings::setPdfRenderer(Settings::PDF_RENDERER_DOMPDF, $domPdfPath)); diff --git a/tests/PhpWordTests/Shared/ConverterTest.php b/tests/PhpWordTests/Shared/ConverterTest.php index 36ba797aeb8..c3533ad0cdf 100644 --- a/tests/PhpWordTests/Shared/ConverterTest.php +++ b/tests/PhpWordTests/Shared/ConverterTest.php @@ -19,6 +19,8 @@ namespace PhpOffice\PhpWordTests\Shared; use PhpOffice\PhpWord\Shared\Converter; +use PhpOffice\PhpWord\SimpleType\Color; +use PhpOffice\PhpWord\Style\Font; /** * Test class for PhpOffice\PhpWord\Shared\Converter. @@ -117,7 +119,91 @@ public function testHtmlToRGB(): void self::assertEquals([102, 119, 136], Converter::htmlToRgb('678')); // 3 characters self::assertEquals($flse, Converter::htmlToRgb('0F9D')); // 4 characters self::assertEquals([0, 0, 0], Converter::htmlToRgb('unknow')); // 6 characters, invalid - self::assertEquals([139, 0, 139], Converter::htmlToRgb(\PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKMAGENTA)); // Constant + self::assertEquals([139, 0, 139], Converter::htmlToRgb(Color::DARKMAGENTA)); // Constant + } + + /** + * Test SimpleType::Color. Ensure all colors come out to correct values. + * Verified against https://c-rex.net/samples/ooxml/e1/Part4/OOXML_P4_DOCX_ST_PresetColorVal_topic_ID0ELA5NB.html. + */ + public function testBuiltInColors(): void + { + self::assertSame([0, 255, 255], Converter::htmlToRgb(Color::AQUA)); + self::assertSame([0, 0, 0], Converter::htmlToRgb(Color::BLACK)); + self::assertSame([0, 0, 255], Converter::htmlToRgb(Color::BLUE)); + self::assertSame([165, 42, 42], Converter::htmlToRgb(Color::BROWN)); + self::assertSame([0, 255, 255], Converter::htmlToRgb(Color::CYAN)); + self::assertSame([0, 0, 139], Converter::htmlToRgb(Color::DARKBLUE)); + self::assertSame([0, 139, 139], Converter::htmlToRgb(Color::DARKCYAN)); + self::assertSame([169, 169, 169], Converter::htmlToRgb(Color::DARKGRAY)); + self::assertSame([0, 100, 0], Converter::htmlToRgb(Color::DARKGREEN)); + self::assertSame([139, 0, 139], Converter::htmlToRgb(Color::DARKMAGENTA)); + self::assertSame([255, 140, 0], Converter::htmlToRgb(Color::DARKORANGE)); + self::assertSame([139, 0, 0], Converter::htmlToRgb(Color::DARKRED)); + self::assertSame([148, 0, 211], Converter::htmlToRgb(Color::DARKVIOLET)); + self::assertSame([128, 128, 0], Converter::htmlToRgb(Color::DARKYELLOW)); + self::assertSame([255, 0, 255], Converter::htmlToRgb(Color::FUCHSIA)); + self::assertSame([255, 215, 0], Converter::htmlToRgb(Color::GOLD)); + self::assertSame([128, 128, 128], Converter::htmlToRgb(Color::GRAY)); + self::assertSame([0, 128, 0], Converter::htmlToRgb(Color::GREEN)); + self::assertSame([173, 216, 230], Converter::htmlToRgb(Color::LIGHTBLUE)); + self::assertSame([224, 255, 255], Converter::htmlToRgb(Color::LIGHTCYAN)); + self::assertSame([211, 211, 211], Converter::htmlToRgb(Color::LIGHTGRAY)); + self::assertSame([144, 238, 144], Converter::htmlToRgb(Color::LIGHTGREEN)); + self::assertSame([255, 182, 193], Converter::htmlToRgb(Color::LIGHTPINK)); + self::assertSame([255, 255, 224], Converter::htmlToRgb(Color::LIGHTYELLOW)); + self::assertSame([0, 255, 0], Converter::htmlToRgb(Color::LIME)); + self::assertSame([255, 0, 255], Converter::htmlToRgb(Color::MAGENTA)); + self::assertSame([128, 0, 0], Converter::htmlToRgb(Color::MAROON)); + self::assertSame([0, 0, 128], Converter::htmlToRgb(Color::NAVY)); + self::assertSame([128, 128, 0], Converter::htmlToRgb(Color::OLIVE)); + self::assertSame([255, 165, 0], Converter::htmlToRgb(Color::ORANGE)); + self::assertSame([255, 192, 203], Converter::htmlToRgb(Color::PINK)); + self::assertSame([128, 0, 128], Converter::htmlToRgb(Color::PURPLE)); + self::assertSame([255, 0, 0], Converter::htmlToRgb(Color::RED)); + self::assertSame([192, 192, 192], Converter::htmlToRgb(Color::SILVER)); + self::assertSame([210, 180, 140], Converter::htmlToRgb(Color::TAN)); + self::assertSame([0, 128, 128], Converter::htmlToRgb(Color::TEAL)); + self::assertSame([64, 224, 208], Converter::htmlToRgb(Color::TURQUOISE)); + self::assertSame([238, 130, 238], Converter::htmlToRgb(Color::VIOLET)); + self::assertSame([255, 255, 255], Converter::htmlToRgb(Color::WHITE)); + self::assertSame([255, 255, 0], Converter::htmlToRgb(Color::YELLOW)); + } + + /** + * Make sure deprecated colors are properly represented in non-deprecated. + * + * @dataProvider providerDeprecatedColorNames + */ + public function testDeprecatedColorNames(string $deprecatedName, string $newName): void + { + self::assertSame($deprecatedName, $newName); + } + + public function testDeprecatedColorNamesCount(): void + { + self::assertCount(15, self::providerDeprecatedColorNames()); + } + + public static function providerDeprecatedColorNames(): array + { + return [ + [Font::FGCOLOR_BLACK, Color::BLACK], + [Font::FGCOLOR_BLUE, Color::BLUE], + [Font::FGCOLOR_CYAN, Color::CYAN], + [Font::FGCOLOR_DARKBLUE, Color::DARKBLUE], + [Font::FGCOLOR_DARKCYAN, Color::DARKCYAN], + [Font::FGCOLOR_DARKGRAY, Color::DARKGRAY], + [Font::FGCOLOR_DARKGREEN, Color::DARKGREEN], + [Font::FGCOLOR_DARKMAGENTA, Color::DARKMAGENTA], + [Font::FGCOLOR_DARKRED, Color::DARKRED], + [Font::FGCOLOR_DARKYELLOW, Color::DARKYELLOW], + [Font::FGCOLOR_LIGHTGRAY, Color::LIGHTGRAY], + [Font::FGCOLOR_LIGHTGREEN, Color::LIGHTGREEN], + [Font::FGCOLOR_MAGENTA, Color::MAGENTA], + [Font::FGCOLOR_RED, Color::RED], + [Font::FGCOLOR_YELLOW, Color::YELLOW], + ]; } /** diff --git a/tests/PhpWordTests/Shared/Html2402Test.php b/tests/PhpWordTests/Shared/Html2402Test.php new file mode 100644 index 00000000000..abb2c461cd1 --- /dev/null +++ b/tests/PhpWordTests/Shared/Html2402Test.php @@ -0,0 +1,241 @@ + + + + header a + header b + header c + + + + 12 + This is bold text6 + + +HTML; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, $html, false, false); + $elements = $section->getElements(); + $table = $elements[0]; + self::assertInstanceOf(Table::class, $table); + $style = $table->getStyle(); + self::assertInstanceOf(TableStyle::class, $style); + self::assertSame('none', $style->getBorderBottomStyle()); + $rows = $table->getRows(); + self::assertCount(3, $rows); + $cells = $rows[1]->getCells(); + self::assertCount(2, $cells); + self::assertSame('dotted', $cells[0]->getStyle()->getBorderRightStyle()); + self::assertSame('FF0000', $cells[0]->getStyle()->getBorderRightColor()); + self::assertEmpty($cells[1]->getStyle()->getBorderRightStyle()); + $writer = new HtmlWriter($phpWord); + $content = $writer->getContent(); + $substring = 'table-layout: auto; border-top-style: none; border-top-width: 0pt; border-left-style: none; border-left-width: 0pt; border-bottom-style: none; border-bottom-width: 0pt; border-right-style: none; border-right-width: 0pt;'; + $count = substr_count($content, $substring); + $expected = substr_count($content, 'header c', $content); + } + + public function testParseTableStyleBorderNone(): void + { + $html = << + + + + + + + + + + + +
      header aheader bheader c
      12
      This is bold text6
      +HTML; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, $html, false, false); + $elements = $section->getElements(); + $table = $elements[0]; + self::assertInstanceOf(Table::class, $table); + $style = $table->getStyle(); + self::assertInstanceOf(TableStyle::class, $style); + self::assertSame('none', $style->getBorderBottomStyle()); + $rows = $table->getRows(); + self::assertCount(3, $rows); + $cells = $rows[1]->getCells(); + self::assertCount(2, $cells); + self::assertSame('dotted', $cells[0]->getStyle()->getBorderRightStyle()); + self::assertSame('ff0000', $cells[0]->getStyle()->getBorderRightColor()); + self::assertEmpty($cells[1]->getStyle()->getBorderRightStyle()); + $writer = new HtmlWriter($phpWord); + $content = $writer->getContent(); + $substring = 'table-layout: auto; border-top-style: none; border-left-style: none; border-bottom-style: none; border-right-style: none;'; + $count = substr_count($content, $substring); + $expected = substr_count($content, ' + + + + + + + + + + + +
      header aheader bheader c
      12
      This is bold text6
      +HTML; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, $html, false, false); + $elements = $section->getElements(); + $table = $elements[0]; + self::assertInstanceOf(Table::class, $table); + $style = $table->getStyle(); + self::assertInstanceOf(TableStyle::class, $style); + self::assertSame('none', $style->getBorderBottomStyle()); + $rows = $table->getRows(); + self::assertCount(3, $rows); + $cells = $rows[1]->getCells(); + self::assertCount(2, $cells); + self::assertSame('dotted', $cells[0]->getStyle()->getBorderRightStyle()); + self::assertSame('ff0000', $cells[0]->getStyle()->getBorderRightColor()); + self::assertEmpty($cells[1]->getStyle()->getBorderRightStyle()); + } + + public function testParseTableStyleBorder2px(): void + { + $html = << + + + header a + header b + header c + + + + 12 + This is bold text6 + + +HTML; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, $html, false, false); + $elements = $section->getElements(); + $table = $elements[0]; + self::assertInstanceOf(Table::class, $table); + $style = $table->getStyle(); + self::assertInstanceOf(TableStyle::class, $style); + self::assertSame('dashed', $style->getBorderBottomStyle()); + self::assertSame('dashed', $style->getBorderInsideHStyle()); + self::assertSame('dashed', $style->getBorderInsideVStyle()); + self::assertSame(15, $style->getBorderBottomSize()); + self::assertSame('00ff00', $style->getBorderBottomColor()); + $rows = $table->getRows(); + self::assertCount(3, $rows); + $cells = $rows[1]->getCells(); + self::assertCount(2, $cells); + self::assertSame('dotted', $cells[0]->getStyle()->getBorderRightStyle()); + self::assertSame('ff0000', $cells[0]->getStyle()->getBorderRightColor()); + self::assertEmpty($cells[1]->getStyle()->getBorderRightStyle()); + $writer = new HtmlWriter($phpWord); + $content = $writer->getContent(); + + $substring = 'table-layout: auto; border-top-style: dashed; border-top-color: #00ff00; border-top-width: 0.75pt; border-left-style: dashed; border-left-color: #00ff00; border-left-width: 0.75pt; border-bottom-style: dashed; border-bottom-color: #00ff00; border-bottom-width: 0.75pt; border-right-style: dashed; border-right-color: #00ff00; border-right-width: 0.75pt;'; + $count = substr_count($content, $substring); + $expected = substr_count($content, ' + + + + + + + + + + + +
      header aheader bheader c
      12
      This is bold text6
      +HTML; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, $html, false, false); + $elements = $section->getElements(); + $table = $elements[0]; + self::assertInstanceOf(Table::class, $table); + $style = $table->getStyle(); + self::assertInstanceOf(TableStyle::class, $style); + self::assertNull($style->getBorderBottomStyle()); + self::assertSame('', $style->getBorderInsideHStyle()); + self::assertSame('', $style->getBorderInsideVStyle()); + self::assertNull($style->getBorderBottomSize()); + self::assertNull($style->getBorderBottomColor()); + } +} diff --git a/tests/PhpWordTests/Shared/Html2Test.php b/tests/PhpWordTests/Shared/Html2Test.php new file mode 100644 index 00000000000..64a71932dd1 --- /dev/null +++ b/tests/PhpWordTests/Shared/Html2Test.php @@ -0,0 +1,204 @@ +expectException(Exception::class); + $this->expectExceptionMessage('loadHTML'); + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, ''); + } + + public function testCssOnIdElement(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '' + . '' + . 'Id Test' + . '' + . '' + . '

      test1.

      ' + . ''; + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord); + $marginPath = '/w:document/w:body/w:p/w:pPr/w:spacing'; + self::assertSame('150', $doc->getElement($marginPath)->getAttribute('w:before')); + self::assertSame('150', $doc->getElement($marginPath)->getAttribute('w:after')); + $path = '/w:document/w:body/w:p/w:r'; + self::assertSame('test1.', $doc->getElement($path)->nodeValue); + $boldPath = $path . '/w:rPr/w:b'; + self::assertSame('1', $doc->getElement($boldPath)->getAttribute('w:val')); + } + + public function testListTypes(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '
      1. Decimal number first
      2. second
      ' + . '
      1. Lowercase first
      2. second
      ' + . '
      1. Uppercase first
      2. second
      ' + . '
      1. Lower roman first
      2. second
      ' + . '
      1. Upper roman first
      2. second
      '; + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord); + + $item = 1; + $expected = '1'; + $path = "/w:document/w:body/w:p[$item]"; + self::assertSame('Decimal number first', $doc->getElement("$path/w:r")->nodeValue); + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + ++$item; + $path = "/w:document/w:body/w:p[$item]"; + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + + ++$item; + $expected = '2'; + $path = "/w:document/w:body/w:p[$item]"; + self::assertSame('Lowercase first', $doc->getElement("$path/w:r")->nodeValue); + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + ++$item; + $path = "/w:document/w:body/w:p[$item]"; + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + + ++$item; + $expected = '3'; + $path = "/w:document/w:body/w:p[$item]"; + self::assertSame('Uppercase first', $doc->getElement("$path/w:r")->nodeValue); + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + ++$item; + $path = "/w:document/w:body/w:p[$item]"; + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + + ++$item; + $expected = '4'; + $path = "/w:document/w:body/w:p[$item]"; + self::assertSame('Lower roman first', $doc->getElement("$path/w:r")->nodeValue); + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + ++$item; + $path = "/w:document/w:body/w:p[$item]"; + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + + ++$item; + $expected = '5'; + $path = "/w:document/w:body/w:p[$item]"; + self::assertSame('Upper roman first', $doc->getElement("$path/w:r")->nodeValue); + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + ++$item; + $path = "/w:document/w:body/w:p[$item]"; + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + } + + public function testPadding(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '' + . '' + . '' + . '' + . '' + . '' + . '' + . '
      2020 30
      20 30 4020 30 40 50
      '; + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord); + + $item = 1; + $td = 1; + $path = "/w:document/w:body/w:tbl/w:tr[$item]/w:tc[$td]"; + self::assertSame('20', $doc->getElement("$path/w:p/w:r")->nodeValue); + $tcMarPath = $path . '/w:tcPr/w:tcMar'; + self::assertSame('300', $doc->getElement($tcMarPath . '/w:top')->getAttribute('w:w')); + self::assertSame('300', $doc->getElement($tcMarPath . '/w:start')->getAttribute('w:w')); + self::assertSame('300', $doc->getElement($tcMarPath . '/w:bottom')->getAttribute('w:w')); + self::assertSame('300', $doc->getElement($tcMarPath . '/w:end')->getAttribute('w:w')); + + ++$td; + $path = "/w:document/w:body/w:tbl/w:tr[$item]/w:tc[$td]"; + self::assertSame('20 30', $doc->getElement("$path/w:p/w:r")->nodeValue); + $tcMarPath = $path . '/w:tcPr/w:tcMar'; + self::assertSame('300', $doc->getElement($tcMarPath . '/w:top')->getAttribute('w:w')); + self::assertSame('450', $doc->getElement($tcMarPath . '/w:start')->getAttribute('w:w')); + self::assertSame('300', $doc->getElement($tcMarPath . '/w:bottom')->getAttribute('w:w')); + self::assertSame('450', $doc->getElement($tcMarPath . '/w:end')->getAttribute('w:w')); + + $item = 1; + $td = 1; + $path = "/w:document/w:body/w:tbl/w:tr[$item]/w:tc[$td]"; + self::assertSame('20', $doc->getElement("$path/w:p/w:r")->nodeValue); + $tcMarPath = $path . '/w:tcPr/w:tcMar'; + self::assertSame('300', $doc->getElement($tcMarPath . '/w:top')->getAttribute('w:w')); + self::assertSame('300', $doc->getElement($tcMarPath . '/w:start')->getAttribute('w:w')); + self::assertSame('300', $doc->getElement($tcMarPath . '/w:bottom')->getAttribute('w:w')); + self::assertSame('300', $doc->getElement($tcMarPath . '/w:end')->getAttribute('w:w')); + + ++$item; + $td = 1; + $path = "/w:document/w:body/w:tbl/w:tr[$item]/w:tc[$td]"; + self::assertSame('20 30 40', $doc->getElement("$path/w:p/w:r")->nodeValue); + $tcMarPath = $path . '/w:tcPr/w:tcMar'; + self::assertSame('300', $doc->getElement($tcMarPath . '/w:top')->getAttribute('w:w')); + self::assertSame('450', $doc->getElement($tcMarPath . '/w:start')->getAttribute('w:w')); + self::assertSame('600', $doc->getElement($tcMarPath . '/w:bottom')->getAttribute('w:w')); + self::assertSame('450', $doc->getElement($tcMarPath . '/w:end')->getAttribute('w:w')); + + ++$td; + $path = "/w:document/w:body/w:tbl/w:tr[$item]/w:tc[$td]"; + self::assertSame('20 30 40 50', $doc->getElement("$path/w:p/w:r")->nodeValue); + $tcMarPath = $path . '/w:tcPr/w:tcMar'; + self::assertSame('300', $doc->getElement($tcMarPath . '/w:top')->getAttribute('w:w')); + self::assertSame('750', $doc->getElement($tcMarPath . '/w:start')->getAttribute('w:w')); + self::assertSame('600', $doc->getElement($tcMarPath . '/w:bottom')->getAttribute('w:w')); + self::assertSame('450', $doc->getElement($tcMarPath . '/w:end')->getAttribute('w:w')); + } +} diff --git a/tests/PhpWordTests/Shared/HtmlFullTest.php b/tests/PhpWordTests/Shared/HtmlFullTest.php new file mode 100644 index 00000000000..53ba093184e --- /dev/null +++ b/tests/PhpWordTests/Shared/HtmlFullTest.php @@ -0,0 +1,94 @@ +addSection(); + $htmlContent = << + + + +Testing Head Section + + + + + + +

      This is bold text.

      + + +EOF; + Html::addHtml($section, $htmlContent, true, true); + self::assertSame('Testing Head Section', $phpWord->getDocInfo()->getTitle()); + self::assertSame('PhpWord Test', $phpWord->getDocInfo()->getCreator()); + self::assertSame('testing html read including meta tags', $phpWord->getDocInfo()->getDescription()); + $elements = $section->getElements(); + self::assertCount(1, $elements); + $element = $elements[0]; + self::assertInstanceOf(TextRun::class, $element); + $textElements = $element->getElements(); + self::assertCount(3, $textElements); + + $textElement = $textElements[0]; + self::assertInstanceOf(Text::class, $textElement); + $style = $textElement->getFontStyle(); + self::assertInstanceOf(Font::class, $style); + self::assertNotTrue($style->isBold()); + self::assertSame('This is ', $textElement->getText()); + + $textElement = $textElements[1]; + self::assertInstanceOf(Text::class, $textElement); + $style = $textElement->getFontStyle(); + self::assertInstanceOf(Font::class, $style); + self::assertTrue($style->isBold()); + self::assertSame('bold', $textElement->getText()); + + $textElement = $textElements[2]; + self::assertInstanceOf(Text::class, $textElement); + $style = $textElement->getFontStyle(); + self::assertInstanceOf(Font::class, $style); + self::assertNotTrue($style->isBold()); + self::assertSame(' text.', $textElement->getText()); + } +} diff --git a/tests/PhpWordTests/Shared/HtmlHeadingsTest.php b/tests/PhpWordTests/Shared/HtmlHeadingsTest.php new file mode 100644 index 00000000000..331935fbaec --- /dev/null +++ b/tests/PhpWordTests/Shared/HtmlHeadingsTest.php @@ -0,0 +1,76 @@ +addTitleStyle(1, ['size' => 20]); + $section = $originalDoc->addSection(); + $expectedStrings = []; + $section->addTitle('Title 1', 1); + $expectedStrings[] = '

      Title 1

      '; + for ($i = 2; $i <= 6; ++$i) { + $textRun = new TextRun(); + $textRun->addText('Title '); + $textRun->addText("$i", ['italic' => true]); + $section->addTitle($textRun, $i); + $expectedStrings[] = "Title $i"; + } + $writer = new HtmlWriter($originalDoc); + $content = $writer->getContent(); + foreach ($expectedStrings as $expectedString) { + self::assertStringContainsString($expectedString, $content); + } + + $newDoc = new PhpWord(); + $newSection = $newDoc->addSection(); + SharedHtml::addHtml($newSection, $content, true); + $newWriter = new HtmlWriter($newDoc); + $newContent = $newWriter->getContent(); + // Reader does not yet support h1 declaration in css. + $content = str_replace('h1 {font-size: 20pt;}' . PHP_EOL, '', $content); + + // Reader transforms Text to TextRun, + // but result is functionally the same. + self::assertSame( + $newContent, + str_replace( + '

      Title 1

      ', + '

      Title 1

      ', + $content + ) + ); + } +} diff --git a/tests/PhpWordTests/Shared/HtmlRtlTest.php b/tests/PhpWordTests/Shared/HtmlRtlTest.php new file mode 100644 index 00000000000..918c1cd6af1 --- /dev/null +++ b/tests/PhpWordTests/Shared/HtmlRtlTest.php @@ -0,0 +1,181 @@ +addSection(); + $html = '

      test1.

      '; + $html .= '

      test2.

      '; + $html .= '

      test3.

      '; + Html::addHtml($section, $html); + $elements = $section->getElements(); + self::assertCount(3, $elements); + + $index = 0; + $element = $elements[$index]; + self::assertInstanceOf(TextRun::class, $element); + $paragraphStyle = $element->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + self::assertTrue($paragraphStyle->isBidi()); + self::assertSame('tbRl', $paragraphStyle->getTextDirection()); + $textElements = $element->getElements(); + self::assertCount(1, $textElements); + $textElement = $textElements[0]; + self::assertInstanceOf(Text::class, $textElement); + self::assertInstanceOf(Font::class, $textElement->getFontStyle()); + self::assertTrue($textElement->getFontStyle()->isRtl()); + + $index = 1; + $element = $elements[$index]; + self::assertInstanceOf(TextRun::class, $element); + $paragraphStyle = $element->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + self::assertFalse($paragraphStyle->isBidi()); + self::assertSame('lrTb', $paragraphStyle->getTextDirection()); + $textElements = $element->getElements(); + self::assertCount(1, $textElements); + $textElement = $textElements[0]; + self::assertInstanceOf(Text::class, $textElement); + self::assertInstanceOf(Font::class, $textElement->getFontStyle()); + self::assertFalse($textElement->getFontStyle()->isRtl()); + + $index = 2; + $element = $elements[$index]; + self::assertInstanceOf(TextRun::class, $element); + $paragraphStyle = $element->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + self::assertNull($paragraphStyle->isBidi()); + self::assertSame('', $paragraphStyle->getTextDirection()); + $textElements = $element->getElements(); + self::assertCount(1, $textElements); + $textElement = $textElements[0]; + self::assertInstanceOf(Text::class, $textElement); + self::assertInstanceOf(Font::class, $textElement->getFontStyle()); + self::assertNull($textElement->getFontStyle()->isRtl()); + } + + public function testParseHtmlDir(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

      test1.

      '; + $html .= '

      test2.

      '; + $html .= '

      test3.

      '; + Html::addHtml($section, $html); + $elements = $section->getElements(); + self::assertCount(3, $elements); + + $index = 0; + $element = $elements[$index]; + self::assertInstanceOf(TextRun::class, $element); + $paragraphStyle = $element->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + self::assertTrue($paragraphStyle->isBidi()); + self::assertSame('tbRl', $paragraphStyle->getTextDirection()); + $textElements = $element->getElements(); + self::assertCount(1, $textElements); + $textElement = $textElements[0]; + self::assertInstanceOf(Text::class, $textElement); + self::assertInstanceOf(Font::class, $textElement->getFontStyle()); + self::assertTrue($textElement->getFontStyle()->isRtl()); + + $index = 1; + $element = $elements[$index]; + self::assertInstanceOf(TextRun::class, $element); + $paragraphStyle = $element->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + self::assertFalse($paragraphStyle->isBidi()); + self::assertSame('lrTb', $paragraphStyle->getTextDirection()); + $textElements = $element->getElements(); + self::assertCount(1, $textElements); + $textElement = $textElements[0]; + self::assertInstanceOf(Text::class, $textElement); + self::assertInstanceOf(Font::class, $textElement->getFontStyle()); + self::assertFalse($textElement->getFontStyle()->isRtl()); + + $index = 2; + $element = $elements[$index]; + self::assertInstanceOf(TextRun::class, $element); + $paragraphStyle = $element->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + self::assertNull($paragraphStyle->isBidi()); + self::assertSame('', $paragraphStyle->getTextDirection()); + $textElements = $element->getElements(); + self::assertCount(1, $textElements); + $textElement = $textElements[0]; + self::assertInstanceOf(Text::class, $textElement); + self::assertInstanceOf(Font::class, $textElement->getFontStyle()); + self::assertNull($textElement->getFontStyle()->isRtl()); + } + + public function testCssClassNameOnPElement(): void + { + $phpWord = new PhpWord(); + $phpWord->addFontStyle('customClass', ['bold' => true], ['borderBottomSize' => 3, 'borderBottomColor' => '#00ff00', 'textDirection' => 'tbRl']); + $section = $phpWord->addSection(); + $html = '

      test1.

      '; + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord); + $path = '/w:document/w:body/w:p'; + $paragraphPath = $path . '/w:pPr'; + $element = $doc->getElement($paragraphPath . '/w:pStyle'); + self::assertSame('customClass', $element->getAttribute('w:val')); + $textPath = $path . '/w:r/w:t'; + self::assertSame('test1.', $doc->getElement($textPath)->nodeValue); + self::assertSame('customClass', $doc->getElement($path . '/w:r/w:rPr/w:rStyle')->getAttribute('w:val')); + + // Styles + $file = 'word/styles.xml'; + $path = '/w:styles/w:style[@w:styleId="customClass"]'; + $paragraphPath = $path . '/w:pPr'; + $element = $doc->getElement($paragraphPath . '/w:pBdr/w:bottom', $file); + self::assertSame('#00ff00', $element->getAttribute('w:color')); + $element = $doc->getElement($paragraphPath . '/w:textDirection', $file); + self::assertSame('tbRl', $element->getAttribute('w:val')); + $fontPath = $path . '/w:rPr'; + $element = $doc->getElement($fontPath . '/w:b', $file); + self::assertSame('1', $element->getAttribute('w:val')); + } +} diff --git a/tests/PhpWordTests/Shared/HtmlTest.php b/tests/PhpWordTests/Shared/HtmlTest.php index 42d8aa598bd..2a8705b4832 100644 --- a/tests/PhpWordTests/Shared/HtmlTest.php +++ b/tests/PhpWordTests/Shared/HtmlTest.php @@ -24,12 +24,15 @@ use PhpOffice\PhpWord\Element\Table; use PhpOffice\PhpWord\Element\Text; use PhpOffice\PhpWord\Element\TextRun; +use PhpOffice\PhpWord\Element\Title; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\Shared\Html; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\LineSpacingRule; use PhpOffice\PhpWord\SimpleType\TblWidth; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; use PhpOffice\PhpWordTests\AbstractWebServerEmbedded; @@ -55,6 +58,7 @@ protected function tearDown(): void */ public function testAddHtml(): void { + Settings::setOutputEscapingEnabled(true); $content = ''; // Default @@ -83,16 +87,31 @@ public function testAddHtml(): void // Other parts $section = $phpWord->addSection(); $content = ''; + $expectd = ''; $content .= '
      HeaderContent
      '; $content .= '
      • Bullet
        • Bullet
      '; $content .= '
      1. Bullet
      '; $content .= "'Single Quoted Text'"; + $expectd .= "'Single Quoted Text'"; $content .= '"Double Quoted Text"'; + $expectd .= '"Double Quoted Text"'; $content .= '& Ampersand'; - $content .= '<>“‘’«»‹›'; + $expectd .= '& Ampersand'; + $content .= '<>“”‘’«»‹›'; + $expectd .= '<>“”‘’«»‹›'; $content .= '&•°…™©®—'; + $expectd .= '&•°…™©®—'; $content .= '–   ²³¼½¾'; + $expectd .= "–\u{a0}\u{2003}\u{2002}²³¼½¾"; Html::addHtml($section, $content); + $elements = $section->getElements(); + foreach ($elements as $element) { + if ($element instanceof Text) { + self::assertSame($expectd, $element->getText()); + + break; + } + } } /** @@ -116,6 +135,8 @@ public function testParseHeader(): void self::assertCount(1, $section->getElements()); $element = $section->getElement(0); + self::assertInstanceOf(Title::class, $element); + $element = $element->getText(); self::assertInstanceOf(TextRun::class, $element); self::assertInstanceOf(Paragraph::class, $element->getParagraphStyle()); self::assertEquals('Heading1', $element->getParagraphStyle()->getStyleName()); @@ -137,6 +158,8 @@ public function testParseHeaderStyle(): void self::assertCount(1, $section->getElements()); $element = $section->getElement(0); + self::assertInstanceOf(Title::class, $element); + $element = $element->getText(); self::assertInstanceOf(TextRun::class, $element); self::assertInstanceOf(Paragraph::class, $element->getParagraphStyle()); self::assertEquals('Heading1', $element->getParagraphStyle()->getStyleName()); @@ -155,7 +178,7 @@ public function testParseHeaderStyle(): void */ public function testParseHtmlEntities(): void { - \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); + Settings::setOutputEscapingEnabled(true); $phpWord = new PhpWord(); $section = $phpWord->addSection(); Html::addHtml($section, 'text with entities <my text>'); @@ -183,13 +206,14 @@ public function testParseStyle(): void 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')); + $element = '/w:document/w:body/w:p'; + self::assertTrue($doc->elementExists($element)); + self::assertTrue($doc->elementExists("$element/w:r")); + self::assertTrue($doc->elementExists("$element/w:r/w:t")); + self::assertEquals('Calculator', $doc->getElement("$element/w:r/w:t")->nodeValue); + self::assertTrue($doc->elementExists("$element/w:r/w:rPr")); + self::assertTrue($doc->elementExists("$element/w:r/w:rPr/w:sz")); + self::assertEquals('22.5', $doc->getElementAttribute("$element/w:r/w:rPr/w:sz", 'w:val')); } public function testParseStyleTableClassName(): void @@ -203,6 +227,36 @@ public function testParseStyleTableClassName(): void self::assertEquals('pStyle', $section->getElement(0)->getStyle()->getStyleName()); } + public function testSpanClassName(): void + { + $phpWord = new PhpWord(); + $phpWord->addFontStyle('boldtext', ['bold' => true]); + $html = '

      This is bold text.

      '; + $section = $phpWord->addSection(); + Html::addHtml($section, $html); + $element = $section->getElements()[0]; + self::assertInstanceOf(TextRun::class, $element); + $textElements = $element->getElements(); + self::assertCount(3, $textElements); + + $text = $textElements[0]; + self::assertInstanceOf(Text::class, $text); + self::assertInstanceOf(Font::class, $text->getFontStyle()); + self::assertNotTrue($text->getFontStyle()->isBold()); + + $text = $textElements[1]; + self::assertInstanceOf(Text::class, $text); + self::assertSame('boldtext', $text->getFontStyle()); + $style = Style::getStyle('boldtext'); + self::assertInstanceOf(Font::class, $style); + self::assertTrue($style->isBold()); + + $text = $textElements[2]; + self::assertInstanceOf(Text::class, $text); + self::assertInstanceOf(Font::class, $text->getFontStyle()); + self::assertNotTrue($text->getFontStyle()->isBold()); + } + /** * Test text-decoration style. */ @@ -248,7 +302,8 @@ public function testParseWidth(string $htmlSize, float $docxSize, string $docxUn $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); $xpath = '/w:document/w:body/w:tbl/w:tblPr/w:tblW'; self::assertTrue($doc->elementExists($xpath)); - self::assertEquals($docxSize, $doc->getElement($xpath)->getAttribute('w:w')); + $actual = (float) $doc->getElement($xpath)->getAttribute('w:w'); + self::assertEqualsWithDelta($docxSize, $actual, 1.0e-12); self::assertEquals($docxUnit, $doc->getElement($xpath)->getAttribute('w:type')); } @@ -558,7 +613,7 @@ public function testParseTableAndCellWidth(): void { $phpWord = new PhpWord(); $section = $phpWord->addSection([ - 'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE, + 'orientation' => Style\Section::ORIENTATION_LANDSCAPE, ]); // borders & backgrounds are here just for better visual comparison @@ -627,7 +682,7 @@ public function testParseTableRowHeight(): void { $phpWord = new PhpWord(); $section = $phpWord->addSection([ - 'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE, + 'orientation' => Style\Section::ORIENTATION_LANDSCAPE, ]); $html = <<addSection([ - 'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE, + 'orientation' => Style\Section::ORIENTATION_LANDSCAPE, ]); // borders & backgrounds are here just for better visual comparison @@ -749,7 +804,7 @@ public function testParseTableStyleAttributeInlineStyle(): void { $phpWord = new PhpWord(); $section = $phpWord->addSection([ - 'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE, + 'orientation' => Style\Section::ORIENTATION_LANDSCAPE, ]); $html = ' @@ -768,7 +823,7 @@ public function testParseTableStyleAttributeInlineStyle(): void $xpath = '/w:document/w:body/w:tbl/w:tr[1]/w:tc[1]/w:tcPr/w:shd'; self::assertTrue($doc->elementExists($xpath)); - self::assertEquals('red', $doc->getElement($xpath)->getAttribute('w:fill')); + self::assertEquals('ff0000', $doc->getElement($xpath)->getAttribute('w:fill')); } /** @@ -876,7 +931,7 @@ public function testParseListWithFormat(): void { $phpWord = new PhpWord(); $section = $phpWord->addSection(); - $html = preg_replace('/\s+/', ' ', '
        + $html = '
        • Some text before list item1 bold with text after bold @@ -888,7 +943,7 @@ public function testParseListWithFormat(): void list item2
        • -
        '); +
      '; Html::addHtml($section, $html, false, false); $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); @@ -1230,7 +1285,7 @@ public function testParseHorizontalRule(): void self::assertTrue($doc->elementExists($xpath)); self::assertEquals('single', $doc->getElement($xpath)->getAttribute('w:val')); self::assertEquals((int) (5 * 15 / 2), $doc->getElement($xpath)->getAttribute('w:sz')); - self::assertEquals('lightblue', $doc->getElement($xpath)->getAttribute('w:color')); + self::assertEquals('add8e6', $doc->getElement($xpath)->getAttribute('w:color')); $xpath = '/w:document/w:body/w:p[4]/w:pPr/w:spacing'; self::assertTrue($doc->elementExists($xpath)); @@ -1363,9 +1418,11 @@ public static function providerParseWidth(): array return [ ['auto', 5000, TblWidth::PERCENT], ['100%', 5000, TblWidth::PERCENT], - ['200pt', 3999.999999999999, TblWidth::TWIP], + ['200pt', 4000, TblWidth::TWIP], ['300px', 4500, TblWidth::TWIP], ['400', 6000, TblWidth::TWIP], + ['2in', 2880, TblWidth::TWIP], + ['2.54cm', 1440, TblWidth::TWIP], ]; } diff --git a/tests/PhpWordTests/Shared/Microsoft/PasswordEncoder2.php b/tests/PhpWordTests/Shared/Microsoft/PasswordEncoder2.php new file mode 100644 index 00000000000..a80e0b98237 --- /dev/null +++ b/tests/PhpWordTests/Shared/Microsoft/PasswordEncoder2.php @@ -0,0 +1,37 @@ +expectException(WordException::class); + $this->expectExceptionMessage('Failed to convert password to UCS-2LE'); + $password = 'hello'; + $salt = base64_decode('uq81pJRRGFIY5U+E9gt8tA=='); + PasswordEncoder2::hashPassword($password, PasswordEncoder::ALGORITHM_MAC, $salt, 1); + } } diff --git a/tests/PhpWordTests/Shared/Text2.php b/tests/PhpWordTests/Shared/Text2.php new file mode 100644 index 00000000000..2d59a2a2ab5 --- /dev/null +++ b/tests/PhpWordTests/Shared/Text2.php @@ -0,0 +1,32 @@ +expectException(WordException::class); + $this->expectExceptionMessage('Unable to convert text to UTF-8'); + Text2::toUTF8("\x80"); + } } diff --git a/tests/PhpWordTests/Shared/XMLReaderTest.php b/tests/PhpWordTests/Shared/XMLReaderTest.php index 18750b5ef90..977c9235e66 100644 --- a/tests/PhpWordTests/Shared/XMLReaderTest.php +++ b/tests/PhpWordTests/Shared/XMLReaderTest.php @@ -137,19 +137,12 @@ public function testReturnNullOnNonExistingNode(): void /** * Test that xpath fails if custom namespace is not registered. */ - public function testShouldThrowExceptionIfNamespaceIsNotKnown(): void + public function testShouldReturnFalseIfNamespaceIsNotKnown(): void { - try { - $reader = new XMLReader(); - $reader->getDomFromString('AAA'); + $reader = new XMLReader(); + $reader->getDomFromString('AAA'); - self::assertTrue($reader->elementExists('/element/test:child')); - self::assertEquals('AAA', $reader->getElement('/element/test:child')->textContent); - self::fail(); - } catch (Exception $e) { - // @phpstan-ignore-next-line - self::assertTrue(true); - } + self::assertFalse($reader->elementExists('/element/test:child')); } /** diff --git a/tests/PhpWordTests/Shared/XMLWriterTest.php b/tests/PhpWordTests/Shared/XMLWriterTest.php index 476b8b3ba94..dbb47edc147 100644 --- a/tests/PhpWordTests/Shared/XMLWriterTest.php +++ b/tests/PhpWordTests/Shared/XMLWriterTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWordTests\Shared; +use PhpOffice\PhpWord\Exception\Exception as WordException; use PhpOffice\PhpWord\Shared\XMLWriter; /** @@ -71,4 +72,12 @@ public function testWriteAttributeShouldWriteFloatValueLocaleIndependent(): void setlocale(LC_NUMERIC, $currentLocale); } + + public function testNoUnserialize(): void + { + $this->expectException(WordException::class); + $this->expectExceptionMessage('Unserialize not permitted'); + $xmlWriter = new XMLWriter(); + unserialize(serialize($xmlWriter)); + } } diff --git a/tests/PhpWordTests/Shared/ZipArchiveTest.php b/tests/PhpWordTests/Shared/ZipArchiveTest.php index 3f998c26ef9..9e285f0e4c9 100644 --- a/tests/PhpWordTests/Shared/ZipArchiveTest.php +++ b/tests/PhpWordTests/Shared/ZipArchiveTest.php @@ -25,39 +25,39 @@ * Test class for PhpOffice\PhpWord\Shared\ZipArchive. * * @coversDefaultClass \PhpOffice\PhpWord\Shared\ZipArchive - * - * @runTestsInSeparateProcesses */ class ZipArchiveTest extends \PHPUnit\Framework\TestCase { -// /** -// * Test close method exception: Working in local, not working in Travis -// * -// * expectedException \PhpOffice\PhpWord\Exception\Exception -// * expectedExceptionMessage Could not close zip file -// * covers ::close -// */ -// public function testCloseException() -// { -// $zipFile = __DIR__ . "/../_files/documents/ziptest.zip"; - -// $object = new ZipArchive(); -// $object->open($zipFile, ZipArchive::CREATE); -// $object->addFromString('content/string.txt', 'Test'); - -// // Lock the file -// $resource = fopen($zipFile, "w"); -// flock($resource, LOCK_EX); - -// // Closing the file should throws an exception -// $object->close(); - -// // Unlock the file -// flock($resource, LOCK_UN); -// fclose($resource); - -// @unlink($zipFile); -// } + /** @var string */ + private $zipFile; + + protected function tearDown(): void + { + if (file_exists($this->zipFile)) { + unlink($this->zipFile); + } + Settings::restoreDefaults(); + } + + /** + * Test close method exception. + * + * covers ::close + */ + public function testCloseException(): void + { + $this->zipFile = __DIR__ . '/../_files/documents/ziptest1.zip'; + $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectExceptionMessage('Could not close zip file'); + + $object = new ZipArchive(); + $object->open($this->zipFile, ZipArchive::CREATE); + $object->addFromString('content/string.txt', 'Test'); + // Should not be able to add a directory like this + $object->addFile('.', '.'); + // But error doesn't express itself till close is attempted + $object->close(); + } /** * Test all methods. @@ -68,7 +68,7 @@ public function testZipArchive($zipClass = 'ZipArchive'): void { // Preparation $existingFile = __DIR__ . '/../_files/documents/sheet.xls'; - $zipFile = __DIR__ . '/../_files/documents/ziptest.zip'; + $this->zipFile = __DIR__ . '/../_files/documents/ziptest2.zip'; $destination1 = __DIR__ . '/../_files/documents/extract1'; $destination2 = __DIR__ . '/../_files/documents/extract2'; @mkdir($destination1); @@ -77,11 +77,11 @@ public function testZipArchive($zipClass = 'ZipArchive'): void Settings::setZipClass($zipClass); $object = new ZipArchive(); - $object->open($zipFile, ZipArchive::CREATE); + $object->open($this->zipFile, ZipArchive::CREATE); $object->addFile($existingFile, 'xls/new.xls'); $object->addFromString('content/string.txt', 'Test'); $object->close(); - $object->open($zipFile); + $object->open($this->zipFile); // Run tests self::assertEquals(0, $object->locateName('xls/new.xls')); @@ -101,7 +101,6 @@ public function testZipArchive($zipClass = 'ZipArchive'): void // Cleanup $this->deleteDir($destination1); $this->deleteDir($destination2); - @unlink($zipFile); } /** diff --git a/tests/PhpWordTests/Style/AbstractStyleClass.php b/tests/PhpWordTests/Style/AbstractStyleClass.php new file mode 100644 index 00000000000..91c576c338b --- /dev/null +++ b/tests/PhpWordTests/Style/AbstractStyleClass.php @@ -0,0 +1,28 @@ +getMockForAbstractClass(AbstractStyle::class); - } else { - /** @var AbstractStyle $stub */ - $stub = new class() extends AbstractStyle { - }; - } + $stub = new AbstractStyleClass(); $stub->setStyleByArray(['index' => 1]); self::assertEquals(1, $stub->getIndex()); @@ -70,15 +60,7 @@ public function testSetStyleByArrayWithAlignment(): void */ public function testSetValNormal(): void { - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $stub = $this->getMockForAbstractClass(AbstractStyle::class); - } else { - /** @var AbstractStyle $stub */ - $stub = new class() extends AbstractStyle { - }; - } - + $stub = new AbstractStyleClass(); self::assertTrue(self::callProtectedMethod($stub, 'setBoolVal', [true, false])); self::assertEquals(12, self::callProtectedMethod($stub, 'setIntVal', [12, 200])); self::assertEquals(871.1, self::callProtectedMethod($stub, 'setFloatVal', [871.1, 2.1])); @@ -91,15 +73,7 @@ public function testSetValNormal(): void */ public function testSetValDefault(): void { - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $stub = $this->getMockForAbstractClass(AbstractStyle::class); - } else { - /** @var AbstractStyle $stub */ - $stub = new class() extends AbstractStyle { - }; - } - + $stub = new AbstractStyleClass(); self::assertNotTrue(self::callProtectedMethod($stub, 'setBoolVal', ['a', false])); self::assertEquals(200, self::callProtectedMethod($stub, 'setIntVal', ['foo', 200])); self::assertEquals(2.1, self::callProtectedMethod($stub, 'setFloatVal', ['foo', 2.1])); @@ -112,29 +86,28 @@ public function testSetValDefault(): void public function testSetValEnumException(): void { $this->expectException(InvalidArgumentException::class); - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $stub = $this->getMockForAbstractClass(AbstractStyle::class); - } else { - /** @var AbstractStyle $stub */ - $stub = new class() extends AbstractStyle { - }; - } - + $stub = new AbstractStyleClass(); self::assertEquals('b', self::callProtectedMethod($stub, 'setEnumVal', ['z', ['a', 'b'], 'b'])); } + /** @var int */ + protected static $temporaryVersionCheck = 80500; + /** * Helper function to call protected method. * * @param mixed $object * @param string $method + * + * @return mixed */ public static function callProtectedMethod($object, $method, array $args = []) { $class = new ReflectionClass(get_class($object)); $method = $class->getMethod($method); - $method->setAccessible(true); + if (PHP_VERSION_ID < self::$temporaryVersionCheck) { + $method->setAccessible(true); + } return $method->invokeArgs($object, $args); } diff --git a/tests/PhpWordTests/Style/CellTest.php b/tests/PhpWordTests/Style/CellTest.php index 56b099c05f5..847ed484bfb 100644 --- a/tests/PhpWordTests/Style/CellTest.php +++ b/tests/PhpWordTests/Style/CellTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Style\Cell. * * @coversDefaultClass \PhpOffice\PhpWord\Style\Cell - * - * @runTestsInSeparateProcesses */ class CellTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/ChartTest.php b/tests/PhpWordTests/Style/ChartTest.php index e8e75901d76..8390bd53c7e 100644 --- a/tests/PhpWordTests/Style/ChartTest.php +++ b/tests/PhpWordTests/Style/ChartTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Style\Chart. * * @coversDefaultClass \PhpOffice\PhpWord\Style\Chart - * - * @runTestsInSeparateProcesses */ class ChartTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/FontTest.php b/tests/PhpWordTests/Style/FontTest.php index 4ba6a762f3a..607021ef8cb 100644 --- a/tests/PhpWordTests/Style/FontTest.php +++ b/tests/PhpWordTests/Style/FontTest.php @@ -20,6 +20,7 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\SimpleType\Color; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Language; @@ -27,8 +28,6 @@ /** * Test class for PhpOffice\PhpWord\Style\Font. - * - * @runTestsInSeparateProcesses */ class FontTest extends \PHPUnit\Framework\TestCase { @@ -85,7 +84,9 @@ public function testSetStyleValueWithNullOrEmpty(): void 'fallbackFont' => '', ]; foreach ($attributes as $key => $default) { - $get = is_bool($default) ? "is{$key}" : "get{$key}"; + $uckey = ucfirst($key); + $get = is_bool($default) ? "is{$uckey}" : "get{$uckey}"; + self::assertTrue(method_exists($object, $get)); self::assertEquals($default, $object->$get()); $object->setStyleValue($key, null); self::assertEquals($default, $object->$get()); @@ -115,7 +116,7 @@ public function testSetStyleValueNormal(): void 'doubleStrikethrough' => false, 'smallCaps' => true, 'allCaps' => false, - 'fgColor' => Font::FGCOLOR_YELLOW, + 'fgColor' => Color::YELLOW, 'bgColor' => 'FFFF00', 'lineHeight' => 2, 'scale' => 150, @@ -130,7 +131,9 @@ public function testSetStyleValueNormal(): void ]; $object->setStyleByArray($attributes); foreach ($attributes as $key => $value) { - $get = is_bool($value) ? "is{$key}" : "get{$key}"; + $uckey = ucfirst($key); + $get = is_bool($value) ? "is{$uckey}" : "get{$uckey}"; + self::assertTrue(method_exists($object, $get)); self::assertEquals($value, $object->$get()); } } diff --git a/tests/PhpWordTests/Style/ImageTest.php b/tests/PhpWordTests/Style/ImageTest.php index 759a85441ee..489abe4cf4f 100644 --- a/tests/PhpWordTests/Style/ImageTest.php +++ b/tests/PhpWordTests/Style/ImageTest.php @@ -26,8 +26,6 @@ * Test class for PhpOffice\PhpWord\Style\Image. * * @coversDefaultClass \PhpOffice\PhpWord\Style\Image - * - * @runTestsInSeparateProcesses */ class ImageTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/LanguageTest.php b/tests/PhpWordTests/Style/LanguageTest.php index 848284e5e3f..81446473b6a 100644 --- a/tests/PhpWordTests/Style/LanguageTest.php +++ b/tests/PhpWordTests/Style/LanguageTest.php @@ -20,7 +20,6 @@ use InvalidArgumentException; use PhpOffice\PhpWord\Style\Language; -use PHPUnit\Framework\Assert; /** * Test class for PhpOffice\PhpWord\Style\Language. @@ -31,39 +30,39 @@ class LanguageTest extends \PHPUnit\Framework\TestCase { public function testGetSetPropertiesInt(): void { - $object = new Language(); foreach ([ - 'langId' => [null, 1036], + 'LangId' => [0, 1036], ] as $property => $value) { + $object = new Language(); [$default, $expected] = $value; $get = "get{$property}"; $set = "set{$property}"; - self::assertEquals($default, $object->$get()); // Default value + self::assertSame($default, $object->$get()); // Default value $object->$set($expected); - self::assertEquals($expected, $object->$get()); // New value + self::assertSame($expected, $object->$get()); // New value } } public function testGetSetPropertiesString(): void { - $object = new Language(); foreach ([ - 'latin' => [null, 'fr-BE'], - 'eastAsia' => [null, 'ja-JP'], - 'bidirectional' => [null, 'ar-SA'], + 'Latin' => [null, 'fr-BE'], + 'EastAsia' => [null, 'ja-JP'], + 'Bidirectional' => [null, 'ar-SA'], ] as $property => $value) { + $object = new Language(); [$default, $expected] = $value; $get = "get{$property}"; $set = "set{$property}"; - self::assertEquals($default, $object->$get()); // Default value + self::assertSame($default, $object->$get()); // Default value $object->$set($expected); - self::assertEquals($expected, $object->$get()); // New value + self::assertSame($expected, $object->$get()); // New value } } @@ -73,6 +72,7 @@ public function testGetSetPropertiesString(): void public function testWrongLanguage(): void { $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('not a valid language code'); $language = new Language(); $language->setLatin('fra'); } @@ -83,10 +83,34 @@ public function testWrongLanguage(): void public function testShortLanguage(): void { //when - $language = new Language(); - $language->setLatin('fr'); + $language = new Language('fr'); + + //then + self::assertSame('fr-FR', $language->getLatin()); + self::assertSame(1036, $language->getLangId()); + } + + public function testUndefined(): void + { + //when + $language = new Language('und'); //then - Assert::assertEquals('fr-FR', $language->getLatin()); + self::assertSame('en-GB', $language->getLatin()); + self::assertSame(2057, $language->getLangId()); + } + + public function testLangId(): void + { + $language = new Language('it-IT'); + self::assertSame(1040, $language->getLangId()); + $language = new Language('xt-IT'); + self::assertSame(0, $language->getLangId()); + $language = new Language('xt-IT', '', '', 1234); + self::assertSame(1234, $language->getLangId()); + $language = new Language('', 'hi-IN'); + self::assertSame(1081, $language->getLangId()); + $language = new Language('', '', 'he-IL'); + self::assertSame(1037, $language->getLangId()); } } diff --git a/tests/PhpWordTests/Style/LineTest.php b/tests/PhpWordTests/Style/LineTest.php index 26049d5cd8e..1fa9a0e19dd 100644 --- a/tests/PhpWordTests/Style/LineTest.php +++ b/tests/PhpWordTests/Style/LineTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Style\Image. * * @coversDefaultClass \PhpOffice\PhpWord\Style\Image - * - * @runTestsInSeparateProcesses */ class LineTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/ListItemTest.php b/tests/PhpWordTests/Style/ListItemTest.php index 59cdfeeda18..4cf03aea0fa 100644 --- a/tests/PhpWordTests/Style/ListItemTest.php +++ b/tests/PhpWordTests/Style/ListItemTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Style\ListItem. * * @coversDefaultClass \PhpOffice\PhpWord\Style\ListItem - * - * @runTestsInSeparateProcesses */ class ListItemTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/NumberingLevelTest.php b/tests/PhpWordTests/Style/NumberingLevelTest.php index b34e445e947..7c905e00ed8 100644 --- a/tests/PhpWordTests/Style/NumberingLevelTest.php +++ b/tests/PhpWordTests/Style/NumberingLevelTest.php @@ -23,8 +23,6 @@ /** * Test class for PhpOffice\PhpWord\Style\NumberingLevel. - * - * @runTestsInSeparateProcesses */ class NumberingLevelTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/PaperTest.php b/tests/PhpWordTests/Style/PaperTest.php index ea5a4fb6749..4996317b843 100644 --- a/tests/PhpWordTests/Style/PaperTest.php +++ b/tests/PhpWordTests/Style/PaperTest.php @@ -18,24 +18,14 @@ namespace PhpOffice\PhpWordTests\Style; +use InvalidArgumentException; use PhpOffice\PhpWord\Style\Paper; -use PhpOffice\PhpWordTests\TestHelperDOCX; /** * Test class for PhpOffice\PhpWord\Style\Paper. - * - * @runTestsInSeparateProcesses */ class PaperTest extends \PHPUnit\Framework\TestCase { - /** - * Tear down after each test. - */ - protected function tearDown(): void - { - TestHelperDOCX::clear(); - } - /** * Test initiation for paper. */ @@ -70,4 +60,41 @@ public function testFolioSize(): void self::assertEqualsWithDelta(12240, $object->getWidth(), 0.1); self::assertEqualsWithDelta(18720, $object->getHeight(), 0.1); } + + /** + * Test paper size for Folio format invoked with explicit values. + */ + public function testFolioSizeExplicit(): void + { + $object = new Paper(); + $object->setSize('XFolio', 8.5, 13, 'in'); + + self::assertEquals('XFolio', $object->getSize()); + self::assertEqualsWithDelta(12240, $object->getWidth(), 0.1); + self::assertEqualsWithDelta(18720, $object->getHeight(), 0.1); + } + + public function testBadUnit(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('unit must be mm or in'); + $object = new Paper(); + $object->setSize('XFolio', 8.5, 13, 'inx'); + } + + public function testBadWidthHeight(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('width and height must be positive'); + $object = new Paper(); + $object->setSize('XFolio', 0, 13, 'inx'); + } + + public function testUnknownSize(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid style value'); + $object = new Paper(); + $object->setSize('XFolio'); + } } diff --git a/tests/PhpWordTests/Style/ParagraphTest.php b/tests/PhpWordTests/Style/ParagraphTest.php index 67645fa4d84..c01569beac3 100644 --- a/tests/PhpWordTests/Style/ParagraphTest.php +++ b/tests/PhpWordTests/Style/ParagraphTest.php @@ -27,8 +27,6 @@ /** * Test class for PhpOffice\PhpWord\Style\Paragraph. - * - * @runTestsInSeparateProcesses */ class ParagraphTest extends \PHPUnit\Framework\TestCase { @@ -37,6 +35,7 @@ class ParagraphTest extends \PHPUnit\Framework\TestCase */ protected function tearDown(): void { + Settings::restoreDefaults(); TestHelperDOCX::clear(); } @@ -379,7 +378,5 @@ public function testBidiVisualSettings(): void Settings::setDefaultRtl(false); $object = new Paragraph(); self::assertFalse($object->isBidi()); - - Settings::setDefaultRtl(null); } } diff --git a/tests/PhpWordTests/Style/RowTest.php b/tests/PhpWordTests/Style/RowTest.php index d3cc480ac71..1c6506eebde 100644 --- a/tests/PhpWordTests/Style/RowTest.php +++ b/tests/PhpWordTests/Style/RowTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Style\Row. * * @coversDefaultClass \PhpOffice\PhpWord\Style\Row - * - * @runTestsInSeparateProcesses */ class RowTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/SectionTest.php b/tests/PhpWordTests/Style/SectionTest.php index e03dbb7596e..6d7692a7503 100644 --- a/tests/PhpWordTests/Style/SectionTest.php +++ b/tests/PhpWordTests/Style/SectionTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Style\Section. * * @coversDefaultClass \PhpOffice\PhpWord\Element\Section - * - * @runTestsInSeparateProcesses */ class SectionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/TablePositionTest.php b/tests/PhpWordTests/Style/TablePositionTest.php index 695bb3d2972..ab97ea91fb9 100644 --- a/tests/PhpWordTests/Style/TablePositionTest.php +++ b/tests/PhpWordTests/Style/TablePositionTest.php @@ -22,8 +22,6 @@ /** * Test class for PhpOffice\PhpWord\Style\Table. - * - * @runTestsInSeparateProcesses */ class TablePositionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/TableTest.php b/tests/PhpWordTests/Style/TableTest.php index e53a51f5df8..4b7906101b0 100644 --- a/tests/PhpWordTests/Style/TableTest.php +++ b/tests/PhpWordTests/Style/TableTest.php @@ -27,11 +27,14 @@ /** * Test class for PhpOffice\PhpWord\Style\Table. - * - * @runTestsInSeparateProcesses */ class TableTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Test class construction. * @@ -72,32 +75,42 @@ public function testSetGetNormal(): void { $object = new Table(); - $attributes = [ + $stringAttributes = [ 'bgColor' => 'FF0000', - 'borderTopSize' => 4, 'borderTopColor' => 'FF0000', - 'borderLeftSize' => 4, 'borderLeftColor' => 'FF0000', - 'borderRightSize' => 4, 'borderRightColor' => 'FF0000', - 'borderBottomSize' => 4, 'borderBottomColor' => 'FF0000', - 'borderInsideHSize' => 4, 'borderInsideHColor' => 'FF0000', - 'borderInsideVSize' => 4, 'borderInsideVColor' => 'FF0000', + 'alignment' => JcTable::CENTER, + 'unit' => 'pct', + 'layout' => Table::LAYOUT_FIXED, + ]; + $intAttributes = [ + 'borderTopSize' => 4, + 'borderLeftSize' => 4, + 'borderRightSize' => 4, + 'borderBottomSize' => 4, + 'borderInsideHSize' => 4, + 'borderInsideVSize' => 4, 'cellMarginTop' => 240, 'cellMarginLeft' => 240, 'cellMarginRight' => 240, 'cellMarginBottom' => 240, - 'alignment' => JcTable::CENTER, 'width' => 100, - 'unit' => 'pct', - 'layout' => Table::LAYOUT_FIXED, ]; - foreach ($attributes as $key => $value) { - $set = "set{$key}"; - $get = "get{$key}"; + foreach ($stringAttributes as $key => $value) { + $uckey = ucfirst($key); + $set = "set{$uckey}"; + $get = "get{$uckey}"; + $object->$set($value); + self::assertEquals($value, $object->$get()); + } + foreach ($intAttributes as $key => $value) { + $uckey = ucfirst($key); + $set = "set{$uckey}"; + $get = "get{$uckey}"; $object->$set($value); self::assertEquals($value, $object->$get()); } diff --git a/tests/PhpWordTests/Style/TextBoxTest.php b/tests/PhpWordTests/Style/TextBoxTest.php index 30c01bd368a..cc665524f2d 100644 --- a/tests/PhpWordTests/Style/TextBoxTest.php +++ b/tests/PhpWordTests/Style/TextBoxTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Style\Image. * * @coversDefaultClass \PhpOffice\PhpWord\Style\Image - * - * @runTestsInSeparateProcesses */ class TextBoxTest extends TestCase { @@ -37,29 +35,39 @@ public function testSetGetNormal(): void { $object = new TextBox(); - $properties = [ - 'width' => 200, - 'height' => 200, + $stringProperties = [ 'alignment' => Jc::START, - 'marginTop' => 240, - 'marginLeft' => 240, 'wrappingStyle' => 'inline', 'positioning' => 'absolute', 'posHorizontal' => 'center', 'posVertical' => 'top', 'posHorizontalRel' => 'margin', 'posVerticalRel' => 'page', - 'innerMarginTop' => '5', - 'innerMarginRight' => '5', - 'innerMarginBottom' => '5', - 'innerMarginLeft' => '5', - 'borderSize' => '2', 'borderColor' => 'red', 'bgColor' => 'blue', ]; - foreach ($properties as $key => $value) { - $set = "set{$key}"; - $get = "get{$key}"; + foreach ($stringProperties as $key => $value) { + $uckey = ucfirst($key); + $set = "set{$uckey}"; + $get = "get{$uckey}"; + $object->$set($value); + self::assertEquals($value, $object->$get()); + } + $intProperties = [ + 'width' => 200, + 'height' => 200, + 'marginTop' => 240, + 'marginLeft' => 240, + 'innerMarginTop' => 5, + 'innerMarginRight' => 5, + 'innerMarginBottom' => 5, + 'innerMarginLeft' => 5, + 'borderSize' => 2, + ]; + foreach ($intProperties as $key => $value) { + $uckey = ucfirst($key); + $set = "set{$uckey}"; + $get = "get{$uckey}"; $object->$set($value); self::assertEquals($value, $object->$get()); } diff --git a/tests/PhpWordTests/StyleTest.php b/tests/PhpWordTests/StyleTest.php index 6115e044267..64a67168d44 100644 --- a/tests/PhpWordTests/StyleTest.php +++ b/tests/PhpWordTests/StyleTest.php @@ -25,11 +25,14 @@ * Test class for PhpOffice\PhpWord\Style. * * @coversDefaultClass \PhpOffice\PhpWord\Style - * - * @runTestsInSeparateProcesses */ class StyleTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Style::resetStyles(); + } + /** * Add and get paragraph, font, link, title, and table styles. * @@ -47,6 +50,8 @@ class StyleTest extends \PHPUnit\Framework\TestCase */ public function testStyles(): void { + Style::resetStyles(); + self::assertCount(0, Style::getStyles()); $paragraph = ['alignment' => Jc::CENTER]; $font = ['italic' => true, '_bold' => true]; $table = ['bgColor' => 'CCCCCC']; @@ -65,13 +70,13 @@ public function testStyles(): void ]; $styles = [ - 'Paragraph' => 'Paragraph', - 'Font' => 'Font', - 'Link' => 'Font', - 'Table' => 'Table', - 'Heading_1' => 'Font', - 'Normal' => 'Paragraph', - 'Numbering' => 'Numbering', + 'Paragraph' => Style\Paragraph::class, + 'Font' => Style\Font::class, + 'Link' => Style\Font::class, + 'Table' => Style\Table::class, + 'Heading_1' => Style\Font::class, + 'Normal' => Style\Paragraph::class, + 'Numbering' => Style\Numbering::class, ]; Style::addParagraphStyle('Paragraph', $paragraph); @@ -84,12 +89,9 @@ public function testStyles(): void self::assertCount(count($styles), Style::getStyles()); foreach ($styles as $name => $style) { - self::assertInstanceOf("PhpOffice\\PhpWord\\Style\\{$style}", Style::getStyle($name)); + self::assertInstanceOf($style, Style::getStyle($name)); } self::assertNull(Style::getStyle('Unknown')); - - Style::resetStyles(); - self::assertCount(0, Style::getStyles()); } /** diff --git a/tests/PhpWordTests/TemplateProcessorSectionTest.php b/tests/PhpWordTests/TemplateProcessorSectionTest.php new file mode 100644 index 00000000000..211566f4c3b --- /dev/null +++ b/tests/PhpWordTests/TemplateProcessorSectionTest.php @@ -0,0 +1,91 @@ +templateProcessor = new TemplateProcessor($filename); + + return $this->templateProcessor; + } + + protected function tearDown(): void + { + if ($this->templateProcessor !== null) { + $filename = $this->templateProcessor->getTempDocumentFilename(); + $this->templateProcessor = null; + if (file_exists($filename)) { + @unlink($filename); + } + } + } + + public function testSetComplexSection(): void + { + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/document22-xml.docx'); + $html = ' +

       Bug Report:

      +

      BugTracker X is ${facing1} an issue.

      +

      BugTracker X is ${facing2} an issue.

      +

      BugTracker X is ${facing1} an issue.

      + '; + $section = new Section(0); + Html::addHtml($section, $html, false, false); + $templateProcessor->setComplexBlock('test', $section); + $facing1 = new TextRun(); + $facing1->addText('facing', ['bold' => true]); + $facing2 = new TextRun(); + $facing2->addText('facing', ['italic' => true]); + + $templateProcessor->setComplexBlock('test', $section); + $templateProcessor->setComplexValue('facing1', $facing1, true); + $templateProcessor->setComplexValue('facing2', $facing2); + + $docName = $templateProcessor->save(); + $docFound = file_exists($docName); + self::assertTrue($docFound); + $contents = file_get_contents("zip://$docName#word/document2.xml"); + unlink($docName); + self::assertNotFalse($contents); + $contents = preg_replace('/>\s+<', $contents) ?? ''; + self::assertStringContainsString('Test', $contents); + $count = substr_count($contents, 'facing'); + self::assertSame(2, $count, 'should be 2 bold strings'); + $count = substr_count($contents, 'facing'); + self::assertSame(1, $count, 'should be 1 italic string'); + self::assertStringNotContainsString('$', $contents, 'no leftover macros'); + self::assertStringNotContainsString('facing1', $contents, 'no leftover replaced string1'); + self::assertStringNotContainsString('facing2', $contents, 'no leftover replaced string2'); + } +} diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index 8ae4dfa59a6..1bc734c5688 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -22,6 +22,7 @@ use Exception; use PhpOffice\PhpWord\Element\Text; use PhpOffice\PhpWord\Element\TextRun; +use PhpOffice\PhpWord\Exception\Exception as PhpWordException; use PhpOffice\PhpWord\IOFactory; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; @@ -96,7 +97,7 @@ public function xtestTemplateCanBeSavedInTemporaryLocation(string $templateFqfn, } $embeddingText = 'The quick Brown Fox jumped over the lazy^H^H^H^Htired unitTester'; - $templateProcessor->zip()->AddFromString('word/embeddings/fox.bin', $embeddingText); + $templateProcessor->zip()->addFromString('word/embeddings/fox.bin', $embeddingText); $documentFqfn = $templateProcessor->save(); self::assertNotEmpty($documentFqfn, 'FQFN of the saved document is empty.'); @@ -177,7 +178,7 @@ public function testXslStyleSheetCanNotBeAppliedOnFailureOfSettingParameterValue $this->expectException(TypeError::class); $this->expectExceptionMessage('must contain only string keys'); } else { - $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectException(PhpWordException::class); $this->expectExceptionMessage('Could not set values for the given XSL style sheet parameters.'); } @@ -200,7 +201,7 @@ public function testXslStyleSheetCanNotBeAppliedOnFailureOfSettingParameterValue */ public function testXslStyleSheetCanNotBeAppliedOnFailureOfLoadingXmlFromTemplate(): void { - $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectException(PhpWordException::class); $this->expectExceptionMessage('Could not load the given XML document.'); $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/corrupted_main_document_part.docx'); @@ -240,6 +241,29 @@ public function testDeleteRow(): void self::assertTrue($docFound); } + public function testNoPhar(): void + { + $this->expectException(PhpWordException::class); + $this->expectExceptionMessage('Invalid protocol'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/delete-row.docx'); + + self::assertEquals( + ['deleteMe', 'deleteMeToo'], + $templateProcessor->getVariables() + ); + + $docName = 'phar://poc.docx'; + $templateProcessor->deleteRow('deleteMe'); + self::assertEquals( + [], + $templateProcessor->getVariables() + ); + $templateProcessor->saveAs($docName); + $docFound = file_exists($docName); + unlink($docName); + self::assertTrue($docFound); + } + /** * @covers ::cloneRow * @covers ::saveAs @@ -855,18 +879,22 @@ public function testSetCheckboxWithCustomMacro(): void /** * @covers ::setImageValue + * @covers \PhpOffice\PhpWord\Shared\ZipArchive::pclzipAddFile */ public function testSetImageValue(): void { $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx'); - $imagePath = __DIR__ . '/_files/images/earth.jpg'; + $imageJpg = __DIR__ . '/_files/images/earth.jpg'; + $imageGif = __DIR__ . '/_files/images/mario.gif'; + $imagePng = __DIR__ . '/_files/images/firefox.png'; + $imageSvg = __DIR__ . '/_files/images/phpword.svg'; $variablesReplace = [ - 'headerValue' => function () use ($imagePath) { - return $imagePath; + 'headerValue' => function () use ($imageJpg) { + return $imageJpg; }, - 'documentContent' => ['path' => $imagePath, 'width' => 500, 'height' => 500], - 'footerValue' => ['path' => $imagePath, 'width' => 100, 'height' => 50, 'ratio' => false], + 'documentContent' => ['path' => $imageJpg, 'width' => 500, 'height' => 500], + 'footerValue' => ['path' => $imageJpg, 'width' => 100, 'height' => 50, 'ratio' => false], ]; $templateProcessor->setImageValue(array_keys($variablesReplace), $variablesReplace); @@ -906,7 +934,16 @@ public function testSetImageValue(): void $testFileName = 'images-test-sample.docx'; $phpWord = new PhpWord(); $section = $phpWord->addSection(); - $section->addText('${Test:width=100:ratio=true}'); + $section->addText('${Test0:width=100:ratio=true}'); + $section->addText('${Test1::50:true}'); + $section->addText('${Test2}'); + $section->addText('${Test3:size=10cmx7cm:ratio=false}'); + $section->addText('${Test4:size=100mmx70mm:ratio=true}'); + $section->addText('${Test5:4in::true}'); + $section->addText('${Test6:300pt:200pt}'); + $section->addText('${Test7:25pc:}'); + $section->addText('${Test8:50%:50%}'); + $section->addText('${Test9::5ex}'); $objWriter = IOFactory::createWriter($phpWord, 'Word2007'); $objWriter->save($testFileName); self::assertFileExists($testFileName, "Generated file '{$testFileName}' not found!"); @@ -914,9 +951,8 @@ public function testSetImageValue(): void $resultFileName = 'images-test-result.docx'; $templateProcessor = new TemplateProcessor($testFileName); unlink($testFileName); - $templateProcessor->setImageValue('Test', $imagePath); - $templateProcessor->setImageValue('Test1', $imagePath); - $templateProcessor->setImageValue('Test2', $imagePath); + $templateProcessor->setImageValue('Test0', $imageJpg); + $templateProcessor->setImageValue(['Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'], [$imageGif, $imagePng, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg]); $templateProcessor->saveAs($resultFileName); self::assertFileExists($resultFileName, "Generated file '{$resultFileName}' not found!"); @@ -928,7 +964,7 @@ public function testSetImageValue(): void } unlink($resultFileName); - self::assertStringNotContainsString('${Test}', $expectedMainPartXml, 'word/document.xml has no image.'); + self::assertStringNotContainsString('${Test', $expectedMainPartXml, 'word/document.xml has not inserted all images.'); } /** diff --git a/tests/PhpWordTests/TestHelperDOCX.php b/tests/PhpWordTests/TestHelperDOCX.php index 2a6fbabae0e..5ebe799cfc6 100644 --- a/tests/PhpWordTests/TestHelperDOCX.php +++ b/tests/PhpWordTests/TestHelperDOCX.php @@ -47,13 +47,14 @@ class TestHelperDOCX */ public static function getDocument(PhpWord $phpWord, $writerName = 'Word2007') { + $tempdir = self::getTempDirPhpunit(); self::$file = tempnam(Settings::getTempDir(), 'PhpWord'); if (false === self::$file) { throw new CreateTemporaryFileException(); } - if (!is_dir(Settings::getTempDir() . '/PhpWord_Unit_Test/')) { - mkdir(Settings::getTempDir() . '/PhpWord_Unit_Test/'); + if (!is_dir($tempdir)) { + mkdir($tempdir); } $xmlWriter = IOFactory::createWriter($phpWord, $writerName); @@ -62,11 +63,11 @@ public static function getDocument(PhpWord $phpWord, $writerName = 'Word2007') $zip = new ZipArchive(); $res = $zip->open(self::$file); if (true === $res) { - $zip->extractTo(Settings::getTempDir() . '/PhpWord_Unit_Test/'); + $zip->extractTo($tempdir); $zip->close(); } - $doc = new XmlDocument(Settings::getTempDir() . '/PhpWord_Unit_Test/'); + $doc = new XmlDocument($tempdir); if ($writerName === 'ODText') { $doc->setDefaultFile('content.xml'); } @@ -83,8 +84,9 @@ public static function clear(): void unlink(self::$file); self::$file = ''; } - if (is_dir(Settings::getTempDir() . '/PhpWord_Unit_Test/')) { - self::deleteDir(Settings::getTempDir() . '/PhpWord_Unit_Test/'); + $tempdir = self::getTempDirPhpunit(); + if (is_dir($tempdir)) { + self::deleteDir($tempdir); } } @@ -117,4 +119,14 @@ public static function getFile() { return self::$file; } + + /** + * Get temporary directory for PhpUnit. + * + * @return string + */ + private static function getTempDirPhpunit() + { + return Settings::getTempDir() . '/PhpWord_Unit_Test'; + } } diff --git a/tests/PhpWordTests/WriteReadback/ODTextTest.php b/tests/PhpWordTests/WriteReadback/ODTextTest.php index cee0f38301a..613eb8e5082 100644 --- a/tests/PhpWordTests/WriteReadback/ODTextTest.php +++ b/tests/PhpWordTests/WriteReadback/ODTextTest.php @@ -27,8 +27,6 @@ * Test class for PhpOffice\PhpWord\Reader\ODText and PhpOffice\PhpWord\Writer\ODText. * * @coversDefaultClass \PhpOffice\PhpWord\Reader\ODText - * - * @runTestsInSeparateProcesses */ class ODTextTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/WriteReadback/RtfBackslashTest.php b/tests/PhpWordTests/WriteReadback/RtfBackslashTest.php new file mode 100644 index 00000000000..8913dd6c902 --- /dev/null +++ b/tests/PhpWordTests/WriteReadback/RtfBackslashTest.php @@ -0,0 +1,49 @@ +addSection(); + $sectionWriter->addText($textWithBackslash); + + $writer = new RTF($phpWordWriter); + $file = __DIR__ . '/../_files/temp.rtf'; + $writer->save($file); + self::assertFileExists($file); + $phpWordReader = IOFactory::load($file, 'RTF'); + unlink($file); + + $sections = $phpWordReader->getSections(); + self::assertCount(1, $sections); + $elements = $sections[0]->getElements(); + $element = $elements[0]; + self::assertInstanceOf(TextRun::class, $element); + self::assertSame($textWithBackslash, $element->getText()); + } +} diff --git a/tests/PhpWordTests/WriteReadback/RtfWidowTest.php b/tests/PhpWordTests/WriteReadback/RtfWidowTest.php new file mode 100644 index 00000000000..ce213107fc0 --- /dev/null +++ b/tests/PhpWordTests/WriteReadback/RtfWidowTest.php @@ -0,0 +1,73 @@ +getSettings()->setRtfWidowControl(true); + $testText = 'Hello World!'; + $sectionWriter = $phpWordWriter->addSection(); + $sectionWriter->addText($testText); + + $writer = new RTF($phpWordWriter); + $file = __DIR__ . '/../_files/temp.rtf'; + $writer->save($file); + + self::assertFileExists($file); + + $phpWordReader = IOFactory::load($file, 'RTF'); + unlink($file); + + self::assertTrue($phpWordReader->getSettings()->hasRtfWidowControl()); + } + + public function testDefaultWidowControl(): void + { + $phpWordWriter = new PhpWord(); + $testText = 'Hello World!'; + $sectionWriter = $phpWordWriter->addSection(); + $sectionWriter->addText($testText); + + $writer = new RTF($phpWordWriter); + $file = __DIR__ . '/../_files/temp.rtf'; + $writer->save($file); + + self::assertFileExists($file); + + $phpWordReader = IOFactory::load($file, 'RTF'); + unlink($file); + + self::assertCount(1, $phpWordReader->getSections()); + self::assertCount(2, $phpWordReader->getSections()[0]->getElements()); + self::assertInstanceOf(TextRun::class, $phpWordReader->getSections()[0]->getElements()[0]); + self::assertEquals($testText, $phpWordReader->getSections()[0]->getElements()[0]->getText()); + self::assertFalse($phpWordReader->getSettings()->hasRtfWidowControl()); + } +} diff --git a/tests/PhpWordTests/WriteReadback/Word2007Test.php b/tests/PhpWordTests/WriteReadback/Word2007Test.php index 572977ccf80..1b21a77adae 100644 --- a/tests/PhpWordTests/WriteReadback/Word2007Test.php +++ b/tests/PhpWordTests/WriteReadback/Word2007Test.php @@ -21,6 +21,7 @@ use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\IOFactory; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Writer\Word2007; @@ -28,11 +29,17 @@ * Test class for PhpOffice\PhpWord\Reader\Word2007 and PhpOffice\PhpWord\Writer\Word2007. * * @coversDefaultClass \PhpOffice\PhpWord\Reader\Word2007 - * - * @runTestsInSeparateProcesses */ class Word2007Test extends \PHPUnit\Framework\TestCase { + /** + * Executed after each method of the class. + */ + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Test default font name. */ @@ -129,6 +136,8 @@ public function testZoom(): void $phpWordWriter = new PhpWord(); $zoomLevel = 75; $phpWordWriter->getSettings()->setZoom($zoomLevel); + $phpWordWriter->getSettings()->setConsecutiveHyphenLimit(3); + $phpWordWriter->getSettings()->setAutoHyphenation(true); $writer = new Word2007($phpWordWriter); $file = __DIR__ . '/../_files/temp.docx'; @@ -139,6 +148,8 @@ public function testZoom(): void $phpWordReader = IOFactory::load($file, 'Word2007'); self::assertEquals($zoomLevel, $phpWordReader->getSettings()->getZoom()); + self::assertSame(3, $phpWordReader->getSettings()->getConsecutiveHyphenLimit()); + self::assertTrue($phpWordReader->getSettings()->hasAutoHyphenation()); unlink($file); } diff --git a/tests/PhpWordTests/Writer/EPub3/ElementTest.php b/tests/PhpWordTests/Writer/EPub3/Element/ElementTest.php similarity index 55% rename from tests/PhpWordTests/Writer/EPub3/ElementTest.php rename to tests/PhpWordTests/Writer/EPub3/Element/ElementTest.php index af745d91761..77ba57e0692 100644 --- a/tests/PhpWordTests/Writer/EPub3/ElementTest.php +++ b/tests/PhpWordTests/Writer/EPub3/Element/ElementTest.php @@ -1,10 +1,11 @@ expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectException(WordException::class); + $this->expectExceptionMessage('Writer element class'); - $element = $this->createMock(AbstractElement::class); + $element = new FakeElement(); WriterElement::getElementClass($element); } } diff --git a/tests/PhpWordTests/Writer/EPub3/Element/FakeElement.php b/tests/PhpWordTests/Writer/EPub3/Element/FakeElement.php new file mode 100644 index 00000000000..f96ccb96e15 --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Element/FakeElement.php @@ -0,0 +1,9 @@ +xmlWriter = new XMLWriter(); + $xmlWriter = new XMLWriter(); $style = new ImageStyle(); $style->setWidth(100); $style->setHeight(100); - $this->element = new Image('tests/PhpWordTests/_files/images/earth.jpg', $style); - $this->writer = new ImageWriter($this->xmlWriter, $this->element); - } - - public function testWrite(): void - { - $this->writer->write(); + $element = new Image('tests/PhpWordTests/_files/images/earth.jpg', $style); + $writer = new ImageWriter($xmlWriter, $element); + $writer->write(); - $expected = '

      '; - self::assertEquals($expected, $this->xmlWriter->getData()); + $expected = '

      '; + self::assertSame($expected, $xmlWriter->getData()); } public function testWriteWithoutP(): void { + $xmlWriter = new XMLWriter(); $style = new ImageStyle(); $style->setWidth(100); $style->setHeight(100); - $this->element = new Image('tests/PhpWordTests/_files/images/earth.jpg', $style); - $this->writer = new ImageWriter($this->xmlWriter, $this->element, true); + $element = new Image('tests/PhpWordTests/_files/images/earth.jpg', $style); + $writer = new ImageWriter($xmlWriter, $element, true); - $this->writer->write(); + $writer->write(); - $expected = ''; - self::assertEquals($expected, $this->xmlWriter->getData()); + $expected = ''; + self::assertSame($expected, $xmlWriter->getData()); } public function testWriteWithInvalidElement(): void { - $invalidElement = $this->createMock(\PhpOffice\PhpWord\Element\AbstractElement::class); - $writer = new ImageWriter($this->xmlWriter, $invalidElement); + $xmlWriter = new XMLWriter(); + $invalidElement = new FakeElement(); + $writer = new ImageWriter($xmlWriter, $invalidElement); $writer->write(); - self::assertEquals('', $this->xmlWriter->getData()); + self::assertSame('', $xmlWriter->getData()); } } diff --git a/tests/PhpWordTests/Writer/EPub3/Element/TextTest.php b/tests/PhpWordTests/Writer/EPub3/Element/TextTest.php index 38490691d56..99b7350f7f7 100644 --- a/tests/PhpWordTests/Writer/EPub3/Element/TextTest.php +++ b/tests/PhpWordTests/Writer/EPub3/Element/TextTest.php @@ -9,75 +9,63 @@ class TextTest extends TestCase { - /** - * @var XMLWriter - */ - private $xmlWriter; - - /** - * @var Text - */ - private $element; - - /** - * @var TextWriter - */ - private $writer; - - protected function setUp(): void - { - $this->xmlWriter = new XMLWriter(); - $this->element = new Text('Sample Text'); - $this->writer = new TextWriter($this->xmlWriter, $this->element); - } - public function testWrite(): void { - $this->writer->write(); + $xmlWriter = new XMLWriter(); + $element = new Text('Sample Text'); + $writer = new TextWriter($xmlWriter, $element); + $writer->write(); $expected = "

      \n Sample Text\n

      \n"; - self::assertEquals($expected, $this->xmlWriter->getData()); + self::assertSame($expected, $xmlWriter->getData()); } public function testWriteWithFontStyle(): void { - $this->element->setFontStyle('customStyle'); + $xmlWriter = new XMLWriter(); + $element = new Text('Sample Text'); + $writer = new TextWriter($xmlWriter, $element); + $element->setFontStyle('customStyle'); - $this->writer->write(); + $writer->write(); $expected = "

      \n Sample Text\n

      \n"; - self::assertEquals($expected, $this->xmlWriter->getData()); + self::assertSame($expected, $xmlWriter->getData()); } public function testWriteWithParagraphStyle(): void { - $this->element->setParagraphStyle('paragraphStyle'); + $xmlWriter = new XMLWriter(); + $element = new Text('Sample Text'); + $writer = new TextWriter($xmlWriter, $element); + $element->setParagraphStyle('paragraphStyle'); - $this->writer->write(); + $writer->write(); $expected = "

      \n Sample Text\n

      \n"; - self::assertEquals($expected, $this->xmlWriter->getData()); + self::assertSame($expected, $xmlWriter->getData()); } public function testWriteWithoutP(): void { $text = new Text('Sample Text'); $xmlWriter = new XMLWriter(); - $this->writer = new TextWriter($xmlWriter, $text, true); + $writer = new TextWriter($xmlWriter, $text, true); - $this->writer->write(); + $writer->write(); $expected = "Sample Text\n"; - self::assertEquals($expected, $xmlWriter->getData()); + self::assertSame($expected, $xmlWriter->getData()); } public function testWriteWithInvalidElement(): void { - $invalidElement = $this->createMock(\PhpOffice\PhpWord\Element\AbstractElement::class); - $writer = new TextWriter($this->xmlWriter, $invalidElement); + $xmlWriter = new XMLWriter(); + $invalidElement = new FakeElement(); + $writer = new TextWriter($xmlWriter, $invalidElement); $writer->write(); - self::assertEquals('', $this->xmlWriter->getData()); + self::assertSame('', $xmlWriter->getData()); } } diff --git a/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartClass.php b/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartClass.php new file mode 100644 index 00000000000..056ec6e1be0 --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartClass.php @@ -0,0 +1,13 @@ +part = $this->getMockForAbstractClass(AbstractPart::class); - } else { - $this->part = new class() extends AbstractPart { - public function write(): string - { - return ''; - } - }; - } - } - public function testParentWriter(): void { + $part = new AbstractPartClass(); $writer = new EPub3(); - $this->part->setParentWriter($writer); + $part->setParentWriter($writer); - self::assertInstanceOf(EPub3::class, $this->part->getParentWriter()); + self::assertInstanceOf(EPub3::class, $part->getParentWriter()); } } diff --git a/tests/PhpWordTests/Writer/EPub3/Part/ContentXhtmlTest.php b/tests/PhpWordTests/Writer/EPub3/Part/ContentXhtmlTest.php new file mode 100644 index 00000000000..08ad30f41e4 --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Part/ContentXhtmlTest.php @@ -0,0 +1,18 @@ +expectException(WordException::class); + $this->expectExceptionMessage('No PhpWord assigned.'); + $object = new ContentXhtml(); + $object->write(); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleClass.php b/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleClass.php new file mode 100644 index 00000000000..2657e126938 --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleClass.php @@ -0,0 +1,13 @@ +getMockForAbstractClass(AbstractStyle::class); - } else { - /** @var AbstractStyle $style */ - $style = new class() extends AbstractStyle { - public function write(): string - { - return ''; - } - }; - } + + $style = new AbstractStyleClass(); $result = $style->setParentWriter($parentWriter); diff --git a/tests/PhpWordTests/Writer/EPub3Test.php b/tests/PhpWordTests/Writer/EPub3Test.php index acdbb5e32f5..7f3336c6c43 100644 --- a/tests/PhpWordTests/Writer/EPub3Test.php +++ b/tests/PhpWordTests/Writer/EPub3Test.php @@ -25,8 +25,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\Epub3. - * - * @runTestsInSeparateProcesses */ class EPub3Test extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/HTML/Element/PreserveTextTest.php b/tests/PhpWordTests/Writer/HTML/Element/PreserveTextTest.php new file mode 100644 index 00000000000..e77876125cf --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Element/PreserveTextTest.php @@ -0,0 +1,63 @@ +addSection(); + $text1 = 'This text is missing in the HTML output.'; + $section->addPreserveText($text1); + $text2 = 'Likewise page {PAGE} of {NUMPAGES}'; + $section->addPreserveText($text2); + $writer = new HTML($phpWord); + $content = $writer->getContent(); + $expected = "

      $text1

      "; + self::assertStringContainsString($expected, $content); + $expected = "

      $text2

      "; + self::assertStringContainsString($expected, $content); + } + + public function testPreserveTextStyle(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $text1 = 'This text is missing in the HTML output.'; + $fontStyle = ['color' => 'red']; + $section->addPreserveText($text1, $fontStyle); + $paragraphStyle = ['align' => 'center']; + $text2 = 'Likewise page {PAGE} of {NUMPAGES}'; + $section->addPreserveText($text2, null, $paragraphStyle); + $section->addPreserveText(''); + $writer = new HTML($phpWord); + $content = $writer->getContent(); + $expected = "

      $text1

      "; + self::assertStringContainsString($expected, $content); + $expected = "

      $text2

      "; + self::assertStringContainsString($expected, $content); + $expected = '

       

      '; + self::assertStringContainsString($expected, $content); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php b/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php index 2ca556bc51d..64509b7c9cc 100644 --- a/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php +++ b/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php @@ -49,17 +49,17 @@ public function testWriteRubyHtml(): void $dom = Helper::getAsHTML($phpWord, '', '', ['ruby', 'rt', 'rp']); $xpath = new DOMXPath($dom); - self::assertEquals(1, $xpath->query('/html/body/div/ruby')->length); + $this->checkLength(1, $xpath, '/html/body/div/ruby'); // ensure text is right $rubyElement = $dom->getElementsByTagName('ruby')->item(0); $rtElement = $dom->getElementsByTagName('rt')->item(0); self::assertNotNull($rubyElement); self::assertNotNull($rtElement); - self::assertEquals($baseTextRun->getText() . ' (' . $rubyTextRun->getText() . ')', $rubyElement->textContent); - self::assertEquals($rubyTextRun->getText(), $rtElement->textContent); + self::assertSame($baseTextRun->getText() . ' (' . $rubyTextRun->getText() . ')', $rubyElement->textContent); + self::assertSame($rubyTextRun->getText(), $rtElement->textContent); // check style - self::assertEquals('font-size:20pt;ruby-align:center;', $rubyElement->attributes->getNamedItem('style')->textContent); - self::assertEquals('font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); + self::assertSame('font-size:20pt;ruby-align:center;', $rubyElement->attributes->getNamedItem('style')->textContent); + self::assertSame('font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); } /** @@ -82,18 +82,47 @@ public function testWriteRubyHtmlParagraphStyle(): void $rubyTextRun->addText('わたし'); $section->addRuby($baseTextRun, $rubyTextRun, $properties); + $phpWord->addParagraphStyle('lineHeight10', [ + 'lineHeight' => '10', + ]); + $properties2 = new RubyProperties(); + $baseTextRun2 = new TextRun('lineHeight10'); + $baseTextRun2->addText('私'); + $rubyTextRun2 = new TextRun(['lineHeight' => '4']); + $rubyTextRun2->addText('わたし'); + $section->addRuby($baseTextRun2, $rubyTextRun2, $properties2); + $dom = Helper::getAsHTML($phpWord, '', '', ['ruby', 'rt', 'rp']); $xpath = new DOMXPath($dom); - self::assertEquals(1, $xpath->query('/html/body/div/ruby')->length); + $this->checkLength(2, $xpath, '/html/body/div/ruby'); // ensure text is right $rubyElement = $dom->getElementsByTagName('ruby')->item(0); $rtElement = $dom->getElementsByTagName('rt')->item(0); self::assertNotNull($rubyElement); self::assertNotNull($rtElement); - self::assertEquals($baseTextRun->getText() . ' (' . $rubyTextRun->getText() . ')', $rubyElement->textContent); - self::assertEquals($rubyTextRun->getText(), $rtElement->textContent); + self::assertSame($baseTextRun->getText() . ' (' . $rubyTextRun->getText() . ')', $rubyElement->textContent); + self::assertSame($rubyTextRun->getText(), $rtElement->textContent); + // check style + self::assertSame('line-height: 8;font-size:20pt;ruby-align:center;', $rubyElement->attributes->getNamedItem('style')->textContent); + self::assertSame('line-height: 4;font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); + + // ensure text is right + $rubyElement2 = $dom->getElementsByTagName('ruby')->item(1); + $rtElement2 = $dom->getElementsByTagName('rt')->item(1); + self::assertNotNull($rubyElement2); + self::assertNotNull($rtElement2); + self::assertSame($baseTextRun2->getText() . ' (' . $rubyTextRun2->getText() . ')', $rubyElement2->textContent); + self::assertSame($rubyTextRun2->getText(), $rtElement2->textContent); // check style - self::assertEquals('line-height: 8;font-size:20pt;ruby-align:center;', $rubyElement->attributes->getNamedItem('style')->textContent); - self::assertEquals('line-height: 4;font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); + self::assertSame('font-size:24pt;ruby-align:space-between;', $rubyElement2->attributes->getNamedItem('style')->textContent); + self::assertSame('lineHeight10', $rubyElement2->attributes->getNamedItem('class')->textContent); + self::assertSame('line-height: 4;font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); + } + + private function checkLength(int $expected, DOMXPath $xpath, string $location): void + { + $temp = $xpath->query($location); + self::assertNotFalse($temp); + self::assertSame($expected, $temp->length); } } diff --git a/tests/PhpWordTests/Writer/HTML/Element/TableTest.php b/tests/PhpWordTests/Writer/HTML/Element/TableTest.php index cd0bafaab0a..df625d7fccf 100644 --- a/tests/PhpWordTests/Writer/HTML/Element/TableTest.php +++ b/tests/PhpWordTests/Writer/HTML/Element/TableTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWordTests\Writer\HTML\Element; +use DOMElement; use DOMXPath; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\SimpleType\VerticalJc; @@ -162,7 +163,7 @@ public function testWriteTableBorders(): void self::assertEquals('tstyle', Helper::getTextContent($xpath, '/html/body/div/table[6]', 'class')); $style = Helper::getTextContent($xpath, '/html/head/style'); self::assertNotFalse(preg_match('/^[.]tstyle[^\\r\\n]*/m', $style, $matches)); - self::assertEquals(".tstyle {table-layout: auto; $cssnone}", $matches[0]); + self::assertEquals(".tstyle {table-layout: auto; $cssnone}", $matches[0] ?? ''); } public function testWriteTableCellVAlign(): void @@ -198,8 +199,9 @@ public function testWriteTableCellVAlign(): void self::assertNotFalse($cell3Query); self::assertCount(1, $cell3Query); - $cell3Style = $cell3Query->item(0)->attributes->getNamedItem('style'); - self::assertNull($cell3Style); + $temp = $cell3Query->item(0); + self::assertInstanceOf(DOMElement::class, $temp); + self::assertNull($temp->attributes->getNamedItem('style')); } public function testWriteTableCellVMerge(): void @@ -230,6 +232,8 @@ public function testWriteTableCellVMerge(): void $cell3Query = $xpath->query('//table/tr[3]/td[1]'); self::assertNotFalse($cell3Query); self::assertCount(1, $cell3Query); - self::assertNull($cell3Query->item(0)->attributes->getNamedItem('rowspan')); + $temp = $cell3Query->item(0); + self::assertInstanceOf(DOMElement::class, $temp); + self::assertNull($temp->attributes->getNamedItem('rowspan')); } } diff --git a/tests/PhpWordTests/Writer/HTML/ElementTest.php b/tests/PhpWordTests/Writer/HTML/ElementTest.php index 3b2580381fd..0a71df4f56c 100644 --- a/tests/PhpWordTests/Writer/HTML/ElementTest.php +++ b/tests/PhpWordTests/Writer/HTML/ElementTest.php @@ -20,6 +20,7 @@ use DateTime; use DOMDocument; +use DOMElement; use DOMXPath; use PhpOffice\PhpWord\Element\Text as TextElement; use PhpOffice\PhpWord\Element\TextRun; @@ -72,13 +73,41 @@ public function testWriteTrackChanges(): void $text = $section->addText('my dummy text'); $text->setChangeInfo(TrackChange::INSERTED, 'author name'); $text2 = $section->addText('my other text'); - $text2->setTrackChange(new TrackChange(TrackChange::DELETED, 'another author', new DateTime())); + $deleteTime = new DateTime(); + $deleteAuthor = "Spec O'char"; + $deleteTrack = new TrackChange(TrackChange::DELETED, $deleteAuthor, $deleteTime); + $text2->setTrackChange($deleteTrack); $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals(1, $xpath->query('/html/body/div/p[1]/ins')->length); - self::assertEquals(1, $xpath->query('/html/body/div/p[2]/del')->length); + $this->checkLength(1, $xpath, '/html/body/div/p[1]/ins'); + $this->checkLength(1, $xpath, '/html/body/div/p[2]/del'); + $node = $xpath->query('/html/body/div/p[2]/del'); + self::assertNotFalse($node); + $allAttributes = $node[0]->attributes; + self::assertCount(4, $allAttributes); + $node = $xpath->query('/html/body/div/p[2]/del'); + self::assertNotFalse($node); + + $attributes = $node[0]->attributes[0]; + self::assertSame('data-phpword-chg-author', $attributes->name); + self::assertSame($deleteAuthor, $attributes->value); + + $text2Id = $text2->getElementId(); + $attributes = $node[0]->attributes[1]; + self::assertSame('data-phpword-chg-id', $attributes->name); + self::assertSame($text2Id, $attributes->value); + + $attributes = $node[0]->attributes[2]; + self::assertSame('data-phpword-chg-timestamp', $attributes->name); + self::assertSame($deleteTime->format('Y-m-d\TH:i:s\Z'), $attributes->value); + + $attributes = $node[0]->attributes[3]; + self::assertSame('title', $attributes->name); + $expected = $deleteAuthor . ' - ' + . $deleteTime->format('Y-m-d H:i:s'); + self::assertSame($expected, $attributes->value); } /** @@ -101,14 +130,14 @@ public function testWriteColSpan(): void $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals(1, $xpath->query('/html/body/div/table/tr[1]/td')->length); - self::assertEquals('2', $xpath->query('/html/body/div/table/tr/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); - self::assertEquals(2, $xpath->query('/html/body/div/table/tr[2]/td')->length); + $this->checkLength(1, $xpath, '/html/body/div/table/tr[1]/td'); + $this->checkContent('2', $xpath, '/html/body/div/table/tr/td[1]', 0, 'colspan'); + $this->checkLength(2, $xpath, '/html/body/div/table/tr[2]/td'); - self::assertEquals('#6086B8', $xpath->query('/html/body/div/table/tr[1]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent); - self::assertEquals('#ffffff', $xpath->query('/html/body/div/table/tr[1]/td')->item(0)->attributes->getNamedItem('color')->textContent); - self::assertEquals('#ffffff', $xpath->query('/html/body/div/table/tr[2]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent); - self::assertNull($xpath->query('/html/body/div/table/tr[2]/td')->item(0)->attributes->getNamedItem('color')); + $this->checkContent('#6086B8', $xpath, '/html/body/div/table/tr[1]/td', 0, 'bgcolor'); + $this->checkContent('#ffffff', $xpath, '/html/body/div/table/tr[1]/td', 0, 'color'); + $this->checkContent('#ffffff', $xpath, '/html/body/div/table/tr[2]/td', 0, 'bgcolor'); + $this->checkContent(null, $xpath, '/html/body/div/table/tr[2]/td', 0, 'color'); } /** @@ -135,9 +164,9 @@ public function testWriteRowSpan(): void $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals(2, $xpath->query('/html/body/div/table/tr[1]/td')->length); - self::assertEquals('3', $xpath->query('/html/body/div/table/tr[1]/td[1]')->item(0)->attributes->getNamedItem('rowspan')->textContent); - self::assertEquals(1, $xpath->query('/html/body/div/table/tr[2]/td')->length); + $this->checkLength(2, $xpath, '/html/body/div/table/tr[1]/td'); + $this->checkContent('3', $xpath, '/html/body/div/table/tr[1]/td[1]', 0, 'rowspan'); + $this->checkLength(1, $xpath, '/html/body/div/table/tr[2]/td'); } /** @@ -167,14 +196,14 @@ public function testWriteRowSpanAndColSpan(): void $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals(3, $xpath->query('/html/body/div/table/tr[1]/td')->length); - self::assertEquals('2', $xpath->query('/html/body/div/table/tr[1]/td[2]')->item(0)->attributes->getNamedItem('colspan')->textContent); - self::assertEquals('3', $xpath->query('/html/body/div/table/tr[1]/td[3]')->item(0)->attributes->getNamedItem('rowspan')->textContent); + $this->checkLength(3, $xpath, '/html/body/div/table/tr[1]/td'); + $this->checkContent('2', $xpath, '/html/body/div/table/tr[1]/td[2]', 0, 'colspan'); + $this->checkContent('3', $xpath, '/html/body/div/table/tr[1]/td[3]', 0, 'rowspan'); - self::assertEquals(1, $xpath->query('/html/body/div/table/tr[2]/td')->length); - self::assertEquals('3', $xpath->query('/html/body/div/table/tr[2]/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); + $this->checkLength(1, $xpath, '/html/body/div/table/tr[2]/td'); + $this->checkContent('3', $xpath, '/html/body/div/table/tr[2]/td[1]', 0, 'colspan'); - self::assertEquals(3, $xpath->query('/html/body/div/table/tr[3]/td')->length); + $this->checkLength(3, $xpath, '/html/body/div/table/tr[3]/td'); } public function testWriteTitleTextRun(): void @@ -240,7 +269,28 @@ public function testWriteTableLayout(): void $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals('table-layout: fixed;', $xpath->query('/html/body/div/table[1]')->item(0)->attributes->getNamedItem('style')->textContent); - self::assertEquals('table-layout: auto;', $xpath->query('/html/body/div/table[2]')->item(0)->attributes->getNamedItem('style')->textContent); + $this->checkContent('table-layout: fixed;', $xpath, '/html/body/div/table[1]', 0, 'style'); + $this->checkContent('table-layout: auto;', $xpath, '/html/body/div/table[2]', 0, 'style'); + } + + private function checkContent(?string $expected, DOMXPath $xpath, string $location, int $index, string $namedItem): void + { + $temp = $xpath->query($location); + self::assertNotFalse($temp); + $temp = $temp->item($index); + self::assertInstanceOf(DOMElement::class, $temp); + $temp = $temp->attributes->getNamedItem($namedItem); + if ($temp === null) { + self::assertNull($expected); + } else { + self::assertSame($expected, $temp->textContent); + } + } + + private function checkLength(int $expected, DOMXPath $xpath, string $location): void + { + $temp = $xpath->query($location); + self::assertNotFalse($temp); + self::assertSame($expected, $temp->length); } } diff --git a/tests/PhpWordTests/Writer/HTML/FontTest.php b/tests/PhpWordTests/Writer/HTML/FontTest.php index 0a203b72376..6b4f44854e0 100644 --- a/tests/PhpWordTests/Writer/HTML/FontTest.php +++ b/tests/PhpWordTests/Writer/HTML/FontTest.php @@ -28,37 +28,17 @@ */ class FontTest extends \PHPUnit\Framework\TestCase { - /** @var string */ - private $defaultFontName; - - /** @var float|int */ - private $defaultFontSize; - - /** @var string */ - private $defaultFontColor; - - /** - * Executed before each method of the class. - */ - protected function setUp(): void - { - $this->defaultFontName = Settings::getDefaultFontName(); - $this->defaultFontSize = Settings::getDefaultFontSize(); - $this->defaultFontColor = Settings::getDefaultFontColor(); - } - /** * Executed after each method of the class. */ protected function tearDown(): void { - Settings::setDefaultFontName($this->defaultFontName); - Settings::setDefaultFontSize($this->defaultFontSize); - Settings::setDefaultFontColor($this->defaultFontColor); + Settings::restoreDefaults(); } public function testDefaultDefaults(): void { + Settings::restoreDefaults(); $phpWord = new PhpWord(); $dom = Helper::getAsHTML($phpWord); @@ -68,7 +48,7 @@ public function testDefaultDefaults(): void $prg = preg_match('/body {(.*?)}/', $style, $matches); self::assertNotEmpty($matches); self::assertNotFalse($prg); - self::assertEquals('body {font-family: \'Arial\'; font-size: 12pt; color: #000000;}', $matches[0]); + self::assertEquals('body {font-family: \'Arial\'; font-size: 10pt; color: #000000;}', $matches[0]); } public function testSettingDefaultFontColor(): void @@ -85,7 +65,7 @@ public function testSettingDefaultFontColor(): void $prg = preg_match('/body {(.*?)}/', $style, $matches); self::assertNotEmpty($matches); self::assertNotFalse($prg); - self::assertEquals('body {font-family: \'Arial\'; font-size: 12pt; color: #00FF00;}', $matches[0]); + self::assertEquals('body {font-family: \'Arial\'; font-size: 10pt; color: #00FF00;}', $matches[0]); } /** @@ -121,10 +101,10 @@ public function testFontNames1(): void self::assertEquals('style5', Helper::getTextContent($xpath, '/html/body/div/p[6]/span', 'class')); $style = Helper::getTextContent($xpath, '/html/head/style'); - $prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches); + $prg = preg_match('/^body[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); self::assertNotFalse($prg); - self::assertEquals('* {font-family: \'Courier New\'; font-size: 12pt; color: #000000;}', $matches[0]); + self::assertEquals('body {font-family: \'Courier New\'; font-size: 12pt; color: #000000;}', $matches[0]); $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); self::assertNotFalse($prg); @@ -177,10 +157,10 @@ public function testFontNames2(): void self::assertEquals('style4', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'class')); $style = Helper::getTextContent($xpath, '/html/head/style'); - $prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches); + $prg = preg_match('/^body[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); self::assertNotFalse($prg); - self::assertEquals('* {font-family: \'Courier New\'; font-size: 12pt; color: #000000;}', $matches[0]); + self::assertEquals('body {font-family: \'Courier New\'; font-size: 12pt; color: #000000;}', $matches[0]); $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); self::assertNotFalse($prg); @@ -229,10 +209,10 @@ public function testFontNames3(): void self::assertEquals('style4', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'class')); $style = Helper::getTextContent($xpath, '/html/head/style'); - $prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches); + $prg = preg_match('/^body[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); self::assertNotFalse($prg); - self::assertEquals('* {font-family: \'Courier New\', monospace; font-size: 12pt; color: #000000;}', $matches[0]); + self::assertEquals('body {font-family: \'Courier New\', monospace; font-size: 12pt; color: #000000;}', $matches[0]); $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); self::assertNotFalse($prg); @@ -274,8 +254,8 @@ public function testWhiteSpace(): void $xpath = new DOMXPath($dom); $style = Helper::getTextContent($xpath, '/html/head/style'); - self::assertNotFalse(preg_match('/^[*][^\\r\\n]*/m', $style, $matches)); - self::assertEquals('* {font-family: \'Arial\'; font-size: 12pt; color: #000000; white-space: pre-wrap;}', $matches[0]); + self::assertNotFalse(preg_match('/^body[^\\r\\n]*/m', $style, $matches)); + self::assertEquals('body {font-family: \'Arial\'; font-size: 12pt; color: #000000; white-space: pre-wrap;}', $matches[0] ?? ''); $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); self::assertNotFalse($prg); diff --git a/tests/PhpWordTests/Writer/HTML/Helper.php b/tests/PhpWordTests/Writer/HTML/Helper.php index 37f640d28a0..40acd8ae5c4 100644 --- a/tests/PhpWordTests/Writer/HTML/Helper.php +++ b/tests/PhpWordTests/Writer/HTML/Helper.php @@ -19,6 +19,7 @@ namespace PhpOffice\PhpWordTests\Writer\HTML; use DOMDocument; +use DOMElement; use DOMXPath; use Exception; use LibXMLError; @@ -41,6 +42,7 @@ public static function getTextContent(DOMXPath $xpath, string $query, string $na if ($item2 === null) { self::fail('Unexpected null return requesting item'); } elseif ($namedItem !== '') { + self::assertInstanceOf(DOMElement::class, $item2); $item3 = $item2->attributes->getNamedItem($namedItem); if ($item3 === null) { self::fail('Unexpected null return requesting namedItem'); @@ -48,6 +50,7 @@ public static function getTextContent(DOMXPath $xpath, string $query, string $na $returnVal = $item3->textContent; } } else { + self::assertInstanceOf(DOMElement::class, $item2); $returnVal = $item2->textContent; } } @@ -67,7 +70,8 @@ public static function getNamedItem(DOMXPath $xpath, string $query, string $name if ($item2 === null) { self::fail('Unexpected null return requesting item'); } else { - $returnValue = $item2->attributes->getNamedItem($namedItem); + self::assertInstanceOf(DOMElement::class, $item2); + $returnVal = $item2->attributes->getNamedItem($namedItem); } } @@ -125,4 +129,13 @@ public static function getAsHTML(PhpWord $phpWord, string $defaultWhiteSpace = ' return $dom; } + + public static function getHtmlString(PhpWord $phpWord, string $defaultWhiteSpace = '', string $defaultGenericFont = ''): string + { + $htmlWriter = new HTML($phpWord); + $htmlWriter->setDefaultWhiteSpace($defaultWhiteSpace); + $htmlWriter->setDefaultGenericFont($defaultGenericFont); + + return $htmlWriter->getContent(); + } } diff --git a/tests/PhpWordTests/Writer/HTML/ParagraphTest.php b/tests/PhpWordTests/Writer/HTML/ParagraphTest.php index 2b2724dba8c..d972aa23d7b 100644 --- a/tests/PhpWordTests/Writer/HTML/ParagraphTest.php +++ b/tests/PhpWordTests/Writer/HTML/ParagraphTest.php @@ -55,7 +55,7 @@ public function testParagraphStyles(): void $style = Helper::getTextContent($xpath, '/html/head/style'); self::assertNotFalse(preg_match('/^[.]indented[^\\r\\n]*/m', $style, $matches)); - self::assertEquals('.indented {margin-left: 0.5in; margin-right: 0.6in;}', $matches[0]); + self::assertEquals('.indented {margin-left: 0.5in; margin-right: 0.6in;}', $matches[0] ?? ''); } /** @@ -88,9 +88,9 @@ public function testParagraphAndFontStyles(): void $style = Helper::getTextContent($xpath, '/html/head/style'); self::assertNotFalse(preg_match('/^[.]indented[^\\r\\n]*/m', $style, $matches)); - self::assertEquals('.indented {margin-left: 0.5in; margin-right: 0.6in;}', $matches[0]); + self::assertEquals('.indented {margin-left: 0.5in; margin-right: 0.6in;}', $matches[0] ?? ''); self::assertNotFalse(preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches)); - self::assertEquals('.style1 {font-family: \'Courier New\', monospace; font-size: 10pt; white-space: pre-wrap;}', $matches[0]); + self::assertEquals('.style1 {font-family: \'Courier New\', monospace; font-size: 10pt; white-space: pre-wrap;}', $matches[0] ?? ''); } /** diff --git a/tests/PhpWordTests/Writer/HTML/PartTest.php b/tests/PhpWordTests/Writer/HTML/PartTest.php index b6748a58c55..d5b842af641 100644 --- a/tests/PhpWordTests/Writer/HTML/PartTest.php +++ b/tests/PhpWordTests/Writer/HTML/PartTest.php @@ -21,6 +21,7 @@ use DOMXPath; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Shared\Converter; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Writer\HTML\Part\Body; /** @@ -28,6 +29,11 @@ */ class PartTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Style::resetStyles(); + } + /** * Test get parent writer exception. */ @@ -44,7 +50,7 @@ public function testGetParentWriterException(): void public function testWriteSections(): void { $phpWord = new PhpWord(); - $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language('en-US')); + $phpWord->getSettings()->setThemeFontLang(new Style\Language('en-US')); $section1 = $phpWord->addSection(); $mtop = 0.5 * Converter::INCH_TO_TWIP; $mbot = 0.5 * Converter::INCH_TO_TWIP; @@ -96,7 +102,7 @@ public function testWriteSections(): void public function testThemeFontEastAsian(): void { $phpWord = new PhpWord(); - $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language('', 'hi-IN')); + $phpWord->getSettings()->setThemeFontLang(new Style\Language('', 'hi-IN')); $section1 = $phpWord->addSection(); $section1->addText('??? ????? ???'); @@ -112,7 +118,7 @@ public function testThemeFontEastAsian(): void public function testThemeBidirecional(): void { $phpWord = new PhpWord(); - $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language('', '', 'he-IL')); + $phpWord->getSettings()->setThemeFontLang(new Style\Language('', '', 'he-IL')); $section1 = $phpWord->addSection(); $section1->addText('????'); @@ -185,5 +191,9 @@ public function testTitleStyles(): void self::assertNotFalse(strpos($style, 'h2 {margin-top: 0.25pt; margin-bottom: 0.25pt;}')); self::assertEquals(1, Helper::getLength($xpath, '/html/body/div/h1')); self::assertEquals(2, Helper::getLength($xpath, '/html/body/div/h2')); + $html = Helper::getHtmlString($phpWord); + self::assertStringContainsString('

      Header 1 #1

      ', $html); + self::assertStringContainsString('

      Header 2 #1

      ', $html); + self::assertStringContainsString('

      Header 2 #2

      ', $html); } } diff --git a/tests/PhpWordTests/Writer/HTML/Sample11Test.php b/tests/PhpWordTests/Writer/HTML/Sample11Test.php new file mode 100644 index 00000000000..dde70b30bdc --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Sample11Test.php @@ -0,0 +1,42 @@ +getContent(); + $expected = 'source file'; + self::assertStringContainsString($expected, $content); + $expected = 'source file'; + self::assertStringContainsString($expected, $content); + $expected = 'even '; + self::assertStringContainsString($expected, $content); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/Sample36RtlTest.php b/tests/PhpWordTests/Writer/HTML/Sample36RtlTest.php new file mode 100644 index 00000000000..0acda2abdbb --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Sample36RtlTest.php @@ -0,0 +1,78 @@ +addSection(); + + $textrun = $section->addTextRun(); + $textrun->addText('This is a Left to Right paragraph.'); + + $textrun = $section->addTextRun(['alignment' => Jc::END]); + $textrun->addText('سلام این یک پاراگراف راست به چپ است', ['rtl' => true]); + + $section->addText('Table visually presented as RTL'); + $style = ['rtl' => true, 'size' => 12]; + $tableStyle = [ + 'borderSize' => 6, + 'borderColor' => '000000', + 'width' => 5000, + 'unit' => TblWidth::PERCENT, + 'bidiVisual' => true, + ]; + + $table = $section->addTable($tableStyle); + $cellHCentered = ['alignment' => Jc::CENTER]; + $cellHEnd = ['alignment' => Jc::END]; + $cellVCentered = ['valign' => VerticalJc::CENTER]; + + //Vidually bidirectinal table + $table->addRow(); + $cell = $table->addCell(1500, $cellVCentered); + $textrun = $cell->addTextRun($cellHCentered); + $textrun->addText('ردیف', $style); + + $cell = $table->addCell(2000); + $textrun = $cell->addTextRun($cellHEnd); + $textrun->addText('سوالات', $style); + + $cell = $table->addCell(1000, $cellVCentered); + $textrun = $cell->addTextRun($cellHCentered); + $textrun->addText('بارم', $style); + $writer = new HTML($phpWord); + $content = $writer->getContent(); + $expected = '
      Test 1

      '; + self::assertStringContainsString($expected, $content); $writer->save($file); self::assertFileExists($file); diff --git a/tests/PhpWordTests/Writer/NoPharTest.php b/tests/PhpWordTests/Writer/NoPharTest.php new file mode 100644 index 00000000000..254640c8b16 --- /dev/null +++ b/tests/PhpWordTests/Writer/NoPharTest.php @@ -0,0 +1,65 @@ +expectException(PhpWordException::class); + $this->expectExceptionMessage('Invalid protocol'); + $phpWord = new PhpWord(); + if ($fileType === 'PDF') { + $rendererName = Settings::PDF_RENDERER_DOMPDF; + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); + self::assertNotFalse($rendererLibraryPath); + Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + } + $writer = IOFactory::createWriter($phpWord, $fileType); + $writer->save('phar://poc.docx'); + } + + public static function providerFileType(): array + { + return [ + ['EPub3'], + ['HTML'], + ['ODText'], + ['RTF'], + ['Word2007'], + ['PDF\DomPDF'], + ['PDF\MPDF'], + ['PDF\TCPDF'], + ['PDF'], + ]; + } +} diff --git a/tests/PhpWordTests/Writer/ODText/ElementTest.php b/tests/PhpWordTests/Writer/ODText/ElementTest.php index 8ca327717c8..1cbb7c0acc9 100644 --- a/tests/PhpWordTests/Writer/ODText/ElementTest.php +++ b/tests/PhpWordTests/Writer/ODText/ElementTest.php @@ -23,6 +23,7 @@ use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\XMLWriter; use PhpOffice\PhpWordTests\TestHelperDOCX; @@ -31,11 +32,20 @@ */ class ElementTest extends \PHPUnit\Framework\TestCase { + /** @var bool */ + private $esc; + + protected function setUp(): void + { + $this->esc = Settings::isOutputEscapingEnabled(); + } + /** * Executed after each method of the class. */ protected function tearDown(): void { + Settings::setOutputEscapingEnabled($this->esc); TestHelperDOCX::clear(); } @@ -241,15 +251,13 @@ public function testTextRunTitle(): void */ public function testTextWithAmpersand(): void { - $esc = \PhpOffice\PhpWord\Settings::isOutputEscapingEnabled(); - \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); + Settings::setOutputEscapingEnabled(true); $phpWord = new PhpWord(); $section = $phpWord->addSection(); $txt = 'this text contains an & (ampersand)'; $section->addText($txt); $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); - \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled($esc); $p2t = '/office:document-content/office:body/office:text/text:section'; $element = "$p2t/text:p[2]"; self::assertTrue($doc->elementExists($element)); @@ -340,8 +348,7 @@ public function testTrackedChanges(): void */ public function testRubyText(): void { - $esc = \PhpOffice\PhpWord\Settings::isOutputEscapingEnabled(); - \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); + Settings::setOutputEscapingEnabled(true); $phpWord = new PhpWord(); $section = $phpWord->addSection(); $properties = new RubyProperties(); @@ -351,17 +358,25 @@ public function testRubyText(): void $properties->setFontSizeForBaseText(18); $properties->setLanguageId('ja-JP'); - $baseTextRun = new TextRun(null); + $phpWord->addParagraphStyle('lineHeight10', [ + 'lineHeight' => '10', + ]); + $baseTextRun = new TextRun('lineHeight10'); $baseTextRun->addText('私'); - $rubyTextRun = new TextRun(null); + $rubyTextRun = new TextRun(['lineHeight' => '4']); $rubyTextRun->addText('わたし'); $section->addRuby($baseTextRun, $rubyTextRun, $properties); $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); - \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled($esc); $p2t = '/office:document-content/office:body/office:text/text:section'; - $element = "$p2t/text:p[2]"; - self::assertTrue($doc->elementExists($element)); - self::assertEquals('私 (わたし)', $doc->getElement($element)->nodeValue); + $textRuby = "$p2t/text:p[2]/text:ruby"; + self::assertTrue($doc->elementExists($textRuby)); + $textRubyBase = "$textRuby/text:ruby-base"; + self::assertTrue($doc->elementExists($textRubyBase)); + self::assertEquals('私', $doc->getElement($textRubyBase)->nodeValue); + self::assertEquals('lineHeight10', $doc->getElementAttribute($textRubyBase, 'text:style-name')); + $textRubyText = "$textRuby/text:ruby-text"; + self::assertTrue($doc->elementExists($textRubyText)); + self::assertEquals('わたし', $doc->getElement($textRubyText)->nodeValue); } } diff --git a/tests/PhpWordTests/Writer/ODText/Part/AbstractPartClass.php b/tests/PhpWordTests/Writer/ODText/Part/AbstractPartClass.php new file mode 100644 index 00000000000..f366a902537 --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Part/AbstractPartClass.php @@ -0,0 +1,29 @@ +getMockForAbstractClass(ODText\Part\AbstractPart::class); - } else { - /** @var ODText\Part\AbstractPart $object */ - $object = new class() extends ODText\Part\AbstractPart { - public function write(): string - { - return ''; - } - }; - } + $object = new AbstractPartClass(); $object->setParentWriter(new ODText()); self::assertEquals(new ODText(), $object->getParentWriter()); } @@ -57,18 +46,7 @@ public function testSetGetParentWriterNull(): void { $this->expectException(Exception::class); $this->expectExceptionMessage('No parent WriterInterface assigned.'); - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $object = $this->getMockForAbstractClass(ODText\Part\AbstractPart::class); - } else { - /** @var ODText\Part\AbstractPart $object */ - $object = new class() extends ODText\Part\AbstractPart { - public function write(): string - { - return ''; - } - }; - } + $object = new AbstractPartClass(); $object->getParentWriter(); } } diff --git a/tests/PhpWordTests/Writer/ODText/Part/ContentTest.php b/tests/PhpWordTests/Writer/ODText/Part/ContentTest.php index e1dc78a32b0..412197f2c68 100644 --- a/tests/PhpWordTests/Writer/ODText/Part/ContentTest.php +++ b/tests/PhpWordTests/Writer/ODText/Part/ContentTest.php @@ -19,6 +19,7 @@ namespace PhpOffice\PhpWordTests\Writer\ODText\Part; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWordTests\TestHelperDOCX; @@ -29,11 +30,9 @@ */ class ContentTest extends \PHPUnit\Framework\TestCase { - /** - * Executed before each method of the class. - */ protected function tearDown(): void { + Settings::restoreDefaults(); TestHelperDOCX::clear(); } diff --git a/tests/PhpWordTests/Writer/ODText/Style/FontTest.php b/tests/PhpWordTests/Writer/ODText/Style/FontTest.php index b377ff4fcdf..970857f58ce 100644 --- a/tests/PhpWordTests/Writer/ODText/Style/FontTest.php +++ b/tests/PhpWordTests/Writer/ODText/Style/FontTest.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWordTests\Writer\ODText\Style; +use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\SimpleType\Color; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWordTests\TestHelperDOCX; @@ -31,6 +33,7 @@ class FontTest extends \PHPUnit\Framework\TestCase */ protected function tearDown(): void { + Settings::restoreDefaults(); TestHelperDOCX::clear(); } @@ -77,7 +80,7 @@ public function testColors(): void $section = $phpWord->addSection(); $section->addText('This is red (800) in rtf/html, default in docx/odt', ['color' => '800']); $section->addText('This should be cyanish (008787)', ['color' => '008787']); - $section->addText('This should be dark green (FGCOLOR_DARKGREEN)', ['color' => Font::FGCOLOR_DARKGREEN]); + $section->addText('This should be dark green (Color::DARKGREEN)', ['color' => Color::DARKGREEN]); $section->addText('This color is default (unknow)', ['color' => 'unknow']); $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); @@ -106,27 +109,27 @@ public function testColors(): void $span = "$s2t/text:p[4]/text:span"; self::assertTrue($doc->elementExists($span)); self::assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); - self::assertEquals('This should be dark green (FGCOLOR_DARKGREEN)', $doc->getElement($span)->nodeValue); + self::assertEquals('This should be dark green (Color::DARKGREEN)', $doc->getElement($span)->nodeValue); } public static function providerAllNamedColors() { return [ - [Font::FGCOLOR_YELLOW, 'FFFF00'], - [Font::FGCOLOR_LIGHTGREEN, '90EE90'], - [Font::FGCOLOR_CYAN, '00FFFF'], - [Font::FGCOLOR_MAGENTA, 'FF00FF'], - [Font::FGCOLOR_BLUE, '0000FF'], - [Font::FGCOLOR_RED, 'FF0000'], - [Font::FGCOLOR_DARKBLUE, '00008B'], - [Font::FGCOLOR_DARKCYAN, '008B8B'], - [Font::FGCOLOR_DARKGREEN, '006400'], - [Font::FGCOLOR_DARKMAGENTA, '8B008B'], - [Font::FGCOLOR_DARKRED, '8B0000'], - [Font::FGCOLOR_DARKYELLOW, '8B8B00'], - [Font::FGCOLOR_DARKGRAY, 'A9A9A9'], - [Font::FGCOLOR_LIGHTGRAY, 'D3D3D3'], - [Font::FGCOLOR_BLACK, '000000'], + [Color::YELLOW, 'FFFF00'], + [Color::LIGHTGREEN, '90EE90'], + [Color::CYAN, '00FFFF'], + [Color::MAGENTA, 'FF00FF'], + [Color::BLUE, '0000FF'], + [Color::RED, 'FF0000'], + [Color::DARKBLUE, '00008B'], + [Color::DARKCYAN, '008B8B'], + [Color::DARKGREEN, '006400'], + [Color::DARKMAGENTA, '8B008B'], + [Color::DARKRED, '8B0000'], + [Color::DARKYELLOW, '808000'], + [Color::DARKGRAY, 'A9A9A9'], + [Color::LIGHTGRAY, 'D3D3D3'], + [Color::BLACK, '000000'], ['unknow', 'unknow'], ['unknown', 'unknown'], ]; @@ -284,4 +287,25 @@ public function testFieldStyles(): void $element = "$s2t/text:p[5]/text:span"; self::assertEquals('namedstyle', $doc->getElementAttribute($element, 'text:style-name')); } + + public function testUnderline(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $phpWord->addFontStyle('underlineStyle', [ + 'underline' => Font::UNDERLINE_DOTDOTDASH, + ]); + $section = $phpWord->addSection(); + $section->addText('Sample text.', 'underlineStyle'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $doc->setDefaultFile('styles.xml'); + $s2a = '/office:document-styles/office:styles'; + self::assertTrue($doc->elementExists($s2a)); + $style = "$s2a/style:style"; + self::assertTrue($doc->elementExists($style)); + self::assertSame('underlineStyle', $doc->getElementAttribute($style, 'style:name')); + $properties = "$style/style:text-properties"; + self::assertTrue($doc->elementExists($properties)); + self::assertSame('dot-dot-dash', $doc->getElementAttribute($properties, 'style:text-underline-style')); + } } diff --git a/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php b/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php index bc02ca8ea08..60248233329 100644 --- a/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php +++ b/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php @@ -25,6 +25,11 @@ class Paragraph2Test extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Style::resetStyles(); + } + /** * Test textAlign. */ @@ -50,14 +55,17 @@ public function testTextAlign(): void $element .= '/style:paragraph-properties'; self::assertTrue($doc->elementExists($element)); self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + self::assertEquals('rl-tb', $doc->getElementAttribute($element, 'style:writing-mode')); $element = "$s2a/style:style[6]/style:paragraph-properties"; self::assertTrue($doc->elementExists($element)); self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + self::assertEquals('rl-tb', $doc->getElementAttribute($element, 'style:writing-mode')); $element = "$s2a/style:style[8]/style:paragraph-properties"; self::assertTrue($doc->elementExists($element)); self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + self::assertEquals('rl-tb', $doc->getElementAttribute($element, 'style:writing-mode')); $doc->setDefaultFile('styles.xml'); $element = '/office:document-styles/office:styles/style:style'; @@ -65,7 +73,8 @@ public function testTextAlign(): void self::assertEquals('Normal', $doc->getElementAttribute($element, 'style:name')); $element .= '/style:paragraph-properties'; self::assertTrue($doc->elementExists($element)); - self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + self::assertEquals('rl-tb', $doc->getElementAttribute($element, 'style:writing-mode')); } /** diff --git a/tests/PhpWordTests/Writer/ODText/Style/Paragraph3Test.php b/tests/PhpWordTests/Writer/ODText/Style/Paragraph3Test.php new file mode 100644 index 00000000000..f8e5c3ffbed --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Style/Paragraph3Test.php @@ -0,0 +1,65 @@ +addParagraphStyle('alignCenter', [ + 'align' => 'center', + ]); + $alignCenter = new Paragraph(); + $alignCenter->setStyleName('alignCenter'); + $phpWord->addParagraphStyle('alignEnd', [ + 'align' => 'end', + ]); + $alignEnd = new Paragraph(); + $alignEnd->setStyleName('alignEnd'); + $section = $phpWord->addSection(); + $section->addText('Should be aligned center.', null, $alignCenter); + $tr = $section->addTextRun($alignEnd); + $tr->addText('Should be aligned right.'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:body/office:text/text:section'; + $element = "$s2a/text:p[2]"; + self::assertEquals('alignCenter', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:p[3]"; + self::assertEquals('alignEnd', $doc->getElementAttribute($element, 'text:style-name')); + + $doc->setDefaultFile('styles.xml'); + $element = '/office:document-styles/office:styles/style:style[1]'; + self::assertEquals('alignCenter', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('center', $doc->getElementAttribute($element, 'fo:text-align')); + $element = '/office:document-styles/office:styles/style:style[2]'; + self::assertEquals('alignEnd', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('end', $doc->getElementAttribute($element, 'fo:text-align')); + } +} diff --git a/tests/PhpWordTests/Writer/ODText/Style/ParagraphTest.php b/tests/PhpWordTests/Writer/ODText/Style/ParagraphTest.php index ecadd387d52..26cae1ea001 100644 --- a/tests/PhpWordTests/Writer/ODText/Style/ParagraphTest.php +++ b/tests/PhpWordTests/Writer/ODText/Style/ParagraphTest.php @@ -20,6 +20,7 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Shared\Converter; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWordTests\TestHelperDOCX; /** @@ -32,6 +33,7 @@ class ParagraphTest extends \PHPUnit\Framework\TestCase */ protected function tearDown(): void { + Style::resetStyles(); TestHelperDOCX::clear(); } diff --git a/tests/PhpWordTests/Writer/ODTextTest.php b/tests/PhpWordTests/Writer/ODTextTest.php index 9746791379f..9450be41761 100644 --- a/tests/PhpWordTests/Writer/ODTextTest.php +++ b/tests/PhpWordTests/Writer/ODTextTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\ODText. - * - * @runTestsInSeparateProcesses */ class ODTextTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/PDF/DomPDFTest.php b/tests/PhpWordTests/Writer/PDF/DomPDFTest.php index 37ea762802b..870e417d3b2 100644 --- a/tests/PhpWordTests/Writer/PDF/DomPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/DomPDFTest.php @@ -24,27 +24,26 @@ /** * Test class for PhpOffice\PhpWord\Writer\PDF\DomPDF. - * - * @runTestsInSeparateProcesses */ class DomPDFTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Test construct. */ public function testConstruct(): void { - define('DOMPDF_ENABLE_AUTOLOAD', false); $file = __DIR__ . '/../../_files/dompdf.pdf'; $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText('Test 1'); - $rendererName = Settings::PDF_RENDERER_DOMPDF; - $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); - Settings::setPdfRenderer($rendererName, $rendererLibraryPath); - $writer = new PDF($phpWord); + $writer = new PDF\DomPDF($phpWord); $writer->save($file); self::assertFileExists($file); @@ -57,12 +56,7 @@ public function testConstruct(): void */ public function testSetGetAbstractRendererProperties(): void { - define('DOMPDF_ENABLE_AUTOLOAD', false); - - $rendererName = Settings::PDF_RENDERER_DOMPDF; - $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); - Settings::setPdfRenderer($rendererName, $rendererLibraryPath); - $writer = new PDF(new PhpWord()); + $writer = new PDF\DomPDF(new PhpWord()); $writer->setFont('arial'); self::assertEquals('arial', $writer->getFont()); @@ -82,10 +76,9 @@ public function testSetGetAbstractRendererProperties(): void */ public function testSetGetAbstractRendererOptions(): void { - define('DOMPDF_ENABLE_AUTOLOAD', false); - $rendererName = Settings::PDF_RENDERER_DOMPDF; $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); + self::assertNotFalse($rendererLibraryPath); Settings::setPdfRenderer($rendererName, $rendererLibraryPath); Settings::setPdfRendererOptions([ 'font' => 'Arial', diff --git a/tests/PhpWordTests/Writer/PDF/MPDFTest.php b/tests/PhpWordTests/Writer/PDF/MPDFTest.php index dc779bd51f2..e48b9617853 100644 --- a/tests/PhpWordTests/Writer/PDF/MPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/MPDFTest.php @@ -25,18 +25,19 @@ /** * Test class for PhpOffice\PhpWord\Writer\PDF\MPDF. - * - * @runTestsInSeparateProcesses */ class MPDFTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Test construct. */ public function testConstruct(): void { - $file = __DIR__ . '/../../_files/mpdf.pdf'; - $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText('Test 1'); @@ -44,21 +45,19 @@ public function testConstruct(): void $section->addText('Test 2'); $oSettings = new \PhpOffice\PhpWord\Style\Section(); $oSettings->setSettingValue('orientation', 'landscape'); - $section = $phpWord->addSection($oSettings); // @phpstan-ignore-line + $section = $phpWord->addSection($oSettings); $section->addText('Section 2 - landscape'); $writer = new MPDF($phpWord); - $writer->save($file); - - self::assertFileExists($file); - - unlink($file); + $writer->setFont('xyz'); + ob_start(); + $writer->save('php://output'); + $contents = (string) ob_get_clean(); + self::assertSame('%PDF', substr($contents, 0, 4)); } public function testEditCallback(): void { - $file = __DIR__ . '/../../_files/mpdf.pdf'; - $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText('Test 1'); @@ -66,18 +65,17 @@ public function testEditCallback(): void $section->addText('Test 2'); $oSettings = new \PhpOffice\PhpWord\Style\Section(); $oSettings->setSettingValue('orientation', 'landscape'); - $section = $phpWord->addSection($oSettings); // @phpstan-ignore-line + $section = $phpWord->addSection($oSettings); $section->addText('Section 2 - landscape'); $writer = new MPDF($phpWord); /** @var callable */ $callback = [self::class, 'cbEditContent']; $writer->setEditCallback($callback); - $writer->save($file); - - self::assertFileExists($file); - - unlink($file); + ob_start(); + $writer->save('php://output'); + $contents = (string) ob_get_clean(); + self::assertSame('%PDF', substr($contents, 0, 4)); } // add a footer @@ -106,6 +104,7 @@ public function testSetGetAbstractRendererOptions(): void { $rendererName = Settings::PDF_RENDERER_MPDF; $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/mpdf/mpdf'); + self::assertNotFalse($rendererLibraryPath); Settings::setPdfRenderer($rendererName, $rendererLibraryPath); Settings::setPdfRendererOptions([ 'font' => 'Arial', diff --git a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php index 16887507a9f..424aed94108 100644 --- a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php @@ -18,38 +18,39 @@ namespace PhpOffice\PhpWordTests\Writer\PDF; +use Exception; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Writer\PDF; /** * Test class for PhpOffice\PhpWord\Writer\PDF\TCPDF. - * - * @runTestsInSeparateProcesses */ class TCPDFTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Settings::restoreDefaults(); + Style::resetStyles(); + } + /** * Test construct. */ public function testConstruct(): void { - $file = __DIR__ . '/../../_files/tcpdf.pdf'; - $phpWord = new PhpWord(); $phpWord->setDefaultParagraphStyle(['spaceBefore' => 0, 'spaceAfter' => 0]); $section = $phpWord->addSection(); $section->addText('Test 1'); - $rendererName = Settings::PDF_RENDERER_TCPDF; - $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/tecnickcom/tcpdf'); - Settings::setPdfRenderer($rendererName, $rendererLibraryPath); - $writer = new PDF($phpWord); - $writer->save($file); - - self::assertFileExists($file); - - unlink($file); + $writer = new PDF\TCPDF($phpWord); + $writer->setFont('Helvetica'); + ob_start(); + $writer->save('php://output'); + $contents = (string) ob_get_clean(); + self::assertSame('%PDF', substr($contents, 0, 4)); } /** @@ -59,6 +60,7 @@ public function testSetGetAbstractRendererOptions(): void { $rendererName = Settings::PDF_RENDERER_TCPDF; $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/tecnickcom/tcpdf'); + self::assertNotFalse($rendererLibraryPath); Settings::setPdfRenderer($rendererName, $rendererLibraryPath); Settings::setPdfRendererOptions([ 'font' => 'Arial', @@ -66,4 +68,40 @@ public function testSetGetAbstractRendererOptions(): void $writer = new PDF(new PhpWord()); self::assertEquals('Arial', $writer->getFont()); } + + public function testSectionPageBreak(): void + { + $rendererName = Settings::PDF_RENDERER_TCPDF; + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/tecnickcom/tcpdf'); + self::assertNotFalse($rendererLibraryPath); + Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + $phpWord = new PhpWord(); + $section1 = $phpWord->addSection(); + $section1->addText('This is section 1.'); + $section2 = $phpWord->addSection(); + $section2->addText('This is section 2.'); + $writer = new PDF($phpWord); + $content = $writer->getContent(); + self::assertStringContainsString("
      ", $content); + self::assertStringContainsString('
      ', $content); + } + + /** + * @runInSeparateProcess + * + * @preserveGlobalState disabled + */ + public function testExceptionRatherThanDie(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Could not include font definition file'); + $phpWord = new PhpWord(); + $section1 = $phpWord->addSection(); + $section1->addText('This is section 1.'); + $section2 = $phpWord->addSection(); + $section2->addText('This is section 2.'); + $writer = new PDF\TcpdfNoDie($phpWord); + $writer->setFont('xyz'); + $writer->save('php://memory'); + } } diff --git a/tests/PhpWordTests/Writer/PDFTest.php b/tests/PhpWordTests/Writer/PDFTest.php index b9e31511cf4..6e1fbc1c74a 100644 --- a/tests/PhpWordTests/Writer/PDFTest.php +++ b/tests/PhpWordTests/Writer/PDFTest.php @@ -24,28 +24,62 @@ /** * Test class for PhpOffice\PhpWord\Writer\PDF. - * - * @runTestsInSeparateProcesses */ class PDFTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Test normal construct. */ - public function testConstruct(): void + public function testConstructDompdf(): void { - define('DOMPDF_ENABLE_AUTOLOAD', false); - $file = __DIR__ . '/../_files/temp.pdf'; - + Settings::restoreDefaults(); $rendererName = Settings::PDF_RENDERER_DOMPDF; $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); + self::assertNotFalse($rendererLibraryPath); + Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + $writer = new PDF(new PhpWord()); + $writer->setFont('xyz'); + ob_start(); + $writer->save('php://output'); + $contents = (string) ob_get_clean(); + self::assertSame('%PDF', substr($contents, 0, 4)); + } + + public function testConstructMpdf(): void + { + $file = __DIR__ . '/../_files/temp.pdf'; + + $rendererName = Settings::PDF_RENDERER_MPDF; + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/mpdf/mpdf'); + self::assertNotFalse($rendererLibraryPath); Settings::setPdfRenderer($rendererName, $rendererLibraryPath); $writer = new PDF(new PhpWord()); - $writer->save($file); + $writer->setFont('xyz'); + ob_start(); + $writer->save('php://output'); + $contents = (string) ob_get_clean(); + self::assertSame('%PDF', substr($contents, 0, 4)); + } - self::assertFileExists($file); + public function testConstructTcpdf(): void + { + $file = __DIR__ . '/../_files/temp.pdf'; - unlink($file); + $rendererName = Settings::PDF_RENDERER_TCPDF; + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/tecnickcom/tcpdf'); + self::assertNotFalse($rendererLibraryPath); + Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + $writer = new PDF(new PhpWord()); + $writer->setFont('Helvetica'); + ob_start(); + $writer->save('php://output'); + $contents = (string) ob_get_clean(); + self::assertSame('%PDF', substr($contents, 0, 4)); } /** @@ -53,6 +87,7 @@ public function testConstruct(): void */ public function testConstructException(): void { + Settings::restoreDefaults(); $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); $this->expectExceptionMessage('PDF rendering library or library path has not been defined.'); $writer = new PDF(new PhpWord()); diff --git a/tests/PhpWordTests/Writer/RTF/Element2Test.php b/tests/PhpWordTests/Writer/RTF/Element/Element2Test.php similarity index 78% rename from tests/PhpWordTests/Writer/RTF/Element2Test.php rename to tests/PhpWordTests/Writer/RTF/Element/Element2Test.php index 2220d93b681..5301e336fcf 100644 --- a/tests/PhpWordTests/Writer/RTF/Element2Test.php +++ b/tests/PhpWordTests/Writer/RTF/Element/Element2Test.php @@ -16,9 +16,8 @@ * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ -namespace PhpOffice\PhpWordTests\Writer\RTF; +namespace PhpOffice\PhpWordTests\Writer\RTF\Element; -use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Writer\RTF; use PhpOffice\PhpWord\Writer\RTF\Element\TextRun as WriterTextRun; use PhpOffice\PhpWord\Writer\RTF\Element\Title as WriterTitle; @@ -28,11 +27,6 @@ */ class Element2Test extends \PHPUnit\Framework\TestCase { - protected function tearDown(): void - { - Settings::setDefaultRtl(null); - } - /** @param WriterTextRun|WriterTitle $field */ public function removeCr($field): string { @@ -41,25 +35,23 @@ public function removeCr($field): string public function testTextRun(): void { - Settings::setDefaultRtl(false); $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\TextRun(); $element->addText('Hello '); $element->addText('there.'); $textrun = new WriterTextRun($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\ql{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; + $expect = "\\pard\\widctlpar {{Hello }{there.}}\\par\n"; self::assertEquals($expect, $this->removeCr($textrun)); } public function testTextRunParagraphStyle(): void { - Settings::setDefaultRtl(false); $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\TextRun(['spaceBefore' => 0, 'spaceAfter' => 0]); $element->addText('Hello '); $element->addText('there.'); $textrun = new WriterTextRun($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\ql\\sb0\\sa0{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; + $expect = "\\pard\\sb0\\sa0\\widctlpar {{Hello }{there.}}\\par\n"; self::assertEquals($expect, $this->removeCr($textrun)); } @@ -67,13 +59,11 @@ public function testTitle(): void { $parentWriter = new RTF(); $phpWord = new \PhpOffice\PhpWord\PhpWord(); - Settings::setDefaultRtl(false); $phpWord->addTitleStyle(1, [], ['spaceBefore' => 0, 'spaceAfter' => 0]); $section = $phpWord->addSection(); $element = $section->addTitle('First Heading', 1); $elwrite = new WriterTitle($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\ql\\sb0\\sa0{\\outlinelevel0{\\cf0\\f0 First Heading}\\par\n}"; + $expect = "\\pard\\sb0\\sa0\\widctlpar {\\outlinelevel0{First Heading}\\par\n}"; self::assertEquals($expect, $this->removeCr($elwrite)); - Settings::setDefaultRtl(null); } } diff --git a/tests/PhpWordTests/Writer/RTF/ElementTest.php b/tests/PhpWordTests/Writer/RTF/Element/ElementTest.php similarity index 86% rename from tests/PhpWordTests/Writer/RTF/ElementTest.php rename to tests/PhpWordTests/Writer/RTF/Element/ElementTest.php index 36504a18f8d..e9668e4f650 100644 --- a/tests/PhpWordTests/Writer/RTF/ElementTest.php +++ b/tests/PhpWordTests/Writer/RTF/Element/ElementTest.php @@ -16,7 +16,7 @@ * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ -namespace PhpOffice\PhpWordTests\Writer\RTF; +namespace PhpOffice\PhpWordTests\Writer\RTF\Element; use PhpOffice\PhpWord\Writer\RTF; @@ -25,6 +25,11 @@ */ class ElementTest extends \PHPUnit\Framework\TestCase { + /** + * @param RTF\Element\Field|RTF\Element\Table|RTF\Element\TextRun|RTF\Element\Title $field + * + * @return string + */ public function removeCr($field) { return str_replace("\r\n", "\n", $field->write()); @@ -121,25 +126,25 @@ public function testTable(): void '\\pard', "\\trowd \\cellx$width \\cellx$width2 ", '\\intbl', - '{\\cf0\\f0 1}\\par', + '\\widctlpar {1}\\par', '\\cell', '\\intbl', - '{\\cf0\\f0 2}\\par', + '{2}\\par', '\\cell', '\\row', "\\trowd \\cellx$width \\cellx$width2 ", '\\intbl', - '{\\cf0\\f0 3}\\par', + '\\widctlpar {3}\\par', '\\cell', '\\intbl', - '{\\cf0\\f0 4}\par', + '{4}\\par', '\\cell', '\\row', '\\pard', '', ]); - self::assertEquals($expect, $this->removeCr($table)); + self::assertSame($expect, $this->removeCr($table)); } public function testTextRun(): void @@ -149,8 +154,8 @@ public function testTextRun(): void $element->addText('Hello '); $element->addText('there.'); $textrun = new RTF\Element\TextRun($parentWriter, $element); - $expect = "\\pard\\nowidctlpar {{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; - self::assertEquals($expect, $this->removeCr($textrun)); + $expect = "\\pard\\widctlpar {{Hello }{there.}}\\par\n"; + self::assertSame($expect, $this->removeCr($textrun)); } public function testTextRunParagraphStyle(): void @@ -160,8 +165,8 @@ public function testTextRunParagraphStyle(): void $element->addText('Hello '); $element->addText('there.'); $textrun = new RTF\Element\TextRun($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sb0\\sa0{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; - self::assertEquals($expect, $this->removeCr($textrun)); + $expect = "\\pard\\sb0\\sa0\\widctlpar {{Hello }{there.}}\\par\n"; + self::assertSame($expect, $this->removeCr($textrun)); } public function testTitle(): void @@ -172,8 +177,8 @@ public function testTitle(): void $section = $phpWord->addSection(); $element = $section->addTitle('First Heading', 1); $elwrite = new RTF\Element\Title($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sb0\\sa0{\\outlinelevel0{\\cf0\\f0 First Heading}\\par\n}"; - self::assertEquals($expect, $this->removeCr($elwrite)); + $expect = "\\pard\\sb0\\sa0\\widctlpar {\\outlinelevel0{First Heading}\\par\n}"; + self::assertSame($expect, $this->removeCr($elwrite)); } public function testRuby(): void @@ -188,8 +193,8 @@ public function testRuby(): void $element->addRuby($baseTextRun, $rubyTextRun, $properties); $textrun = new RTF\Element\TextRun($parentWriter, $element); - $expect = "\\pard\\nowidctlpar {{base text (ruby)}}\\par\n"; - self::assertEquals($expect, $this->removeCr($textrun)); + $expect = "\\pard\\widctlpar {{base text (ruby)}}\\par\n"; + self::assertSame($expect, $this->removeCr($textrun)); } public function testRubyTitle(): void @@ -213,7 +218,7 @@ public function testRubyTitle(): void $element = $section->addTitle($textRun, 1); $elwrite = new RTF\Element\Title($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sb0\\sa2{\\outlinelevel0{\\cf0\\f0\\fs48\\b base text (ruby)}\\par\n}"; - self::assertEquals($expect, $this->removeCr($elwrite)); + $expect = "\\pard\\sb0\\sa2\\widctlpar {\\outlinelevel0{\\cf0\\fs48\\b base text (ruby)}\\par\n}"; + self::assertSame($expect, $this->removeCr($elwrite)); } } diff --git a/tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php b/tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php new file mode 100644 index 00000000000..907d8154647 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php @@ -0,0 +1,94 @@ +addSection(); + $section->addText('Basic simple bulleted list.'); + $section->addListItem('List Item 1'); + $section->addListItem('List Item 2'); + $section->addListItem('List Item 3'); + $writer = new RtfWriter($phpWord); + $content = $writer->getContent(); + $content = str_replace("\r\n", "\n", $content); + $expectedArray = [ + '\pard\widctlpar {Basic simple bulleted list.}\par', + '\ilvl0\ls1\tx720\fi-360\li720\lin720', + '{List Item 1}', + '\par', + '\ilvl0\ls1\tx720\fi-360\li720\lin720', + '{List Item 2}', + '\par', + '\ilvl0\ls1\tx720\fi-360\li720\lin720', + '{List Item 3}', + '\par', + ]; + $expected = implode("\n", $expectedArray); + self::assertStringContainsString($expected, $content); + } + + public function testListItemSingleLevel(): void + { + $phpWord = new PhpWord(); + $singlelevelNumberingStyleName = 'singleLevel'; + $phpWord->addNumberingStyle( + $singlelevelNumberingStyleName, + [ + 'type' => 'singleLevel', + 'levels' => [ + ['format' => 'decimal', 'text' => '%1.', 'left' => 360, 'hanging' => 360, 'tabPos' => 360, 'restart' => 1], + ], + ] + ); + $section = $phpWord->addSection(); + $section->addText('SingleLevel formatted list.'); + $section->addListItem('List Item 1', 0, null, $singlelevelNumberingStyleName); + $section->addListItem('List Item 2', 0, null, $singlelevelNumberingStyleName); + $section->addListItem('List Item 3', 0, null, $singlelevelNumberingStyleName); + $writer = new RtfWriter($phpWord); + $content = $writer->getContent(); + $content = str_replace("\r\n", "\n", $content); + $expectedArray = [ + '\pard\widctlpar {SingleLevel formatted list.}\par', + '\ilvl0\ls1\tx360\fi-360\li360\lin360', + '{List Item 1}', + '\par', + '\ilvl0\ls1\tx360\fi-360\li360\lin360', + '{List Item 2}', + '\par', + '\ilvl0\ls1\tx360\fi-360\li360\lin360', + '{List Item 3}', + '\par', + ]; + $expected = implode("\n", $expectedArray); + self::assertStringContainsString($expected, $content); + self::assertStringContainsString('\levelnorestart1', $content); + $table = $writer->getListTable(); + self::assertCount(1, $table); + self::assertSame('singleLevel', $table[0]['type']); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Element/PreserveTextTest.php b/tests/PhpWordTests/Writer/RTF/Element/PreserveTextTest.php new file mode 100644 index 00000000000..4bce9c86539 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Element/PreserveTextTest.php @@ -0,0 +1,83 @@ +addSection(); + $footer = $section->addFooter(); + $footer->addPreserveText('Page {PAGE} of {NUMPAGES}'); + $section->addText('First paragraph'); + $section->addPageBreak(); + $section->addText('Second paragraph'); + $section->addPageBreak(); + $section->addText('Third paragraph'); + + $writer = new RtfWriter($phpWord); + $content = $writer->getContent(); + $content = str_replace("\r\n", "\n", $content); + $expected = '\footer{Page {\field {\*\fldinst {PAGE}}{\fldrslt {}}} of {\field {\*\fldinst {NUMPAGES}}{\fldrslt {}}}}'; + self::assertStringContainsString($expected, $content); + } + + public function testPreserveTextText(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $footer = $section->addFooter(); + $footer->addPreserveText('Footertext'); + $section->addText('First paragraph'); + $section->addPageBreak(); + $section->addText('Second paragraph'); + $section->addPageBreak(); + $section->addText('Third paragraph'); + + $writer = new RtfWriter($phpWord); + $content = $writer->getContent(); + $content = str_replace("\r\n", "\n", $content); + $expected = '\footer{Footertext}'; + self::assertStringContainsString($expected, $content); + } + + public function testPreserveTextEmpty(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $footer = $section->addFooter(); + $footer->addPreserveText(''); + $section->addText('First paragraph'); + $section->addPageBreak(); + $section->addText('Second paragraph'); + $section->addPageBreak(); + $section->addText('Third paragraph'); + + $writer = new RtfWriter($phpWord); + $content = $writer->getContent(); + $content = str_replace("\r\n", "\n", $content); + $expected = '\footer{}'; + self::assertStringContainsString($expected, $content); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Element/TableTest.php b/tests/PhpWordTests/Writer/RTF/Element/TableTest.php index 7c1ceac68e5..c7955e30821 100644 --- a/tests/PhpWordTests/Writer/RTF/Element/TableTest.php +++ b/tests/PhpWordTests/Writer/RTF/Element/TableTest.php @@ -19,7 +19,6 @@ namespace PhpOffice\PhpWordTests\Writer\RTF\Element; use PhpOffice\PhpWord\Element\Table; -use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Border; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Writer\RTF; @@ -27,11 +26,6 @@ class TableTest extends \PHPUnit\Framework\TestCase { - protected function tearDown(): void - { - Settings::setDefaultRtl(null); - } - public function removeCr(WriterTable $field): string { return str_replace("\r\n", "\n", $field->write()); @@ -39,7 +33,6 @@ public function removeCr(WriterTable $field): string public function testTable(): void { - Settings::setDefaultRtl(false); $parentWriter = new RTF(); $element = new Table(); $width = 100; @@ -59,18 +52,18 @@ public function testTable(): void '\\pard', "\\trowd \\cellx$width \\cellx$width2 ", '\\intbl', - '\\ql{\\cf0\\f0 1}\\par', + '\\widctlpar {1}\\par', '\\cell', '\\intbl', - '{\\cf0\\f0 2}\\par', + '{2}\\par', '\\cell', '\\row', "\\trowd \\cellx$width \\cellx$width2 ", '\\intbl', - '\\ql{\\cf0\\f0 3}\\par', + '\\widctlpar {3}\\par', '\\cell', '\\intbl', - '{\\cf0\\f0 4}\par', + '{4}\\par', '\\cell', '\\row', '\\pard', @@ -84,7 +77,6 @@ public function testTableStyle(): void { $width = 100; - Settings::setDefaultRtl(false); $parentWriter = new RTF(); Style::addTableStyle('TableStyle', ['borderSize' => 6, 'borderColor' => '006699']); @@ -102,7 +94,7 @@ public function testTableStyle(): void '\\clbrdrr\\brdrs\\brdrw2\\brdrcf0', "\\cellx$width ", '\\intbl', - '\\ql{\\cf0\\f0 1}\\par', + '\\widctlpar {1}\\par', '\\cell', '\\row', '\\pard', @@ -116,7 +108,6 @@ public function testTableStyleNotExisting(): void { $width = 100; - Settings::setDefaultRtl(false); $parentWriter = new RTF(); $element = new Table('TableStyleNotExisting'); @@ -128,7 +119,7 @@ public function testTableStyleNotExisting(): void '\\pard', "\\trowd \\cellx$width ", '\\intbl', - '\\ql{\\cf0\\f0 1}\\par', + '\\widctlpar {1}\\par', '\\cell', '\\row', '\\pard', @@ -142,7 +133,6 @@ public function testTableCellStyle(): void { $width = 100; - Settings::setDefaultRtl(false); $parentWriter = new RTF(); $element = new Table(); @@ -158,7 +148,7 @@ public function testTableCellStyle(): void '\\clbrdrr\\brdrdot\\brdrw2\\brdrcf0', "\\cellx$width ", '\\intbl', - '\\ql{\\cf0\\f0 1}\\par', + '\\widctlpar {1}\\par', '\\cell', '\\row', '\\pard', diff --git a/tests/PhpWordTests/Writer/RTF/Element/TextBreakTest.php b/tests/PhpWordTests/Writer/RTF/Element/TextBreakTest.php new file mode 100644 index 00000000000..ca152a0e548 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Element/TextBreakTest.php @@ -0,0 +1,61 @@ +write()); + } + + /** + * Test a normal textBreak. + * See page 142-143 of RTF Specification 1.9.1. + */ + public function testTextBreakParagraph(): void + { + $parentWriter = new RTF(); + $element = new TextBreakElement(); + $writer = new TextBreakWriter($parentWriter, $element); + $expect = "\\pard\\par\n"; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test a textBreak as a line break. + * See page 142-143 of RTF Specification 1.9.1. + */ + public function testTextBreakLine(): void + { + $parentWriter = new RTF(); + $element = new TextBreakElement(); + $writer = new TextBreakWriter($parentWriter, $element, true); + $expect = "\\line\n"; + self::assertEquals($expect, $this->removeCr($writer)); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/NoParent.php b/tests/PhpWordTests/Writer/RTF/NoParent.php new file mode 100644 index 00000000000..eeb7c1ad5c4 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/NoParent.php @@ -0,0 +1,35 @@ +expectException(WordException::class); + $this->expectExceptionMessage('No parent WriterInterface assigned.'); + $noParent = new NoParent(); + $noParent->getParentWriter(); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Part/BookFoldTest.php b/tests/PhpWordTests/Writer/RTF/Part/BookFoldTest.php new file mode 100644 index 00000000000..1ba4bf3bf36 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Part/BookFoldTest.php @@ -0,0 +1,56 @@ +getSettings()->setBookFoldPrinting(true); + $section1 = $phpWord->addSection(); + $textRun1 = $section1->addTextRun(); + $textRun1->addText('Section 1 Paragraph 1'); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + $expected = '\bookfold\landscape'; + self::assertStringContainsString($expected, $content); + } + + public function testMirrorMargins(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setMirrorMargins(true); + $section1 = $phpWord->addSection(); + $textRun1 = $section1->addTextRun(); + $textRun1->addText('Section 1 Paragraph 1'); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + $expected = '\margmirror'; + self::assertStringContainsString($expected, $content); + $expected = '\facingp'; + self::assertStringContainsString($expected, $content); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/RichTextTitleTest.php b/tests/PhpWordTests/Writer/RTF/RichTextTitleTest.php new file mode 100644 index 00000000000..72d22e5eb25 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/RichTextTitleTest.php @@ -0,0 +1,51 @@ +addSection(); + $htmlContent = '

      This is heading 1

      This is heading 2

      '; + Html::addHtml($section, $htmlContent, false, false); + $elements = $section->getElements(); + self::assertInstanceOf(Title::class, $elements[0]); + self::assertInstanceOf(TextRun::class, $elements[0]->getText()); + + $writer = new RTF($phpWord); + $contents = $writer->getContent(); + self::assertStringContainsString('{This is heading 1}\par', $contents); + self::assertStringContainsString('{This is heading 2}\par', $contents); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Sample11Test.php b/tests/PhpWordTests/Writer/RTF/Sample11Test.php new file mode 100644 index 00000000000..ad6d71b0b72 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Sample11Test.php @@ -0,0 +1,138 @@ +getContent(); + $expected = '{\colortbl;\red0\green0\blue0;\red255\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;\red0\green176\blue80;\red255\green255\blue0;}'; + self::assertStringContainsString($expected, $content); + $expected = '\highlight6 highlighted'; + self::assertStringContainsString($expected, $content); + $expected = '\strike even '; + self::assertStringContainsString($expected, $content); + } + + public function testBorderColor(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->getStyle() + ->setBorderSize(5) + ->setBorderColor('FF00FF'); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + $expected = '{\colortbl;\red0\green0\blue0;\red255\green0\blue255;}'; + self::assertStringContainsString($expected, $content); + $expected = '\brdrcf2'; + self::assertStringContainsString($expected, $content); + } + + public function testFooters(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $phpWord->getSettings()->setEvenAndOddHeaders(true); + $section = $phpWord->addSection(); + $footerFirst = $section->addFooter(Footer::FIRST); + $textRunFirst = $footerFirst->addTextRun(); + $textRunFirst->addText('First Footer'); + $footerEven = $section->addFooter(Footer::EVEN); + $textRunEven = $footerEven->addTextRun(); + $textRunEven->addText('Even Footer'); + $footerAuto = $section->addFooter(Footer::AUTO); + $textRunAuto = $footerAuto->addTextRun(); + $textRunAuto->addText('Odd Footer'); + $section->addText('This should be page 1'); + $section->addPageBreak(); + $section->addText('This should be page 2'); + $section->addPageBreak(); + $section->addText('This should be page 3'); + $section->addPageBreak(); + $section->addText('This should be page 4'); + $section->addPageBreak(); + $section->addText('This should be page 5'); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + $expected = '{\footerf\pard\widctlpar {{First Footer}}\par'; + self::assertStringContainsString($expected, $content); + $expected = '{\footerl\pard\widctlpar {{Even Footer}}\par'; + self::assertStringContainsString($expected, $content); + $expected = '{\footerr\pard\widctlpar {{Odd Footer}}\par'; + self::assertStringContainsString($expected, $content); + } + + public function testPageBreakBeforeTextRun(): void + { + $phpWord = new PhpWord(); + $phpWord->addParagraphStyle('pbb', [ + 'pageBreakBefore' => true, + ]); + $section1 = $phpWord->addSection(); + $textRun1 = $section1->addTextRun(); + $textRun1->addText('Section 1 Paragraph 1'); + $section2 = $phpWord->addSection(); + $textRun2 = $section2->addTextRun('pbb'); + $textRun2->addText('Section 2 Paragraph 1'); + $textRun3 = $section2->addTextRun('pbb'); + $textRun3->addText('Section 2 Paragraph 2'); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + $expected = '\pard\widctlpar {{Section 2 Paragraph 1}}\par'; + self::assertStringContainsString($expected, $content, 'no page break'); + $expected = '\pard\widctlpar\pagebb {{Section 2 Paragraph 2}}\par'; + self::assertStringContainsString($expected, $content, 'page break'); + } + + public function testPageBreakBeforeTitle(): void + { + $phpWord = new PhpWord(); + $pbb = ['pageBreakBefore' => true]; + $phpWord->addTitleStyle( + 1, + ['bold' => true], + $pbb + ); + $section1 = $phpWord->addSection(); + $textRun1 = $section1->addTextRun(); + $textRun1->addText('Section 1 Paragraph 1'); + $section2 = $phpWord->addSection(); + $section2->addTitle('Heading1 with pbb first element in section', 1); + $section2->addTitle('Heading1 with pbb not first element in section', 1); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + $expected = '\pard\widctlpar {\outlinelevel0{\b Heading1 with pbb first element in section}'; + self::assertStringContainsString($expected, $content, 'no page break'); + $expected = '\pard\widctlpar\pagebb {\outlinelevel0{\b Heading1 with pbb not first element in section}'; + self::assertStringContainsString($expected, $content, 'page break'); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Style/BorderTest.php b/tests/PhpWordTests/Writer/RTF/Style/BorderTest.php new file mode 100644 index 00000000000..75660cf6bef --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/BorderTest.php @@ -0,0 +1,325 @@ +write()); + } + + /** + * Test Border styles in paragraph. + * See page 89-90 of RTF Specification 1.9.1 for Paragraph Borders. + */ + public function testBorderBasic(): void + { + $parentWriter = new RTF(); + $style = new BorderStyle(); + $writer = new BorderWriter($style); + $writer->setParentWriter($parentWriter); + + $expect = ''; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderSize(1); + $expect = '\brdrt\brdrs\brdrw1\brsp20 '; + $expect .= '\brdrl\brdrs\brdrw1\brsp80 '; + $expect .= '\brdrr\brdrs\brdrw1\brsp80 '; + $expect .= '\brdrb\brdrs\brdrw1\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test Border not all all four sides. + * See page 89-90 of RTF Specification 1.9.1 for Paragraph Borders. + */ + public function testBorderSide(): void + { + $parentWriter = new RTF(); + $style = new BorderStyle(); + $writer = new BorderWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setBorderLeftSize(1); + $expect = '\brdrl\brdrs\brdrw1\brsp80 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderBottomSize(100); + $expect = '\brdrl\brdrs\brdrw1\brsp80 '; + $expect .= '\brdrb\brdrs\brdrw100\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test Border styles in paragraph. + * See page 76 of RTF Specification 1.9.1 for Page Borders. + * See page 89-90 of RTF Specification 1.9.1 for Paragraph Borders. + * See page 103 of RTF Specification 1.9.1 for Row Borders and Cell Borders. + * See page 139 of RTF Specification 1.9.1 for Character Borders. + */ + public function testBorderType(): void + { + $parentWriter = new RTF(); + $style = new BorderStyle(); + $writer = new BorderWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setBorderSize(1); + + $writer->setType('section'); + $expect = '\pgbrdropt32'; + $expect .= '\pgbrdrt\brdrs\brdrw1\brsp480 '; + $expect .= '\pgbrdrl\brdrs\brdrw1\brsp480 '; + $expect .= '\pgbrdrr\brdrs\brdrw1\brsp480 '; + $expect .= '\pgbrdrb\brdrs\brdrw1\brsp480 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $writer->setType('paragraph'); + $expect = '\brdrt\brdrs\brdrw1\brsp20 '; + $expect .= '\brdrl\brdrs\brdrw1\brsp80 '; + $expect .= '\brdrr\brdrs\brdrw1\brsp80 '; + $expect .= '\brdrb\brdrs\brdrw1\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $writer->setType('font'); + $expect = '\chbrdr\brdrs\brdrw1 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $writer->setType('row'); + $expect = '\trbrdrt\brdrs\brdrw1 '; + $expect .= '\trbrdrl\brdrs\brdrw1 '; + $expect .= '\trbrdrr\brdrs\brdrw1 '; + $expect .= '\trbrdrb\brdrs\brdrw1 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $writer->setType('cell'); + $expect = '\clbrdrt\brdrs\brdrw1 '; + $expect .= '\clbrdrl\brdrs\brdrw1 '; + $expect .= '\clbrdrr\brdrs\brdrw1 '; + $expect .= '\clbrdrb\brdrs\brdrw1 '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test Border style. + * See page 89-90 of RTF Specification 1.9.1 for Paragraph Borders. + */ + public function testBorderStyle(): void + { + $parentWriter = new RTF(); + $style = new BorderStyle(); + $writer = new BorderWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setBorderStyle(BorderType::DASH_DOT_STROKED); + $expect = '\brdrt\brdrdashdotstr\brsp20 '; + $expect .= '\brdrl\brdrdashdotstr\brsp80 '; + $expect .= '\brdrr\brdrdashdotstr\brsp80 '; + $expect .= '\brdrb\brdrdashdotstr\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopStyle(BorderType::DASHED); + $style->setBorderLeftStyle(BorderType::DASH_SMALL_GAP); + $style->setBorderRightStyle(BorderType::DOT_DASH); + $style->setBorderBottomStyle(BorderType::DOT_DOT_DASH); + $expect = '\brdrt\brdrdash\brsp20 '; + $expect .= '\brdrl\brdrdashsm\brsp80 '; + $expect .= '\brdrr\brdrdashd\brsp80 '; + $expect .= '\brdrb\brdrdashdd\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopStyle(BorderType::DOTTED); + $style->setBorderLeftStyle(BorderType::DOUBLE); + $style->setBorderRightStyle(BorderType::DOUBLE_WAVE); + $style->setBorderBottomStyle(BorderType::INSET); + $expect = '\brdrt\brdrdot\brsp20 '; + $expect .= '\brdrl\brdrdb\brsp80 '; + $expect .= '\brdrr\brdrwavydb\brsp80 '; + $expect .= '\brdrb\brdrinset\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopStyle(BorderType::NIL); + $style->setBorderLeftStyle(BorderType::NONE); + $style->setBorderRightStyle(BorderType::OUTSET); + $style->setBorderBottomStyle(BorderType::THICK); + $expect = '\brdrt\brdrnil\brsp20 '; + $expect .= '\brdrl\brdrnone\brsp80 '; + $expect .= '\brdrr\brdroutset\brsp80 '; + $expect .= '\brdrb\brdrth\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopStyle(BorderType::THICK_THIN_LARGE_GAP); + $style->setBorderLeftStyle(BorderType::THICK_THIN_MEDIUM_GAP); + $style->setBorderRightStyle(BorderType::THICK_THIN_SMALL_GAP); + $style->setBorderBottomStyle(BorderType::THIN_THICK_LARGE_GAP); + $expect = '\brdrt\brdrtnthlg\brsp20 '; + $expect .= '\brdrl\brdrtnthmg\brsp80 '; + $expect .= '\brdrr\brdrtnthsg\brsp80 '; + $expect .= '\brdrb\brdrthtnlg\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopStyle(BorderType::THIN_THICK_MEDIUM_GAP); + $style->setBorderLeftStyle(BorderType::THIN_THICK_SMALL_GAP); + $style->setBorderRightStyle(BorderType::THIN_THICK_THIN_LARGE_GAP); + $style->setBorderBottomStyle(BorderType::THIN_THICK_THIN_MEDIUM_GAP); + $expect = '\brdrt\brdrthtnmg\brsp20 '; + $expect .= '\brdrl\brdrthtnsg\brsp80 '; + $expect .= '\brdrr\brdrtnthtnlg\brsp80 '; + $expect .= '\brdrb\brdrtnthtnmg\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopStyle(BorderType::THIN_THICK_THIN_SMALL_GAP); + $style->setBorderLeftStyle(BorderType::THREE_D_EMBOSS); + $style->setBorderRightStyle(BorderType::THREE_D_ENGRAVE); + $style->setBorderBottomStyle(BorderType::TRIPLE); + $expect = '\brdrt\brdrtnthtnsg\brsp20 '; + $expect .= '\brdrl\brdremboss\brsp80 '; + $expect .= '\brdrr\brdrengrave\brsp80 '; + $expect .= '\brdrb\brdrtriple\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderStyle(BorderType::WAVE); + $expect = '\brdrt\brdrwavy\brsp20 '; + $expect .= '\brdrl\brdrwavy\brsp80 '; + $expect .= '\brdrr\brdrwavy\brsp80 '; + $expect .= '\brdrb\brdrwavy\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test Border size. + * See page 89-90 of RTF Specification 1.9.1 for Paragraph Borders. + */ + public function testBorderSize(): void + { + $parentWriter = new RTF(); + $style = new BorderStyle(); + $writer = new BorderWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setBorderSize(100); + $expect = '\brdrt\brdrs\brdrw100\brsp20 '; + $expect .= '\brdrl\brdrs\brdrw100\brsp80 '; + $expect .= '\brdrr\brdrs\brdrw100\brsp80 '; + $expect .= '\brdrb\brdrs\brdrw100\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopSize(200); + $style->setBorderLeftSize(150); + $style->setBorderRightSize(50); + $style->setBorderBottomSize(20); + $expect = '\brdrt\brdrs\brdrw200\brsp20 '; + $expect .= '\brdrl\brdrs\brdrw150\brsp80 '; + $expect .= '\brdrr\brdrs\brdrw50\brsp80 '; + $expect .= '\brdrb\brdrs\brdrw20\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test Border colors. + * See page 89-90 of RTF Specification 1.9.1 for Paragraph Borders. + * + * Create test when paragraph inherits border. + */ + + /** + * Test Border space. + * See page 89-90 of RTF Specification 1.9.1 for Paragraph Borders. + */ + public function testBorderSpace(): void + { + $parentWriter = new RTF(); + $style = new BorderStyle(); + $writer = new BorderWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setBorderSpace(100); + $expect = '\brdrt\brdrs\brsp100 '; + $expect .= '\brdrl\brdrs\brsp100 '; + $expect .= '\brdrr\brdrs\brsp100 '; + $expect .= '\brdrb\brdrs\brsp100 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopSpace(200); + $style->setBorderLeftSpace(150); + $style->setBorderRightSpace(50); + $style->setBorderBottomSpace(20); + $expect = '\brdrt\brdrs\brsp200 '; + $expect .= '\brdrl\brdrs\brsp150 '; + $expect .= '\brdrr\brdrs\brsp50 '; + $expect .= '\brdrb\brdrs\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + // Space doesn't matter for fonts. + $writer->setType('font'); + $expect = ''; + self::assertEquals($expect, $this->removeCr($writer)); + + // Space doesn't matter for rows. + $writer->setType('row'); + $expect = ''; + self::assertEquals($expect, $this->removeCr($writer)); + + // Space doesn't matter for cells. + $writer->setType('cell'); + $expect = ''; + self::assertEquals($expect, $this->removeCr($writer)); + } + + public function testBorderColor(): void + { + $phpWord = new PhpWord(); + + $paragraphStyleName = 'P-Style'; + $pstyle = $phpWord->addParagraphStyle($paragraphStyleName, [ + 'spaceAfter' => 95, + 'borderTopSize' => 12, + 'borderTopColor' => 'FF0000', + 'borderBottomSize' => 12, + 'borderBottomColor' => '00FF00', + 'borderLeftSize' => 12, + 'borderLeftColor' => '0000FF', + 'borderRightSize' => 12, + 'borderRightColor' => 'FFFF00', + ]); + + $section = $phpWord->addSection(); + $section->addText('Hello', null, $pstyle); + $section->addText('Goodbye'); + + $writer = new RTF($phpWord); + $content = $writer->getContent(); + $expected = '{\colortbl;\red0\green0\blue0;\red255\green0\blue0;\red0\green0\blue255;\red255\green255\blue0;\red0\green255\blue0;}'; + self::assertStringContainsString($expected, $content); + $expected = '\pard\sa95\widctlpar\brdrt\brdrs\brdrw12\brdrcf2\brsp20 \brdrl\brdrs\brdrw12\brdrcf3\brsp80 \brdrr\brdrs\brdrw12\brdrcf4\brsp80 \brdrb\brdrs\brdrw12\brdrcf5\brsp20 {Hello}\par'; + self::assertStringContainsString($expected, $content); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Style/FontTest.php b/tests/PhpWordTests/Writer/RTF/Style/FontTest.php new file mode 100644 index 00000000000..c66d531c4db --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/FontTest.php @@ -0,0 +1,264 @@ +write()); + } + + /** + * Test font and color. + * See page 131 of RTF Specification 1.9.1 for Font (Character). + * See page 142 of RTF Specification 1.9.1 for Highlighting. + */ + public function testFontColor(): void + { + $parentWriter = new RTF(); + $style = new FontStyle(); + $writer = new FontWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setName('Times New Roman'); + $style->setFallbackFont('serif'); // currently does nothing in RTF, see pg. 18 of spec for implementation + $style->setSize(24); + $style->setColor(Color::YELLOW); + $style->setFgColor(Color::RED); + $style->setBgColor('#123456'); + $expect = '\f0\cf0\fs48\highlight0\chshdng0\chcbpat0\cb0 '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test font and color after registering header tables. + */ + public function testFontColorRegistered(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $parentWriter = new RTF($phpWord); + $style = new FontStyle(); + $element = new TextElement(); + $writer = new TextWriter($parentWriter, $element, true); + + $style->setName('Times New Roman'); + $style->setFallbackFont('serif'); // currently does nothing in RTF, see pg. 18 of spec for implementation + $style->setSize(24); + $style->setColor(Color::YELLOW); + $style->setFgColor(Color::RED); + $style->setBgColor('#123456'); + + $phpWord->addFontStyle('style1', $style); + $parentWriter->getWriterPart('Header')->write(); + + $element->setText('Test'); + $element->setFontStyle($style); + + $expect = '{\f1\cf2\fs48\highlight3\chshdng0\chcbpat4\cb4 Test}'; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test formatting. + * See page 130-133 of RTF Specification 1.9.1 for Font (Character). + */ + public function testFontFormatting(): void + { + $parentWriter = new RTF(); + $style = new FontStyle(); + $writer = new FontWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setAllCaps(true); + $style->setBold(true); + $style->setdoubleStrikethrough(true); + $style->setHidden(true); + $style->setItalic(true); + $style->setNoProof(true); + $style->setSubScript(true); + $expect = '\b\i\striked1\caps\v\sub\noproof\lang1024 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setSmallCaps(true); + $style->setStrikethrough(true); + $style->setSuperScript(true); + $style->setNoProof(false); + $expect = '\b\i\strike\scaps\v\super '; + self::assertEquals($expect, $this->removeCr($writer)); + + // Disable styles (in case default is enabled) + $style->setBold(false); + $style->setHidden(false); + $style->setItalic(false); + $style->setSmallCaps(false); // should display \caps0\scaps0; currently doesn't work + $style->setStrikethrough(false); + $style->setSuperScript(false); // should display \nosupersub; currently doesn't work + $expect = '\b0\i0\strike0\v0 '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test underline. + * See page 132-133 of RTF Specification 1.9.1 for Formatting. + */ + public function testFontUnderline(): void + { + $parentWriter = new RTF(); + $style = new FontStyle(); + $writer = new FontWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setUnderline($style::UNDERLINE_DASH); + $expect = '\uldash '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DASHEDHEAVY); + $expect = '\ulthdash '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DASHLONG); + $expect = '\ulldash '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DASHLONGHEAVY); + $expect = '\ulthldash '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DOUBLE); + $expect = '\uldb '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DOTDASH); + $expect = '\uldashd '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DASHDOTHEAVY); + $expect = '\ulthdashd '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DOTDOTDASH); + $expect = '\uldashdd '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DASHDOTDOTHEAVY); + $expect = '\ulthdashdd '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DOTTED); + $expect = '\uld '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DOTTEDHEAVY); + $expect = '\ulthd '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_HEAVY); + $expect = '\ulth '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_SINGLE); + $expect = '\ul '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_WAVY); + $expect = '\ulwave '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_WAVYDOUBLE); + $expect = '\ululdbwave '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_WAVYHEAVY); + $expect = '\ulhwave '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_WORDS); + $expect = '\ulw '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_NONE); // should display \ulnone\ul0; currently doesn't work + $expect = ''; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test language. + * See page 132 of RTF Specification 1.9.1 for Spacing. + */ + public function testFontLang(): void + { + $parentWriter = new RTF(); + $style = new FontStyle(); + $writer = new FontWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setRTL(true); + $style->setLang(Language::KO_KR); + $expect = '\lang1042\rtlch '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setRTL(false); + $style->setLang(Language::EN_US); + $expect = '\lang1033\ltrch '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setRTL(false); + $style->setLang(Language::EN_US); + $style->setNoProof(true); + $expect = '\langnp1033\noproof\lang1024\ltrch '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test font spacing settings. + */ + public function testFontSpacing(): void + { + $parentWriter = new RTF(); + $style = new FontStyle(); + $writer = new FontWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setScale(5); + $style->setSpacing(4); + $style->setKerning(100); + $style->setPosition(10); + $expect = '\charscalex5\expnd1\expndtw4\kerning200\up10 '; + self::assertEquals($expect, $this->removeCr($writer)); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Style/IndentationTest.php b/tests/PhpWordTests/Writer/RTF/Style/IndentationTest.php new file mode 100644 index 00000000000..f555263ddb3 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/IndentationTest.php @@ -0,0 +1,73 @@ +write()); + } + + /** + * Test indentation. + * See page 79 of RTF Specification 1.9.1 for Indentation. + */ + public function testIndentation(): void + { + $parentWriter = new RTF(); + $style = new IndentationStyle(); + $writer = new IndentationWriter($style); + $writer->setParentWriter($parentWriter); + + $expect = ' '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setLeft(1440); + $style->setRight(720); + $style->setFirstLine(360); + $style->setFirstLineChars(180); + $expect = '\fi360\cufi180\li1440\ri720 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style = new IndentationStyle(); + $writer = new IndentationWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setLeft(1440); + $style->setRight(720); + $style->setHanging(360); + $expect = '\fi-360\li1440\ri720 '; + self::assertEquals($expect, $this->removeCr($writer)); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Style/RtlTest.php b/tests/PhpWordTests/Writer/RTF/Style/RtlTest.php new file mode 100644 index 00000000000..d21581a1bb9 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/RtlTest.php @@ -0,0 +1,70 @@ +write()); + } + + public function testRTL(): void + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('אב גד', ['RTL' => true]); + $text = new RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\widctlpar {\\rtlch \\uc0\\u1488 \\uc0\\u1489 \\uc0\\u1490 \\uc0\\u1491 }\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } + + public function testRTL2(): void + { + Settings::setDefaultRtl(true); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('אב גד'); + $text = new RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\qr\\rtlpar\\widctlpar {\\rtlch \\uc0\\u1488 \\uc0\\u1489 \\uc0\\u1490 \\uc0\\u1491 }\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } + + public function testPageBreakLineHeight2(): void + { + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); + $text = new RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\ql\\sl259\\slmult1\\widctlpar\\pagebb {\\ltrch New page}\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Style/SectionTest.php b/tests/PhpWordTests/Writer/RTF/Style/SectionTest.php new file mode 100644 index 00000000000..f0f3a829e2b --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/SectionTest.php @@ -0,0 +1,124 @@ +addSection(); + $style = $section->getStyle(); + $style->setVAlign($vAlign); + $writer = new RTF($phpWord); + self::assertStringContainsString($expect, $writer->getContent()); + } + + public static function verticalAlignProvider(): array + { + return [ + [VerticalJc::TOP, '\vertalt'], + [VerticalJc::CENTER, '\vertalc'], + [VerticalJc::BOTH, '\vertalj'], + [VerticalJc::BOTTOM, '\vertalb'], + ]; + } + + public function testNoVerticalAlignNoBreakType(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $style = $section->getStyle(); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + self::assertStringNotContainsString('vert', $content); + self::assertStringNotContainsString('sbk', $content); + } + + /** @dataProvider breakTypeProvider */ + public function testBreakType(string $breakType, string $expect): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $style = $section->getStyle(); + $style->setBreakType($breakType); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + self::assertStringContainsString($expect, $content); + } + + public static function breakTypeProvider(): array + { + return [ + ['nextPage', '\sbkpage'], + ['nextColumn', '\sbkcol'], + ['continuous', '\sbknone'], + ['evenPage', '\sbkeven'], + ['oddPage', '\sbkodd'], + ]; + } + + public function testColsNum(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $style = $section->getStyle(); + $style->setColsNum(5); + $style->setColsSpace(7); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + self::assertStringContainsString('\cols5', $content); + self::assertStringContainsString('\colsx7', $content); + } + + public function testPageSize(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $temp1 = $section->getStyle()->getOrientation(); + self::assertSame('portrait', $temp1); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + + self::assertStringContainsString('\sectd \pgwsxn11906\pghsxn16838', $content); + $temp2 = $section->getStyle()->getOrientation(); + self::assertSame('portrait', $temp2); + } + + public function testPageSizeBookFold(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $phpWord->getSettings()->setBookFoldPrinting(true); + $temp1 = $section->getStyle()->getOrientation(); + self::assertSame('portrait', $temp1); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + + self::assertStringContainsString('\sectd \pgwsxn8419\pghsxn11906', $content); + $temp2 = $section->getStyle()->getOrientation(); + self::assertSame('landscape', $temp2); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Style/SpacingTest.php b/tests/PhpWordTests/Writer/RTF/Style/SpacingTest.php new file mode 100644 index 00000000000..e27d36df50f --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/SpacingTest.php @@ -0,0 +1,46 @@ +write()); + } + + public function testSpacing(): void + { + $parentWriter = new RTF(); + $style = new SpacingStyle(); + $writer = new SpacingWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setLineRule(LineSpacingRule::EXACT); + $style->setLine(5); + $expect = '\sl-5\slmult0'; + self::assertEquals($expect, $this->removeCr($writer)); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php b/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php new file mode 100644 index 00000000000..019a3f8134e --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php @@ -0,0 +1,82 @@ +write()); + } + + /** + * Test empty styles. + */ + public function testEmptyStyles(): void + { + $styles = ['Font', 'Paragraph', 'Section', 'Tab', 'Indentation']; + foreach ($styles as $style) { + $objectClass = 'PhpOffice\\PhpWord\\Writer\\RTF\\Style\\' . $style; + $object = new $objectClass(); + + self::assertEquals('', $object->write()); + } + } + + public function testPageBreakLineHeight(): void + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); + $text = new RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\sl259\\slmult1\\widctlpar\\pagebb {New page}\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } + + public function testPageNumberRestart(): void + { + //$parentWriter = new RTF(); + $phpword = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpword->addSection(['pageNumberingStart' => 5]); + $styleWriter = new RTF\Style\Section($section->getStyle()); + $wstyle = $this->removeCr($styleWriter); + // following have default values which might change so don't use them + $wstyle = preg_replace('/\\\\pgwsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\pghsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\margtsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\margrsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\margbsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\marglsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\headery\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\footery\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\guttersxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/ +/', ' ', $wstyle); + $expect = "\\sectd \\pgnstarts5\\pgnrestart \n"; + self::assertEquals($expect, $wstyle); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Style/TabTest.php b/tests/PhpWordTests/Writer/RTF/Style/TabTest.php new file mode 100644 index 00000000000..809224f6794 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/TabTest.php @@ -0,0 +1,119 @@ +write()); + } + + /** + * Test tab stops. + * See page 83 of RTF Specification 1.9.1 for Tabs. + */ + public function testTabStop(): void + { + $parentWriter = new RTF(); + $style = new TabStyle(); + $writer = new TabWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setPosition(3000); + $style->setType($style::TAB_STOP_CLEAR); + $expect = '\tx3000'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setType(TabStyle::TAB_STOP_LEFT); + $expect = '\tx3000'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setType(TabStyle::TAB_STOP_CENTER); + $expect = '\tqc\tx3000'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setType(TabStyle::TAB_STOP_RIGHT); + $expect = '\tqr\tx3000'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setType(TabStyle::TAB_STOP_DECIMAL); + $expect = '\tqdec\tx3000'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setType(TabStyle::TAB_STOP_BAR); + $expect = '\tb3000'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setType(TabStyle::TAB_STOP_NUM); // No equivalent specified in RTF + $expect = '\tx3000'; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test tab leaders. + * See page 83 of RTF Specification 1.9.1 for Tabs. + */ + public function testTabLeader(): void + { + $parentWriter = new RTF(); + $style = new TabStyle(); + $writer = new TabWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setPosition(600); + $style->setLeader(TabStyle::TAB_LEADER_NONE); + $expect = '\tx600'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setLeader(TabStyle::TAB_LEADER_DOT); + $expect = '\tldot\tx600'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setLeader(TabStyle::TAB_LEADER_HYPHEN); + $expect = '\tlhyph\tx600'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setLeader(TabStyle::TAB_LEADER_UNDERSCORE); + $expect = '\tlul\tx600'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setLeader(TabStyle::TAB_LEADER_HEAVY); + $expect = '\tlth\tx600'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setLeader(TabStyle::TAB_LEADER_MIDDLEDOT); + $expect = '\tlmdot\tx600'; + self::assertEquals($expect, $this->removeCr($writer)); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/StyleTest.php b/tests/PhpWordTests/Writer/RTF/StyleTest.php deleted file mode 100644 index 8ba2bcb9c90..00000000000 --- a/tests/PhpWordTests/Writer/RTF/StyleTest.php +++ /dev/null @@ -1,183 +0,0 @@ -write()); - } - - /** - * Test empty styles. - */ - public function testEmptyStyles(): void - { - $styles = ['Font', 'Paragraph', 'Section', 'Tab', 'Indentation']; - foreach ($styles as $style) { - $objectClass = 'PhpOffice\\PhpWord\\Writer\\RTF\\Style\\' . $style; - $object = new $objectClass(); - - self::assertEquals('', $object->write()); - } - } - - public function testBorderWithNonRegisteredColors(): void - { - $border = new Border(); - $border->setSizes([1, 2, 3, 4]); - $border->setColors(['#FF0000', '#FF0000', '#FF0000', '#FF0000']); - $border->setSizes([20, 20, 20, 20]); - - $content = $border->write(); - - $expected = '\pgbrdropt32'; - $expected .= '\pgbrdrt\brdrs\brdrw20\brdrcf0\brsp480 '; - $expected .= '\pgbrdrl\brdrs\brdrw20\brdrcf0\brsp480 '; - $expected .= '\pgbrdrr\brdrs\brdrw20\brdrcf0\brsp480 '; - $expected .= '\pgbrdrb\brdrs\brdrw20\brdrcf0\brsp480 '; - - self::assertEquals($expected, $content); - } - - public function testIndentation(): void - { - $indentation = new \PhpOffice\PhpWord\Style\Indentation(); - $indentation->setLeft(1); - $indentation->setRight(2); - $indentation->setFirstLine(3); - - $indentWriter = new RTF\Style\Indentation($indentation); - $indentWriter->setParentWriter(new RTF()); - $result = $indentWriter->write(); - - Assert::assertEquals('\fi3\li1\ri2 ', $result); - } - - public function testRightTab(): void - { - $tabRight = new \PhpOffice\PhpWord\Style\Tab(); - $tabRight->setType(\PhpOffice\PhpWord\Style\Tab::TAB_STOP_RIGHT); - $tabRight->setPosition(5); - - $tabWriter = new RTF\Style\Tab($tabRight); - $tabWriter->setParentWriter(new RTF()); - $result = $tabWriter->write(); - - Assert::assertEquals('\tqr\tx5', $result); - } - - public function testCenterTab(): void - { - $tabRight = new \PhpOffice\PhpWord\Style\Tab(); - $tabRight->setType(\PhpOffice\PhpWord\Style\Tab::TAB_STOP_CENTER); - - $tabWriter = new RTF\Style\Tab($tabRight); - $tabWriter->setParentWriter(new RTF()); - $result = $tabWriter->write(); - - Assert::assertEquals('\tqc\tx0', $result); - } - - public function testDecimalTab(): void - { - $tabRight = new \PhpOffice\PhpWord\Style\Tab(); - $tabRight->setType(\PhpOffice\PhpWord\Style\Tab::TAB_STOP_DECIMAL); - - $tabWriter = new RTF\Style\Tab($tabRight); - $tabWriter->setParentWriter(new RTF()); - $result = $tabWriter->write(); - - Assert::assertEquals('\tqdec\tx0', $result); - } - - public function testRTL(): void - { - $parentWriter = new RTF(); - $element = new \PhpOffice\PhpWord\Element\Text('אב גד', ['RTL' => true]); - $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar {\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; - self::assertEquals($expect, $this->removeCr($text)); - } - - public function testRTL2(): void - { - Settings::setDefaultRtl(true); - $parentWriter = new RTF(); - $element = new \PhpOffice\PhpWord\Element\Text('אב גד'); - $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\qr{\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; - self::assertEquals($expect, $this->removeCr($text)); - } - - public function testPageBreakLineHeight(): void - { - $parentWriter = new RTF(); - $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); - $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sl259\\slmult1\\page{\\cf0\\f0 New page}\\par\n"; - self::assertEquals($expect, $this->removeCr($text)); - } - - public function testPageBreakLineHeight2(): void - { - Settings::setDefaultRtl(false); - $parentWriter = new RTF(); - $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); - $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\ql\\sl259\\slmult1\\page{\\cf0\\f0 New page}\\par\n"; - self::assertEquals($expect, $this->removeCr($text)); - } - - public function testPageNumberRestart(): void - { - //$parentWriter = new RTF(); - $phpword = new \PhpOffice\PhpWord\PhpWord(); - $section = $phpword->addSection(['pageNumberingStart' => 5]); - $styleWriter = new RTF\Style\Section($section->getStyle()); - $wstyle = $this->removeCr($styleWriter); - // following have default values which might change so don't use them - $wstyle = preg_replace('/\\\\pgwsxn\\d+/', '', $wstyle); - $wstyle = preg_replace('/\\\\pghsxn\\d+/', '', $wstyle); - $wstyle = preg_replace('/\\\\margtsxn\\d+/', '', $wstyle); - $wstyle = preg_replace('/\\\\margrsxn\\d+/', '', $wstyle); - $wstyle = preg_replace('/\\\\margbsxn\\d+/', '', $wstyle); - $wstyle = preg_replace('/\\\\marglsxn\\d+/', '', $wstyle); - $wstyle = preg_replace('/\\\\headery\\d+/', '', $wstyle); - $wstyle = preg_replace('/\\\\footery\\d+/', '', $wstyle); - $wstyle = preg_replace('/\\\\guttersxn\\d+/', '', $wstyle); - $wstyle = preg_replace('/ +/', ' ', $wstyle); - $expect = "\\sectd \\pgnstarts5\\pgnrestart \n"; - self::assertEquals($expect, $wstyle); - } -} diff --git a/tests/PhpWordTests/Writer/RTF/WidowTest.php b/tests/PhpWordTests/Writer/RTF/WidowTest.php new file mode 100644 index 00000000000..9214ec32118 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/WidowTest.php @@ -0,0 +1,53 @@ +addSection(); + $textRun1 = $section1->addTextRun(); + $textRun1->addText('Section 1 Paragraph 1'); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + $expected = '\widowctrl'; + self::assertStringNotContainsString($expected, $content, 'should not contain widowctrl'); + } + + public function testTrueWidow(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setRtfWidowControl(true); + $section1 = $phpWord->addSection(); + $textRun1 = $section1->addTextRun(); + $textRun1->addText('Section 1 Paragraph 1'); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + $expected = '\deftab720\viewkind1\uc1\widowctrl\lang1036\kerning1\fs20'; + self::assertStringContainsString($expected, $content, 'should contain widowctrl'); + } +} diff --git a/tests/PhpWordTests/Writer/RTFTest.php b/tests/PhpWordTests/Writer/RTFTest.php index 4f9f8944ac6..06372030be7 100644 --- a/tests/PhpWordTests/Writer/RTFTest.php +++ b/tests/PhpWordTests/Writer/RTFTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\RTF. - * - * @runTestsInSeparateProcesses */ class RTFTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php b/tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php index 4951da39ce6..d422ec9457b 100644 --- a/tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php @@ -190,6 +190,7 @@ public function testValueAxisTitle(): void 'showGridY' => true, 'showLegend' => false, 'valueAxisTitle' => 'Values', + 'categoryAxisTitle' => 'Categories', ]; $chartType = 'line'; $categories = ['A', 'B', 'C', 'D', 'E']; @@ -208,6 +209,10 @@ public function testValueAxisTitle(): void self::assertTrue($doc->elementExists($element)); $element .= '/c:title/c:tx/c:rich/a:p/a:r/a:t'; self::assertEquals('Values', $doc->getElement($element)->nodeValue); + $element = "$path/c:catAx"; + self::assertTrue($doc->elementExists($element)); + $element .= '/c:title/c:tx/c:rich/a:p/a:r/a:t'; + self::assertEquals('Categories', $doc->getElement($element)->nodeValue); } public function testNoAxisLabels(): void diff --git a/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php b/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php index d3128e80076..cfad51263d8 100644 --- a/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php @@ -49,7 +49,8 @@ public function testWriteWithRefType(): void ], [ 'InsertParagraphNumberRelativeContext', - 'CreateHyperLink', + 'h', // could have been supplied as 'CreateHyperLink' or '\h' + 'NumberSeperatorSequence' => ',', ] ); @@ -65,7 +66,7 @@ public function testWriteWithRefType(): void $bookMarkElement = $doc->getElement($refFieldPath); self::assertNotNull($bookMarkElement); - self::assertEquals(' REF my-bookmark \r \h ', $bookMarkElement->textContent); + self::assertEquals(' REF my-bookmark \r \h \d , ', $bookMarkElement->textContent); $bookmarkPath = '/w:document/w:body/w:bookmarkStart'; self::assertTrue($doc->elementExists($bookmarkPath)); diff --git a/tests/PhpWordTests/Writer/Word2007/Element/ImageSvgTest.php b/tests/PhpWordTests/Writer/Word2007/Element/ImageSvgTest.php new file mode 100644 index 00000000000..12edba8d5d0 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/ImageSvgTest.php @@ -0,0 +1,77 @@ +addSection(); + $svg = $section->addImage( + 'samples/resources/sample.svg' + ); + $svg->getStyle()->setWidth(0); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + $path = '/w:document/w:body/w:p[1]/w:r[1]/w:drawing/wp:inline/wp:extent'; + self::assertTrue($doc->elementExists($path)); + self::assertSame( + self::CX, + $doc->getElementAttribute($path, 'cx') + ); + self::assertSame( + self::CY, + $doc->getElementAttribute($path, 'cy') + ); + } + + public function testNoStyle(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $svg = $section->addImage( + 'samples/resources/sample.svg' + ); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + $path = '/w:document/w:body/w:p[1]/w:r[1]/w:drawing/wp:inline/wp:extent'; + self::assertTrue($doc->elementExists($path)); + self::assertSame( + self::CX, + $doc->getElementAttribute($path, 'cx') + ); + self::assertSame( + self::CY, + $doc->getElementAttribute($path, 'cy') + ); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartClass.php b/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartClass.php new file mode 100644 index 00000000000..395c0f80da6 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartClass.php @@ -0,0 +1,29 @@ +getMockForAbstractClass(Word2007\Part\AbstractPart::class); - } else { - /** @var Word2007\Part\AbstractPart $stub */ - $stub = new class() extends Word2007\Part\AbstractPart { - public function write(): string - { - return ''; - } - }; - } + $stub = new AbstractPartClass(); $stub->setParentWriter(new Word2007()); self::assertEquals(new Word2007(), $stub->getParentWriter()); } @@ -55,18 +41,7 @@ public function testSetGetParentWriterNull(): void { $this->expectException(Exception::class); $this->expectExceptionMessage('No parent WriterInterface assigned.'); - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $stub = $this->getMockForAbstractClass(Word2007\Part\AbstractPart::class); - } else { - /** @var Word2007\Part\AbstractPart $stub */ - $stub = new class() extends Word2007\Part\AbstractPart { - public function write(): string - { - return ''; - } - }; - } + $stub = new AbstractPartClass(); $stub->getParentWriter(); } } diff --git a/tests/PhpWordTests/Writer/Word2007/Part/CommentsTest.php b/tests/PhpWordTests/Writer/Word2007/Part/CommentsTest.php index 8f8c8240a99..c7c9a70e66d 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/CommentsTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/CommentsTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\Word2007\Part\Comment. - * - * @runTestsInSeparateProcesses */ class CommentsTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php b/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php index d1f5b2585d5..d13a975d535 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php @@ -28,8 +28,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\Word2007\Part\Document. - * - * @runTestsInSeparateProcesses */ class DocumentTest extends \PHPUnit\Framework\TestCase { @@ -383,6 +381,9 @@ public function testWriteTextBreak(): void self::assertEquals($pName, $element->getAttribute('w:val')); } + /** @var string[] */ + protected static $possibleMethods = ['assertMatchesRegularExpression', 'assertRegExp']; + /** * covers ::_writeImage. */ @@ -407,12 +408,16 @@ public function testWriteImage(): void // behind $element = $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:pict/v:shape'); $style = $element->getAttribute('style'); - // @phpstan-ignore-next-line - if (method_exists(self::class, 'assertMatchesRegularExpression')) { - self::assertMatchesRegularExpression('/z\-index:\-[0-9]*/', $style); - } elseif (method_exists(self::class, 'assertRegExp')) { // @phpstan-ignore-line - self::assertRegExp('/z\-index:\-[0-9]*/', $style); - } else { + $found = false; + foreach (self::$possibleMethods as $method) { + if (method_exists(self::class, $method)) { + self::$method('/z\-index:\-[0-9]*/', $style); + $found = true; + + break; + } + } + if (!$found) { self::fail('Unsure how to test regexp'); } diff --git a/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php b/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php index beb6e971e28..aef48c6cc49 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php @@ -27,8 +27,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Part\Footer. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Part\Footer - * - * @runTestsInSeparateProcesses */ class FooterTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php b/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php index d8bf6785415..d795491cdc4 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php @@ -24,8 +24,6 @@ /** * @coversNothing - * - * @runTestsInSeparateProcesses */ class FootnotesTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php b/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php index 164365f1139..b506949a512 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php @@ -25,8 +25,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\Word2007\Part\Header. - * - * @runTestsInSeparateProcesses */ class HeaderTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Part/NumberingTest.php b/tests/PhpWordTests/Writer/Word2007/Part/NumberingTest.php index 1cf596fc85e..5c75d951fa8 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/NumberingTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/NumberingTest.php @@ -28,8 +28,6 @@ * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Part\Numbering * - * @runTestsInSeparateProcesses - * * @since 0.10.0 */ class NumberingTest extends \PHPUnit\Framework\TestCase diff --git a/tests/PhpWordTests/Writer/Word2007/Part/StylesTest.php b/tests/PhpWordTests/Writer/Word2007/Part/StylesTest.php index 09936d6d33a..563b147884b 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/StylesTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/StylesTest.php @@ -19,7 +19,9 @@ namespace PhpOffice\PhpWordTests\Writer\Word2007\Part; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Jc; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; use PhpOffice\PhpWordTests\TestHelperDOCX; @@ -28,8 +30,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Part\Styles. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Part\Styles - * - * @runTestsInSeparateProcesses */ class StylesTest extends \PHPUnit\Framework\TestCase { @@ -38,6 +38,8 @@ class StylesTest extends \PHPUnit\Framework\TestCase */ protected function tearDown(): void { + Settings::restoreDefaults(); + Style::resetStyles(); TestHelperDOCX::clear(); } diff --git a/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php b/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php index c0e2653a7c9..6b1fd98905e 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php @@ -20,14 +20,13 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Shared\Html; +use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWordTests\TestHelperDOCX; /** * Test class for PhpOffice\PhpWord\Writer\Word2007\Style\Font. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Style\Font - * - * @runTestsInSeparateProcesses */ class FontTest extends \PHPUnit\Framework\TestCase { @@ -198,4 +197,26 @@ public static function testRgb(): void self::assertTrue($doc->elementExists($styelem . '/w:color')); self::assertSame('A7D9C1', $doc->getElementAttribute($styelem . '/w:color', 'w:val')); } + + public function testUnderline(): void + { + $phpWord = new PhpWord(); + $phpWord->addFontStyle('underlineStyle', [ + 'underline' => Font::UNDERLINE_DOTDOTDASH, + ]); + $section = $phpWord->addSection(); + $section->addText('Sample text.', 'underlineStyle'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + $doc->setDefaultFile('word/styles.xml'); + $s2a = '/w:styles/w:style[3]'; + self::assertTrue($doc->elementExists($s2a)); + self::assertSame('character', $doc->getElementAttribute($s2a, 'w:type')); + $name = "$s2a/w:name"; + self::assertTrue($doc->elementExists($name)); + self::assertSame('underlineStyle', $doc->getElementAttribute($name, 'w:val')); + $u = "$s2a/w:rPr/w:u"; + self::assertTrue($doc->elementExists($u)); + self::assertSame('dotDotDash', $doc->getElementAttribute($u, 'w:val')); + } } diff --git a/tests/PhpWordTests/Writer/Word2007/Style/ImageTest.php b/tests/PhpWordTests/Writer/Word2007/Style/ImageTest.php index 2b5c25e01f2..a14549410cc 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/ImageTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/ImageTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Style\Font. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Style\Frame - * - * @runTestsInSeparateProcesses */ class ImageTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Style/ParagraphTest.php b/tests/PhpWordTests/Writer/Word2007/Style/ParagraphTest.php index c1f985ccd2b..238d2a2d280 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/ParagraphTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/ParagraphTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Style\Paragraph. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Style\Paragraph - * - * @runTestsInSeparateProcesses */ class ParagraphTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Style/SectionTest.php b/tests/PhpWordTests/Writer/Word2007/Style/SectionTest.php index f527ded1909..80f0c1f989d 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/SectionTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/SectionTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Style\Section. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Style\Section - * - * @runTestsInSeparateProcesses */ class SectionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Style/TableCellTest.php b/tests/PhpWordTests/Writer/Word2007/Style/TableCellTest.php index bd3f587b75a..25b5ec93554 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/TableCellTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/TableCellTest.php @@ -26,8 +26,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Style\Table. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Style\Table - * - * @runTestsInSeparateProcesses */ class TableCellTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Style/TableTest.php b/tests/PhpWordTests/Writer/Word2007/Style/TableTest.php index b10d8365682..58b51863423 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/TableTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/TableTest.php @@ -28,8 +28,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Style\Table. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Style\Table - * - * @runTestsInSeparateProcesses */ class TableTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007Test.php b/tests/PhpWordTests/Writer/Word2007Test.php index 64f6a29b520..6cac9727ed2 100644 --- a/tests/PhpWordTests/Writer/Word2007Test.php +++ b/tests/PhpWordTests/Writer/Word2007Test.php @@ -28,8 +28,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\Word2007. - * - * @runTestsInSeparateProcesses */ class Word2007Test extends AbstractWebServerEmbedded { @@ -76,6 +74,9 @@ public function testConstruct(): void /** * Save. + * Method getRemoteGifImageUrl seems to require separate process. + * + * @runInSeparateProcess */ public function testSave(): void { diff --git a/tests/PhpWordTests/XmlDocument.php b/tests/PhpWordTests/XmlDocument.php index 8c865932ba9..7a76c85ddec 100644 --- a/tests/PhpWordTests/XmlDocument.php +++ b/tests/PhpWordTests/XmlDocument.php @@ -23,6 +23,7 @@ use DOMNode; use DOMNodeList; use DOMXPath; +use Exception; /** * DOM wrapper class. @@ -157,7 +158,13 @@ public function getNodeList(string $path, string $file = ''): DOMNodeList $this->xpath->registerNamespace('w14', 'http://schemas.microsoft.com/office/word/2010/wordml'); } - return $this->xpath->query($path); + $temp = $this->xpath->query($path); + if ($temp === false) { + throw new Exception('xpath query returned false'); + } + + /** @var DOMNodeList $temp */ + return $temp; } /** diff --git a/tests/PhpWordTests/ZampleTest.php b/tests/PhpWordTests/ZampleTest.php new file mode 100644 index 00000000000..b5312b06363 --- /dev/null +++ b/tests/PhpWordTests/ZampleTest.php @@ -0,0 +1,108 @@ + $path] + */ + public static function getSamples(): array + { + // Populate samples + $baseDir = realpath('samples'); + if ($baseDir === false) { + // @codeCoverageIgnoreStart + throw new RuntimeException('realpath returned false'); + // @codeCoverageIgnoreEnd + } + $directory = new RecursiveDirectoryIterator($baseDir); + $iterator = new RecursiveIteratorIterator($directory); + $regex = new RegexIterator($iterator, '/Sample_\\d+_.+[.]php$/', RecursiveRegexIterator::GET_MATCH); + + $files = []; + /** @var string[] $file */ + foreach ($regex as $file) { + $file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0])); + $info = pathinfo($file); + $category = 'PhpWord'; + $name = str_replace('_', ' ', (string) preg_replace('/(|\.php)/', '', $info['filename'])); + if (!isset($files[$category])) { + $files[$category] = []; + } + $files[$category][$name] = $file; + } + + // Sort everything + ksort($files); + foreach ($files as &$f) { + asort($f); + } + + return $files; + } +} diff --git a/tests/PhpWordTests/_files/documents/word.2474.docx b/tests/PhpWordTests/_files/documents/word.2474.docx new file mode 100644 index 00000000000..8ecbaef2b3b Binary files /dev/null and b/tests/PhpWordTests/_files/documents/word.2474.docx differ diff --git a/tests/PhpWordTests/_files/html/charset.ISO-8859-1.html b/tests/PhpWordTests/_files/html/charset.ISO-8859-1.html new file mode 100644 index 00000000000..fd27c975f3c --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.ISO-8859-1.html @@ -0,0 +1,17 @@ + + + + + ISO-8859-1 + + +

      1

      +

      B1

      +

      1

      +

      D1

      +

      2

      +

      B2

      +

      C2

      +

      2

      + + diff --git a/tests/PhpWordTests/_files/html/charset.ISO-8859-1.html4.html b/tests/PhpWordTests/_files/html/charset.ISO-8859-1.html4.html new file mode 100644 index 00000000000..8a148945170 --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.ISO-8859-1.html4.html @@ -0,0 +1,17 @@ + + + + + ISO-8859-1 Html4 Doctype and Meta + + +

      1

      +

      B1

      +

      1

      +

      D1

      +

      2

      +

      B2

      +

      C2

      +

      2

      + + diff --git a/tests/PhpWordTests/_files/html/charset.ISO-8859-2.html b/tests/PhpWordTests/_files/html/charset.ISO-8859-2.html new file mode 100644 index 00000000000..c2b494ff99e --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.ISO-8859-2.html @@ -0,0 +1,17 @@ + + + + + ISO-8859-2 + + +

      1

      +

      B1

      +

      1

      +

      D1

      +

      2

      +

      B2

      +

      C2

      +

      2

      + + diff --git a/tests/PhpWordTests/_files/html/charset.UTF-16.bebom.html b/tests/PhpWordTests/_files/html/charset.UTF-16.bebom.html new file mode 100644 index 00000000000..6b29e7d2b01 Binary files /dev/null and b/tests/PhpWordTests/_files/html/charset.UTF-16.bebom.html differ diff --git a/tests/PhpWordTests/_files/html/charset.UTF-16.lebom.html b/tests/PhpWordTests/_files/html/charset.UTF-16.lebom.html new file mode 100644 index 00000000000..4ba47a81395 Binary files /dev/null and b/tests/PhpWordTests/_files/html/charset.UTF-16.lebom.html differ diff --git a/tests/PhpWordTests/_files/html/charset.UTF-8.bom.html b/tests/PhpWordTests/_files/html/charset.UTF-8.bom.html new file mode 100644 index 00000000000..5a49399018f --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.UTF-8.bom.html @@ -0,0 +1,16 @@ + + + + UTF-8 + + +

      À1

      +

      B1

      +

      ç1

      +

      D1

      +

      Ã2

      +

      B2

      +

      C2

      +

      Ð2

      + + diff --git a/tests/PhpWordTests/_files/html/charset.UTF-8.html b/tests/PhpWordTests/_files/html/charset.UTF-8.html new file mode 100644 index 00000000000..9ae5a8e343a --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.UTF-8.html @@ -0,0 +1,17 @@ + + + + + UTF-8 + + +

      À1

      +

      B1

      +

      ç1

      +

      D1

      +

      Ã2

      +

      B2

      +

      C2

      +

      Ð2

      + + diff --git a/tests/PhpWordTests/_files/html/charset.gb18030.html b/tests/PhpWordTests/_files/html/charset.gb18030.html new file mode 100644 index 00000000000..271a55fc548 --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.gb18030.html @@ -0,0 +1,9 @@ + + + +gb18030 + + +

      ӻ

      + + diff --git a/tests/PhpWordTests/_files/html/charset.nocharset.html b/tests/PhpWordTests/_files/html/charset.nocharset.html new file mode 100644 index 00000000000..d6829b2edc9 --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.nocharset.html @@ -0,0 +1,8 @@ +

      À1

      +

      B1

      +

      ç1

      +

      D1

      +

      Ã2

      +

      B2

      +

      C2

      +

      Ð2

      diff --git a/tests/PhpWordTests/_files/html/charset.unknown.html b/tests/PhpWordTests/_files/html/charset.unknown.html new file mode 100644 index 00000000000..189638a80ff --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.unknown.html @@ -0,0 +1,17 @@ + + + + + UTF-8 + + +

      À1

      +

      B1

      +

      ç1

      +

      D1

      +

      Ã2

      +

      B2

      +

      C2

      +

      Ð2

      + + diff --git a/tests/PhpWordTests/_files/html/loripsum.net.html b/tests/PhpWordTests/_files/html/loripsum.net.html new file mode 100644 index 00000000000..7cf1357e71d --- /dev/null +++ b/tests/PhpWordTests/_files/html/loripsum.net.html @@ -0,0 +1,19 @@ +

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cupiditates non Epicuri divisione finiebat, sed sua satietate. Hic, qui utrumque probat, ambobus debuit uti, sicut facit re, neque tamen dividit verbis. Quibus natura iure responderit non esse verum aliunde finem beate vivendi, a se principia rei gerendae peti; Quibus rebus vita consentiens virtutibusque respondens recta et honesta et constans et naturae congruens existimari potest. Illa videamus, quae a te de amicitia dicta sunt. Addo etiam illud, multa iam mihi dare signa puerum et pudoris et ingenii, sed aetatem vides. Quibus rebus vita consentiens virtutibusque respondens recta et honesta et constans et naturae congruens existimari potest. Duo Reges: constructio interrete. Quid, cum volumus nomina eorum, qui quid gesserint, nota nobis esse, parentes, patriam, multa praeterea minime necessaria? Apparet statim, quae sint officia, quae actiones.

      + +

      Nam ante Aristippus, et ille melius. Quamquam id quidem licebit iis existimare, qui legerint. Inde igitur, inquit, ordiendum est. Quo modo autem optimum, si bonum praeterea nullum est? Non dolere, inquam, istud quam vim habeat postea videro; Perfecto enim et concluso neque virtutibus neque amicitiis usquam locum esse, si ad voluptatem omnia referantur, nihil praeterea est magnopere dicendum. Maximas vero virtutes iacere omnis necesse est voluptate dominante. Tertium autem omnibus aut maximis rebus iis, quae secundum naturam sint, fruentem vivere.

      + +

      Huic ego, si negaret quicquam interesse ad beate vivendum quali uteretur victu, concederem, laudarem etiam; Maximas vero virtutes iacere omnis necesse est voluptate dominante. Polemoni et iam ante Aristoteli ea prima visa sunt, quae paulo ante dixi. Atqui haec patefactio quasi rerum opertarum, cum quid quidque sit aperitur, definitio est. Num igitur utiliorem tibi hunc Triarium putas esse posse, quam si tua sint Puteolis granaria?

      + +

      Unum est sine dolore esse, alterum cum voluptate. Sed quoniam et advesperascit et mihi ad villam revertendum est, nunc quidem hactenus; Quid enim mihi potest esse optatius quam cum Catone, omnium virtutum auctore, de virtutibus disputare? Ergo infelix una molestia, fellx rursus, cum is ipse anulus in praecordiis piscis inventus est? Neque enim disputari sine reprehensione nec cum iracundia aut pertinacia recte disputari potest. Paulum, cum regem Persem captum adduceret, eodem flumine invectio?

      + +

      Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; Res enim concurrent contrariae. Tenesne igitur, inquam, Hieronymus Rhodius quid dicat esse summum bonum, quo putet omnia referri oportere? Etenim nec iustitia nec amicitia esse omnino poterunt, nisi ipsae per se expetuntur. Invidiosum nomen est, infame, suspectum. Et ille ridens: Video, inquit, quid agas;

      + +

      Satis est tibi in te, satis in legibus, satis in mediocribus amicitiis praesidii. Gracchum patrem non beatiorem fuisse quam fillum, cum alter stabilire rem publicam studuerit, alter evertere. Nunc dicam de voluptate, nihil scilicet novi, ea tamen, quae te ipsum probaturum esse confidam. Quid, si reviviscant Platonis illi et deinceps qui eorum auditores fuerunt, et tecum ita loquantur? Quid, si reviviscant Platonis illi et deinceps qui eorum auditores fuerunt, et tecum ita loquantur? Sed residamus, inquit, si placet. Illa videamus, quae a te de amicitia dicta sunt. A villa enim, credo, et: Si ibi te esse scissem, ad te ipse venissem. Si ista mala sunt, in quae potest incidere sapiens, sapientem esse non esse ad beate vivendum satis.

      + +

      Intrandum est igitur in rerum naturam et penitus quid ea postulet pervidendum; Cum autem venissemus in Academiae non sine causa nobilitata spatia, solitudo erat ea, quam volueramus.

      + +

      Non igitur potestis voluptate omnia dirigentes aut tueri aut retinere virtutem. Ne tum quidem te respicies et cogitabis sibi quemque natum esse et suis voluptatibus? Quae sunt igitur communia vobis cum antiquis, iis sic utamur quasi concessis; Scripsit enim et multis saepe verbis et breviter arteque in eo libro, quem modo nominavi, mortem nihil ad nos pertinere. An me, inquam, nisi te audire vellem, censes haec dicturum fuisse? Quae similitudo in genere etiam humano apparet.

      + +

      Tum mihi Piso: Quid ergo? Ita multo sanguine profuso in laetitia et in victoria est mortuus. Et ille ridens: Video, inquit, quid agas; Sequitur disserendi ratio cognitioque naturae; Virtutis, magnitudinis animi, patientiae, fortitudinis fomentis dolor mitigari solet. -delector enim, quamquam te non possum, ut ais, corrumpere, delector, inquam, et familia vestra et nomine. Polemoni et iam ante Aristoteli ea prima visa sunt, quae paulo ante dixi. Graecum enim hunc versum nostis omnes-: Suavis laborum est praeteritorum memoria.

      + +

      Itaque ne iustitiam quidem recte quis dixerit per se ipsam optabilem, sed quia iucunditatis vel plurimum afferat. An, partus ancillae sitne in fructu habendus, disseretur inter principes civitatis, P. Cum autem assumpta ratío est, tanto in dominatu locatur, ut omnia illa prima naturae hulus tutelae subiciantur. Similiter sensus, cum accessit ad naturam, tuetur illam quidem, sed etiam se tuetur; Non semper, inquam; Potius ergo illa dicantur: turpe esse, viri non esse debilitari dolore, frangi, succumbere. Quia voluptatem hanc esse sentiunt omnes, quam sensus accipiens movetur et iucunditate quadam perfunditur. Cum sciret confestim esse moriendum eamque mortem ardentiore studio peteret, quam Epicurus voluptatem petendam putat.

      diff --git a/tests/PhpWordTests/_files/images/phpword.svg b/tests/PhpWordTests/_files/images/phpword.svg new file mode 100644 index 00000000000..2fbeeb4af07 --- /dev/null +++ b/tests/PhpWordTests/_files/images/phpword.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d94d68d8804..13dec158da4 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -59,8 +59,13 @@ function utf8decode(string $value, string $toEncoding = 'ISO-8859-1'): string return $result === false ? '' : $result; } -// @phpstan-ignore-next-line -if (!method_exists(PHPUnit\Framework\TestCase::class, 'setOutputCallback')) { +/** @param string $methodName */ +function methodFound($methodName): bool +{ + return method_exists(PHPUnit\Framework\TestCase::class, $methodName); +} + +if (!methodFound('setOutputCallback')) { ini_set('error_reporting', (string) E_ALL); set_error_handler('phpunit10ErrorHandler'); }