Merge branch 'develop' into develop

This commit is contained in:
troosan 2018-07-14 17:12:45 +02:00 committed by GitHub
commit af4f834149
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 454 additions and 414 deletions

View File

@ -24,6 +24,7 @@ v0.15.0 (?? ??? 2018)
- 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
@ -36,6 +37,9 @@ v0.15.0 (?? ??? 2018)
- Fix colspan and rowspan for tables in HTML Writer @mattbolt #1292
- 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

View File

@ -61,10 +61,12 @@
"php": "^5.3.3 || ^7.0",
"ext-xml": "*",
"zendframework/zend-escaper": "^2.2",
"phpoffice/common": "^0.2"
"phpoffice/common": "^0.2.9"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36 || ^5.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.*",

View File

@ -1,7 +1,7 @@
includes:
- vendor/phpstan/phpstan/conf/config.level1.neon
parameters:
memory-limit: 200000
memory-limit: 20000000
autoload_directories:
- tests
autoload_files:

View File

@ -6,8 +6,7 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false">
stopOnFailure="false">
<testsuites>
<testsuite name="PhpWord Test Suite">
<directory>./tests/PhpWord</directory>
@ -22,7 +21,7 @@
</whitelist>
</filter>
<logging>
<log type="coverage-html" target="./build/coverage" charset="UTF-8" highlight="true" />
<log type="coverage-html" target="./build/coverage" />
<log type="coverage-clover" target="./build/logs/clover.xml" />
</logging>
</phpunit>

View File

@ -1,6 +1,5 @@
<?php
use PhpOffice\PhpWord\Style\Font;
use PhpOffice\PhpWord\Style\Paragraph;
include_once 'Sample_Header.php';

View File

@ -35,7 +35,7 @@ namespace PhpOffice\PhpWord\Element;
* @method TOC addTOC(mixed $fontStyle = null, mixed $tocStyle = null, int $minDepth = 1, int $maxDepth = 9)
* @method PageBreak addPageBreak()
* @method Table addTable(mixed $style = null)
* @method Image addImage(string $source, mixed $style = null, bool $isWatermark = false)
* @method Image addImage(string $source, mixed $style = null, bool $isWatermark = false, $name = null)
* @method OLEObject addOLEObject(string $source, mixed $style = null)
* @method TextBox addTextBox(mixed $style = null)
* @method Field addField(string $type = null, array $properties = array(), array $options = array(), mixed $text = null)

View File

