Skip to content

Commit 9ffce35

Browse files
authored
Merge pull request #1299 from zobo/word2007_read_tabs
Add support for reading <w:tab/> element in runs.
2 parents 6b48451 + bb70eb0 commit 9ffce35

File tree

7 files changed

+196
-79
lines changed

7 files changed

+196
-79
lines changed

src/PhpWord/Reader/Word2007/AbstractPart.php

Lines changed: 71 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
namespace PhpOffice\PhpWord\Reader\Word2007;
1919

2020
use PhpOffice\Common\XMLReader;
21+
use PhpOffice\PhpWord\Element\AbstractContainer;
2122
use PhpOffice\PhpWord\Element\TextRun;
2223
use PhpOffice\PhpWord\Element\TrackChange;
2324
use PhpOffice\PhpWord\PhpWord;
@@ -161,20 +162,14 @@ protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, $pa
161162
$parent->addTitle($textContent, $headingDepth);
162163
} else {
163164
// Text and TextRun
164-
$runCount = $xmlReader->countElements('w:r', $domNode);
165-
$insCount = $xmlReader->countElements('w:ins', $domNode);
166-
$delCount = $xmlReader->countElements('w:del', $domNode);
167-
$linkCount = $xmlReader->countElements('w:hyperlink', $domNode);
168-
$runLinkCount = $runCount + $insCount + $delCount + $linkCount;
169-
if (0 == $runLinkCount) {
165+
$textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag', $domNode);
166+
if (0 === $textRunContainers) {
170167
$parent->addTextBreak(null, $paragraphStyle);
171168
} else {
172169
$nodes = $xmlReader->getElements('*', $domNode);
173-
if ($runLinkCount > 1) {
174-
$parent = $parent->addTextRun($paragraphStyle);
175-
}
170+
$paragraph = $parent->addTextRun($paragraphStyle);
176171
foreach ($nodes as $node) {
177-
$this->readRun($xmlReader, $node, $parent, $docPart, $paragraphStyle);
172+
$this->readRun($xmlReader, $node, $paragraph, $docPart, $paragraphStyle);
178173
}
179174
}
180175
}
@@ -216,75 +211,85 @@ private function getHeadingDepth(array $paragraphStyle = null)
216211
*/
217212
protected function readRun(XMLReader $xmlReader, \DOMElement $domNode, $parent, $docPart, $paragraphStyle = null)
218213
{
219-
if (in_array($domNode->nodeName, array('w:ins', 'w:del'))) {
214+
if (in_array($domNode->nodeName, array('w:ins', 'w:del', 'w:smartTag', 'w:hyperlink'))) {
220215
$nodes = $xmlReader->getElements('*', $domNode);
221216
foreach ($nodes as $node) {
222-
return $this->readRun($xmlReader, $node, $parent, $docPart, $paragraphStyle);
217+
$this->readRun($xmlReader, $node, $parent, $docPart, $paragraphStyle);
218+
}
219+
} elseif ($domNode->nodeName == 'w:r') {
220+
$fontStyle = $this->readFontStyle($xmlReader, $domNode);
221+
$nodes = $xmlReader->getElements('*', $domNode);
222+
foreach ($nodes as $node) {
223+
$this->readRunChild($xmlReader, $node, $parent, $docPart, $paragraphStyle, $fontStyle);
223224
}
224225
}
226+
}
225227

226-
if (!in_array($domNode->nodeName, array('w:r', 'w:hyperlink'))) {
227-
return;
228-
}
229-
$fontStyle = $this->readFontStyle($xmlReader, $domNode);
230-
231-
// Link
232-
if ('w:hyperlink' == $domNode->nodeName) {
233-
$rId = $xmlReader->getAttribute('r:id', $domNode);
234-
$textContent = $xmlReader->getValue('w:r/w:t', $domNode);
228+
/**
229+
* Parses nodes under w:r
230+
*
231+
* @param XMLReader $xmlReader
232+
* @param \DOMElement $node
233+
* @param AbstractContainer $parent
234+
* @param string $docPart
235+
* @param mixed $paragraphStyle
236+
* @param mixed $fontStyle
237+
*/
238+
protected function readRunChild(XMLReader $xmlReader, \DOMElement $node, AbstractContainer $parent, $docPart, $paragraphStyle = null, $fontStyle = null)
239+
{
240+
$runParent = $node->parentNode->parentNode;
241+
if ($node->nodeName == 'w:footnoteReference') {
242+
// Footnote
243+
$wId = $xmlReader->getAttribute('w:id', $node);
244+
$footnote = $parent->addFootnote();
245+
$footnote->setRelationId($wId);
246+
} elseif ($node->nodeName == 'w:endnoteReference') {
247+
// Endnote
248+
$wId = $xmlReader->getAttribute('w:id', $node);
249+
$endnote = $parent->addEndnote();
250+
$endnote->setRelationId($wId);
251+
} elseif ($node->nodeName == 'w:pict') {
252+
// Image
253+
$rId = $xmlReader->getAttribute('r:id', $node, 'v:shape/v:imagedata');
235254
$target = $this->getMediaTarget($docPart, $rId);
236255
if (!is_null($target)) {
237-
$parent->addLink($target, $textContent, $fontStyle, $paragraphStyle);
238-
}
239-
} else {
240-
if ($xmlReader->elementExists('w:footnoteReference', $domNode)) {
241-
// Footnote
242-
$wId = $xmlReader->getAttribute('w:id', $domNode, 'w:footnoteReference');
243-
$footnote = $parent->addFootnote();
244-
$footnote->setRelationId($wId);
245-
} elseif ($xmlReader->elementExists('w:endnoteReference', $domNode)) {
246-
// Endnote
247-
$wId = $xmlReader->getAttribute('w:id', $domNode, 'w:endnoteReference');
248-
$endnote = $parent->addEndnote();
249-
$endnote->setRelationId($wId);
250-
} elseif ($xmlReader->elementExists('w:pict', $domNode)) {
251-
// Image
252-
$rId = $xmlReader->getAttribute('r:id', $domNode, 'w:pict/v:shape/v:imagedata');
253-
$target = $this->getMediaTarget($docPart, $rId);
254-
if (!is_null($target)) {
255-
if ('External' == $this->getTargetMode($docPart, $rId)) {
256-
$imageSource = $target;
257-
} else {
258-
$imageSource = "zip://{$this->docFile}#{$target}";
259-
}
260-
$parent->addImage($imageSource);
261-
}
262-
} elseif ($xmlReader->elementExists('w:object', $domNode)) {
263-
// Object
264-
$rId = $xmlReader->getAttribute('r:id', $domNode, 'w:object/o:OLEObject');
265-
// $rIdIcon = $xmlReader->getAttribute('r:id', $domNode, 'w:object/v:shape/v:imagedata');
266-
$target = $this->getMediaTarget($docPart, $rId);
267-
if (!is_null($target)) {
268-
$textContent = "&lt;Object: {$target}>";
269-
$parent->addText($textContent, $fontStyle, $paragraphStyle);
256+
if ('External' == $this->getTargetMode($docPart, $rId)) {
257+
$imageSource = $target;
258+
} else {
259+
$imageSource = "zip://{$this->docFile}#{$target}";
270260
}
261+
$parent->addImage($imageSource);
271262
}
272-
if ($xmlReader->elementExists('w:br', $domNode)) {
273-
$parent->addTextBreak();
263+
} elseif ($node->nodeName == 'w:object') {
264+
// Object
265+
$rId = $xmlReader->getAttribute('r:id', $node, 'o:OLEObject');
266+
// $rIdIcon = $xmlReader->getAttribute('r:id', $domNode, 'w:object/v:shape/v:imagedata');
267+
$target = $this->getMediaTarget($docPart, $rId);
268+
if (!is_null($target)) {
269+
$textContent = "&lt;Object: {$target}>";
270+
$parent->addText($textContent, $fontStyle, $paragraphStyle);
274271
}
275-
if ($xmlReader->elementExists('w:t', $domNode)) {
276-
// TextRun
277-
if ($domNode->parentNode->nodeName == 'w:del') {
278-
$textContent = $xmlReader->getValue('w:delText', $domNode);
279-
} else {
280-
$textContent = $xmlReader->getValue('w:t', $domNode);
272+
} elseif ($node->nodeName == 'w:br') {
273+
$parent->addTextBreak();
274+
} elseif ($node->nodeName == 'w:tab') {
275+
$parent->addText("\t");
276+
} elseif ($node->nodeName == 'w:t' || $node->nodeName == 'w:delText') {
277+
// TextRun
278+
$textContent = $xmlReader->getValue('.', $node);
279+
280+
if ($runParent->nodeName == 'w:hyperlink') {
281+
$rId = $xmlReader->getAttribute('r:id', $runParent);
282+
$target = $this->getMediaTarget($docPart, $rId);
283+
if (!is_null($target)) {
284+
$parent->addLink($target, $textContent, $fontStyle, $paragraphStyle);
281285
}
286+
} else {
282287
/** @var AbstractElement $element */
283288
$element = $parent->addText($textContent, $fontStyle, $paragraphStyle);
284-
if (in_array($domNode->parentNode->nodeName, array('w:ins', 'w:del'))) {
285-
$type = ($domNode->parentNode->nodeName == 'w:del') ? TrackChange::DELETED : TrackChange::INSERTED;
286-
$author = $domNode->parentNode->getAttribute('w:author');
287-
$date = \DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $domNode->parentNode->getAttribute('w:date'));
289+
if (in_array($runParent->nodeName, array('w:ins', 'w:del'))) {
290+
$type = ($runParent->nodeName == 'w:del') ? TrackChange::DELETED : TrackChange::INSERTED;
291+
$author = $runParent->getAttribute('w:author');
292+
$date = \DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $runParent->getAttribute('w:date'));
288293
$element->setChangeInfo($type, $author, $date);
289294
}
290295
}

src/PhpWord/Writer/HTML/Element/Title.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,17 @@ public function write()
3838
}
3939

4040
$tag = 'h' . $this->element->getDepth();
41-
if (Settings::isOutputEscapingEnabled()) {
42-
$text = $this->escaper->escapeHtml($this->element->getText());
43-
} else {
44-
$text = $this->element->getText();
41+
42+
$text = $this->element->getText();
43+
if (is_string($text)) {
44+
if (Settings::isOutputEscapingEnabled()) {
45+
$text = $this->escaper->escapeHtml($text);
46+
}
47+
} elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) {
48+
$writer = new Container($this->parentWriter, $this->element);
49+
$text = $writer->write();
4550
}
51+
4652
$content = "<{$tag}>{$text}</{$tag}>" . PHP_EOL;
4753

4854
return $content;

src/PhpWord/Writer/ODText/Element/Title.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@ public function write()
3737

3838
$xmlWriter->startElement('text:h');
3939
$xmlWriter->writeAttribute('text:outline-level', $element->getDepth());
40-
$this->writeText($element->getText());
40+
$text = $element->getText();
41+
if (is_string($text)) {
42+
$this->writeText($text);
43+
} elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) {
44+
$containerWriter = new Container($xmlWriter, $text);
45+
$containerWriter->write();
46+
}
4147
$xmlWriter->endElement(); // text:h
4248
}
4349
}

src/PhpWord/Writer/RTF/Element/Text.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function write()
3434
/** @var \PhpOffice\PhpWord\Element\Text $element Type hint */
3535
$element = $this->element;
3636
$elementClass = str_replace('\\Writer\\RTF', '', get_class($this));
37-
if (!$element instanceof $elementClass) {
37+
if (!$element instanceof $elementClass || !is_string($element->getText())) {
3838
return '';
3939
}
4040

tests/PhpWord/Reader/Word2007/ElementTest.php

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
namespace PhpOffice\PhpWord\Reader\Word2007;
1919

2020
use PhpOffice\PhpWord\AbstractTestReader;
21+
use PhpOffice\PhpWord\Element\TrackChange;
2122

2223
/**
2324
* Test class for PhpOffice\PhpWord\Reader\Word2007\Element subnamespace
@@ -39,9 +40,35 @@ public function testReadTextBreak()
3940
$phpWord = $this->getDocumentFromString(array('document' => $documentXml));
4041

4142
$elements = $phpWord->getSection(0)->getElements();
42-
$this->assertInstanceOf('PhpOffice\PhpWord\Element\TextBreak', $elements[0]);
43-
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $elements[1]);
44-
$this->assertEquals('test string', $elements[1]->getText());
43+
$this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]);
44+
/** @var \PhpOffice\PhpWord\Element\TextRun $textRun */
45+
$textRun = $elements[0];
46+
$this->assertInstanceOf('PhpOffice\PhpWord\Element\TextBreak', $textRun->getElement(0));
47+
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $textRun->getElement(1));
48+
$this->assertEquals('test string', $textRun->getElement(1)->getText());
49+
}
50+
51+
/**
52+
* Test reading content inside w:smartTag
53+
*/
54+
public function testSmartTag()
55+
{
56+
$documentXml = '<w:p>
57+
<w:smartTag>
58+
<w:r>
59+
<w:t xml:space="preserve">test string</w:t>
60+
</w:r>
61+
</w:smartTag>
62+
</w:p>';
63+
64+
$phpWord = $this->getDocumentFromString(array('document' => $documentXml));
65+
66+
$elements = $phpWord->getSection(0)->getElements();
67+
$this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]);
68+
/** @var \PhpOffice\PhpWord\Element\TextRun $textRun */
69+
$textRun = $elements[0];
70+
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $textRun->getElement(0));
71+
$this->assertEquals('test string', $textRun->getElement(0)->getText());
4572
}
4673

