Skip to content

Commit

Permalink
Sheet view cleanup (#20)
Browse files Browse the repository at this point in the history
* Sizecollection cleanup.

* Optimized ctrl character escaping performance ty@adrilo

* merged sheetviews with pane xml & added method to get final dimension xml.
  • Loading branch information
nimmneun committed Sep 5, 2016
1 parent 01c44a7 commit cc980e8
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 72 deletions.
23 changes: 17 additions & 6 deletions src/OneSheet/CellBuilder.php
Expand Up @@ -5,7 +5,7 @@
use OneSheet\Xml\CellXml;

/**
* Class DefaultCellBuilder to build xml cell strings.
* Class CellBuilder to build xml cell strings.
* Wheter a numeric value is written as a number or string type cell
* is simply determined by type, to allow for some control by typecasting.
*
Expand All @@ -27,6 +27,13 @@ class CellBuilder
*/
private $escapeCharacters = array();

/**
* Regex pattern containing control characters.
*
* @var string
*/
private $escapeCharacterPattern;

/**
* CellBuilder constructor to create escaping arrays.
*/
Expand All @@ -38,6 +45,8 @@ public function __construct()
$this->escapeCharacters[] = sprintf('_x%04s_', strtoupper(dechex($key)));
}
}

$this->escapeCharacterPattern = implode($this->controlCharacters);
}

/**
Expand All @@ -58,9 +67,6 @@ public function build($rowNumber, $cellNumber, $cellValue, $styleId = 0)
return sprintf(CellXml::NUMBER_XML, $cellId, $styleId, $cellValue);
} elseif (is_bool($cellValue)) {
return sprintf(CellXml::BOOLEAN_XML, $cellId, $styleId, (int)$cellValue);
} elseif (is_numeric($cellValue) || 1 != preg_match('~[^\w]~', $cellValue)) {
return sprintf(CellXml::STRING_XML, $cellId, $styleId,
htmlspecialchars($cellValue, ENT_QUOTES));
}

return sprintf(CellXml::STRING_XML, $cellId, $styleId, $this->escape($cellValue));
Expand Down Expand Up @@ -93,8 +99,13 @@ public function getCellId($cellNumber, $rowNumber = null)
*/
private function escape($value)
{
return str_replace($this->controlCharacters, $this->escapeCharacters,
htmlspecialchars($value, ENT_QUOTES));
if (1 !== preg_match('~[' . preg_quote($this->escapeCharacterPattern) . ']~', $value)) {
return htmlspecialchars($value, ENT_QUOTES);
}

return str_replace(
$this->controlCharacters, $this->escapeCharacters, htmlspecialchars($value, ENT_QUOTES)
);
}
}

6 changes: 2 additions & 4 deletions src/OneSheet/Finalizer.php
Expand Up @@ -75,10 +75,8 @@ private function finalizeSheet()
$this->sheetFile->fwrite('</sheetData></worksheet>');
$this->sheetFile->rewind();
$this->sheetFile->fwrite(SheetXml::HEADER_XML);
$this->sheetFile->fwrite(sprintf(SheetXml::DIMENSION_XML,
$this->sheet->getDimensionUpperBound()));
$this->sheetFile->fwrite(sprintf(SheetXml::SHEETVIEWS_XML,
$this->sheet->getFreezePaneXml()));
$this->sheetFile->fwrite($this->sheet->getDimensionXml());
$this->sheetFile->fwrite($this->sheet->getSheetViewsXml());
$this->writeColumnWidths();
$this->zip->addFile($this->sheetFile->getFilePath(), 'xl/worksheets/sheet1.xml');
}
Expand Down
34 changes: 18 additions & 16 deletions src/OneSheet/Sheet.php
Expand Up @@ -154,16 +154,6 @@ public function getColumnWidths()
return $this->columnWidths;
}

/**
* Return cellId for dimensions.
*
* @return string
*/
public function getDimensionUpperBound()
{
return $this->cellBuilder->getCellId($this->maxColumnCount, $this->rowIndex - 1);
}

/**
* Add single row and style to sheet.
*
Expand Down Expand Up @@ -255,17 +245,29 @@ private function updateColumnWidths($value, $cellIndex, Style $style)
}

/**
* Return freeze pane xml string for sheetView.
* Return xml string for dimension.
*
* @return string
*/
public function getFreezePaneXml()
public function getDimensionXml()
{
if (!$this->freezePaneCellId
|| 1 !== preg_match('~^[A-Z]+(\d+)$~', $this->freezePaneCellId, $m)
) {
return sprintf(SheetXml::DIMENSION_XML,
$this->cellBuilder->getCellId($this->maxColumnCount - 1, $this->rowIndex - 1)
);
}

/**
* Return sheetViews xml containing the freeze pane.
* <sheetViews> Currently leads to random excel crashes :-/
*
* @return string
*/
public function getSheetViewsXml()
{
if (1 !== preg_match('~^[A-Z]+(\d+)$~', $this->freezePaneCellId, $m)) {
return '';
}
return sprintf(SheetXml::FREEZE_PANE_XML, array_pop($m) - 1, $this->freezePaneCellId);

return sprintf(SheetXml::SHEETVIEWS_XML, array_pop($m) - 1, $this->freezePaneCellId);
}
}
4 changes: 1 addition & 3 deletions src/OneSheet/SheetFile.php
Expand Up @@ -37,9 +37,7 @@ public function __construct()
*/
public function fwrite($string)
{
if (!fwrite($this->filePointer, $string)) {
throw new \RuntimeException("Failed to write to sheet file!");
}
fwrite($this->filePointer, $string);
}

