1
This commit is contained in:
@@ -42,6 +42,13 @@ abstract class AbstractReader implements ReaderInterface
|
||||
*/
|
||||
protected $fileHandle;
|
||||
|
||||
/**
|
||||
* Load images.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $imageLoading = true;
|
||||
|
||||
/**
|
||||
* Read data only?
|
||||
*
|
||||
@@ -67,6 +74,18 @@ abstract class AbstractReader implements ReaderInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasImageLoading(): bool
|
||||
{
|
||||
return $this->imageLoading;
|
||||
}
|
||||
|
||||
public function setImageLoading(bool $value): self
|
||||
{
|
||||
$this->imageLoading = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open file for reading.
|
||||
*
|
||||
|
||||
@@ -82,6 +82,12 @@ class MsDoc extends AbstractReader implements ReaderInterface
|
||||
*/
|
||||
private $arraySections = [];
|
||||
|
||||
/** @var string */
|
||||
private $summaryInformation;
|
||||
|
||||
/** @var string */
|
||||
private $documentSummaryInformation;
|
||||
|
||||
const VERSION_97 = '97';
|
||||
const VERSION_2000 = '2000';
|
||||
const VERSION_2002 = '2002';
|
||||
@@ -150,9 +156,9 @@ class MsDoc extends AbstractReader implements ReaderInterface
|
||||
// Get Data stream
|
||||
$this->dataObjectPool = $ole->getStream($ole->wrkObjectPool);
|
||||
// Get Summary Information data
|
||||
$this->_SummaryInformation = $ole->getStream($ole->summaryInformation);
|
||||
$this->summaryInformation = $ole->getStream($ole->summaryInformation);
|
||||
// Get Document Summary Information data
|
||||
$this->_DocumentSummaryInformation = $ole->getStream($ole->docSummaryInfos);
|
||||
$this->documentSummaryInformation = $ole->getStream($ole->docSummaryInfos);
|
||||
}
|
||||
|
||||
private function getNumInLcb($lcb, $iSize)
|
||||
@@ -1131,7 +1137,7 @@ class MsDoc extends AbstractReader implements ReaderInterface
|
||||
/**
|
||||
* Section and information about them.
|
||||
*
|
||||
* @see : http://msdn.microsoft.com/en-us/library/dd924458%28v=office.12%29.aspx
|
||||
* @see http://msdn.microsoft.com/en-us/library/dd924458%28v=office.12%29.aspx
|
||||
*/
|
||||
private function readRecordPlcfSed(): void
|
||||
{
|
||||
@@ -1145,7 +1151,7 @@ class MsDoc extends AbstractReader implements ReaderInterface
|
||||
$posMem += 4;
|
||||
|
||||
// PlcfSed : aSed
|
||||
//@see : http://msdn.microsoft.com/en-us/library/dd950194%28v=office.12%29.aspx
|
||||
//@see http://msdn.microsoft.com/en-us/library/dd950194%28v=office.12%29.aspx
|
||||
$numSed = $this->getNumInLcb($this->arrayFib['lcbPlcfSed'], 12);
|
||||
|
||||
$aSed = [];
|
||||
@@ -1177,7 +1183,7 @@ class MsDoc extends AbstractReader implements ReaderInterface
|
||||
/**
|
||||
* Specifies the fonts that are used in the document.
|
||||
*
|
||||
* @see : http://msdn.microsoft.com/en-us/library/dd943880%28v=office.12%29.aspx
|
||||
* @see http://msdn.microsoft.com/en-us/library/dd943880%28v=office.12%29.aspx
|
||||
*/
|
||||
private function readRecordSttbfFfn(): void
|
||||
{
|
||||
@@ -1468,7 +1474,7 @@ class MsDoc extends AbstractReader implements ReaderInterface
|
||||
$offset = $offsetBase;
|
||||
|
||||
// ChpxFkp
|
||||
// @see : http://msdn.microsoft.com/en-us/library/dd910989%28v=office.12%29.aspx
|
||||
// @see http://msdn.microsoft.com/en-us/library/dd910989%28v=office.12%29.aspx
|
||||
$numRGFC = self::getInt1d($this->dataWorkDocument, $offset + 511);
|
||||
$arrayRGFC = [];
|
||||
for ($inc = 0; $inc <= $numRGFC; ++$inc) {
|
||||
@@ -1491,7 +1497,7 @@ class MsDoc extends AbstractReader implements ReaderInterface
|
||||
|
||||
if ($rgb > 0) {
|
||||
// Chp Structure
|
||||
// @see : http://msdn.microsoft.com/en-us/library/dd772849%28v=office.12%29.aspx
|
||||
// @see http://msdn.microsoft.com/en-us/library/dd772849%28v=office.12%29.aspx
|
||||
$posRGB = $offsetBase + $rgb * 2;
|
||||
|
||||
$cb = self::getInt1d($this->dataWorkDocument, $posRGB);
|
||||
@@ -1926,12 +1932,13 @@ class MsDoc extends AbstractReader implements ReaderInterface
|
||||
// $operand = self::getInt2d($data, $pos);
|
||||
$pos += 2;
|
||||
$cbNum -= 2;
|
||||
|
||||
// $ipat = ($operand >> 0) && bindec('111111');
|
||||
// $icoBack = ($operand >> 6) && bindec('11111');
|
||||
// $icoFore = ($operand >> 11) && bindec('11111');
|
||||
break;
|
||||
// sprmCCv
|
||||
//@see : http://msdn.microsoft.com/en-us/library/dd952824%28v=office.12%29.aspx
|
||||
//@see http://msdn.microsoft.com/en-us/library/dd952824%28v=office.12%29.aspx
|
||||
case 0x70:
|
||||
$red = str_pad(dechex(self::getInt1d($this->dataWorkDocument, $pos)), 2, '0', STR_PAD_LEFT);
|
||||
++$pos;
|
||||
@@ -2042,7 +2049,7 @@ class MsDoc extends AbstractReader implements ReaderInterface
|
||||
// HFD > clsid
|
||||
$sprmCPicLocation += 16;
|
||||
// HFD > hyperlink
|
||||
//@see : http://msdn.microsoft.com/en-us/library/dd909835%28v=office.12%29.aspx
|
||||
//@see http://msdn.microsoft.com/en-us/library/dd909835%28v=office.12%29.aspx
|
||||
$streamVersion = self::getInt4d($this->dataData, $sprmCPicLocation);
|
||||
$sprmCPicLocation += 4;
|
||||
$data = self::getInt4d($this->dataData, $sprmCPicLocation);
|
||||
@@ -2110,8 +2117,8 @@ class MsDoc extends AbstractReader implements ReaderInterface
|
||||
}*/
|
||||
} else {
|
||||
// Pictures
|
||||
//@see : http://msdn.microsoft.com/en-us/library/dd925458%28v=office.12%29.aspx
|
||||
//@see : http://msdn.microsoft.com/en-us/library/dd926136%28v=office.12%29.aspx
|
||||
//@see http://msdn.microsoft.com/en-us/library/dd925458%28v=office.12%29.aspx
|
||||
//@see http://msdn.microsoft.com/en-us/library/dd926136%28v=office.12%29.aspx
|
||||
// PICF : lcb
|
||||
$sprmCPicLocation += 4;
|
||||
// PICF : cbHeader
|
||||
@@ -2198,13 +2205,13 @@ class MsDoc extends AbstractReader implements ReaderInterface
|
||||
$sprmCPicLocation += $shapeRH['recLen'];
|
||||
}
|
||||
// picture : rgfb
|
||||
//@see : http://msdn.microsoft.com/en-us/library/dd950560%28v=office.12%29.aspx
|
||||
//@see http://msdn.microsoft.com/en-us/library/dd950560%28v=office.12%29.aspx
|
||||
$fileBlockRH = $this->loadRecordHeader($this->dataData, $sprmCPicLocation);
|
||||
while ($fileBlockRH['recType'] == 0xF007 || ($fileBlockRH['recType'] >= 0xF018 && $fileBlockRH['recType'] <= 0xF117)) {
|
||||
$sprmCPicLocation += 8;
|
||||
switch ($fileBlockRH['recType']) {
|
||||
// OfficeArtFBSE
|
||||
//@see : http://msdn.microsoft.com/en-us/library/dd944923%28v=office.12%29.aspx
|
||||
//@see http://msdn.microsoft.com/en-us/library/dd944923%28v=office.12%29.aspx
|
||||
case 0xF007:
|
||||
// btWin32
|
||||
++$sprmCPicLocation;
|
||||
@@ -2239,7 +2246,7 @@ class MsDoc extends AbstractReader implements ReaderInterface
|
||||
}
|
||||
}
|
||||
// embeddedBlip
|
||||
//@see : http://msdn.microsoft.com/en-us/library/dd910081%28v=office.12%29.aspx
|
||||
//@see http://msdn.microsoft.com/en-us/library/dd910081%28v=office.12%29.aspx
|
||||
$embeddedBlipRH = $this->loadRecordHeader($this->dataData, $sprmCPicLocation);
|
||||
switch ($embeddedBlipRH['recType']) {
|
||||
case self::OFFICEARTBLIPJPG:
|
||||
|
||||
@@ -53,13 +53,8 @@ class ODText extends AbstractReader implements ReaderInterface
|
||||
|
||||
/**
|
||||
* 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 @@ class ODText extends AbstractReader implements ReaderInterface
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
namespace PhpOffice\PhpWord\Reader\ODText;
|
||||
|
||||
use DateTime;
|
||||
use DOMElement;
|
||||
use DOMNodeList;
|
||||
use PhpOffice\Math\Reader\MathML;
|
||||
use PhpOffice\PhpWord\Element\Section;
|
||||
use PhpOffice\PhpWord\Element\TrackChange;
|
||||
use PhpOffice\PhpWord\PhpWord;
|
||||
use PhpOffice\PhpWord\Shared\XMLReader;
|
||||
@@ -29,6 +33,9 @@ use PhpOffice\PhpWord\Shared\XMLReader;
|
||||
*/
|
||||
class Content extends AbstractPart
|
||||
{
|
||||
/** @var ?Section */
|
||||
private $section;
|
||||
|
||||
/**
|
||||
* Read content.xml.
|
||||
*/
|
||||
@@ -40,48 +47,112 @@ class Content extends AbstractPart
|
||||
$trackedChanges = [];
|
||||
|
||||
$nodes = $xmlReader->getElements('office:body/office:text/*');
|
||||
$this->section = null;
|
||||
$this->processNodes($nodes, $xmlReader, $phpWord);
|
||||
$this->section = null;
|
||||
}
|
||||
|
||||
/** @param DOMNodeList<DOMElement> $nodes */
|
||||
public function processNodes(DOMNodeList $nodes, XMLReader $xmlReader, PhpWord $phpWord): void
|
||||
{
|
||||
if ($nodes->length > 0) {
|
||||
$section = $phpWord->addSection();
|
||||
foreach ($nodes as $node) {
|
||||
// $styleName = $xmlReader->getAttribute('text:style-name', $node);
|
||||
switch ($node->nodeName) {
|
||||
case 'text:h': // Heading
|
||||
$depth = $xmlReader->getAttribute('text:outline-level', $node);
|
||||
$section->addTitle($node->nodeValue, $depth);
|
||||
$this->getSection($phpWord)->addTitle($node->nodeValue, $depth);
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
break;
|
||||
case 'text:change-end':
|
||||
unset($changed);
|
||||
|
||||
break;
|
||||
case 'text:change':
|
||||
$changeId = $child->getAttribute('text:change-id');
|
||||
if (isset($trackedChanges[$changeId])) {
|
||||
$changed = $trackedChanges[$changeId];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
$styleName = $xmlReader->getAttribute('text:style-name', $node);
|
||||
if (substr($styleName, 0, 2) === 'SB') {
|
||||
break;
|
||||
}
|
||||
$element = $xmlReader->getElement('draw:frame/draw:object', $node);
|
||||
if ($element) {
|
||||
$mathFile = str_replace('./', '', $element->getAttribute('xlink:href')) . '/content.xml';
|
||||
|
||||
$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']);
|
||||
$xmlReaderObject = new XMLReader();
|
||||
$mathElement = $xmlReaderObject->getDomFromZip($this->docFile, $mathFile);
|
||||
if ($mathElement) {
|
||||
$mathXML = $mathElement->saveXML($mathElement);
|
||||
|
||||
if (is_string($mathXML)) {
|
||||
$reader = new MathML();
|
||||
$math = $reader->read($mathXML);
|
||||
|
||||
$this->getSection($phpWord)->addFormula($math);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$children = $node->childNodes;
|
||||
$spans = false;
|
||||
/** @var DOMElement $child */
|
||||
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);
|
||||
|
||||
break;
|
||||
case 'text:change':
|
||||
$changeId = $child->getAttribute('text:change-id');
|
||||
if (isset($trackedChanges[$changeId])) {
|
||||
$changed = $trackedChanges[$changeId];
|
||||
}
|
||||
|
||||
break;
|
||||
case 'text:span':
|
||||
$spans = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($spans) {
|
||||
$element = $this->getSection($phpWord)->addTextRun();
|
||||
foreach ($children as $child) {
|
||||
switch ($child->nodeName) {
|
||||
case 'text:span':
|
||||
/** @var DOMElement $child2 */
|
||||
foreach ($child->childNodes as $child2) {
|
||||
switch ($child2->nodeName) {
|
||||
case '#text':
|
||||
$element->addText($child2->nodeValue);
|
||||
|
||||
break;
|
||||
case 'text:tab':
|
||||
$element->addText("\t");
|
||||
|
||||
break;
|
||||
case 'text:s':
|
||||
$spaces = (int) $child2->getAttribute('text:c') ?: 1;
|
||||
$element->addText(str_repeat(' ', $spaces));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$element = $this->getSection($phpWord)->addText($node->nodeValue);
|
||||
}
|
||||
if (isset($changed) && is_array($changed)) {
|
||||
$element->setTrackChange($changed['changed']);
|
||||
if (isset($changed['textNodes'])) {
|
||||
foreach ($changed['textNodes'] as $changedNode) {
|
||||
$element = $this->getSection($phpWord)->addText($changedNode->nodeValue);
|
||||
$element->setTrackChange($changed['changed']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +162,7 @@ class Content extends AbstractPart
|
||||
$listItems = $xmlReader->getElements('text:list-item/text:p', $node);
|
||||
foreach ($listItems as $listItem) {
|
||||
// $listStyleName = $xmlReader->getAttribute('text:style-name', $listItem);
|
||||
$section->addListItem($listItem->nodeValue, 0);
|
||||
$this->getSection($phpWord)->addListItem($listItem->nodeValue, 0);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -110,9 +181,26 @@ class Content extends AbstractPart
|
||||
$trackedChanges[$changedRegion->getAttribute('text:id')] = ['changed' => $changed, 'textNodes' => $textNodes];
|
||||
}
|
||||
|
||||
break;
|
||||
case 'text:section': // Section
|
||||
// $sectionStyleName = $xmlReader->getAttribute('text:style-name', $listItem);
|
||||
$this->section = $phpWord->addSection();
|
||||
$children = $node->childNodes;
|
||||
$this->processNodes($children, $xmlReader, $phpWord);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getSection(PhpWord $phpWord): Section
|
||||
{
|
||||
$section = $this->section;
|
||||
if ($section === null) {
|
||||
$section = $this->section = $phpWord->addSection();
|
||||
}
|
||||
|
||||
return $section;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
|
||||
namespace PhpOffice\PhpWord\Reader;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpWord\Element\AbstractElement;
|
||||
use PhpOffice\PhpWord\PhpWord;
|
||||
use PhpOffice\PhpWord\Reader\Word2007\AbstractPart;
|
||||
use PhpOffice\PhpWord\Shared\XMLReader;
|
||||
use PhpOffice\PhpWord\Shared\ZipArchive;
|
||||
|
||||
@@ -42,23 +45,34 @@ class Word2007 extends AbstractReader implements ReaderInterface
|
||||
{
|
||||
$phpWord = new PhpWord();
|
||||
$relationships = $this->readRelationships($docFile);
|
||||
$commentRefs = [];
|
||||
|
||||
$steps = [
|
||||
['stepPart' => 'document', 'stepItems' => [
|
||||
'styles' => 'Styles',
|
||||
'numbering' => 'Numbering',
|
||||
]],
|
||||
['stepPart' => 'main', 'stepItems' => [
|
||||
'officeDocument' => 'Document',
|
||||
'core-properties' => 'DocPropsCore',
|
||||
'extended-properties' => 'DocPropsApp',
|
||||
'custom-properties' => 'DocPropsCustom',
|
||||
]],
|
||||
['stepPart' => 'document', 'stepItems' => [
|
||||
'endnotes' => 'Endnotes',
|
||||
'footnotes' => 'Footnotes',
|
||||
'settings' => 'Settings',
|
||||
]],
|
||||
[
|
||||
'stepPart' => 'document',
|
||||
'stepItems' => [
|
||||
'styles' => 'Styles',
|
||||
'numbering' => 'Numbering',
|
||||
],
|
||||
],
|
||||
[
|
||||
'stepPart' => 'main',
|
||||
'stepItems' => [
|
||||
'officeDocument' => 'Document',
|
||||
'core-properties' => 'DocPropsCore',
|
||||
'extended-properties' => 'DocPropsApp',
|
||||
'custom-properties' => 'DocPropsCustom',
|
||||
],
|
||||
],
|
||||
[
|
||||
'stepPart' => 'document',
|
||||
'stepItems' => [
|
||||
'endnotes' => 'Endnotes',
|
||||
'footnotes' => 'Footnotes',
|
||||
'settings' => 'Settings',
|
||||
'comments' => 'Comments',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($steps as $step) {
|
||||
@@ -72,7 +86,8 @@ class Word2007 extends AbstractReader implements ReaderInterface
|
||||
if (isset($stepItems[$relType])) {
|
||||
$partName = $stepItems[$relType];
|
||||
$xmlFile = $relItem['target'];
|
||||
$this->readPart($phpWord, $relationships, $partName, $docFile, $xmlFile);
|
||||
$part = $this->readPart($phpWord, $relationships, $commentRefs, $partName, $docFile, $xmlFile);
|
||||
$commentRefs = $part->getCommentReferences();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,20 +98,23 @@ class Word2007 extends AbstractReader implements ReaderInterface
|
||||
/**
|
||||
* Read document part.
|
||||
*
|
||||
* @param array $relationships
|
||||
* @param string $partName
|
||||
* @param string $docFile
|
||||
* @param string $xmlFile
|
||||
* @param array<string, array<string, null|AbstractElement>> $commentRefs
|
||||
*/
|
||||
private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, $xmlFile): void
|
||||
private function readPart(PhpWord $phpWord, array $relationships, array $commentRefs, string $partName, string $docFile, string $xmlFile): AbstractPart
|
||||
{
|
||||
$partClass = "PhpOffice\\PhpWord\\Reader\\Word2007\\{$partName}";
|
||||
if (class_exists($partClass)) {
|
||||
/** @var \PhpOffice\PhpWord\Reader\Word2007\AbstractPart $part Type hint */
|
||||
$part = new $partClass($docFile, $xmlFile);
|
||||
$part->setRels($relationships);
|
||||
$part->read($phpWord);
|
||||
if (!class_exists($partClass)) {
|
||||
throw new Exception(sprintf('The part "%s" doesn\'t exist', $partClass));
|
||||
}
|
||||
|
||||
/** @var AbstractPart $part Type hint */
|
||||
$part = new $partClass($docFile, $xmlFile);
|
||||
$part->setImageLoading($this->hasImageLoading());
|
||||
$part->setRels($relationships);
|
||||
$part->setCommentReferences($commentRefs);
|
||||
$part->read($phpWord);
|
||||
|
||||
return $part;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,8 +19,11 @@ namespace PhpOffice\PhpWord\Reader\Word2007;
|
||||
|
||||
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;
|
||||
use PhpOffice\PhpWord\Element\TextRun;
|
||||
use PhpOffice\PhpWord\Element\TrackChange;
|
||||
use PhpOffice\PhpWord\PhpWord;
|
||||
@@ -67,6 +70,20 @@ abstract class AbstractPart
|
||||
*/
|
||||
protected $rels = [];
|
||||
|
||||
/**
|
||||
* Comment references.
|
||||
*
|
||||
* @var array<string, array<string, AbstractElement>>
|
||||
*/
|
||||
protected $commentRefs = [];
|
||||
|
||||
/**
|
||||
* Image Loading.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $imageLoading = true;
|
||||
|
||||
/**
|
||||
* Read part.
|
||||
*/
|
||||
@@ -94,6 +111,74 @@ abstract class AbstractPart
|
||||
$this->rels = $value;
|
||||
}
|
||||
|
||||
public function setImageLoading(bool $value): self
|
||||
{
|
||||
$this->imageLoading = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasImageLoading(): bool
|
||||
{
|
||||
return $this->imageLoading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comment references.
|
||||
*
|
||||
* @return array<string, array<string, null|AbstractElement>>
|
||||
*/
|
||||
public function getCommentReferences(): array
|
||||
{
|
||||
return $this->commentRefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set comment references.
|
||||
*
|
||||
* @param array<string, array<string, null|AbstractElement>> $commentRefs
|
||||
*/
|
||||
public function setCommentReferences(array $commentRefs): self
|
||||
{
|
||||
$this->commentRefs = $commentRefs;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set comment reference.
|
||||
*/
|
||||
private function setCommentReference(string $type, string $id, AbstractElement $element): self
|
||||
{
|
||||
if (!in_array($type, ['start', 'end'])) {
|
||||
throw new InvalidArgumentException('Type must be "start" or "end"');
|
||||
}
|
||||
|
||||
if (!array_key_exists($id, $this->commentRefs)) {
|
||||
$this->commentRefs[$id] = [
|
||||
'start' => null,
|
||||
'end' => null,
|
||||
];
|
||||
}
|
||||
$this->commentRefs[$id][$type] = $element;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comment reference.
|
||||
*
|
||||
* @return array<string, null|AbstractElement>
|
||||
*/
|
||||
protected function getCommentReference(string $id): array
|
||||
{
|
||||
if (!array_key_exists($id, $this->commentRefs)) {
|
||||
throw new InvalidArgumentException(sprintf('Comment with id %s isn\'t referenced in document', $id));
|
||||
}
|
||||
|
||||
return $this->commentRefs[$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Read w:p.
|
||||
*
|
||||
@@ -105,12 +190,7 @@ abstract class AbstractPart
|
||||
protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart = 'document'): void
|
||||
{
|
||||
// Paragraph style
|
||||
$paragraphStyle = null;
|
||||
$headingDepth = null;
|
||||
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)) {
|
||||
@@ -137,8 +217,27 @@ abstract class AbstractPart
|
||||
}
|
||||
}
|
||||
$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);
|
||||
@@ -148,10 +247,15 @@ abstract class AbstractPart
|
||||
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', $domNode);
|
||||
$nodes = $xmlReader->getElements('w:r|w:hyperlink', $domNode);
|
||||
if ($nodes->length === 1) {
|
||||
$textContent = htmlspecialchars($xmlReader->getValue('w:t', $nodes->item(0)), ENT_QUOTES, 'UTF-8');
|
||||
} else {
|
||||
@@ -161,17 +265,19 @@ abstract class AbstractPart
|
||||
}
|
||||
}
|
||||
$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', $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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,8 +285,6 @@ abstract class AbstractPart
|
||||
/**
|
||||
* Returns the depth of the Heading, returns 0 for a Title.
|
||||
*
|
||||
* @param array $paragraphStyle
|
||||
*
|
||||
* @return null|number
|
||||
*/
|
||||
private function getHeadingDepth(?array $paragraphStyle = null)
|
||||
@@ -211,7 +315,7 @@ abstract class AbstractPart
|
||||
*/
|
||||
protected function readRun(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart, $paragraphStyle = null): void
|
||||
{
|
||||
if (in_array($domNode->nodeName, ['w:ins', 'w:del', 'w:smartTag', 'w:hyperlink'])) {
|
||||
if (in_array($domNode->nodeName, ['w:ins', 'w:del', 'w:smartTag', 'w:hyperlink', 'w:commentReference'])) {
|
||||
$nodes = $xmlReader->getElements('*', $domNode);
|
||||
foreach ($nodes as $node) {
|
||||
$this->readRun($xmlReader, $node, $parent, $docPart, $paragraphStyle);
|
||||
@@ -223,6 +327,17 @@ abstract class AbstractPart
|
||||
$this->readRunChild($xmlReader, $node, $parent, $docPart, $paragraphStyle, $fontStyle);
|
||||
}
|
||||
}
|
||||
|
||||
if ($xmlReader->elementExists('.//*["commentReference"=local-name()]', $domNode)) {
|
||||
$node = iterator_to_array($xmlReader->getElements('.//*["commentReference"=local-name()]', $domNode))[0];
|
||||
$attributeIdentifier = $node->attributes->getNamedItem('id');
|
||||
if ($attributeIdentifier) {
|
||||
$id = $attributeIdentifier->nodeValue;
|
||||
|
||||
$this->setCommentReference('start', $id, $parent->getElement($parent->countElements() - 1));
|
||||
$this->setCommentReference('end', $id, $parent->getElement($parent->countElements() - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,7 +364,7 @@ abstract class AbstractPart
|
||||
// Image
|
||||
$rId = $xmlReader->getAttribute('r:id', $node, 'v:shape/v:imagedata');
|
||||
$target = $this->getMediaTarget($docPart, $rId);
|
||||
if (null !== $target) {
|
||||
if ($this->hasImageLoading() && null !== $target) {
|
||||
if ('External' == $this->getTargetMode($docPart, $rId)) {
|
||||
$imageSource = $target;
|
||||
} else {
|
||||
@@ -271,7 +386,7 @@ abstract class AbstractPart
|
||||
$embedId = $xmlReader->getAttribute('r:embed', $node, 'wp:anchor/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip');
|
||||
}
|
||||
$target = $this->getMediaTarget($docPart, $embedId);
|
||||
if (null !== $target) {
|
||||
if ($this->hasImageLoading() && null !== $target) {
|
||||
$imageSource = "zip://{$this->docFile}#{$target}";
|
||||
$parent->addImage($imageSource, null, false, $name);
|
||||
}
|
||||
@@ -320,9 +435,12 @@ abstract class AbstractPart
|
||||
$type = ($runParent->nodeName == 'w:del') ? TrackChange::DELETED : TrackChange::INSERTED;
|
||||
$author = $runParent->getAttribute('w:author');
|
||||
$date = DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $runParent->getAttribute('w:date'));
|
||||
$date = $date instanceof DateTime ? $date : null;
|
||||
$element->setChangeInfo($type, $author, $date);
|
||||
}
|
||||
}
|
||||
} elseif ($node->nodeName == 'w:softHyphen') {
|
||||
$element = $parent->addText("\u{200c}", $fontStyle, $paragraphStyle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,9 +482,8 @@ abstract class AbstractPart
|
||||
} elseif ('w:tc' == $rowNode->nodeName) { // Cell
|
||||
$cellWidth = $xmlReader->getAttribute('w:w', $rowNode, 'w:tcPr/w:tcW');
|
||||
$cellStyle = null;
|
||||
$cellStyleNode = $xmlReader->getElement('w:tcPr', $rowNode);
|
||||
if (null !== $cellStyleNode) {
|
||||
$cellStyle = $this->readCellStyle($xmlReader, $cellStyleNode);
|
||||
if ($xmlReader->elementExists('w:tcPr', $rowNode)) {
|
||||
$cellStyle = $this->readCellStyle($xmlReader, $rowNode);
|
||||
}
|
||||
|
||||
$cell = $row->addCell($cellWidth, $cellStyle);
|
||||
@@ -374,6 +491,8 @@ abstract class AbstractPart
|
||||
foreach ($cellNodes as $cellNode) {
|
||||
if ('w:p' == $cellNode->nodeName) { // Paragraph
|
||||
$this->readParagraph($xmlReader, $cellNode, $cell, $docPart);
|
||||
} elseif ($cellNode->nodeName == 'w:tbl') { // Table
|
||||
$this->readTable($xmlReader, $cellNode, $cell, $docPart);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -550,7 +669,7 @@ abstract class AbstractPart
|
||||
/**
|
||||
* Read w:tcPr.
|
||||
*
|
||||
* @return array
|
||||
* @return null|array
|
||||
*/
|
||||
private function readCellStyle(XMLReader $xmlReader, DOMElement $domNode)
|
||||
{
|
||||
@@ -558,11 +677,28 @@ abstract class AbstractPart
|
||||
'valign' => [self::READ_VALUE, 'w:vAlign'],
|
||||
'textDirection' => [self::READ_VALUE, 'w:textDirection'],
|
||||
'gridSpan' => [self::READ_VALUE, 'w:gridSpan'],
|
||||
'vMerge' => [self::READ_VALUE, 'w:vMerge'],
|
||||
'vMerge' => [self::READ_VALUE, 'w:vMerge', null, null, 'continue'],
|
||||
'bgColor' => [self::READ_VALUE, 'w:shd', 'w:fill'],
|
||||
'noWrap' => [self::READ_VALUE, 'w:noWrap', null, null, true],
|
||||
];
|
||||
$style = null;
|
||||
|
||||
return $this->readStyleDefs($xmlReader, $domNode, $styleDefs);
|
||||
if ($xmlReader->elementExists('w:tcPr', $domNode)) {
|
||||
$styleNode = $xmlReader->getElement('w:tcPr', $domNode);
|
||||
|
||||
$borders = ['top', 'left', 'bottom', 'right'];
|
||||
foreach ($borders as $side) {
|
||||
$ucfSide = ucfirst($side);
|
||||
|
||||
$styleDefs['border' . $ucfSide . 'Size'] = [self::READ_VALUE, 'w:tcBorders/w:' . $side, 'w:sz'];
|
||||
$styleDefs['border' . $ucfSide . 'Color'] = [self::READ_VALUE, 'w:tcBorders/w:' . $side, 'w:color'];
|
||||
$styleDefs['border' . $ucfSide . 'Style'] = [self::READ_VALUE, 'w:tcBorders/w:' . $side, 'w:val'];
|
||||
}
|
||||
|
||||
$style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs);
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -614,7 +750,6 @@ abstract class AbstractPart
|
||||
/**
|
||||
* Read style definition.
|
||||
*
|
||||
* @param DOMElement $parentNode
|
||||
* @param array $styleDefs
|
||||
*
|
||||
* @ignoreScrutinizerPatch
|
||||
@@ -626,7 +761,7 @@ abstract class AbstractPart
|
||||
$styles = [];
|
||||
|
||||
foreach ($styleDefs as $styleProp => $styleVal) {
|
||||
[$method, $element, $attribute, $expected] = array_pad($styleVal, 4, null);
|
||||
[$method, $element, $attribute, $expected, $default] = array_pad($styleVal, 5, null);
|
||||
|
||||
$element = $this->findPossibleElement($xmlReader, $parentNode, $element);
|
||||
if ($element === null) {
|
||||
@@ -640,7 +775,7 @@ abstract class AbstractPart
|
||||
|
||||
// Use w:val as default if no attribute assigned
|
||||
$attribute = ($attribute === null) ? 'w:val' : $attribute;
|
||||
$attributeValue = $xmlReader->getAttribute($attribute, $node);
|
||||
$attributeValue = $xmlReader->getAttribute($attribute, $node) ?? $default;
|
||||
|
||||
$styleValue = $this->readStyleDef($method, $attributeValue, $expected);
|
||||
if ($styleValue !== null) {
|
||||
|
||||
56
vendor/phpoffice/phpword/src/PhpWord/Reader/Word2007/Comments.php
vendored
Normal file
56
vendor/phpoffice/phpword/src/PhpWord/Reader/Word2007/Comments.php
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpWord\Reader\Word2007;
|
||||
|
||||
use DateTime;
|
||||
use PhpOffice\PhpWord\Element\Comment;
|
||||
use PhpOffice\PhpWord\PhpWord;
|
||||
use PhpOffice\PhpWord\Shared\XMLReader;
|
||||
|
||||
class Comments extends AbstractPart
|
||||
{
|
||||
/**
|
||||
* Collection name comments.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $collection = 'comments';
|
||||
|
||||
/**
|
||||
* Read settings.xml.
|
||||
*/
|
||||
public function read(PhpWord $phpWord): void
|
||||
{
|
||||
$xmlReader = new XMLReader();
|
||||
$xmlReader->getDomFromZip($this->docFile, $this->xmlFile);
|
||||
|
||||
$comments = $phpWord->getComments();
|
||||
|
||||
$nodes = $xmlReader->getElements('*');
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$name = str_replace('w:', '', $node->nodeName);
|
||||
|
||||
$author = $xmlReader->getAttribute('w:author', $node);
|
||||
$date = $xmlReader->getAttribute('w:date', $node);
|
||||
$initials = $xmlReader->getAttribute('w:initials', $node);
|
||||
|
||||
$element = new Comment($author, new DateTime($date), $initials);
|
||||
|
||||
$range = $this->getCommentReference($xmlReader->getAttribute('w:id', $node));
|
||||
if ($range['start']) {
|
||||
$range['start']->setCommentRangeStart($element);
|
||||
}
|
||||
if ($range['end']) {
|
||||
$range['end']->setCommentRangeEnd($element);
|
||||
}
|
||||
|
||||
$pNodes = $xmlReader->getElements('w:p/w:r', $node);
|
||||
foreach ($pNodes as $pNode) {
|
||||
$this->readRun($xmlReader, $pNode, $element, $this->collection);
|
||||
}
|
||||
|
||||
$phpWord->getComments()->addItem($element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,10 @@ use PhpOffice\PhpWord\Style\Language;
|
||||
*/
|
||||
class Settings extends AbstractPart
|
||||
{
|
||||
private static $booleanProperties = [
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
private $booleanProperties = [
|
||||
'mirrorMargins',
|
||||
'hideSpellingErrors',
|
||||
'hideGrammaticalErrors',
|
||||
@@ -41,6 +44,7 @@ class Settings extends AbstractPart
|
||||
'updateFields',
|
||||
'autoHyphenation',
|
||||
'doNotHyphenateCaps',
|
||||
'bookFoldPrinting',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -60,12 +64,8 @@ class Settings extends AbstractPart
|
||||
$value = $xmlReader->getAttribute('w:val', $node);
|
||||
$method = 'set' . $name;
|
||||
|
||||
if (in_array($name, $this::$booleanProperties)) {
|
||||
if ($value == 'false') {
|
||||
$docSettings->$method(false);
|
||||
} else {
|
||||
$docSettings->$method(true);
|
||||
}
|
||||
if (in_array($name, $this->booleanProperties)) {
|
||||
$docSettings->$method($value !== 'false');
|
||||
} elseif (method_exists($this, $method)) {
|
||||
$this->$method($xmlReader, $phpWord, $node);
|
||||
} elseif (method_exists($docSettings, $method)) {
|
||||
|
||||
@@ -39,14 +39,16 @@ class Styles extends AbstractPart
|
||||
$fontDefaults = $xmlReader->getElement('w:docDefaults/w:rPrDefault');
|
||||
if ($fontDefaults !== null) {
|
||||
$fontDefaultStyle = $this->readFontStyle($xmlReader, $fontDefaults);
|
||||
if (array_key_exists('name', $fontDefaultStyle)) {
|
||||
$phpWord->setDefaultFontName($fontDefaultStyle['name']);
|
||||
}
|
||||
if (array_key_exists('size', $fontDefaultStyle)) {
|
||||
$phpWord->setDefaultFontSize($fontDefaultStyle['size']);
|
||||
}
|
||||
if (array_key_exists('lang', $fontDefaultStyle)) {
|
||||
$phpWord->getSettings()->setThemeFontLang(new Language($fontDefaultStyle['lang']));
|
||||
if ($fontDefaultStyle) {
|
||||
if (array_key_exists('name', $fontDefaultStyle)) {
|
||||
$phpWord->setDefaultFontName($fontDefaultStyle['name']);
|
||||
}
|
||||
if (array_key_exists('size', $fontDefaultStyle)) {
|
||||
$phpWord->setDefaultFontSize($fontDefaultStyle['size']);
|
||||
}
|
||||
if (array_key_exists('lang', $fontDefaultStyle)) {
|
||||
$phpWord->getSettings()->setThemeFontLang(new Language($fontDefaultStyle['lang']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user