4774
/**
@@ -85,6 +112,76 @@ public function testReadListItemRunWithFormatting()
85112
$this->assertTrue($listElements[2]->getFontStyle()->getBold());
86113
}
87114

115+
/**
116+
* Test reading track changes
117+
*/
118+
public function testReadTrackChange()
119+
{
120+
$documentXml = '<w:p>
121+
<w:r>
122+
<w:t>One </w:t>
123+
</w:r>
124+
<w:del w:author="Barney" w:date="2018-03-14T10:57:05Z">
125+
<w:r>
126+
<w:delText>two</w:delText>
127+
</w:r>
128+
</w:del>
129+
<w:ins w:author="Fred" w:date="2018-03-14T10:57:05Z">
130+
<w:r>
131+
<w:t>three</w:t>
132+
</w:r>
133+
</w:ins>
134+
</w:p>';
135+
136+
$phpWord = $this->getDocumentFromString(array('document' => $documentXml));
137+
138+
$elements = $phpWord->getSection(0)->getElements();
139+
$this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]);
140+
/** @var \PhpOffice\PhpWord\Element\TextRun $elements */
141+
$textRun = $elements[0];
142+
143+
$this->assertEquals('One ', $textRun->getElement(0)->getText());
144+
145+
$this->assertEquals('two', $textRun->getElement(1)->getText());
146+
$this->assertNotNull($textRun->getElement(1)->getTrackChange());
147+
/** @var \PhpOffice\PhpWord\Element\TrackChange $trackChange */
148+
$trackChange = $textRun->getElement(1)->getTrackChange();
149+
$this->assertEquals(TrackChange::DELETED, $trackChange->getChangeType());
150+
151+
$this->assertEquals('three', $textRun->getElement(2)->getText());
152+
$this->assertNotNull($textRun->getElement(2)->getTrackChange());
153+
/** @var \PhpOffice\PhpWord\Element\TrackChange $trackChange */
154+
$trackChange = $textRun->getElement(2)->getTrackChange();
155+
$this->assertEquals(TrackChange::INSERTED, $trackChange->getChangeType());
156+
}
157+
158+
/**
159+
* Test reading of tab
160+
*/
161+
public function testReadTab()
162+
{
163+
$documentXml = '<w:p>
164+
<w:r>
165+
<w:t>One</w:t>
166+
<w:tab/>
167+
<w:t>Two</w:t>
168+
</w:r>
169+
</w:p>';
170+
171+
$phpWord = $this->getDocumentFromString(array('document' => $documentXml));
172+
173+
$elements = $phpWord->getSection(0)->getElements();
174+
$this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]);
175+
/** @var \PhpOffice\PhpWord\Element\TextRun $textRun */
176+
$textRun = $elements[0];
177+
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $textRun->getElement(0));
178+
$this->assertEquals('One', $textRun->getElement(0)->getText());
179+
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $textRun->getElement(1));
180+
$this->assertEquals("\t", $textRun->getElement(1)->getText());
181+
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $textRun->getElement(2));
182+
$this->assertEquals('Two', $textRun->getElement(2)->getText());
183+
}
184+
88185
/**
89186
* Test reading Title style
90187
*/