/**
Expand Down
73 changes: 41 additions & 32 deletions src/OneSheet/Size/SizeCollection.php
Expand Up @@ -19,32 +19,14 @@ class SizeCollection
*
* @var array
*/
private static $widths = array();
private $widths = array();

/**
* Create character width map for each font.
* SizeCollection constructor.
*/
public function __construct()
{
self::loadWidthsFromCsv(dirname(__FILE__) . '/size_collection.csv');
}

/**
* Dirty way to allow developers to load character widths that
* are not yet included.
*
* @param string $csvPath
*/
public static function loadWidthsFromCsv($csvPath)
{
$fh = fopen($csvPath, 'r');
$head = fgetcsv($fh);
unset($head[0], $head[1]);
while ($row = fgetcsv($fh)) {
$fontName = array_shift($row);
$fontSize = array_shift($row);
self::$widths[$fontName][$fontSize] = array_combine($head, $row);
}
$this->loadWidthsFromCsv();
}

/**
Expand All @@ -56,11 +38,11 @@ public static function loadWidthsFromCsv($csvPath)
*/
public function get($fontName, $fontSize)
{
if (isset(self::$widths[$fontName][$fontSize])) {
return self::$widths[$fontName][$fontSize];
if (isset($this->widths[$fontName][$fontSize])) {
return $this->widths[$fontName][$fontSize];
}

return self::calculate($fontName, $fontSize);
return $this->calculate($fontName, $fontSize);
}

/**
Expand All @@ -70,16 +52,16 @@ public function get($fontName, $fontSize)
* @param int $fontSize
* @return array
*/
private static function calculate($fontName, $fontSize)
private function calculate($fontName, $fontSize)
{
foreach (self::getBaseWidths($fontName) as $character => $width) {
foreach ($this->getBaseWidths($fontName) as $character => $width) {
if ('bold' !== $character) {
$width = round($width / self::BASE_SIZE * $fontSize, 3);
}
self::$widths[$fontName][$fontSize][$character] = $width;
$this->widths[$fontName][$fontSize][$character] = $width;
}

return self::$widths[$fontName][$fontSize];
return $this->widths[$fontName][$fontSize];
}

/**
Expand All @@ -88,11 +70,38 @@ private static function calculate($fontName, $fontSize)
* @param string $fontName
* @return array
*/
private static function getBaseWidths($fontName)
private function getBaseWidths($fontName)
{
if (isset($this->widths[$fontName])) {
return $this->widths[$fontName][self::BASE_SIZE];
}
return $this->widths['Calibri'][self::BASE_SIZE];
}

/**
* Initialize collection from csv file.
*/
public function loadWidthsFromCsv()
{
if (isset(self::$widths[$fontName])) {
return self::$widths[$fontName][self::BASE_SIZE];
$fh = fopen(dirname(__FILE__) . '/size_collection.csv', 'r');
$head = fgetcsv($fh);
unset($head[0], $head[1]);

while ($row = fgetcsv($fh)) {
$this->addSizesToCollection($head, $row);
}
return self::$widths['Calibri'][self::BASE_SIZE];
}

/**
* Add character widths for a single font.
*
* @param array $head
* @param array$row
*/
private function addSizesToCollection(array $head, array $row)
{
$fontName = array_shift($row);
$fontSize = array_shift($row);
$this->widths[$fontName][$fontSize] = array_combine($head, $row);
}
}
11 changes: 3 additions & 8 deletions src/OneSheet/Xml/SheetXml.php
Expand Up @@ -15,19 +15,14 @@ class SheetXml
const HEADER_XML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">';

/**
* Constant for single sheetview xml string.
*/
const SHEETVIEWS_XML = '<sheetViews><sheetView tabSelected="1" workbookViewId="0" showGridLines="true" showRowColHeaders="1">%s</sheetView></sheetViews>';

/**
* Constant for dimesion xml string.
* Constant for dimension xml string.
*/
const DIMENSION_XML = '<dimension ref="A1:%s"/>';

/**
* Constant for sheetview pane.
* Constant for sheetview xml string containing the freeze pane xml.
*/
const FREEZE_PANE_XML = '<pane ySplit="%d" topLeftCell="%s" activePane="bottomLeft" state="frozen"/>';
const SHEETVIEWS_XML = '<sheetViews><sheetView tabSelected="1" workbookViewId="0"><pane ySplit="%d" topLeftCell="%s" activePane="bottomLeft" state="frozen"/></sheetView></sheetViews>';

/**
* Constant for single column width string.
Expand Down
6 changes: 3 additions & 3 deletions tests/OneSheetTest/SheetTest.php
Expand Up @@ -33,11 +33,11 @@ public function testEnableAutoSizing()
public function testGetFreezePaneXml()
{
$sheet = new Sheet(new CellBuilder(), new SizeCalculator(new SizeCollection()));
$this->assertEquals('', $sheet->getFreezePaneXml());
$this->assertEquals('', $sheet->getSheetViewsXml());

$sheet->setFreezePaneCellId('A5');
$expectedXml = '<pane ySplit="4" topLeftCell="A5" activePane="bottomLeft" state="frozen"/>';
$this->assertEquals($expectedXml, $sheet->getFreezePaneXml());
$expectedXml = '<sheetViews><sheetView tabSelected="1" workbookViewId="0"><pane ySplit="4" topLeftCell="A5" activePane="bottomLeft" state="frozen"/></sheetView></sheetViews>';
$this->assertEquals($expectedXml, $sheet->getSheetViewsXml());
}

public function testWidthCalculation()
Expand Down

0 comments on commit cc980e8

Please sign in to comment.