diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..f3d033a7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,21 @@ +# build config +/.scrutinizer.yml export-ignore +/.travis.yml export-ignore +/php_cs.dist export-ignore +/phpmd.xml.dist export-ignore +/phpstan.neon export-ignore + +/composer.lock export-ignore + +# git files +/.gitignore export-ignore +/.gitattributes export-ignore + +# project directories +/build export-ignore +/docs export-ignore +/samples export-ignore + +# tests +/phpunit.xml.dist export-ignore +/tests export-ignore \ No newline at end of file diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 291a6d60..2b395afd 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -19,7 +19,7 @@ tools: config: ruleset: phpmd.xml.dist external_code_coverage: - enabled: true + enabled: false timeout: 1200 php_cpd: true # php_sim: # Temporarily disabled to allow focus on things other than duplicates diff --git a/.travis.yml b/.travis.yml index 281c2630..c72dd8c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ before_script: - composer self-update - travis_wait composer install --prefer-source ## PHPDocumentor - - mkdir -p build/docs + ##- mkdir -p build/docs - mkdir -p build/coverage script: @@ -52,10 +52,8 @@ script: ## PHPLOC - if [ -z "$COVERAGE" ]; then ./vendor/bin/phploc src/ ; fi ## PHPDocumentor - - if [ -z "$COVERAGE" ]; then ./vendor/bin/phpdoc -q -d ./src -t ./build/docs --ignore "*/src/PhpWord/Shared/*/*" --template="responsive-twig" ; fi + ##- if [ -z "$COVERAGE" ]; then ./vendor/bin/phpdoc -q -d ./src -t ./build/docs --ignore "*/src/PhpWord/Shared/*/*" --template="responsive-twig" ; fi -after_script: - ## PHPDocumentor - - bash .travis_shell_after_success.sh - ## Scrutinizer - - if [ -n "$COVERAGE" ]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml ; fi +after_success: + ## Coveralls + - if [ -n "$COVERAGE" ]; then travis_retry php vendor/bin/php-coveralls -v ; fi diff --git a/.travis_shell_after_success.sh b/.travis_shell_after_success.sh deleted file mode 100644 index 12728526..00000000 --- a/.travis_shell_after_success.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -echo "--DEBUG--" -echo "TRAVIS_REPO_SLUG: $TRAVIS_REPO_SLUG" -echo "TRAVIS_PHP_VERSION: $TRAVIS_PHP_VERSION" -echo "TRAVIS_PULL_REQUEST: $TRAVIS_PULL_REQUEST" - -if [ "$TRAVIS_REPO_SLUG" == "PHPOffice/PHPWord" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_PHP_VERSION" == "5.6" ]; then - - echo -e "Publishing PHPDoc...\n" - - cp -R build/docs $HOME/docs-latest - cp -R build/coverage $HOME/coverage-latest - - cd $HOME - git config --global user.email "travis@travis-ci.org" - git config --global user.name "travis-ci" - git clone --quiet --branch=gh-pages https://${GH_TOKEN}@github.com/PHPOffice/PHPWord gh-pages > /dev/null - - cd gh-pages - echo "--DEBUG : Suppression" - git rm -rf ./docs/$TRAVIS_BRANCH - - echo "--DEBUG : Dossier" - mkdir -p docs/$TRAVIS_BRANCH - mkdir -p coverage/$TRAVIS_BRANCH - - echo "--DEBUG : Copie" - cp -Rf $HOME/docs-latest/* ./docs/$TRAVIS_BRANCH/ - cp -Rf $HOME/coverage-latest/* ./coverage/$TRAVIS_BRANCH/ - - echo "--DEBUG : Git" - git add -f . - git commit -m "PHPDocumentor (Travis Build: $TRAVIS_BUILD_NUMBER - Branch: $TRAVIS_BRANCH)" - git push -fq origin gh-pages > /dev/null - - echo -e "Published PHPDoc to gh-pages.\n" - -fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f1e194b..4606db9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,11 @@ v0.15.0 (?? ??? 2018) - Added support for Image text wrapping distance @troosan #1310 - Added parsing of CSS line-height and text-indent in HTML reader @troosan #1316 - Added the ability to enable gridlines and axislabels on charts @FrankMeyer #576 +- Add support for table indent (tblInd) @Trainmaster #1343 +- Added parsing of internal links in HTML reader @lalop #1336 +- Several improvements to charts @JAEK-S #1332 +- Add parsing of html image in base64 format @jgpATs2w #1382 +- Added Support for Indentation & Tabs on RTF Writer. @smaug1985 #1405 ### Fixed - Fix reading of docx default style - @troosan #1238 @@ -33,10 +38,15 @@ v0.15.0 (?? ??? 2018) - Fix parsing of Heading and Title formating @troosan @gthomas2 #465 - Fix Dateformat typo, fix hours casing, add Month-Day-Year formats @ComputerTinker #591 - Support reading of w:drawing for documents produced by word 2011+ @gthomas2 #464 #1324 +- Fix missing column width in ODText writer @potofcoffee #413 +- Disable entity loader before parsing XML to avoid XXE injection @Tom4t0 #1427 ### Changed - Remove zend-stdlib dependency @Trainmaster #1284 +- The default unit for `\PhpOffice\PhpWord\Style\Image` changed from `px` to `pt`. +### Miscelaneous +- Drop GitHub pages, switch to coveralls for code coverage analysis @czosel #1360 v0.14.0 (29 Dec 2017) ---------------------- diff --git a/README.md b/README.md index 59fc3c44..7531a6bc 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Latest Stable Version](https://poser.pugx.org/phpoffice/phpword/v/stable.png)](https://packagist.org/packages/phpoffice/phpword) [![Build Status](https://travis-ci.org/PHPOffice/PHPWord.svg?branch=master)](https://travis-ci.org/PHPOffice/PHPWord) [![Code Quality](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/badges/quality-score.png?s=b5997ce59ac2816b4514f3a38de9900f6d492c1d)](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/) -[![Code Coverage](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/badges/coverage.png?s=742a98745725c562955440edc8d2c39d7ff5ae25)](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/) +[![Coverage Status](https://coveralls.io/repos/github/PHPOffice/PHPWord/badge.svg?branch=develop)](https://coveralls.io/github/PHPOffice/PHPWord?branch=develop) [![Total Downloads](https://poser.pugx.org/phpoffice/phpword/downloads.png)](https://packagist.org/packages/phpoffice/phpword) [![License](https://poser.pugx.org/phpoffice/phpword/license.png)](https://packagist.org/packages/phpoffice/phpword) [![Join the chat at https://gitter.im/PHPOffice/PHPWord](https://img.shields.io/badge/GITTER-join%20chat-green.svg)](https://gitter.im/PHPOffice/PHPWord) diff --git a/composer.json b/composer.json index 1844e82e..9f0e8a43 100644 --- a/composer.json +++ b/composer.json @@ -64,15 +64,17 @@ "phpoffice/common": "^0.2.9" }, "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^5.0", - "phpdocumentor/phpdocumentor":"2.*", - "squizlabs/php_codesniffer": "^2.7", - "friendsofphp/php-cs-fixer": "^2.0", + "ext-zip": "*", + "ext-gd": "*", + "phpunit/phpunit": "^4.8.36 || ^7.0", + "squizlabs/php_codesniffer": "^2.9", + "friendsofphp/php-cs-fixer": "^2.2", "phpmd/phpmd": "2.*", "phploc/phploc": "2.* || 3.* || 4.*", "dompdf/dompdf":"0.8.*", "tecnickcom/tcpdf": "6.*", - "mpdf/mpdf": "5.* || 6.* || 7.*" + "mpdf/mpdf": "5.7.4 || 6.* || 7.*", + "php-coveralls/php-coveralls": "1.1.0 || ^2.0" }, "suggest": { "ext-zip": "Allows writing OOXML and ODF", diff --git a/docs/elements.rst b/docs/elements.rst index 8f33b503..74b1d56f 100644 --- a/docs/elements.rst +++ b/docs/elements.rst @@ -242,7 +242,7 @@ To add an image, use the ``addImage`` method to sections, headers, footers, text $section->addImage($src, [$style]); -- ``$src``. String path to a local image, URL of a remote image or the image data, as a string. +- ``$src``. String path to a local image, URL of a remote image or the image data, as a string. Warning: Do not pass user-generated strings here, as that would allow an attacker to read arbitrary files or perform server-side request forgery by passing file paths or URLs instead of image data. - ``$style``. See :ref:`image-style`. Examples: @@ -435,8 +435,8 @@ Available line style attributes: - ``dash``. Line types: dash, rounddot, squaredot, dashdot, longdash, longdashdot, longdashdotdot. - ``beginArrow``. Start type of arrow: block, open, classic, diamond, oval. - ``endArrow``. End type of arrow: block, open, classic, diamond, oval. -- ``width``. Line-object width in pt. -- ``height``. Line-object height in pt. +- ``width``. Line-object width in *pt*. +- ``height``. Line-object height in *pt*. - ``flip``. Flip the line element: true, false. Chart diff --git a/docs/installing.rst b/docs/installing.rst index 4f407f54..34353be8 100644 --- a/docs/installing.rst +++ b/docs/installing.rst @@ -54,7 +54,5 @@ Example: Using samples ------------- -After installation, you can browse and use the samples that we've -provided, either by command line or using browser. If you can access -your PHPWord library folder using browser, point your browser to the -``samples`` folder, e.g. ``http://localhost/PhpWord/samples/``. +More examples are provided in the ``samples`` directory. +For an easy access to those samples launch ``php -S localhost:8000`` in the samples directory then browse to http://localhost:8000 to view the samples. diff --git a/docs/styles.rst b/docs/styles.rst index 0bda3faf..03366427 100644 --- a/docs/styles.rst +++ b/docs/styles.rst @@ -104,6 +104,7 @@ Available Table style options: - ``border(Top|Right|Bottom|Left)Color``. Border color, e.g. '9966CC'. - ``border(Top|Right|Bottom|Left)Size``. Border size in *twip*. - ``cellMargin(Top|Right|Bottom|Left)``. Cell margin in *twip*. +- ``indent``. Table indent from leading margin. Must be an instance of ``\PhpOffice\PhpWord\ComplexType\TblWidth``. - ``width``. Table width in percent. - ``unit``. The unit to use for the width. One of ``\PhpOffice\PhpWord\SimpleType\TblWidth``. Defaults to *auto*. - ``layout``. Table layout, either *fixed* or *autofit* See ``\PhpOffice\PhpWord\Style\Table`` for constants. @@ -149,10 +150,10 @@ Image Available Image style options: - ``alignment``. See ``\PhpOffice\PhpWord\SimpleType\Jc`` class for the details. -- ``height``. Height in pixels. +- ``height``. Height in *pt*. - ``marginLeft``. Left margin in inches, can be negative. - ``marginTop``. Top margin in inches, can be negative. -- ``width``. Width in pixels. +- ``width``. Width in *pt*. - ``wrappingStyle``. Wrapping style, *inline*, *square*, *tight*, *behind*, or *infront*. - ``wrapDistanceTop``. Top text wrapping in pixels. - ``wrapDistanceBottom``. Bottom text wrapping in pixels. diff --git a/phpstan.neon b/phpstan.neon index 5ae6d0f2..666c63b9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,7 +1,7 @@ includes: - vendor/phpstan/phpstan/conf/config.level1.neon parameters: - memory-limit: 200000 + memory-limit: 20000000 autoload_directories: - tests autoload_files: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 015dd2ed..17fcfa39 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,8 +6,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false" - syntaxCheck="false"> + stopOnFailure="false"> ./tests/PhpWord @@ -22,7 +21,7 @@ - + \ No newline at end of file diff --git a/samples/Sample_01_SimpleText.php b/samples/Sample_01_SimpleText.php index 5a3393b3..8af44d20 100644 --- a/samples/Sample_01_SimpleText.php +++ b/samples/Sample_01_SimpleText.php @@ -1,6 +1,5 @@ setValue('userPhone#3', '+1 428 889 775'); echo date('H:i:s'), ' Saving the result document...', EOL; $templateProcessor->saveAs('results/Sample_07_TemplateCloneRow.docx'); -echo getEndingNotes(array('Word2007' => 'docx')); +echo getEndingNotes(array('Word2007' => 'docx'), 'results/Sample_07_TemplateCloneRow.docx'); if (!CLI) { include_once 'Sample_Footer.php'; } diff --git a/samples/Sample_23_TemplateBlock.php b/samples/Sample_23_TemplateBlock.php index 2b7e9f68..ed986618 100644 --- a/samples/Sample_23_TemplateBlock.php +++ b/samples/Sample_23_TemplateBlock.php @@ -14,7 +14,7 @@ $templateProcessor->deleteBlock('DELETEME'); echo date('H:i:s'), ' Saving the result document...', EOL; $templateProcessor->saveAs('results/Sample_23_TemplateBlock.docx'); -echo getEndingNotes(array('Word2007' => 'docx')); +echo getEndingNotes(array('Word2007' => 'docx'), 'results/Sample_23_TemplateBlock.docx'); if (!CLI) { include_once 'Sample_Footer.php'; } diff --git a/samples/Sample_26_Html.php b/samples/Sample_26_Html.php index d8763805..e1823c43 100644 --- a/samples/Sample_26_Html.php +++ b/samples/Sample_26_Html.php @@ -11,7 +11,7 @@ $html = '

