From e24b2e1ba78f828c79db5b8538c4cc4934c4b560 Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Sun, 5 Jan 2020 10:27:45 -0800 Subject: [PATCH 1/7] ODT Changes Implement a number of features implemented in PhpWord, but not yet supported in PhpWord ODT Writer. 1. Add default file to tests/PhpWord/_includes/XmlDocument.php to make it considerably easier to test ODT changes (and Word2007 changes involving files other that document.xml). 2. Page break before each section. 3. Page numbering start. 4. Font style for Headings. 5. Alignment for images. 6. Paragraph style for TextRun. 7. "Hide grammatical errors" for whole document. 8. Page layout for each section. 9. For each page layout, support user-specified page width, page height, orientation, margin top, margin bottom, margin left, margin right. 10. Page header and footer. 11. Named colors. 12. NoProof font style. 13. Paragraph Style - spaceBefore, spaceAfter, lineHeight, pageBreakBefore, indentation, text alignment. 14. Tab stops. 15. Basic support for some Fields (DATE, PAGE, NUMPAGES). 16. Link had an error in how it was handling internal links (needs leading #). 17. In addition to tests for all the above, added some tests for Tables. Item 11 above needs 1 module from Pull Request 1775, which is targeted for v0.18.0 but not yet merged, so the relevant module is also here. Item 15 above needs 1 module from Pull Request 1774, which is targeted for v0.18.0 but not yet merged, so the relevant module is also here. Testing change from Pull Request 1771 is included here, but was merged after my fork. --- src/PhpWord/Element/Field.php | 40 +- src/PhpWord/Shared/Converter.php | 46 ++ src/PhpWord/Writer/ODText/Element/Field.php | 81 ++++ src/PhpWord/Writer/ODText/Element/Image.php | 2 +- src/PhpWord/Writer/ODText/Element/Link.php | 2 +- .../Writer/ODText/Element/PageBreak.php | 2 +- src/PhpWord/Writer/ODText/Element/Text.php | 48 +- src/PhpWord/Writer/ODText/Element/TextRun.php | 7 +- src/PhpWord/Writer/ODText/Element/Title.php | 19 +- src/PhpWord/Writer/ODText/Part/Content.php | 109 ++++- src/PhpWord/Writer/ODText/Part/Styles.php | 143 +++++- src/PhpWord/Writer/ODText/Style/Font.php | 19 +- src/PhpWord/Writer/ODText/Style/Paragraph.php | 113 ++++- .../Writer/ODText/Element/ImageTest.php | 66 +++ tests/PhpWord/Writer/ODText/ElementTest.php | 174 ++++++- .../Writer/ODText/Part/ContentTest.php | 2 +- .../PhpWord/Writer/ODText/Style/FontTest.php | 131 ++++++ .../Writer/ODText/Style/ParagraphTest.php | 434 ++++++++++++++++++ .../Writer/ODText/Style/SectionTest.php | 249 ++++++++++ tests/PhpWord/_includes/TestHelperDOCX.php | 7 +- tests/PhpWord/_includes/XmlDocument.php | 62 ++- 21 files changed, 1679 insertions(+), 77 deletions(-) create mode 100644 src/PhpWord/Writer/ODText/Element/Field.php create mode 100644 tests/PhpWord/Writer/ODText/Element/ImageTest.php create mode 100644 tests/PhpWord/Writer/ODText/Style/FontTest.php create mode 100644 tests/PhpWord/Writer/ODText/Style/ParagraphTest.php create mode 100644 tests/PhpWord/Writer/ODText/Style/SectionTest.php diff --git a/src/PhpWord/Element/Field.php b/src/PhpWord/Element/Field.php index 2efc6b0b..3d1503fe 100644 --- a/src/PhpWord/Element/Field.php +++ b/src/PhpWord/Element/Field.php @@ -17,6 +17,8 @@ namespace PhpOffice\PhpWord\Element; +use PhpOffice\PhpWord\Style\Font; + /** * Field element * @@ -115,10 +117,42 @@ class Field extends AbstractElement /** * Font style * - * @var \PhpOffice\PhpWord\Style\Font + * @var string|\PhpOffice\PhpWord\Style\Font */ protected $fontStyle; + /** + * Set Font style + * + * @param string|array|\PhpOffice\PhpWord\Style\Font $style + * @return string|\PhpOffice\PhpWord\Style\Font + */ + public function setFontStyle($style = null) + { + if ($style instanceof Font) { + $this->fontStyle = $style; + } elseif (is_array($style)) { + $this->fontStyle = new Font('text'); + $this->fontStyle->setStyleByArray($style); + } elseif (null === $style) { + $this->fontStyle = null; + } else { + $this->fontStyle = $style; + } + + return $this->fontStyle; + } + + /** + * Get Font style + * + * @return string|\PhpOffice\PhpWord\Style\Font + */ + public function getFontStyle() + { + return $this->fontStyle; + } + /** * Create a new Field Element * @@ -126,13 +160,15 @@ class Field extends AbstractElement * @param array $properties * @param array $options * @param TextRun|string|null $text + * @param string|array|\PhpOffice\PhpWord\Style\Font $fontStyle */ - public function __construct($type = null, $properties = array(), $options = array(), $text = null) + public function __construct($type = null, $properties = array(), $options = array(), $text = null, $fontStyle = null) { $this->setType($type); $this->setProperties($properties); $this->setOptions($options); $this->setText($text); + $this->setFontStyle($fontStyle); } /** diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php index 7008ac5d..9206a3bc 100644 --- a/src/PhpWord/Shared/Converter.php +++ b/src/PhpWord/Shared/Converter.php @@ -272,6 +272,50 @@ class Converter return round($angle / self::DEGREE_TO_ANGLE); } + /** + * Convert colorname as string to RGB + * + * @param string $value color name + * @return string color as hex RGB string, or original value if unknown + */ + public static function stringToRgb($value) + { + switch ($value) { + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_YELLOW: + return 'FFFF00'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_LIGHTGREEN: + return '90EE90'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_CYAN: + return '00FFFF'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_MAGENTA: + return 'FF00FF'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_BLUE: + return '0000FF'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_RED: + return 'FF0000'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKBLUE: + return '00008B'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKCYAN: + return '008B8B'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKGREEN: + return '006400'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKMAGENTA: + return '8B008B'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKRED: + return '8B0000'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKYELLOW: + return '8B8B00'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKGRAY: + return 'A9A9A9'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_LIGHTGRAY: + return 'D3D3D3'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_BLACK: + return '000000'; + } + + return $value; + } + /** * Convert HTML hexadecimal to RGB * @@ -282,6 +326,8 @@ class Converter { if ($value[0] == '#') { $value = substr($value, 1); + } else { + $value = self::stringToRgb($value); } if (strlen($value) == 6) { diff --git a/src/PhpWord/Writer/ODText/Element/Field.php b/src/PhpWord/Writer/ODText/Element/Field.php new file mode 100644 index 00000000..c95139ac --- /dev/null +++ b/src/PhpWord/Writer/ODText/Element/Field.php @@ -0,0 +1,81 @@ +getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Field) { + return; + } + + $type = strtolower($element->getType()); + switch ($type) { + case 'date': // Owen 2020-01-02 + case 'page': + case 'numpages': + $this->writeDefault($element, $type); + break; + } + } + + private function writeDefault(\PhpOffice\PhpWord\Element\Field $element, $type) + { + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('text:span'); + if (method_exists($element, 'getFontStyle')) { + $fstyle = $element->getFontStyle(); + if (is_string($fstyle)) { + $xmlWriter->writeAttribute('text:style-name', $fstyle); + } + } + switch ($type) { + case 'date': // Owen 2019-01-02 + $xmlWriter->startElement('text:date'); + $xmlWriter->writeAttribute('text:fixed', 'false'); + $xmlWriter->endElement(); + break; + case 'page': + $xmlWriter->startElement('text:page-number'); + $xmlWriter->writeAttribute('text:fixed', 'false'); + $xmlWriter->endElement(); + break; + case 'numpages': + $xmlWriter->startElement('text:page-count'); + $xmlWriter->endElement(); + break; + } + $xmlWriter->endElement(); // text:span + } +} diff --git a/src/PhpWord/Writer/ODText/Element/Image.php b/src/PhpWord/Writer/ODText/Element/Image.php index add45e10..57aa546a 100644 --- a/src/PhpWord/Writer/ODText/Element/Image.php +++ b/src/PhpWord/Writer/ODText/Element/Image.php @@ -44,7 +44,7 @@ class Image extends AbstractElement $height = Converter::pixelToCm($style->getHeight()); $xmlWriter->startElement('text:p'); - $xmlWriter->writeAttribute('text:style-name', 'Standard'); + $xmlWriter->writeAttribute('text:style-name', 'IM' . $mediaIndex); $xmlWriter->startElement('draw:frame'); $xmlWriter->writeAttribute('draw:style-name', 'fr' . $mediaIndex); diff --git a/src/PhpWord/Writer/ODText/Element/Link.php b/src/PhpWord/Writer/ODText/Element/Link.php index d6fec507..23c9804b 100644 --- a/src/PhpWord/Writer/ODText/Element/Link.php +++ b/src/PhpWord/Writer/ODText/Element/Link.php @@ -41,7 +41,7 @@ class Link extends AbstractElement $xmlWriter->startElement('text:a'); $xmlWriter->writeAttribute('xlink:type', 'simple'); - $xmlWriter->writeAttribute('xlink:href', $element->getSource()); + $xmlWriter->writeAttribute('xlink:href', ($element->isInternal() ? '#' : '') . $element->getSource()); $this->writeText($element->getText()); $xmlWriter->endElement(); // text:a diff --git a/src/PhpWord/Writer/ODText/Element/PageBreak.php b/src/PhpWord/Writer/ODText/Element/PageBreak.php index ecf47607..8e4f4695 100644 --- a/src/PhpWord/Writer/ODText/Element/PageBreak.php +++ b/src/PhpWord/Writer/ODText/Element/PageBreak.php @@ -30,7 +30,7 @@ class PageBreak extends AbstractElement $xmlWriter = $this->getXmlWriter(); $xmlWriter->startElement('text:p'); - $xmlWriter->writeAttribute('text:style-name', 'P1'); + $xmlWriter->writeAttribute('text:style-name', 'PB'); $xmlWriter->endElement(); } } diff --git a/src/PhpWord/Writer/ODText/Element/Text.php b/src/PhpWord/Writer/ODText/Element/Text.php index 7dcd28a0..2bf9908d 100644 --- a/src/PhpWord/Writer/ODText/Element/Text.php +++ b/src/PhpWord/Writer/ODText/Element/Text.php @@ -59,18 +59,26 @@ class Text extends AbstractElement } else { if (empty($fontStyle)) { if (empty($paragraphStyle)) { - $xmlWriter->writeAttribute('text:style-name', 'P1'); + if (!$this->withoutP) { + $xmlWriter->writeAttribute('text:style-name', 'Normal'); + } } elseif (is_string($paragraphStyle)) { - $xmlWriter->writeAttribute('text:style-name', $paragraphStyle); + if (!$this->withoutP) { + $xmlWriter->writeAttribute('text:style-name', $paragraphStyle); + } } $this->writeChangeInsertion(true, $element->getTrackChange()); - $this->writeText($element->getText()); + $this->replaceTabs($element->getText(), $xmlWriter); $this->writeChangeInsertion(false, $element->getTrackChange()); } else { if (empty($paragraphStyle)) { - $xmlWriter->writeAttribute('text:style-name', 'Standard'); + if (!$this->withoutP) { + $xmlWriter->writeAttribute('text:style-name', 'Normal'); + } } elseif (is_string($paragraphStyle)) { - $xmlWriter->writeAttribute('text:style-name', $paragraphStyle); + if (!$this->withoutP) { + $xmlWriter->writeAttribute('text:style-name', $paragraphStyle); + } } // text:span $xmlWriter->startElement('text:span'); @@ -78,7 +86,7 @@ class Text extends AbstractElement $xmlWriter->writeAttribute('text:style-name', $fontStyle); } $this->writeChangeInsertion(true, $element->getTrackChange()); - $this->writeText($element->getText()); + $this->replaceTabs($element->getText(), $xmlWriter); $this->writeChangeInsertion(false, $element->getTrackChange()); $xmlWriter->endElement(); } @@ -88,6 +96,34 @@ class Text extends AbstractElement } } + private function replacetabs($text, $xmlWriter) + { + if (preg_match('/^ +/', $text, $matches)) { + $num = strlen($matches[0]); + $xmlWriter->startElement('text:s'); + $xmlWriter->writeAttributeIf($num > 1, 'text:c', "$num"); + $xmlWriter->endElement(); + $text = preg_replace('/^ +/', '', $text); + } + preg_match_all('/([\\s\\S]*?)(\\t| +| ?$)/', $text, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $this->writeText($match[1]); + if ($match[2] === '') { + break; + } elseif ($match[2] === "\t") { + $xmlWriter->writeElement('text:tab'); + } elseif ($match[2] === ' ') { + $xmlWriter->writeElement('text:s'); + break; + } else { + $num = strlen($match[2]); + $xmlWriter->startElement('text:s'); + $xmlWriter->writeAttributeIf($num > 1, 'text:c', "$num"); + $xmlWriter->endElement(); + } + } + } + private function writeChangeInsertion($start = true, TrackChange $trackChange = null) { if ($trackChange == null || $trackChange->getChangeType() != TrackChange::INSERTED) { diff --git a/src/PhpWord/Writer/ODText/Element/TextRun.php b/src/PhpWord/Writer/ODText/Element/TextRun.php index 78e5a8ad..cde996f6 100644 --- a/src/PhpWord/Writer/ODText/Element/TextRun.php +++ b/src/PhpWord/Writer/ODText/Element/TextRun.php @@ -22,7 +22,7 @@ namespace PhpOffice\PhpWord\Writer\ODText\Element; * * @since 0.10.0 */ -class TextRun extends AbstractElement +class TextRun extends Text { /** * Write element @@ -33,6 +33,11 @@ class TextRun extends AbstractElement $element = $this->getElement(); $xmlWriter->startElement('text:p'); + $pStyle = $element->getParagraphStyle(); + if (!is_string($pStyle)) { + $pStyle = 'Normal'; + } + $xmlWriter->writeAttribute('text:style-name', $pStyle); $containerWriter = new Container($xmlWriter, $element); $containerWriter->write(); diff --git a/src/PhpWord/Writer/ODText/Element/Title.php b/src/PhpWord/Writer/ODText/Element/Title.php index 8b9440ab..99153b5e 100644 --- a/src/PhpWord/Writer/ODText/Element/Title.php +++ b/src/PhpWord/Writer/ODText/Element/Title.php @@ -36,7 +36,23 @@ class Title extends AbstractElement } $xmlWriter->startElement('text:h'); - $xmlWriter->writeAttribute('text:outline-level', $element->getDepth()); + $hdname = 'HD'; + $sect = $element->getParent(); + if ($sect instanceof \PhpOffice\PhpWord\Element\Section) { + $elems = $sect->getElements(); + if ($elems[0] === $element) { + $hdname = 'HE'; + } + } + $depth = $element->getDepth(); + $xmlWriter->writeAttribute('text:style-name', "$hdname$depth"); + $xmlWriter->writeAttribute('text:outline-level', $depth); + $xmlWriter->startElement('text:span'); + if ($depth > 0) { + $xmlWriter->writeAttribute('text:style-name', 'Heading_' . $depth); + } else { + $xmlWriter->writeAttribute('text:style-name', 'Title'); + } $text = $element->getText(); if (is_string($text)) { $this->writeText($text); @@ -44,6 +60,7 @@ class Title extends AbstractElement $containerWriter = new Container($xmlWriter, $text); $containerWriter->write(); } + $xmlWriter->endElement(); // text:span $xmlWriter->endElement(); // text:h } } diff --git a/src/PhpWord/Writer/ODText/Part/Content.php b/src/PhpWord/Writer/ODText/Part/Content.php index 99ee9353..8eaad40f 100644 --- a/src/PhpWord/Writer/ODText/Part/Content.php +++ b/src/PhpWord/Writer/ODText/Part/Content.php @@ -46,6 +46,7 @@ class Content extends AbstractPart * @var array */ private $autoStyles = array('Section' => array(), 'Image' => array(), 'Table' => array()); + private $imageParagraphStyles = array(); /** * Write part @@ -128,6 +129,9 @@ class Content extends AbstractPart $xmlWriter->startElement('text:section'); $xmlWriter->writeAttribute('text:name', $name); $xmlWriter->writeAttribute('text:style-name', $name); + $xmlWriter->startElement('text:p'); + $xmlWriter->writeAttribute('text:style-name', 'SB' . $section->getSectionId()); + $xmlWriter->endElement(); $containerWriter = new Container($xmlWriter, $section); $containerWriter->write(); $xmlWriter->endElement(); // text:section @@ -174,28 +178,58 @@ class Content extends AbstractPart { $styles = Style::getStyles(); $paragraphStyleCount = 0; - if (count($styles) > 0) { - foreach ($styles as $style) { - if ($style->isAuto() === true) { - $styleClass = str_replace('\\Style\\', '\\Writer\\ODText\\Style\\', get_class($style)); - if (class_exists($styleClass)) { - /** @var \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle $styleWriter Type hint */ - $styleWriter = new $styleClass($xmlWriter, $style); - $styleWriter->write(); - } - if ($style instanceof Paragraph) { - $paragraphStyleCount++; - } - } - } - if ($paragraphStyleCount == 0) { + + $style = new Paragraph(); + $style->setStyleName('PB'); + $style->setAuto(); + $styleWriter = new ParagraphStyleWriter($xmlWriter, $style); + $styleWriter->write(); + + $sects = $this->getParentWriter()->getPhpWord()->getSections(); + for ($i = 0; $i < count($sects); ++$i) { + $iplus1 = $i + 1; + $style = new Paragraph(); + $style->setStyleName("SB$iplus1"); + $style->setAuto(); + $pnstart = $sects[$i]->getStyle()->getPageNumberingStart(); + $style->setNumLevel($pnstart); + $styleWriter = new ParagraphStyleWriter($xmlWriter, $style); + $styleWriter->write(); + } + + foreach ($styles as $style) { + $sty = $style->getStyleName(); + if (substr($sty, 0, 8) === 'Heading_') { $style = new Paragraph(); - $style->setStyleName('P1'); + $style->setStyleName('HD' . substr($sty, 8)); + $style->setAuto(); + $styleWriter = new ParagraphStyleWriter($xmlWriter, $style); + $styleWriter->write(); + $style = new Paragraph(); + $style->setStyleName('HE' . substr($sty, 8)); $style->setAuto(); $styleWriter = new ParagraphStyleWriter($xmlWriter, $style); $styleWriter->write(); } } + + foreach ($styles as $style) { + if ($style->isAuto() === true) { + $styleClass = str_replace('\\Style\\', '\\Writer\\ODText\\Style\\', get_class($style)); + if (class_exists($styleClass)) { + /** @var \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle $styleWriter Type hint */ + $styleWriter = new $styleClass($xmlWriter, $style); + $styleWriter->write(); + } + if ($style instanceof Paragraph) { + $paragraphStyleCount++; + } + } + } + foreach ($this->imageParagraphStyles as $style) { + $styleWriter = new \PhpOffice\PhpWord\Writer\ODText\Style\Paragraph($xmlWriter, $style); + $styleWriter->write(); + } } /** @@ -231,6 +265,7 @@ class Content extends AbstractPart $elements = $container->getElements(); foreach ($elements as $element) { if ($element instanceof TextRun) { + $this->getElementStyle($element, $paragraphStyleCount, $fontStyleCount); $this->getContainerStyle($element, $paragraphStyleCount, $fontStyleCount); } elseif ($element instanceof Text) { $this->getElementStyle($element, $paragraphStyleCount, $fontStyleCount); @@ -238,13 +273,19 @@ class Content extends AbstractPart $style = $element->getStyle(); $style->setStyleName('fr' . $element->getMediaIndex()); $this->autoStyles['Image'][] = $style; + $sty = new \PhpOffice\PhpWord\Style\Paragraph(); + $sty->setStyleName('IM' . $element->getMediaIndex()); + $sty->setAuto(); + $sty->setAlignment($style->getAlignment()); + $this->imageParagraphStyles[] = $sty; } elseif ($element instanceof Table) { /** @var \PhpOffice\PhpWord\Style\Table $style */ $style = $element->getStyle(); + if (is_string($style)) { + $style = Style::getStyle($style); + } if ($style === null) { $style = new TableStyle(); - } elseif (is_string($style)) { - $style = Style::getStyle($style); } $style->setStyleName($element->getElementId()); $style->setColumnWidths($element->findFirstDefinedCellWidths()); @@ -268,16 +309,34 @@ class Content extends AbstractPart if ($fontStyle instanceof Font) { // Font - $fontStyleCount++; - $style = $phpWord->addFontStyle("T{$fontStyleCount}", $fontStyle); - $style->setAuto(); - $element->setFontStyle("T{$fontStyleCount}"); - } elseif ($paragraphStyle instanceof Paragraph) { + $name = $fontStyle->getStyleName(); + if (!$name) { + $fontStyleCount++; + $style = $phpWord->addFontStyle("T{$fontStyleCount}", $fontStyle, null); + $style->setAuto(); + $style->setParagraph(null); + $element->setFontStyle("T{$fontStyleCount}"); + } else { + $element->setFontStyle($name); + } + } + if ($paragraphStyle instanceof Paragraph) { // Paragraph + $name = $paragraphStyle->getStyleName(); + if (!$name) { + $paragraphStyleCount++; + $style = $phpWord->addParagraphStyle("P{$paragraphStyleCount}", $paragraphStyle); + $style->setAuto(); + $element->setParagraphStyle("P{$paragraphStyleCount}"); + } else { + $element->setParagraphStyle($name); + } + } elseif (is_string($paragraphStyle)) { $paragraphStyleCount++; - $style = $phpWord->addParagraphStyle("P{$paragraphStyleCount}", array()); + $parstylename = "P$paragraphStyleCount" . "_$paragraphStyle"; + $style = $phpWord->addParagraphStyle($parstylename, $paragraphStyle); $style->setAuto(); - $element->setParagraphStyle("P{$paragraphStyleCount}"); + $element->setParagraphStyle($parstylename); } } diff --git a/src/PhpWord/Writer/ODText/Part/Styles.php b/src/PhpWord/Writer/ODText/Part/Styles.php index e7635e98..862f4b2a 100644 --- a/src/PhpWord/Writer/ODText/Part/Styles.php +++ b/src/PhpWord/Writer/ODText/Part/Styles.php @@ -19,6 +19,7 @@ namespace PhpOffice\PhpWord\Writer\ODText\Part; use PhpOffice\Common\XMLWriter; use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\Style; /** @@ -86,6 +87,9 @@ class Styles extends AbstractPart $latinLang = $language != null && is_string($language->getLatin()) ? explode('-', $language->getLatin()) : array('fr', 'FR'); $asianLang = $language != null && is_string($language->getEastAsia()) ? explode('-', $language->getEastAsia()) : array('zh', 'CN'); $complexLang = $language != null && is_string($language->getBidirectional()) ? explode('-', $language->getBidirectional()) : array('hi', 'IN'); + if ($this->getParentWriter()->getPhpWord()->getSettings()->hasHideGrammaticalErrors()) { + $latinLang = $asianLang = $complexLang = array('zxx', 'none'); + } // Font $xmlWriter->startElement('style:text-properties'); @@ -134,24 +138,87 @@ class Styles extends AbstractPart } /** - * Write page layout styles. + * Convert int in twips to inches/cm then to string and append unit + * + * @param int $twips + * @param string $dflt + * @param float $factor + * return string + */ + private static function cvttwiptostr($twips, $dflt, $factor = 1.0) // Owen 2019-08-06 + { + if ($twips === null) { + return $dflt; + } + $ins = (string) ($twips * $factor / Converter::INCH_TO_TWIP) . 'in'; + $cms = (string) ($twips * $factor * Converter::INCH_TO_CM / Converter::INCH_TO_TWIP) . 'cm'; + + return (strlen($ins) < strlen($cms)) ? $ins : $cms; + } + + /** + * call writePageLayoutIndiv to write page layout styles for each page * * @param \PhpOffice\Common\XMLWriter $xmlWriter */ - private function writePageLayout(XMLWriter $xmlWriter) + private function writePageLayout(XMLWriter $xmlWriter) // Owen 2019-06-19 { + $sections = $this->getParentWriter()->getPhpWord()->getSections(); + for ($i = 0; $i < count($sections); ++$i) { + $this->writePageLayoutIndiv($xmlWriter, $sections[$i], $i + 1); + } + } + + /** + * Write page layout styles. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\Section $section + * @param int $sectionNbr + */ + private function writePageLayoutIndiv(XMLWriter $xmlWriter, $section, $sectionNbr) + { + $sty = $section->getStyle(); + if (count($section->getHeaders()) > 0) { + $topfactor = 0.5; + } else { + $topfactor = 1.0; + } + if (count($section->getFooters()) > 0) { + $botfactor = 0.5; + } else { + $botfactor = 1.0; + } + $pwidth = '21.001cm'; + $pheight = '29.7cm'; + $orient = 'portrait'; + $mtop = $mleft = $mright = '2.501cm'; + $mbottom = '2cm'; + if ($sty instanceof \PhpOffice\PhpWord\Style\Section) { + $ori = $sty->getOrientation(); + if ($ori !== null) { + $orient = $ori; + } + $pwidth = self::cvttwiptostr($sty->getPageSizeW(), $pwidth); + $pheight = self::cvttwiptostr($sty->getPageSizeH(), $pheight); + $mtop = self::cvttwiptostr($sty->getMarginTop(), $mtop, $topfactor); + $mbottom = self::cvttwiptostr($sty->getMarginBottom(), $mbottom, $botfactor); + $mleft = self::cvttwiptostr($sty->getMarginRight(), $mleft); + $mright = self::cvttwiptostr($sty->getMarginLeft(), $mright); + } + $xmlWriter->startElement('style:page-layout'); - $xmlWriter->writeAttribute('style:name', 'Mpm1'); + $xmlWriter->writeAttribute('style:name', "Mpm$sectionNbr"); $xmlWriter->startElement('style:page-layout-properties'); - $xmlWriter->writeAttribute('fo:page-width', '21.001cm'); - $xmlWriter->writeAttribute('fo:page-height', '29.7cm'); + $xmlWriter->writeAttribute('fo:page-width', $pwidth); + $xmlWriter->writeAttribute('fo:page-height', $pheight); $xmlWriter->writeAttribute('style:num-format', '1'); - $xmlWriter->writeAttribute('style:print-orientation', 'portrait'); - $xmlWriter->writeAttribute('fo:margin-top', '2.501cm'); - $xmlWriter->writeAttribute('fo:margin-bottom', '2cm'); - $xmlWriter->writeAttribute('fo:margin-left', '2.501cm'); - $xmlWriter->writeAttribute('fo:margin-right', '2.501cm'); + $xmlWriter->writeAttribute('style:print-orientation', $orient); + $xmlWriter->writeAttribute('fo:margin-top', $mtop); + $xmlWriter->writeAttribute('fo:margin-bottom', $mbottom); + $xmlWriter->writeAttribute('fo:margin-left', $mleft); + $xmlWriter->writeAttribute('fo:margin-right', $mright); $xmlWriter->writeAttribute('style:writing-mode', 'lr-tb'); $xmlWriter->writeAttribute('style:layout-grid-color', '#c0c0c0'); $xmlWriter->writeAttribute('style:layout-grid-lines', '25199'); @@ -176,9 +243,23 @@ class Styles extends AbstractPart $xmlWriter->endElement(); // style:page-layout-properties $xmlWriter->startElement('style:header-style'); + if ($topfactor < 1.0) { + $xmlWriter->startElement('style:header-footer-properties'); + $xmlWriter->writeAttribute('fo:min-height', $mtop); + $xmlWriter->writeAttribute('fo:margin-bottom', $mtop); + $xmlWriter->writeAttribute('style:dynamic-spacing', 'true'); + $xmlWriter->endElement(); // style:header-footer-properties + } $xmlWriter->endElement(); // style:header-style $xmlWriter->startElement('style:footer-style'); + if ($botfactor < 1.0) { // Owen 2019-08-03 + $xmlWriter->startElement('style:header-footer-properties'); + $xmlWriter->writeAttribute('fo:min-height', $mbottom); + $xmlWriter->writeAttribute('fo:margin-top', $mbottom); + $xmlWriter->writeAttribute('style:dynamic-spacing', 'true'); + $xmlWriter->endElement(); // style:header-footer-properties + } $xmlWriter->endElement(); // style:footer-style $xmlWriter->endElement(); // style:page-layout @@ -193,11 +274,43 @@ class Styles extends AbstractPart { $xmlWriter->startElement('office:master-styles'); - $xmlWriter->startElement('style:master-page'); - $xmlWriter->writeAttribute('style:name', 'Standard'); - $xmlWriter->writeAttribute('style:page-layout-name', 'Mpm1'); - $xmlWriter->endElement(); // style:master-page - + $sections = $this->getParentWriter()->getPhpWord()->getSections(); + for ($i = 0; $i < count($sections); ++$i) { + $iplus1 = $i + 1; + $xmlWriter->startElement('style:master-page'); + $xmlWriter->writeAttribute('style:name', "Standard$iplus1"); + $xmlWriter->writeAttribute('style:page-layout-name', "Mpm$iplus1"); + // Multiple headers and footers probably not supported, + // and, even if they are, I'm not sure how, + // so quit after generating one. + foreach ($sections[$i]->getHeaders() as $hdr) { + $xmlWriter->startElement('style:header'); + foreach ($hdr->getElements() as $elem) { + $cl1 = get_class($elem); + $cl2 = str_replace('\\Element\\', '\\Writer\\ODText\\Element\\', $cl1); + if (class_exists($cl2)) { + $wtr = new $cl2($xmlWriter, $elem); + $wtr->write(); + } + } + $xmlWriter->endElement(); // style:header + break; + } + foreach ($sections[$i]->getFooters() as $hdr) { + $xmlWriter->startElement('style:footer'); + foreach ($hdr->getElements() as $elem) { + $cl1 = get_class($elem); + $cl2 = str_replace('\\Element\\', '\\Writer\\ODText\\Element\\', $cl1); + if (class_exists($cl2)) { + $wtr = new $cl2($xmlWriter, $elem); + $wtr->write(); + } + } + $xmlWriter->endElement(); // style:footer + break; + } + $xmlWriter->endElement(); // style:master-page + } $xmlWriter->endElement(); // office:master-styles } } diff --git a/src/PhpWord/Writer/ODText/Style/Font.php b/src/PhpWord/Writer/ODText/Style/Font.php index 29657c5a..ae9c417e 100644 --- a/src/PhpWord/Writer/ODText/Style/Font.php +++ b/src/PhpWord/Writer/ODText/Style/Font.php @@ -35,6 +35,14 @@ class Font extends AbstractStyle } $xmlWriter = $this->getXmlWriter(); + $stylep = (method_exists($style, 'getParagraph')) ? $style->getParagraph() : null; + if ($stylep instanceof \PhpOffice\PhpWord\Style\Paragraph) { + $temp1 = clone $stylep; + $temp1->setStyleName($style->getStyleName()); + $temp2 = new \PhpOffice\PhpWord\Writer\ODText\Style\Paragraph($xmlWriter, $temp1); + $temp2->write(); + } + $xmlWriter->startElement('style:style'); $xmlWriter->writeAttribute('style:name', $style->getStyleName()); $xmlWriter->writeAttribute('style:family', 'text'); @@ -53,7 +61,7 @@ class Font extends AbstractStyle // Color $color = $style->getColor(); - $xmlWriter->writeAttributeIf($color != '', 'fo:color', '#' . $color); + $xmlWriter->writeAttributeIf($color != '', 'fo:color', '#' . \PhpOffice\PhpWord\Shared\Converter::stringToRgb($color)); // Bold & italic $xmlWriter->writeAttributeIf($style->isBold(), 'fo:font-weight', 'bold'); @@ -82,6 +90,15 @@ class Font extends AbstractStyle $xmlWriter->writeAttributeIf($style->isSuperScript(), 'style:text-position', 'super'); $xmlWriter->writeAttributeIf($style->isSubScript(), 'style:text-position', 'sub'); + if ($style->isNoProof()) { + $xmlWriter->writeAttribute('fo:language', 'zxx'); + $xmlWriter->writeAttribute('style:language-asian', 'zxx'); + $xmlWriter->writeAttribute('style:language-complex', 'zxx'); + $xmlWriter->writeAttribute('fo:country', 'none'); + $xmlWriter->writeAttribute('style:country-asian', 'none'); + $xmlWriter->writeAttribute('style:country-complex', 'none'); + } + // @todo Foreground-Color // @todo Background color diff --git a/src/PhpWord/Writer/ODText/Style/Paragraph.php b/src/PhpWord/Writer/ODText/Style/Paragraph.php index f247dcc1..555a4825 100644 --- a/src/PhpWord/Writer/ODText/Style/Paragraph.php +++ b/src/PhpWord/Writer/ODText/Style/Paragraph.php @@ -17,6 +17,8 @@ namespace PhpOffice\PhpWord\Writer\ODText\Style; +use PhpOffice\PhpWord\Shared\Converter; + /** * Font style writer * @@ -35,31 +37,120 @@ class Paragraph extends AbstractStyle } $xmlWriter = $this->getXmlWriter(); - $marginTop = (is_null($style->getSpaceBefore()) || $style->getSpaceBefore() == 0) ? '0' : round(17.6 / $style->getSpaceBefore(), 2); - $marginBottom = (is_null($style->getSpaceAfter()) || $style->getSpaceAfter() == 0) ? '0' : round(17.6 / $style->getSpaceAfter(), 2); + $marginTop = $style->getSpaceBefore(); + $marginBottom = $style->getSpaceAfter(); $xmlWriter->startElement('style:style'); + + $styleName = $style->getStyleName(); + $styleAuto = false; + $mpm = ''; + $psm = ''; + $pagestart = -1; + $breakafter = $breakbefore = $breakauto = false; + if ($style->isAuto()) { + if (substr($styleName, 0, 2) === 'PB') { + $styleAuto = true; + $breakafter = true; + } elseif (substr($styleName, 0, 2) === 'SB') { + $styleAuto = true; + $mpm = 'Standard' . substr($styleName, 2); + $psn = $style->getNumLevel(); + if (is_numeric($psn)) { + $pagestart = (int) $psn; + } + } elseif (substr($styleName, 0, 2) === 'HD') { + $styleAuto = true; + $psm = 'Heading_' . substr($styleName, 2); + $stylep = \PhpOffice\PhpWord\Style::getStyle($psm); + if ($stylep instanceof \PhpOffice\PhpWord\Style\Font) { + if (method_exists($stylep, 'getParagraph')) { + $stylep = $stylep->getParagraph(); + } + } + if ($stylep instanceof \PhpOffice\PhpWord\Style\Paragraph) { + if ($stylep->hasPageBreakBefore()) { + $breakbefore = true; + } + } + } elseif (substr($styleName, 0, 2) === 'HE') { + $styleAuto = true; + $psm = 'Heading_' . substr($styleName, 2); + $breakauto = true; + } else { + $styleAuto = true; + $psm = 'Normal'; + if (preg_match('/^P\\d+_(\\w+)$/', $styleName, $matches)) { + $psm = $matches[1]; + } + } + } + $xmlWriter->writeAttribute('style:name', $style->getStyleName()); $xmlWriter->writeAttribute('style:family', 'paragraph'); - if ($style->isAuto()) { - $xmlWriter->writeAttribute('style:parent-style-name', 'Standard'); - $xmlWriter->writeAttribute('style:master-page-name', 'Standard'); + if ($styleAuto) { + $xmlWriter->writeAttributeIf($psm !== '', 'style:parent-style-name', $psm); + $xmlWriter->writeAttributeIf($mpm !== '', 'style:master-page-name', $mpm); } $xmlWriter->startElement('style:paragraph-properties'); - if ($style->isAuto()) { - $xmlWriter->writeAttribute('style:page-number', 'auto'); - } else { - $xmlWriter->writeAttribute('fo:margin-top', $marginTop . 'cm'); - $xmlWriter->writeAttribute('fo:margin-bottom', $marginBottom . 'cm'); - $xmlWriter->writeAttribute('fo:text-align', $style->getAlignment()); + if ($styleAuto) { + if ($breakafter) { + $xmlWriter->writeAttribute('fo:break-after', 'page'); + $xmlWriter->writeAttribute('fo:margin-top', '0cm'); + $xmlWriter->writeAttribute('fo:margin-bottom', '0cm'); + } elseif ($breakbefore) { + $xmlWriter->writeAttribute('fo:break-before', 'page'); + } elseif ($breakauto) { + $xmlWriter->writeAttribute('fo:break-before', 'auto'); + } + if ($pagestart > 0) { + $xmlWriter->writeAttribute('style:page-number', $pagestart); + } + } + if (!$breakafter && !$breakbefore && !$breakauto) { + $twipToPoint = Converter::INCH_TO_TWIP / Converter::INCH_TO_POINT; // 20 + $xmlWriter->writeAttributeIf($marginTop !== null, 'fo:margin-top', ($marginTop / $twipToPoint) . 'pt'); + $xmlWriter->writeAttributeIf($marginBottom !== null, 'fo:margin-bottom', ($marginBottom / $twipToPoint) . 'pt'); + } + $temp = $style->getAlignment(); + $xmlWriter->writeAttributeIf($temp !== '', 'fo:text-align', $temp); + $temp = $style->getLineHeight(); + $xmlWriter->writeAttributeIf($temp !== null, 'fo:line-height', ((string) ($temp * 100) . '%')); + $xmlWriter->writeAttributeIf($style->getPageBreakBefore() === true, 'fo:break-before', 'page'); + + $tabs = $style->getTabs(); + if ($tabs !== null && count($tabs) > 0) { + $xmlWriter->startElement('style:tab-stops'); + foreach ($tabs as $tab) { + $xmlWriter->startElement('style:tab-stop'); + $xmlWriter->writeAttribute('style:type', $tab->getType()); + $xmlWriter->writeAttribute('style:position', (string) ($tab->getPosition() / Converter::INCH_TO_TWIP) . 'in'); + $xmlWriter->endElement(); + } + $xmlWriter->endElement(); } //Right to left $xmlWriter->writeAttributeIf($style->isBidi(), 'style:writing-mode', 'rl-tb'); + //Indentation + $indent = $style->getIndentation(); + if ($indent instanceof \PhpOffice\PhpWord\Style\Indentation) { + $marg = $indent->getLeft(); + $xmlWriter->writeAttributeIf($marg !== null, 'fo:margin-left', (string) ($marg / Converter::INCH_TO_TWIP) . 'in'); + $marg = $indent->getRight(); + $xmlWriter->writeAttributeIf($marg !== null, 'fo:margin-right', (string) ($marg / Converter::INCH_TO_TWIP) . 'in'); + } + $xmlWriter->endElement(); //style:paragraph-properties + if ($styleAuto && substr($styleName, 0, 2) === 'SB') { + $xmlWriter->startElement('style:text-properties'); + $xmlWriter->writeAttribute('text:display', 'none'); + $xmlWriter->endElement(); + } + $xmlWriter->endElement(); //style:style } } diff --git a/tests/PhpWord/Writer/ODText/Element/ImageTest.php b/tests/PhpWord/Writer/ODText/Element/ImageTest.php new file mode 100644 index 00000000..bc861104 --- /dev/null +++ b/tests/PhpWord/Writer/ODText/Element/ImageTest.php @@ -0,0 +1,66 @@ +addSection(); + $section->addImage(__DIR__ . '/../../../_files/images/earth.jpg'); + $section->addImage(__DIR__ . '/../../../_files/images/mario.gif', array('align' => 'end')); + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $element = "$s2a/style:style[3]"; + $this->assertEquals('IM1', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:text-align')); + $element = "$s2a/style:style[4]"; + $this->assertEquals('IM2', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + $this->assertEquals('end', $doc->getElementAttribute($element, 'fo:text-align')); + + $path = '/office:document-content/office:body/office:text/text:section/text:p[2]'; + $this->assertTrue($doc->elementExists($path)); + $this->assertEquals('IM1', $doc->getElementAttribute($path, 'text:style-name')); + $path = '/office:document-content/office:body/office:text/text:section/text:p[3]'; + $this->assertTrue($doc->elementExists($path)); + $this->assertEquals('IM2', $doc->getElementAttribute($path, 'text:style-name')); + } +} diff --git a/tests/PhpWord/Writer/ODText/ElementTest.php b/tests/PhpWord/Writer/ODText/ElementTest.php index 37f0d1ef..500ee247 100644 --- a/tests/PhpWord/Writer/ODText/ElementTest.php +++ b/tests/PhpWord/Writer/ODText/ElementTest.php @@ -26,6 +26,14 @@ use PhpOffice\PhpWord\TestHelperDOCX; */ class ElementTest extends \PHPUnit\Framework\TestCase { + /** + * Executed after each method of the class + */ + public function tearDown() + { + TestHelperDOCX::clear(); + } + /** * Test unmatched elements */ @@ -39,10 +47,168 @@ class ElementTest extends \PHPUnit\Framework\TestCase $object = new $objectClass($xmlWriter, $newElement); $object->write(); - $this->assertEquals('', $xmlWriter->getData()); + self::assertEquals('', $xmlWriter->getData()); } } + // ODT Line Element not yet implemented + // ODT Bookmark not yet implemented + // ODT Table with style name not yet implemented (Word test defective) + // ODT Shape Elements not yet implemented + // ODT Chart Elements not yet implemented + // ODT adding Field to Section not yet implemented + // ODT List not yet implemented + // ODT Macro Button not yet implemented + // ODT Form Field not yet implemented + // ODT SDT not yet implemented + // ODT Comment not yet implemented + // ODT Track Changes implemented, possibly not correctly + // ODT List Item not yet implemented + + /** + * Test link element + */ + public function testLinkElement() + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $extlink = 'https://github.com/PHPOffice/PHPWord'; + $section->addLink($extlink); + $intlink = 'internal_link'; + $section->addLink($intlink, null, null, null, true); + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $p2t = '/office:document-content/office:body/office:text/text:section'; + $element = "$p2t/text:p[2]/text:a"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals($extlink, $doc->getElementAttribute($element, 'xlink:href')); + + $element = "$p2t/text:p[3]/text:a"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals("#$intlink", $doc->getElementAttribute($element, 'xlink:href')); + } + + /** + * Basic test for table element + */ + public function testTableElements() + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $table = $section->addTable(array('alignment' => \PhpOffice\PhpWord\SimpleType\JcTable::CENTER)); + $table->addRow(900); + $table->addCell(2000)->addText('Row 1'); + $table->addCell(2000)->addText('Row 2'); + $table->addCell(2000)->addText('Row 3'); + $table->addCell(2000)->addText('Row 4'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $p2s = '/office:document-content/office:automatic-styles'; + $tableStyleNum = 1; + $tableStyleName = ''; + while ($tableStyleName === '') { + $element = "$p2s/style:style[$tableStyleNum]"; + if (!$doc->elementExists($element)) { + break; + } + if ($doc->getElementAttribute($element, 'style:family') === 'table') { + $tableStyleName = $doc->getElementAttribute($element, 'style:name'); + break; + } + ++$tableStyleNum; + } + self::AssertNotEquals('', $tableStyleName); + $element = "$element/style:table-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals(\PhpOffice\PhpWord\SimpleType\JcTable::CENTER, $doc->getElementAttribute($element, 'table:align')); + $p2t = '/office:document-content/office:body/office:text/text:section'; + $tableRootElement = "$p2t/table:table"; + self::assertTrue($doc->elementExists($tableRootElement)); + self::assertEquals($tableStyleName, $doc->getElementAttribute($tableRootElement, 'table:style')); + self::assertTrue($doc->elementExists($tableRootElement . '/table:table-column[4]')); + } + + /** + * Test Title and Headings + */ + public function testTitleAndHeading() + { + $phpWord = new PhpWord(); + $phpWord->addTitleStyle(0, array('size' => 14, 'italic' => true)); + $phpWord->addTitleStyle(1, array('size' => 20, 'color' => '333333', 'bold' => true)); + + $section = $phpWord->addSection(); + $section->addTitle('This is a title', 0); + $section->addTitle('Heading 1', 1); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $p2t = '/office:document-content/office:body/office:text/text:section'; + $element = "$p2t/text:h[1]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('HE0', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertEquals('0', $doc->getElementAttribute($element, 'text:outline-level')); + $span = "$element/text:span"; + $this->assertTrue($doc->elementExists($span)); + $this->assertEquals('This is a title', $doc->getElement($span)->textContent); + $this->assertEquals('Title', $doc->getElementAttribute($span, 'text:style-name')); + + $element = "$p2t/text:h[2]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('HD1', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertEquals('1', $doc->getElementAttribute($element, 'text:outline-level')); + $span = "$element/text:span"; + $this->assertTrue($doc->elementExists($span)); + $this->assertEquals('Heading 1', $doc->getElement($span)->textContent); + $this->assertEquals('Heading_1', $doc->getElementAttribute($span, 'text:style-name')); + + $doc->setDefaultFile('styles.xml'); + $element = '/office:document-styles/office:styles/style:style[1]'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('Title', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:text-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('14pt', $doc->getElementAttribute($element, 'fo:font-size')); + $this->assertEquals('italic', $doc->getElementAttribute($element, 'fo:font-style')); + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:font-weight')); + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:color')); + + $element = '/office:document-styles/office:styles/style:style[2]'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('Heading_1', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:text-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('20pt', $doc->getElementAttribute($element, 'fo:font-size')); + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:font-style')); + $this->assertEquals('bold', $doc->getElementAttribute($element, 'fo:font-weight')); + $this->assertEquals('#333333', $doc->getElementAttribute($element, 'fo:color')); + } + + /** + * Test correct writing of text with ampersand in it + */ + public function testTextWithAmpersand() + { + $esc = \PhpOffice\PhpWord\Settings::isOutputEscapingEnabled(); + \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $txt = 'this text contains an & (ampersand)'; + $section->addText($txt); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled($esc); + $p2t = '/office:document-content/office:body/office:text/text:section'; + $element = "$p2t/text:p[2]"; + $this->assertTrue($doc->elementExists($element)); + $span = "$element/text:span"; + $this->assertTrue($doc->elementExists($span)); + $this->assertEquals($txt, $doc->getElement($span)->nodeValue); + } + /** * Test PageBreak */ @@ -55,8 +221,8 @@ class ElementTest extends \PHPUnit\Framework\TestCase $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); - $element = '/office:document-content/office:body/office:text/text:section/text:p[2]'; - $this->assertTrue($doc->elementExists($element, 'content.xml')); - $this->assertEquals('P1', $doc->getElementAttribute($element, 'text:style-name', 'content.xml')); + $element = '/office:document-content/office:body/office:text/text:section/text:p[3]'; + self::assertTrue($doc->elementExists($element, 'content.xml')); + self::assertEquals('PB', $doc->getElementAttribute($element, 'text:style-name', 'content.xml')); } } diff --git a/tests/PhpWord/Writer/ODText/Part/ContentTest.php b/tests/PhpWord/Writer/ODText/Part/ContentTest.php index 2e501c60..34eb8068 100644 --- a/tests/PhpWord/Writer/ODText/Part/ContentTest.php +++ b/tests/PhpWord/Writer/ODText/Part/ContentTest.php @@ -92,7 +92,7 @@ class ContentTest extends \PHPUnit\Framework\TestCase $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); - $element = '/office:document-content/office:body/office:text/text:section/text:p'; + $element = '/office:document-content/office:body/office:text/text:section/text:p[2]'; $this->assertEquals($expected, $doc->getElement($element, 'content.xml')->nodeValue); } diff --git a/tests/PhpWord/Writer/ODText/Style/FontTest.php b/tests/PhpWord/Writer/ODText/Style/FontTest.php new file mode 100644 index 00000000..5306c6b0 --- /dev/null +++ b/tests/PhpWord/Writer/ODText/Style/FontTest.php @@ -0,0 +1,131 @@ +addSection(); + $section->addText('This is red (800) in rtf/html, default in docx/odt', array('color' => '800')); + $section->addText('This should be cyanish (008787)', array('color' => '008787')); + $section->addText('This should be dark green (FGCOLOR_DARKGREEN)', array('color' => \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKGREEN)); + $section->addText('This color is default (unknow)', array('color' => 'unknow')); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $this->assertTrue($doc->elementExists($s2a)); + $s2t = '/office:document-content/office:body/office:text/text:section'; + $this->assertTrue($doc->elementExists($s2t)); + + $element = "$s2a/style:style[5]"; + $this->assertTrue($doc->elementExists($element)); + $style = $doc->getElementAttribute($element, 'style:name'); + $element .= '/style:text-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('#008787', $doc->getElementAttribute($element, 'fo:color')); + $span = "$s2t/text:p[3]/text:span"; + $this->assertTrue($doc->elementExists($span)); + $this->assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); + $this->assertEquals('This should be cyanish (008787)', $doc->getElement($span)->nodeValue); + + $element = "$s2a/style:style[7]"; + $this->assertTrue($doc->elementExists($element)); + $style = $doc->getElementAttribute($element, 'style:name'); + $element .= '/style:text-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('#006400', $doc->getElementAttribute($element, 'fo:color')); + $span = "$s2t/text:p[4]/text:span"; + $this->assertTrue($doc->elementExists($span)); + $this->assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); + $this->assertEquals('This should be dark green (FGCOLOR_DARKGREEN)', $doc->getElement($span)->nodeValue); + } + + /** + * Test noproof + */ + public function testNoProof() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Noproof not specified', array('color' => 'black')); + $section->addText('Noproof is true', array('color' => 'black', 'noproof' => true)); + $section->addText('Noproof is false', array('color' => 'black', 'noproof' => false)); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $this->assertTrue($doc->elementExists($s2a)); + $s2t = '/office:document-content/office:body/office:text/text:section'; + $this->assertTrue($doc->elementExists($s2t)); + + $element = "$s2a/style:style[3]"; + $this->assertTrue($doc->elementExists($element)); + $style = $doc->getElementAttribute($element, 'style:name'); + $element .= '/style:text-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:language')); + $span = "$s2t/text:p[2]/text:span"; + $this->assertTrue($doc->elementExists($span)); + $this->assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); + $this->assertEquals('Noproof not specified', $doc->getElement($span)->nodeValue); + + $element = "$s2a/style:style[5]"; + $this->assertTrue($doc->elementExists($element)); + $style = $doc->getElementAttribute($element, 'style:name'); + $element .= '/style:text-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('zxx', $doc->getElementAttribute($element, 'fo:language')); + $this->assertEquals('zxx', $doc->getElementAttribute($element, 'style:language-asian')); + $this->assertEquals('zxx', $doc->getElementAttribute($element, 'style:language-complex')); + $this->assertEquals('none', $doc->getElementAttribute($element, 'fo:country')); + $this->assertEquals('none', $doc->getElementAttribute($element, 'style:country-asian')); + $this->assertEquals('none', $doc->getElementAttribute($element, 'style:country-complex')); + $span = "$s2t/text:p[3]/text:span"; + $this->assertTrue($doc->elementExists($span)); + $this->assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); + $this->assertEquals('Noproof is true', $doc->getElement($span)->nodeValue); + + $element = "$s2a/style:style[7]"; + $this->assertTrue($doc->elementExists($element)); + $style = $doc->getElementAttribute($element, 'style:name'); + $element .= '/style:text-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:language')); + $span = "$s2t/text:p[4]/text:span"; + $this->assertTrue($doc->elementExists($span)); + $this->assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); + $this->assertEquals('Noproof is false', $doc->getElement($span)->nodeValue); + } +} diff --git a/tests/PhpWord/Writer/ODText/Style/ParagraphTest.php b/tests/PhpWord/Writer/ODText/Style/ParagraphTest.php new file mode 100644 index 00000000..0e9948cf --- /dev/null +++ b/tests/PhpWord/Writer/ODText/Style/ParagraphTest.php @@ -0,0 +1,434 @@ +addSection(); + $section->addText('Text on first page'); + $section->addPageBreak(); + $section->addText('Text on second page'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $s2a = '/office:document-content/office:automatic-styles'; + $element = "$s2a/style:style[1]"; + $this->assertEquals('PB', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + $this->assertEquals('page', $doc->getElementAttribute($element, 'fo:break-after')); + $this->assertEquals('0cm', $doc->getElementAttribute($element, 'fo:margin-top')); + $this->assertEquals('0cm', $doc->getElementAttribute($element, 'fo:margin-bottom')); + + $s2a = '/office:document-content/office:body/office:text/text:section'; + $element = "$s2a/text:p[3]"; + $this->assertEquals('PB', $doc->getElementAttribute($element, 'text:style-name')); + } + + /** + * Test normal/indent + */ + public function testNormalIndent() + { + $phpWord = new PhpWord(); + $cvt = Converter::INCH_TO_TWIP; + $indent1 = array('indentation' => array('left' => 0.50 * $cvt)); + $indent2 = array('indentation' => array('left' => 1.00 * $cvt, 'right' => 1.05 * $cvt)); + $indent3 = array('indentation' => array('left' => -0.50 * $cvt)); + $indent4 = array('indentation' => array('left' => 0 * $cvt)); + $phpWord->setDefaultParagraphStyle($indent1); + $section = $phpWord->addSection(); + $section->addText('Should use default indent (0.5)'); + $section->addText('Should use non-default indent (1.0) on both sides, and here\'s an extra long line to prove it', null, $indent2); + $section->addText('Should use non-default indent (-0.5)', null, $indent3); + $section->addText('Should use non-default indent (0)', null, $indent4); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $this->assertTrue($doc->elementExists($s2a)); + + $element = "$s2a/style:style[4]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('Normal', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:margin-left')); + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:margin-right')); + + $element = "$s2a/style:style[6]/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('1in', $doc->getElementAttribute($element, 'fo:margin-left')); + $this->assertEquals('1.05in', $doc->getElementAttribute($element, 'fo:margin-right')); + + $element = "$s2a/style:style[8]/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('-0.5in', $doc->getElementAttribute($element, 'fo:margin-left')); + $this->assertEquals('0in', $doc->getElementAttribute($element, 'fo:margin-right')); + + $element = "$s2a/style:style[10]/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('0in', $doc->getElementAttribute($element, 'fo:margin-left')); + $this->assertEquals('0in', $doc->getElementAttribute($element, 'fo:margin-right')); + + $doc->setDefaultFile('styles.xml'); + $element = '/office:document-styles/office:styles/style:style'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('Normal', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('0.5in', $doc->getElementAttribute($element, 'fo:margin-left')); + $this->assertEquals('0in', $doc->getElementAttribute($element, 'fo:margin-right')); + } + + /** + * Test textAlign + */ + public function testTextAlign() + { + $phpWord = new PhpWord(); + $align1 = array('alignment' => 'end'); + $align2 = array('alignment' => 'start'); + $phpWord->setDefaultParagraphStyle($align1); + $section = $phpWord->addSection(); + $section->addText('Should use default alignment (right for this doc)'); + $section->addText('Explicit left alignment', null, $align2); + $section->addText('Explicit right alignment', null, $align1); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $this->assertTrue($doc->elementExists($s2a)); + + $element = "$s2a/style:style[4]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('Normal', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:text-align')); + + $element = "$s2a/style:style[6]/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('start', $doc->getElementAttribute($element, 'fo:text-align')); + + $element = "$s2a/style:style[8]/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('end', $doc->getElementAttribute($element, 'fo:text-align')); + + $doc->setDefaultFile('styles.xml'); + $element = '/office:document-styles/office:styles/style:style'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('Normal', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('end', $doc->getElementAttribute($element, 'fo:text-align')); + } + + /** + * Test lineHeight + */ + public function testLineHeight() + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Should use line height 1.08, and here\'s a long line which ought to overflow onto a second line to prove it', null, array('lineHeight' => 1.08)); + $section->addText('Should use line height 1.20, and here\'s a long line which ought to overflow onto a second line to prove it', null, array('lineHeight' => 1.20)); + $section->addText('Should use line height 0.90, and here\'s a long line which ought to overflow onto a second line to prove it', null, array('lineHeight' => 0.90)); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $this->assertTrue($doc->elementExists($s2a)); + + $element = "$s2a/style:style[4]/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('108%', $doc->getElementAttribute($element, 'fo:line-height')); + + $element = "$s2a/style:style[6]/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('120%', $doc->getElementAttribute($element, 'fo:line-height')); + + $element = "$s2a/style:style[8]/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('90%', $doc->getElementAttribute($element, 'fo:line-height')); + } + + /** + * Test SpaceBeforeAfter + */ + public function testSpaceBeforeAfter() + { + $phpWord = new PhpWord(); + $phpWord->setDefaultParagraphStyle(array('spaceBefore' => 0, 'spaceAfter' => 0)); + $section = $phpWord->addSection(); + $section->addText('No spacing between this paragraph and next'); + $section->addText('No spacing between this paragraph and previous'); + $section->addText('No spacing before this but 100 after', null, array('spaceAfter' => 100)); + $section->addText('No spacing for this paragraph but previous specified 100 after and next specifies 100 before'); + $section->addText('No spacing after this but 100 before', null, array('spaceBefore' => 100)); + $section->addText('No spacing before this paragraph'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $this->assertTrue($doc->elementExists($s2a)); + + $element = "$s2a/style:style[8]/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:margin-top')); + $this->assertEquals('5pt', $doc->getElementAttribute($element, 'fo:margin-bottom')); + + $element = "$s2a/style:style[12]/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('5pt', $doc->getElementAttribute($element, 'fo:margin-top')); + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:margin-bottom')); + + $doc->setDefaultFile('styles.xml'); + $element = '/office:document-styles/office:styles/style:style'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('Normal', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('0pt', $doc->getElementAttribute($element, 'fo:margin-top')); + $this->assertEquals('0pt', $doc->getElementAttribute($element, 'fo:margin-bottom')); + } + + /** + * Test Page Break Before + */ + public function testPageBreakBefore() + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('This is my first paragraph.'); + $section->addText('This is my second paragraph, on a new page.', null, array('pageBreakBefore' => true)); + $section->addText('This is my third paragraph, on same page as second.'); + $section->addText('This is my fourth paragraph, on a new page.', null, array('pageBreakBefore' => true)); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $this->assertTrue($doc->elementExists($s2a)); + + $element = "$s2a/style:style[4]/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:break-before')); + $element = "$s2a/style:style[6]/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('page', $doc->getElementAttribute($element, 'fo:break-before')); + $element = "$s2a/style:style[8]/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:break-before')); + $element = "$s2a/style:style[10]/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('page', $doc->getElementAttribute($element, 'fo:break-before')); + } + + /** + * Test Heading Page Break Before + */ + public function testHeadingPageBreakBefore() + { + $phpWord = new PhpWord(); + $phpWord->addTitleStyle(1, null, array('pageBreakBefore' => true)); + $phpWord->addTitleStyle(2, null, array()); + $section = $phpWord->addSection(); + $section->addTitle('Section1 Heading1 #1', 1); + $section->addTitle('Section1 Heading2 #1', 2); + $section->addTitle('Section1 Heading1 #2', 1); + $section->addTitle('Section1 Heading2 #2', 2); + $section = $phpWord->addSection(); + $section->addTitle('Section2 Heading1 #1', 1); + $section->addTitle('Section2 Heading2 #1', 2); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $this->assertTrue($doc->elementExists($s2a)); + + $element = "$s2a/style:style[4]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('HD1', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('Heading_1', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('page', $doc->getElementAttribute($element, 'fo:break-before')); + + $element = "$s2a/style:style[5]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('HE1', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('Heading_1', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('auto', $doc->getElementAttribute($element, 'fo:break-before')); + + $element = "$s2a/style:style[6]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('HD2', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('Heading_2', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:break-before')); + + $element = "$s2a/style:style[7]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('HE2', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('Heading_2', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('auto', $doc->getElementAttribute($element, 'fo:break-before')); + + $s2a = '/office:document-content/office:body/office:text/text:section[1]'; + $this->assertTrue($doc->elementExists($s2a)); + $element = "$s2a/text:h[1]"; + $this->assertEquals('HE1', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertEquals('1', $doc->getElementAttribute($element, 'text:outline-level')); + $element .= '/text:span'; + $this->assertEquals('Heading_1', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:h[2]"; + $this->assertEquals('HD2', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertEquals('2', $doc->getElementAttribute($element, 'text:outline-level')); + $element .= '/text:span'; + $this->assertEquals('Heading_2', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:h[3]"; + $this->assertEquals('HD1', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertEquals('1', $doc->getElementAttribute($element, 'text:outline-level')); + $element .= '/text:span'; + $this->assertEquals('Heading_1', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:h[4]"; + $this->assertEquals('HD2', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertEquals('2', $doc->getElementAttribute($element, 'text:outline-level')); + $element .= '/text:span'; + $this->assertEquals('Heading_2', $doc->getElementAttribute($element, 'text:style-name')); + + $s2a = '/office:document-content/office:body/office:text/text:section[2]'; + $this->assertTrue($doc->elementExists($s2a)); + $element = "$s2a/text:h[1]"; + $this->assertEquals('HE1', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertEquals('1', $doc->getElementAttribute($element, 'text:outline-level')); + $element .= '/text:span'; + $this->assertEquals('Heading_1', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:h[2]"; + $this->assertEquals('HD2', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertEquals('2', $doc->getElementAttribute($element, 'text:outline-level')); + $element .= '/text:span'; + $this->assertEquals('Heading_2', $doc->getElementAttribute($element, 'text:style-name')); + + $doc->setDefaultFile('styles.xml'); + $s2a = '/office:document-styles/office:styles'; + $this->assertTrue($doc->elementExists($s2a)); + $element = "$s2a/style:style[1]"; + $this->assertEquals('Heading_1', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('paragraph', $doc->getElementAttribute($element, 'style:family')); + $element .= '/style:paragraph-properties'; + $this->assertEquals('page', $doc->getElementAttribute($element, 'fo:break-before')); + $element = "$s2a/style:style[3]"; + $this->assertEquals('Heading_2', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('paragraph', $doc->getElementAttribute($element, 'style:family')); + $element .= '/style:paragraph-properties'; + $this->assertEquals('', $doc->getElementAttribute($element, 'fo:break-before')); + } + + /** + * Test text run paragraph style using named style + */ + public function testTextRun() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $phpWord->addParagraphStyle('parstyle1', array('align' => 'start')); + $phpWord->addParagraphStyle('parstyle2', array('align' => 'end')); + $section = $phpWord->addSection(); + $trx = $section->addTextRun('parstyle1'); + $trx->addText('First text in textrun. '); + $trx->addText('Second text - paragraph style is specified but ignored.', null, 'parstyle2'); + $section->addText('Third text added to section not textrun - paragraph style is specified and used.', null, 'parstyle2'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $element = "$s2a/style:style[3]"; + $this->assertEquals('P1_parstyle1', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('parstyle1', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element = "$s2a/style:style[9]"; + $this->assertEquals('P4_parstyle2', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('parstyle2', $doc->getElementAttribute($element, 'style:parent-style-name')); + + $s2a = '/office:document-content/office:body/office:text/text:section'; + $element = "$s2a/text:p[2]"; + $this->assertEquals('P1_parstyle1', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:p[3]"; + $this->assertEquals('P4_parstyle2', $doc->getElementAttribute($element, 'text:style-name')); + + $doc->setDefaultFile('styles.xml'); + $element = '/office:document-styles/office:styles/style:style[1]'; + $this->assertEquals('parstyle1', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + $this->assertEquals('start', $doc->getElementAttribute($element, 'fo:text-align')); + $element = '/office:document-styles/office:styles/style:style[2]'; + $this->assertEquals('parstyle2', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + $this->assertEquals('end', $doc->getElementAttribute($element, 'fo:text-align')); + } + + /** + * Test text run paragraph style using unnamed style + */ + public function testTextRunUnnamed() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $parstyle1 = array('align' => 'start'); + $parstyle2 = array('align' => 'end'); + $section = $phpWord->addSection(); + $trx = $section->addTextRun($parstyle1); + $trx->addText('First text in textrun. '); + $trx->addText('Second text - paragraph style is specified but ignored.', null, $parstyle2); + $section->addText('Third text added to section not textrun - paragraph style is specified and used.', null, $parstyle2); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $element = "$s2a/style:style[3]"; + $this->assertEquals('P1', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('Normal', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + $this->assertEquals('start', $doc->getElementAttribute($element, 'fo:text-align')); + $element = "$s2a/style:style[9]"; + $this->assertEquals('P4', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('Normal', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + $this->assertEquals('end', $doc->getElementAttribute($element, 'fo:text-align')); + + $s2a = '/office:document-content/office:body/office:text/text:section'; + $element = "$s2a/text:p[2]"; + $this->assertEquals('P1', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:p[3]"; + $this->assertEquals('P4', $doc->getElementAttribute($element, 'text:style-name')); + } +} diff --git a/tests/PhpWord/Writer/ODText/Style/SectionTest.php b/tests/PhpWord/Writer/ODText/Style/SectionTest.php new file mode 100644 index 00000000..d471c7f0 --- /dev/null +++ b/tests/PhpWord/Writer/ODText/Style/SectionTest.php @@ -0,0 +1,249 @@ +addFontStyle('hdrstyle1', array('name' => 'Courier New', 'size' => 8)); + $section = $phpWord->addSection(array('paperSize' => 'Letter', 'marginTop' => $margins, 'marginBottom' => $margins)); + $header = $section->createHeader(); + $phpWord->addParagraphStyle('centerheader', array('align' => 'center')); + $header->addText('Centered Header', 'hdrstyle1', 'centerheader'); + $footer = $section->createFooter(); + $sizew = $section->getStyle()->getPageSizeW(); + $sizel = $section->getStyle()->getMarginLeft(); + $sizer = $section->getStyle()->getMarginRight(); + $footerwidth = $sizew - $sizel - $sizer; + $phpWord->addParagraphStyle( + 'footerTab', + array( + 'tabs' => array( + new \PhpOffice\PhpWord\Style\Tab('center', (int) ($footerwidth / 2)), + new \PhpOffice\PhpWord\Style\Tab('right', (int) $footerwidth), + ), + ) + ); + $textrun = $footer->addTextRun('footerTab'); + $textrun->addText('Left footer', 'hdrstyle1'); + $textrun->addText("\t", 'hdrstyle1'); + $fld = $textrun->addField('DATE'); + $fld->setFontStyle('hdrstyle1'); + $textrun->addText("\t", 'hdrstyle1'); + $textrun->addText('Page ', 'hdrstyle1'); + $fld = $textrun->addField('PAGE'); + $fld->setFontStyle('hdrstyle1'); + $textrun->addText(' of ', 'hdrstyle1'); + $fld = $textrun->addField('NUMPAGES'); + $fld->setFontStyle('hdrstyle1'); + $section->addText('First page'); + $section->addPageBreak(); + $section->addText('Second page'); + $section->addPageBreak(); + $section->addText('Third page'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $doc->setDefaultFile('styles.xml'); + $s2a = '/office:document-styles/office:automatic-styles'; + $element = "$s2a/style:page-layout/style:page-layout-properties"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('8.5in', $doc->getElementAttribute($element, 'fo:page-width')); + $this->assertEquals('11in', $doc->getElementAttribute($element, 'fo:page-height')); + $this->assertEquals('0.5in', $doc->getElementAttribute($element, 'fo:margin-top')); + $this->assertEquals('0.5in', $doc->getElementAttribute($element, 'fo:margin-bottom')); + + $s2s = '/office:document-styles/office:styles'; + $element = "$s2s/style:style[1]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('hdrstyle1', $doc->getElementAttribute($element, 'style:name')); + $tprop = "$element/style:text-properties"; + $this->assertTrue($doc->elementExists($tprop)); + $this->assertEquals('Courier New', $doc->getElementAttribute($tprop, 'style:font-name')); + + $element = "$s2s/style:style[2]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('centerheader', $doc->getElementAttribute($element, 'style:name')); + $tprop = "$element/style:paragraph-properties"; + $this->assertTrue($doc->elementExists($tprop)); + $this->assertEquals('center', $doc->getElementAttribute($tprop, 'fo:text-align')); + + $element = "$s2s/style:style[3]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('footerTab', $doc->getElementAttribute($element, 'style:name')); + $tprop = "$element/style:paragraph-properties/style:tab-stops"; + $this->assertTrue($doc->elementExists($tprop)); + $tstop = "$tprop/style:tab-stop[1]"; + $this->assertTrue($doc->elementExists($tstop)); + $this->assertEquals('center', $doc->getElementAttribute($tstop, 'style:type')); + $this->assertEquals('3.25in', $doc->getElementAttribute($tstop, 'style:position')); + $tstop = "$tprop/style:tab-stop[2]"; + $this->assertTrue($doc->elementExists($tstop)); + $this->assertEquals('right', $doc->getElementAttribute($tstop, 'style:type')); + $this->assertEquals('6.5in', $doc->getElementAttribute($tstop, 'style:position')); + + $s2s = '/office:document-styles/office:master-styles/style:master-page/style:footer/text:p'; + $this->assertTrue($doc->elementExists($s2s)); + $element = "$s2s/text:span[1]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('hdrstyle1', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertEquals('Left footer', $doc->getElement($element)->nodeValue); + $element = "$s2s/text:span[2]/text:tab"; + $this->assertTrue($doc->elementExists($element)); + $element = "$s2s/text:span[3]/text:date"; + $this->assertTrue($doc->elementExists($element)); + $element = "$s2s/text:span[4]/text:tab"; + $this->assertTrue($doc->elementExists($element)); + $element = "$s2s/text:span[5]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('Page', $doc->getElement($element)->nodeValue); + $this->assertTrue($doc->elementExists("$element/text:s")); + $element = "$s2s/text:span[6]/text:page-number"; + $this->assertTrue($doc->elementExists($element)); + $element = "$s2s/text:span[7]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('of', $doc->getElement($element)->nodeValue); + $this->assertTrue($doc->elementExists("$element/text:s")); + $this->assertTrue($doc->elementExists("$element/text:s[2]")); + $element = "$s2s/text:span[8]/text:page-count"; + $this->assertTrue($doc->elementExists($element)); + } + + /** + * Test HideErrors + */ + public function testHideErrors() + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setHideGrammaticalErrors(true); + $phpWord->getSettings()->setHideSpellingErrors(true); + $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language('en-US')); + $phpWord->getSettings()->getThemeFontLang()->setLangId(\PhpOffice\PhpWord\Style\Language::EN_US_ID); + $section = $phpWord->addSection(); + $section->addText('Here is a paragraph with some speling errorz'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $doc->setDefaultFile('styles.xml'); + $element = '/office:document-styles/office:styles/style:default-style/style:text-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('zxx', $doc->getElementAttribute($element, 'fo:language')); + $this->assertEquals('zxx', $doc->getElementAttribute($element, 'style:language-asian')); + $this->assertEquals('zxx', $doc->getElementAttribute($element, 'style:language-complex')); + $this->assertEquals('none', $doc->getElementAttribute($element, 'fo:country')); + $this->assertEquals('none', $doc->getElementAttribute($element, 'style:country-asian')); + $this->assertEquals('none', $doc->getElementAttribute($element, 'style:country-complex')); + } + + /** + * Test SpaceBeforeAfter + */ + public function testMultipleSections() + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(array('paperSize' => 'Letter', 'Orientation' => 'portrait')); + $section->addText('This section uses Letter paper in portrait orientation.'); + $section = $phpWord->addSection(array('paperSize' => 'A4', 'Orientation' => 'landscape', 'pageNumberingStart' => '9')); + $header = $section->createHeader(); + $header->addField('PAGE'); + $section->addText('This section uses A4 paper in landscape orientation. It should have a page break beforehand. It artificially starts on page 9.'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $s2t = '/office:document-content/office:body/office:text'; + $this->assertTrue($doc->elementExists($s2a)); + $this->assertTrue($doc->elementExists($s2t)); + + $element = "$s2a/style:style[2]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('SB1', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('Standard1', $doc->getElementAttribute($element, 'style:master-page-name')); + $element .= '/style:text-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('none', $doc->getElementAttribute($element, 'text:display')); + $element = "$s2a/style:style[3]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('SB2', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('Standard2', $doc->getElementAttribute($element, 'style:master-page-name')); + $elemen2 = "$element/style:paragraph-properties"; + $this->assertEquals('9', $doc->getElementAttribute($elemen2, 'style:page-number')); + $element .= '/style:text-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('none', $doc->getElementAttribute($element, 'text:display')); + + $element = "$s2t/text:section[1]"; + $this->assertTrue($doc->elementExists($element)); + $element .= '/text:p[1]'; + $this->assertEquals('SB1', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2t/text:section[2]"; + $this->assertTrue($doc->elementExists($element)); + $element .= '/text:p[1]'; + $this->assertEquals('SB2', $doc->getElementAttribute($element, 'text:style-name')); + + $doc->setDefaultFile('styles.xml'); + $s2a = '/office:document-styles/office:automatic-styles'; + $this->assertTrue($doc->elementExists($s2a)); + + $element = "$s2a/style:page-layout[1]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('Mpm1', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:page-layout-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('8.5in', $doc->getElementAttribute($element, 'fo:page-width')); + $this->assertEquals('11in', $doc->getElementAttribute($element, 'fo:page-height')); + $this->assertEquals('portrait', $doc->getElementAttribute($element, 'style:print-orientation')); + + $element = "$s2a/style:page-layout[2]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('Mpm2', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:page-layout-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('29.7cm', $doc->getElementAttribute($element, 'fo:page-width')); + $this->assertEquals('21cm', $doc->getElementAttribute($element, 'fo:page-height')); + $this->assertEquals('landscape', $doc->getElementAttribute($element, 'style:print-orientation')); + + $s2a = '/office:document-styles/office:master-styles'; + $this->assertTrue($doc->elementExists($s2a)); + $element = "$s2a/style:master-page[1]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('Standard1', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('Mpm1', $doc->getElementAttribute($element, 'style:page-layout-name')); + $element = "$s2a/style:master-page[2]"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('Standard2', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('Mpm2', $doc->getElementAttribute($element, 'style:page-layout-name')); + } +} diff --git a/tests/PhpWord/_includes/TestHelperDOCX.php b/tests/PhpWord/_includes/TestHelperDOCX.php index 02fa7d78..d35f0e3f 100644 --- a/tests/PhpWord/_includes/TestHelperDOCX.php +++ b/tests/PhpWord/_includes/TestHelperDOCX.php @@ -63,7 +63,12 @@ class TestHelperDOCX $zip->close(); } - return new XmlDocument(Settings::getTempDir() . '/PhpWord_Unit_Test/'); + $doc = new XmlDocument(Settings::getTempDir() . '/PhpWord_Unit_Test/'); + if ($writerName === 'ODText') { + $doc->setDefaultFile('content.xml'); + } + + return $doc; } /** diff --git a/tests/PhpWord/_includes/XmlDocument.php b/tests/PhpWord/_includes/XmlDocument.php index 3a7869bc..41a9d9db 100644 --- a/tests/PhpWord/_includes/XmlDocument.php +++ b/tests/PhpWord/_includes/XmlDocument.php @@ -50,6 +50,37 @@ class XmlDocument */ private $file; + /** + * Default file name + * + * @var string + */ + private $defaultFile = 'word/document.xml'; + + /** + * Get default file + * + * @return string + */ + public function getDefaultFile() + { + return $this->defaultFile; + } + + /** + * Set default file + * + * @param string $file + * @return string + */ + public function setDefaultFile($file) + { + $temp = $this->defaultFile; + $this->defaultFile = $file; + + return $temp; + } + /** * Create new instance * @@ -66,8 +97,11 @@ class XmlDocument * @param string $file * @return \DOMDocument */ - public function getFileDom($file = 'word/document.xml') + public function getFileDom($file = '') { + if (!$file) { + $file = $this->defaultFile; + } if (null !== $this->dom && $file === $this->file) { return $this->dom; } @@ -91,8 +125,11 @@ class XmlDocument * @param string $file * @return \DOMNodeList */ - public function getNodeList($path, $file = 'word/document.xml') + public function getNodeList($path, $file = '') { + if (!$file) { + $file = $this->defaultFile; + } if (null === $this->dom || $file !== $this->file) { $this->getFileDom($file); } @@ -112,8 +149,11 @@ class XmlDocument * @param string $file * @return \DOMElement */ - public function getElement($path, $file = 'word/document.xml') + public function getElement($path, $file = '') { + if (!$file) { + $file = $this->defaultFile; + } $elements = $this->getNodeList($path, $file); return $elements->item(0); @@ -147,8 +187,12 @@ class XmlDocument * @param string $file * @return string */ - public function getElementAttribute($path, $attribute, $file = 'word/document.xml') + public function getElementAttribute($path, $attribute, $file = '') { + if (!$file) { + $file = $this->defaultFile; + } + return $this->getElement($path, $file)->getAttribute($attribute); } @@ -159,8 +203,11 @@ class XmlDocument * @param string $file * @return string */ - public function elementExists($path, $file = 'word/document.xml') + public function elementExists($path, $file = '') { + if (!$file) { + $file = $this->defaultFile; + } $nodeList = $this->getNodeList($path, $file); return $nodeList->length != 0; @@ -173,8 +220,11 @@ class XmlDocument * @param string $file * @return string */ - public function printXml($path = '/', $file = 'word/document.xml') + public function printXml($path = '/', $file = '') { + if (!$file) { + $file = $this->defaultFile; + } $element = $this->getElement($path, $file); if ($element instanceof \DOMDocument) { $element->formatOutput = true; From cfa29cc1c2bb746f1413ada91b748e9712bd003b Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Sun, 5 Jan 2020 13:52:20 -0800 Subject: [PATCH 2/7] Applying Scrutinizer Suggestions I do not understand one suggestion, and I believe one is wrong. I will add comments to my ticket once this is pushed. One that I can discuss up front PhpWord/Style/Paragraph indicates that Indentation must be of type \PhpOffice\PhpWord\Style\Indentation, but it can also be null. My test for instanceof ... is one of the Scrutinizer reports. I did not change PhpWord/Style/Paragraph, but this commit does so by updating @var for indentation. --- src/PhpWord/Style/Paragraph.php | 2 +- src/PhpWord/Writer/ODText/Element/Field.php | 4 +- src/PhpWord/Writer/ODText/Part/Content.php | 7 ++-- src/PhpWord/Writer/ODText/Part/Styles.php | 42 +++++++------------ src/PhpWord/Writer/ODText/Style/Paragraph.php | 6 +-- 5 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/PhpWord/Style/Paragraph.php b/src/PhpWord/Style/Paragraph.php index 72f0f809..580ef54a 100644 --- a/src/PhpWord/Style/Paragraph.php +++ b/src/PhpWord/Style/Paragraph.php @@ -85,7 +85,7 @@ class Paragraph extends Border /** * Indentation * - * @var \PhpOffice\PhpWord\Style\Indentation + * @var \PhpOffice\PhpWord\Style\Indentation|null */ private $indentation; diff --git a/src/PhpWord/Writer/ODText/Element/Field.php b/src/PhpWord/Writer/ODText/Element/Field.php index c95139ac..f7a74c1d 100644 --- a/src/PhpWord/Writer/ODText/Element/Field.php +++ b/src/PhpWord/Writer/ODText/Element/Field.php @@ -41,7 +41,7 @@ class Field extends Text $type = strtolower($element->getType()); switch ($type) { - case 'date': // Owen 2020-01-02 + case 'date': case 'page': case 'numpages': $this->writeDefault($element, $type); @@ -61,7 +61,7 @@ class Field extends Text } } switch ($type) { - case 'date': // Owen 2019-01-02 + case 'date': $xmlWriter->startElement('text:date'); $xmlWriter->writeAttribute('text:fixed', 'false'); $xmlWriter->endElement(); diff --git a/src/PhpWord/Writer/ODText/Part/Content.php b/src/PhpWord/Writer/ODText/Part/Content.php index 8eaad40f..ea4c87d2 100644 --- a/src/PhpWord/Writer/ODText/Part/Content.php +++ b/src/PhpWord/Writer/ODText/Part/Content.php @@ -186,7 +186,8 @@ class Content extends AbstractPart $styleWriter->write(); $sects = $this->getParentWriter()->getPhpWord()->getSections(); - for ($i = 0; $i < count($sects); ++$i) { + $countsects = count($sects); + for ($i = 0; $i < $countsects; ++$i) { $iplus1 = $i + 1; $style = new Paragraph(); $style->setStyleName("SB$iplus1"); @@ -297,7 +298,7 @@ class Content extends AbstractPart /** * Get style of individual element * - * @param \PhpOffice\PhpWord\Element\Text $element + * @param \PhpOffice\PhpWord\Element\Text|\PhpOffice\PhpWord\Element\TextRun $element * @param int $paragraphStyleCount * @param int $fontStyleCount */ @@ -331,7 +332,7 @@ class Content extends AbstractPart } else { $element->setParagraphStyle($name); } - } elseif (is_string($paragraphStyle)) { + } else { $paragraphStyleCount++; $parstylename = "P$paragraphStyleCount" . "_$paragraphStyle"; $style = $phpWord->addParagraphStyle($parstylename, $paragraphStyle); diff --git a/src/PhpWord/Writer/ODText/Part/Styles.php b/src/PhpWord/Writer/ODText/Part/Styles.php index 862f4b2a..bcd57ad5 100644 --- a/src/PhpWord/Writer/ODText/Part/Styles.php +++ b/src/PhpWord/Writer/ODText/Part/Styles.php @@ -140,16 +140,12 @@ class Styles extends AbstractPart /** * Convert int in twips to inches/cm then to string and append unit * - * @param int $twips - * @param string $dflt + * @param int|float $twips * @param float $factor * return string */ - private static function cvttwiptostr($twips, $dflt, $factor = 1.0) // Owen 2019-08-06 + private static function cvttwiptostr($twips, $factor = 1.0) { - if ($twips === null) { - return $dflt; - } $ins = (string) ($twips * $factor / Converter::INCH_TO_TWIP) . 'in'; $cms = (string) ($twips * $factor * Converter::INCH_TO_CM / Converter::INCH_TO_TWIP) . 'cm'; @@ -161,10 +157,11 @@ class Styles extends AbstractPart * * @param \PhpOffice\Common\XMLWriter $xmlWriter */ - private function writePageLayout(XMLWriter $xmlWriter) // Owen 2019-06-19 + private function writePageLayout(XMLWriter $xmlWriter) { $sections = $this->getParentWriter()->getPhpWord()->getSections(); - for ($i = 0; $i < count($sections); ++$i) { + $countsects = count($sections); + for ($i = 0; $i < $countsects; ++$i) { $this->writePageLayoutIndiv($xmlWriter, $sections[$i], $i + 1); } } @@ -189,23 +186,13 @@ class Styles extends AbstractPart } else { $botfactor = 1.0; } - $pwidth = '21.001cm'; - $pheight = '29.7cm'; - $orient = 'portrait'; - $mtop = $mleft = $mright = '2.501cm'; - $mbottom = '2cm'; - if ($sty instanceof \PhpOffice\PhpWord\Style\Section) { - $ori = $sty->getOrientation(); - if ($ori !== null) { - $orient = $ori; - } - $pwidth = self::cvttwiptostr($sty->getPageSizeW(), $pwidth); - $pheight = self::cvttwiptostr($sty->getPageSizeH(), $pheight); - $mtop = self::cvttwiptostr($sty->getMarginTop(), $mtop, $topfactor); - $mbottom = self::cvttwiptostr($sty->getMarginBottom(), $mbottom, $botfactor); - $mleft = self::cvttwiptostr($sty->getMarginRight(), $mleft); - $mright = self::cvttwiptostr($sty->getMarginLeft(), $mright); - } + $orient = $sty->getOrientation(); + $pwidth = self::cvttwiptostr($sty->getPageSizeW()); + $pheight = self::cvttwiptostr($sty->getPageSizeH()); + $mtop = self::cvttwiptostr($sty->getMarginTop(), $topfactor); + $mbottom = self::cvttwiptostr($sty->getMarginBottom(), $botfactor); + $mleft = self::cvttwiptostr($sty->getMarginRight()); + $mright = self::cvttwiptostr($sty->getMarginLeft()); $xmlWriter->startElement('style:page-layout'); $xmlWriter->writeAttribute('style:name', "Mpm$sectionNbr"); @@ -253,7 +240,7 @@ class Styles extends AbstractPart $xmlWriter->endElement(); // style:header-style $xmlWriter->startElement('style:footer-style'); - if ($botfactor < 1.0) { // Owen 2019-08-03 + if ($botfactor < 1.0) { $xmlWriter->startElement('style:header-footer-properties'); $xmlWriter->writeAttribute('fo:min-height', $mbottom); $xmlWriter->writeAttribute('fo:margin-top', $mbottom); @@ -275,7 +262,8 @@ class Styles extends AbstractPart $xmlWriter->startElement('office:master-styles'); $sections = $this->getParentWriter()->getPhpWord()->getSections(); - for ($i = 0; $i < count($sections); ++$i) { + $countsects = count($sections); + for ($i = 0; $i < $countsects; ++$i) { $iplus1 = $i + 1; $xmlWriter->startElement('style:master-page'); $xmlWriter->writeAttribute('style:name', "Standard$iplus1"); diff --git a/src/PhpWord/Writer/ODText/Style/Paragraph.php b/src/PhpWord/Writer/ODText/Style/Paragraph.php index 555a4825..be974e72 100644 --- a/src/PhpWord/Writer/ODText/Style/Paragraph.php +++ b/src/PhpWord/Writer/ODText/Style/Paragraph.php @@ -56,9 +56,7 @@ class Paragraph extends AbstractStyle $styleAuto = true; $mpm = 'Standard' . substr($styleName, 2); $psn = $style->getNumLevel(); - if (is_numeric($psn)) { - $pagestart = (int) $psn; - } + $pagestart = $psn; } elseif (substr($styleName, 0, 2) === 'HD') { $styleAuto = true; $psm = 'Heading_' . substr($styleName, 2); @@ -117,7 +115,7 @@ class Paragraph extends AbstractStyle $xmlWriter->writeAttributeIf($temp !== '', 'fo:text-align', $temp); $temp = $style->getLineHeight(); $xmlWriter->writeAttributeIf($temp !== null, 'fo:line-height', ((string) ($temp * 100) . '%')); - $xmlWriter->writeAttributeIf($style->getPageBreakBefore() === true, 'fo:break-before', 'page'); + $xmlWriter->writeAttributeIf($style->hasPageBreakBefore() === true, 'fo:break-before', 'page'); $tabs = $style->getTabs(); if ($tabs !== null && count($tabs) > 0) { From 46c41c5ac182f8eadc9493f4089ca740d034c6e4 Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Sun, 5 Jan 2020 15:05:00 -0800 Subject: [PATCH 3/7] More Scrutinizer Changes Still one report that I don't understand at all, and one I'm not sure of. --- src/PhpWord/Writer/ODText/Element/TextRun.php | 1 + src/PhpWord/Writer/ODText/Part/Content.php | 35 +++++++++++++++++-- src/PhpWord/Writer/ODText/Style/Paragraph.php | 3 +- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/PhpWord/Writer/ODText/Element/TextRun.php b/src/PhpWord/Writer/ODText/Element/TextRun.php index cde996f6..9a005773 100644 --- a/src/PhpWord/Writer/ODText/Element/TextRun.php +++ b/src/PhpWord/Writer/ODText/Element/TextRun.php @@ -33,6 +33,7 @@ class TextRun extends Text $element = $this->getElement(); $xmlWriter->startElement('text:p'); + /** @scrutinizer ignore-call */ $pStyle = $element->getParagraphStyle(); if (!is_string($pStyle)) { $pStyle = 'Normal'; diff --git a/src/PhpWord/Writer/ODText/Part/Content.php b/src/PhpWord/Writer/ODText/Part/Content.php index ea4c87d2..9d6a50c4 100644 --- a/src/PhpWord/Writer/ODText/Part/Content.php +++ b/src/PhpWord/Writer/ODText/Part/Content.php @@ -266,7 +266,7 @@ class Content extends AbstractPart $elements = $container->getElements(); foreach ($elements as $element) { if ($element instanceof TextRun) { - $this->getElementStyle($element, $paragraphStyleCount, $fontStyleCount); + $this->getElementStyleTextRun($element, $paragraphStyleCount); $this->getContainerStyle($element, $paragraphStyleCount, $fontStyleCount); } elseif ($element instanceof Text) { $this->getElementStyle($element, $paragraphStyleCount, $fontStyleCount); @@ -298,7 +298,7 @@ class Content extends AbstractPart /** * Get style of individual element * - * @param \PhpOffice\PhpWord\Element\Text|\PhpOffice\PhpWord\Element\TextRun $element + * @param \PhpOffice\PhpWord\Element\Text $element * @param int $paragraphStyleCount * @param int $fontStyleCount */ @@ -341,6 +341,37 @@ class Content extends AbstractPart } } + /** + * Get style of individual element + * + * @param \PhpOffice\PhpWord\Element\TextRun $element + * @param int $paragraphStyleCount + */ + private function getElementStyleTextRun(&$element, &$paragraphStyleCount) + { + $paragraphStyle = $element->getParagraphStyle(); + $phpWord = $this->getParentWriter()->getPhpWord(); + + if ($paragraphStyle instanceof Paragraph) { + // Paragraph + $name = $paragraphStyle->getStyleName(); + if (!$name) { + $paragraphStyleCount++; + $style = $phpWord->addParagraphStyle("P{$paragraphStyleCount}", $paragraphStyle); + $style->setAuto(); + $element->setParagraphStyle("P{$paragraphStyleCount}"); + } else { + $element->setParagraphStyle($name); + } + } else { + $paragraphStyleCount++; + $parstylename = "P$paragraphStyleCount" . "_$paragraphStyle"; + $style = $phpWord->addParagraphStyle($parstylename, $paragraphStyle); + $style->setAuto(); + $element->setParagraphStyle($parstylename); + } + } + /** * Finds all tracked changes * diff --git a/src/PhpWord/Writer/ODText/Style/Paragraph.php b/src/PhpWord/Writer/ODText/Style/Paragraph.php index be974e72..9d38de86 100644 --- a/src/PhpWord/Writer/ODText/Style/Paragraph.php +++ b/src/PhpWord/Writer/ODText/Style/Paragraph.php @@ -134,7 +134,8 @@ class Paragraph extends AbstractStyle //Indentation $indent = $style->getIndentation(); - if ($indent instanceof \PhpOffice\PhpWord\Style\Indentation) { + //if ($indent instanceof \PhpOffice\PhpWord\Style\Indentation) { + if (!empty($indent)) { $marg = $indent->getLeft(); $xmlWriter->writeAttributeIf($marg !== null, 'fo:margin-left', (string) ($marg / Converter::INCH_TO_TWIP) . 'in'); $marg = $indent->getRight(); From d5149b2867a6887e9b35523194f8a379194099f4 Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Thu, 6 Feb 2020 19:20:13 -0800 Subject: [PATCH 4/7] Coveralls Changes Changes to improve test coverage based on Coveralls report. --- src/PhpWord/Writer/ODText/Part/Content.php | 19 ++- tests/PhpWord/Shared/ConverterTest.php | 3 + .../Writer/ODText/Element/ImageTest.php | 5 +- tests/PhpWord/Writer/ODText/ElementTest.php | 2 +- .../Writer/ODText/Part/AbstractPartTest.php | 1 - .../Writer/ODText/Part/ContentTest.php | 1 - .../PhpWord/Writer/ODText/Style/FontTest.php | 116 ++++++++++++++++++ .../Writer/ODText/Style/ParagraphTest.php | 31 +++++ 8 files changed, 166 insertions(+), 12 deletions(-) diff --git a/src/PhpWord/Writer/ODText/Part/Content.php b/src/PhpWord/Writer/ODText/Part/Content.php index 9d6a50c4..88d7acbd 100644 --- a/src/PhpWord/Writer/ODText/Part/Content.php +++ b/src/PhpWord/Writer/ODText/Part/Content.php @@ -19,6 +19,7 @@ namespace PhpOffice\PhpWord\Writer\ODText\Part; use PhpOffice\Common\XMLWriter; use PhpOffice\PhpWord\Element\AbstractContainer; +use PhpOffice\PhpWord\Element\Field; use PhpOffice\PhpWord\Element\Image; use PhpOffice\PhpWord\Element\Table; use PhpOffice\PhpWord\Element\Text; @@ -270,6 +271,8 @@ class Content extends AbstractPart $this->getContainerStyle($element, $paragraphStyleCount, $fontStyleCount); } elseif ($element instanceof Text) { $this->getElementStyle($element, $paragraphStyleCount, $fontStyleCount); + } elseif ($element instanceof Field) { + $this->getElementStyle($element, $paragraphStyleCount, $fontStyleCount); } elseif ($element instanceof Image) { $style = $element->getStyle(); $style->setStyleName('fr' . $element->getMediaIndex()); @@ -298,14 +301,18 @@ class Content extends AbstractPart /** * Get style of individual element * - * @param \PhpOffice\PhpWord\Element\Text $element + * @param \PhpOffice\PhpWord\Element\Text|\PhpOffice\PhpWord\Element\Field $element * @param int $paragraphStyleCount * @param int $fontStyleCount */ - private function getElementStyle(&$element, &$paragraphStyleCount, &$fontStyleCount) + private function getElementStyle($element, &$paragraphStyleCount, &$fontStyleCount) { $fontStyle = $element->getFontStyle(); - $paragraphStyle = $element->getParagraphStyle(); + if (method_exists($element, 'getParagraphStyle')) { + $paragraphStyle = $element->getParagraphStyle(); + } else { + $paragraphStyle = null; + } $phpWord = $this->getParentWriter()->getPhpWord(); if ($fontStyle instanceof Font) { @@ -332,7 +339,7 @@ class Content extends AbstractPart } else { $element->setParagraphStyle($name); } - } else { + } elseif ($paragraphStyle) { $paragraphStyleCount++; $parstylename = "P$paragraphStyleCount" . "_$paragraphStyle"; $style = $phpWord->addParagraphStyle($parstylename, $paragraphStyle); @@ -347,7 +354,7 @@ class Content extends AbstractPart * @param \PhpOffice\PhpWord\Element\TextRun $element * @param int $paragraphStyleCount */ - private function getElementStyleTextRun(&$element, &$paragraphStyleCount) + private function getElementStyleTextRun($element, &$paragraphStyleCount) { $paragraphStyle = $element->getParagraphStyle(); $phpWord = $this->getParentWriter()->getPhpWord(); @@ -363,7 +370,7 @@ class Content extends AbstractPart } else { $element->setParagraphStyle($name); } - } else { + } elseif ($paragraphStyle) { $paragraphStyleCount++; $parstylename = "P$paragraphStyleCount" . "_$paragraphStyle"; $style = $phpWord->addParagraphStyle($parstylename, $paragraphStyle); diff --git a/tests/PhpWord/Shared/ConverterTest.php b/tests/PhpWord/Shared/ConverterTest.php index 15be8ec1..39ffe090 100644 --- a/tests/PhpWord/Shared/ConverterTest.php +++ b/tests/PhpWord/Shared/ConverterTest.php @@ -135,5 +135,8 @@ class ConverterTest extends \PHPUnit\Framework\TestCase $this->assertEquals(120, Converter::cssToPoint('10pc')); $this->assertEquals(28.346457, Converter::cssToPoint('10mm'), '', 0.000001); $this->assertEquals(283.464567, Converter::cssToPoint('10cm'), '', 0.000001); + $this->assertEquals(40, Converter::cssToPixel('30pt')); + $this->assertEquals(1.27, Converter::cssToCm('36pt')); + $this->assertEquals(127000, Converter::cssToEmu('10pt')); } } diff --git a/tests/PhpWord/Writer/ODText/Element/ImageTest.php b/tests/PhpWord/Writer/ODText/Element/ImageTest.php index bc861104..2e0fdeef 100644 --- a/tests/PhpWord/Writer/ODText/Element/ImageTest.php +++ b/tests/PhpWord/Writer/ODText/Element/ImageTest.php @@ -21,10 +21,9 @@ use PhpOffice\PhpWord\Style\Image; use PhpOffice\PhpWord\TestHelperDOCX; /** - * Test class for PhpOffice\PhpWord\Writer\Word2007\Style\Font + * Test class for PhpOffice\PhpWord\Writer\ODText\Element\Image * - * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Style\Frame - * @runTestsInSeparateProcesses + * @coversDefaultClass \PhpOffice\PhpWord\Writer\ODText\Element\Image */ class ImageTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWord/Writer/ODText/ElementTest.php b/tests/PhpWord/Writer/ODText/ElementTest.php index 500ee247..afad150d 100644 --- a/tests/PhpWord/Writer/ODText/ElementTest.php +++ b/tests/PhpWord/Writer/ODText/ElementTest.php @@ -39,7 +39,7 @@ class ElementTest extends \PHPUnit\Framework\TestCase */ public function testUnmatchedElements() { - $elements = array('Image', 'Link', 'Table', 'Text', 'Title'); + $elements = array('Image', 'Link', 'Table', 'Text', 'Title', 'Field'); foreach ($elements as $element) { $objectClass = 'PhpOffice\\PhpWord\\Writer\\ODText\\Element\\' . $element; $xmlWriter = new XMLWriter(); diff --git a/tests/PhpWord/Writer/ODText/Part/AbstractPartTest.php b/tests/PhpWord/Writer/ODText/Part/AbstractPartTest.php index 51d893d2..3f0c8129 100644 --- a/tests/PhpWord/Writer/ODText/Part/AbstractPartTest.php +++ b/tests/PhpWord/Writer/ODText/Part/AbstractPartTest.php @@ -23,7 +23,6 @@ use PhpOffice\PhpWord\Writer\ODText; * Test class for PhpOffice\PhpWord\Writer\ODText\Part\AbstractPart * * @coversDefaultClass \PhpOffice\PhpWord\Writer\ODText\Part\AbstractPart - * @runTestsInSeparateProcesses */ class AbstractPartTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWord/Writer/ODText/Part/ContentTest.php b/tests/PhpWord/Writer/ODText/Part/ContentTest.php index 34eb8068..55d1a00e 100644 --- a/tests/PhpWord/Writer/ODText/Part/ContentTest.php +++ b/tests/PhpWord/Writer/ODText/Part/ContentTest.php @@ -25,7 +25,6 @@ use PhpOffice\PhpWord\TestHelperDOCX; * Test class for PhpOffice\PhpWord\Writer\ODText\Part\Content * * @coversDefaultClass \PhpOffice\PhpWord\Writer\ODText\Part\Content - * @runTestsInSeparateProcesses */ class ContentTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWord/Writer/ODText/Style/FontTest.php b/tests/PhpWord/Writer/ODText/Style/FontTest.php index 5306c6b0..f1224179 100644 --- a/tests/PhpWord/Writer/ODText/Style/FontTest.php +++ b/tests/PhpWord/Writer/ODText/Style/FontTest.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWord\Writer\ODText\Style; +use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\TestHelperDOCX; /** @@ -73,6 +74,61 @@ class FontTest extends \PHPUnit\Framework\TestCase $this->assertEquals('This should be dark green (FGCOLOR_DARKGREEN)', $doc->getElement($span)->nodeValue); } + public function providerAllNamedColors() + { + return array( + array(Font::FGCOLOR_YELLOW, 'FFFF00'), + array(Font::FGCOLOR_LIGHTGREEN, '90EE90'), + array(Font::FGCOLOR_CYAN, '00FFFF'), + array(Font::FGCOLOR_MAGENTA, 'FF00FF'), + array(Font::FGCOLOR_BLUE, '0000FF'), + array(Font::FGCOLOR_RED, 'FF0000'), + array(Font::FGCOLOR_DARKBLUE, '00008B'), + array(Font::FGCOLOR_DARKCYAN, '008B8B'), + array(Font::FGCOLOR_DARKGREEN, '006400'), + array(Font::FGCOLOR_DARKMAGENTA, '8B008B'), + array(Font::FGCOLOR_DARKRED, '8B0000'), + array(Font::FGCOLOR_DARKYELLOW, '8B8B00'), + array(Font::FGCOLOR_DARKGRAY, 'A9A9A9'), + array(Font::FGCOLOR_LIGHTGRAY, 'D3D3D3'), + array(Font::FGCOLOR_BLACK, '000000'), + array('unknow', 'unknow'), + array('unknown', 'unknown'), + ); + } + + /** + * @dataProvider providerAllNamedColors + * + * @param string $namedColor + * @param string $rgbColor + */ + public function testAllNamedColors($namedColor, $rgbColor) + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $section->addText('This is red (800) in rtf/html, default in docx/odt', array('color' => '800')); + $section->addText('This should be cyanish (008787)', array('color' => '008787')); + $section->addText($namedColor, array('color' => $namedColor)); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $this->assertTrue($doc->elementExists($s2a)); + $s2t = '/office:document-content/office:body/office:text/text:section'; + $this->assertTrue($doc->elementExists($s2t)); + + $element = "$s2a/style:style[7]"; + $this->assertTrue($doc->elementExists($element)); + $style = $doc->getElementAttribute($element, 'style:name'); + $element .= '/style:text-properties'; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals("#$rgbColor", $doc->getElementAttribute($element, 'fo:color')); + $span = "$s2t/text:p[4]/text:span"; + $this->assertTrue($doc->elementExists($span)); + $this->assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); + $this->assertEquals($namedColor, $doc->getElement($span)->nodeValue); + } + /** * Test noproof */ @@ -128,4 +184,64 @@ class FontTest extends \PHPUnit\Framework\TestCase $this->assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); $this->assertEquals('Noproof is false', $doc->getElement($span)->nodeValue); } + + /** + * Test using object with a name as font style for addText + */ + public function testNamedStyleAsObject() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $named = $phpWord->addFontStyle('namedobject', array('color' => '008787')); + $section = $phpWord->addSection(); + $section->addText('Let us see what color we wind up with', $named); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2t = '/office:document-content/office:body/office:text/text:section'; + $this->assertTrue($doc->elementExists($s2t)); + $element = "$s2t/text:p[2]/text:span"; + $this->assertTrue($doc->elementExists($element)); + $this->assertEquals('namedobject', $doc->getElementAttribute($element, 'text:style-name')); + } + + /** + * Test supplying field font style as array or object or string + */ + public function testFieldStyles() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $phpWord->addFontStyle('namedstyle', array('color' => '800000')); + $section = $phpWord->addSection(); + $textrun = $section->addTextRun(); + $fld = $textrun->addField('DATE'); + $fld->setFontStyle('namedstyle'); + $textrun = $section->addTextRun(); + $fld = $textrun->addField('DATE'); + $fld->setFontStyle(array('color' => '008000')); + $textrun = $section->addTextRun(); + $fld = $textrun->addField('DATE'); + $font = new \PhpOffice\PhpWord\Style\Font(); + $font->setColor('000080'); + $fld->setFontStyle($font); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $s2t = '/office:document-content/office:body/office:text/text:section'; + + $element = "$s2a/style:style[5]"; + $this->assertEquals('T1', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('#008000', $doc->getElementAttribute("$element/style:text-properties", 'fo:color')); + $element = "$s2a/style:style[7]"; + $this->assertEquals('T2', $doc->getElementAttribute($element, 'style:name')); + $this->assertEquals('#000080', $doc->getElementAttribute("$element/style:text-properties", 'fo:color')); + + $element = "$s2t/text:p[2]/text:span"; + $this->assertEquals('namedstyle', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertTrue($doc->elementExists("$element/text:date")); + $element = "$s2t/text:p[3]/text:span"; + $this->assertEquals('T1', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertTrue($doc->elementExists("$element/text:date")); + $element = "$s2t/text:p[4]/text:span"; + $this->assertEquals('T2', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertTrue($doc->elementExists("$element/text:date")); + } } diff --git a/tests/PhpWord/Writer/ODText/Style/ParagraphTest.php b/tests/PhpWord/Writer/ODText/Style/ParagraphTest.php index 0e9948cf..9ddb5fe1 100644 --- a/tests/PhpWord/Writer/ODText/Style/ParagraphTest.php +++ b/tests/PhpWord/Writer/ODText/Style/ParagraphTest.php @@ -431,4 +431,35 @@ class ParagraphTest extends \PHPUnit\Framework\TestCase $element = "$s2a/text:p[3]"; $this->assertEquals('P4', $doc->getElementAttribute($element, 'text:style-name')); } + + /** + * Test Empty font and paragraph styles + */ + public function testEmptyFontAndParagraphStyles() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $phpWord->addFontStyle('namedfont', array('name' => 'Courier New', 'size' => 8)); + $phpWord->addParagraphStyle('namedpar', array('lineHeight' => 1.08)); + $section->addText('Empty Font Style and Empty Paragraph Style', '', ''); + $section->addText('Named Font Style and Empty Paragraph Style', 'namedfont', ''); + $section->addText('Empty Font Style and Named Paragraph Style', '', 'namedpar'); + $section->addText('Named Font Style and Named Paragraph Style', 'namedfont', 'namedpar'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:body/office:text/text:section'; + $element = "$s2a/text:p[2]"; + $this->assertEquals('Normal', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertEquals(5, $doc->getElementAttribute("$element/text:s", 'text:c')); + $this->assertFalse($doc->elementExists("$element/text:span")); + $element = "$s2a/text:p[3]"; + $this->assertEquals('Normal', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertEquals('namedfont', $doc->getElementAttribute("$element/text:span", 'text:style-name')); + $element = "$s2a/text:p[4]"; + $this->assertEquals('P1_namedpar', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertFalse($doc->elementExists("$element/text:span")); + $element = "$s2a/text:p[5]"; + $this->assertEquals('P2_namedpar', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertEquals('namedfont', $doc->getElementAttribute("$element/text:span", 'text:style-name')); + } } From 677e042c3a13b51c1f90529e18c1ad569b3c9695 Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Thu, 6 Feb 2020 20:41:40 -0800 Subject: [PATCH 5/7] Scrutinizer Workaroun Attempt to work around demonstrably incorrect Scrutinizer analysis (flags code as bug because "condition is always false" even though Coveralls reports that code which would be executed only if condition is true is indeed executed). --- src/PhpWord/Writer/ODText/Element/Title.php | 17 ++++++++-- src/PhpWord/Writer/ODText/Part/Content.php | 36 +++++++++++++++++---- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/PhpWord/Writer/ODText/Element/Title.php b/src/PhpWord/Writer/ODText/Element/Title.php index 99153b5e..98ddbbf4 100644 --- a/src/PhpWord/Writer/ODText/Element/Title.php +++ b/src/PhpWord/Writer/ODText/Element/Title.php @@ -39,8 +39,7 @@ class Title extends AbstractElement $hdname = 'HD'; $sect = $element->getParent(); if ($sect instanceof \PhpOffice\PhpWord\Element\Section) { - $elems = $sect->getElements(); - if ($elems[0] === $element) { + if (self::compareToFirstElement($element, $sect->getElements())) { $hdname = 'HE'; } } @@ -63,4 +62,18 @@ class Title extends AbstractElement $xmlWriter->endElement(); // text:span $xmlWriter->endElement(); // text:h } + + /** + * Test if element is same as first element in array + * + * @param \PhpOffice\PhpWord\Element\AbstractElement $elem + * + * @param \PhpOffice\PhpWord\Element\AbstractElement[] $elemarray + * + * @return bool + */ + private static function compareToFirstElement($elem, $elemarray) + { + return $elem === $elemarray[0]; + } } diff --git a/src/PhpWord/Writer/ODText/Part/Content.php b/src/PhpWord/Writer/ODText/Part/Content.php index 88d7acbd..f0e60441 100644 --- a/src/PhpWord/Writer/ODText/Part/Content.php +++ b/src/PhpWord/Writer/ODText/Part/Content.php @@ -272,7 +272,7 @@ class Content extends AbstractPart } elseif ($element instanceof Text) { $this->getElementStyle($element, $paragraphStyleCount, $fontStyleCount); } elseif ($element instanceof Field) { - $this->getElementStyle($element, $paragraphStyleCount, $fontStyleCount); + $this->getElementStyleField($element, $fontStyleCount); } elseif ($element instanceof Image) { $style = $element->getStyle(); $style->setStyleName('fr' . $element->getMediaIndex()); @@ -301,18 +301,14 @@ class Content extends AbstractPart /** * Get style of individual element * - * @param \PhpOffice\PhpWord\Element\Text|\PhpOffice\PhpWord\Element\Field $element + * @param \PhpOffice\PhpWord\Element\Text $element * @param int $paragraphStyleCount * @param int $fontStyleCount */ private function getElementStyle($element, &$paragraphStyleCount, &$fontStyleCount) { $fontStyle = $element->getFontStyle(); - if (method_exists($element, 'getParagraphStyle')) { - $paragraphStyle = $element->getParagraphStyle(); - } else { - $paragraphStyle = null; - } + $paragraphStyle = $element->getParagraphStyle(); $phpWord = $this->getParentWriter()->getPhpWord(); if ($fontStyle instanceof Font) { @@ -348,6 +344,32 @@ class Content extends AbstractPart } } + /** + * Get font style of individual field element + * + * @param \PhpOffice\PhpWord\Element\Field $element + * @param int $paragraphStyleCount + * @param int $fontStyleCount + */ + private function getElementStyleField($element, &$fontStyleCount) + { + $fontStyle = $element->getFontStyle(); + $phpWord = $this->getParentWriter()->getPhpWord(); + + if ($fontStyle instanceof Font) { + $name = $fontStyle->getStyleName(); + if (!$name) { + $fontStyleCount++; + $style = $phpWord->addFontStyle("T{$fontStyleCount}", $fontStyle, null); + $style->setAuto(); + $style->setParagraph(null); + $element->setFontStyle("T{$fontStyleCount}"); + } else { + $element->setFontStyle($name); + } + } + } + /** * Get style of individual element * From 4e347b33d7fd73668363c5bebaec3c7a110b9b88 Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Thu, 6 Feb 2020 23:34:24 -0800 Subject: [PATCH 6/7] One Additional Coveralls Test Cover one line previously omitted from coverage. --- tests/PhpWord/Writer/ODText/Style/FontTest.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/PhpWord/Writer/ODText/Style/FontTest.php b/tests/PhpWord/Writer/ODText/Style/FontTest.php index f1224179..22a7151c 100644 --- a/tests/PhpWord/Writer/ODText/Style/FontTest.php +++ b/tests/PhpWord/Writer/ODText/Style/FontTest.php @@ -209,7 +209,7 @@ class FontTest extends \PHPUnit\Framework\TestCase public function testFieldStyles() { $phpWord = new \PhpOffice\PhpWord\PhpWord(); - $phpWord->addFontStyle('namedstyle', array('color' => '800000')); + $namedstyle = $phpWord->addFontStyle('namedstyle', array('color' => '800000')); $section = $phpWord->addSection(); $textrun = $section->addTextRun(); $fld = $textrun->addField('DATE'); @@ -222,6 +222,9 @@ class FontTest extends \PHPUnit\Framework\TestCase $font = new \PhpOffice\PhpWord\Style\Font(); $font->setColor('000080'); $fld->setFontStyle($font); + $textrun = $section->addTextRun(); + $fld = $textrun->addField('DATE'); + $fld->setFontStyle($namedstyle); $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); $s2a = '/office:document-content/office:automatic-styles'; @@ -243,5 +246,7 @@ class FontTest extends \PHPUnit\Framework\TestCase $element = "$s2t/text:p[4]/text:span"; $this->assertEquals('T2', $doc->getElementAttribute($element, 'text:style-name')); $this->assertTrue($doc->elementExists("$element/text:date")); + $element = "$s2t/text:p[5]/text:span"; + $this->assertEquals('namedstyle', $doc->getElementAttribute($element, 'text:style-name')); } } From ba3d6162825d23a999064dbc856410912e86939b Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Thu, 23 Apr 2020 17:25:56 -0700 Subject: [PATCH 7/7] Improve Test Coverage Coverage for Writer/ODText is now 100%. --- src/PhpWord/Element/Section.php | 2 + src/PhpWord/Writer/ODText/Element/Text.php | 10 +- tests/PhpWord/Element/ImageTest.php | 4 +- tests/PhpWord/Writer/ODText/ElementTest.php | 100 ++++++++++++++++++++ 4 files changed, 110 insertions(+), 6 deletions(-) diff --git a/src/PhpWord/Element/Section.php b/src/PhpWord/Element/Section.php index b6da9f3b..caf2ca27 100644 --- a/src/PhpWord/Element/Section.php +++ b/src/PhpWord/Element/Section.php @@ -157,6 +157,8 @@ class Section extends AbstractContainer * @deprecated Use the `getFootnoteProperties` method instead * * @return FootnoteProperties + * + * @codeCoverageIgnore */ public function getFootnotePropoperties() { diff --git a/src/PhpWord/Writer/ODText/Element/Text.php b/src/PhpWord/Writer/ODText/Element/Text.php index 2bf9908d..464d2777 100644 --- a/src/PhpWord/Writer/ODText/Element/Text.php +++ b/src/PhpWord/Writer/ODText/Element/Text.php @@ -42,12 +42,12 @@ class Text extends AbstractElement // @todo Commented for TextRun. Should really checkout this value // $fStyleIsObject = ($fontStyle instanceof Font) ? true : false; - $fStyleIsObject = false; + //$fStyleIsObject = false; - if ($fStyleIsObject) { - // Don't never be the case, because I browse all sections for cleaning all styles not declared - throw new Exception('PhpWord : $fStyleIsObject wouldn\'t be an object'); - } + //if ($fStyleIsObject) { + // Don't never be the case, because I browse all sections for cleaning all styles not declared + // throw new Exception('PhpWord : $fStyleIsObject wouldn\'t be an object'); + //} if (!$this->withoutP) { $xmlWriter->startElement('text:p'); // text:p diff --git a/tests/PhpWord/Element/ImageTest.php b/tests/PhpWord/Element/ImageTest.php index f56d0794..e83be708 100644 --- a/tests/PhpWord/Element/ImageTest.php +++ b/tests/PhpWord/Element/ImageTest.php @@ -77,10 +77,12 @@ class ImageTest extends AbstractWebServerEmbeddedTest foreach ($images as $imageData) { list($source, $type, $extension, $createFunction, $imageFunction) = $imageData; + $nam = ucfirst(strtok($source, '.')); $source = __DIR__ . "/../_files/images/{$source}"; - $image = new Image($source); + $image = new Image($source, null, null, $nam); $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $image); $this->assertEquals($source, $image->getSource()); + $this->assertEquals($nam, $image->getName()); $this->assertEquals(md5($source), $image->getMediaId()); $this->assertEquals($type, $image->getImageType()); $this->assertEquals($extension, $image->getImageExtension()); diff --git a/tests/PhpWord/Writer/ODText/ElementTest.php b/tests/PhpWord/Writer/ODText/ElementTest.php index afad150d..eda4568d 100644 --- a/tests/PhpWord/Writer/ODText/ElementTest.php +++ b/tests/PhpWord/Writer/ODText/ElementTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Writer\ODText; use PhpOffice\Common\XMLWriter; +use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\TestHelperDOCX; @@ -187,6 +188,47 @@ class ElementTest extends \PHPUnit\Framework\TestCase $this->assertEquals('#333333', $doc->getElementAttribute($element, 'fo:color')); } + /** + * Test title specified as text run rather than text + */ + public function testTextRunTitle() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $phpWord->addTitleStyle(1, array('name' => 'Times New Roman', 'size' => 18, 'bold' => true)); + $section = $phpWord->addSection(); + $section->addTitle('Text Title', 1); + $section->addText('Text following Text Title'); + $textRun = new \PhpOffice\PhpWord\Element\TextRun(); + $textRun->addText('Text Run'); + $textRun->addText(' Title'); + $section->addTitle($textRun, 1); + $section->addText('Text following Text Run Title'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $p2t = '/office:document-content/office:body/office:text/text:section'; + + $element = "$p2t/text:h[1]"; + $this->assertEquals('HE1', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertEquals('1', $doc->getElementAttribute($element, 'text:outline-level')); + $span = "$element/text:span"; + $this->assertEquals('Text Title', $doc->getElement($span)->textContent); + $this->assertEquals('Heading_1', $doc->getElementAttribute($span, 'text:style-name')); + $element = "$p2t/text:p[2]/text:span"; + $this->assertEquals('Text following Text Title', $doc->getElement($element)->nodeValue); + + $element = "$p2t/text:h[2]"; + $this->assertEquals('HD1', $doc->getElementAttribute($element, 'text:style-name')); + $this->assertEquals('1', $doc->getElementAttribute($element, 'text:outline-level')); + $span = "$element/text:span"; + $this->assertEquals('Text Run', $doc->getElement("$span/text:span[1]")->textContent); + $this->assertTrue($doc->elementExists("$span/text:span[2]/text:s")); + $this->assertEquals('Title', $doc->getElement("$span/text:span[2]")->textContent); + $this->assertEquals('Heading_1', $doc->getElementAttribute($span, 'text:style-name')); + $element = "$p2t/text:p[3]/text:span"; + $this->assertEquals('Text following Text Run Title', $doc->getElement($element)->nodeValue); + } + /** * Test correct writing of text with ampersand in it */ @@ -225,4 +267,62 @@ class ElementTest extends \PHPUnit\Framework\TestCase self::assertTrue($doc->elementExists($element, 'content.xml')); self::assertEquals('PB', $doc->getElementAttribute($element, 'text:style-name', 'content.xml')); } + + /** + * Test tracked changes + */ + public function testTrackedChanges() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + + // New portrait section + $section = $phpWord->addSection(); + $textRun = $section->addTextRun(); + + $text = $textRun->addText('Hello World! Time to '); + + $text = $textRun->addText('wake ', array('bold' => true)); + $text->setChangeInfo(TrackChange::INSERTED, 'Fred', time() - 1800); + + $text = $textRun->addText('up'); + $text->setTrackChange(new TrackChange(TrackChange::INSERTED, 'Fred')); + + $text = $textRun->addText('go to sleep'); + $text->setChangeInfo(TrackChange::DELETED, 'Barney', new \DateTime('@' . (time() - 3600))); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $tcs = '/office:document-content/office:body/office:text/text:tracked-changes'; + $tc1 = "$tcs/text:changed-region[1]"; + $tc1id = $doc->getElementAttribute($tc1, 'text:id'); + $element = "$tc1/text:insertion"; + self::assertTrue($doc->elementExists($element)); + $element .= '/office:change-info'; + self::AssertEquals('Fred', $doc->getElement("$element/dc:creator")->nodeValue); + self::assertTrue($doc->elementExists("$element/dc:date")); + + $tc2 = "$tcs/text:changed-region[2]"; + $tc2id = $doc->getElementAttribute($tc2, 'text:id'); + $element = "$tc2/text:insertion"; + self::assertTrue($doc->elementExists($element)); + $element .= '/office:change-info'; + self::AssertEquals('Fred', $doc->getElement("$element/dc:creator")->nodeValue); + //self::assertTrue($doc->elementExists("$element/dc:date")); + + $tc3 = "$tcs/text:changed-region[3]"; + $tc3id = $doc->getElementAttribute($tc3, 'text:id'); + $element = "$tc3/text:deletion"; + self::assertTrue($doc->elementExists($element)); + $element .= '/office:change-info'; + self::AssertEquals('Barney', $doc->getElement("$element/dc:creator")->nodeValue); + self::assertTrue($doc->elementExists("$element/dc:date")); + + $p2t = '/office:document-content/office:body/office:text/text:section/text:p[2]'; + $element = "$p2t/text:span[2]/text:change-start"; + self::AssertEquals($tc1id, $doc->getElementAttribute($element, 'text:change-id')); + $element = "$p2t/text:span[3]/text:change-start"; + self::AssertEquals($tc2id, $doc->getElementAttribute($element, 'text:change-id')); + $element = "$p2t/text:change"; + self::AssertEquals($tc3id, $doc->getElementAttribute($element, 'text:change-id')); + } }