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

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