@ -65,6 +65,13 @@ class Image extends AbstractElement
*/
private $watermark;
/**
* Name of image
*
* @var string
*/
private $name;
/**
* Image type
*
@ -127,15 +134,17 @@ class Image extends AbstractElement
* @param string $source
* @param mixed $style
* @param bool $watermark
* @param string $name
*
* @throws \PhpOffice\PhpWord\Exception\InvalidImageException
* @throws \PhpOffice\PhpWord\Exception\UnsupportedImageTypeException
*/
public function __construct($source, $style = null, $watermark = false)
public function __construct($source, $style = null, $watermark = false, $name = null)
{
$this->source = $source;
$this->setIsWatermark($watermark);
$this->style = $this->setNewStyle(new ImageStyle(), $style, true);
$this->setIsWatermark($watermark);
$this->setName($name);
$this->checkImage();
}
@ -170,6 +179,26 @@ class Image extends AbstractElement
return $this->sourceType;
}
/**
* Sets the image name
*
* @param string $value
*/
public function setName($value)
{
$this->name = $value;
}
/**
* Get image name
*
* @return null|string
*/
public function getName()
{
return $this->name;
}
/**
* Get image media ID
*

View File

@ -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();

View File

@ -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()
{

View File

@ -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;
}
}

View File

@ -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;
}
/**

View File

@ -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)

View File

@ -261,6 +261,20 @@ abstract class AbstractPart
}
$parent->addImage($imageSource);
}
} elseif ($node->nodeName == 'w:drawing') {
// Office 2011 Image
$xmlReader->registerNamespace('wp', 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing');
$xmlReader->registerNamespace('r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
$xmlReader->registerNamespace('pic', 'http://schemas.openxmlformats.org/drawingml/2006/picture');
$xmlReader->registerNamespace('a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
$name = $xmlReader->getAttribute('name', $node, 'wp:inline/a:graphic/a:graphicData/pic:pic/pic:nvPicPr/pic:cNvPr');
$embedId = $xmlReader->getAttribute('r:embed', $node, 'wp:inline/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip');
$target = $this->getMediaTarget($docPart, $embedId);
if (!is_null($target)) {
$imageSource = "zip://{$this->docFile}#{$target}";
$parent->addImage($imageSource, null, false, $name);
}
} elseif ($node->nodeName == 'w:object') {
// Object
$rId = $xmlReader->getAttribute('r:id', $node, 'o:OLEObject');

View File

@ -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,7 +102,7 @@ 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)
{
@ -119,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)
{
@ -141,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)
{
@ -158,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)
{

View File

@ -71,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);

View File

@ -1,235 +0,0 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
* @copyright 2010-2018 PHPWord contributors
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Shared\Microsoft;
/**
* Password encoder for microsoft office applications
*/
class PasswordEncoder
{
const ALGORITHM_MD2 = 'MD2';
const ALGORITHM_MD4 = 'MD4';
const ALGORITHM_MD5 = 'MD5';
const ALGORITHM_SHA_1 = 'SHA-1';
const ALGORITHM_SHA_256 = 'SHA-256';
const ALGORITHM_SHA_384 = 'SHA-384';
const ALGORITHM_SHA_512 = 'SHA-512';
const ALGORITHM_RIPEMD = 'RIPEMD';
const ALGORITHM_RIPEMD_160 = 'RIPEMD-160';
const ALGORITHM_MAC = 'MAC';
const ALGORITHM_HMAC = 'HMAC';
/**
* Mapping between algorithm name and algorithm ID
*
* @var array
* @see https://msdn.microsoft.com/en-us/library/documentformat.openxml.wordprocessing.writeprotection.cryptographicalgorithmsid(v=office.14).aspx
*/
private static $algorithmMapping = array(
self::ALGORITHM_MD2 => 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 keys 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;
}
}

View File

@ -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;

View File

@ -163,6 +163,13 @@ class Table extends Border
/** @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
*
@ -748,4 +755,24 @@ class Table extends Border
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;
}
}

View File

@ -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.');

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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
}
}
}

View File

@ -0,0 +1,45 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
* @copyright 2010-2018 PHPWord contributors
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Writer\RTF\Style;
/**
* RTF indentation style writer
*
* @since 0.11.0
*/
class Indentation extends AbstractStyle
{
/**
* Write style
*
* @return string
*/
public function write()
{
$style = $this->getStyle();
if (!$style instanceof \PhpOffice\PhpWord\Style\Indentation) {
return '';
}
$content = '\fi' . $style->getFirstLine();
$content .= '\li' . $style->getLeft();
$content .= '\ri' . $style->getRight();
return $content . ' ';
}
}

View File

@ -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;
}

View File

@ -0,0 +1,49 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
* @copyright 2010-2018 PHPWord contributors
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Writer\RTF\Style;
/**
* Line numbering style writer
*
* @since 0.10.0
*/
class Tab extends AbstractStyle
{
/**
* Write style.
*/
public function write()
{
$style = $this->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;
}
}

View File

@ -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) {

View File

@ -47,6 +47,7 @@ class Title extends AbstractElement
$xmlWriter->endElement();
}
$bookmarkRId = null;
if ($element->getDepth() !== 0) {
$rId = $element->getRelationId();
$bookmarkRId = $element->getPhpWord()->addBookmark();

View File

@ -330,11 +330,11 @@ class Chart extends AbstractPart
$valueAxisTitle = $style->getValueAxisTitle();
if ($axisType == 'c:catAx') {
if (isset($categoryAxisTitle)) {
if (!is_null($categoryAxisTitle)) {
$this->writeAxisTitle($xmlWriter, $categoryAxisTitle);
}
} elseif ($axisType == 'c:valAx') {
if (isset($valueAxisTitle)) {
if (!is_null($valueAxisTitle)) {
$this->writeAxisTitle($xmlWriter, $valueAxisTitle);
}
}

View File

@ -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;
/**

View File

@ -236,4 +236,40 @@ class ElementTest extends AbstractTestReader
$this->assertEquals('Title', $formattedTitle->getStyle());
$this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $formattedTitle->getText());
}
/**
* Test reading Drawing
*/
public function testReadDrawing()
{
$documentXml = '<w:p>
<w:r>
<w:drawing xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing">
<wp:inline distT="0" distB="0" distL="0" distR="0">
<wp:extent cx="5727700" cy="6621145"/>
<wp:docPr id="1" name="Picture 1"/>
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:nvPicPr>
<pic:cNvPr id="1" name="file_name.jpg"/>
<pic:cNvPicPr/>
</pic:nvPicPr>
<pic:blipFill>
<a:blip r:embed="rId4" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
</a:blip>
</pic:blipFill>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</w:r>
</w:p>';
$phpWord = $this->getDocumentFromString(array('document' => $documentXml));
$elements = $phpWord->getSection(0)->getElements();
$this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]);
}
}

