diff --git a/composer.json b/composer.json index 6890bf6d9f..303e0556da 100644 --- a/composer.json +++ b/composer.json @@ -68,7 +68,8 @@ "ext-dom": "*", "ext-json": "*", "ext-xml": "*", - "laminas/laminas-escaper": ">=2.6" + "laminas/laminas-escaper": ">=2.6", + "phpoffice/math": "dev-master" }, "require-dev": { "ext-zip": "*", diff --git a/composer.lock b/composer.lock index a4557e01da..8b450b63e6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2d45739d1122eb36186b5cfe7cd6d6ab", + "content-hash": "ae95020f2e191f3202f8e1c5ac26b4e6", "packages": [ { "name": "laminas/laminas-escaper", @@ -67,6 +67,58 @@ } ], "time": "2022-10-10T10:11:09+00:00" + }, + { + "name": "phpoffice/math", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/Math.git", + "reference": "6cb7422c7165529e1f7f9ed0b00fad947d937224" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/Math/zipball/6cb7422c7165529e1f7f9ed0b00fad947d937224", + "reference": "6cb7422c7165529e1f7f9ed0b00fad947d937224", + "shasum": "" + }, + "require": { + "ext-xml": "*", + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpunit/phpunit": ">=7.0" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\Math\\": "src/Math/", + "Tests\\PhpOffice\\Math\\": "tests/Math/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Progi1984", + "homepage": "https://lefevre.dev" + } + ], + "description": "Math - Manipulate Math Formula", + "keywords": [ + "MathML", + "officemathml", + "php" + ], + "support": { + "issues": "https://github.com/PHPOffice/Math/issues", + "source": "https://github.com/PHPOffice/Math/tree/master" + }, + "time": "2023-09-21T16:13:12+00:00" } ], "packages-dev": [ @@ -5017,6 +5069,7 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { + "phpoffice/math": 20, "phpstan/phpstan-phpunit": 0 }, "prefer-stable": false, diff --git a/src/PhpWord/Element/AbstractContainer.php b/src/PhpWord/Element/AbstractContainer.php index 884ec29385..f9b2822a12 100644 --- a/src/PhpWord/Element/AbstractContainer.php +++ b/src/PhpWord/Element/AbstractContainer.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Element; use BadMethodCallException; +use PhpOffice\Math\Math; use ReflectionClass; /** @@ -47,6 +48,7 @@ * @method Chart addChart(string $type, array $categories, array $values, array $style = null, $seriesName = null) * @method FormField addFormField(string $type, mixed $fStyle = null, mixed $pStyle = null) * @method SDT addSDT(string $type) + * @method Formula addFormula(Math $math) * @method \PhpOffice\PhpWord\Element\OLEObject addObject(string $source, mixed $style = null) deprecated, use addOLEObject instead * * @since 0.10.0 @@ -88,6 +90,7 @@ public function __call($function, $args) 'Footnote', 'Endnote', 'CheckBox', 'TextBox', 'Field', 'Line', 'Shape', 'Title', 'TOC', 'PageBreak', 'Chart', 'FormField', 'SDT', 'Comment', + 'Formula', ]; $functions = []; foreach ($elements as $element) { diff --git a/src/PhpWord/Element/Formula.php b/src/PhpWord/Element/Formula.php new file mode 100644 index 0000000000..ca9f5d6b4b --- /dev/null +++ b/src/PhpWord/Element/Formula.php @@ -0,0 +1,53 @@ +setMath($math); + } + + public function setMath(Math $math): self + { + $this->math = $math; + + return $this; + } + + public function getMath(): Math + { + return $this->math; + } +} diff --git a/src/PhpWord/Reader/ODText.php b/src/PhpWord/Reader/ODText.php index aba280db01..d7f8344443 100644 --- a/src/PhpWord/Reader/ODText.php +++ b/src/PhpWord/Reader/ODText.php @@ -53,13 +53,8 @@ public function load($docFile) /** * Read document part. - * - * @param array $relationships - * @param string $partName - * @param string $docFile - * @param string $xmlFile */ - private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, $xmlFile): void + private function readPart(PhpWord $phpWord, array $relationships, string $partName, string $docFile, string $xmlFile): void { $partClass = "PhpOffice\\PhpWord\\Reader\\ODText\\{$partName}"; if (class_exists($partClass)) { @@ -72,12 +67,8 @@ private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, /** * Read all relationship files. - * - * @param string $docFile - * - * @return array */ - private function readRelationships($docFile) + private function readRelationships(string $docFile): array { $rels = []; $xmlFile = 'META-INF/manifest.xml'; diff --git a/src/PhpWord/Reader/ODText/Content.php b/src/PhpWord/Reader/ODText/Content.php index ccbc5eec96..45cb0704db 100644 --- a/src/PhpWord/Reader/ODText/Content.php +++ b/src/PhpWord/Reader/ODText/Content.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Reader\ODText; use DateTime; +use PhpOffice\Math\Reader\MathML; use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Shared\XMLReader; @@ -51,37 +52,55 @@ public function read(PhpWord $phpWord): void break; case 'text:p': // Paragraph - $children = $node->childNodes; - foreach ($children as $child) { - switch ($child->nodeName) { - case 'text:change-start': - $changeId = $child->getAttribute('text:change-id'); - if (isset($trackedChanges[$changeId])) { - $changed = $trackedChanges[$changeId]; - } + $element = $xmlReader->getElement('draw:frame/draw:object', $node); + if ($element) { + $mathFile = str_replace('./', '', $element->getAttribute('xlink:href')) . '/content.xml'; - break; - case 'text:change-end': - unset($changed); + $xmlReaderObject = new XMLReader(); + $mathElement = $xmlReaderObject->getDomFromZip($this->docFile, $mathFile); + if ($mathElement) { + $mathXML = $mathElement->saveXML($mathElement); - break; - case 'text:change': - $changeId = $child->getAttribute('text:change-id'); - if (isset($trackedChanges[$changeId])) { - $changed = $trackedChanges[$changeId]; - } + if (is_string($mathXML)) { + $reader = new MathML(); + $math = $reader->read($mathXML); - break; + $section->addFormula($math); + } } - } + } else { + $children = $node->childNodes; + foreach ($children as $child) { + switch ($child->nodeName) { + case 'text:change-start': + $changeId = $child->getAttribute('text:change-id'); + if (isset($trackedChanges[$changeId])) { + $changed = $trackedChanges[$changeId]; + } + + break; + case 'text:change-end': + unset($changed); - $element = $section->addText($node->nodeValue); - if (isset($changed) && is_array($changed)) { - $element->setTrackChange($changed['changed']); - if (isset($changed['textNodes'])) { - foreach ($changed['textNodes'] as $changedNode) { - $element = $section->addText($changedNode->nodeValue); - $element->setTrackChange($changed['changed']); + break; + case 'text:change': + $changeId = $child->getAttribute('text:change-id'); + if (isset($trackedChanges[$changeId])) { + $changed = $trackedChanges[$changeId]; + } + + break; + } + } + + $element = $section->addText($node->nodeValue); + if (isset($changed) && is_array($changed)) { + $element->setTrackChange($changed['changed']); + if (isset($changed['textNodes'])) { + foreach ($changed['textNodes'] as $changedNode) { + $element = $section->addText($changedNode->nodeValue); + $element->setTrackChange($changed['changed']); + } } } } diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index dc61b09356..e9272ae71d 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -20,6 +20,7 @@ use DateTime; use DOMElement; use InvalidArgumentException; +use PhpOffice\Math\Reader\OfficeMathML; use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType; use PhpOffice\PhpWord\Element\AbstractContainer; use PhpOffice\PhpWord\Element\AbstractElement; @@ -189,25 +190,7 @@ protected function getCommentReference(string $id): array protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart = 'document'): void { // Paragraph style - $paragraphStyle = null; - $headingDepth = null; - if ($xmlReader->elementExists('w:commentReference', $domNode) - || $xmlReader->elementExists('w:commentRangeStart', $domNode) - || $xmlReader->elementExists('w:commentRangeEnd', $domNode) - ) { - $nodes = $xmlReader->getElements('w:commentReference|w:commentRangeStart|w:commentRangeEnd', $domNode); - $node = current(iterator_to_array($nodes)); - if ($node) { - $attributeIdentifier = $node->attributes->getNamedItem('id'); - if ($attributeIdentifier) { - $id = $attributeIdentifier->nodeValue; - } - } - } - if ($xmlReader->elementExists('w:pPr', $domNode)) { - $paragraphStyle = $this->readParagraphStyle($xmlReader, $domNode); - $headingDepth = $this->getHeadingDepth($paragraphStyle); - } + $paragraphStyle = $xmlReader->elementExists('w:pPr', $domNode) ? $this->readParagraphStyle($xmlReader, $domNode) : null; // PreserveText if ($xmlReader->elementExists('w:r/w:instrText', $domNode)) { @@ -234,8 +217,27 @@ protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $par } } $parent->addPreserveText(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8'), $fontStyle, $paragraphStyle); - } elseif ($xmlReader->elementExists('w:pPr/w:numPr', $domNode)) { - // List item + + return; + } + + // Formula + $xmlReader->registerNamespace('m', 'http://schemas.openxmlformats.org/officeDocument/2006/math'); + if ($xmlReader->elementExists('m:oMath', $domNode)) { + $mathElement = $xmlReader->getElement('m:oMath', $domNode); + $mathXML = $mathElement->ownerDocument->saveXML($mathElement); + if (is_string($mathXML)) { + $reader = new OfficeMathML(); + $math = $reader->read($mathXML); + + $parent->addFormula($math); + } + + return; + } + + // List item + if ($xmlReader->elementExists('w:pPr/w:numPr', $domNode)) { $numId = $xmlReader->getAttribute('w:val', $domNode, 'w:pPr/w:numPr/w:numId'); $levelId = $xmlReader->getAttribute('w:val', $domNode, 'w:pPr/w:numPr/w:ilvl'); $nodes = $xmlReader->getElements('*', $domNode); @@ -245,8 +247,13 @@ protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $par foreach ($nodes as $node) { $this->readRun($xmlReader, $node, $listItemRun, $docPart, $paragraphStyle); } - } elseif ($headingDepth !== null) { - // Heading or Title + + return; + } + + // Heading or Title + $headingDepth = $xmlReader->elementExists('w:pPr', $domNode) ? $this->getHeadingDepth($paragraphStyle) : null; + if ($headingDepth !== null) { $textContent = null; $nodes = $xmlReader->getElements('w:r|w:hyperlink', $domNode); if ($nodes->length === 1) { @@ -258,17 +265,19 @@ protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $par } } $parent->addTitle($textContent, $headingDepth); + + return; + } + + // Text and TextRun + $textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag|w:commentReference|w:commentRangeStart|w:commentRangeEnd', $domNode); + if (0 === $textRunContainers) { + $parent->addTextBreak(null, $paragraphStyle); } else { - // Text and TextRun - $textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag|w:commentReference|w:commentRangeStart|w:commentRangeEnd', $domNode); - if (0 === $textRunContainers) { - $parent->addTextBreak(null, $paragraphStyle); - } else { - $nodes = $xmlReader->getElements('*', $domNode); - $paragraph = $parent->addTextRun($paragraphStyle); - foreach ($nodes as $node) { - $this->readRun($xmlReader, $node, $paragraph, $docPart, $paragraphStyle); - } + $nodes = $xmlReader->getElements('*', $domNode); + $paragraph = $parent->addTextRun($paragraphStyle); + foreach ($nodes as $node) { + $this->readRun($xmlReader, $node, $paragraph, $docPart, $paragraphStyle); } } } diff --git a/src/PhpWord/Writer/ODText.php b/src/PhpWord/Writer/ODText.php index 10d9d701fa..55290084a4 100644 --- a/src/PhpWord/Writer/ODText.php +++ b/src/PhpWord/Writer/ODText.php @@ -17,6 +17,9 @@ namespace PhpOffice\PhpWord\Writer; +use PhpOffice\Math\Writer\MathML; +use PhpOffice\PhpWord\Element\AbstractElement; +use PhpOffice\PhpWord\Element\Formula; use PhpOffice\PhpWord\Media; use PhpOffice\PhpWord\PhpWord; @@ -27,6 +30,11 @@ */ class ODText extends AbstractWriter implements WriterInterface { + /** + * @var AbstractElement[] + */ + protected $objects = []; + /** * Create new ODText writer. * @@ -82,8 +90,33 @@ public function save($filename = null): void } } + // Write objects charts + if (!empty($this->objects)) { + $writer = new MathML(); + foreach ($this->objects as $idxObject => $object) { + if ($object instanceof Formula) { + $zip->addFromString('Formula' . $idxObject . '/content.xml', $writer->write($object->getMath())); + } + } + } + // Close zip archive and cleanup temp file $zip->close(); $this->cleanupTempFile(); } + + public function addObject(AbstractElement $object): int + { + $this->objects[] = $object; + + return count($this->objects) - 1; + } + + /** + * @return AbstractElement[] + */ + public function getObjects(): array + { + return $this->objects; + } } diff --git a/src/PhpWord/Writer/ODText/Element/Formula.php b/src/PhpWord/Writer/ODText/Element/Formula.php new file mode 100644 index 0000000000..393db3c9dd --- /dev/null +++ b/src/PhpWord/Writer/ODText/Element/Formula.php @@ -0,0 +1,74 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof ElementFormula) { + return; + } + + $parentWriter = $this->getPart()->getParentWriter(); + if (!$parentWriter instanceof ODText) { + return; + } + + $objectIdx = $parentWriter->addObject($element); + + //$style = $element->getStyle(); + //$width = Converter::pixelToCm($style->getWidth()); + //$height = Converter::pixelToCm($style->getHeight()); + + $xmlWriter->startElement('text:p'); + $xmlWriter->writeAttribute('text:style-name', 'OB' . $objectIdx); + + $xmlWriter->startElement('draw:frame'); + $xmlWriter->writeAttribute('draw:name', $element->getElementId()); + $xmlWriter->writeAttribute('text:anchor-type', 'as-char'); + //$xmlWriter->writeAttribute('svg:width', $width . 'cm'); + //$xmlWriter->writeAttribute('svg:height', $height . 'cm'); + //$xmlWriter->writeAttribute('draw:z-index', $mediaIndex); + + $xmlWriter->startElement('draw:object'); + $xmlWriter->writeAttribute('xlink:href', 'Formula' . $objectIdx); + $xmlWriter->writeAttribute('xlink:type', 'simple'); + $xmlWriter->writeAttribute('xlink:show', 'embed'); + $xmlWriter->writeAttribute('xlink:actuate', 'onLoad'); + $xmlWriter->endElement(); // draw:object + + $xmlWriter->endElement(); // draw:frame + + $xmlWriter->endElement(); // text:p + } +} diff --git a/src/PhpWord/Writer/ODText/Element/Table.php b/src/PhpWord/Writer/ODText/Element/Table.php index 783bb21232..951226fff3 100644 --- a/src/PhpWord/Writer/ODText/Element/Table.php +++ b/src/PhpWord/Writer/ODText/Element/Table.php @@ -83,6 +83,7 @@ private function writeRow(XMLWriter $xmlWriter, RowElement $row): void $xmlWriter->writeAttribute('office:value-type', 'string'); $containerWriter = new Container($xmlWriter, $cell); + $containerWriter->setPart($this->getPart()); $containerWriter->write(); $xmlWriter->endElement(); // table:table-cell diff --git a/src/PhpWord/Writer/ODText/Element/TextRun.php b/src/PhpWord/Writer/ODText/Element/TextRun.php index 6d1e1a191a..28e9f80eb7 100644 --- a/src/PhpWord/Writer/ODText/Element/TextRun.php +++ b/src/PhpWord/Writer/ODText/Element/TextRun.php @@ -41,6 +41,7 @@ public function write(): void $xmlWriter->writeAttribute('text:style-name', $pStyle); $containerWriter = new Container($xmlWriter, $element); + $containerWriter->setPart($this->getPart()); $containerWriter->write(); $xmlWriter->endElement(); diff --git a/src/PhpWord/Writer/ODText/Element/Title.php b/src/PhpWord/Writer/ODText/Element/Title.php index 45fe65c7f5..7730a64e4c 100644 --- a/src/PhpWord/Writer/ODText/Element/Title.php +++ b/src/PhpWord/Writer/ODText/Element/Title.php @@ -57,6 +57,7 @@ public function write(): void $this->writeText($text); } elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { $containerWriter = new Container($xmlWriter, $text); + $containerWriter->setPart($this->getPart()); $containerWriter->write(); } $xmlWriter->endElement(); // text:span diff --git a/src/PhpWord/Writer/ODText/Part/Content.php b/src/PhpWord/Writer/ODText/Part/Content.php index 8c96650240..0c0607a06f 100644 --- a/src/PhpWord/Writer/ODText/Part/Content.php +++ b/src/PhpWord/Writer/ODText/Part/Content.php @@ -135,8 +135,11 @@ public function write() $xmlWriter->startElement('text:p'); $xmlWriter->writeAttribute('text:style-name', 'SB' . $section->getSectionId()); $xmlWriter->endElement(); + $containerWriter = new Container($xmlWriter, $section); + $containerWriter->setPart($this); $containerWriter->write(); + $xmlWriter->endElement(); // text:section } diff --git a/src/PhpWord/Writer/ODText/Part/Manifest.php b/src/PhpWord/Writer/ODText/Part/Manifest.php index 7d428b2c76..ea4964257b 100644 --- a/src/PhpWord/Writer/ODText/Part/Manifest.php +++ b/src/PhpWord/Writer/ODText/Part/Manifest.php @@ -17,7 +17,9 @@ namespace PhpOffice\PhpWord\Writer\ODText\Part; +use PhpOffice\PhpWord\Element\Formula; use PhpOffice\PhpWord\Media; +use PhpOffice\PhpWord\Writer\ODText; /** * ODText manifest part writer: META-INF/manifest.xml. @@ -31,7 +33,6 @@ class Manifest extends AbstractPart */ public function write() { - $parts = ['content.xml', 'meta.xml', 'styles.xml']; $xmlWriter = $this->getXmlWriter(); $xmlWriter->startDocument('1.0', 'UTF-8'); @@ -46,7 +47,7 @@ public function write() $xmlWriter->endElement(); // Parts - foreach ($parts as $part) { + foreach (['content.xml', 'meta.xml', 'styles.xml'] as $part) { $xmlWriter->startElement('manifest:file-entry'); $xmlWriter->writeAttribute('manifest:media-type', 'text/xml'); $xmlWriter->writeAttribute('manifest:full-path', $part); @@ -64,6 +65,23 @@ public function write() } } + $parentWriter = $this->getParentWriter(); + if ($parentWriter instanceof ODText) { + foreach ($parentWriter->getObjects() as $idxObject => $object) { + if ($object instanceof Formula) { + $xmlWriter->startElement('manifest:file-entry'); + $xmlWriter->writeAttribute('manifest:full-path', 'Formula' . $idxObject . '/content.xml'); + $xmlWriter->writeAttribute('manifest:media-type', 'text/xml'); + $xmlWriter->endElement(); + $xmlWriter->startElement('manifest:file-entry'); + $xmlWriter->writeAttribute('manifest:full-path', 'Formula' . $idxObject . '/'); + $xmlWriter->writeAttribute('manifest:version', '1.2'); + $xmlWriter->writeAttribute('manifest:media-type', 'application/vnd.oasis.opendocument.formula'); + $xmlWriter->endElement(); + } + } + } + $xmlWriter->endElement(); // manifest:manifest return $xmlWriter->getData(); diff --git a/src/PhpWord/Writer/Word2007/Element/AbstractElement.php b/src/PhpWord/Writer/Word2007/Element/AbstractElement.php index a7bfeab78b..abd1324aad 100644 --- a/src/PhpWord/Writer/Word2007/Element/AbstractElement.php +++ b/src/PhpWord/Writer/Word2007/Element/AbstractElement.php @@ -21,6 +21,7 @@ use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Text as SharedText; use PhpOffice\PhpWord\Shared\XMLWriter; +use PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart; /** * Abstract element writer. @@ -50,6 +51,11 @@ abstract class AbstractElement */ protected $withoutP = false; + /** + * @var null|AbstractPart + */ + protected $part; + /** * Write element. */ @@ -224,4 +230,16 @@ protected function writeText($content) return $this->getXmlWriter()->writeRaw($content); } + + public function setPart(?AbstractPart $part): self + { + $this->part = $part; + + return $this; + } + + public function getPart(): ?AbstractPart + { + return $this->part; + } } diff --git a/src/PhpWord/Writer/Word2007/Element/Container.php b/src/PhpWord/Writer/Word2007/Element/Container.php index b6db45197a..88a46cd0c9 100644 --- a/src/PhpWord/Writer/Word2007/Element/Container.php +++ b/src/PhpWord/Writer/Word2007/Element/Container.php @@ -64,6 +64,7 @@ public function write(): void $writerClass = $this->namespace . '\\TextBreak'; /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $writer Type hint */ $writer = new $writerClass($xmlWriter, new TextBreakElement(), $withoutP); + $writer->setPart($this->getPart()); $writer->write(); } } @@ -83,6 +84,7 @@ private function writeElement(XMLWriter $xmlWriter, Element $element, $withoutP) if (class_exists($writerClass)) { /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $writer Type hint */ $writer = new $writerClass($xmlWriter, $element, $withoutP); + $writer->setPart($this->getPart()); $writer->write(); } diff --git a/src/PhpWord/Writer/Word2007/Element/Field.php b/src/PhpWord/Writer/Word2007/Element/Field.php index 6fdb48b0f4..d889651ac2 100644 --- a/src/PhpWord/Writer/Word2007/Element/Field.php +++ b/src/PhpWord/Writer/Word2007/Element/Field.php @@ -75,6 +75,7 @@ private function writeDefault(\PhpOffice\PhpWord\Element\Field $element): void if ($element->getText() != null) { if ($element->getText() instanceof \PhpOffice\PhpWord\Element\TextRun) { $containerWriter = new Container($xmlWriter, $element->getText(), true); + $containerWriter->setPart($this->getPart()); $containerWriter->write(); $xmlWriter->startElement('w:r'); @@ -146,6 +147,7 @@ protected function writeMacrobutton(\PhpOffice\PhpWord\Element\Field $element): if ($element->getText() != null) { if ($element->getText() instanceof \PhpOffice\PhpWord\Element\TextRun) { $containerWriter = new Container($xmlWriter, $element->getText(), true); + $containerWriter->setPart($this->getPart()); $containerWriter->write(); } } diff --git a/src/PhpWord/Writer/Word2007/Element/Formula.php b/src/PhpWord/Writer/Word2007/Element/Formula.php new file mode 100644 index 0000000000..00286cb7dd --- /dev/null +++ b/src/PhpWord/Writer/Word2007/Element/Formula.php @@ -0,0 +1,53 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof FormulaElement) { + return; + } + + $this->startElementP(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->writeElement('w:rPr'); + $xmlWriter->endElement(); + + $writer = new OfficeMathML(); + + $xmlWriter->writeRaw( + $writer->write($element->getMath()) + ); + + $this->endElementP(); + } +} diff --git a/src/PhpWord/Writer/Word2007/Element/ListItemRun.php b/src/PhpWord/Writer/Word2007/Element/ListItemRun.php index daa2fc1d0f..2d86b3abac 100644 --- a/src/PhpWord/Writer/Word2007/Element/ListItemRun.php +++ b/src/PhpWord/Writer/Word2007/Element/ListItemRun.php @@ -49,6 +49,7 @@ private function writeParagraph(ListItemRunElement $element): void $this->writeParagraphProperties($element); $containerWriter = new Container($xmlWriter, $element); + $containerWriter->setPart($this->getPart()); $containerWriter->write(); $xmlWriter->endElement(); // w:p diff --git a/src/PhpWord/Writer/Word2007/Element/Table.php b/src/PhpWord/Writer/Word2007/Element/Table.php index 9364fe45c1..a47b86d04c 100644 --- a/src/PhpWord/Writer/Word2007/Element/Table.php +++ b/src/PhpWord/Writer/Word2007/Element/Table.php @@ -127,6 +127,7 @@ private function writeCell(XMLWriter $xmlWriter, CellElement $cell): void // Write content $containerWriter = new Container($xmlWriter, $cell); + $containerWriter->setPart($this->getPart()); $containerWriter->write(); $xmlWriter->endElement(); // w:tc diff --git a/src/PhpWord/Writer/Word2007/Element/TextBox.php b/src/PhpWord/Writer/Word2007/Element/TextBox.php index ff94094de7..9f02c215e7 100644 --- a/src/PhpWord/Writer/Word2007/Element/TextBox.php +++ b/src/PhpWord/Writer/Word2007/Element/TextBox.php @@ -61,6 +61,7 @@ public function write(): void // TextBox content, serving as a container $xmlWriter->startElement('w:txbxContent'); $containerWriter = new Container($xmlWriter, $element); + $containerWriter->setPart($this->getPart()); $containerWriter->write(); $xmlWriter->endElement(); // w:txbxContent diff --git a/src/PhpWord/Writer/Word2007/Element/TextRun.php b/src/PhpWord/Writer/Word2007/Element/TextRun.php index 8a5870777b..b631ac4dd5 100644 --- a/src/PhpWord/Writer/Word2007/Element/TextRun.php +++ b/src/PhpWord/Writer/Word2007/Element/TextRun.php @@ -35,6 +35,7 @@ public function write(): void $this->startElementP(); $containerWriter = new Container($xmlWriter, $element); + $containerWriter->setPart($this->getPart()); $containerWriter->write(); $this->endElementP(); // w:p diff --git a/src/PhpWord/Writer/Word2007/Element/Title.php b/src/PhpWord/Writer/Word2007/Element/Title.php index dd46d755e3..a1a6351fee 100644 --- a/src/PhpWord/Writer/Word2007/Element/Title.php +++ b/src/PhpWord/Writer/Word2007/Element/Title.php @@ -69,6 +69,7 @@ public function write(): void $xmlWriter->endElement(); // w:r } elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { $containerWriter = new Container($xmlWriter, $text); + $containerWriter->setPart($this->getPart()); $containerWriter->write(); } diff --git a/src/PhpWord/Writer/Word2007/Part/Comments.php b/src/PhpWord/Writer/Word2007/Part/Comments.php index 93dd4e1ce5..425c11534a 100644 --- a/src/PhpWord/Writer/Word2007/Part/Comments.php +++ b/src/PhpWord/Writer/Word2007/Part/Comments.php @@ -81,6 +81,7 @@ protected function writeComment(XMLWriter $xmlWriter, Comment $comment): void $xmlWriter->writeAttributeIf($comment->getInitials() != null, 'w:initials', $comment->getInitials()); $containerWriter = new Container($xmlWriter, $comment); + $containerWriter->setPart($this); $containerWriter->write(); $xmlWriter->endElement(); // w:comment diff --git a/src/PhpWord/Writer/Word2007/Part/Document.php b/src/PhpWord/Writer/Word2007/Part/Document.php index 6eca90e730..0b2740cac3 100644 --- a/src/PhpWord/Writer/Word2007/Part/Document.php +++ b/src/PhpWord/Writer/Word2007/Part/Document.php @@ -61,6 +61,7 @@ public function write() ++$currentSection; $containerWriter = new Container($xmlWriter, $section); + $containerWriter->setPart($this); $containerWriter->write(); if ($currentSection == $sectionCount) { diff --git a/src/PhpWord/Writer/Word2007/Part/Footer.php b/src/PhpWord/Writer/Word2007/Part/Footer.php index fd62c8941f..79de91c085 100644 --- a/src/PhpWord/Writer/Word2007/Part/Footer.php +++ b/src/PhpWord/Writer/Word2007/Part/Footer.php @@ -61,6 +61,7 @@ public function write() $xmlWriter->writeAttribute('xmlns:wne', 'http://schemas.microsoft.com/office/word/2006/wordml'); $containerWriter = new Container($xmlWriter, $this->element); + $containerWriter->setPart($this); $containerWriter->write(); $xmlWriter->endElement(); // $this->rootElement diff --git a/src/PhpWord/Writer/Word2007/Part/Footnotes.php b/src/PhpWord/Writer/Word2007/Part/Footnotes.php index 9624ab7459..349ea45c2b 100644 --- a/src/PhpWord/Writer/Word2007/Part/Footnotes.php +++ b/src/PhpWord/Writer/Word2007/Part/Footnotes.php @@ -168,6 +168,7 @@ protected function writeNote(XMLWriter $xmlWriter, $element): void $xmlWriter->endElement(); // w:r $containerWriter = new Container($xmlWriter, $element); + $containerWriter->setPart($this); $containerWriter->write(); $xmlWriter->endElement(); // w:p diff --git a/tests/PhpWordTests/Reader/ODTextTest.php b/tests/PhpWordTests/Reader/ODTextTest.php index e4baf8d480..20c5916c7d 100644 --- a/tests/PhpWordTests/Reader/ODTextTest.php +++ b/tests/PhpWordTests/Reader/ODTextTest.php @@ -17,7 +17,11 @@ namespace PhpOffice\PhpWordTests\Reader; +use PhpOffice\Math\Element; +use PhpOffice\PhpWord\Element\Formula; +use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\IOFactory; +use PhpOffice\PhpWord\PhpWord; /** * Test class for PhpOffice\PhpWord\Reader\ODText. @@ -33,8 +37,31 @@ class ODTextTest extends \PHPUnit\Framework\TestCase */ public function testLoad(): void { - $filename = __DIR__ . '/../_files/documents/reader.odt'; - $phpWord = IOFactory::load($filename, 'ODText'); - self::assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); + $phpWord = IOFactory::load(dirname(__DIR__, 1) . '/_files/documents/reader.odt', 'ODText'); + self::assertInstanceOf(PhpWord::class, $phpWord); + } + + public function testLoadFormula(): void + { + $phpWord = IOFactory::load(dirname(__DIR__, 1) . '/_files/documents/reader-formula.odt', 'ODText'); + + self::assertInstanceOf(PhpWord::class, $phpWord); + + $sections = $phpWord->getSections(); + self::assertCount(1, $sections); + + $section = $sections[0]; + self::assertInstanceOf(Section::class, $section); + + $elements = $section->getElements(); + self::assertCount(1, $elements); + + $element = $elements[0]; + self::assertInstanceOf(Formula::class, $element); + + $elements = $element->getMath()->getElements(); + self::assertCount(1, $elements); + + self::assertInstanceOf(Element\Semantics::class, $elements[0]); } } diff --git a/tests/PhpWordTests/Reader/Word2007Test.php b/tests/PhpWordTests/Reader/Word2007Test.php index e42f0110d5..1d8674d729 100644 --- a/tests/PhpWordTests/Reader/Word2007Test.php +++ b/tests/PhpWordTests/Reader/Word2007Test.php @@ -18,8 +18,11 @@ namespace PhpOffice\PhpWordTests\Reader; use DateTime; +use PhpOffice\Math\Element; use PhpOffice\PhpWord\Element\Comment; +use PhpOffice\PhpWord\Element\Formula; use PhpOffice\PhpWord\Element\Image; +use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\Element\Text; use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\IOFactory; @@ -159,4 +162,58 @@ public function testLoadComments(): void self::assertInstanceOf(Font::class, $fontStyle); self::assertEquals('de-DE', $fontStyle->getLang()->getLatin()); } + + public function testLoadFormula(): void + { + $phpWord = IOFactory::load(dirname(__DIR__, 1) . '/_files/documents/reader-formula.docx'); + + self::assertInstanceOf(PhpWord::class, $phpWord); + + $sections = $phpWord->getSections(); + self::assertCount(1, $sections); + + $section = $sections[0]; + self::assertInstanceOf(Section::class, $section); + + $elements = $section->getElements(); + self::assertCount(1, $elements); + + $element = $elements[0]; + self::assertInstanceOf(Formula::class, $element); + + $elements = $element->getMath()->getElements(); + self::assertCount(5, $elements); + + /** @var Element\Fraction $element */ + $element = $elements[0]; + self::assertInstanceOf(Element\Fraction::class, $element); + /** @var Element\Identifier $numerator */ + $numerator = $element->getNumerator(); + self::assertInstanceOf(Element\Identifier::class, $numerator); + self::assertEquals('π', $numerator->getValue()); + /** @var Element\Numeric $denominator */ + $denominator = $element->getDenominator(); + self::assertInstanceOf(Element\Numeric::class, $denominator); + self::assertEquals(2, $denominator->getValue()); + + /** @var Element\Operator $element */ + $element = $elements[1]; + self::assertInstanceOf(Element\Operator::class, $element); + self::assertEquals('+', $element->getValue()); + + /** @var Element\Identifier $element */ + $element = $elements[2]; + self::assertInstanceOf(Element\Identifier::class, $element); + self::assertEquals('a', $element->getValue()); + + /** @var Element\Operator $element */ + $element = $elements[3]; + self::assertInstanceOf(Element\Operator::class, $element); + self::assertEquals('∗', $element->getValue()); + + /** @var Element\Numeric $element */ + $element = $elements[4]; + self::assertInstanceOf(Element\Numeric::class, $element); + self::assertEquals(2, $element->getValue()); + } } diff --git a/tests/PhpWordTests/Writer/ODText/Element/FormulaTest.php b/tests/PhpWordTests/Writer/ODText/Element/FormulaTest.php new file mode 100644 index 0000000000..9e6e418670 --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Element/FormulaTest.php @@ -0,0 +1,71 @@ +add( + (new Element\Fraction()) + ->setDenominator(new Element\Identifier('π')) + ->setNumerator(new Element\Numeric(2)) + ) + ->add( + new Element\Operator('+') + ) + ->add( + new Element\Identifier('a') + ) + ->add( + new Element\Operator('∗') + ) + ->add( + new Element\Numeric(2) + ); + + $phpWord = new PhpWord(); + + $section = $phpWord->addSection(); + $section->addFormula($math); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + self::assertTrue($doc->elementExists('/office:document-content/office:body/office:text/text:section/text:p/draw:frame/draw:object')); + //self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/m:oMathPara/m:oMath')); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Element/FormulaTest.php b/tests/PhpWordTests/Writer/Word2007/Element/FormulaTest.php new file mode 100644 index 0000000000..065906d045 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/FormulaTest.php @@ -0,0 +1,71 @@ +add( + (new Element\Fraction()) + ->setDenominator(new Element\Identifier('π')) + ->setNumerator(new Element\Numeric(2)) + ) + ->add( + new Element\Operator('+') + ) + ->add( + new Element\Identifier('a') + ) + ->add( + new Element\Operator('∗') + ) + ->add( + new Element\Numeric(2) + ); + + $phpWord = new PhpWord(); + + $section = $phpWord->addSection(); + $section->addFormula($math); + + $doc = TestHelperDOCX::getDocument($phpWord); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/m:oMathPara')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/m:oMathPara/m:oMath')); + } +} diff --git a/tests/PhpWordTests/_files/documents/reader-formula.docx b/tests/PhpWordTests/_files/documents/reader-formula.docx new file mode 100644 index 0000000000..0e40f6672c Binary files /dev/null and b/tests/PhpWordTests/_files/documents/reader-formula.docx differ diff --git a/tests/PhpWordTests/_files/documents/reader-formula.odt b/tests/PhpWordTests/_files/documents/reader-formula.odt new file mode 100644 index 0000000000..f94c44afbb Binary files /dev/null and b/tests/PhpWordTests/_files/documents/reader-formula.odt differ