Adding element via HTML

'; $html .= '

Some well-formed HTML snippet needs to be used

'; $html .= '

With for example some1 inline formatting1

'; -$html .= '

A link to Read the docs

'; +$html .= '

A link to Read the docs

'; $html .= '

היי, זה פסקה מימין לשמאל

'; diff --git a/samples/Sample_27_Field.php b/samples/Sample_27_Field.php index 9c37dffe..4e7a5b22 100644 --- a/samples/Sample_27_Field.php +++ b/samples/Sample_27_Field.php @@ -6,15 +6,20 @@ include_once 'Sample_Header.php'; // New Word document echo date('H:i:s'), ' Create new PhpWord object', EOL; $phpWord = new \PhpOffice\PhpWord\PhpWord(); +PhpOffice\PhpWord\Style::addTitleStyle(1, array('size' => 14)); // New section $section = $phpWord->addSection(); +$section->addTitle('This page demos fields'); // Add Field elements // See Element/Field.php for all options $section->addText('Date field:'); $section->addField('DATE', array('dateformat' => 'dddd d MMMM yyyy H:mm:ss'), array('PreserveFormat')); +$section->addText('Style Ref field:'); +$section->addField('STYLEREF', array('StyleIdentifier' => 'Heading 1')); + $section->addText('Page field:'); $section->addField('PAGE', array('format' => 'Arabic')); diff --git a/samples/Sample_32_Chart.php b/samples/Sample_32_Chart.php index 87d6f3e3..c24a6f8e 100644 --- a/samples/Sample_32_Chart.php +++ b/samples/Sample_32_Chart.php @@ -16,8 +16,8 @@ $section = $phpWord->addSection(); $section->addTitle('2D charts', 1); $section = $phpWord->addSection(array('colsNum' => 2, 'breakType' => 'continuous')); -$chartTypes = array('pie', 'doughnut', 'bar', 'column', 'line', 'area', 'scatter', 'radar'); -$twoSeries = array('bar', 'column', 'line', 'area', 'scatter', 'radar'); +$chartTypes = array('pie', 'doughnut', 'bar', 'column', 'line', 'area', 'scatter', 'radar', 'stacked_bar', 'percent_stacked_bar', 'stacked_column', 'percent_stacked_column'); +$twoSeries = array('bar', 'column', 'line', 'area', 'scatter', 'radar', 'stacked_bar', 'percent_stacked_bar', 'stacked_column', 'percent_stacked_column'); $threeSeries = array('bar', 'line'); $categories = array('A', 'B', 'C', 'D', 'E'); $series1 = array(1, 3, 2, 5, 4); diff --git a/samples/Sample_Header.php b/samples/Sample_Header.php index ac51f983..f0fc6266 100644 --- a/samples/Sample_Header.php +++ b/samples/Sample_Header.php @@ -83,7 +83,7 @@ function write($phpWord, $filename, $writers) $result .= EOL; } - $result .= getEndingNotes($writers); + $result .= getEndingNotes($writers, $filename); return $result; } @@ -92,10 +92,10 @@ function write($phpWord, $filename, $writers) * Get ending notes * * @param array $writers - * + * @param mixed $filename * @return string */ -function getEndingNotes($writers) +function getEndingNotes($writers, $filename) { $result = ''; diff --git a/samples/resources/Sample_30_ReadHTML.html b/samples/resources/Sample_30_ReadHTML.html index 5593298b..b3d2fad2 100644 --- a/samples/resources/Sample_30_ReadHTML.html +++ b/samples/resources/Sample_30_ReadHTML.html @@ -11,5 +11,15 @@

Ordered (numbered) list:

  1. Item 1
  2. Item 2
+ +

Double height

+ +

Includes images

