Bugfix for #236 (OpenOffice crash when opening DOCX) and paragraph style refactoring

This commit is contained in:
Ivan Lanin 2014-05-30 19:59:57 +07:00
parent d3ac5b1fa1
commit 900a96addf
6 changed files with 338 additions and 249 deletions

View File

@ -32,6 +32,7 @@ This release marked the change of PHPWord license from LGPL 2.1 to LGPL 3. Four
- Element: New `Field` element - @basjan GH-251 - Element: New `Field` element - @basjan GH-251
- RTF Reader: Basic RTF reader - @ivanlanin GH-72 GH-252 - RTF Reader: Basic RTF reader - @ivanlanin GH-72 GH-252
- Element: New `Line` element - @basjan GH-253 - Element: New `Line` element - @basjan GH-253
- Title: Ability to apply numbering in heading - @ivanlanin GH-193
### Bugfixes ### Bugfixes
@ -39,6 +40,7 @@ This release marked the change of PHPWord license from LGPL 2.1 to LGPL 3. Four
- Conversion: Fix conversion from cm to pixel, pixel to cm, and pixel to point - @basjan GH-233 GH-234 - Conversion: Fix conversion from cm to pixel, pixel to cm, and pixel to point - @basjan GH-233 GH-234
- PageBreak: Page break adds new line in the beginning of the new page - @ivanlanin GH-150 - PageBreak: Page break adds new line in the beginning of the new page - @ivanlanin GH-150
- Image: `marginLeft` and `marginTop` cannot accept float value - @ivanlanin GH-248 - Image: `marginLeft` and `marginTop` cannot accept float value - @ivanlanin GH-248
- Title: Orphan `w:fldChar` caused OpenOffice to crash when opening DOCX - @ivanlanin GH-236
### Deprecated ### Deprecated
@ -61,6 +63,9 @@ This release marked the change of PHPWord license from LGPL 2.1 to LGPL 3. Four
- Docs: Show code quality and test code coverage badge on README - Docs: Show code quality and test code coverage badge on README
- Style: Change behaviour of `set...` function of boolean properties; when none is defined, assumed true - @ivanlanin - Style: Change behaviour of `set...` function of boolean properties; when none is defined, assumed true - @ivanlanin
- Shared: Unify PHP ZipArchive and PCLZip features into PhpWord ZipArchive - @ivanlanin - Shared: Unify PHP ZipArchive and PCLZip features into PhpWord ZipArchive - @ivanlanin
- Docs: Create VERSION file - @ivanlanin
- QA: Improve dan update requirement check in `samples` folder - @ivanlanin
## 0.10.1 - 21 May 2014 ## 0.10.1 - 21 May 2014

View File

