diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index b4b5d829..9b3a7aa0 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -22,9 +22,22 @@ use PhpOffice\PhpWord\Shared\XMLReader; /** * Abstract part reader + * + * This class is inherited by ODText reader */ abstract class AbstractPart { + /** + * Conversion method + * + * @const int + */ + const READ_VALUE = 'attributeValue'; // Read attribute value + const READ_EQUAL = 'attributeEquals'; // Read `true` when attribute value equals specified value + const READ_TRUE = 'attributeTrue'; // Read `true` when element exists + const READ_FALSE = 'attributeFalse'; // Read `false` when element exists + const READ_SIZE = 'attributeMultiplyByTwo'; // Read special attribute value for Font::$size + /** * Document file * @@ -145,58 +158,26 @@ abstract class AbstractPart protected function readParagraphStyle(XMLReader $xmlReader, \DOMElement $domNode) { if (!$xmlReader->elementExists('w:pPr', $domNode)) { - return ''; + return null; } - $style = array(); - $mapping = array( - 'w:pStyle' => 'styleName', - 'w:ind' => 'indent', 'w:spacing' => 'spacing', - 'w:jc' => 'align', 'w:basedOn' => 'basedOn', 'w:next' => 'next', - 'w:widowControl' => 'widowControl', 'w:keepNext' => 'keepNext', - 'w:keepLines' => 'keepLines', 'w:pageBreakBefore' => 'pageBreakBefore', + $styleNode = $xmlReader->getElement('w:pPr', $domNode); + $styleDefs = array( + 'styleName' => array(self::READ_VALUE, 'w:pStyle'), + 'align' => array(self::READ_VALUE, 'w:jc'), + 'basedOn' => array(self::READ_VALUE, 'w:basedOn'), + 'next' => array(self::READ_VALUE, 'w:next'), + 'indent' => array(self::READ_VALUE, 'w:ind', 'w:left'), + 'hanging' => array(self::READ_VALUE, 'w:ind', 'w:hanging'), + 'spaceAfter' => array(self::READ_VALUE, 'w:spacing', 'w:after'), + 'spaceBefore' => array(self::READ_VALUE, 'w:spacing', 'w:before'), + 'widowControl' => array(self::READ_FALSE, 'w:widowControl'), + 'keepNext' => array(self::READ_TRUE, 'w:keepNext'), + 'keepLines' => array(self::READ_TRUE, 'w:keepLines'), + 'pageBreakBefore' => array(self::READ_TRUE, 'w:pageBreakBefore'), ); - $nodes = $xmlReader->getElements('w:pPr/*', $domNode); - foreach ($nodes as $node) { - if (!array_key_exists($node->nodeName, $mapping)) { - continue; - } - $property = $mapping[$node->nodeName]; - switch ($node->nodeName) { - - case 'w:ind': - $style['indent'] = $xmlReader->getAttribute('w:left', $node); - $style['hanging'] = $xmlReader->getAttribute('w:hanging', $node); - break; - - case 'w:spacing': - $style['spaceAfter'] = $xmlReader->getAttribute('w:after', $node); - $style['spaceBefore'] = $xmlReader->getAttribute('w:before', $node); - // Commented. Need to adjust the number when return value is null - // $style['spacing'] = $xmlReader->getAttribute('w:line', $node); - break; - - case 'w:keepNext': - case 'w:keepLines': - case 'w:pageBreakBefore': - $style[$property] = true; - break; - - case 'w:widowControl': - $style[$property] = false; - break; - - case 'w:pStyle': - case 'w:jc': - case 'w:basedOn': - case 'w:next': - $style[$property] = $xmlReader->getAttribute('w:val', $node); - break; - } - } - - return $style; + return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); } /** @@ -204,7 +185,7 @@ abstract class AbstractPart * * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader * @param \DOMElement $domNode - * @return array|null + * @return array */ protected function readFontStyle(XMLReader $xmlReader, \DOMElement $domNode) { @@ -219,62 +200,26 @@ abstract class AbstractPart return null; } - $style = array(); - $mapping = array( - 'w:rStyle' => 'styleName', - 'w:b' => 'bold', 'w:i' => 'italic', 'w:color' => 'color', 'w:u' => 'underline', - 'w:strike' => 'strikethrough', 'w:dstrike' => 'doubleStrikethrough', - 'w:highlight' => 'fgColor', 'w:sz' => 'size', - 'w:rFonts' => 'name', 'w:vertAlign' => 'superScript', - 'w:smallCaps' => 'smallCaps', 'w:caps' => 'allCaps', + $styleNode = $xmlReader->getElement('w:rPr', $domNode); + $styleDefs = array( + 'styleName' => array(self::READ_VALUE, 'w:rStyle'), + 'name' => array(self::READ_VALUE, 'w:rFonts', 'w:ascii'), + 'hint' => array(self::READ_VALUE, 'w:rFonts', 'w:hint'), + 'size' => array(self::READ_SIZE, 'w:sz'), + 'color' => array(self::READ_VALUE, 'w:color'), + 'underline' => array(self::READ_VALUE, 'w:u'), + 'bold' => array(self::READ_TRUE, 'w:b'), + 'italic' => array(self::READ_TRUE, 'w:i'), + 'strikethrough' => array(self::READ_TRUE, 'w:strike'), + 'doubleStrikethrough' => array(self::READ_TRUE, 'w:dstrike'), + 'smallCaps' => array(self::READ_TRUE, 'w:smallCaps'), + 'allCaps' => array(self::READ_TRUE, 'w:caps'), + 'superScript' => array(self::READ_EQUAL, 'w:vertAlign', 'w:val', 'superscript'), + 'subScript' => array(self::READ_EQUAL, 'w:vertAlign', 'w:val', 'subscript'), + 'fgColor' => array(self::READ_VALUE, 'w:highlight'), ); - $nodes = $xmlReader->getElements('w:rPr/*', $domNode); - foreach ($nodes as $node) { - if (!array_key_exists($node->nodeName, $mapping)) { - continue; - } - $property = $mapping[$node->nodeName]; - switch ($node->nodeName) { - - case 'w:rFonts': - $style['name'] = $xmlReader->getAttribute('w:ascii', $node); - $style['hint'] = $xmlReader->getAttribute('w:hint', $node); - break; - - case 'w:b': - case 'w:i': - case 'w:strike': - case 'w:dstrike': - case 'w:smallCaps': - case 'w:caps': - $style[$property] = true; - break; - - case 'w:rStyle': - case 'w:u': - case 'w:highlight': - case 'w:color': - $style[$property] = $xmlReader->getAttribute('w:val', $node); - break; - - case 'w:sz': - $style[$property] = $xmlReader->getAttribute('w:val', $node) / 2; - break; - - case 'w:vertAlign': - $style[$property] = $xmlReader->getAttribute('w:val', $node); - if ($style[$property] == 'superscript') { - $style['superScript'] = true; - } else { - $style['superScript'] = false; - $style['subScript'] = true; - } - break; - } - } - - return $style; + return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); } /** @@ -295,42 +240,64 @@ abstract class AbstractPart if ($xmlReader->elementExists('w:tblPr/w:tblStyle', $domNode)) { $style = $xmlReader->getAttribute('w:val', $domNode, 'w:tblPr/w:tblStyle'); } else { - $style = array(); - $mapping = array( - 'w:tblCellMar' => 'cellMargin', - 'w:tblBorders' => 'border', - ); - - $nodes = $xmlReader->getElements('w:tblPr/*', $domNode); - foreach ($nodes as $node) { - if (!array_key_exists($node->nodeName, $mapping)) { - continue; - } - // $property = $mapping[$node->nodeName]; - switch ($node->nodeName) { - - case 'w:tblCellMar': - foreach ($margins as $side) { - $ucfSide = ucfirst($side); - $style["cellMargin$ucfSide"] = $xmlReader->getAttribute('w:w', $node, "w:$side"); - } - break; - - case 'w:tblBorders': - foreach ($borders as $side) { - $ucfSide = ucfirst($side); - $style["border{$ucfSide}Size"] = $xmlReader->getAttribute('w:sz', $node, "w:$side"); - $style["border{$ucfSide}Color"] = $xmlReader->getAttribute('w:color', $node, "w:$side"); - } - break; - } + $styleNode = $xmlReader->getElement('w:tblPr', $domNode); + // $styleDefs['styleName'] = array(self::READ_VALUE, 'w:tblStyle'); + foreach ($margins as $side) { + $ucfSide = ucfirst($side); + $styleDefs["cellMargin$ucfSide"] = array(self::READ_VALUE, "w:tblCellMar/w:$side", 'w:w'); } + foreach ($borders as $side) { + $ucfSide = ucfirst($side); + $styleDefs["border{$ucfSide}Size"] = array(self::READ_VALUE, "w:tblBorders/w:$side", 'w:sz'); + $styleDefs["border{$ucfSide}Color"] = array(self::READ_VALUE, "w:tblBorders/w:$side", 'w:color'); + } + $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); } } return $style; } + /** + * Read style definition + * + * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader + * @param \DOMElement $domNode + * @param array $styleDefs + * @return array + */ + protected function readStyleDefs(XMLReader $xmlReader, \DOMElement $parentNode, $styleDefs) + { + $styles = array(); + + foreach ($styleDefs as $styleProp => $styleVal) { + @list($method, $element, $attribute, $expected) = $styleVal; + + if ($xmlReader->elementExists($element, $parentNode)) { + $node = $xmlReader->getElement($element, $parentNode); + + // Use w:val as default if no attribute assigned + $attribute = ($attribute === null) ? 'w:val' : $attribute; + $attributeValue = $xmlReader->getAttribute($attribute, $node); + + // Assign style value based on conversion model + if ($method == self::READ_VALUE) { + $styles[$styleProp] = $attributeValue; + } elseif ($method == self::READ_SIZE) { + $styles[$styleProp] = $attributeValue / 2; + } elseif ($method == self::READ_TRUE) { + $styles[$styleProp] = true; + } elseif ($method == self::READ_FALSE) { + $styles[$styleProp] = false; + } elseif ($method == self::READ_EQUAL && $attributeValue == $expected) { + $styles[$styleProp] = true; + } + } + } + + return $styles; + } + /** * Returns the target of image, object, or link as stored in ::readMainRels * diff --git a/src/PhpWord/Reader/Word2007/Document.php b/src/PhpWord/Reader/Word2007/Document.php index a57a4127..bf736013 100644 --- a/src/PhpWord/Reader/Word2007/Document.php +++ b/src/PhpWord/Reader/Word2007/Document.php @@ -56,9 +56,7 @@ class Document extends AbstractPart if (!is_null($settingsNode)) { $settings = $this->readSectionStyle($xmlReader, $settingsNode); $section->setSettings($settings); - if (!is_null($settings)) { - $this->readHeaderFooter($settings, $section); - } + $this->readHeaderFooter($settings, $section); } $section = $phpWord->addSection(); } @@ -71,9 +69,7 @@ class Document extends AbstractPart case 'w:sectPr': // Last section $settings = $this->readSectionStyle($xmlReader, $node); $section->setSettings($settings); - if (!is_null($settings)) { - $this->readHeaderFooter($settings, $section); - } + $this->readHeaderFooter($settings, $section); break; } } @@ -273,61 +269,41 @@ class Document extends AbstractPart * * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader * @param \DOMElement $domNode - * @return array|null + * @return array */ private function readSectionStyle(XMLReader $xmlReader, \DOMElement $domNode) { - $ret = null; - $mapping = array( - 'w:type' => 'breakType', 'w:pgSz' => 'pageSize', - 'w:pgMar' => 'pageMargin', 'w:cols' => 'columns', - 'w:headerReference' => 'header', 'w:footerReference' => 'footer', + $styleDefs = array( + 'breakType' => array(self::READ_VALUE, 'w:type'), + 'pageSizeW' => array(self::READ_VALUE, 'w:pgSz', 'w:w'), + 'pageSizeH' => array(self::READ_VALUE, 'w:pgSz', 'w:h'), + 'orientation' => array(self::READ_VALUE, 'w:pgSz', 'w:orient'), + 'colsNum' => array(self::READ_VALUE, 'w:cols', 'w:num'), + 'colsSpace' => array(self::READ_VALUE, 'w:cols', 'w:space'), + 'topMargin' => array(self::READ_VALUE, 'w:pgMar', 'w:top'), + 'leftMargin' => array(self::READ_VALUE, 'w:pgMar', 'w:left'), + 'bottomMargin' => array(self::READ_VALUE, 'w:pgMar', 'w:bottom'), + 'rightMargin' => array(self::READ_VALUE, 'w:pgMar', 'w:right'), + 'headerHeight' => array(self::READ_VALUE, 'w:pgMar', 'w:header'), + 'footerHeight' => array(self::READ_VALUE, 'w:pgMar', 'w:footer'), + 'gutter' => array(self::READ_VALUE, 'w:pgMar', 'w:gutter'), ); + $styles = $this->readStyleDefs($xmlReader, $domNode, $styleDefs); + + // Header and footer + // @todo Cleanup this part $nodes = $xmlReader->getElements('*', $domNode); foreach ($nodes as $node) { - if (!array_key_exists($node->nodeName, $mapping)) { - continue; - } - $property = $mapping[$node->nodeName]; - switch ($node->nodeName) { - - case 'w:type': - $ret['breakType'] = $xmlReader->getAttribute('w:val', $node); - break; - - case 'w:pgSz': - $ret['pageSizeW'] = $xmlReader->getAttribute('w:w', $node); - $ret['pageSizeH'] = $xmlReader->getAttribute('w:h', $node); - $ret['orientation'] = $xmlReader->getAttribute('w:orient', $node); - break; - - case 'w:pgMar': - $ret['topMargin'] = $xmlReader->getAttribute('w:top', $node); - $ret['leftMargin'] = $xmlReader->getAttribute('w:left', $node); - $ret['bottomMargin'] = $xmlReader->getAttribute('w:bottom', $node); - $ret['rightMargin'] = $xmlReader->getAttribute('w:right', $node); - $ret['headerHeight'] = $xmlReader->getAttribute('w:header', $node); - $ret['footerHeight'] = $xmlReader->getAttribute('w:footer', $node); - $ret['gutter'] = $xmlReader->getAttribute('w:gutter', $node); - break; - - case 'w:cols': - $ret['colsNum'] = $xmlReader->getAttribute('w:num', $node); - $ret['colsSpace'] = $xmlReader->getAttribute('w:space', $node); - break; - - case 'w:headerReference': - case 'w:footerReference': - $id = $xmlReader->getAttribute('r:id', $node); - $ret['hf'][$id] = array( - 'method' => $property, - 'type' => $xmlReader->getAttribute('w:type', $node), - ); - break; + if ($node->nodeName == 'w:headerReference' || $node->nodeName == 'w:footerReference') { + $id = $xmlReader->getAttribute('r:id', $node); + $styles['hf'][$id] = array( + 'method' => str_replace('w:', '', str_replace('Reference', '', $node->nodeName)), + 'type' => $xmlReader->getAttribute('w:type', $node), + ); } } - return $ret; + return $styles; } /** @@ -335,33 +311,18 @@ class Document extends AbstractPart * * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader * @param \DOMElement $domNode - * @return array|null + * @return array */ private function readCellStyle(XMLReader $xmlReader, \DOMElement $domNode) { - $style = null; - $mapping = array( - 'w:shd' => 'bgColor', - 'w:vAlign' => 'valign', 'w:textDirection' => 'textDirection', - 'w:gridSpan' => 'gridSpan', 'w:vMerge' => 'vMerge', + $styleDefs = array( + 'valign' => array(self::READ_VALUE, 'w:vAlign'), + 'textDirection' => array(self::READ_VALUE, 'w:textDirection'), + 'gridSpan' => array(self::READ_VALUE, 'w:gridSpan'), + 'vMerge' => array(self::READ_VALUE, 'w:vMerge'), + 'bgColor' => array(self::READ_VALUE, 'w:shd/w:fill'), ); - $nodes = $xmlReader->getElements('*', $domNode); - foreach ($nodes as $node) { - if (!array_key_exists($node->nodeName, $mapping)) { - continue; - } - $property = $mapping[$node->nodeName]; - switch ($node->nodeName) { - case 'w:shd': - $style['bgColor'] = $xmlReader->getAttribute('w:fill', $node); - break; - default: - $style[$property] = $xmlReader->getAttribute('w:val', $node); - break; - } - } - - return $style; + return $this->readStyleDefs($xmlReader, $domNode, $styleDefs); } }