+ + + + + + diff --git a/samples/results/.gitignore b/samples/results/.gitignore old mode 100644 new mode 100755 diff --git a/src/PhpWord/ComplexType/TblWidth.php b/src/PhpWord/ComplexType/TblWidth.php new file mode 100644 index 00000000..0d1a2419 --- /dev/null +++ b/src/PhpWord/ComplexType/TblWidth.php @@ -0,0 +1,59 @@ +value = $value; + TblWidthSimpleType::validate($type); + $this->type = $type; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @return int + */ + public function getValue() + { + return $this->value; + } +} diff --git a/src/PhpWord/Element/AbstractElement.php b/src/PhpWord/Element/AbstractElement.php index 5ff85b8f..e3e54ed4 100644 --- a/src/PhpWord/Element/AbstractElement.php +++ b/src/PhpWord/Element/AbstractElement.php @@ -347,7 +347,7 @@ abstract class AbstractElement * * @param \PhpOffice\PhpWord\Element\AbstractElement $container */ - public function setParentContainer(AbstractElement $container) + public function setParentContainer(self $container) { $this->parentContainer = substr(get_class($container), strrpos(get_class($container), '\\') + 1); $this->parent = $container; diff --git a/src/PhpWord/Element/Chart.php b/src/PhpWord/Element/Chart.php index 755f45e1..92152c87 100644 --- a/src/PhpWord/Element/Chart.php +++ b/src/PhpWord/Element/Chart.php @@ -61,11 +61,12 @@ class Chart extends AbstractElement * @param array $categories * @param array $values * @param array $style + * @param null|mixed $seriesName */ - public function __construct($type, $categories, $values, $style = null) + public function __construct($type, $categories, $values, $style = null, $seriesName = null) { $this->setType($type); - $this->addSeries($categories, $values); + $this->addSeries($categories, $values, $seriesName); $this->style = $this->setNewStyle(new ChartStyle(), $style, true); } @@ -86,7 +87,7 @@ class Chart extends AbstractElement */ public function setType($value) { - $enum = array('pie', 'doughnut', 'line', 'bar', 'column', 'area', 'radar', 'scatter'); + $enum = array('pie', 'doughnut', 'line', 'bar', 'stacked_bar', 'percent_stacked_bar', 'column', 'stacked_column', 'percent_stacked_column', 'area', 'radar', 'scatter'); $this->type = $this->setEnumVal($value, $enum, 'pie'); } @@ -95,10 +96,15 @@ class Chart extends AbstractElement * * @param array $categories * @param array $values + * @param null|mixed $name */ - public function addSeries($categories, $values) + public function addSeries($categories, $values, $name = null) { - $this->series[] = array('categories' => $categories, 'values' => $values); + $this->series[] = array( + 'categories' => $categories, + 'values' => $values, + 'name' => $name, + ); } /** diff --git a/src/PhpWord/Element/Field.php b/src/PhpWord/Element/Field.php index 0e5e28ed..2efc6b0b 100644 --- a/src/PhpWord/Element/Field.php +++ b/src/PhpWord/Element/Field.php @@ -78,6 +78,10 @@ class Field extends AbstractElement 'properties' => array(), 'options' => array('PreserveFormat'), ), + 'STYLEREF' => array( + 'properties' => array('StyleIdentifier' => ''), + 'options' => array('PreserveFormat'), + ), ); /** diff --git a/src/PhpWord/Element/OLEObject.php b/src/PhpWord/Element/OLEObject.php index c0c7f217..1a17b747 100644 --- a/src/PhpWord/Element/OLEObject.php +++ b/src/PhpWord/Element/OLEObject.php @@ -83,7 +83,7 @@ class OLEObject extends AbstractElement $this->style = $this->setNewStyle(new ImageStyle(), $style, true); $this->icon = realpath(__DIR__ . "/../resources/{$ext}.png"); - return $this; + return; } throw new InvalidObjectException(); diff --git a/src/PhpWord/Element/PreserveText.php b/src/PhpWord/Element/PreserveText.php index 1ce2dcdd..374f1a99 100644 --- a/src/PhpWord/Element/PreserveText.php +++ b/src/PhpWord/Element/PreserveText.php @@ -29,7 +29,7 @@ class PreserveText extends AbstractElement /** * Text content * - * @var string + * @var string|array */ private $text; @@ -64,8 +64,6 @@ class PreserveText extends AbstractElement if (isset($matches[0])) { $this->text = $matches; } - - return $this; } /** @@ -91,7 +89,7 @@ class PreserveText extends AbstractElement /** * Get Text content * - * @return string + * @return string|array */ public function getText() { diff --git a/src/PhpWord/Element/Table.php b/src/PhpWord/Element/Table.php index 10c4db69..44fe3744 100644 --- a/src/PhpWord/Element/Table.php +++ b/src/PhpWord/Element/Table.php @@ -135,18 +135,40 @@ class Table extends AbstractElement public function countColumns() { $columnCount = 0; - if (is_array($this->rows)) { - $rowCount = count($this->rows); - for ($i = 0; $i < $rowCount; $i++) { - /** @var \PhpOffice\PhpWord\Element\Row $row Type hint */ - $row = $this->rows[$i]; - $cellCount = count($row->getCells()); - if ($columnCount < $cellCount) { - $columnCount = $cellCount; - } + + $rowCount = count($this->rows); + for ($i = 0; $i < $rowCount; $i++) { + /** @var \PhpOffice\PhpWord\Element\Row $row Type hint */ + $row = $this->rows[$i]; + $cellCount = count($row->getCells()); + if ($columnCount < $cellCount) { + $columnCount = $cellCount; } } return $columnCount; } + + /** + * The first declared cell width for each column + * + * @return int[] + */ + public function findFirstDefinedCellWidths() + { + $cellWidths = array(); + + foreach ($this->rows as $row) { + $cells = $row->getCells(); + if (count($cells) <= count($cellWidths)) { + continue; + } + $cellWidths = array(); + foreach ($cells as $cell) { + $cellWidths[] = $cell->getWidth(); + } + } + + return $cellWidths; + } } diff --git a/src/PhpWord/Element/Title.php b/src/PhpWord/Element/Title.php index 569cea92..d01f7f33 100644 --- a/src/PhpWord/Element/Title.php +++ b/src/PhpWord/Element/Title.php @@ -61,14 +61,12 @@ class Title extends AbstractElement */ public function __construct($text, $depth = 1) { - if (isset($text)) { - if (is_string($text)) { - $this->text = CommonText::toUTF8($text); - } elseif ($text instanceof TextRun) { - $this->text = $text; - } else { - throw new \InvalidArgumentException('Invalid text, should be a string or a TextRun'); - } + if (is_string($text)) { + $this->text = CommonText::toUTF8($text); + } elseif ($text instanceof TextRun) { + $this->text = $text; + } else { + throw new \InvalidArgumentException('Invalid text, should be a string or a TextRun'); } $this->depth = $depth; @@ -76,8 +74,6 @@ class Title extends AbstractElement if (array_key_exists($styleName, Style::getStyles())) { $this->style = str_replace('_', '', $styleName); } - - return $this; } /** diff --git a/src/PhpWord/Metadata/Protection.php b/src/PhpWord/Metadata/Protection.php index 584ed83e..15aa3ff1 100644 --- a/src/PhpWord/Metadata/Protection.php +++ b/src/PhpWord/Metadata/Protection.php @@ -17,7 +17,7 @@ namespace PhpOffice\PhpWord\Metadata; -use PhpOffice\PhpWord\Shared\Microsoft\PasswordEncoder; +use PhpOffice\Common\Microsoft\PasswordEncoder; use PhpOffice\PhpWord\SimpleType\DocProtect; /** @@ -113,7 +113,7 @@ class Protection /** * Set password * - * @param $password + * @param string $password * @return self */ public function setPassword($password) @@ -136,7 +136,7 @@ class Protection /** * Set count for hash iterations * - * @param $spinCount + * @param int $spinCount * @return self */ public function setSpinCount($spinCount) @@ -159,7 +159,7 @@ class Protection /** * Set algorithm * - * @param $algorithm + * @param string $algorithm * @return self */ public function setAlgorithm($algorithm) diff --git a/src/PhpWord/PhpWord.php b/src/PhpWord/PhpWord.php index b5cc0c51..a78df2c4 100644 --- a/src/PhpWord/PhpWord.php +++ b/src/PhpWord/PhpWord.php @@ -35,10 +35,10 @@ use PhpOffice\PhpWord\Exception\Exception; * @method int addChart(Element\Chart $chart) * @method int addComment(Element\Comment $comment) * - * @method Style\Paragraph addParagraphStyle(string $styleName, array $styles) + * @method Style\Paragraph addParagraphStyle(string $styleName, mixed $styles) * @method Style\Font addFontStyle(string $styleName, mixed $fontStyle, mixed $paragraphStyle = null) * @method Style\Font addLinkStyle(string $styleName, mixed $styles) - * @method Style\Font addTitleStyle(int $depth, mixed $fontStyle, mixed $paragraphStyle = null) + * @method Style\Font addTitleStyle(mixed $depth, mixed $fontStyle, mixed $paragraphStyle = null) * @method Style\Table addTableStyle(string $styleName, mixed $styleTable, mixed $styleFirstRow = null) * @method Style\Numbering addNumberingStyle(string $styleName, mixed $styles) */ diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index c7ec4ca7..5e5eb1d6 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Reader\Word2007; use PhpOffice\Common\XMLReader; +use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType; use PhpOffice\PhpWord\Element\AbstractContainer; use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\Element\TrackChange; @@ -486,6 +487,11 @@ abstract class AbstractPart if ($tablePositionNode !== null) { $style['position'] = $this->readTablePosition($xmlReader, $tablePositionNode); } + + $indentNode = $xmlReader->getElement('w:tblInd', $styleNode); + if ($indentNode !== null) { + $style['indent'] = $this->readTableIndent($xmlReader, $indentNode); + } } } @@ -517,6 +523,24 @@ abstract class AbstractPart return $this->readStyleDefs($xmlReader, $domNode, $styleDefs); } + /** + * Read w:tblInd + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $domNode + * @return TblWidthComplexType + */ + private function readTableIndent(XMLReader $xmlReader, \DOMElement $domNode) + { + $styleDefs = array( + 'value' => array(self::READ_VALUE, '.', 'w:w'), + 'type' => array(self::READ_VALUE, '.', 'w:type'), + ); + $styleDefs = $this->readStyleDefs($xmlReader, $domNode, $styleDefs); + + return new TblWidthComplexType((int) $styleDefs['value'], $styleDefs['type']); + } + /** * Read w:tcPr * diff --git a/src/PhpWord/Reader/Word2007/Settings.php b/src/PhpWord/Reader/Word2007/Settings.php index 5cfe5453..3084943b 100644 --- a/src/PhpWord/Reader/Word2007/Settings.php +++ b/src/PhpWord/Reader/Word2007/Settings.php @@ -81,7 +81,7 @@ class Settings extends AbstractPart * * @param XMLReader $xmlReader * @param PhpWord $phpWord - * @param \DOMNode $node + * @param \DOMElement $node */ protected function setThemeFontLang(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) { @@ -102,14 +102,16 @@ class Settings extends AbstractPart * * @param XMLReader $xmlReader * @param PhpWord $phpWord - * @param \DOMNode $node + * @param \DOMElement $node */ protected function setDocumentProtection(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) { $documentProtection = $phpWord->getSettings()->getDocumentProtection(); $edit = $xmlReader->getAttribute('w:edit', $node); - $documentProtection->setEditing($edit); + if ($edit !== null) { + $documentProtection->setEditing($edit); + } } /** @@ -117,7 +119,7 @@ class Settings extends AbstractPart * * @param XMLReader $xmlReader * @param PhpWord $phpWord - * @param \DOMNode $node + * @param \DOMElement $node */ protected function setProofState(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) { @@ -139,7 +141,7 @@ class Settings extends AbstractPart * * @param XMLReader $xmlReader * @param PhpWord $phpWord - * @param \DOMNode $node + * @param \DOMElement $node */ protected function setZoom(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) { @@ -156,7 +158,7 @@ class Settings extends AbstractPart * * @param XMLReader $xmlReader * @param PhpWord $phpWord - * @param \DOMNode $node + * @param \DOMElement $node */ protected function setRevisionView(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) { diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index f8b25605..f725c366 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -20,6 +20,7 @@ namespace PhpOffice\PhpWord\Shared; use PhpOffice\PhpWord\Element\AbstractContainer; use PhpOffice\PhpWord\Element\Row; use PhpOffice\PhpWord\Element\Table; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\NumberFormat; @@ -32,23 +33,30 @@ class Html { private static $listIndex = 0; private static $xpath; + private static $options; /** * Add HTML parts. * * Note: $stylesheet parameter is removed to avoid PHPMD error for unused parameter + * Warning: Do not pass user-generated HTML here, as that would allow an attacker to read arbitrary + * files or perform server-side request forgery by passing local file paths or URLs in . * * @param \PhpOffice\PhpWord\Element\AbstractContainer $element Where the parts need to be added * @param string $html The code to parse * @param bool $fullHTML If it's a full HTML, no need to add 'body' tag * @param bool $preserveWhiteSpace If false, the whitespaces between nodes will be removed + * @param array $options: + * + IMG_SRC_SEARCH: optional to speed up images loading from remote url when files can be found locally + * + IMG_SRC_REPLACE: optional to speed up images loading from remote url when files can be found locally */ - public static function addHtml($element, $html, $fullHTML = false, $preserveWhiteSpace = true) + public static function addHtml($element, $html, $fullHTML = false, $preserveWhiteSpace = true, $options = null) { /* * @todo parse $stylesheet for default styles. Should result in an array based on id, class and element, * which could be applied when such an element occurs in the parseNode function. */ + self::$options = $options; // Preprocess: remove all line ends, decode HTML entity, // fix ampersand and angle brackets and add body tag for HTML fragments @@ -63,10 +71,11 @@ class Html } // Load DOM + libxml_disable_entity_loader(true); $dom = new \DOMDocument(); $dom->preserveWhiteSpace = $preserveWhiteSpace; $dom->loadXML($html); - self::$xpath = new \DOMXpath($dom); + self::$xpath = new \DOMXPath($dom); $node = $dom->getElementsByTagName('body'); self::parseNode($node->item(0), $element); @@ -139,6 +148,7 @@ class Html 'sup' => array('Property', null, null, $styles, null, 'superScript', true), 'sub' => array('Property', null, null, $styles, null, 'subScript', true), 'span' => array('Span', $node, null, $styles, null, null, null), + 'font' => array('Span', $node, null, $styles, null, null, null), 'table' => array('Table', $node, $element, $styles, null, null, null), 'tr' => array('Row', $node, $element, $styles, null, null, null), 'td' => array('Cell', $node, $element, $styles, null, null, null), @@ -646,7 +656,52 @@ class Html break; } } - $newElement = $element->addImage($src, $style); + $originSrc = $src; + if (strpos($src, 'data:image') !== false) { + $tmpDir = Settings::getTempDir() . '/'; + + $match = array(); + preg_match('/data:image\/(\w+);base64,(.+)/', $src, $match); + + $src = $imgFile = $tmpDir . uniqid() . '.' . $match[1]; + + $ifp = fopen($imgFile, 'wb'); + + if ($ifp !== false) { + fwrite($ifp, base64_decode($match[2])); + fclose($ifp); + } + } + $src = urldecode($src); + + if (!is_file($src) + && !is_null(self::$options) + && isset(self::$options['IMG_SRC_SEARCH']) + && isset(self::$options['IMG_SRC_REPLACE'])) { + $src = str_replace(self::$options['IMG_SRC_SEARCH'], self::$options['IMG_SRC_REPLACE'], $src); + } + + if (!is_file($src)) { + if ($imgBlob = @file_get_contents($src)) { + $tmpDir = Settings::getTempDir() . '/'; + $match = array(); + preg_match('/.+\.(\w+)$/', $src, $match); + $src = $tmpDir . uniqid() . '.' . $match[1]; + + $ifp = fopen($src, 'wb'); + + if ($ifp !== false) { + fwrite($ifp, $imgBlob); + fclose($ifp); + } + } + } + + if (is_file($src)) { + $newElement = $element->addImage($src, $style); + } else { + throw new \Exception("Could not load image $originSrc"); + } return $newElement; } @@ -719,7 +774,11 @@ class Html break; } } - self::parseInlineStyle($node, $styles['font']); + $styles['font'] = self::parseInlineStyle($node, $styles['font']); + + if (strpos($target, '#') === 0) { + return $element->addLink(substr($target, 1), $node->textContent, $styles['font'], $styles['paragraph'], true); + } return $element->addLink($target, $node->textContent, $styles['font'], $styles['paragraph']); } diff --git a/src/PhpWord/Shared/Microsoft/PasswordEncoder.php b/src/PhpWord/Shared/Microsoft/PasswordEncoder.php deleted file mode 100644 index fc0c7ecd..00000000 --- a/src/PhpWord/Shared/Microsoft/PasswordEncoder.php +++ /dev/null @@ -1,235 +0,0 @@ - array(1, 'md2'), - self::ALGORITHM_MD4 => array(2, 'md4'), - self::ALGORITHM_MD5 => array(3, 'md5'), - self::ALGORITHM_SHA_1 => array(4, 'sha1'), - self::ALGORITHM_MAC => array(5, ''), // 'mac' -> not possible with hash() - self::ALGORITHM_RIPEMD => array(6, 'ripemd'), - self::ALGORITHM_RIPEMD_160 => array(7, 'ripemd160'), - self::ALGORITHM_HMAC => array(9, ''), //'hmac' -> not possible with hash() - self::ALGORITHM_SHA_256 => array(12, 'sha256'), - self::ALGORITHM_SHA_384 => array(13, 'sha384'), - self::ALGORITHM_SHA_512 => array(14, 'sha512'), - ); - - private static $initialCodeArray = array( - 0xE1F0, - 0x1D0F, - 0xCC9C, - 0x84C0, - 0x110C, - 0x0E10, - 0xF1CE, - 0x313E, - 0x1872, - 0xE139, - 0xD40F, - 0x84F9, - 0x280C, - 0xA96A, - 0x4EC3, - ); - - private static $encryptionMatrix = array( - array(0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09), - array(0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF), - array(0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0), - array(0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40), - array(0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5), - array(0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A), - array(0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9), - array(0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0), - array(0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC), - array(0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10), - array(0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168), - array(0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C), - array(0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD), - array(0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC), - array(0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4), - ); - - private static $passwordMaxLength = 15; - - /** - * Create a hashed password that MS Word will be able to work with - * @see https://blogs.msdn.microsoft.com/vsod/2010/04/05/how-to-set-the-editing-restrictions-in-word-using-open-xml-sdk-2-0/ - * - * @param string $password - * @param string $algorithmName - * @param string $salt - * @param int $spinCount - * @return string - */ - public static function hashPassword($password, $algorithmName = self::ALGORITHM_SHA_1, $salt = null, $spinCount = 10000) - { - $origEncoding = mb_internal_encoding(); - mb_internal_encoding('UTF-8'); - - $password = mb_substr($password, 0, min(self::$passwordMaxLength, mb_strlen($password))); - - // Get the single-byte values by iterating through the Unicode characters of the truncated password. - // For each character, if the low byte is not equal to 0, take it. Otherwise, take the high byte. - $passUtf8 = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8'); - $byteChars = array(); - - for ($i = 0; $i < mb_strlen($password); $i++) { - $byteChars[$i] = ord(substr($passUtf8, $i * 2, 1)); - - if ($byteChars[$i] == 0) { - $byteChars[$i] = ord(substr($passUtf8, $i * 2 + 1, 1)); - } - } - - // build low-order word and hig-order word and combine them - $combinedKey = self::buildCombinedKey($byteChars); - // build reversed hexadecimal string - $hex = str_pad(strtoupper(dechex($combinedKey & 0xFFFFFFFF)), 8, '0', \STR_PAD_LEFT); - $reversedHex = $hex[6] . $hex[7] . $hex[4] . $hex[5] . $hex[2] . $hex[3] . $hex[0] . $hex[1]; - - $generatedKey = mb_convert_encoding($reversedHex, 'UCS-2LE', 'UTF-8'); - - // Implementation Notes List: - // Word requires that the initial hash of the password with the salt not be considered in the count. - // The initial hash of salt + key is not included in the iteration count. - $algorithm = self::getAlgorithm($algorithmName); - $generatedKey = hash($algorithm, $salt . $generatedKey, true); - - for ($i = 0; $i < $spinCount; $i++) { - $generatedKey = hash($algorithm, $generatedKey . pack('CCCC', $i, $i >> 8, $i >> 16, $i >> 24), true); - } - $generatedKey = base64_encode($generatedKey); - - mb_internal_encoding($origEncoding); - - return $generatedKey; - } - - /** - * Get algorithm from self::$algorithmMapping - * - * @param string $algorithmName - * @return string - */ - private static function getAlgorithm($algorithmName) - { - $algorithm = self::$algorithmMapping[$algorithmName][1]; - if ($algorithm == '') { - $algorithm = 'sha1'; - } - - return $algorithm; - } - - /** - * Returns the algorithm ID - * - * @param string $algorithmName - * @return int - */ - public static function getAlgorithmId($algorithmName) - { - return self::$algorithmMapping[$algorithmName][0]; - } - - /** - * Build combined key from low-order word and high-order word - * - * @param array $byteChars byte array representation of password - * @return int - */ - private static function buildCombinedKey($byteChars) - { - $byteCharsLength = count($byteChars); - // Compute the high-order word - // Initialize from the initial code array (see above), depending on the passwords length. - $highOrderWord = self::$initialCodeArray[$byteCharsLength - 1]; - - // For each character in the password: - // For every bit in the character, starting with the least significant and progressing to (but excluding) - // the most significant, if the bit is set, XOR the key’s high-order word with the corresponding word from - // the Encryption Matrix - for ($i = 0; $i < $byteCharsLength; $i++) { - $tmp = self::$passwordMaxLength - $byteCharsLength + $i; - $matrixRow = self::$encryptionMatrix[$tmp]; - for ($intBit = 0; $intBit < 7; $intBit++) { - if (($byteChars[$i] & (0x0001 << $intBit)) != 0) { - $highOrderWord = ($highOrderWord ^ $matrixRow[$intBit]); - } - } - } - - // Compute low-order word - // Initialize with 0 - $lowOrderWord = 0; - // For each character in the password, going backwards - for ($i = $byteCharsLength - 1; $i >= 0; $i--) { - // low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR character - $lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteChars[$i]); - } - // Lastly, low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR strPassword length XOR 0xCE4B. - $lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteCharsLength ^ 0xCE4B); - - // Combine the Low and High Order Word - return self::int32(($highOrderWord << 16) + $lowOrderWord); - } - - /** - * Simulate behaviour of (signed) int32 - * - * @codeCoverageIgnore - * @param int $value - * @return int - */ - private static function int32($value) - { - $value = ($value & 0xFFFFFFFF); - - if ($value & 0x80000000) { - $value = -((~$value & 0xFFFFFFFF) + 1); - } - - return $value; - } -} diff --git a/src/PhpWord/Style.php b/src/PhpWord/Style.php index 47242621..62783b63 100644 --- a/src/PhpWord/Style.php +++ b/src/PhpWord/Style.php @@ -39,7 +39,7 @@ class Style * Add paragraph style * * @param string $styleName - * @param array $styles + * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $styles * @return \PhpOffice\PhpWord\Style\Paragraph */ public static function addParagraphStyle($styleName, $styles) @@ -51,8 +51,8 @@ class Style * Add font style * * @param string $styleName - * @param array $fontStyle - * @param array $paragraphStyle + * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $fontStyle + * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $paragraphStyle * @return \PhpOffice\PhpWord\Style\Font */ public static function addFontStyle($styleName, $fontStyle, $paragraphStyle = null) @@ -64,7 +64,7 @@ class Style * Add link style * * @param string $styleName - * @param array $styles + * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $styles * @return \PhpOffice\PhpWord\Style\Font */ public static function addLinkStyle($styleName, $styles) @@ -76,7 +76,7 @@ class Style * Add numbering style * * @param string $styleName - * @param array $styleValues + * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $styleValues * @return \PhpOffice\PhpWord\Style\Numbering * @since 0.10.0 */ @@ -88,14 +88,14 @@ class Style /** * Add title style * - * @param int $depth - * @param array $fontStyle - * @param array $paragraphStyle + * @param int|null $depth Provide null to set title font + * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $fontStyle + * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $paragraphStyle * @return \PhpOffice\PhpWord\Style\Font */ public static function addTitleStyle($depth, $fontStyle, $paragraphStyle = null) { - if ($depth == null) { + if (empty($depth)) { $styleName = 'Title'; } else { $styleName = "Heading_{$depth}"; @@ -141,7 +141,7 @@ class Style /** * Set default paragraph style * - * @param array $styles Paragraph style definition + * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $styles Paragraph style definition * @return \PhpOffice\PhpWord\Style\Paragraph */ public static function setDefaultParagraphStyle($styles) diff --git a/src/PhpWord/Style/Chart.php b/src/PhpWord/Style/Chart.php index 041736be..5b02e636 100644 --- a/src/PhpWord/Style/Chart.php +++ b/src/PhpWord/Style/Chart.php @@ -46,6 +46,60 @@ class Chart extends AbstractStyle private $is3d = false; /** + * A list of colors to use in the chart + * + * @var array + */ + private $colors = array(); + + /** + * A list of display options for data labels + * + * @var array + */ + private $dataLabelOptions = array( + 'showVal' => true, // value + 'showCatName' => true, // category name + 'showLegendKey' => false, //show the cart legend + 'showSerName' => false, // series name + 'showPercent' => false, + 'showLeaderLines' => false, + 'showBubbleSize' => false, + ); + + /** + * A string that tells the writer where to write chart labels or to skip + * "nextTo" - sets labels next to the axis (bar graphs on the left) (default) + * "low" - labels on the left side of the graph + * "high" - labels on the right side of the graph + * + * @var string + */ + private $categoryLabelPosition = 'nextTo'; + + /** + * A string that tells the writer where to write chart labels or to skip + * "nextTo" - sets labels next to the axis (bar graphs on the bottom) (default) + * "low" - labels are below the graph + * "high" - labels above the graph + * + * @var string + */ + private $valueLabelPosition = 'nextTo'; + + /** + * @var string + */ + private $categoryAxisTitle; + + /** + * @var string + */ + private $valueAxisTitle; + + private $majorTickMarkPos = 'none'; + + /* * Show labels for axis * * @var bool @@ -146,6 +200,28 @@ class Chart extends AbstractStyle } /** + * Get the list of colors to use in a chart. + * + * @return array + */ + public function getColors() + { + return $this->colors; + } + + /** + * Set the colors to use in a chart. + * + * @param array $value a list of colors to use in the chart + */ + public function setColors($value = array()) + { + $this->colors = $value; + + return $this; + } + + /* * Show labels for axis * * @return bool @@ -169,6 +245,31 @@ class Chart extends AbstractStyle } /** + * get the list of options for data labels + * + * @return array + */ + public function getDataLabelOptions() + { + return $this->dataLabelOptions; + } + + /** + * Set values for data label options. + * This will only change values for options defined in $this->dataLabelOptions, and cannot create new ones. + * + * @param array $values [description] + */ + public function setDataLabelOptions($values = array()) + { + foreach (array_keys($this->dataLabelOptions) as $option) { + if (isset($values[$option])) { + $this->dataLabelOptions[$option] = $this->setBoolVal($values[$option], $this->dataLabelOptions[$option]); + } + } + } + + /* * Show Gridlines for Y-Axis * * @return bool @@ -192,6 +293,117 @@ class Chart extends AbstractStyle } /** + * Get the categoryLabelPosition setting + * + * @return string + */ + public function getCategoryLabelPosition() + { + return $this->categoryLabelPosition; + } + + /** + * Set the categoryLabelPosition setting + * "none" - skips writing labels + * "nextTo" - sets labels next to the (bar graphs on the left) + * "low" - labels on the left side of the graph + * "high" - labels on the right side of the graph + * + * @param mixed $labelPosition + * @return self + */ + public function setCategoryLabelPosition($labelPosition) + { + $enum = array('nextTo', 'low', 'high'); + $this->categoryLabelPosition = $this->setEnumVal($labelPosition, $enum, $this->categoryLabelPosition); + + return $this; + } + + /** + * Get the valueAxisLabelPosition setting + * + * @return string + */ + public function getValueLabelPosition() + { + return $this->valueLabelPosition; + } + + /** + * Set the valueLabelPosition setting + * "none" - skips writing labels + * "nextTo" - sets labels next to the value + * "low" - sets labels are below the graph + * "high" - sets labels above the graph + * + * @param string + * @param mixed $labelPosition + */ + public function setValueLabelPosition($labelPosition) + { + $enum = array('nextTo', 'low', 'high'); + $this->valueLabelPosition = $this->setEnumVal($labelPosition, $enum, $this->valueLabelPosition); + + return $this; + } + + /** + * Get the categoryAxisTitle + * @return string + */ + public function getCategoryAxisTitle() + { + return $this->categoryAxisTitle; + } + + /** + * Set the title that appears on the category side of the chart + * @param string $axisTitle + */ + public function setCategoryAxisTitle($axisTitle) + { + $this->categoryAxisTitle = $axisTitle; + + return $this; + } + + /** + * Get the valueAxisTitle + * @return string + */ + public function getValueAxisTitle() + { + return $this->valueAxisTitle; + } + + /** + * Set the title that appears on the value side of the chart + * @param string $axisTitle + */ + public function setValueAxisTitle($axisTitle) + { + $this->valueAxisTitle = $axisTitle; + + return $this; + } + + public function getMajorTickPosition() + { + return $this->majorTickMarkPos; + } + + /** + * set the position for major tick marks + * @param string $position [description] + */ + public function setMajorTickPosition($position) + { + $enum = array('in', 'out', 'cross', 'none'); + $this->majorTickMarkPos = $this->setEnumVal($position, $enum, $this->majorTickMarkPos); + } + + /* * Show Gridlines for X-Axis * * @return bool diff --git a/src/PhpWord/Style/Font.php b/src/PhpWord/Style/Font.php index c58cee49..e9f3c9d6 100644 --- a/src/PhpWord/Style/Font.php +++ b/src/PhpWord/Style/Font.php @@ -264,7 +264,7 @@ class Font extends AbstractStyle * Create new font style * * @param string $type Type of font - * @param array $paragraph Paragraph styles definition + * @param array|string|\PhpOffice\PhpWord\Style\AbstractStyle $paragraph Paragraph styles definition */ public function __construct($type = 'text', $paragraph = null) { diff --git a/src/PhpWord/Style/Language.php b/src/PhpWord/Style/Language.php index 8bb60149..4a50a595 100644 --- a/src/PhpWord/Style/Language.php +++ b/src/PhpWord/Style/Language.php @@ -47,6 +47,9 @@ final class Language extends AbstractStyle const HE_IL = 'he-IL'; const HE_IL_ID = 1037; + const IT_IT = 'it-IT'; + const IT_IT_ID = 1040; + const JA_JP = 'ja-JP'; const JA_JP_ID = 1041; diff --git a/src/PhpWord/Style/Table.php b/src/PhpWord/Style/Table.php index feb028da..0f7bf7dc 100644 --- a/src/PhpWord/Style/Table.php +++ b/src/PhpWord/Style/Table.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\JcTable; use PhpOffice\PhpWord\SimpleType\TblWidth; @@ -159,6 +160,16 @@ class Table extends Border */ private $position; + /** @var TblWidthComplexType|null */ + private $indent; + + /** + * The width of each column, computed based on the max cell width of each column + * + * @var int[] + */ + private $columnWidths; + /** * Create new table style * @@ -724,4 +735,44 @@ class Table extends Border return $this; } + + /** + * @return TblWidthComplexType + */ + public function getIndent() + { + return $this->indent; + } + + /** + * @param TblWidthComplexType $indent + * @return self + * @see http://www.datypic.com/sc/ooxml/e-w_tblInd-1.html + */ + public function setIndent(TblWidthComplexType $indent) + { + $this->indent = $indent; + + return $this; + } + + /** + * Get the columnWidths + * + * @return number[] + */ + public function getColumnWidths() + { + return $this->columnWidths; + } + + /** + * The column widths + * + * @param int[] $value + */ + public function setColumnWidths(array $value = null) + { + $this->columnWidths = $value; + } } diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 72446ae7..946d6691 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -113,6 +113,7 @@ class TemplateProcessor */ protected function transformSingleXml($xml, $xsltProcessor) { + libxml_disable_entity_loader(true); $domDocument = new \DOMDocument(); if (false === $domDocument->loadXML($xml)) { throw new Exception('Could not load the given XML document.'); diff --git a/src/PhpWord/Writer/ODText/Element/Table.php b/src/PhpWord/Writer/ODText/Element/Table.php index 8a21ee1b..088330ae 100644 --- a/src/PhpWord/Writer/ODText/Element/Table.php +++ b/src/PhpWord/Writer/ODText/Element/Table.php @@ -17,6 +17,10 @@ namespace PhpOffice\PhpWord\Writer\ODText\Element; +use PhpOffice\Common\XMLWriter; +use PhpOffice\PhpWord\Element\Row as RowElement; +use PhpOffice\PhpWord\Element\Table as TableElement; + /** * Table element writer * @@ -36,32 +40,59 @@ class Table extends AbstractElement } $rows = $element->getRows(); $rowCount = count($rows); - $colCount = $element->countColumns(); if ($rowCount > 0) { $xmlWriter->startElement('table:table'); $xmlWriter->writeAttribute('table:name', $element->getElementId()); $xmlWriter->writeAttribute('table:style', $element->getElementId()); - $xmlWriter->startElement('table:table-column'); - $xmlWriter->writeAttribute('table:number-columns-repeated', $colCount); - $xmlWriter->endElement(); // table:table-column + // Write columns + $this->writeColumns($xmlWriter, $element); + // Write rows foreach ($rows as $row) { - $xmlWriter->startElement('table:table-row'); - /** @var $row \PhpOffice\PhpWord\Element\Row Type hint */ - foreach ($row->getCells() as $cell) { - $xmlWriter->startElement('table:table-cell'); - $xmlWriter->writeAttribute('office:value-type', 'string'); - - $containerWriter = new Container($xmlWriter, $cell); - $containerWriter->write(); - - $xmlWriter->endElement(); // table:table-cell - } - $xmlWriter->endElement(); // table:table-row + $this->writeRow($xmlWriter, $row); } $xmlWriter->endElement(); // table:table } } + + /** + * Write column. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\Table $element + */ + private function writeColumns(XMLWriter $xmlWriter, TableElement $element) + { + $colCount = $element->countColumns(); + + for ($i = 0; $i < $colCount; $i++) { + $xmlWriter->startElement('table:table-column'); + $xmlWriter->writeAttribute('table:style-name', $element->getElementId() . '.' . $i); + $xmlWriter->endElement(); + } + } + + /** + * Write row. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\Row $row + */ + private function writeRow(XMLWriter $xmlWriter, RowElement $row) + { + $xmlWriter->startElement('table:table-row'); + /** @var $row \PhpOffice\PhpWord\Element\Row Type hint */ + foreach ($row->getCells() as $cell) { + $xmlWriter->startElement('table:table-cell'); + $xmlWriter->writeAttribute('office:value-type', 'string'); + + $containerWriter = new Container($xmlWriter, $cell); + $containerWriter->write(); + + $xmlWriter->endElement(); // table:table-cell + } + $xmlWriter->endElement(); // table:table-row + } } diff --git a/src/PhpWord/Writer/ODText/Part/Content.php b/src/PhpWord/Writer/ODText/Part/Content.php index a50eea7b..99ee9353 100644 --- a/src/PhpWord/Writer/ODText/Part/Content.php +++ b/src/PhpWord/Writer/ODText/Part/Content.php @@ -239,6 +239,7 @@ class Content extends AbstractPart $style->setStyleName('fr' . $element->getMediaIndex()); $this->autoStyles['Image'][] = $style; } elseif ($element instanceof Table) { + /** @var \PhpOffice\PhpWord\Style\Table $style */ $style = $element->getStyle(); if ($style === null) { $style = new TableStyle(); @@ -246,6 +247,7 @@ class Content extends AbstractPart $style = Style::getStyle($style); } $style->setStyleName($element->getElementId()); + $style->setColumnWidths($element->findFirstDefinedCellWidths()); $this->autoStyles['Table'][] = $style; } } diff --git a/src/PhpWord/Writer/ODText/Style/Table.php b/src/PhpWord/Writer/ODText/Style/Table.php index 249321cf..5ddee25a 100644 --- a/src/PhpWord/Writer/ODText/Style/Table.php +++ b/src/PhpWord/Writer/ODText/Style/Table.php @@ -45,5 +45,19 @@ class Table extends AbstractStyle $xmlWriter->writeAttribute('table:align', 'center'); $xmlWriter->endElement(); // style:table-properties $xmlWriter->endElement(); // style:style + + $cellWidths = $style->getColumnWidths(); + $countCellWidths = count($cellWidths); + + for ($i = 0; $i < $countCellWidths; $i++) { + $width = $cellWidths[$i]; + $xmlWriter->startElement('style:style'); + $xmlWriter->writeAttribute('style:name', $style->getStyleName() . '.' . $i); + $xmlWriter->writeAttribute('style:family', 'table-column'); + $xmlWriter->startElement('style:table-column-properties'); + $xmlWriter->writeAttribute('style:column-width', number_format($width * 0.0017638889, 2, '.', '') . 'cm'); + $xmlWriter->endElement(); // style:table-column-properties + $xmlWriter->endElement(); // style:style + } } } diff --git a/src/PhpWord/Writer/RTF/Style/Indentation.php b/src/PhpWord/Writer/RTF/Style/Indentation.php new file mode 100644 index 00000000..dd52230e --- /dev/null +++ b/src/PhpWord/Writer/RTF/Style/Indentation.php @@ -0,0 +1,45 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Indentation) { + return ''; + } + + $content = '\fi' . $style->getFirstLine(); + $content .= '\li' . $style->getLeft(); + $content .= '\ri' . $style->getRight(); + + return $content . ' '; + } +} diff --git a/src/PhpWord/Writer/RTF/Style/Paragraph.php b/src/PhpWord/Writer/RTF/Style/Paragraph.php index 3b8690cd..cb50a31b 100644 --- a/src/PhpWord/Writer/RTF/Style/Paragraph.php +++ b/src/PhpWord/Writer/RTF/Style/Paragraph.php @@ -64,9 +64,49 @@ class Paragraph extends AbstractStyle if (isset($alignments[$style->getAlignment()])) { $content .= $alignments[$style->getAlignment()]; } + $content .= $this->writeIndentation($style->getIndentation()); $content .= $this->getValueIf($spaceBefore !== null, '\sb' . $spaceBefore); $content .= $this->getValueIf($spaceAfter !== null, '\sa' . $spaceAfter); + $styles = $style->getStyleValues(); + $content .= $this->writeTabs($styles['tabs']); + + return $content; + } + + /** + * Writes an \PhpOffice\PhpWord\Style\Indentation + * + * @param null|\PhpOffice\PhpWord\Style\Indentation $indent + * @return string + */ + private function writeIndentation($indent = null) + { + if (isset($indent) && $indent instanceof \PhpOffice\PhpWord\Style\Indentation) { + $writer = new Indentation($indent); + + return $writer->write(); + } + + return ''; + } + + /** + * Writes tabs + * + * @param \PhpOffice\PhpWord\Style\Tab[] $tabs + * @return string + */ + private function writeTabs($tabs = null) + { + $content = ''; + if (!empty($tabs)) { + foreach ($tabs as $tab) { + $styleWriter = new Tab($tab); + $content .= $styleWriter->write(); + } + } + return $content; } diff --git a/src/PhpWord/Writer/RTF/Style/Tab.php b/src/PhpWord/Writer/RTF/Style/Tab.php new file mode 100644 index 00000000..fe1f9363 --- /dev/null +++ b/src/PhpWord/Writer/RTF/Style/Tab.php @@ -0,0 +1,49 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Tab) { + return; + } + $tabs = array( + \PhpOffice\PhpWord\Style\Tab::TAB_STOP_RIGHT => '\tqr', + \PhpOffice\PhpWord\Style\Tab::TAB_STOP_CENTER => '\tqc', + \PhpOffice\PhpWord\Style\Tab::TAB_STOP_DECIMAL => '\tqdec', + ); + $content = ''; + if (isset($tabs[$style->getType()])) { + $content .= $tabs[$style->getType()]; + } + $content .= '\tx' . $style->getPosition(); + + return $content; + } +} diff --git a/src/PhpWord/Writer/Word2007/Element/Field.php b/src/PhpWord/Writer/Word2007/Element/Field.php index cf3fbd66..e79dd24a 100644 --- a/src/PhpWord/Writer/Word2007/Element/Field.php +++ b/src/PhpWord/Writer/Word2007/Element/Field.php @@ -177,6 +177,9 @@ class Field extends Text case 'macroname': $propertiesAndOptions .= $propval . ' '; break; + default: + $propertiesAndOptions .= '"' . $propval . '" '; + break; } } diff --git a/src/PhpWord/Writer/Word2007/Element/Table.php b/src/PhpWord/Writer/Word2007/Element/Table.php index 25a48ab2..c365b028 100644 --- a/src/PhpWord/Writer/Word2007/Element/Table.php +++ b/src/PhpWord/Writer/Word2007/Element/Table.php @@ -76,21 +76,7 @@ class Table extends AbstractElement */ private function writeColumns(XMLWriter $xmlWriter, TableElement $element) { - $rows = $element->getRows(); - $rowCount = count($rows); - - $cellWidths = array(); - for ($i = 0; $i < $rowCount; $i++) { - $row = $rows[$i]; - $cells = $row->getCells(); - if (count($cells) <= count($cellWidths)) { - continue; - } - $cellWidths = array(); - foreach ($cells as $cell) { - $cellWidths[] = $cell->getWidth(); - } - } + $cellWidths = $element->findFirstDefinedCellWidths(); $xmlWriter->startElement('w:tblGrid'); foreach ($cellWidths as $width) { diff --git a/src/PhpWord/Writer/Word2007/Element/Title.php b/src/PhpWord/Writer/Word2007/Element/Title.php index 858ecfef..6a05a34d 100644 --- a/src/PhpWord/Writer/Word2007/Element/Title.php +++ b/src/PhpWord/Writer/Word2007/Element/Title.php @@ -47,6 +47,7 @@ class Title extends AbstractElement $xmlWriter->endElement(); } + $bookmarkRId = null; if ($element->getDepth() !== 0) { $rId = $element->getRelationId(); $bookmarkRId = $element->getPhpWord()->addBookmark(); diff --git a/src/PhpWord/Writer/Word2007/Part/Chart.php b/src/PhpWord/Writer/Word2007/Part/Chart.php index 2aeccca0..5a3ef276 100644 --- a/src/PhpWord/Writer/Word2007/Part/Chart.php +++ b/src/PhpWord/Writer/Word2007/Part/Chart.php @@ -41,14 +41,18 @@ class Chart extends AbstractPart * @var array */ private $types = array( - 'pie' => array('type' => 'pie', 'colors' => 1), - 'doughnut' => array('type' => 'doughnut', 'colors' => 1, 'hole' => 75, 'no3d' => true), - 'bar' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'bar'), - 'column' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'col'), - 'line' => array('type' => 'line', 'colors' => 0, 'axes' => true), - 'area' => array('type' => 'area', 'colors' => 0, 'axes' => true), - 'radar' => array('type' => 'radar', 'colors' => 0, 'axes' => true, 'radar' => 'standard', 'no3d' => true), - 'scatter' => array('type' => 'scatter', 'colors' => 0, 'axes' => true, 'scatter' => 'marker', 'no3d' => true), + 'pie' => array('type' => 'pie', 'colors' => 1), + 'doughnut' => array('type' => 'doughnut', 'colors' => 1, 'hole' => 75, 'no3d' => true), + 'bar' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'bar', 'grouping' => 'clustered'), + 'stacked_bar' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'bar', 'grouping' => 'stacked'), + 'percent_stacked_bar' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'bar', 'grouping' => 'percentStacked'), + 'column' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'col', 'grouping' => 'clustered'), + 'stacked_column' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'col', 'grouping' => 'stacked'), + 'percent_stacked_column' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'col', 'grouping' => 'percentStacked'), + 'line' => array('type' => 'line', 'colors' => 0, 'axes' => true), + 'area' => array('type' => 'area', 'colors' => 0, 'axes' => true), + 'radar' => array('type' => 'radar', 'colors' => 0, 'axes' => true, 'radar' => 'standard', 'no3d' => true), + 'scatter' => array('type' => 'scatter', 'colors' => 0, 'axes' => true, 'scatter' => 'marker', 'no3d' => true), ); /** @@ -145,7 +149,7 @@ class Chart extends AbstractPart } if (isset($this->options['bar'])) { $xmlWriter->writeElementBlock('c:barDir', 'val', $this->options['bar']); // bar|col - $xmlWriter->writeElementBlock('c:grouping', 'val', 'clustered'); // 3d; standard = percentStacked + $xmlWriter->writeElementBlock('c:grouping', 'val', $this->options['grouping']); // 3d; standard = percentStacked } if (isset($this->options['radar'])) { $xmlWriter->writeElementBlock('c:radarStyle', 'val', $this->options['radar']); @@ -157,6 +161,8 @@ class Chart extends AbstractPart // Series $this->writeSeries($xmlWriter, isset($this->options['scatter'])); + $xmlWriter->writeElementBlock('c:overlap', 'val', '100'); + // Axes if (isset($this->options['axes'])) { $xmlWriter->writeElementBlock('c:axId', 'val', 1); @@ -183,6 +189,8 @@ class Chart extends AbstractPart private function writeSeries(XMLWriter $xmlWriter, $scatter = false) { $series = $this->element->getSeries(); + $style = $this->element->getStyle(); + $colors = $style->getColors(); $index = 0; foreach ($series as $seriesItem) { @@ -194,6 +202,32 @@ class Chart extends AbstractPart $xmlWriter->writeElementBlock('c:idx', 'val', $index); $xmlWriter->writeElementBlock('c:order', 'val', $index); + if (!is_null($seriesItem['name']) && $seriesItem['name'] != '') { + $xmlWriter->startElement('c:tx'); + $xmlWriter->startElement('c:strRef'); + $xmlWriter->startElement('c:strCache'); + $xmlWriter->writeElementBlock('c:ptCount', 'val', 1); + $xmlWriter->startElement('c:pt'); + $xmlWriter->writeAttribute('idx', 0); + $xmlWriter->startElement('c:v'); + $xmlWriter->writeRaw($seriesItem['name']); + $xmlWriter->endElement(); // c:v + $xmlWriter->endElement(); // c:pt + $xmlWriter->endElement(); // c:strCache + $xmlWriter->endElement(); // c:strRef + $xmlWriter->endElement(); // c:tx + } + + // The c:dLbls was added to make word charts look more like the reports in SurveyGizmo + // This section needs to be made configurable before a pull request is made + $xmlWriter->startElement('c:dLbls'); + + foreach ($style->getDataLabelOptions() as $option => $val) { + $xmlWriter->writeElementBlock("c:{$option}", 'val', (int) $val); + } + + $xmlWriter->endElement(); // c:dLbls + if (isset($this->options['scatter'])) { $this->writeShape($xmlWriter); } @@ -204,6 +238,26 @@ class Chart extends AbstractPart } else { $this->writeSeriesItem($xmlWriter, 'cat', $categories); $this->writeSeriesItem($xmlWriter, 'val', $values); + + // setting the chart colors was taken from https://github.com/PHPOffice/PHPWord/issues/494 + if (is_array($colors) && count($colors)) { + // This is a workaround to make each series in a stack chart use a different color + if ($this->options['type'] == 'bar' && stristr($this->options['grouping'], 'stacked')) { + array_shift($colors); + } + $colorIndex = 0; + foreach ($colors as $color) { + $xmlWriter->startElement('c:dPt'); + $xmlWriter->writeElementBlock('c:idx', 'val', $colorIndex); + $xmlWriter->startElement('c:spPr'); + $xmlWriter->startElement('a:solidFill'); + $xmlWriter->writeElementBlock('a:srgbClr', 'val', $color); + $xmlWriter->endElement(); // a:solidFill + $xmlWriter->endElement(); // c:spPr + $xmlWriter->endElement(); // c:dPt + $colorIndex++; + } + } } $xmlWriter->endElement(); // c:ser @@ -230,14 +284,19 @@ class Chart extends AbstractPart $xmlWriter->startElement($itemType); $xmlWriter->startElement($itemLit); + $xmlWriter->writeElementBlock('c:ptCount', 'val', count($values)); $index = 0; foreach ($values as $value) { $xmlWriter->startElement('c:pt'); $xmlWriter->writeAttribute('idx', $index); - $xmlWriter->startElement('c:v'); - $xmlWriter->text($value); - $xmlWriter->endElement(); // c:v + if (\PhpOffice\PhpWord\Settings::isOutputEscapingEnabled()) { + $xmlWriter->writeElement('c:v', $value); + } else { + $xmlWriter->startElement('c:v'); + $xmlWriter->writeRaw($value); + $xmlWriter->endElement(); // c:v + } $xmlWriter->endElement(); // c:pt $index++; } @@ -266,15 +325,33 @@ class Chart extends AbstractPart $xmlWriter->writeElementBlock('c:axId', 'val', $axisId); $xmlWriter->writeElementBlock('c:axPos', 'val', $axisPos); + + $categoryAxisTitle = $style->getCategoryAxisTitle(); + $valueAxisTitle = $style->getValueAxisTitle(); + + if ($axisType == 'c:catAx') { + if (!is_null($categoryAxisTitle)) { + $this->writeAxisTitle($xmlWriter, $categoryAxisTitle); + } + } elseif ($axisType == 'c:valAx') { + if (!is_null($valueAxisTitle)) { + $this->writeAxisTitle($xmlWriter, $valueAxisTitle); + } + } + $xmlWriter->writeElementBlock('c:crossAx', 'val', $axisCross); $xmlWriter->writeElementBlock('c:auto', 'val', 1); if (isset($this->options['axes'])) { $xmlWriter->writeElementBlock('c:delete', 'val', 0); - $xmlWriter->writeElementBlock('c:majorTickMark', 'val', 'none'); + $xmlWriter->writeElementBlock('c:majorTickMark', 'val', $style->getMajorTickPosition()); $xmlWriter->writeElementBlock('c:minorTickMark', 'val', 'none'); if ($style->showAxisLabels()) { - $xmlWriter->writeElementBlock('c:tickLblPos', 'val', 'nextTo'); + if ($axisType == 'c:catAx') { + $xmlWriter->writeElementBlock('c:tickLblPos', 'val', $style->getCategoryLabelPosition()); + } else { + $xmlWriter->writeElementBlock('c:tickLblPos', 'val', $style->getValueLabelPosition()); + } } else { $xmlWriter->writeElementBlock('c:tickLblPos', 'val', 'none'); } @@ -312,4 +389,30 @@ class Chart extends AbstractPart $xmlWriter->endElement(); // a:ln $xmlWriter->endElement(); // c:spPr } + + private function writeAxisTitle(XMLWriter $xmlWriter, $title) + { + $xmlWriter->startElement('c:title'); //start c:title + $xmlWriter->startElement('c:tx'); //start c:tx + $xmlWriter->startElement('c:rich'); // start c:rich + $xmlWriter->writeElement('a:bodyPr'); + $xmlWriter->writeElement('a:lstStyle'); + $xmlWriter->startElement('a:p'); + $xmlWriter->startElement('a:pPr'); + $xmlWriter->writeElement('a:defRPr'); + $xmlWriter->endElement(); // end a:pPr + $xmlWriter->startElement('a:r'); + $xmlWriter->writeElementBlock('a:rPr', 'lang', 'en-US'); + + $xmlWriter->startElement('a:t'); + $xmlWriter->writeRaw($title); + $xmlWriter->endElement(); //end a:t + + $xmlWriter->endElement(); // end a:r + $xmlWriter->endElement(); //end a:p + $xmlWriter->endElement(); //end c:rich + $xmlWriter->endElement(); // end c:tx + $xmlWriter->writeElementBlock('c:overlay', 'val', '0'); + $xmlWriter->endElement(); // end c:title + } } diff --git a/src/PhpWord/Writer/Word2007/Part/Settings.php b/src/PhpWord/Writer/Word2007/Part/Settings.php index 42d3a5d5..b764642a 100644 --- a/src/PhpWord/Writer/Word2007/Part/Settings.php +++ b/src/PhpWord/Writer/Word2007/Part/Settings.php @@ -17,9 +17,9 @@ namespace PhpOffice\PhpWord\Writer\Word2007\Part; +use PhpOffice\Common\Microsoft\PasswordEncoder; use PhpOffice\PhpWord\ComplexType\ProofState; use PhpOffice\PhpWord\ComplexType\TrackChangesView; -use PhpOffice\PhpWord\Shared\Microsoft\PasswordEncoder; use PhpOffice\PhpWord\Style\Language; /** diff --git a/src/PhpWord/Writer/Word2007/Style/Table.php b/src/PhpWord/Writer/Word2007/Style/Table.php index eb5af86d..7f49be7c 100644 --- a/src/PhpWord/Writer/Word2007/Style/Table.php +++ b/src/PhpWord/Writer/Word2007/Style/Table.php @@ -79,6 +79,7 @@ class Table extends AbstractStyle $this->writeTblWidth($xmlWriter, 'w:tblW', $style->getUnit(), $style->getWidth()); $this->writeTblWidth($xmlWriter, 'w:tblCellSpacing', TblWidth::TWIP, $style->getCellSpacing()); + $this->writeIndent($xmlWriter, $style); $this->writeLayout($xmlWriter, $style->getLayout()); // Position @@ -216,4 +217,19 @@ class Table extends AbstractStyle { $this->width = $value; } + + /** + * @param XMLWriter $xmlWriter + * @param TableStyle $style + */ + private function writeIndent(XMLWriter $xmlWriter, TableStyle $style) + { + $indent = $style->getIndent(); + + if ($indent === null) { + return; + } + + $this->writeTblWidth($xmlWriter, 'w:tblInd', $indent->getType(), $indent->getValue()); + } } diff --git a/tests/PhpWord/Reader/Word2007/StyleTest.php b/tests/PhpWord/Reader/Word2007/StyleTest.php index 9bb6d3bd..d64079fa 100644 --- a/tests/PhpWord/Reader/Word2007/StyleTest.php +++ b/tests/PhpWord/Reader/Word2007/StyleTest.php @@ -126,4 +126,23 @@ class StyleTest extends AbstractTestReader $fontStyle = $textRun->getElement(0)->getFontStyle(); $this->assertEquals(15, $fontStyle->getPosition()); } + + public function testReadIndent() + { + $documentXml = ' + + + + '; + + $phpWord = $this->getDocumentFromString(array('document' => $documentXml)); + + $elements = $phpWord->getSection(0)->getElements(); + $this->assertInstanceOf('PhpOffice\PhpWord\Element\Table', $elements[0]); + $this->assertInstanceOf('PhpOffice\PhpWord\Style\Table', $elements[0]->getStyle()); + /** @var \PhpOffice\PhpWord\Style\Table $tableStyle */ + $tableStyle = $elements[0]->getStyle(); + $this->assertSame(TblWidth::TWIP, $tableStyle->getIndent()->getType()); + $this->assertSame(2160, $tableStyle->getIndent()->getValue()); + } } diff --git a/tests/PhpWord/Shared/HtmlTest.php b/tests/PhpWord/Shared/HtmlTest.php index 1171489c..32f4bf46 100644 --- a/tests/PhpWord/Shared/HtmlTest.php +++ b/tests/PhpWord/Shared/HtmlTest.php @@ -116,6 +116,21 @@ class HtmlTest extends \PHPUnit\Framework\TestCase $this->assertEquals('single', $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:rPr/w:u', 'w:val')); } + /** + * Test font + */ + public function testParseFont() + { + $html = 'test'; + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr')); + //TODO check style + } + /** * Test line-height style */ @@ -125,6 +140,7 @@ class HtmlTest extends \PHPUnit\Framework\TestCase $section = $phpWord->addSection(); Html::addHtml($section, '

test

'); Html::addHtml($section, '

test

'); + Html::addHtml($section, '

test

'); $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); $this->assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:pPr/w:spacing')); @@ -134,6 +150,10 @@ class HtmlTest extends \PHPUnit\Framework\TestCase $this->assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:pPr/w:spacing')); $this->assertEquals(300, $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:pPr/w:spacing', 'w:line')); $this->assertEquals(LineSpacingRule::EXACT, $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:pPr/w:spacing', 'w:lineRule')); + + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p[3]/w:pPr/w:spacing')); + $this->assertEquals(Paragraph::LINE_HEIGHT * 1.2, $doc->getElementAttribute('/w:document/w:body/w:p[3]/w:pPr/w:spacing', 'w:line')); + $this->assertEquals(LineSpacingRule::AUTO, $doc->getElementAttribute('/w:document/w:body/w:p[3]/w:pPr/w:spacing', 'w:lineRule')); } /** @@ -442,6 +462,78 @@ class HtmlTest extends \PHPUnit\Framework\TestCase $this->assertStringMatchesFormat('%Smso-position-horizontal:left%S', $doc->getElementAttribute($baseXpath . '[2]/w:pict/v:shape', 'style')); } + /** + * Test parsing of remote img + */ + public function testParseRemoteImage() + { + $src = 'https://phpword.readthedocs.io/en/latest/_images/phpword.png'; + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $html = '

'; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $baseXpath = '/w:document/w:body/w:p/w:r'; + $this->assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape')); + } + + /** + * Test parsing embedded image + */ + public function testParseEmbeddedImage() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $html = '

'; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $baseXpath = '/w:document/w:body/w:p/w:r'; + $this->assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape')); + } + + /** + * Test parsing of remote img that can be found locally + */ + public function testParseRemoteLocalImage() + { + $src = 'https://fakedomain.io/images/firefox.png'; + $localPath = __DIR__ . '/../_files/images/'; + $options = array( + 'IMG_SRC_SEARCH' => 'https://fakedomain.io/images/', + 'IMG_SRC_REPLACE' => $localPath, + ); + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $html = '

'; + Html::addHtml($section, $html, false, true, $options); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $baseXpath = '/w:document/w:body/w:p/w:r'; + $this->assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape')); + } + + /** + * Test parsing of remote img that can be found locally + * + * @expectedException \Exception + */ + public function testCouldNotLoadImage() + { + $src = 'https://fakedomain.io/images/firefox.png'; + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $html = '

'; + Html::addHtml($section, $html, false, true); + } + public function testParseLink() { $phpWord = new \PhpOffice\PhpWord\PhpWord(); @@ -453,6 +545,19 @@ class HtmlTest extends \PHPUnit\Framework\TestCase $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:hyperlink')); $this->assertEquals('link text', $doc->getElement('/w:document/w:body/w:p/w:hyperlink/w:r/w:t')->nodeValue); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:hyperlink/w:r/w:rPr/w:u')); + $this->assertEquals('single', $doc->getElementAttribute('/w:document/w:body/w:p/w:hyperlink/w:r/w:rPr/w:u', 'w:val')); + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $section->addBookmark('bookmark'); + $html = '