@ -22,6 +22,30 @@ use PhpOffice\PhpWord\Shared\String;
/** /**
* Paragraph style * Paragraph style
*
* OOXML:
* - General: alignment, outline level
* - Indentation: left, right, firstline, hanging
* - Spacing: before, after, line spacing
* - Pagination: widow control, keep next, keep line, page break before
* - Formatting exception: suppress line numbers, don't hyphenate
* - Textbox options
* - Tabs
* - Shading
* - Borders
*
* OpenOffice:
* - Indents & spacing
* - Alignment
* - Text flow
* - Outline & numbering
* - Tabs
* - Dropcaps
* - Tabs
* - Borders
* - Background
*
* @link http://www.schemacentral.com/sc/ooxml/t-w_CT_PPr.html
*/ */
class Paragraph extends AbstractStyle class Paragraph extends AbstractStyle
{ {
@ -37,20 +61,6 @@ class Paragraph extends AbstractStyle
*/ */
protected $aliases = array('line-height' => 'lineHeight'); protected $aliases = array('line-height' => 'lineHeight');
/**
* Text line height
*
* @var int
*/
private $lineHeight;
/**
* Set of Custom Tab Stops
*
* @var \PhpOffice\PhpWord\Style\Tab[]
*/
private $tabs = array();
/** /**
* Parent style * Parent style
* *
@ -65,6 +75,34 @@ class Paragraph extends AbstractStyle
*/ */
private $next; private $next;
/**
* Alignment
*
* @var \PhpOffice\PhpWord\Style\Alignment
*/
private $alignment;
/**
* Indentation
*
* @var \PhpOffice\PhpWord\Style\Indentation
*/
private $indentation;
/**
* Spacing
*
* @var \PhpOffice\PhpWord\Style\Spacing
*/
private $spacing;
/**
* Text line height
*
* @var int
*/
private $lineHeight;
/** /**
* Allow first/last line to display on a separate page * Allow first/last line to display on a separate page
* *
@ -93,27 +131,6 @@ class Paragraph extends AbstractStyle
*/ */
private $pageBreakBefore = false; private $pageBreakBefore = false;
/**
* Indentation
*
* @var \PhpOffice\PhpWord\Style\Indentation
*/
private $indentation;
/**
* Spacing
*
* @var \PhpOffice\PhpWord\Style\Spacing
*/
private $spacing;
/**
* Alignment
*
* @var \PhpOffice\PhpWord\Style\Alignment
*/
private $alignment;
/** /**
* Numbering style name * Numbering style name
* *
@ -128,6 +145,13 @@ class Paragraph extends AbstractStyle
*/ */
private $numLevel = 0; private $numLevel = 0;
/**
* Set of Custom Tab Stops
*
* @var \PhpOffice\PhpWord\Style\Tab[]
*/
private $tabs = array();
/** /**
* Create new instance * Create new instance
*/ */
@ -155,6 +179,38 @@ class Paragraph extends AbstractStyle
return parent::setStyleValue($key, $value); return parent::setStyleValue($key, $value);
} }
/**
* Get style values
*
* An experiment to retrieve all style values in one function. This will
* reduce function call and increase cohesion between functions. Should be
* implemented in all styles.
*
* @return array
*/
public function getStyleValues()
{
return array(
'name' => $this->getStyleName(),
'basedOn' => $this->getBasedOn(),
'next' => $this->getNext(),
'alignment' => $this->getAlign(),
'indentation' => $this->getIndentation(),
'spacing' => $this->getSpace(),
'pagination' => array(
'widowControl' => $this->hasWidowControl(),
'keepNext' => $this->isKeepNext(),
'keepLines' => $this->isKeepLines(),
'pageBreak' => $this->hasPageBreakBefore(),
),
'numbering' => array(
'style' => $this->getNumStyle(),
'level' => $this->getNumLevel(),
),
'tabs' => $this->getTabs(),
);
}
/** /**
* Get alignment * Get alignment
* *
@ -178,6 +234,150 @@ class Paragraph extends AbstractStyle
return $this; return $this;
} }
/**
* Get parent style ID
*
* @return string
*/
public function getBasedOn()
{
return $this->basedOn;
}
/**
* Set parent style ID
*
* @param string $value
* @return self
*/
public function setBasedOn($value = 'Normal')
{
$this->basedOn = $value;
return $this;
}
/**
* Get style for next paragraph
*
* @return string
*/
public function getNext()
{
return $this->next;
}
/**
* Set style for next paragraph
*
* @param string $value
* @return self
*/
public function setNext($value = null)
{
$this->next = $value;
return $this;
}
/**
* Get shading
*
* @return \PhpOffice\PhpWord\Style\Indentation
*/
public function getIndentation()
{
return $this->indentation;
}
/**
* Set shading
*
* @param mixed $value
* @return self
*/
public function setIndentation($value = null)
{
$this->setObjectVal($value, 'Indentation', $this->indentation);
return $this;
}
/**
* Get indentation
*
* @return int
*/
public function getIndent()
{
if ($this->indentation !== null) {
return $this->indentation->getLeft();
} else {
return null;
}
}
/**
* Set indentation
*
* @param int $value
* @return self
*/
public function setIndent($value = null)
{
return $this->setIndentation(array('left' => $value));
}
/**
* Get hanging
*
* @return int
*/
public function getHanging()
{
if ($this->indentation !== null) {
return $this->indentation->getHanging();
} else {
return null;
}
}
/**
* Set hanging
*
* @param int $value
* @return self
*/
public function setHanging($value = null)
{
return $this->setIndentation(array('hanging' => $value));
}
/**
* Get spacing
*
* @return \PhpOffice\PhpWord\Style\Spacing
* @todo Rename to getSpacing in 1.0
*/
public function getSpace()
{
return $this->spacing;
}
/**
* Set spacing
*
* @param mixed $value
* @return self
* @todo Rename to setSpacing in 1.0
*/
public function setSpace($value = null)
{
$this->setObjectVal($value, 'Spacing', $this->spacing);
return $this;
}
/** /**
* Get space before paragraph * Get space before paragraph
* *
@ -285,127 +485,6 @@ class Paragraph extends AbstractStyle
return $this; return $this;
} }
/**
* Get indentation
*
* @return int
*/
public function getIndent()
{
if ($this->indentation !== null) {
return $this->indentation->getLeft();
} else {
return null;
}
}
/**
* Set indentation
*
* @param int $value
* @return self
*/
public function setIndent($value = null)
{
return $this->setIndentation(array('left' => $value));
}
/**
* Get hanging
*
* @return int
*/
public function getHanging()
{
if ($this->indentation !== null) {
return $this->indentation->getHanging();
} else {
return null;
}
}
/**
* Set hanging
*
* @param int $value
* @return self
*/
public function setHanging($value = null)
{
return $this->setIndentation(array('hanging' => $value));
}
/**
* Get tabs
*
* @return \PhpOffice\PhpWord\Style\Tab[]
*/
public function getTabs()
{
return $this->tabs;
}
/**
* Set tabs
*
* @param array $value
* @return self
*/
public function setTabs($value = null)
{
if (is_array($value)) {
$this->tabs = $value;
}
return $this;
}
/**
* Get parent style ID
*
* @return string
*/
public function getBasedOn()
{
return $this->basedOn;
}
/**
* Set parent style ID
*
* @param string $value
* @return self
*/
public function setBasedOn($value = 'Normal')
{
$this->basedOn = $value;
return $this;
}
/**
* Get style for next paragraph
*
* @return string
*/
public function getNext()
{
return $this->next;
}
/**
* Set style for next paragraph
*
* @param string $value
* @return self
*/
public function setNext($value = null)
{
$this->next = $value;
return $this;
}
/** /**
* Get allow first/last line to display on a separate page setting * Get allow first/last line to display on a separate page setting
* *
@ -498,54 +577,6 @@ class Paragraph extends AbstractStyle
return $this; return $this;
} }
/**
* Get shading
*
* @return \PhpOffice\PhpWord\Style\Indentation
*/
public function getIndentation()
{
return $this->indentation;
}
/**
* Set shading
*
* @param mixed $value
* @return self
*/
public function setIndentation($value = null)
{
$this->setObjectVal($value, 'Indentation', $this->indentation);
return $this;
}
/**
* Get shading
*
* @return \PhpOffice\PhpWord\Style\Spacing
* @todo Rename to getSpacing in 1.0
*/
public function getSpace()
{
return $this->spacing;
}
/**
* Set shading
*
* @param mixed $value
* @return self
* @todo Rename to setSpacing in 1.0
*/
public function setSpace($value = null)
{
$this->setObjectVal($value, 'Spacing', $this->spacing);
return $this;
}
/** /**
* Get numbering style name * Get numbering style name
* *
@ -592,6 +623,31 @@ class Paragraph extends AbstractStyle
return $this; return $this;
} }
/**
* Get tabs
*
* @return \PhpOffice\PhpWord\Style\Tab[]
*/
public function getTabs()
{
return $this->tabs;
}
/**
* Set tabs
*
* @param array $value
* @return self
*/
public function setTabs($value = null)
{
if (is_array($value)) {
$this->tabs = $value;
}
return $this;
}
/** /**
* Get allow first/last line to display on a separate page setting * Get allow first/last line to display on a separate page setting
* *

View File

@ -74,7 +74,7 @@ class TOC extends AbstractElement
$tocStyle = $element->getStyleTOC(); $tocStyle = $element->getStyleTOC();
$fontStyle = $element->getStyleFont(); $fontStyle = $element->getStyleFont();
$isObject = ($fontStyle instanceof Font) ? true : false; $isObject = ($fontStyle instanceof Font) ? true : false;
$anchor = '_Toc' . ($title->getRelationId() + 252634154); $rId = $title->getRelationId();
$indent = ($title->getDepth() - 1) * $tocStyle->getIndent(); $indent = ($title->getDepth() - 1) * $tocStyle->getIndent();
$xmlWriter->startElement('w:p'); $xmlWriter->startElement('w:p');
@ -87,7 +87,7 @@ class TOC extends AbstractElement
// Hyperlink // Hyperlink
$xmlWriter->startElement('w:hyperlink'); $xmlWriter->startElement('w:hyperlink');
$xmlWriter->writeAttribute('w:anchor', $anchor); $xmlWriter->writeAttribute('w:anchor', "_Toc{$rId}");
$xmlWriter->writeAttribute('w:history', '1'); $xmlWriter->writeAttribute('w:history', '1');
// Title text // Title text
@ -114,7 +114,7 @@ class TOC extends AbstractElement
$xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:r');
$xmlWriter->startElement('w:instrText'); $xmlWriter->startElement('w:instrText');
$xmlWriter->writeAttribute('xml:space', 'preserve'); $xmlWriter->writeAttribute('xml:space', 'preserve');
$xmlWriter->writeRaw('PAGEREF ' . $anchor . ' \h'); $xmlWriter->writeRaw("PAGEREF _Toc{$rId} \h");
$xmlWriter->endElement(); $xmlWriter->endElement();
$xmlWriter->endElement(); $xmlWriter->endElement();

View File

@ -35,8 +35,6 @@ class Title extends AbstractElement
return; return;
} }
$rId = $element->getRelationId();
$anchor = '_Toc' . ($rId + 252634154);
$style = $element->getStyle(); $style = $element->getStyle();
$xmlWriter->startElement('w:p'); $xmlWriter->startElement('w:p');
@ -49,23 +47,22 @@ class Title extends AbstractElement
$xmlWriter->endElement(); $xmlWriter->endElement();
} }
$xmlWriter->startElement('w:r'); $rId = $element->getRelationId();
$xmlWriter->startElement('w:fldChar');
$xmlWriter->writeAttribute('w:fldCharType', 'end');
$xmlWriter->endElement();
$xmlWriter->endElement();
// Bookmark start for TOC
$xmlWriter->startElement('w:bookmarkStart'); $xmlWriter->startElement('w:bookmarkStart');
$xmlWriter->writeAttribute('w:id', $rId); $xmlWriter->writeAttribute('w:id', $rId);
$xmlWriter->writeAttribute('w:name', $anchor); $xmlWriter->writeAttribute('w:name', "_Toc{$rId}");
$xmlWriter->endElement(); $xmlWriter->endElement();
// Actual text
$xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:r');
$xmlWriter->startElement('w:t'); $xmlWriter->startElement('w:t');
$xmlWriter->writeRaw($this->getText($element->getText())); $xmlWriter->writeRaw($this->getText($element->getText()));
$xmlWriter->endElement(); $xmlWriter->endElement();
$xmlWriter->endElement(); $xmlWriter->endElement();
// Bookmark end
$xmlWriter->startElement('w:bookmarkEnd'); $xmlWriter->startElement('w:bookmarkEnd');
$xmlWriter->writeAttribute('w:id', $rId); $xmlWriter->writeAttribute('w:id', $rId);
$xmlWriter->endElement(); $xmlWriter->endElement();

View File

@ -17,6 +17,7 @@
namespace PhpOffice\PhpWord\Writer\Word2007\Style; namespace PhpOffice\PhpWord\Writer\Word2007\Style;
use PhpOffice\PhpWord\Shared\XMLWriter;
use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\Style\Alignment as AlignmentStyle; use PhpOffice\PhpWord\Style\Alignment as AlignmentStyle;
@ -74,41 +75,66 @@ class Paragraph extends AbstractStyle
return; return;
} }
$xmlWriter = $this->getXmlWriter(); $xmlWriter = $this->getXmlWriter();
$styles = $style->getStyleValues();
if (!$this->withoutPPR) { if (!$this->withoutPPR) {
$xmlWriter->startElement('w:pPr'); $xmlWriter->startElement('w:pPr');
} }
// Style name // Style name
$styleName = $style->getStyleName(); $xmlWriter->writeElementIf($styles['name'] !== null, 'w:pStyle', 'w:val', $styles['name']);
$xmlWriter->writeElementIf(!is_null($styleName), 'w:pStyle', 'w:val', $styleName);
// Alignment // Alignment
$styleWriter = new Alignment($xmlWriter, new AlignmentStyle(array('value' => $style->getAlign()))); $styleWriter = new Alignment($xmlWriter, new AlignmentStyle(array('value' => $styles['alignment'])));
$styleWriter->write(); $styleWriter->write();
// Pagination // Pagination
$xmlWriter->writeElementIf(!$style->hasWidowControl(), 'w:widowControl', 'w:val', '0'); $xmlWriter->writeElementIf($styles['pagination']['widowControl'] === false, 'w:widowControl', 'w:val', '0');
$xmlWriter->writeElementIf($style->isKeepNext(), 'w:keepNext', 'w:val', '1'); $xmlWriter->writeElementIf($styles['pagination']['keepNext'] === true, 'w:keepNext', 'w:val', '1');
$xmlWriter->writeElementIf($style->isKeepLines(), 'w:keepLines', 'w:val', '1'); $xmlWriter->writeElementIf($styles['pagination']['keepLines'] === true, 'w:keepLines', 'w:val', '1');
$xmlWriter->writeElementIf($style->hasPageBreakBefore(), 'w:pageBreakBefore', 'w:val', '1'); $xmlWriter->writeElementIf($styles['pagination']['pageBreak'] === true, 'w:pageBreakBefore', 'w:val', '1');
// Indentation // Indentation & spacing
$indentation = $style->getIndentation(); $this->writeChildStyle($xmlWriter, 'Indentation', $styles['indentation']);
if (!is_null($indentation)) { $this->writeChildStyle($xmlWriter, 'Spacing', $styles['spacing']);
$styleWriter = new Indentation($xmlWriter, $indentation);
$styleWriter->write();
}
// Spacing
$spacing = $style->getSpace();
if (!is_null($spacing)) {
$styleWriter = new Spacing($xmlWriter, $spacing);
$styleWriter->write();
}
// Tabs // Tabs
$tabs = $style->getTabs(); $this->writeTabs($xmlWriter, $styles['tabs']);
// Numbering
$this->writeNumbering($xmlWriter, $styles['numbering']);
if (!$this->withoutPPR) {
$xmlWriter->endElement(); // w:pPr
}
}
/**
* Write child style
*
* @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter
* @param string $name
* @param string $value
*/
private function writeChildStyle(XMLWriter $xmlWriter, $name, $value)
{
if ($value !== null) {
$class = "PhpOffice\\PhpWord\\Writer\\Word2007\\Style\\" . $name;
/** @var \PhpOffice\PhpWord\Writer\Word2007\Style\AbstractStyle $writer */
$writer = new $class($xmlWriter, $value);
$writer->write();
}
}
/**
* Write tabs
*
* @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter
* @param array $tabs
*/
private function writeTabs(XMLWriter $xmlWriter, $tabs)
{
if (!empty($tabs)) { if (!empty($tabs)) {
$xmlWriter->startElement("w:tabs"); $xmlWriter->startElement("w:tabs");
foreach ($tabs as $tab) { foreach ($tabs as $tab) {
@ -117,28 +143,35 @@ class Paragraph extends AbstractStyle
} }
$xmlWriter->endElement(); $xmlWriter->endElement();
} }
}
// Numbering /**
$numStyleName = $style->getNumStyle(); * Write numbering
$numStyleObject = Style::getStyle($numStyleName); *
if ($numStyleName !== null && $numStyleObject !== null) { * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter
* @param array $numbering
*/
private function writeNumbering(XMLWriter $xmlWriter, $numbering)
{
$numStyle = $numbering['style'];
$numLevel = $numbering['level'];
/** @var \PhpOffice\PhpWord\Style\Numbering $numbering */
$numbering = Style::getStyle($numStyle);
if ($numStyle !== null && $numbering !== null) {
$xmlWriter->startElement('w:numPr'); $xmlWriter->startElement('w:numPr');
$xmlWriter->startElement('w:numId'); $xmlWriter->startElement('w:numId');
$xmlWriter->writeAttribute('w:val', $numStyleObject->getIndex()); $xmlWriter->writeAttribute('w:val', $numbering->getIndex());
$xmlWriter->endElement(); // w:numId $xmlWriter->endElement(); // w:numId
$xmlWriter->startElement('w:ilvl'); $xmlWriter->startElement('w:ilvl');
$xmlWriter->writeAttribute('w:val', $style->getNumLevel()); $xmlWriter->writeAttribute('w:val', $numLevel);
$xmlWriter->endElement(); // w:ilvl $xmlWriter->endElement(); // w:ilvl
$xmlWriter->endElement(); // w:numPr $xmlWriter->endElement(); // w:numPr
$xmlWriter->startElement('w:outlineLvl'); $xmlWriter->startElement('w:outlineLvl');
$xmlWriter->writeAttribute('w:val', $style->getNumLevel()); $xmlWriter->writeAttribute('w:val', $numLevel);
$xmlWriter->endElement(); // w:outlineLvl $xmlWriter->endElement(); // w:outlineLvl
} }
if (!$this->withoutPPR) {
$xmlWriter->endElement(); // w:pPr
}
} }
/** /**

View File

@ -342,8 +342,6 @@ class DocumentTest extends \PHPUnit_Framework_TestCase
$element = "/w:document/w:body/w:p/w:pPr/w:pStyle"; $element = "/w:document/w:body/w:p/w:pPr/w:pStyle";
$this->assertEquals('Heading1', $doc->getElementAttribute($element, 'w:val')); $this->assertEquals('Heading1', $doc->getElementAttribute($element, 'w:val'));
$element = "/w:document/w:body/w:p/w:r/w:fldChar";
$this->assertEquals('end', $doc->getElementAttribute($element, 'w:fldCharType'));
} }
/** /**