View File

@ -64,4 +64,18 @@ class Word2007Test extends \PHPUnit\Framework\TestCase
$doc = TestHelperDOCX::getDocument($phpWord);
$this->assertFalse($doc->elementExists('/w:document/w:body/w:p/w:r[w:t/node()="italics"]/w:rPr/w:b'));
}
/**
* Load a Word 2011 file
*/
public function testLoadWord2011()
{
$filename = __DIR__ . '/../_files/documents/reader-2011.docx';
$phpWord = IOFactory::load($filename);
$this->assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord);
$doc = TestHelperDOCX::getDocument($phpWord);
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p[3]/w:r/w:pict/v:shape/v:imagedata'));
}
}

View File

@ -1,91 +0,0 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
* @copyright 2010-2018 PHPWord contributors
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Shared;
use PhpOffice\PhpWord\Shared\Microsoft\PasswordEncoder;
use PHPUnit\Framework\TestCase;
/**
* Test class for PhpOffice\PhpWord\Shared\Html
* @coversDefaultClass \PhpOffice\PhpWord\Shared\Html
*/
class PasswordEncoderTest extends \PHPUnit\Framework\TestCase
{
/**
* Test that a password can be hashed without specifying any additional parameters
*/
public function testEncodePassword()
{
//given
$password = 'test';
//when
$hashPassword = PasswordEncoder::hashPassword($password);
//then
TestCase::assertEquals('M795/MAlmGU8RIsY9Q9uDLHC7bk=', $hashPassword);
}
/**
* Test that a password can be hashed with a custom salt
*/
public function testEncodePasswordWithSalt()
{
//given
$password = 'test';
$salt = base64_decode('uq81pJRRGFIY5U+E9gt8tA==');
//when
$hashPassword = PasswordEncoder::hashPassword($password, PasswordEncoder::ALGORITHM_SHA_1, $salt);
//then
TestCase::assertEquals('QiDOcpia1YzSVJPiKPwWebl9p/0=', $hashPassword);
}
/**
* Test that the encoder falls back on SHA-1 if a non supported algorithm is given
*/
public function testDafaultsToSha1IfUnsupportedAlgorithm()
{
//given
$password = 'test';
$salt = base64_decode('uq81pJRRGFIY5U+E9gt8tA==');
//when
$hashPassword = PasswordEncoder::hashPassword($password, PasswordEncoder::ALGORITHM_MAC, $salt);
//then
TestCase::assertEquals('QiDOcpia1YzSVJPiKPwWebl9p/0=', $hashPassword);
}
/**
* Test that the encoder falls back on SHA-1 if a non supported algorithm is given
*/
public function testEncodePasswordWithNullAsciiCodeInPassword()
{
//given
$password = 'test' . chr(0);
$salt = base64_decode('uq81pJRRGFIY5U+E9gt8tA==');
//when
$hashPassword = PasswordEncoder::hashPassword($password, PasswordEncoder::ALGORITHM_MAC, $salt, 1);
//then
TestCase::assertEquals('rDV9sgdDsztoCQlvRCb1lF2wxNg=', $hashPassword);
}
}

View File

@ -17,7 +17,6 @@
namespace PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\TestHelperDOCX;
/**

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;

Binary file not shown.

View File

@ -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');
}