tests/PhpWord/Reader/Word2007/StyleTest.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,13 @@ public function testReadPosition()
117117
$phpWord = $this->getDocumentFromString(array('document' => $documentXml));
118118

119119
$elements = $phpWord->getSection(0)->getElements();
120-
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $elements[0]);
121-
$this->assertInstanceOf('PhpOffice\PhpWord\Style\Font', $elements[0]->getFontStyle());
120+
/** @var \PhpOffice\PhpWord\Element\TextRun $elements */
121+
$textRun = $elements[0];
122+
$this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $textRun);
123+
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $textRun->getElement(0));
124+
$this->assertInstanceOf('PhpOffice\PhpWord\Style\Font', $textRun->getElement(0)->getFontStyle());
122125
/** @var \PhpOffice\PhpWord\Style\Font $fontStyle */
123-
$fontStyle = $elements[0]->getFontStyle();
126+
$fontStyle = $textRun->getElement(0)->getFontStyle();
124127
$this->assertEquals(15, $fontStyle->getPosition());
125128
}
126129
}

tests/PhpWord/Writer/Word2007Test.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public function testConstruct()
7575
public function testSave()
7676
{
7777
$localImage = __DIR__ . '/../_files/images/earth.jpg';
78-
$remoteImage = 'http://php.net//images/logos/php-med-trans-light.gif';
78+
$remoteImage = 'http://php.net/images/logos/new-php-logo.png';
7979
$phpWord = new PhpWord();
8080
$phpWord->addFontStyle('Font', array('size' => 11));
8181
$phpWord->addParagraphStyle('Paragraph', array('alignment' => Jc::CENTER));

0 commit comments

Comments
 (0)