This commit is contained in:
wangjinlei
2024-12-31 10:28:40 +08:00
parent 145b0ab8a5
commit a54a837670
1179 changed files with 2686 additions and 230376 deletions

View File

@@ -21,22 +21,23 @@ namespace PhpOffice\PhpWord\Collection;
* Collection abstract class.
*
* @since 0.10.0
* @template T
*/
abstract class AbstractCollection
{
/**
* Items.
*
* @var \PhpOffice\PhpWord\Element\AbstractContainer[]
* @var T[]
*/
private $items = [];
/**
* Get items.
*
* @return \PhpOffice\PhpWord\Element\AbstractContainer[]
* @return T[]
*/
public function getItems()
public function getItems(): array
{
return $this->items;
}
@@ -44,11 +45,9 @@ abstract class AbstractCollection
/**
* Get item by index.
*
* @param int $index
*
* @return ?\PhpOffice\PhpWord\Element\AbstractContainer
* @return ?T
*/
public function getItem($index)
public function getItem(int $index)
{
if (array_key_exists($index, $this->items)) {
return $this->items[$index];
@@ -60,10 +59,9 @@ abstract class AbstractCollection
/**
* Set item.
*
* @param int $index
* @param ?\PhpOffice\PhpWord\Element\AbstractContainer $item
* @param ?T $item
*/
public function setItem($index, $item): void
public function setItem(int $index, $item): void
{
if (array_key_exists($index, $this->items)) {
$this->items[$index] = $item;
@@ -73,11 +71,9 @@ abstract class AbstractCollection
/**
* Add new item.
*
* @param \PhpOffice\PhpWord\Element\AbstractContainer $item
*
* @return int
* @param T $item
*/
public function addItem($item)
public function addItem($item): int
{
$index = $this->countItems();
$this->items[$index] = $item;
@@ -87,10 +83,8 @@ abstract class AbstractCollection
/**
* Get item count.
*
* @return int
*/
public function countItems()
public function countItems(): int
{
return count($this->items);
}

View File

@@ -17,10 +17,13 @@
namespace PhpOffice\PhpWord\Collection;
use PhpOffice\PhpWord\Element\Bookmark;
/**
* Bookmarks collection.
*
* @since 0.12.0
* @extends AbstractCollection<Bookmark>
*/
class Bookmarks extends AbstractCollection
{

View File

@@ -17,10 +17,13 @@
namespace PhpOffice\PhpWord\Collection;
use PhpOffice\PhpWord\Element\Chart;
/**
* Charts collection.
*
* @since 0.12.0
* @extends AbstractCollection<Chart>
*/
class Charts extends AbstractCollection
{

View File

@@ -17,10 +17,13 @@
namespace PhpOffice\PhpWord\Collection;
use PhpOffice\PhpWord\Element\Comment;
/**
* Comments collection.
*
* @since 0.12.0
* @extends AbstractCollection<Comment>
*/
class Comments extends AbstractCollection
{

View File

@@ -17,10 +17,13 @@
namespace PhpOffice\PhpWord\Collection;
use PhpOffice\PhpWord\Element\Endnote;
/**
* Endnotes collection.
*
* @since 0.10.0
* @extends AbstractCollection<Endnote>
*/
class Endnotes extends AbstractCollection
{

View File

@@ -17,10 +17,13 @@
namespace PhpOffice\PhpWord\Collection;
use PhpOffice\PhpWord\Element\Footnote;
/**
* Footnotes collection.
*
* @since 0.10.0
* @extends AbstractCollection<Footnote>
*/
class Footnotes extends AbstractCollection
{

View File

@@ -17,10 +17,13 @@
namespace PhpOffice\PhpWord\Collection;
use PhpOffice\PhpWord\Element\Title;
/**
* Titles collection.
*
* @since 0.10.0
* @extends AbstractCollection<Title>
*/
class Titles extends AbstractCollection
{

View File

@@ -19,8 +19,10 @@ namespace PhpOffice\PhpWord\Element;
use DateTime;
use InvalidArgumentException;
use PhpOffice\PhpWord\Collection\Comments;
use PhpOffice\PhpWord\Media;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Style;
/**
* Element abstract class.
@@ -32,7 +34,7 @@ abstract class AbstractElement
/**
* PhpWord object.
*
* @var ?\PhpOffice\PhpWord\PhpWord
* @var ?PhpWord
*/
protected $phpWord;
@@ -131,25 +133,25 @@ abstract class AbstractElement
protected $collectionRelation = false;
/**
* The start position for the linked comment.
* The start position for the linked comments.
*
* @var Comment
* @var Comments
*/
protected $commentRangeStart;
protected $commentsRangeStart;
/**
* The end position for the linked comment.
* The end position for the linked comments.
*
* @var Comment
* @var Comments
*/
protected $commentRangeEnd;
protected $commentsRangeEnd;
/**
* Get PhpWord.
*
* @return ?\PhpOffice\PhpWord\PhpWord
* @return ?PhpWord
*/
public function getPhpWord()
public function getPhpWord(): ?PhpWord
{
return $this->phpWord;
}
@@ -287,14 +289,28 @@ abstract class AbstractElement
return $this->nestedLevel;
}
/**
* Get comments start.
*
* @return Comments
*/
public function getCommentsRangeStart(): ?Comments
{
return $this->commentsRangeStart;
}
/**
* Get comment start.
*
* @return Comment
*/
public function getCommentRangeStart()
public function getCommentRangeStart(): ?Comment
{
return $this->commentRangeStart;
if ($this->commentsRangeStart != null) {
return $this->commentsRangeStart->getItem($this->commentsRangeStart->countItems());
}
return null;
}
/**
@@ -305,8 +321,30 @@ abstract class AbstractElement
if ($this instanceof Comment) {
throw new InvalidArgumentException('Cannot set a Comment on a Comment');
}
$this->commentRangeStart = $value;
$this->commentRangeStart->setStartElement($this);
if ($this->commentsRangeStart == null) {
$this->commentsRangeStart = new Comments();
}
// Set ID early to avoid duplicates.
if ($value->getElementId() == null) {
$value->setElementId();
}
foreach ($this->commentsRangeStart->getItems() as $comment) {
if ($value->getElementId() == $comment->getElementId()) {
return;
}
}
$idxItem = $this->commentsRangeStart->addItem($value);
$this->commentsRangeStart->getItem($idxItem)->setStartElement($this);
}
/**
* Get comments end.
*
* @return Comments
*/
public function getCommentsRangeEnd(): ?Comments
{
return $this->commentsRangeEnd;
}
/**
@@ -314,9 +352,13 @@ abstract class AbstractElement
*
* @return Comment
*/
public function getCommentRangeEnd()
public function getCommentRangeEnd(): ?Comment
{
return $this->commentRangeEnd;
if ($this->commentsRangeEnd != null) {
return $this->commentsRangeEnd->getItem($this->commentsRangeEnd->countItems());
}
return null;
}
/**
@@ -327,8 +369,20 @@ abstract class AbstractElement
if ($this instanceof Comment) {
throw new InvalidArgumentException('Cannot set a Comment on a Comment');
}
$this->commentRangeEnd = $value;
$this->commentRangeEnd->setEndElement($this);
if ($this->commentsRangeEnd == null) {
$this->commentsRangeEnd = new Comments();
}
// Set ID early to avoid duplicates.
if ($value->getElementId() == null) {
$value->setElementId();
}
foreach ($this->commentsRangeEnd->getItems() as $comment) {
if ($value->getElementId() == $comment->getElementId()) {
return;
}
}
$idxItem = $this->commentsRangeEnd->addItem($value);
$this->commentsRangeEnd->getItem($idxItem)->setEndElement($this);
}
/**
@@ -428,7 +482,7 @@ abstract class AbstractElement
* Set new style value.
*
* @param mixed $styleObject Style object
* @param null|array|\PhpOffice\PhpWord\Style|string $styleValue Style value
* @param null|array|string|Style $styleValue Style value
* @param bool $returnObject Always return object
*
* @return mixed

View File

@@ -83,9 +83,7 @@ class Comment extends TrackChange
public function setStartElement(AbstractElement $value): void
{
$this->startElement = $value;
if ($value->getCommentRangeStart() == null) {
$value->setCommentRangeStart($this);
}
$value->setCommentRangeStart($this);
}
/**
@@ -104,9 +102,7 @@ class Comment extends TrackChange
public function setEndElement(AbstractElement $value): void
{
$this->endElement = $value;
if ($value->getCommentRangeEnd() == null) {
$value->setCommentRangeEnd($this);
}
$value->setCommentRangeEnd($this);
}
/**

View File

@@ -91,6 +91,10 @@ class Field extends AbstractElement
],
'options' => ['Path', 'PreserveFormat'],
],
'REF' => [
'properties' => ['name' => ''],
'options' => ['f', 'h', 'n', 'p', 'r', 't', 'w'],
],
];
/**

View File

@@ -386,8 +386,9 @@ class Image extends AbstractElement
$imageBinary = $this->source;
} else {
$fileHandle = fopen($actualSource, 'rb', false);
if ($fileHandle !== false) {
$imageBinary = fread($fileHandle, filesize($actualSource));
$fileSize = filesize($actualSource);
if ($fileHandle !== false && $fileSize > 0) {
$imageBinary = fread($fileHandle, $fileSize);
fclose($fileHandle);
}
}

View File

@@ -32,14 +32,14 @@ class TextRun extends AbstractContainer
/**
* Paragraph style.
*
* @var \PhpOffice\PhpWord\Style\Paragraph|string
* @var Paragraph|string
*/
protected $paragraphStyle;
/**
* Create new instance.
*
* @param array|\PhpOffice\PhpWord\Style\Paragraph|string $paragraphStyle
* @param array|Paragraph|string $paragraphStyle
*/
public function __construct($paragraphStyle = null)
{
@@ -49,7 +49,7 @@ class TextRun extends AbstractContainer
/**
* Get Paragraph style.
*
* @return \PhpOffice\PhpWord\Style\Paragraph|string
* @return Paragraph|string
*/
public function getParagraphStyle()
{
@@ -59,9 +59,9 @@ class TextRun extends AbstractContainer
/**
* Set Paragraph style.
*
* @param array|\PhpOffice\PhpWord\Style\Paragraph|string $style
* @param array|Paragraph|string $style
*
* @return \PhpOffice\PhpWord\Style\Paragraph|string
* @return Paragraph|string
*/
public function setParagraphStyle($style = null)
{

View File

@@ -17,6 +17,8 @@
namespace PhpOffice\PhpWord;
use PhpOffice\PhpWord\Element\Text;
use PhpOffice\PhpWord\Element\TextRun;
use PhpOffice\PhpWord\Exception\Exception;
use PhpOffice\PhpWord\Reader\ReaderInterface;
use PhpOffice\PhpWord\Writer\WriterInterface;
@@ -89,6 +91,43 @@ abstract class IOFactory
return $reader->load($filename);
}
/**
* Loads PhpWord ${variable} from file.
*
* @param string $filename The name of the file
*
* @return array The extracted variables
*/
public static function extractVariables(string $filename, string $readerName = 'Word2007'): array
{
/** @var \PhpOffice\PhpWord\Reader\ReaderInterface $reader */
$reader = self::createReader($readerName);
$document = $reader->load($filename);
$extractedVariables = [];
foreach ($document->getSections() as $section) {
$concatenatedText = '';
foreach ($section->getElements() as $element) {
if ($element instanceof TextRun) {
foreach ($element->getElements() as $textElement) {
if ($textElement instanceof Text) {
$text = $textElement->getText();
$concatenatedText .= $text;
}
}
}
}
preg_match_all('/\$\{([^}]+)\}/', $concatenatedText, $matches);
if (!empty($matches[1])) {
foreach ($matches[1] as $match) {
$trimmedMatch = trim($match);
$extractedVariables[] = $trimmedMatch;
}
}
}
return $extractedVariables;
}
/**
* Check if it's a concrete class (not abstract nor interface).
*

View File

@@ -134,7 +134,6 @@ class PhpWord
if (in_array($function, $addCollection)) {
$key = ucfirst(str_replace('add', '', $function) . 's');
/** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collectionObject */
$collectionObject = $this->collections[$key];
return $collectionObject->addItem($args[0] ?? null);

View File

@@ -1279,10 +1279,12 @@ class MsDoc extends AbstractReader implements ReaderInterface
break;
}
$strLen = $arrayRGFC[$key + 1] - $arrayRGFC[$key] - 1;
for ($inc = 0; $inc < $strLen; ++$inc) {
$byte = self::getInt1d($this->dataWorkDocument, $arrayRGFC[$key] + $inc);
for ($inc = 0; $inc < ($strLen * 2); ++$inc) {
$byte = self::getInt2d($this->dataWorkDocument, $arrayRGFC[$key] + ($inc * 2));
if ($byte > 0) {
$string .= chr($byte);
$string .= mb_chr($byte, 'UTF-8');
} else {
break;
}
}
}
@@ -1871,7 +1873,7 @@ class MsDoc extends AbstractReader implements ReaderInterface
break;
// sprmCHps
case 0x43:
$oStylePrl->styleFont['size'] = dechex($operand / 2);
$oStylePrl->styleFont['size'] = $operand / 2;
break;
// sprmCIss
@@ -2331,7 +2333,7 @@ class MsDoc extends AbstractReader implements ReaderInterface
foreach ($this->arrayParagraphs as $itmParagraph) {
$textPara = $itmParagraph;
foreach ($this->arrayCharacters as $oCharacters) {
$subText = substr($textPara, $oCharacters->pos_start, $oCharacters->pos_len);
$subText = mb_substr($textPara, $oCharacters->pos_start, $oCharacters->pos_len);
$subText = str_replace(chr(13), PHP_EOL, $subText);
$arrayText = explode(PHP_EOL, $subText);
if (end($arrayText) == '') {

View File

@@ -24,6 +24,7 @@ use PhpOffice\Math\Reader\OfficeMathML;
use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType;
use PhpOffice\PhpWord\Element\AbstractContainer;
use PhpOffice\PhpWord\Element\AbstractElement;
use PhpOffice\PhpWord\Element\FormField;
use PhpOffice\PhpWord\Element\TextRun;
use PhpOffice\PhpWord\Element\TrackChange;
use PhpOffice\PhpWord\PhpWord;
@@ -192,25 +193,64 @@ abstract class AbstractPart
// Paragraph style
$paragraphStyle = $xmlReader->elementExists('w:pPr', $domNode) ? $this->readParagraphStyle($xmlReader, $domNode) : null;
// PreserveText
if ($xmlReader->elementExists('w:r/w:instrText', $domNode)) {
if ($xmlReader->elementExists('w:r/w:fldChar/w:ffData', $domNode)) {
// FormField
$partOfFormField = false;
$formNodes = [];
$formType = null;
$textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag', $domNode);
if ($textRunContainers > 0) {
$nodes = $xmlReader->getElements('*', $domNode);
$paragraph = $parent->addTextRun($paragraphStyle);
foreach ($nodes as $node) {
if ($xmlReader->elementExists('w:fldChar/w:ffData', $node)) {
$partOfFormField = true;
$formNodes[] = $node;
if ($xmlReader->elementExists('w:fldChar/w:ffData/w:ddList', $node)) {
$formType = 'dropdown';
} elseif ($xmlReader->elementExists('w:fldChar/w:ffData/w:textInput', $node)) {
$formType = 'textinput';
} elseif ($xmlReader->elementExists('w:fldChar/w:ffData/w:checkBox', $node)) {
$formType = 'checkbox';
}
} elseif ($partOfFormField &&
$xmlReader->elementExists('w:fldChar', $node) &&
'end' == $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar')
) {
$formNodes[] = $node;
$partOfFormField = false;
// Process the form fields
$this->readFormField($xmlReader, $formNodes, $paragraph, $paragraphStyle, $formType);
} elseif ($partOfFormField) {
$formNodes[] = $node;
} else {
// normal runs
$this->readRun($xmlReader, $node, $paragraph, $docPart, $paragraphStyle);
}
}
}
} elseif ($xmlReader->elementExists('w:r/w:instrText', $domNode)) {
// PreserveText
$ignoreText = false;
$textContent = '';
$fontStyle = $this->readFontStyle($xmlReader, $domNode);
$nodes = $xmlReader->getElements('w:r', $domNode);
foreach ($nodes as $node) {
$instrText = $xmlReader->getValue('w:instrText', $node);
if ($xmlReader->elementExists('w:fldChar', $node)) {
$fldCharType = $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar');
if ('begin' == $fldCharType) {
$ignoreText = true;
} elseif ('end' == $fldCharType) {
$ignoreText = false;
}
if ($xmlReader->elementExists('w:lastRenderedPageBreak', $node)) {
$parent->addPageBreak();
}
$instrText = $xmlReader->getValue('w:instrText', $node);
if (null !== $instrText) {
$textContent .= '{' . $instrText . '}';
} else {
if ($xmlReader->elementExists('w:fldChar', $node)) {
$fldCharType = $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar');
if ('begin' == $fldCharType) {
$ignoreText = true;
} elseif ('end' == $fldCharType) {
$ignoreText = false;
}
}
if (false === $ignoreText) {
$textContent .= $xmlReader->getValue('w:t', $node);
}
@@ -272,7 +312,7 @@ abstract class AbstractPart
// Text and TextRun
$textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag|w:commentReference|w:commentRangeStart|w:commentRangeEnd', $domNode);
if (0 === $textRunContainers) {
$parent->addTextBreak(null, $paragraphStyle);
$parent->addTextBreak(1, $paragraphStyle);
} else {
$nodes = $xmlReader->getElements('*', $domNode);
$paragraph = $parent->addTextRun($paragraphStyle);
@@ -282,6 +322,109 @@ abstract class AbstractPart
}
}
/**
* @param DOMElement[] $domNodes
* @param AbstractContainer $parent
* @param mixed $paragraphStyle
* @param string $formType
*/
private function readFormField(XMLReader $xmlReader, array $domNodes, $parent, $paragraphStyle, $formType): void
{
if (!in_array($formType, ['textinput', 'checkbox', 'dropdown'])) {
return;
}
$formField = $parent->addFormField($formType, null, $paragraphStyle);
$ffData = $xmlReader->getElement('w:fldChar/w:ffData', $domNodes[0]);
foreach ($xmlReader->getElements('*', $ffData) as $node) {
/** @var DOMElement $node */
switch ($node->localName) {
case 'name':
$formField->setName($node->getAttribute('w:val'));
break;
case 'ddList':
$listEntries = [];
foreach ($xmlReader->getElements('*', $node) as $ddListNode) {
switch ($ddListNode->localName) {
case 'result':
$formField->setValue($xmlReader->getAttribute('w:val', $ddListNode));
break;
case 'default':
$formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode));
break;
case 'listEntry':
$listEntries[] = $xmlReader->getAttribute('w:val', $ddListNode);
break;
}
}
$formField->setEntries($listEntries);
if (null !== $formField->getValue()) {
$formField->setText($listEntries[$formField->getValue()]);
}
break;
case 'textInput':
foreach ($xmlReader->getElements('*', $node) as $ddListNode) {
switch ($ddListNode->localName) {
case 'default':
$formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode));
break;
case 'format':
case 'maxLength':
break;
}
}
break;
case 'checkBox':
foreach ($xmlReader->getElements('*', $node) as $ddListNode) {
switch ($ddListNode->localName) {
case 'default':
$formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode));
break;
case 'checked':
$formField->setValue($xmlReader->getAttribute('w:val', $ddListNode));
break;
case 'size':
case 'sizeAuto':
break;
}
}
break;
}
}
if ('textinput' == $formType) {
$ignoreText = true;
$textContent = '';
foreach ($domNodes as $node) {
if ($xmlReader->elementExists('w:fldChar', $node)) {
$fldCharType = $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar');
if ('separate' == $fldCharType) {
$ignoreText = false;
} elseif ('end' == $fldCharType) {
$ignoreText = true;
}
}
if (false === $ignoreText) {
$textContent .= $xmlReader->getValue('w:t', $node);
}
}
$formField->setValue(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8'));
$formField->setText(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8'));
}
}
/**
* Returns the depth of the Heading, returns 0 for a Title.
*
@@ -529,6 +672,18 @@ abstract class AbstractPart
'contextualSpacing' => [self::READ_TRUE, 'w:contextualSpacing'],
'bidi' => [self::READ_TRUE, 'w:bidi'],
'suppressAutoHyphens' => [self::READ_TRUE, 'w:suppressAutoHyphens'],
'borderTopStyle' => [self::READ_VALUE, 'w:pBdr/w:top'],
'borderTopColor' => [self::READ_VALUE, 'w:pBdr/w:top', 'w:color'],
'borderTopSize' => [self::READ_VALUE, 'w:pBdr/w:top', 'w:sz'],
'borderRightStyle' => [self::READ_VALUE, 'w:pBdr/w:right'],
'borderRightColor' => [self::READ_VALUE, 'w:pBdr/w:right', 'w:color'],
'borderRightSize' => [self::READ_VALUE, 'w:pBdr/w:right', 'w:sz'],
'borderBottomStyle' => [self::READ_VALUE, 'w:pBdr/w:bottom'],
'borderBottomColor' => [self::READ_VALUE, 'w:pBdr/w:bottom', 'w:color'],
'borderBottomSize' => [self::READ_VALUE, 'w:pBdr/w:bottom', 'w:sz'],
'borderLeftStyle' => [self::READ_VALUE, 'w:pBdr/w:left'],
'borderLeftColor' => [self::READ_VALUE, 'w:pBdr/w:left', 'w:color'],
'borderLeftSize' => [self::READ_VALUE, 'w:pBdr/w:left', 'w:sz'],
];
return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs);

View File

@@ -138,8 +138,6 @@ class Document extends AbstractPart
/**
* Read w:p node.
*
* @todo <w:lastRenderedPageBreak>
*/
private function readWPNode(XMLReader $xmlReader, DOMElement $node, Section &$section): void
{

View File

@@ -37,6 +37,8 @@ use PhpOffice\PhpWord\Style\Paragraph;
*/
class Html
{
private const RGB_REGEXP = '/^\s*rgb\s*[(]\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*[)]\s*$/';
protected static $listIndex = 0;
protected static $xpath;
@@ -100,7 +102,7 @@ class Html
* parse Inline style of a node.
*
* @param DOMNode $node Node to check on attributes and to compile a style array
* @param array $styles is supplied, the inline style attributes are added to the already existing style
* @param array<string, mixed> $styles is supplied, the inline style attributes are added to the already existing style
*
* @return array
*/
@@ -109,7 +111,9 @@ class Html
if (XML_ELEMENT_NODE == $node->nodeType) {
$attributes = $node->attributes; // get all the attributes(eg: id, class)
$bidi = ($attributes['dir'] ?? '') === 'rtl';
$attributeDir = $attributes->getNamedItem('dir');
$attributeDirValue = $attributeDir ? $attributeDir->nodeValue : '';
$bidi = $attributeDirValue === 'rtl';
foreach ($attributes as $attribute) {
$val = $attribute->value;
switch (strtolower($attribute->name)) {
@@ -142,7 +146,7 @@ class Html
break;
case 'bgcolor':
// tables, rows, cells e.g. <tr bgColor="#FF0000">
$styles['bgColor'] = trim($val, '# ');
$styles['bgColor'] = self::convertRgb($val);
break;
case 'valign':
@@ -157,15 +161,15 @@ class Html
$attributeIdentifier = $attributes->getNamedItem('id');
if ($attributeIdentifier && self::$css) {
$styles = self::parseStyleDeclarations(self::$css->getStyle('#' . $attributeIdentifier->value), $styles);
$styles = self::parseStyleDeclarations(self::$css->getStyle('#' . $attributeIdentifier->nodeValue), $styles);
}
$attributeClass = $attributes->getNamedItem('class');
if ($attributeClass) {
if (self::$css) {
$styles = self::parseStyleDeclarations(self::$css->getStyle('.' . $attributeClass->value), $styles);
$styles = self::parseStyleDeclarations(self::$css->getStyle('.' . $attributeClass->nodeValue), $styles);
}
$styles['className'] = $attributeClass->value;
$styles['className'] = $attributeClass->nodeValue;
}
$attributeStyle = $attributes->getNamedItem('style');
@@ -323,10 +327,10 @@ class Html
return;
}
$inputType = $attributes->getNamedItem('type')->value;
$inputType = $attributes->getNamedItem('type')->nodeValue;
switch ($inputType) {
case 'checkbox':
$checked = ($checked = $attributes->getNamedItem('checked')) && $checked->value === 'true' ? true : false;
$checked = ($checked = $attributes->getNamedItem('checked')) && $checked->nodeValue === 'true' ? true : false;
$textrun = $element->addTextRun($styles['paragraph']);
$textrun->addFormField('checkbox')->setValue($checked);
@@ -421,8 +425,8 @@ class Html
}
$attributes = $node->attributes;
if ($attributes->getNamedItem('border') !== null) {
$border = (int) $attributes->getNamedItem('border')->value;
if ($attributes->getNamedItem('border')) {
$border = (int) $attributes->getNamedItem('border')->nodeValue;
$newElement->getStyle()->setBorderSize(Converter::pixelToTwip($border));
}
@@ -720,11 +724,11 @@ class Html
break;
case 'color':
$styles['color'] = trim($value, '#');
$styles['color'] = self::convertRgb($value);
break;
case 'background-color':
$styles['bgColor'] = trim($value, '#');
$styles['bgColor'] = self::convertRgb($value);
break;
case 'line-height':
@@ -898,12 +902,34 @@ class Html
break;
case 'width':
$width = $attribute->value;
// pt
if (false !== strpos($width, 'pt')) {
$width = Converter::pointToPixel((float) str_replace('pt', '', $width));
}
// px
if (false !== strpos($width, 'px')) {
$width = str_replace('px', '', $width);
}
$style['width'] = $width;
$style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX;
break;
case 'height':
$height = $attribute->value;
// pt
if (false !== strpos($height, 'pt')) {
$height = Converter::pointToPixel((float) str_replace('pt', '', $height));
}
// px
if (false !== strpos($height, 'px')) {
$height = str_replace('px', '', $height);
}
$style['height'] = $height;
$style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX;
@@ -1130,7 +1156,11 @@ class Html
}
$styles['font'] = self::parseInlineStyle($node, $styles['font']);
if (strpos($target, '#') === 0) {
if (empty($target)) {
$target = '#';
}
if (strpos($target, '#') === 0 && strlen($target) > 1) {
return $element->addLink(substr($target, 1), $node->textContent, $styles['font'], $styles['paragraph'], true);
}
@@ -1170,4 +1200,13 @@ class Html
// - line - that is a shape, has different behaviour
// - repeated text, e.g. underline "_", because of unpredictable line wrapping
}
private static function convertRgb(string $rgb): string
{
if (preg_match(self::RGB_REGEXP, $rgb, $matches) === 1) {
return sprintf('%02X%02X%02X', $matches[1], $matches[2], $matches[3]);
}
return trim($rgb, '# ');
}
}

View File

@@ -34,6 +34,9 @@ class PasswordEncoder
const ALGORITHM_MAC = 'MAC';
const ALGORITHM_HMAC = 'HMAC';
private const ALL_ONE_BITS = (PHP_INT_SIZE > 4) ? 0xFFFFFFFF : -1;
private const HIGH_ORDER_BIT = (PHP_INT_SIZE > 4) ? 0x80000000 : PHP_INT_MIN;
/**
* Mapping between algorithm name and algorithm ID.
*
@@ -128,7 +131,7 @@ class PasswordEncoder
// 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);
$hex = str_pad(strtoupper(dechex($combinedKey & self::ALL_ONE_BITS)), 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');
@@ -232,10 +235,10 @@ class PasswordEncoder
*/
private static function int32($value)
{
$value = ($value & 0xFFFFFFFF);
$value = $value & self::ALL_ONE_BITS;
if ($value & 0x80000000) {
$value = -((~$value & 0xFFFFFFFF) + 1);
if ($value & self::HIGH_ORDER_BIT) {
$value = -((~$value & self::ALL_ONE_BITS) + 1);
}
return $value;

View File

@@ -61,7 +61,15 @@ class XMLReader
}
$zip = new ZipArchive();
$zip->open($zipFile);
$openStatus = $zip->open($zipFile);
if ($openStatus !== true) {
/**
* Throw an exception since making further calls on the ZipArchive would cause a fatal error.
* This prevents fatal errors on corrupt archives and attempts to open old "doc" files.
*/
throw new Exception("The archive failed to load with the following error code: $openStatus");
}
$content = $zip->getFromName(ltrim($xmlFile, '/'));
$zip->close();

View File

@@ -20,6 +20,7 @@ namespace PhpOffice\PhpWord\Shared;
use PclZip;
use PhpOffice\PhpWord\Exception\Exception;
use PhpOffice\PhpWord\Settings;
use Throwable;
/**
* ZipArchive wrapper.
@@ -162,13 +163,16 @@ class ZipArchive
* Close the active archive.
*
* @return bool
*
* @codeCoverageIgnore Can't find any test case. Uncomment when found.
*/
public function close()
{
if (!$this->usePclzip) {
if ($this->zip->close() === false) {
try {
$result = @$this->zip->close();
} catch (Throwable $e) {
$result = false;
}
if ($result === false) {
throw new Exception("Could not close zip file {$this->filename}: ");
}
}

View File

@@ -34,7 +34,7 @@ class Border extends AbstractStyle
/**
* Border Top Color.
*
* @var string
* @var null|string
*/
protected $borderTopColor;
@@ -55,7 +55,7 @@ class Border extends AbstractStyle
/**
* Border Left Color.
*
* @var string
* @var null|string
*/
protected $borderLeftColor;
@@ -76,7 +76,7 @@ class Border extends AbstractStyle
/**
* Border Right Color.
*
* @var string
* @var null|string
*/
protected $borderRightColor;
@@ -97,7 +97,7 @@ class Border extends AbstractStyle
/**
* Border Bottom Color.
*
* @var string
* @var null|string
*/
protected $borderBottomColor;
@@ -171,7 +171,7 @@ class Border extends AbstractStyle
/**
* Get border color.
*
* @return string[]
* @return array<null|string>
*/
public function getBorderColor()
{
@@ -186,7 +186,7 @@ class Border extends AbstractStyle
/**
* Set border color.
*
* @param string $value
* @param null|string $value
*
* @return self
*/
@@ -259,7 +259,7 @@ class Border extends AbstractStyle
/**
* Get border top color.
*
* @return string
* @return null|string
*/
public function getBorderTopColor()
{
@@ -269,7 +269,7 @@ class Border extends AbstractStyle
/**
* Set border top color.
*
* @param string $value
* @param null|string $value
*
* @return self
*/
@@ -331,7 +331,7 @@ class Border extends AbstractStyle
/**
* Get border left color.
*
* @return string
* @return null|string
*/
public function getBorderLeftColor()
{
@@ -341,7 +341,7 @@ class Border extends AbstractStyle
/**
* Set border left color.
*
* @param string $value
* @param null|string $value
*
* @return self
*/
@@ -403,7 +403,7 @@ class Border extends AbstractStyle
/**
* Get border right color.
*
* @return string
* @return null|string
*/
public function getBorderRightColor()
{
@@ -413,7 +413,7 @@ class Border extends AbstractStyle
/**
* Set border right color.
*
* @param string $value
* @param null|string $value
*
* @return self
*/
@@ -475,7 +475,7 @@ class Border extends AbstractStyle
/**
* Get border bottom color.
*
* @return string
* @return null|string
*/
public function getBorderBottomColor()
{
@@ -485,7 +485,7 @@ class Border extends AbstractStyle
/**
* Set border bottom color.
*
* @param string $value
* @param null|string $value
*
* @return self
*/

View File

@@ -565,10 +565,8 @@ class Font extends AbstractStyle
/**
* Get strikethrough.
*
* @return bool
*/
public function isStrikethrough()
public function isStrikethrough(): ?bool
{
return $this->strikethrough;
}
@@ -577,20 +575,16 @@ class Font extends AbstractStyle
* Set strikethrough.
*
* @param bool $value
*
* @return self
*/
public function setStrikethrough($value = true)
public function setStrikethrough($value = true): self
{
return $this->setPairedVal($this->strikethrough, $this->doubleStrikethrough, $value);
}
/**
* Get double strikethrough.
*
* @return bool
*/
public function isDoubleStrikethrough()
public function isDoubleStrikethrough(): ?bool
{
return $this->doubleStrikethrough;
}
@@ -599,10 +593,8 @@ class Font extends AbstractStyle
* Set double strikethrough.
*
* @param bool $value
*
* @return self
*/
public function setDoubleStrikethrough($value = true)
public function setDoubleStrikethrough($value = true): self
{
return $this->setPairedVal($this->doubleStrikethrough, $this->strikethrough, $value);
}

View File

@@ -146,18 +146,6 @@ class TemplateProcessor
// Nothing to do here.
}
}
// Temporary file
if ($this->tempDocumentFilename && file_exists($this->tempDocumentFilename)) {
unlink($this->tempDocumentFilename);
}
}
public function __wakeup(): void
{
$this->tempDocumentFilename = '';
$this->zipClass = null;
throw new Exception('unserialize not permitted for this class');
}
/**
@@ -357,6 +345,15 @@ class TemplateProcessor
$replace = $xmlEscaper->escape($replace);
}
// convert carriage returns
if (is_array($replace)) {
foreach ($replace as &$item) {
$item = $this->replaceCarriageReturns($item);
}
} else {
$replace = $this->replaceCarriageReturns($replace);
}
$this->tempDocumentHeaders = $this->setValueForPart($search, $replace, $this->tempDocumentHeaders, $limit);
$this->tempDocumentMainPart = $this->setValueForPart($search, $replace, $this->tempDocumentMainPart, $limit);
$this->tempDocumentFooters = $this->setValueForPart($search, $replace, $this->tempDocumentFooters, $limit);
@@ -665,13 +662,13 @@ class TemplateProcessor
foreach (array_keys($this->tempDocumentHeaders) as $headerIndex) {
$searchParts[$this->getHeaderName($headerIndex)] = &$this->tempDocumentHeaders[$headerIndex];
}
foreach (array_keys($this->tempDocumentFooters) as $headerIndex) {
$searchParts[$this->getFooterName($headerIndex)] = &$this->tempDocumentFooters[$headerIndex];
foreach (array_keys($this->tempDocumentFooters) as $footerIndex) {
$searchParts[$this->getFooterName($footerIndex)] = &$this->tempDocumentFooters[$footerIndex];
}
// define templates
// result can be verified via "Open XML SDK 2.5 Productivity Tool" (http://www.microsoft.com/en-us/download/details.aspx?id=30425)
$imgTpl = '<w:pict><v:shape type="#_x0000_t75" style="width:{WIDTH};height:{HEIGHT}" stroked="f"><v:imagedata r:id="{RID}" o:title=""/></v:shape></w:pict>';
$imgTpl = '<w:pict><v:shape type="#_x0000_t75" style="width:{WIDTH};height:{HEIGHT}" stroked="f" filled="f"><v:imagedata r:id="{RID}" o:title=""/></v:shape></w:pict>';
$i = 0;
foreach ($searchParts as $partFileName => &$partContent) {
@@ -808,8 +805,8 @@ class TemplateProcessor
*/
public function deleteRow(string $search): void
{
if ('${' !== substr($search, 0, 2) && '}' !== substr($search, -1)) {
$search = '${' . $search . '}';
if (self::$macroOpeningChars !== substr($search, 0, 2) && self::$macroClosingChars !== substr($search, -1)) {
$search = self::$macroOpeningChars . $search . self::$macroClosingChars;
}
$tagPos = strpos($this->tempDocumentMainPart, $search);
@@ -1305,6 +1302,14 @@ class TemplateProcessor
return $results;
}
/**
* Replace carriage returns with xml.
*/
public function replaceCarriageReturns(string $string): string
{
return str_replace(["\r\n", "\r", "\n"], '</w:t><w:br/><w:t>', $string);
}
/**
* Replaces variables with values from array, array keys are the variable names.
*
@@ -1489,4 +1494,9 @@ class TemplateProcessor
self::$macroOpeningChars = $macroOpeningChars;
self::$macroClosingChars = $macroClosingChars;
}
public function getTempDocumentFilename(): string
{
return $this->tempDocumentFilename;
}
}

View File

@@ -65,16 +65,9 @@ class Table extends AbstractElement
$cellColSpan = $cellStyle->getGridSpan();
$cellRowSpan = 1;
$cellVMerge = $cellStyle->getVMerge();
// If this is the first cell of the vertical merge, find out how man rows it spans
// If this is the first cell of the vertical merge, find out how many rows it spans
if ($cellVMerge === 'restart') {
for ($k = $i + 1; $k < $rowCount; ++$k) {
$kRowCells = $rows[$k]->getCells();
if (isset($kRowCells[$j]) && $kRowCells[$j]->getStyle()->getVMerge() === 'continue') {
++$cellRowSpan;
} else {
break;
}
}
$cellRowSpan = $this->calculateCellRowSpan($rows, $i, $j);
}
// Ignore cells that are merged vertically with previous rows
if ($cellVMerge !== 'continue') {
@@ -120,9 +113,7 @@ class Table extends AbstractElement
return '';
}
if (is_string($tableStyle)) {
$style = ' class="' . $tableStyle;
return $style . '"';
return ' class="' . $tableStyle . '"';
}
$styleWriter = new TableStyleWriter($tableStyle);
@@ -133,4 +124,58 @@ class Table extends AbstractElement
return ' style="' . $style . '"';
}
/**
* Calculates cell rowspan.
*
* @param \PhpOffice\PhpWord\Element\Row[] $rows
*/
private function calculateCellRowSpan(array $rows, int $rowIndex, int $colIndex): int
{
$currentRow = $rows[$rowIndex];
$currentRowCells = $currentRow->getCells();
$shiftedColIndex = 0;
foreach ($currentRowCells as $cell) {
if ($cell === $currentRowCells[$colIndex]) {
break;
}
$colSpan = 1;
if ($cell->getStyle()->getGridSpan() !== null) {
$colSpan = $cell->getStyle()->getGridSpan();
}
$shiftedColIndex += $colSpan;
}
$rowCount = count($rows);
$rowSpan = 1;
for ($i = $rowIndex + 1; $i < $rowCount; ++$i) {
$rowCells = $rows[$i]->getCells();
$colIndex = 0;
foreach ($rowCells as $cell) {
if ($colIndex === $shiftedColIndex) {
if ($cell->getStyle()->getVMerge() === 'continue') {
++$rowSpan;
}
break;
}
$colSpan = 1;
if ($cell->getStyle()->getGridSpan() !== null) {
$colSpan = $cell->getStyle()->getGridSpan();
}
$colIndex += $colSpan;
}
}
return $rowSpan;
}
}

View File

@@ -24,7 +24,7 @@ use PhpOffice\PhpWord\Shared\Text as SharedText;
use PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\Style\Font as FontStyle;
use PhpOffice\PhpWord\Style\Paragraph as ParagraphStyle;
use PhpOffice\PhpWord\Writer\AbstractWriter;
use PhpOffice\PhpWord\Writer\RTF as WriterRTF;
use PhpOffice\PhpWord\Writer\RTF\Style\Font as FontStyleWriter;
use PhpOffice\PhpWord\Writer\RTF\Style\Paragraph as ParagraphStyleWriter;
@@ -38,7 +38,7 @@ abstract class AbstractElement
/**
* Parent writer.
*
* @var \PhpOffice\PhpWord\Writer\AbstractWriter
* @var WriterRTF
*/
protected $parentWriter;
@@ -82,7 +82,7 @@ abstract class AbstractElement
*/
protected $escaper;
public function __construct(AbstractWriter $parentWriter, Element $element, bool $withoutP = false)
public function __construct(WriterRTF $parentWriter, Element $element, bool $withoutP = false)
{
$this->parentWriter = $parentWriter;
$this->element = $element;

View File

@@ -21,7 +21,10 @@ use PhpOffice\PhpWord\Element\Cell as CellElement;
use PhpOffice\PhpWord\Element\Row as RowElement;
use PhpOffice\PhpWord\Element\Table as TableElement;
use PhpOffice\PhpWord\Settings;
use PhpOffice\PhpWord\SimpleType\Border;
use PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\Style\Cell as CellStyle;
use PhpOffice\PhpWord\Style\Table as TableStyle;
/**
* Table element RTF writer.
@@ -30,6 +33,11 @@ use PhpOffice\PhpWord\Style;
*/
class Table extends AbstractElement
{
/**
* @var TableElement
*/
protected $element;
/**
* Write element.
*
@@ -77,9 +85,18 @@ class Table extends AbstractElement
private function writeRowDef(RowElement $row)
{
$content = '';
$tableStyle = $this->element->getStyle();
if (is_string($tableStyle)) {
$tableStyle = Style::getStyle($tableStyle);
if (!($tableStyle instanceof TableStyle)) {
$tableStyle = null;
}
}
$rightMargin = 0;
foreach ($row->getCells() as $cell) {
$content .= $this->writeCellStyle($cell->getStyle(), $tableStyle);
$width = $cell->getWidth();
$vMerge = $this->getVMerge($cell->getStyle()->getVMerge());
if ($width === null) {
@@ -127,6 +144,103 @@ class Table extends AbstractElement
return $content;
}
private function writeCellStyle(CellStyle $cell, ?TableStyle $table): string
{
$content = $this->writeCellBorder(
't',
$cell->getBorderTopStyle() ?: ($table ? $table->getBorderTopStyle() : null),
(int) round($cell->getBorderTopSize() ?: ($table ? ($table->getBorderTopSize() ?: 0) : 0)),
$cell->getBorderTopColor() ?? ($table ? $table->getBorderTopColor() : null)
);
$content .= $this->writeCellBorder(
'l',
$cell->getBorderLeftStyle() ?: ($table ? $table->getBorderLeftStyle() : null),
(int) round($cell->getBorderLeftSize() ?: ($table ? ($table->getBorderLeftSize() ?: 0) : 0)),
$cell->getBorderLeftColor() ?? ($table ? $table->getBorderLeftColor() : null)
);
$content .= $this->writeCellBorder(
'b',
$cell->getBorderBottomStyle() ?: ($table ? $table->getBorderBottomStyle() : null),
(int) round($cell->getBorderBottomSize() ?: ($table ? ($table->getBorderBottomSize() ?: 0) : 0)),
$cell->getBorderBottomColor() ?? ($table ? $table->getBorderBottomColor() : null)
);
$content .= $this->writeCellBorder(
'r',
$cell->getBorderRightStyle() ?: ($table ? $table->getBorderRightStyle() : null),
(int) round($cell->getBorderRightSize() ?: ($table ? ($table->getBorderRightSize() ?: 0) : 0)),
$cell->getBorderRightColor() ?? ($table ? $table->getBorderRightColor() : null)
);
return $content;
}
private function writeCellBorder(string $prefix, ?string $borderStyle, int $borderSize, ?string $borderColor): string
{
if ($borderSize == 0) {
return '';
}
$content = '\clbrdr' . $prefix;
/**
* \brdrs Single-thickness border.
* \brdrth Double-thickness border.
* \brdrsh Shadowed border.
* \brdrdb Double border.
* \brdrdot Dotted border.
* \brdrdash Dashed border.
* \brdrhair Hairline border.
* \brdrinset Inset border.
* \brdrdashsm Dash border (small).
* \brdrdashd Dot dash border.
* \brdrdashdd Dot dot dash border.
* \brdroutset Outset border.
* \brdrtriple Triple border.
* \brdrtnthsg Thick thin border (small).
* \brdrthtnsg Thin thick border (small).
* \brdrtnthtnsg Thin thick thin border (small).
* \brdrtnthmg Thick thin border (medium).
* \brdrthtnmg Thin thick border (medium).
* \brdrtnthtnmg Thin thick thin border (medium).
* \brdrtnthlg Thick thin border (large).
* \brdrthtnlg Thin thick border (large).
* \brdrtnthtnlg Thin thick thin border (large).
* \brdrwavy Wavy border.
* \brdrwavydb Double wavy border.
* \brdrdashdotstr Striped border.
* \brdremboss Emboss border.
* \brdrengrave Engrave border.
*/
switch ($borderStyle) {
case Border::DOTTED:
$content .= '\brdrdot';
break;
case Border::SINGLE:
default:
$content .= '\brdrs';
break;
}
// \brdrwN N is the width in twips (1/20 pt) of the pen used to draw the paragraph border line.
// N cannot be greater than 75.
// To obtain a larger border width, the \brdth control word can be used to obtain a width double that of N.
// $borderSize is in eights of a point, i.e. 4 / 8 = .5pt
// 1/20 pt => 1/8 / 2.5
$content .= '\brdrw' . (int) ($borderSize / 2.5);
// \brdrcfN N is the color of the paragraph border, specified as an index into the color table in the RTF header.
$colorIndex = 0;
$index = array_search($borderColor, $this->parentWriter->getColorTable());
if ($index !== false) {
$colorIndex = (int) $index + 1;
}
$content .= '\brdrcf' . $colorIndex;
$content .= PHP_EOL;
return $content;
}
/**
* Get vertical merge style.
*

View File

@@ -21,6 +21,7 @@ use PhpOffice\PhpWord\Settings;
use PhpOffice\PhpWord\Shared\Converter;
use PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\Style\Font;
use PhpOffice\PhpWord\Style\Table;
/**
* RTF header part writer.
@@ -236,6 +237,14 @@ class Header extends AbstractPart
$this->registerTableItem($this->fontTable, $style->getName(), $defaultFont);
$this->registerTableItem($this->colorTable, $style->getColor(), $defaultColor);
$this->registerTableItem($this->colorTable, $style->getFgColor(), $defaultColor);
return;
}
if ($style instanceof Table) {
$this->registerTableItem($this->colorTable, $style->getBorderTopColor(), $defaultColor);
$this->registerTableItem($this->colorTable, $style->getBorderRightColor(), $defaultColor);
$this->registerTableItem($this->colorTable, $style->getBorderLeftColor(), $defaultColor);
$this->registerTableItem($this->colorTable, $style->getBorderBottomColor(), $defaultColor);
}
}

View File

@@ -227,7 +227,6 @@ class Word2007 extends AbstractWriter implements WriterInterface
$collection = $phpWord->$method();
// Add footnotes media files, relations, and contents
/** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collection Type hint */
if ($collection->countItems() > 0) {
$media = Media::getElements($noteType);
$this->addFilesToPackage($zip, $media);
@@ -260,7 +259,6 @@ class Word2007 extends AbstractWriter implements WriterInterface
$partName = 'comments';
// Add comment relations and contents
/** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collection Type hint */
if ($collection->countItems() > 0) {
$this->relationships[] = ['target' => "{$partName}.xml", 'type' => $partName, 'rID' => ++$rId];

View File

@@ -126,14 +126,10 @@ abstract class AbstractElement
*/
protected function writeCommentRangeStart(): void
{
if ($this->element->getCommentRangeStart() != null) {
$comment = $this->element->getCommentRangeStart();
//only set the ID if it is not yet set, otherwise it will overwrite it
if ($comment->getElementId() == null) {
$comment->setElementId();
if ($this->element->getCommentsRangeStart() != null) {
foreach ($this->element->getCommentsRangeStart()->getItems() as $comment) {
$this->xmlWriter->writeElementBlock('w:commentRangeStart', ['w:id' => $comment->getElementId()]);
}
$this->xmlWriter->writeElementBlock('w:commentRangeStart', ['w:id' => $comment->getElementId()]);
}
}
@@ -142,28 +138,23 @@ abstract class AbstractElement
*/
protected function writeCommentRangeEnd(): void
{
if ($this->element->getCommentRangeEnd() != null) {
$comment = $this->element->getCommentRangeEnd();
//only set the ID if it is not yet set, otherwise it will overwrite it, this should normally not happen
if ($comment->getElementId() == null) {
$comment->setElementId(); // @codeCoverageIgnore
} // @codeCoverageIgnore
$this->xmlWriter->writeElementBlock('w:commentRangeEnd', ['w:id' => $comment->getElementId()]);
$this->xmlWriter->startElement('w:r');
$this->xmlWriter->writeElementBlock('w:commentReference', ['w:id' => $comment->getElementId()]);
$this->xmlWriter->endElement();
} elseif ($this->element->getCommentRangeStart() != null && $this->element->getCommentRangeStart()->getEndElement() == null) {
$comment = $this->element->getCommentRangeStart();
//only set the ID if it is not yet set, otherwise it will overwrite it, this should normally not happen
if ($comment->getElementId() == null) {
$comment->setElementId(); // @codeCoverageIgnore
} // @codeCoverageIgnore
$this->xmlWriter->writeElementBlock('w:commentRangeEnd', ['w:id' => $comment->getElementId()]);
$this->xmlWriter->startElement('w:r');
$this->xmlWriter->writeElementBlock('w:commentReference', ['w:id' => $comment->getElementId()]);
$this->xmlWriter->endElement();
if ($this->element->getCommentsRangeEnd() != null) {
foreach ($this->element->getCommentsRangeEnd()->getItems() as $comment) {
$this->xmlWriter->writeElementBlock('w:commentRangeEnd', ['w:id' => $comment->getElementId()]);
$this->xmlWriter->startElement('w:r');
$this->xmlWriter->writeElementBlock('w:commentReference', ['w:id' => $comment->getElementId()]);
$this->xmlWriter->endElement();
}
}
if ($this->element->getCommentsRangeStart() != null) {
foreach ($this->element->getCommentsRangeStart()->getItems() as $comment) {
if ($comment->getEndElement() == null) {
$this->xmlWriter->writeElementBlock('w:commentRangeEnd', ['w:id' => $comment->getElementId()]);
$this->xmlWriter->startElement('w:r');
$this->xmlWriter->writeElementBlock('w:commentReference', ['w:id' => $comment->getElementId()]);
$this->xmlWriter->endElement();
}
}
}
}

View File

@@ -17,6 +17,9 @@
namespace PhpOffice\PhpWord\Writer\Word2007\Element;
use PhpOffice\PhpWord\Element\Field as ElementField;
use PhpOffice\PhpWord\Element\TextRun;
/**
* Field element writer.
*
@@ -30,7 +33,7 @@ class Field extends Text
public function write(): void
{
$element = $this->getElement();
if (!$element instanceof \PhpOffice\PhpWord\Element\Field) {
if (!$element instanceof ElementField) {
return;
}
@@ -42,7 +45,7 @@ class Field extends Text
}
}
private function writeDefault(\PhpOffice\PhpWord\Element\Field $element): void
private function writeDefault(ElementField $element): void
{
$xmlWriter = $this->getXmlWriter();
$this->startElementP();
@@ -73,7 +76,7 @@ class Field extends Text
$xmlWriter->endElement(); // w:r
if ($element->getText() != null) {
if ($element->getText() instanceof \PhpOffice\PhpWord\Element\TextRun) {
if ($element->getText() instanceof TextRun) {
$containerWriter = new Container($xmlWriter, $element->getText(), true);
$containerWriter->write();
@@ -120,7 +123,7 @@ class Field extends Text
*
* //TODO A lot of code duplication with general method, should maybe be refactored
*/
protected function writeMacrobutton(\PhpOffice\PhpWord\Element\Field $element): void
protected function writeMacrobutton(ElementField $element): void
{
$xmlWriter = $this->getXmlWriter();
$this->startElementP();
@@ -159,7 +162,7 @@ class Field extends Text
$this->endElementP(); // w:p
}
private function buildPropertiesAndOptions(\PhpOffice\PhpWord\Element\Field $element)
private function buildPropertiesAndOptions(ElementField $element)
{
$propertiesAndOptions = '';
$properties = $element->getProperties();
@@ -226,4 +229,104 @@ class Field extends Text
return $propertiesAndOptions;
}
/**
* Writes a REF field.
*/
protected function writeRef(ElementField $element): void
{
$xmlWriter = $this->getXmlWriter();
$this->startElementP();
$xmlWriter->startElement('w:r');
$xmlWriter->startElement('w:fldChar');
$xmlWriter->writeAttribute('w:fldCharType', 'begin');
$xmlWriter->endElement(); // w:fldChar
$xmlWriter->endElement(); // w:r
$instruction = ' ' . $element->getType() . ' ';
foreach ($element->getProperties() as $property) {
$instruction .= $property . ' ';
}
foreach ($element->getOptions() as $optionKey => $optionValue) {
$instruction .= $this->convertRefOption($optionKey, $optionValue) . ' ';
}
$xmlWriter->startElement('w:r');
$this->writeFontStyle();
$xmlWriter->startElement('w:instrText');
$xmlWriter->writeAttribute('xml:space', 'preserve');
$xmlWriter->text($instruction);
$xmlWriter->endElement(); // w:instrText
$xmlWriter->endElement(); // w:r
if ($element->getText() != null) {
if ($element->getText() instanceof \PhpOffice\PhpWord\Element\TextRun) {
$containerWriter = new Container($xmlWriter, $element->getText(), true);
$containerWriter->write();
$xmlWriter->startElement('w:r');
$xmlWriter->startElement('w:instrText');
$xmlWriter->text('"' . $this->buildPropertiesAndOptions($element));
$xmlWriter->endElement(); // w:instrText
$xmlWriter->endElement(); // w:r
$xmlWriter->startElement('w:r');
$xmlWriter->startElement('w:instrText');
$xmlWriter->writeAttribute('xml:space', 'preserve');
$xmlWriter->text(' ');
$xmlWriter->endElement(); // w:instrText
$xmlWriter->endElement(); // w:r
}
}
$xmlWriter->startElement('w:r');
$xmlWriter->startElement('w:fldChar');
$xmlWriter->writeAttribute('w:fldCharType', 'separate');
$xmlWriter->endElement(); // w:fldChar
$xmlWriter->endElement(); // w:r
$xmlWriter->startElement('w:r');
$xmlWriter->startElement('w:rPr');
$xmlWriter->startElement('w:noProof');
$xmlWriter->endElement(); // w:noProof
$xmlWriter->endElement(); // w:rPr
$xmlWriter->writeElement('w:t', $element->getText() != null && is_string($element->getText()) ? $element->getText() : '1');
$xmlWriter->endElement(); // w:r
$xmlWriter->startElement('w:r');
$xmlWriter->startElement('w:fldChar');
$xmlWriter->writeAttribute('w:fldCharType', 'end');
$xmlWriter->endElement(); // w:fldChar
$xmlWriter->endElement(); // w:r
$this->endElementP(); // w:p
}
private function convertRefOption(string $optionKey, string $optionValue): string
{
if ($optionKey === 'NumberSeperatorSequence') {
return '\\d ' . $optionValue;
}
switch ($optionValue) {
case 'IncrementAndInsertText':
return '\\f';
case 'CreateHyperLink':
return '\\h';
case 'NoTrailingPeriod':
return '\\n';
case 'IncludeAboveOrBelow':
return '\\p';
case 'InsertParagraphNumberRelativeContext':
return '\\r';
case 'SuppressNonDelimiterNonNumericalText':
return '\\t';
case 'InsertParagraphNumberFullContext':
return '\\w';
default:
return '';
}
}
}

View File

@@ -51,7 +51,7 @@ class Footnote extends Text
$xmlWriter->endElement(); // w:rStyle
$xmlWriter->endElement(); // w:rPr
$xmlWriter->startElement("w:{$this->referenceType}");
$xmlWriter->writeAttribute('w:id', $element->getRelationId());
$xmlWriter->writeAttribute('w:id', $element->getRelationId() + 1);
$xmlWriter->endElement(); // w:$referenceType
$xmlWriter->endElement(); // w:r

View File

@@ -103,8 +103,14 @@ class Table extends AbstractElement
}
// Write cells
foreach ($row->getCells() as $cell) {
$this->writeCell($xmlWriter, $cell);
$cells = $row->getCells();
if (count($cells) === 0) {
// issue 2505 - Word treats doc as corrupt if row without cell
$this->writeCell($xmlWriter, new CellElement());
} else {
foreach ($cells as $cell) {
$this->writeCell($xmlWriter, $cell);
}
}
$xmlWriter->endElement(); // w:tr

View File

@@ -141,7 +141,7 @@ class Footnotes extends AbstractPart
protected function writeNote(XMLWriter $xmlWriter, $element): void
{
$xmlWriter->startElement($this->elementNode);
$xmlWriter->writeAttribute('w:id', $element->getRelationId());
$xmlWriter->writeAttribute('w:id', $element->getRelationId() + 1);
$xmlWriter->startElement('w:p');
// Paragraph style

View File

@@ -117,8 +117,8 @@ class Font extends AbstractStyle
$xmlWriter->writeElementIf($style->isItalic() !== null, 'w:iCs', 'w:val', $this->writeOnOf($style->isItalic()));
// Strikethrough, double strikethrough
$xmlWriter->writeElementIf($style->isStrikethrough() !== null, 'w:strike', 'w:val', $this->writeOnOf($style->isStrikethrough()));
$xmlWriter->writeElementIf($style->isDoubleStrikethrough() !== null, 'w:dstrike', 'w:val', $this->writeOnOf($style->isDoubleStrikethrough()));
$xmlWriter->writeElementIf($style->isStrikethrough(), 'w:strike', 'w:val', $this->writeOnOf($style->isStrikethrough()));
$xmlWriter->writeElementIf($style->isDoubleStrikethrough(), 'w:dstrike', 'w:val', $this->writeOnOf($style->isDoubleStrikethrough()));
// Small caps, all caps
$xmlWriter->writeElementIf($style->isSmallCaps() !== null, 'w:smallCaps', 'w:val', $this->writeOnOf($style->isSmallCaps()));

View File

@@ -120,7 +120,7 @@ class MarginBorder extends AbstractStyle
/**
* Set colors.
*
* @param string[] $value
* @param array<null|string> $value
*/
public function setColors($value): void
{