internal link text

'; + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:hyperlink')); + $this->assertTrue($doc->getElement('/w:document/w:body/w:p/w:hyperlink')->hasAttribute('w:anchor')); + $this->assertEquals('bookmark', $doc->getElement('/w:document/w:body/w:p/w:hyperlink')->getAttribute('w:anchor')); } public function testParseMalformedStyleIsIgnored() diff --git a/tests/PhpWord/Shared/Microsoft/PasswordEncoderTest.php b/tests/PhpWord/Shared/Microsoft/PasswordEncoderTest.php deleted file mode 100644 index 5a050c54..00000000 --- a/tests/PhpWord/Shared/Microsoft/PasswordEncoderTest.php +++ /dev/null @@ -1,91 +0,0 @@ -assertEquals($chart->getWidth(), 1000000); + + $chart->setWidth(200); + + $this->assertEquals($chart->getWidth(), 200); + } + + /** + * Testing getter and setter for chart height + */ + public function testSetGetHeight() + { + $chart = new Chart(); + + $this->assertEquals($chart->getHeight(), 1000000); + + $chart->setHeight(200); + + $this->assertEquals($chart->getHeight(), 200); + } + + /** + * Testing getter and setter for is3d + */ + public function testSetIs3d() + { + $chart = new Chart(); + + $this->assertEquals($chart->is3d(), false); + + $chart->set3d(true); + + $this->assertEquals($chart->is3d(), true); + } + + /** + * Testing getter and setter for chart colors + */ + public function testSetGetColors() + { + $chart = new Chart(); + + $this->assertInternalType('array', $chart->getColors()); + + $this->assertEquals(count($chart->getColors()), 0); + + $chart->setColors(array('FFFFFFFF', 'FF000000', 'FFFF0000')); + + $this->assertEquals($chart->getColors(), array('FFFFFFFF', 'FF000000', 'FFFF0000')); + } + + /** + * Testing getter and setter for dataLabelOptions + */ + public function testSetGetDataLabelOptions() + { + $chart = new Chart(); + + $originalDataLabelOptions = array( + 'showVal' => true, + 'showCatName' => true, + 'showLegendKey' => false, + 'showSerName' => false, + 'showPercent' => false, + 'showLeaderLines' => false, + 'showBubbleSize' => false, + ); + + $this->assertEquals($chart->getDataLabelOptions(), $originalDataLabelOptions); + + $changedDataLabelOptions = array( + 'showVal' => false, + 'showCatName' => false, + 'showLegendKey' => true, + 'showSerName' => true, + 'showPercent' => true, + 'showLeaderLines' => true, + 'showBubbleSize' => true, + ); + + $chart->setDataLabelOptions( + array( + 'showVal' => false, + 'showCatName' => false, + 'showLegendKey' => true, + 'showSerName' => true, + 'showPercent' => true, + 'showLeaderLines' => true, + 'showBubbleSize' => true, + ) + ); + $this->assertEquals($chart->getDataLabelOptions(), $changedDataLabelOptions); + } + + /** + * Testing categoryLabelPosition getter and setter + */ + public function testSetGetCategoryLabelPosition() + { + $chart = new Chart(); + + $this->assertEquals($chart->getCategoryLabelPosition(), 'nextTo'); + + $chart->setCategoryLabelPosition('high'); + + $this->assertEquals($chart->getCategoryLabelPosition(), 'high'); + } + + /** + * Testing valueLabelPosition getter and setter + */ + public function testSetGetValueLabelPosition() + { + $chart = new Chart(); + + $this->assertEquals($chart->getValueLabelPosition(), 'nextTo'); + + $chart->setValueLabelPosition('low'); + + $this->assertEquals($chart->getValueLabelPosition(), 'low'); + } + + /** + * Testing categoryAxisTitle getter and setter + */ + public function testSetGetCategoryAxisTitle() + { + $chart = new Chart(); + + $chart->getCategoryAxisTitle(); + + $this->assertEquals($chart->getCategoryAxisTitle(), null); + + $chart->setCategoryAxisTitle('Test Category Axis Title'); + + $this->assertEquals($chart->getCategoryAxisTitle(), 'Test Category Axis Title'); + } + + /** + * Testing valueAxisTitle getter and setter + */ + public function testSetGetValueAxisTitle() + { + $chart = new Chart(); + + $chart->getValueAxisTitle(); + + $this->assertEquals($chart->getValueAxisTitle(), null); + + $chart->setValueAxisTitle('Test Value Axis Title'); + + $this->assertEquals($chart->getValueAxisTitle(), 'Test Value Axis Title'); + } +} diff --git a/tests/PhpWord/Style/PaperTest.php b/tests/PhpWord/Style/PaperTest.php index 688f31af..f8f00701 100644 --- a/tests/PhpWord/Style/PaperTest.php +++ b/tests/PhpWord/Style/PaperTest.php @@ -17,7 +17,6 @@ namespace PhpOffice\PhpWord\Style; -use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\TestHelperDOCX; /** diff --git a/tests/PhpWord/Style/TableTest.php b/tests/PhpWord/Style/TableTest.php index 332d31aa..91fc3550 100644 --- a/tests/PhpWord/Style/TableTest.php +++ b/tests/PhpWord/Style/TableTest.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType; use PhpOffice\PhpWord\SimpleType\JcTable; use PhpOffice\PhpWord\SimpleType\TblWidth; @@ -57,6 +58,7 @@ class TableTest extends \PHPUnit\Framework\TestCase $this->assertNull($object->getBgColor()); $this->assertEquals(Table::LAYOUT_AUTO, $object->getLayout()); $this->assertEquals(TblWidth::AUTO, $object->getUnit()); + $this->assertNull($object->getIndent()); } /** @@ -208,4 +210,13 @@ class TableTest extends \PHPUnit\Framework\TestCase $this->assertNotNull($object->getPosition()); $this->assertEquals(TablePosition::VANCHOR_PAGE, $object->getPosition()->getVertAnchor()); } + + public function testIndent() + { + $indent = new TblWidthComplexType(100, TblWidth::TWIP); + + $table = new Table(array('indent' => $indent)); + + $this->assertSame($indent, $table->getIndent()); + } } diff --git a/tests/PhpWord/Writer/HTML/ElementTest.php b/tests/PhpWord/Writer/HTML/ElementTest.php index 90044b92..b76ddded 100644 --- a/tests/PhpWord/Writer/HTML/ElementTest.php +++ b/tests/PhpWord/Writer/HTML/ElementTest.php @@ -70,7 +70,7 @@ class ElementTest extends \PHPUnit\Framework\TestCase $text2->setTrackChange(new TrackChange(TrackChange::DELETED, 'another author', new \DateTime())); $dom = $this->getAsHTML($phpWord); - $xpath = new \DOMXpath($dom); + $xpath = new \DOMXPath($dom); $this->assertTrue($xpath->query('/html/body/p[1]/ins')->length == 1); $this->assertTrue($xpath->query('/html/body/p[2]/del')->length == 1); @@ -94,7 +94,7 @@ class ElementTest extends \PHPUnit\Framework\TestCase $cell22->addText('second cell'); $dom = $this->getAsHTML($phpWord); - $xpath = new \DOMXpath($dom); + $xpath = new \DOMXPath($dom); $this->assertTrue($xpath->query('/html/body/table/tr[1]/td')->length == 1); $this->assertEquals('2', $xpath->query('/html/body/table/tr/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); @@ -123,7 +123,7 @@ class ElementTest extends \PHPUnit\Framework\TestCase $row3->addCell(500)->addText('third cell being spanned'); $dom = $this->getAsHTML($phpWord); - $xpath = new \DOMXpath($dom); + $xpath = new \DOMXPath($dom); $this->assertTrue($xpath->query('/html/body/table/tr[1]/td')->length == 2); $this->assertEquals('3', $xpath->query('/html/body/table/tr[1]/td[1]')->item(0)->attributes->getNamedItem('rowspan')->textContent); diff --git a/tests/PhpWord/Writer/RTF/StyleTest.php b/tests/PhpWord/Writer/RTF/StyleTest.php index 5f04f1a8..317014c6 100644 --- a/tests/PhpWord/Writer/RTF/StyleTest.php +++ b/tests/PhpWord/Writer/RTF/StyleTest.php @@ -17,7 +17,9 @@ namespace PhpOffice\PhpWord\Writer\RTF; +use PhpOffice\PhpWord\Writer\RTF; use PhpOffice\PhpWord\Writer\RTF\Style\Border; +use PHPUnit\Framework\Assert; /** * Test class for PhpOffice\PhpWord\Writer\RTF\Style subnamespace @@ -29,7 +31,7 @@ class StyleTest extends \PHPUnit\Framework\TestCase */ public function testEmptyStyles() { - $styles = array('Font', 'Paragraph', 'Section'); + $styles = array('Font', 'Paragraph', 'Section', 'Tab', 'Indentation'); foreach ($styles as $style) { $objectClass = 'PhpOffice\\PhpWord\\Writer\\RTF\\Style\\' . $style; $object = new $objectClass(); @@ -55,4 +57,55 @@ class StyleTest extends \PHPUnit\Framework\TestCase $this->assertEquals($expected, $content); } + + public function testIndentation() + { + $indentation = new \PhpOffice\PhpWord\Style\Indentation(); + $indentation->setLeft(1); + $indentation->setRight(2); + $indentation->setFirstLine(3); + + $indentWriter = new \PhpOffice\PhpWord\Writer\RTF\Style\Indentation($indentation); + $indentWriter->setParentWriter(new RTF()); + $result = $indentWriter->write(); + + Assert::assertEquals('\fi3\li1\ri2 ', $result); + } + + public function testRightTab() + { + $tabRight = new \PhpOffice\PhpWord\Style\Tab(); + $tabRight->setType(\PhpOffice\PhpWord\Style\Tab::TAB_STOP_RIGHT); + $tabRight->setPosition(5); + + $tabWriter = new \PhpOffice\PhpWord\Writer\RTF\Style\Tab($tabRight); + $tabWriter->setParentWriter(new RTF()); + $result = $tabWriter->write(); + + Assert::assertEquals('\tqr\tx5', $result); + } + + public function testCenterTab() + { + $tabRight = new \PhpOffice\PhpWord\Style\Tab(); + $tabRight->setType(\PhpOffice\PhpWord\Style\Tab::TAB_STOP_CENTER); + + $tabWriter = new \PhpOffice\PhpWord\Writer\RTF\Style\Tab($tabRight); + $tabWriter->setParentWriter(new RTF()); + $result = $tabWriter->write(); + + Assert::assertEquals('\tqc\tx0', $result); + } + + public function testDecimalTab() + { + $tabRight = new \PhpOffice\PhpWord\Style\Tab(); + $tabRight->setType(\PhpOffice\PhpWord\Style\Tab::TAB_STOP_DECIMAL); + + $tabWriter = new \PhpOffice\PhpWord\Writer\RTF\Style\Tab($tabRight); + $tabWriter->setParentWriter(new RTF()); + $result = $tabWriter->write(); + + Assert::assertEquals('\tqdec\tx0', $result); + } } diff --git a/tests/PhpWord/Writer/Word2007/ElementTest.php b/tests/PhpWord/Writer/Word2007/ElementTest.php index 979a4337..25c62ecc 100644 --- a/tests/PhpWord/Writer/Word2007/ElementTest.php +++ b/tests/PhpWord/Writer/Word2007/ElementTest.php @@ -256,7 +256,7 @@ class ElementTest extends \PHPUnit\Framework\TestCase { $phpWord = new PhpWord(); $section = $phpWord->addSection(); - $style = array('width' => 1000000, 'height' => 1000000); + $style = array('width' => 1000000, 'height' => 1000000, 'showAxisLabels' => true, 'showGridX' => true, 'showGridY' => true); $chartTypes = array('pie', 'doughnut', 'bar', 'line', 'area', 'scatter', 'radar'); $categories = array('A', 'B', 'C', 'D', 'E'); @@ -288,6 +288,7 @@ class ElementTest extends \PHPUnit\Framework\TestCase $section->addField('DATE', array(), array('LunarCalendar')); $section->addField('DATE', array(), array('SakaEraCalendar')); $section->addField('NUMPAGES', array('format' => 'roman', 'numformat' => '0,00'), array('SakaEraCalendar')); + $section->addField('STYLEREF', array('StyleIdentifier' => 'Heading 1')); $doc = TestHelperDOCX::getDocument($phpWord); $element = '/w:document/w:body/w:p/w:r/w:instrText'; diff --git a/tests/PhpWord/Writer/Word2007/Part/SettingsTest.php b/tests/PhpWord/Writer/Word2007/Part/SettingsTest.php index 8201d746..8a21e827 100644 --- a/tests/PhpWord/Writer/Word2007/Part/SettingsTest.php +++ b/tests/PhpWord/Writer/Word2007/Part/SettingsTest.php @@ -17,11 +17,10 @@ namespace PhpOffice\PhpWord\Writer\Word2007\Part; +use PhpOffice\Common\Microsoft\PasswordEncoder; use PhpOffice\PhpWord\ComplexType\ProofState; use PhpOffice\PhpWord\ComplexType\TrackChangesView; use PhpOffice\PhpWord\PhpWord; -use PhpOffice\PhpWord\Settings; -use PhpOffice\PhpWord\Shared\Microsoft\PasswordEncoder; use PhpOffice\PhpWord\SimpleType\Zoom; use PhpOffice\PhpWord\Style\Language; use PhpOffice\PhpWord\TestHelperDOCX; diff --git a/tests/PhpWord/Writer/Word2007/Style/FontTest.php b/tests/PhpWord/Writer/Word2007/Style/FontTest.php index c57f50ab..ccfffbfb 100644 --- a/tests/PhpWord/Writer/Word2007/Style/FontTest.php +++ b/tests/PhpWord/Writer/Word2007/Style/FontTest.php @@ -43,7 +43,7 @@ class FontTest extends \PHPUnit\Framework\TestCase $phpWord = new \PhpOffice\PhpWord\PhpWord(); $section = $phpWord->addSection(); $textrun = $section->addTextRun(); - $textrun->addText('سلام این یک پاراگراف راست به چپ است', array('rtl' => true)); + $textrun->addText('سلام این یک پاراگراف راست به چپ است', array('rtl' => true, 'lang' => 'ar-DZ')); $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); $file = 'word/document.xml'; diff --git a/tests/PhpWord/Writer/Word2007/Style/TableTest.php b/tests/PhpWord/Writer/Word2007/Style/TableTest.php index 364a34d6..ec3b2483 100644 --- a/tests/PhpWord/Writer/Word2007/Style/TableTest.php +++ b/tests/PhpWord/Writer/Word2007/Style/TableTest.php @@ -17,6 +17,8 @@ namespace PhpOffice\PhpWord\Writer\Word2007\Style; +use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType; +use PhpOffice\PhpWord\SimpleType\TblWidth; use PhpOffice\PhpWord\Style\Table; use PhpOffice\PhpWord\Style\TablePosition; use PhpOffice\PhpWord\TestHelperDOCX; @@ -75,7 +77,7 @@ class TableTest extends \PHPUnit\Framework\TestCase $path = '/w:document/w:body/w:tbl/w:tblPr/w:tblCellSpacing'; $this->assertTrue($doc->elementExists($path)); $this->assertEquals(10.3, $doc->getElementAttribute($path, 'w:w')); - $this->assertEquals(\PhpOffice\PhpWord\SimpleType\TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type')); + $this->assertEquals(TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type')); } /** @@ -118,4 +120,25 @@ class TableTest extends \PHPUnit\Framework\TestCase $this->assertEquals(TablePosition::YALIGN_TOP, $doc->getElementAttribute($path, 'w:tblpYSpec')); $this->assertEquals(60, $doc->getElementAttribute($path, 'w:tblpY')); } + + public function testIndent() + { + $value = 100; + $type = TblWidth::TWIP; + + $tableStyle = new Table(); + $tableStyle->setIndent(new TblWidthComplexType($value, $type)); + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $table = $section->addTable($tableStyle); + $table->addRow(); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:tbl/w:tblPr/w:tblInd'; + $this->assertTrue($doc->elementExists($path)); + $this->assertSame($value, (int) $doc->getElementAttribute($path, 'w:w')); + $this->assertSame($type, $doc->getElementAttribute($path, 'w:type')); + } } diff --git a/tests/PhpWord/_includes/XmlDocument.php b/tests/PhpWord/_includes/XmlDocument.php index 8c937bf5..7062ebbf 100644 --- a/tests/PhpWord/_includes/XmlDocument.php +++ b/tests/PhpWord/_includes/XmlDocument.php @@ -37,9 +37,9 @@ class XmlDocument private $dom; /** - * DOMXpath object + * DOMXPath object * - * @var \DOMXpath + * @var \DOMXPath */ private $xpath; @@ -76,8 +76,10 @@ class XmlDocument $this->file = $file; $file = $this->path . '/' . $file; + libxml_disable_entity_loader(false); $this->dom = new \DOMDocument(); $this->dom->load($file); + libxml_disable_entity_loader(true); return $this->dom; } @@ -96,7 +98,7 @@ class XmlDocument } if (null === $this->xpath) { - $this->xpath = new \DOMXpath($this->dom); + $this->xpath = new \DOMXPath($this->dom); $this->xpath->registerNamespace('w14', 'http://schemas.microsoft.com/office/word/2010/wordml'); }