Compare commits

...
19 Commits
Author SHA1 Message Date
Adrien Loison 7964dadc21 Add support for cells in error when writing XLSX and ODS
When appending data to an existing sheet, it was possible to get cells in error when reading (DIV/0 for instance). When trying to write them back, `addRow` would throw because `Cell`s in error were not supported by the writers.
2019-12-02 22:21:41 +01:00
drowe eb88bb4c3a Automated native_function_invocation fixes 2019-11-18 12:17:27 +01:00
madflow 94b654175c disable no_superfluous_phpdoc_tags 2019-11-18 11:20:20 +01:00
Adrien Loison dbdf5f7f38 [ODS] Add support for whitespaces inside <text:span>
The `<text:p>` node can contain the string value directly or contain child elements. In this case, whitespaces contain in the child elements should be replaced by their XML equivalent:
 - space => `<text:s />`
 - tab => `<text:tab />`
 - line break => `<text:line-break />`

@see https://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1415200_253892949
2019-10-27 20:14:51 +01:00
Adrien Loison 9f4c094fa0 Cell alignment
This PR adds support for cell alignment for XLSX and ODS files.
You can now align the content of the cells this way:
```
use Box\Spout\Common\Entity\Style\CellAlignment;
use Box\Spout\Writer\Common\Creator\Style\StyleBuilder;

$style = (new StyleBuilder())
    ->setCellAlignment(CellAlignment::RIGHT)
    ->build();
...
```

Possible cell alignments are: LEFT, RIGHT, CENTER and JUSTIFY.
2019-10-27 18:58:56 +01:00
Adrien Loison 0a0b1f7196 [docs] Fix site_url - take 2 2019-10-17 13:12:40 +00:00
Adrien Loison db32a7c7db [docs] Fix site_url 2019-10-17 11:15:55 +00:00
Adrien Loison 859b8d336e [Docs] Force using current protocol scheme for resources and links
If the docs is accessed with https, change all links to be https as well.
2019-10-17 10:32:39 +00:00
Adrien Loison 8a2dcc946b Improve docs for local dev 2019-10-17 09:29:03 +00:00
Adrien Loison 74146c6224 Switch documentation site to HTTPS
Accessing the website through HTTP may be blocked in some regions of the world.
Using HTTPS should help with this problem and is the good thing to do anyway.
2019-10-17 07:44:13 +00:00
Adrien Loison 6a6d1df9df Fix typo - Boder => Border 2019-10-17 07:37:37 +00:00
Alexander Rakushin 52312b7045 Added tests for StyleRegistry and StyleMerger 2019-10-01 20:08:54 +00:00
Alexander Rakushin d4e12b1812 Simplification of the code 2019-10-01 20:08:54 +00:00
Alexander Rakushin e8c6d83104 сode formatting (CS Fixer) 2019-10-01 20:08:54 +00:00
Alexander Rakushin 40aecd7b90 Added support for cell formats when writing Excel files 2019-10-01 20:08:54 +00:00
Adrien Loison 16a2f91a22 Cell indexes not being respected when rendering row
Fixes #682
When calling `Row::setCellIndex`, it's possible to create a Row with holes.
Instead of iterating over existing cells of a Row, we should instead use the cell indexes (from 0 to max cell index).
2019-09-28 14:02:23 +00:00
Ilya Troy 2d297e954b Update getting-started.md
Fix writer usage example
2019-07-30 22:14:20 +02:00
Adrien Loison 2716d7eeed [PHP 7.4] Updated way to disable the fgetcsv/fputcsv escape character
From PHP 7.4, the recommended way to disable the escape character for fgetcsv() and fputcsv() is an empty string, instead of "\0".
Discussed here: https://github.com/php/php-src/pull/3515
2019-07-21 23:15:34 +02:00
Adrien Loison 1bbfd45b82 Support for missing styles XML file in XLSX
Some files don't have a "styles.xml" file. Excel supports these files, Spout should do too.
2019-07-20 16:48:51 +02:00
103 changed files with 1064 additions and 370 deletions

No files matched your search

+1
View File
@@ -21,6 +21,7 @@ $config = PhpCsFixer\Config::create()
'no_null_property_initialization' => true, 'no_null_property_initialization' => true,
'no_short_echo_tag' => true, 'no_short_echo_tag' => true,
'no_superfluous_elseif' => true, 'no_superfluous_elseif' => true,
'no_superfluous_phpdoc_tags' => false,
'no_unneeded_control_parentheses' => ['statements' => ['break', 'clone', 'continue', 'echo_print', 'switch_case', 'yield']], 'no_unneeded_control_parentheses' => ['statements' => ['break', 'clone', 'continue', 'echo_print', 'switch_case', 'yield']],
'no_unneeded_curly_braces' => true, 'no_unneeded_curly_braces' => true,
'no_unneeded_final_method' => true, 'no_unneeded_final_method' => true,
+1 -1
View File
@@ -6,7 +6,7 @@ All contributions are welcome to this project.
Before a contribution can be merged into this project, please fill out the Contributor License Agreement (CLA) located at: Before a contribution can be merged into this project, please fill out the Contributor License Agreement (CLA) located at:
http://opensource.box.com/cla https://opensource.box.com/cla
To learn more about CLAs and why they are important to open source projects, please see the [Wikipedia entry](http://en.wikipedia.org/wiki/Contributor_License_Agreement). To learn more about CLAs and why they are important to open source projects, please see the [Wikipedia entry](http://en.wikipedia.org/wiki/Contributor_License_Agreement).
+2 -2
View File
@@ -1,7 +1,7 @@
# Spout # Spout
[![Latest Stable Version](https://poser.pugx.org/box/spout/v/stable)](https://packagist.org/packages/box/spout) [![Latest Stable Version](https://poser.pugx.org/box/spout/v/stable)](https://packagist.org/packages/box/spout)
[![Project Status](http://opensource.box.com/badges/active.svg)](http://opensource.box.com/badges) [![Project Status](https://opensource.box.com/badges/active.svg)](https://opensource.box.com/badges)
[![Build Status](https://travis-ci.org/box/spout.svg?branch=master)](https://travis-ci.org/box/spout) [![Build Status](https://travis-ci.org/box/spout.svg?branch=master)](https://travis-ci.org/box/spout)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/box/spout/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/box/spout/?branch=master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/box/spout/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/box/spout/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/box/spout/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/box/spout/?branch=master) [![Code Coverage](https://scrutinizer-ci.com/g/box/spout/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/box/spout/?branch=master)
@@ -15,7 +15,7 @@ Join the community and come discuss about Spout: [![Gitter](https://badges.gitte
## Documentation ## Documentation
Full documentation can be found at [http://opensource.box.com/spout/](http://opensource.box.com/spout/). Full documentation can be found at [https://opensource.box.com/spout/](https://opensource.box.com/spout/).
## Requirements ## Requirements
+1 -1
View File
@@ -10,7 +10,7 @@ title: Spout
email: oss@box.com email: oss@box.com
description: "An open source PHP library to read and write spreadsheet files (XLSX, ODS and CSV), in a fast and scalable way." description: "An open source PHP library to read and write spreadsheet files (XLSX, ODS and CSV), in a fast and scalable way."
baseurl: "" # the subpath of your site, e.g. /blog baseurl: "" # the subpath of your site, e.g. /blog
url: "http://opensource.box.com" # the base hostname & protocol for your site url: "https://opensource.box.com" # the base hostname & protocol for your site
# Build settings # Build settings
markdown: kramdown markdown: kramdown
+7
View File
@@ -0,0 +1,7 @@
# Github Metadata plugin
repository: box/spout
plugins:
- "jekyll-github-metadata"
github:
url: http://localhost:8080
+1 -1
View File
@@ -11,7 +11,7 @@
<div class="footer-col footer-col-2"> <div class="footer-col footer-col-2">
<ul class="social-media-list"> <ul class="social-media-list">
<li><a href="https://github.com/box/spout/issues"><span class="icon icon--github">{% include icon-github.svg %}</span><span class="username">GitHub Issues</span></a></li> <li><a href="{{ site.github.issues_url }}"><span class="icon icon--github">{% include icon-github.svg %}</span><span class="username">GitHub Issues</span></a></li>
</ul> </ul>
</div> </div>
+1 -1
View File
@@ -6,7 +6,7 @@
<title>{% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}</title> <title>{% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}</title>
<meta name="description" content="{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines | truncate: 160 }}{% else %}{{ site.description }}{% endif %}"> <meta name="description" content="{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
<link rel="stylesheet" href="{{ "/css/main.css" | prepend: site.github.url }}"> <link rel="stylesheet" href="{{ "/css/main.css" | prepend: site_url }}">
{% if site.algolia.enabled %} {% if site.algolia.enabled %}
<link rel="stylesheet" href="//cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css" /> <link rel="stylesheet" href="//cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css" />
{% endif %} {% endif %}
+6 -6
View File
@@ -1,7 +1,7 @@
<header class="site-header"> <header class="site-header">
<div class="wrapper"> <div class="wrapper">
<a class="site-title" href="{{ site.github.url }}/">{{ site.title }}</a> <a class="site-title" href="{{ site_url }}/">{{ site.title }}</a>
<nav class="site-nav"> <nav class="site-nav">
<a href="#" class="menu-icon"> <a href="#" class="menu-icon">
@@ -12,11 +12,11 @@
</svg> </svg>
</a> </a>
<div class="trigger"> <div class="trigger">
<a class="page-link" href="{{ site.github.url }}/getting-started/">Getting Started</a> <a class="page-link" href="{{ site_url }}/getting-started/">Getting Started</a>
<a class="page-link" href="{{ site.github.url }}/docs/">Documentation</a> <a class="page-link" href="{{ site_url }}/docs/">Documentation</a>
<a class="page-link" href="{{ site.github.url }}/guides/">Guides</a> <a class="page-link" href="{{ site_url }}/guides/">Guides</a>
<a class="page-link" href="{{ site.github.url }}/faq/">FAQ</a> <a class="page-link" href="{{ site_url }}/faq/">FAQ</a>
<a class="page-link" href="https://github.com/box/spout"> <a class="page-link" href="{{ site.github.repository_url }}">
<span class="icon icon--github">{% include icon-github.svg %}</span> <span class="icon icon--github">{% include icon-github.svg %}</span>
<span class="username">GitHub</span> <span class="username">GitHub</span>
</a> </a>
@@ -8,4 +8,4 @@
Writing an ODS file with millions of rows? {{ site.spout_html }} can do it in no time! Writing an ODS file with millions of rows? {{ site.spout_html }} can do it in no time!
{% endcapture %} {% endcapture %}
{% include section.html %} {% include section.html site_url = include.site_url %}
@@ -11,4 +11,4 @@
Switching from one type to another is ridiculously easy! Switching from one type to another is ridiculously easy!
{% endcapture %} {% endcapture %}
{% include section.html %} {% include section.html site_url=include.site_url %}
+3 -2
View File
@@ -13,8 +13,9 @@
<li class="feature-check">{{ site.spout_html }}'s API is {{emS}}developer-friendly{{emE}}.</li> <li class="feature-check">{{ site.spout_html }}'s API is {{emS}}developer-friendly{{emE}}.</li>
</ul><br> </ul><br>
<div class="btn-wrapper"> <div class="btn-wrapper">
<a href="{{ site.github.url }}/getting-started/" class="page-link"><button class="btn">Get started</button></a> <a href="{{ include.site_url }}/getting-started/" class="page-link"><button class="btn">Get started</button></a>
</div> </div>
{% endcapture %} {% endcapture %}
{% include section.html %} {% include section.html site_url=include.site_url %}
+1 -2
View File
@@ -1,4 +1,3 @@
<div class="section {{ sectionClass }} vertical-align-middle"> <div class="section {{ sectionClass }} vertical-align-middle">
<div class="wrapper"> <div class="wrapper">
<div class="description mbl"> <div class="description mbl">
@@ -9,7 +8,7 @@
{% assign sectionIconsAsArray = sectionIcons | split:'~~' %} {% assign sectionIconsAsArray = sectionIcons | split:'~~' %}
<div class="icons"> <div class="icons">
{% for icon in sectionIconsAsArray %} {% for icon in sectionIconsAsArray %}
<img src="{{ site.github.url }}/images/{{ icon }}"/> <img src="{{ include.site_url }}/images/{{ icon }}"/>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
+12
View File
@@ -0,0 +1,12 @@
{% comment %}
This is a hack to force using HTTPS...
The URL of the docs should be changed in the repo settings instead
{% endcomment %}
{% assign protocol_scheme = page.url | absolute_url | truncate: 5, "" %}
{%- capture site_url -%}
{%- if protocol_scheme == "https" -%}
{{ site.github.url | replace: "http://", "https://" }}
{%- else -%}
{{ site.github.url }}
{%- endif -%}
{%- endcapture -%}
+2 -8
View File
@@ -1,6 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
{% include set-global-site-url.html %}
{% include head.html %} {% include head.html %}
<body> <body>
@@ -13,14 +15,6 @@
{{ content }} {{ content }}
<!--
<div class="page-content">
<div class="wrapper">
</div>
</div>
-->
{% include footer.html %} {% include footer.html %}
{% include algolia.html %} {% include algolia.html %}
+1
View File
@@ -1,6 +1,7 @@
--- ---
layout: default layout: default
--- ---
<article class="post"> <article class="post">
<div class="wrapper"> <div class="wrapper">
<header class="post-header"> <header class="post-header">
-15
View File
@@ -1,15 +0,0 @@
---
layout: default
---
<article class="post" itemscope itemtype="http://schema.org/BlogPosting">
<header class="post-header">
<h1 class="post-title" itemprop="name headline">{{ page.title }}</h1>
<p class="post-meta"><time datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">{{ page.date | date: "%b %-d, %Y" }}</time>{% if page.author %} • <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span itemprop="name">{{ page.author }}</span></span>{% endif %}</p>
</header>
<div class="post-content" itemprop="articleBody">
{{ content }}
</div>
</article>
+5 -2
View File
@@ -117,7 +117,7 @@ $reader->setShouldPreserveEmptyRows(true);
For fonts and alignments, {{ site.spout_html }} does not support all the possible formatting options yet. But you can find the most important ones: For fonts and alignments, {{ site.spout_html }} does not support all the possible formatting options yet. But you can find the most important ones:
| Category | Property | API | Category | Property | API
|:----------|:--------------|:-------------------------------------- |:----------|:---------------|:--------------------------------------
| Font | Bold | `StyleBuilder::setFontBold()` | Font | Bold | `StyleBuilder::setFontBold()`
| | Italic | `StyleBuilder::setFontItalic()` | | Italic | `StyleBuilder::setFontItalic()`
| | Underline | `StyleBuilder::setFontUnderline()` | | Underline | `StyleBuilder::setFontUnderline()`
@@ -125,7 +125,8 @@ For fonts and alignments, {{ site.spout_html }} does not support all the possibl
| | Font name | `StyleBuilder::setFontName('Arial')` | | Font name | `StyleBuilder::setFontName('Arial')`
| | Font size | `StyleBuilder::setFontSize(14)` | | Font size | `StyleBuilder::setFontSize(14)`
| | Font color | `StyleBuilder::setFontColor(Color::BLUE)`<br>`StyleBuilder::setFontColor(Color::rgb(0, 128, 255))` | | Font color | `StyleBuilder::setFontColor(Color::BLUE)`<br>`StyleBuilder::setFontColor(Color::rgb(0, 128, 255))`
| Alignment | Wrap text | `StyleBuilder::setShouldWrapText(true|false)` | Alignment | Cell alignment | `StyleBuilder::setCellAlignment(CellAlignment::CENTER)`
| | Wrap text | `StyleBuilder::setShouldWrapText(true)`
### Styling rows ### Styling rows
@@ -135,6 +136,7 @@ It is possible to apply some formatting options to a row. In this case, all cell
```php ```php
use Box\Spout\Writer\Common\Creator\WriterEntityFactory; use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
use Box\Spout\Writer\Common\Creator\Style\StyleBuilder; use Box\Spout\Writer\Common\Creator\Style\StyleBuilder;
use Box\Spout\Common\Entity\Style\CellAlignment;
use Box\Spout\Common\Entity\Style\Color; use Box\Spout\Common\Entity\Style\Color;
$writer = WriterEntityFactory::createXLSXWriter(); $writer = WriterEntityFactory::createXLSXWriter();
@@ -146,6 +148,7 @@ $style = (new StyleBuilder())
->setFontSize(15) ->setFontSize(15)
->setFontColor(Color::BLUE) ->setFontColor(Color::BLUE)
->setShouldWrapText() ->setShouldWrapText()
->setCellAlignment(CellAlignment::RIGHT)
->setBackgroundColor(Color::YELLOW) ->setBackgroundColor(Color::YELLOW)
->build(); ->build();
+6 -2
View File
@@ -4,6 +4,8 @@ title: Getting Started
permalink: /getting-started/ permalink: /getting-started/
--- ---
{% include set-global-site-url.html %}
This guide will help you install {{ site.spout_html }} and teach you how to use it. This guide will help you install {{ site.spout_html }} and teach you how to use it.
## Requirements ## Requirements
@@ -86,7 +88,9 @@ As with the reader, there is one common interface to write data to a file:
use Box\Spout\Writer\Common\Creator\WriterEntityFactory; use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
use Box\Spout\Common\Entity\Row; use Box\Spout\Common\Entity\Row;
$reader = ReaderEntityFactory::createReaderFromFile('/path/to/file.ext'); $writer = WriterEntityFactory::createXLSXWriter();
// $writer = WriterEntityFactory::createODSWriter();
// $writer = WriterEntityFactory::createCSVWriter();
$writer->openToFile($filePath); // write data to a file or to a PHP stream $writer->openToFile($filePath); // write data to a file or to a PHP stream
//$writer->openToBrowser($fileName); // stream data directly to the browser //$writer->openToBrowser($fileName); // stream data directly to the browser
@@ -132,4 +136,4 @@ For XLSX and ODS files, the number of rows per sheet is limited to *1,048,576*.
## Advanced usage ## Advanced usage
You can do a lot more with {{ site.spout_html }}! Check out the [full documentation]({{ site.github.url }}/docs/) to learn about all the features. You can do a lot more with {{ site.spout_html }}! Check out the [full documentation]({{ site_url }}/docs/) to learn about all the features.
+4 -2
View File
@@ -4,15 +4,17 @@ title: Guides
permalink: /guides/ permalink: /guides/
--- ---
{% include set-global-site-url.html %}
These guides focus on common and more advanced usages of {{ site.spout_html }}.<br> These guides focus on common and more advanced usages of {{ site.spout_html }}.<br>
If you are just starting with {{ site.spout_html }}, check out the [Getting Started page]({{ site.github.url }}/getting-started/) and the [Documentation]({{ site.github.url }}/docs/) first. If you are just starting with {{ site.spout_html }}, check out the [Getting Started page]({{ site_url }}/getting-started/) and the [Documentation]({{ site_url }}/docs/) first.
{% assign pages=site.pages | sort: 'path' %} {% assign pages=site.pages | sort: 'path' %}
<ul> <ul>
{% for page in pages %} {% for page in pages %}
{% if page.title and page.category contains 'guide' %} {% if page.title and page.category contains 'guide' %}
<li> <li>
<a class="page-link" href="{{ page.url | prepend: site.github.url }}">{{ page.title }}</a> <a class="page-link" href="{{ page.url | prepend: site_url }}">{{ page.title }}</a>
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
+5 -3
View File
@@ -5,6 +5,8 @@ title: Spout - Read and write spreadsheets, quickly and at scale
permalink: / permalink: /
--- ---
{% include section-supported-spreadsheet-types.html %} {% include set-global-site-url.html %}
{% include section-fast-and-scalable.html %}
{% include section-why-use-spout.html %} {% include section-supported-spreadsheet-types.html site_url=site_url %}
{% include section-fast-and-scalable.html site_url=site_url %}
{% include section-why-use-spout.html site_url=site_url %}
+3 -1
View File
@@ -1,7 +1,9 @@
version: "3" version: "3"
services: services:
site: site:
command: jekyll serve command: bash -c "
gem install 'jekyll-github-metadata'
&& jekyll serve --config _config.yml,_config_local.yml"
image: jekyll/jekyll:latest image: jekyll/jekyll:latest
volumes: volumes:
- $PWD:/srv/jekyll - $PWD:/srv/jekyll
+11 -11
View File
@@ -23,7 +23,7 @@ class Psr4Autoloader
*/ */
public function register() public function register()
{ {
spl_autoload_register([$this, 'loadClass']); \spl_autoload_register([$this, 'loadClass']);
} }
/** /**
@@ -40,10 +40,10 @@ class Psr4Autoloader
public function addNamespace($prefix, $baseDir, $prepend = false) public function addNamespace($prefix, $baseDir, $prepend = false)
{ {
// normalize namespace prefix // normalize namespace prefix
$prefix = trim($prefix, '\\') . '\\'; $prefix = \trim($prefix, '\\') . '\\';
// normalize the base directory with a trailing separator // normalize the base directory with a trailing separator
$baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . '/'; $baseDir = \rtrim($baseDir, DIRECTORY_SEPARATOR) . '/';
// initialize the namespace prefix array // initialize the namespace prefix array
if (isset($this->prefixes[$prefix]) === false) { if (isset($this->prefixes[$prefix]) === false) {
@@ -52,9 +52,9 @@ class Psr4Autoloader
// retain the base directory for the namespace prefix // retain the base directory for the namespace prefix
if ($prepend) { if ($prepend) {
array_unshift($this->prefixes[$prefix], $baseDir); \array_unshift($this->prefixes[$prefix], $baseDir);
} else { } else {
array_push($this->prefixes[$prefix], $baseDir); \array_push($this->prefixes[$prefix], $baseDir);
} }
} }
@@ -72,12 +72,12 @@ class Psr4Autoloader
// work backwards through the namespace names of the fully-qualified // work backwards through the namespace names of the fully-qualified
// class name to find a mapped file name // class name to find a mapped file name
while (($pos = strrpos($prefix, '\\')) !== false) { while (($pos = \strrpos($prefix, '\\')) !== false) {
// retain the trailing namespace separator in the prefix // retain the trailing namespace separator in the prefix
$prefix = substr($class, 0, $pos + 1); $prefix = \substr($class, 0, $pos + 1);
// the rest is the relative class name // the rest is the relative class name
$relativeClass = substr($class, $pos + 1); $relativeClass = \substr($class, $pos + 1);
// try to load a mapped file for the prefix and relative class // try to load a mapped file for the prefix and relative class
$mappedFile = $this->loadMappedFile($prefix, $relativeClass); $mappedFile = $this->loadMappedFile($prefix, $relativeClass);
@@ -87,7 +87,7 @@ class Psr4Autoloader
// remove the trailing namespace separator for the next iteration // remove the trailing namespace separator for the next iteration
// of strrpos() // of strrpos()
$prefix = rtrim($prefix, '\\'); $prefix = \rtrim($prefix, '\\');
} }
// never found a mapped file // never found a mapped file
@@ -115,7 +115,7 @@ class Psr4Autoloader
// replace namespace separators with directory separators // replace namespace separators with directory separators
// in the relative class name, append with .php // in the relative class name, append with .php
$file = $baseDir $file = $baseDir
. str_replace('\\', '/', $relativeClass) . \str_replace('\\', '/', $relativeClass)
. '.php'; . '.php';
// if the mapped file exists, require it // if the mapped file exists, require it
@@ -137,7 +137,7 @@ class Psr4Autoloader
*/ */
protected function requireFile($file) protected function requireFile($file)
{ {
if (file_exists($file)) { if (\file_exists($file)) {
require $file; require $file;
return true; return true;
+1 -1
View File
@@ -8,7 +8,7 @@ require_once 'Psr4Autoloader.php';
* @var string * @var string
* Full path to "src/Spout" which is what we want "Box\Spout" to map to. * Full path to "src/Spout" which is what we want "Box\Spout" to map to.
*/ */
$srcBaseDirectory = dirname(dirname(__FILE__)); $srcBaseDirectory = \dirname(\dirname(__FILE__));
$loader = new Psr4Autoloader(); $loader = new Psr4Autoloader();
$loader->register(); $loader->register();
+8
View File
@@ -91,6 +91,14 @@ class Cell
return !$this->isError() ? $this->value : null; return !$this->isError() ? $this->value : null;
} }
/**
* @return mixed
*/
public function getValueEvenIfError()
{
return $this->value;
}
/** /**
* @param Style|null $style * @param Style|null $style
*/ */
+9 -3
View File
@@ -70,7 +70,7 @@ class Row
*/ */
public function getCellAtIndex($cellIndex) public function getCellAtIndex($cellIndex)
{ {
return isset($this->cells[$cellIndex]) ? $this->cells[$cellIndex] : null; return $this->cells[$cellIndex] ?? null;
} }
/** /**
@@ -89,7 +89,13 @@ class Row
*/ */
public function getNumCells() public function getNumCells()
{ {
return count($this->cells); // When using "setCellAtIndex", it's possible to
// have "$this->cells" contain holes.
if (empty($this->cells)) {
return 0;
}
return \max(\array_keys($this->cells)) + 1;
} }
/** /**
@@ -116,7 +122,7 @@ class Row
*/ */
public function toArray() public function toArray()
{ {
return array_map(function (Cell $cell) { return \array_map(function (Cell $cell) {
return $cell->getValue(); return $cell->getValue();
}, $this->cells); }, $this->cells);
} }
+3 -3
View File
@@ -93,7 +93,7 @@ class BorderPart
*/ */
public function setName($name) public function setName($name)
{ {
if (!in_array($name, self::$allowedNames)) { if (!\in_array($name, self::$allowedNames)) {
throw new InvalidNameException($name); throw new InvalidNameException($name);
} }
$this->name = $name; $this->name = $name;
@@ -114,7 +114,7 @@ class BorderPart
*/ */
public function setStyle($style) public function setStyle($style)
{ {
if (!in_array($style, self::$allowedStyles)) { if (!\in_array($style, self::$allowedStyles)) {
throw new InvalidStyleException($style); throw new InvalidStyleException($style);
} }
$this->style = $style; $this->style = $style;
@@ -152,7 +152,7 @@ class BorderPart
*/ */
public function setWidth($width) public function setWidth($width)
{ {
if (!in_array($width, self::$allowedWidths)) { if (!\in_array($width, self::$allowedWidths)) {
throw new InvalidWidthException($width); throw new InvalidWidthException($width);
} }
$this->width = $width; $this->width = $width;
@@ -0,0 +1,32 @@
<?php
namespace Box\Spout\Common\Entity\Style;
/**
* Class Alignment
* This class provides constants to work with text alignment.
*/
abstract class CellAlignment
{
const LEFT = 'left';
const RIGHT = 'right';
const CENTER = 'center';
const JUSTIFY = 'justify';
private static $VALID_ALIGNMENTS = [
self::LEFT => 1,
self::RIGHT => 1,
self::CENTER => 1,
self::JUSTIFY => 1,
];
/**
* @param string $cellAlignment
*
* @return bool Whether the given cell alignment is valid
*/
public static function isValid($cellAlignment)
{
return isset(self::$VALID_ALIGNMENTS[$cellAlignment]);
}
}
+4 -4
View File
@@ -8,7 +8,7 @@ use Box\Spout\Common\Exception\InvalidColorException;
* Class Color * Class Color
* This class provides constants and functions to work with colors * This class provides constants and functions to work with colors
*/ */
class Color abstract class Color
{ {
/** Standard colors - based on Office Online */ /** Standard colors - based on Office Online */
const BLACK = '000000'; const BLACK = '000000';
@@ -38,7 +38,7 @@ class Color
self::throwIfInvalidColorComponentValue($green); self::throwIfInvalidColorComponentValue($green);
self::throwIfInvalidColorComponentValue($blue); self::throwIfInvalidColorComponentValue($blue);
return strtoupper( return \strtoupper(
self::convertColorComponentToHex($red) . self::convertColorComponentToHex($red) .
self::convertColorComponentToHex($green) . self::convertColorComponentToHex($green) .
self::convertColorComponentToHex($blue) self::convertColorComponentToHex($blue)
@@ -54,7 +54,7 @@ class Color
*/ */
protected static function throwIfInvalidColorComponentValue($colorComponent) protected static function throwIfInvalidColorComponentValue($colorComponent)
{ {
if (!is_int($colorComponent) || $colorComponent < 0 || $colorComponent > 255) { if (!\is_int($colorComponent) || $colorComponent < 0 || $colorComponent > 255) {
throw new InvalidColorException("The RGB components must be between 0 and 255. Received: $colorComponent"); throw new InvalidColorException("The RGB components must be between 0 and 255. Received: $colorComponent");
} }
} }
@@ -67,7 +67,7 @@ class Color
*/ */
protected static function convertColorComponentToHex($colorComponent) protected static function convertColorComponentToHex($colorComponent)
{ {
return str_pad(dechex($colorComponent), 2, '0', STR_PAD_LEFT); return \str_pad(\dechex($colorComponent), 2, '0', STR_PAD_LEFT);
} }
/** /**
+81 -1
View File
@@ -8,7 +8,7 @@ namespace Box\Spout\Common\Entity\Style;
*/ */
class Style class Style
{ {
/** Default font values */ /** Default values */
const DEFAULT_FONT_SIZE = 11; const DEFAULT_FONT_SIZE = 11;
const DEFAULT_FONT_COLOR = Color::BLACK; const DEFAULT_FONT_COLOR = Color::BLACK;
const DEFAULT_FONT_NAME = 'Arial'; const DEFAULT_FONT_NAME = 'Arial';
@@ -54,6 +54,13 @@ class Style
/** @var bool Whether specific font properties should be applied */ /** @var bool Whether specific font properties should be applied */
private $shouldApplyFont = false; private $shouldApplyFont = false;
/** @var bool Whether specific cell alignment should be applied */
private $shouldApplyCellAlignment = false;
/** @var string Cell alignment */
private $cellAlignment;
/** @var bool Whether the cell alignment property was set */
private $hasSetCellAlignment = false;
/** @var bool Whether the text should wrap in the cell (useful for long or multi-lines text) */ /** @var bool Whether the text should wrap in the cell (useful for long or multi-lines text) */
private $shouldWrapText = false; private $shouldWrapText = false;
/** @var bool Whether the wrap text property was set */ /** @var bool Whether the wrap text property was set */
@@ -71,6 +78,12 @@ class Style
/** @var bool */ /** @var bool */
private $hasSetBackgroundColor = false; private $hasSetBackgroundColor = false;
/** @var string Format */
private $format;
/** @var bool */
private $hasSetFormat = false;
/** /**
* @return int|null * @return int|null
*/ */
@@ -319,6 +332,44 @@ class Style
return $this->hasSetFontName; return $this->hasSetFontName;
} }
/**
* @return string
*/
public function getCellAlignment()
{
return $this->cellAlignment;
}
/**
* @param string $cellAlignment The cell alignment
*
* @return Style
*/
public function setCellAlignment($cellAlignment)
{
$this->cellAlignment = $cellAlignment;
$this->hasSetCellAlignment = true;
$this->shouldApplyCellAlignment = true;
return $this;
}
/**
* @return bool
*/
public function hasSetCellAlignment()
{
return $this->hasSetCellAlignment;
}
/**
* @return bool Whether specific cell alignment should be applied
*/
public function shouldApplyCellAlignment()
{
return $this->shouldApplyCellAlignment;
}
/** /**
* @return bool * @return bool
*/ */
@@ -383,4 +434,33 @@ class Style
{ {
return $this->hasSetBackgroundColor; return $this->hasSetBackgroundColor;
} }
/**
* Sets format
* @param string $format
* @return Style
*/
public function setFormat($format)
{
$this->hasSetFormat = true;
$this->format = $format;
return $this;
}
/**
* @return string
*/
public function getFormat()
{
return $this->format;
}
/**
* @return bool Whether format should be applied
*/
public function shouldApplyFormat()
{
return $this->hasSetFormat;
}
} }
+3 -3
View File
@@ -23,7 +23,7 @@ class CellTypeHelper
*/ */
public static function isNonEmptyString($value) public static function isNonEmptyString($value)
{ {
return (gettype($value) === 'string' && $value !== ''); return (\gettype($value) === 'string' && $value !== '');
} }
/** /**
@@ -35,7 +35,7 @@ class CellTypeHelper
*/ */
public static function isNumeric($value) public static function isNumeric($value)
{ {
$valueType = gettype($value); $valueType = \gettype($value);
return ($valueType === 'integer' || $valueType === 'double'); return ($valueType === 'integer' || $valueType === 'double');
} }
@@ -49,7 +49,7 @@ class CellTypeHelper
*/ */
public static function isBoolean($value) public static function isBoolean($value)
{ {
return gettype($value) === 'boolean'; return \gettype($value) === 'boolean';
} }
/** /**
+3 -3
View File
@@ -61,7 +61,7 @@ class EncodingHelper
$bomUsed = $this->supportedEncodingsWithBom[$encoding]; $bomUsed = $this->supportedEncodingsWithBom[$encoding];
// we skip the N first bytes // we skip the N first bytes
$byteOffsetToSkipBom = strlen($bomUsed); $byteOffsetToSkipBom = \strlen($bomUsed);
} }
return $byteOffsetToSkipBom; return $byteOffsetToSkipBom;
@@ -80,9 +80,9 @@ class EncodingHelper
$this->globalFunctionsHelper->rewind($filePointer); $this->globalFunctionsHelper->rewind($filePointer);
if (array_key_exists($encoding, $this->supportedEncodingsWithBom)) { if (\array_key_exists($encoding, $this->supportedEncodingsWithBom)) {
$potentialBom = $this->supportedEncodingsWithBom[$encoding]; $potentialBom = $this->supportedEncodingsWithBom[$encoding];
$numBytesInBom = strlen($potentialBom); $numBytesInBom = \strlen($potentialBom);
$hasBOM = ($this->globalFunctionsHelper->fgets($filePointer, $numBytesInBom + 1) === $potentialBom); $hasBOM = ($this->globalFunctionsHelper->fgets($filePointer, $numBytesInBom + 1) === $potentialBom);
} }
+4 -4
View File
@@ -18,14 +18,14 @@ class ODS implements EscaperInterface
{ {
// @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as // @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as
// single/double quotes (for XML attributes) need to be encoded. // single/double quotes (for XML attributes) need to be encoded.
if (defined('ENT_DISALLOWED')) { if (\defined('ENT_DISALLOWED')) {
// 'ENT_DISALLOWED' ensures that invalid characters in the given document type are replaced. // 'ENT_DISALLOWED' ensures that invalid characters in the given document type are replaced.
// Otherwise control characters like a vertical tab "\v" will make the XML document unreadable by the XML processor // Otherwise control characters like a vertical tab "\v" will make the XML document unreadable by the XML processor
// @link https://github.com/box/spout/issues/329 // @link https://github.com/box/spout/issues/329
$replacedString = htmlspecialchars($string, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8'); $replacedString = \htmlspecialchars($string, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8');
} else { } else {
// We are on hhvm or any other engine that does not support ENT_DISALLOWED. // We are on hhvm or any other engine that does not support ENT_DISALLOWED.
$escapedString = htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); $escapedString = \htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
// control characters values are from 0 to 1F (hex values) in the ASCII table // control characters values are from 0 to 1F (hex values) in the ASCII table
// some characters should not be escaped though: "\t", "\r" and "\n". // some characters should not be escaped though: "\t", "\r" and "\n".
@@ -34,7 +34,7 @@ class ODS implements EscaperInterface
'\x0B-\x0C' . '\x0B-\x0C' .
// skipping "\r" (0xD) // skipping "\r" (0xD)
'\x0E-\x1F]'; '\x0E-\x1F]';
$replacedString = preg_replace("/$regexPattern/", '�', $escapedString); $replacedString = \preg_replace("/$regexPattern/", '�', $escapedString);
} }
return $replacedString; return $replacedString;
+11 -11
View File
@@ -28,7 +28,7 @@ class XLSX implements EscaperInterface
if (!$this->isAlreadyInitialized) { if (!$this->isAlreadyInitialized) {
$this->escapableControlCharactersPattern = $this->getEscapableControlCharactersPattern(); $this->escapableControlCharactersPattern = $this->getEscapableControlCharactersPattern();
$this->controlCharactersEscapingMap = $this->getControlCharactersEscapingMap(); $this->controlCharactersEscapingMap = $this->getControlCharactersEscapingMap();
$this->controlCharactersEscapingReverseMap = array_flip($this->controlCharactersEscapingMap); $this->controlCharactersEscapingReverseMap = \array_flip($this->controlCharactersEscapingMap);
$this->isAlreadyInitialized = true; $this->isAlreadyInitialized = true;
} }
@@ -47,7 +47,7 @@ class XLSX implements EscaperInterface
$escapedString = $this->escapeControlCharacters($string); $escapedString = $this->escapeControlCharacters($string);
// @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as // @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as
// single/double quotes (for XML attributes) need to be encoded. // single/double quotes (for XML attributes) need to be encoded.
$escapedString = htmlspecialchars($escapedString, ENT_QUOTES, 'UTF-8'); $escapedString = \htmlspecialchars($escapedString, ENT_QUOTES, 'UTF-8');
return $escapedString; return $escapedString;
} }
@@ -103,10 +103,10 @@ class XLSX implements EscaperInterface
// control characters values are from 0 to 1F (hex values) in the ASCII table // control characters values are from 0 to 1F (hex values) in the ASCII table
for ($charValue = 0x00; $charValue <= 0x1F; $charValue++) { for ($charValue = 0x00; $charValue <= 0x1F; $charValue++) {
$character = chr($charValue); $character = \chr($charValue);
if (preg_match("/{$this->escapableControlCharactersPattern}/", $character)) { if (\preg_match("/{$this->escapableControlCharactersPattern}/", $character)) {
$charHexValue = dechex($charValue); $charHexValue = \dechex($charValue);
$escapedChar = '_x' . sprintf('%04s', strtoupper($charHexValue)) . '_'; $escapedChar = '_x' . \sprintf('%04s', \strtoupper($charHexValue)) . '_';
$controlCharactersEscapingMap[$escapedChar] = $character; $controlCharactersEscapingMap[$escapedChar] = $character;
} }
} }
@@ -132,11 +132,11 @@ class XLSX implements EscaperInterface
$escapedString = $this->escapeEscapeCharacter($string); $escapedString = $this->escapeEscapeCharacter($string);
// if no control characters // if no control characters
if (!preg_match("/{$this->escapableControlCharactersPattern}/", $escapedString)) { if (!\preg_match("/{$this->escapableControlCharactersPattern}/", $escapedString)) {
return $escapedString; return $escapedString;
} }
return preg_replace_callback("/({$this->escapableControlCharactersPattern})/", function ($matches) { return \preg_replace_callback("/({$this->escapableControlCharactersPattern})/", function ($matches) {
return $this->controlCharactersEscapingReverseMap[$matches[0]]; return $this->controlCharactersEscapingReverseMap[$matches[0]];
}, $escapedString); }, $escapedString);
} }
@@ -149,7 +149,7 @@ class XLSX implements EscaperInterface
*/ */
protected function escapeEscapeCharacter($string) protected function escapeEscapeCharacter($string)
{ {
return preg_replace('/_(x[\dA-F]{4})_/', '_x005F_$1_', $string); return \preg_replace('/_(x[\dA-F]{4})_/', '_x005F_$1_', $string);
} }
/** /**
@@ -171,7 +171,7 @@ class XLSX implements EscaperInterface
foreach ($this->controlCharactersEscapingMap as $escapedCharValue => $charValue) { foreach ($this->controlCharactersEscapingMap as $escapedCharValue => $charValue) {
// only unescape characters that don't contain the escaped escape character for now // only unescape characters that don't contain the escaped escape character for now
$unescapedString = preg_replace("/(?<!_x005F)($escapedCharValue)/", $charValue, $unescapedString); $unescapedString = \preg_replace("/(?<!_x005F)($escapedCharValue)/", $charValue, $unescapedString);
} }
return $this->unescapeEscapeCharacter($unescapedString); return $this->unescapeEscapeCharacter($unescapedString);
@@ -185,6 +185,6 @@ class XLSX implements EscaperInterface
*/ */
protected function unescapeEscapeCharacter($string) protected function unescapeEscapeCharacter($string)
{ {
return preg_replace('/_x005F(_x[\dA-F]{4}_)/', '$1', $string); return \preg_replace('/_x005F(_x[\dA-F]{4}_)/', '$1', $string);
} }
} }
+10 -10
View File
@@ -19,7 +19,7 @@ class FileSystemHelper implements FileSystemHelperInterface
*/ */
public function __construct($baseFolderPath) public function __construct($baseFolderPath)
{ {
$this->baseFolderRealPath = realpath($baseFolderPath); $this->baseFolderRealPath = \realpath($baseFolderPath);
} }
/** /**
@@ -36,7 +36,7 @@ class FileSystemHelper implements FileSystemHelperInterface
$folderPath = $parentFolderPath . '/' . $folderName; $folderPath = $parentFolderPath . '/' . $folderName;
$wasCreationSuccessful = mkdir($folderPath, 0777, true); $wasCreationSuccessful = \mkdir($folderPath, 0777, true);
if (!$wasCreationSuccessful) { if (!$wasCreationSuccessful) {
throw new IOException("Unable to create folder: $folderPath"); throw new IOException("Unable to create folder: $folderPath");
} }
@@ -60,7 +60,7 @@ class FileSystemHelper implements FileSystemHelperInterface
$filePath = $parentFolderPath . '/' . $fileName; $filePath = $parentFolderPath . '/' . $fileName;
$wasCreationSuccessful = file_put_contents($filePath, $fileContents); $wasCreationSuccessful = \file_put_contents($filePath, $fileContents);
if ($wasCreationSuccessful === false) { if ($wasCreationSuccessful === false) {
throw new IOException("Unable to create file: $filePath"); throw new IOException("Unable to create file: $filePath");
} }
@@ -79,8 +79,8 @@ class FileSystemHelper implements FileSystemHelperInterface
{ {
$this->throwIfOperationNotInBaseFolder($filePath); $this->throwIfOperationNotInBaseFolder($filePath);
if (file_exists($filePath) && is_file($filePath)) { if (\file_exists($filePath) && \is_file($filePath)) {
unlink($filePath); \unlink($filePath);
} }
} }
@@ -102,13 +102,13 @@ class FileSystemHelper implements FileSystemHelperInterface
foreach ($itemIterator as $item) { foreach ($itemIterator as $item) {
if ($item->isDir()) { if ($item->isDir()) {
rmdir($item->getPathname()); \rmdir($item->getPathname());
} else { } else {
unlink($item->getPathname()); \unlink($item->getPathname());
} }
} }
rmdir($folderPath); \rmdir($folderPath);
} }
/** /**
@@ -122,8 +122,8 @@ class FileSystemHelper implements FileSystemHelperInterface
*/ */
protected function throwIfOperationNotInBaseFolder($operationFolderPath) protected function throwIfOperationNotInBaseFolder($operationFolderPath)
{ {
$operationFolderRealPath = realpath($operationFolderPath); $operationFolderRealPath = \realpath($operationFolderPath);
$isInBaseFolder = (strpos($operationFolderRealPath, $this->baseFolderRealPath) === 0); $isInBaseFolder = (\strpos($operationFolderRealPath, $this->baseFolderRealPath) === 0);
if (!$isInBaseFolder) { if (!$isInBaseFolder) {
throw new IOException("Cannot perform I/O operation outside of the base folder: {$this->baseFolderRealPath}"); throw new IOException("Cannot perform I/O operation outside of the base folder: {$this->baseFolderRealPath}");
} }
@@ -20,7 +20,7 @@ class GlobalFunctionsHelper
*/ */
public function fopen($fileName, $mode) public function fopen($fileName, $mode)
{ {
return fopen($fileName, $mode); return \fopen($fileName, $mode);
} }
/** /**
@@ -33,7 +33,7 @@ class GlobalFunctionsHelper
*/ */
public function fgets($handle, $length = null) public function fgets($handle, $length = null)
{ {
return fgets($handle, $length); return \fgets($handle, $length);
} }
/** /**
@@ -46,7 +46,7 @@ class GlobalFunctionsHelper
*/ */
public function fputs($handle, $string) public function fputs($handle, $string)
{ {
return fputs($handle, $string); return \fputs($handle, $string);
} }
/** /**
@@ -58,7 +58,7 @@ class GlobalFunctionsHelper
*/ */
public function fflush($handle) public function fflush($handle)
{ {
return fflush($handle); return \fflush($handle);
} }
/** /**
@@ -71,7 +71,7 @@ class GlobalFunctionsHelper
*/ */
public function fseek($handle, $offset) public function fseek($handle, $offset)
{ {
return fseek($handle, $offset); return \fseek($handle, $offset);
} }
/** /**
@@ -90,9 +90,9 @@ class GlobalFunctionsHelper
// To fix that, simply disable the escape character. // To fix that, simply disable the escape character.
// @see https://bugs.php.net/bug.php?id=43225 // @see https://bugs.php.net/bug.php?id=43225
// @see http://tools.ietf.org/html/rfc4180 // @see http://tools.ietf.org/html/rfc4180
$escapeCharacter = "\0"; $escapeCharacter = PHP_VERSION_ID >= 70400 ? '' : "\0";
return fgetcsv($handle, $length, $delimiter, $enclosure, $escapeCharacter); return \fgetcsv($handle, $length, $delimiter, $enclosure, $escapeCharacter);
} }
/** /**
@@ -111,9 +111,9 @@ class GlobalFunctionsHelper
// To fix that, simply disable the escape character. // To fix that, simply disable the escape character.
// @see https://bugs.php.net/bug.php?id=43225 // @see https://bugs.php.net/bug.php?id=43225
// @see http://tools.ietf.org/html/rfc4180 // @see http://tools.ietf.org/html/rfc4180
$escapeCharacter = "\0"; $escapeCharacter = PHP_VERSION_ID >= 70400 ? '' : "\0";
return fputcsv($handle, $fields, $delimiter, $enclosure, $escapeCharacter); return \fputcsv($handle, $fields, $delimiter, $enclosure, $escapeCharacter);
} }
/** /**
@@ -126,7 +126,7 @@ class GlobalFunctionsHelper
*/ */
public function fwrite($handle, $string) public function fwrite($handle, $string)
{ {
return fwrite($handle, $string); return \fwrite($handle, $string);
} }
/** /**
@@ -138,7 +138,7 @@ class GlobalFunctionsHelper
*/ */
public function fclose($handle) public function fclose($handle)
{ {
return fclose($handle); return \fclose($handle);
} }
/** /**
@@ -150,7 +150,7 @@ class GlobalFunctionsHelper
*/ */
public function rewind($handle) public function rewind($handle)
{ {
return rewind($handle); return \rewind($handle);
} }
/** /**
@@ -162,7 +162,7 @@ class GlobalFunctionsHelper
*/ */
public function file_exists($fileName) public function file_exists($fileName)
{ {
return file_exists($fileName); return \file_exists($fileName);
} }
/** /**
@@ -176,7 +176,7 @@ class GlobalFunctionsHelper
{ {
$realFilePath = $this->convertToUseRealPath($filePath); $realFilePath = $this->convertToUseRealPath($filePath);
return file_get_contents($realFilePath); return \file_get_contents($realFilePath);
} }
/** /**
@@ -191,13 +191,13 @@ class GlobalFunctionsHelper
$realFilePath = $filePath; $realFilePath = $filePath;
if ($this->isZipStream($filePath)) { if ($this->isZipStream($filePath)) {
if (preg_match('/zip:\/\/(.*)#(.*)/', $filePath, $matches)) { if (\preg_match('/zip:\/\/(.*)#(.*)/', $filePath, $matches)) {
$documentPath = $matches[1]; $documentPath = $matches[1];
$documentInsideZipPath = $matches[2]; $documentInsideZipPath = $matches[2];
$realFilePath = 'zip://' . realpath($documentPath) . '#' . $documentInsideZipPath; $realFilePath = 'zip://' . \realpath($documentPath) . '#' . $documentInsideZipPath;
} }
} else { } else {
$realFilePath = realpath($filePath); $realFilePath = \realpath($filePath);
} }
return $realFilePath; return $realFilePath;
@@ -211,7 +211,7 @@ class GlobalFunctionsHelper
*/ */
protected function isZipStream($path) protected function isZipStream($path)
{ {
return (strpos($path, 'zip://') === 0); return (\strpos($path, 'zip://') === 0);
} }
/** /**
@@ -223,7 +223,7 @@ class GlobalFunctionsHelper
*/ */
public function feof($handle) public function feof($handle)
{ {
return feof($handle); return \feof($handle);
} }
/** /**
@@ -235,7 +235,7 @@ class GlobalFunctionsHelper
*/ */
public function is_readable($fileName) public function is_readable($fileName)
{ {
return is_readable($fileName); return \is_readable($fileName);
} }
/** /**
@@ -248,7 +248,7 @@ class GlobalFunctionsHelper
*/ */
public function basename($path, $suffix = null) public function basename($path, $suffix = null)
{ {
return basename($path, $suffix); return \basename($path, $suffix);
} }
/** /**
@@ -260,7 +260,7 @@ class GlobalFunctionsHelper
*/ */
public function header($string) public function header($string)
{ {
header($string); \header($string);
} }
/** /**
@@ -271,8 +271,8 @@ class GlobalFunctionsHelper
*/ */
public function ob_end_clean() public function ob_end_clean()
{ {
if (ob_get_length() > 0) { if (\ob_get_length() > 0) {
ob_end_clean(); \ob_end_clean();
} }
} }
@@ -287,7 +287,7 @@ class GlobalFunctionsHelper
*/ */
public function iconv($string, $sourceEncoding, $targetEncoding) public function iconv($string, $sourceEncoding, $targetEncoding)
{ {
return iconv($sourceEncoding, $targetEncoding, $string); return \iconv($sourceEncoding, $targetEncoding, $string);
} }
/** /**
@@ -301,7 +301,7 @@ class GlobalFunctionsHelper
*/ */
public function mb_convert_encoding($string, $sourceEncoding, $targetEncoding) public function mb_convert_encoding($string, $sourceEncoding, $targetEncoding)
{ {
return mb_convert_encoding($string, $targetEncoding, $sourceEncoding); return \mb_convert_encoding($string, $targetEncoding, $sourceEncoding);
} }
/** /**
@@ -312,7 +312,7 @@ class GlobalFunctionsHelper
*/ */
public function stream_get_wrappers() public function stream_get_wrappers()
{ {
return stream_get_wrappers(); return \stream_get_wrappers();
} }
/** /**
@@ -324,6 +324,6 @@ class GlobalFunctionsHelper
*/ */
public function function_exists($functionName) public function function_exists($functionName)
{ {
return function_exists($functionName); return \function_exists($functionName);
} }
} }
+4 -4
View File
@@ -18,7 +18,7 @@ class StringHelper
*/ */
public function __construct() public function __construct()
{ {
$this->hasMbstringSupport = extension_loaded('mbstring'); $this->hasMbstringSupport = \extension_loaded('mbstring');
} }
/** /**
@@ -32,7 +32,7 @@ class StringHelper
*/ */
public function getStringLength($string) public function getStringLength($string)
{ {
return $this->hasMbstringSupport ? mb_strlen($string) : strlen($string); return $this->hasMbstringSupport ? \mb_strlen($string) : \strlen($string);
} }
/** /**
@@ -47,7 +47,7 @@ class StringHelper
*/ */
public function getCharFirstOccurrencePosition($char, $string) public function getCharFirstOccurrencePosition($char, $string)
{ {
$position = $this->hasMbstringSupport ? mb_strpos($string, $char) : strpos($string, $char); $position = $this->hasMbstringSupport ? \mb_strpos($string, $char) : \strpos($string, $char);
return ($position !== false) ? $position : -1; return ($position !== false) ? $position : -1;
} }
@@ -64,7 +64,7 @@ class StringHelper
*/ */
public function getCharLastOccurrencePosition($char, $string) public function getCharLastOccurrencePosition($char, $string)
{ {
$position = $this->hasMbstringSupport ? mb_strrpos($string, $char) : strrpos($string, $char); $position = $this->hasMbstringSupport ? \mb_strrpos($string, $char) : \strrpos($string, $char);
return ($position !== false) ? $position : -1; return ($position !== false) ? $position : -1;
} }
@@ -46,7 +46,7 @@ abstract class OptionsManagerAbstract implements OptionsManagerInterface
*/ */
public function setOption($optionName, $optionValue) public function setOption($optionName, $optionValue)
{ {
if (in_array($optionName, $this->supportedOptions)) { if (\in_array($optionName, $this->supportedOptions)) {
$this->options[$optionName] = $optionValue; $this->options[$optionName] = $optionValue;
} }
} }
@@ -89,7 +89,7 @@ class InternalEntityFactory implements InternalEntityFactoryInterface
*/ */
public function createRowFromArray(array $cellValues = []) public function createRowFromArray(array $cellValues = [])
{ {
$cells = array_map(function ($cellValue) { $cells = \array_map(function ($cellValue) {
return $this->createCell($cellValue); return $this->createCell($cellValue);
}, $cellValues); }, $cellValues);
+3 -3
View File
@@ -84,8 +84,8 @@ class Reader extends ReaderAbstract
*/ */
protected function openReader($filePath) protected function openReader($filePath)
{ {
$this->originalAutoDetectLineEndings = ini_get('auto_detect_line_endings'); $this->originalAutoDetectLineEndings = \ini_get('auto_detect_line_endings');
ini_set('auto_detect_line_endings', '1'); \ini_set('auto_detect_line_endings', '1');
$this->filePointer = $this->globalFunctionsHelper->fopen($filePath, 'r'); $this->filePointer = $this->globalFunctionsHelper->fopen($filePath, 'r');
if (!$this->filePointer) { if (!$this->filePointer) {
@@ -123,6 +123,6 @@ class Reader extends ReaderAbstract
$this->globalFunctionsHelper->fclose($this->filePointer); $this->globalFunctionsHelper->fclose($this->filePointer);
} }
ini_set('auto_detect_line_endings', $this->originalAutoDetectLineEndings); \ini_set('auto_detect_line_endings', $this->originalAutoDetectLineEndings);
} }
} }
+4 -4
View File
@@ -147,7 +147,7 @@ class RowIterator implements IteratorInterface
if ($rowData !== false) { if ($rowData !== false) {
// str_replace will replace NULL values by empty strings // str_replace will replace NULL values by empty strings
$rowDataBufferAsArray = str_replace(null, null, $rowData); $rowDataBufferAsArray = \str_replace(null, null, $rowData);
$this->rowBuffer = $this->entityFactory->createRowFromArray($rowDataBufferAsArray); $this->rowBuffer = $this->entityFactory->createRowFromArray($rowDataBufferAsArray);
$this->numReadRows++; $this->numReadRows++;
} else { } else {
@@ -193,13 +193,13 @@ class RowIterator implements IteratorInterface
case EncodingHelper::ENCODING_UTF16_LE: case EncodingHelper::ENCODING_UTF16_LE:
case EncodingHelper::ENCODING_UTF32_LE: case EncodingHelper::ENCODING_UTF32_LE:
// remove whitespace from the beginning of a string as fgetcsv() add extra whitespace when it try to explode non UTF-8 data // remove whitespace from the beginning of a string as fgetcsv() add extra whitespace when it try to explode non UTF-8 data
$cellValue = ltrim($cellValue); $cellValue = \ltrim($cellValue);
break; break;
case EncodingHelper::ENCODING_UTF16_BE: case EncodingHelper::ENCODING_UTF16_BE:
case EncodingHelper::ENCODING_UTF32_BE: case EncodingHelper::ENCODING_UTF32_BE:
// remove whitespace from the end of a string as fgetcsv() add extra whitespace when it try to explode non UTF-8 data // remove whitespace from the end of a string as fgetcsv() add extra whitespace when it try to explode non UTF-8 data
$cellValue = rtrim($cellValue); $cellValue = \rtrim($cellValue);
break; break;
} }
@@ -215,7 +215,7 @@ class RowIterator implements IteratorInterface
*/ */
protected function isEmptyLine($lineData) protected function isEmptyLine($lineData)
{ {
return (is_array($lineData) && count($lineData) === 1 && $lineData[0] === null); return (\is_array($lineData) && \count($lineData) === 1 && $lineData[0] === null);
} }
/** /**
@@ -37,7 +37,7 @@ class ReaderFactory
*/ */
public static function createFromFile(string $path) public static function createFromFile(string $path)
{ {
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); $extension = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
return self::createFromType($extension); return self::createFromType($extension);
} }
@@ -47,12 +47,14 @@ class RowManager
*/ */
public function fillMissingIndexesWithEmptyCells(Row $row) public function fillMissingIndexesWithEmptyCells(Row $row)
{ {
if ($row->getNumCells() === 0) { $numCells = $row->getNumCells();
if ($numCells === 0) {
return $row; return $row;
} }
$rowCells = $row->getCells(); $rowCells = $row->getCells();
$maxCellIndex = max(array_keys($rowCells)); $maxCellIndex = $numCells;
for ($cellIndex = 0; $cellIndex < $maxCellIndex; $cellIndex++) { for ($cellIndex = 0; $cellIndex < $maxCellIndex; $cellIndex++) {
if (!isset($rowCells[$cellIndex])) { if (!isset($rowCells[$cellIndex])) {
+1 -1
View File
@@ -73,7 +73,7 @@ class XMLProcessor
{ {
$callbackObject = $callback[0]; $callbackObject = $callback[0];
$callbackMethodName = $callback[1]; $callbackMethodName = $callback[1];
$reflectionMethod = new \ReflectionMethod(get_class($callbackObject), $callbackMethodName); $reflectionMethod = new \ReflectionMethod(\get_class($callbackObject), $callbackMethodName);
$reflectionMethod->setAccessible(true); $reflectionMethod->setAccessible(true);
return [ return [
@@ -22,9 +22,11 @@ class CellValueFormatter
/** Definition of XML nodes names used to parse data */ /** Definition of XML nodes names used to parse data */
const XML_NODE_P = 'p'; const XML_NODE_P = 'p';
const XML_NODE_S = 'text:s'; const XML_NODE_TEXT_A = 'text:a';
const XML_NODE_A = 'text:a'; const XML_NODE_TEXT_SPAN = 'text:span';
const XML_NODE_SPAN = 'text:span'; const XML_NODE_TEXT_S = 'text:s';
const XML_NODE_TEXT_TAB = 'text:tab';
const XML_NODE_TEXT_LINE_BREAK = 'text:line-break';
/** Definition of XML attributes used to parse data */ /** Definition of XML attributes used to parse data */
const XML_ATTRIBUTE_TYPE = 'office:value-type'; const XML_ATTRIBUTE_TYPE = 'office:value-type';
@@ -41,6 +43,13 @@ class CellValueFormatter
/** @var \Box\Spout\Common\Helper\Escaper\ODS Used to unescape XML data */ /** @var \Box\Spout\Common\Helper\Escaper\ODS Used to unescape XML data */
protected $escaper; protected $escaper;
/** @var array List of XML nodes representing whitespaces and their corresponding value */
private static $WHITESPACE_XML_NODES = [
self::XML_NODE_TEXT_S => ' ',
self::XML_NODE_TEXT_TAB => "\t",
self::XML_NODE_TEXT_LINE_BREAK => "\n",
];
/** /**
* @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings * @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings
* @param \Box\Spout\Common\Helper\Escaper\ODS $escaper Used to unescape XML data * @param \Box\Spout\Common\Helper\Escaper\ODS $escaper Used to unescape XML data
@@ -96,29 +105,71 @@ class CellValueFormatter
$pNodes = $node->getElementsByTagName(self::XML_NODE_P); $pNodes = $node->getElementsByTagName(self::XML_NODE_P);
foreach ($pNodes as $pNode) { foreach ($pNodes as $pNode) {
$currentPValue = ''; $pNodeValues[] = $this->extractTextValueFromNode($pNode);
foreach ($pNode->childNodes as $childNode) {
if ($childNode instanceof \DOMText) {
$currentPValue .= $childNode->nodeValue;
} elseif ($childNode->nodeName === self::XML_NODE_S) {
$spaceAttribute = $childNode->getAttribute(self::XML_ATTRIBUTE_C);
$numSpaces = (!empty($spaceAttribute)) ? (int) $spaceAttribute : 1;
$currentPValue .= str_repeat(' ', $numSpaces);
} elseif ($childNode->nodeName === self::XML_NODE_A || $childNode->nodeName === self::XML_NODE_SPAN) {
$currentPValue .= $childNode->nodeValue;
}
} }
$pNodeValues[] = $currentPValue; $escapedCellValue = \implode("\n", $pNodeValues);
}
$escapedCellValue = implode("\n", $pNodeValues);
$cellValue = $this->escaper->unescape($escapedCellValue); $cellValue = $this->escaper->unescape($escapedCellValue);
return $cellValue; return $cellValue;
} }
/**
* @param $pNode
* @return string
*/
private function extractTextValueFromNode($pNode)
{
$textValue = '';
foreach ($pNode->childNodes as $childNode) {
if ($childNode instanceof \DOMText) {
$textValue .= $childNode->nodeValue;
} elseif ($this->isWhitespaceNode($childNode->nodeName)) {
$textValue .= $this->transformWhitespaceNode($childNode);
} elseif ($childNode->nodeName === self::XML_NODE_TEXT_A || $childNode->nodeName === self::XML_NODE_TEXT_SPAN) {
$textValue .= $this->extractTextValueFromNode($childNode);
}
}
return $textValue;
}
/**
* Returns whether the given node is a whitespace node. It must be one of these:
* - <text:s />
* - <text:tab />
* - <text:line-break />
*
* @param string $nodeName
* @return bool
*/
private function isWhitespaceNode($nodeName)
{
return isset(self::$WHITESPACE_XML_NODES[$nodeName]);
}
/**
* The "<text:p>" node can contain the string value directly
* or contain child elements. In this case, whitespaces contain in
* the child elements should be replaced by their XML equivalent:
* - space => <text:s />
* - tab => <text:tab />
* - line break => <text:line-break />
*
* @see https://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1415200_253892949
*
* @param \DOMNode $node The XML node representing a whitespace
* @return string The corresponding whitespace value
*/
private function transformWhitespaceNode($node)
{
$countAttribute = $node->getAttribute(self::XML_ATTRIBUTE_C); // only defined for "<text:s>"
$numWhitespaces = (!empty($countAttribute)) ? (int) $countAttribute : 1;
return \str_repeat(self::$WHITESPACE_XML_NODES[$node->nodeName], $numWhitespaces);
}
/** /**
* Returns the cell Numeric value from the given node. * Returns the cell Numeric value from the given node.
* *
+3 -3
View File
@@ -145,7 +145,7 @@ abstract class ReaderAbstract implements ReaderInterface
} }
// Need to use realpath to fix "Can't open file" on some Windows setup // Need to use realpath to fix "Can't open file" on some Windows setup
return realpath($filePath); return \realpath($filePath);
} }
/** /**
@@ -158,7 +158,7 @@ abstract class ReaderAbstract implements ReaderInterface
protected function getStreamWrapperScheme($filePath) protected function getStreamWrapperScheme($filePath)
{ {
$streamScheme = null; $streamScheme = null;
if (preg_match('/^(\w+):\/\//', $filePath, $matches)) { if (\preg_match('/^(\w+):\/\//', $filePath, $matches)) {
$streamScheme = $matches[1]; $streamScheme = $matches[1];
} }
@@ -190,7 +190,7 @@ abstract class ReaderAbstract implements ReaderInterface
$streamScheme = $this->getStreamWrapperScheme($filePath); $streamScheme = $this->getStreamWrapperScheme($filePath);
return ($streamScheme !== null) ? return ($streamScheme !== null) ?
in_array($streamScheme, $this->globalFunctionsHelper->stream_get_wrappers()) : \in_array($streamScheme, $this->globalFunctionsHelper->stream_get_wrappers()) :
true; true;
} }
@@ -20,8 +20,8 @@ trait XMLInternalErrorsHelper
*/ */
protected function useXMLInternalErrors() protected function useXMLInternalErrors()
{ {
libxml_clear_errors(); \libxml_clear_errors();
$this->initialUseInternalErrorsValue = libxml_use_internal_errors(true); $this->initialUseInternalErrorsValue = \libxml_use_internal_errors(true);
} }
/** /**
@@ -48,7 +48,7 @@ trait XMLInternalErrorsHelper
*/ */
private function hasXMLErrorOccured() private function hasXMLErrorOccured()
{ {
return (libxml_get_last_error() !== false); return (\libxml_get_last_error() !== false);
} }
/** /**
@@ -60,10 +60,10 @@ trait XMLInternalErrorsHelper
private function getLastXMLErrorMessage() private function getLastXMLErrorMessage()
{ {
$errorMessage = null; $errorMessage = null;
$error = libxml_get_last_error(); $error = \libxml_get_last_error();
if ($error !== false) { if ($error !== false) {
$errorMessage = trim($error->message); $errorMessage = \trim($error->message);
} }
return $errorMessage; return $errorMessage;
@@ -74,6 +74,6 @@ trait XMLInternalErrorsHelper
*/ */
protected function resetXMLInternalErrorsSetting() protected function resetXMLInternalErrorsSetting()
{ {
libxml_use_internal_errors($this->initialUseInternalErrorsValue); \libxml_use_internal_errors($this->initialUseInternalErrorsValue);
} }
} }
+4 -4
View File
@@ -46,9 +46,9 @@ class XMLReader extends \XMLReader
public function getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath) public function getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath)
{ {
// The file path should not start with a '/', otherwise it won't be found // The file path should not start with a '/', otherwise it won't be found
$fileInsideZipPathWithoutLeadingSlash = ltrim($fileInsideZipPath, '/'); $fileInsideZipPathWithoutLeadingSlash = \ltrim($fileInsideZipPath, '/');
return (self::ZIP_WRAPPER . realpath($zipFilePath) . '#' . $fileInsideZipPathWithoutLeadingSlash); return (self::ZIP_WRAPPER . \realpath($zipFilePath) . '#' . $fileInsideZipPathWithoutLeadingSlash);
} }
/** /**
@@ -62,7 +62,7 @@ class XMLReader extends \XMLReader
$doesFileExists = false; $doesFileExists = false;
$pattern = '/zip:\/\/([^#]+)#(.*)/'; $pattern = '/zip:\/\/([^#]+)#(.*)/';
if (preg_match($pattern, $zipStreamURI, $matches)) { if (\preg_match($pattern, $zipStreamURI, $matches)) {
$zipFilePath = $matches[1]; $zipFilePath = $matches[1];
$innerFilePath = $matches[2]; $innerFilePath = $matches[2];
@@ -158,7 +158,7 @@ class XMLReader extends \XMLReader
// In some cases, the node has a prefix (for instance, "<sheet>" can also be "<x:sheet>"). // In some cases, the node has a prefix (for instance, "<sheet>" can also be "<x:sheet>").
// So if the given node name does not have a prefix, we need to look at the unprefixed name ("localName"). // So if the given node name does not have a prefix, we need to look at the unprefixed name ("localName").
// @see https://github.com/box/spout/issues/233 // @see https://github.com/box/spout/issues/233
$hasPrefix = (strpos($nodeName, ':') !== false); $hasPrefix = (\strpos($nodeName, ':') !== false);
$currentNodeName = ($hasPrefix) ? $this->name : $this->localName; $currentNodeName = ($hasPrefix) ? $this->name : $this->localName;
return ($this->nodeType === $nodeType && $currentNodeName === $nodeName); return ($this->nodeType === $nodeType && $currentNodeName === $nodeName);
+2 -2
View File
@@ -37,7 +37,7 @@ class CellHelper
$columnIndex = 0; $columnIndex = 0;
// Remove row information // Remove row information
$columnLetters = preg_replace('/\d/', '', $cellIndex); $columnLetters = \preg_replace('/\d/', '', $cellIndex);
// strlen() is super slow too... Using isset() is way faster and not too unreadable, // strlen() is super slow too... Using isset() is way faster and not too unreadable,
// since we checked before that there are between 1 and 3 letters. // since we checked before that there are between 1 and 3 letters.
@@ -75,6 +75,6 @@ class CellHelper
*/ */
protected static function isValidCellIndex($cellIndex) protected static function isValidCellIndex($cellIndex)
{ {
return (preg_match('/^[A-Z]{1,3}\d+$/', $cellIndex) === 1); return (\preg_match('/^[A-Z]{1,3}\d+$/', $cellIndex) === 1);
} }
} }
@@ -163,7 +163,7 @@ class CellValueFormatter
*/ */
protected function formatStrCellValue($nodeValue) protected function formatStrCellValue($nodeValue)
{ {
$escapedCellValue = trim($nodeValue); $escapedCellValue = \trim($nodeValue);
$cellValue = $this->escaper->unescape($escapedCellValue); $cellValue = $this->escaper->unescape($escapedCellValue);
return $cellValue; return $cellValue;
@@ -248,8 +248,8 @@ class CellValueFormatter
$baseDate = $this->shouldUse1904Dates ? '1904-01-01' : '1899-12-30'; $baseDate = $this->shouldUse1904Dates ? '1904-01-01' : '1899-12-30';
$daysSinceBaseDate = (int) $nodeValue; $daysSinceBaseDate = (int) $nodeValue;
$timeRemainder = fmod($nodeValue, 1); $timeRemainder = \fmod($nodeValue, 1);
$secondsRemainder = round($timeRemainder * self::NUM_SECONDS_IN_ONE_DAY, 0); $secondsRemainder = \round($timeRemainder * self::NUM_SECONDS_IN_ONE_DAY, 0);
$dateObj = \DateTime::createFromFormat('|Y-m-d', $baseDate); $dateObj = \DateTime::createFromFormat('|Y-m-d', $baseDate);
$dateObj->modify('+' . $daysSinceBaseDate . 'days'); $dateObj->modify('+' . $daysSinceBaseDate . 'days');
@@ -62,13 +62,13 @@ class DateFormatHelper
// Remove brackets potentially present at the beginning of the format string // Remove brackets potentially present at the beginning of the format string
// and text portion of the format at the end of it (starting with ";") // and text portion of the format at the end of it (starting with ";")
// See §18.8.31 of ECMA-376 for more detail. // See §18.8.31 of ECMA-376 for more detail.
$dateFormat = preg_replace('/^(?:\[\$[^\]]+?\])?([^;]*).*/', '$1', $excelDateFormat); $dateFormat = \preg_replace('/^(?:\[\$[^\]]+?\])?([^;]*).*/', '$1', $excelDateFormat);
// Double quotes are used to escape characters that must not be interpreted. // Double quotes are used to escape characters that must not be interpreted.
// For instance, ["Day " dd] should result in "Day 13" and we should not try to interpret "D", "a", "y" // For instance, ["Day " dd] should result in "Day 13" and we should not try to interpret "D", "a", "y"
// By exploding the format string using double quote as a delimiter, we can get all parts // By exploding the format string using double quote as a delimiter, we can get all parts
// that must be transformed (even indexes) and all parts that must not be (odd indexes). // that must be transformed (even indexes) and all parts that must not be (odd indexes).
$dateFormatParts = explode('"', $dateFormat); $dateFormatParts = \explode('"', $dateFormat);
foreach ($dateFormatParts as $partIndex => $dateFormatPart) { foreach ($dateFormatParts as $partIndex => $dateFormatPart) {
// do not look at odd indexes // do not look at odd indexes
@@ -77,19 +77,19 @@ class DateFormatHelper
} }
// Make sure all characters are lowercase, as the mapping table is using lowercase characters // Make sure all characters are lowercase, as the mapping table is using lowercase characters
$transformedPart = strtolower($dateFormatPart); $transformedPart = \strtolower($dateFormatPart);
// Remove escapes related to non-format characters // Remove escapes related to non-format characters
$transformedPart = str_replace('\\', '', $transformedPart); $transformedPart = \str_replace('\\', '', $transformedPart);
// Apply general transformation first... // Apply general transformation first...
$transformedPart = strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_GENERAL]); $transformedPart = \strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_GENERAL]);
// ... then apply hour transformation, for 12-hour or 24-hour format // ... then apply hour transformation, for 12-hour or 24-hour format
if (self::has12HourFormatMarker($dateFormatPart)) { if (self::has12HourFormatMarker($dateFormatPart)) {
$transformedPart = strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_12]); $transformedPart = \strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_12]);
} else { } else {
$transformedPart = strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_24]); $transformedPart = \strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_24]);
} }
// overwrite the parts array with the new transformed part // overwrite the parts array with the new transformed part
@@ -97,16 +97,16 @@ class DateFormatHelper
} }
// Merge all transformed parts back together // Merge all transformed parts back together
$phpDateFormat = implode('"', $dateFormatParts); $phpDateFormat = \implode('"', $dateFormatParts);
// Finally, to have the date format compatible with the DateTime::format() function, we need to escape // Finally, to have the date format compatible with the DateTime::format() function, we need to escape
// all characters that are inside double quotes (and double quotes must be removed). // all characters that are inside double quotes (and double quotes must be removed).
// For instance, ["Day " dd] should become [\D\a\y\ dd] // For instance, ["Day " dd] should become [\D\a\y\ dd]
$phpDateFormat = preg_replace_callback('/"(.+?)"/', function ($matches) { $phpDateFormat = \preg_replace_callback('/"(.+?)"/', function ($matches) {
$stringToEscape = $matches[1]; $stringToEscape = $matches[1];
$letters = preg_split('//u', $stringToEscape, -1, PREG_SPLIT_NO_EMPTY); $letters = \preg_split('//u', $stringToEscape, -1, PREG_SPLIT_NO_EMPTY);
return '\\' . implode('\\', $letters); return '\\' . \implode('\\', $letters);
}, $phpDateFormat); }, $phpDateFormat);
return $phpDateFormat; return $phpDateFormat;
@@ -118,6 +118,6 @@ class DateFormatHelper
*/ */
private static function has12HourFormatMarker($excelDateFormat) private static function has12HourFormatMarker($excelDateFormat)
{ {
return (stripos($excelDateFormat, 'am/pm') !== false); return (\stripos($excelDateFormat, 'am/pm') !== false);
} }
} }
@@ -29,7 +29,7 @@ class OptionsManager extends OptionsManagerAbstract
*/ */
protected function setDefaultOptions() protected function setDefaultOptions()
{ {
$this->setOption(Options::TEMP_FOLDER, sys_get_temp_dir()); $this->setOption(Options::TEMP_FOLDER, \sys_get_temp_dir());
$this->setOption(Options::SHOULD_FORMAT_DATES, false); $this->setOption(Options::SHOULD_FORMAT_DATES, false);
$this->setOption(Options::SHOULD_PRESERVE_EMPTY_ROWS, false); $this->setOption(Options::SHOULD_PRESERVE_EMPTY_ROWS, false);
$this->setOption(Options::SHOULD_USE_1904_DATES, false); $this->setOption(Options::SHOULD_USE_1904_DATES, false);
@@ -103,14 +103,14 @@ class CachingStrategyFactory
protected function getMemoryLimitInKB() protected function getMemoryLimitInKB()
{ {
$memoryLimitFormatted = $this->getMemoryLimitFromIni(); $memoryLimitFormatted = $this->getMemoryLimitFromIni();
$memoryLimitFormatted = strtolower(trim($memoryLimitFormatted)); $memoryLimitFormatted = \strtolower(\trim($memoryLimitFormatted));
// No memory limit // No memory limit
if ($memoryLimitFormatted === '-1') { if ($memoryLimitFormatted === '-1') {
return -1; return -1;
} }
if (preg_match('/(\d+)([bkmgt])b?/', $memoryLimitFormatted, $matches)) { if (\preg_match('/(\d+)([bkmgt])b?/', $memoryLimitFormatted, $matches)) {
$amount = (int) ($matches[1]); $amount = (int) ($matches[1]);
$unit = $matches[2]; $unit = $matches[2];
@@ -133,6 +133,6 @@ class CachingStrategyFactory
*/ */
protected function getMemoryLimitFromIni() protected function getMemoryLimitFromIni()
{ {
return ini_get('memory_limit'); return \ini_get('memory_limit');
} }
} }
@@ -55,7 +55,7 @@ class FileBasedStrategy implements CachingStrategyInterface
public function __construct($tempFolder, $maxNumStringsPerTempFile, $helperFactory) public function __construct($tempFolder, $maxNumStringsPerTempFile, $helperFactory)
{ {
$this->fileSystemHelper = $helperFactory->createFileSystemHelper($tempFolder); $this->fileSystemHelper = $helperFactory->createFileSystemHelper($tempFolder);
$this->tempFolder = $this->fileSystemHelper->createFolder($tempFolder, uniqid('sharedstrings')); $this->tempFolder = $this->fileSystemHelper->createFolder($tempFolder, \uniqid('sharedstrings'));
$this->maxNumStringsPerTempFile = $maxNumStringsPerTempFile; $this->maxNumStringsPerTempFile = $maxNumStringsPerTempFile;
@@ -135,7 +135,7 @@ class FileBasedStrategy implements CachingStrategyInterface
// free memory // free memory
unset($this->inMemoryTempFileContents); unset($this->inMemoryTempFileContents);
$this->inMemoryTempFileContents = explode(PHP_EOL, $this->globalFunctionsHelper->file_get_contents($tempFilePath)); $this->inMemoryTempFileContents = \explode(PHP_EOL, $this->globalFunctionsHelper->file_get_contents($tempFilePath));
$this->inMemoryTempFilePath = $tempFilePath; $this->inMemoryTempFilePath = $tempFilePath;
} }
@@ -151,7 +151,7 @@ class FileBasedStrategy implements CachingStrategyInterface
throw new SharedStringNotFoundException("Shared string not found for index: $sharedStringIndex"); throw new SharedStringNotFoundException("Shared string not found for index: $sharedStringIndex");
} }
return rtrim($sharedString, PHP_EOL); return \rtrim($sharedString, PHP_EOL);
} }
/** /**
@@ -162,7 +162,7 @@ class FileBasedStrategy implements CachingStrategyInterface
*/ */
private function escapeLineFeed($unescapedString) private function escapeLineFeed($unescapedString)
{ {
return str_replace("\n", self::ESCAPED_LINE_FEED_CHARACTER, $unescapedString); return \str_replace("\n", self::ESCAPED_LINE_FEED_CHARACTER, $unescapedString);
} }
/** /**
@@ -173,7 +173,7 @@ class FileBasedStrategy implements CachingStrategyInterface
*/ */
private function unescapeLineFeed($escapedString) private function unescapeLineFeed($escapedString)
{ {
return str_replace(self::ESCAPED_LINE_FEED_CHARACTER, "\n", $escapedString); return \str_replace(self::ESCAPED_LINE_FEED_CHARACTER, "\n", $escapedString);
} }
/** /**
@@ -190,7 +190,7 @@ class SharedStringsManager
$textNodeValue = $textNode->nodeValue; $textNodeValue = $textNode->nodeValue;
$shouldPreserveWhitespace = $this->shouldPreserveWhitespace($textNode); $shouldPreserveWhitespace = $this->shouldPreserveWhitespace($textNode);
$sharedStringValue .= ($shouldPreserveWhitespace) ? $textNodeValue : trim($textNodeValue); $sharedStringValue .= ($shouldPreserveWhitespace) ? $textNodeValue : \trim($textNodeValue);
} }
} }
@@ -116,7 +116,7 @@ class SheetManager
{ {
// Using "filter_var($x, FILTER_VALIDATE_BOOLEAN)" here because the value of the "date1904" attribute // Using "filter_var($x, FILTER_VALIDATE_BOOLEAN)" here because the value of the "date1904" attribute
// may be the string "false", that is not mapped to the boolean "false" by default... // may be the string "false", that is not mapped to the boolean "false" by default...
$shouldUse1904Dates = filter_var($xmlReader->getAttribute(self::XML_ATTRIBUTE_DATE_1904), FILTER_VALIDATE_BOOLEAN); $shouldUse1904Dates = \filter_var($xmlReader->getAttribute(self::XML_ATTRIBUTE_DATE_1904), FILTER_VALIDATE_BOOLEAN);
$this->optionsManager->setOption(Options::SHOULD_USE_1904_DATES, $shouldUse1904Dates); $this->optionsManager->setOption(Options::SHOULD_USE_1904_DATES, $shouldUse1904Dates);
return XMLProcessor::PROCESSING_CONTINUE; return XMLProcessor::PROCESSING_CONTINUE;
@@ -211,7 +211,7 @@ class SheetManager
$sheetDataXMLFilePath = $xmlReader->getAttribute(self::XML_ATTRIBUTE_TARGET); $sheetDataXMLFilePath = $xmlReader->getAttribute(self::XML_ATTRIBUTE_TARGET);
// sometimes, the sheet data file path already contains "/xl/"... // sometimes, the sheet data file path already contains "/xl/"...
if (strpos($sheetDataXMLFilePath, '/xl/') !== 0) { if (\strpos($sheetDataXMLFilePath, '/xl/') !== 0) {
$sheetDataXMLFilePath = '/xl/' . $sheetDataXMLFilePath; $sheetDataXMLFilePath = '/xl/' . $sheetDataXMLFilePath;
break; break;
} }
+16 -6
View File
@@ -48,7 +48,10 @@ class StyleManager
/** @var string Path of the XLSX file being read */ /** @var string Path of the XLSX file being read */
protected $filePath; protected $filePath;
/** @var string Path of the styles XML file */ /** @var bool Whether the XLSX file contains a styles XML file */
protected $hasStylesXMLFile;
/** @var string|null Path of the styles XML file */
protected $stylesXMLFilePath; protected $stylesXMLFilePath;
/** @var InternalEntityFactory Factory to create entities */ /** @var InternalEntityFactory Factory to create entities */
@@ -75,9 +78,12 @@ class StyleManager
{ {
$this->filePath = $filePath; $this->filePath = $filePath;
$this->entityFactory = $entityFactory; $this->entityFactory = $entityFactory;
$this->builtinNumFmtIdIndicatingDates = array_keys(self::$builtinNumFmtIdToNumFormatMapping); $this->builtinNumFmtIdIndicatingDates = \array_keys(self::$builtinNumFmtIdToNumFormatMapping);
$this->hasStylesXMLFile = $workbookRelationshipsManager->hasStylesXMLFile();
if ($this->hasStylesXMLFile) {
$this->stylesXMLFilePath = $workbookRelationshipsManager->getStylesXMLFilePath(); $this->stylesXMLFilePath = $workbookRelationshipsManager->getStylesXMLFilePath();
} }
}
/** /**
* Returns whether the style with the given ID should consider * Returns whether the style with the given ID should consider
@@ -88,6 +94,10 @@ class StyleManager
*/ */
public function shouldFormatNumericValueAsDate($styleId) public function shouldFormatNumericValueAsDate($styleId)
{ {
if (!$this->hasStylesXMLFile) {
return false;
}
$stylesAttributes = $this->getStylesAttributes(); $stylesAttributes = $this->getStylesAttributes();
// Default style (0) does not format numeric values as timestamps. Only custom styles do. // Default style (0) does not format numeric values as timestamps. Only custom styles do.
@@ -263,7 +273,7 @@ class StyleManager
*/ */
protected function isNumFmtIdBuiltInDateFormat($numFmtId) protected function isNumFmtIdBuiltInDateFormat($numFmtId)
{ {
return in_array($numFmtId, $this->builtinNumFmtIdIndicatingDates); return \in_array($numFmtId, $this->builtinNumFmtIdIndicatingDates);
} }
/** /**
@@ -273,7 +283,7 @@ class StyleManager
protected function isFormatCodeCustomDateFormat($formatCode) protected function isFormatCodeCustomDateFormat($formatCode)
{ {
// if no associated format code or if using the default "General" format // if no associated format code or if using the default "General" format
if ($formatCode === null || strcasecmp($formatCode, self::NUMBER_FORMAT_GENERAL) === 0) { if ($formatCode === null || \strcasecmp($formatCode, self::NUMBER_FORMAT_GENERAL) === 0) {
return false; return false;
} }
@@ -288,7 +298,7 @@ class StyleManager
{ {
// Remove extra formatting (what's between [ ], the brackets should not be preceded by a "\") // Remove extra formatting (what's between [ ], the brackets should not be preceded by a "\")
$pattern = '((?<!\\\)\[.+?(?<!\\\)\])'; $pattern = '((?<!\\\)\[.+?(?<!\\\)\])';
$formatCode = preg_replace($pattern, '', $formatCode); $formatCode = \preg_replace($pattern, '', $formatCode);
// custom date formats contain specific characters to represent the date: // custom date formats contain specific characters to represent the date:
// e - yy - m - d - h - s // e - yy - m - d - h - s
@@ -300,7 +310,7 @@ class StyleManager
// character not preceded by "\" (case insensitive) // character not preceded by "\" (case insensitive)
$pattern = '/(?<!\\\)' . $dateFormatCharacter . '/i'; $pattern = '/(?<!\\\)' . $dateFormatCharacter . '/i';
if (preg_match($pattern, $formatCode)) { if (\preg_match($pattern, $formatCode)) {
$hasFoundDateFormatCharacter = true; $hasFoundDateFormatCharacter = true;
break; break;
} }
@@ -55,7 +55,7 @@ class WorkbookRelationshipsManager
$sharedStringsXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS]; $sharedStringsXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS];
// the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml") // the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
$doesContainBasePath = (strpos($sharedStringsXMLFilePath, self::BASE_PATH) !== false); $doesContainBasePath = (\strpos($sharedStringsXMLFilePath, self::BASE_PATH) !== false);
if (!$doesContainBasePath) { if (!$doesContainBasePath) {
// make sure we return an absolute file path // make sure we return an absolute file path
$sharedStringsXMLFilePath = self::BASE_PATH . $sharedStringsXMLFilePath; $sharedStringsXMLFilePath = self::BASE_PATH . $sharedStringsXMLFilePath;
@@ -75,7 +75,17 @@ class WorkbookRelationshipsManager
} }
/** /**
* @return string|null The path of the styles XML file * @return bool Whether the XLSX file contains a styles XML file
*/
public function hasStylesXMLFile()
{
$workbookRelationships = $this->getWorkbookRelationships();
return isset($workbookRelationships[self::RELATIONSHIP_TYPE_STYLES]);
}
/**
* @return string The path of the styles XML file
*/ */
public function getStylesXMLFilePath() public function getStylesXMLFilePath()
{ {
@@ -83,7 +93,7 @@ class WorkbookRelationshipsManager
$stylesXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES]; $stylesXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES];
// the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml") // the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
$doesContainBasePath = (strpos($stylesXMLFilePath, self::BASE_PATH) !== false); $doesContainBasePath = (\strpos($stylesXMLFilePath, self::BASE_PATH) !== false);
if (!$doesContainBasePath) { if (!$doesContainBasePath) {
// make sure we return a full path // make sure we return a full path
$stylesXMLFilePath = self::BASE_PATH . $stylesXMLFilePath; $stylesXMLFilePath = self::BASE_PATH . $stylesXMLFilePath;
+4 -4
View File
@@ -127,7 +127,7 @@ class RowIterator implements IteratorInterface
*/ */
protected function normalizeSheetDataXMLFilePath($sheetDataXMLFilePath) protected function normalizeSheetDataXMLFilePath($sheetDataXMLFilePath)
{ {
return ltrim($sheetDataXMLFilePath, '/'); return \ltrim($sheetDataXMLFilePath, '/');
} }
/** /**
@@ -234,7 +234,7 @@ class RowIterator implements IteratorInterface
{ {
// Read dimensions of the sheet // Read dimensions of the sheet
$dimensionRef = $xmlReader->getAttribute(self::XML_ATTRIBUTE_REF); // returns 'A1:M13' for instance (or 'A1' for empty sheet) $dimensionRef = $xmlReader->getAttribute(self::XML_ATTRIBUTE_REF); // returns 'A1:M13' for instance (or 'A1' for empty sheet)
if (preg_match('/[A-Z]+\d+:([A-Z]+\d+)/', $dimensionRef, $matches)) { if (\preg_match('/[A-Z]+\d+:([A-Z]+\d+)/', $dimensionRef, $matches)) {
$this->numColumns = CellHelper::getColumnIndexFromCellIndex($matches[1]) + 1; $this->numColumns = CellHelper::getColumnIndexFromCellIndex($matches[1]) + 1;
} }
@@ -257,11 +257,11 @@ class RowIterator implements IteratorInterface
$numberOfColumnsForRow = $this->numColumns; $numberOfColumnsForRow = $this->numColumns;
$spans = $xmlReader->getAttribute(self::XML_ATTRIBUTE_SPANS); // returns '1:5' for instance $spans = $xmlReader->getAttribute(self::XML_ATTRIBUTE_SPANS); // returns '1:5' for instance
if ($spans) { if ($spans) {
list(, $numberOfColumnsForRow) = explode(':', $spans); list(, $numberOfColumnsForRow) = \explode(':', $spans);
$numberOfColumnsForRow = (int) $numberOfColumnsForRow; $numberOfColumnsForRow = (int) $numberOfColumnsForRow;
} }
$cells = array_fill(0, $numberOfColumnsForRow, $this->entityFactory->createCell('')); $cells = \array_fill(0, $numberOfColumnsForRow, $this->entityFactory->createCell(''));
$this->currentlyProcessedRow->setCells($cells); $this->currentlyProcessedRow->setCells($cells);
return XMLProcessor::PROCESSING_CONTINUE; return XMLProcessor::PROCESSING_CONTINUE;
+2 -2
View File
@@ -27,7 +27,7 @@ class SheetIterator implements IteratorInterface
// Fetch all available sheets // Fetch all available sheets
$this->sheets = $sheetManager->getSheets(); $this->sheets = $sheetManager->getSheets();
if (count($this->sheets) === 0) { if (\count($this->sheets) === 0) {
throw new NoSheetsFoundException('The file must contain at least one sheet.'); throw new NoSheetsFoundException('The file must contain at least one sheet.');
} }
} }
@@ -51,7 +51,7 @@ class SheetIterator implements IteratorInterface
*/ */
public function valid() public function valid()
{ {
return ($this->currentSheetIndex < count($this->sheets)); return ($this->currentSheetIndex < \count($this->sheets));
} }
/** /**
@@ -3,7 +3,9 @@
namespace Box\Spout\Writer\Common\Creator\Style; namespace Box\Spout\Writer\Common\Creator\Style;
use Box\Spout\Common\Entity\Style\Border; use Box\Spout\Common\Entity\Style\Border;
use Box\Spout\Common\Entity\Style\CellAlignment;
use Box\Spout\Common\Entity\Style\Style; use Box\Spout\Common\Entity\Style\Style;
use Box\Spout\Common\Exception\InvalidArgumentException;
/** /**
* Class StyleBuilder * Class StyleBuilder
@@ -122,6 +124,25 @@ class StyleBuilder
return $this; return $this;
} }
/**
* Sets the cell alignment.
*
* @param string $cellAlignment The cell alignment
*
* @throws InvalidArgumentException If the given cell alignment is not valid
* @return StyleBuilder
*/
public function setCellAlignment($cellAlignment)
{
if (!CellAlignment::isValid($cellAlignment)) {
throw new InvalidArgumentException('Invalid cell alignment value');
}
$this->style->setCellAlignment($cellAlignment);
return $this;
}
/** /**
* Set a border * Set a border
* *
@@ -148,6 +169,20 @@ class StyleBuilder
return $this; return $this;
} }
/**
* Sets a format
*
* @param string $format Format
* @return StyleBuilder
* @api
*/
public function setFormat($format)
{
$this->style->setFormat($format);
return $this;
}
/** /**
* Returns the configured style. The style is cached and can be reused. * Returns the configured style. The style is cached and can be reused.
* *
@@ -98,7 +98,7 @@ class WriterEntityFactory
*/ */
public static function createRowFromArray(array $cellValues = [], Style $rowStyle = null) public static function createRowFromArray(array $cellValues = [], Style $rowStyle = null)
{ {
$cells = array_map(function ($cellValue) { $cells = \array_map(function ($cellValue) {
return new Cell($cellValue); return new Cell($cellValue);
}, $cellValues); }, $cellValues);
@@ -35,7 +35,7 @@ class WriterFactory
*/ */
public static function createFromFile(string $path) public static function createFromFile(string $path)
{ {
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); $extension = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
return self::createFromType($extension); return self::createFromType($extension);
} }
+1 -1
View File
@@ -19,7 +19,7 @@ class Workbook
*/ */
public function __construct() public function __construct()
{ {
$this->internalId = uniqid(); $this->internalId = \uniqid();
} }
/** /**
+16 -15
View File
@@ -8,38 +8,39 @@ namespace Box\Spout\Writer\Common\Helper;
*/ */
class CellHelper class CellHelper
{ {
/** @var array Cache containing the mapping column index => cell index */ /** @var array Cache containing the mapping column index => column letters */
private static $columnIndexToCellIndexCache = []; private static $columnIndexToColumnLettersCache = [];
/** /**
* Returns the cell index (base 26) associated to the base 10 column index. * Returns the column letters (base 26) associated to the base 10 column index.
* Excel uses A to Z letters for column indexing, where A is the 1st column, * Excel uses A to Z letters for column indexing, where A is the 1st column,
* Z is the 26th and AA is the 27th. * Z is the 26th and AA is the 27th.
* The mapping is zero based, so that 0 maps to A, B maps to 1, Z to 25 and AA to 26. * The mapping is zero based, so that 0 maps to A, B maps to 1, Z to 25 and AA to 26.
* *
* @param int $columnIndex The Excel column index (0, 42, ...) * @param int $columnIndexZeroBased The Excel column index (0, 42, ...)
*
* @return string The associated cell index ('A', 'BC', ...) * @return string The associated cell index ('A', 'BC', ...)
*/ */
public static function getCellIndexFromColumnIndex($columnIndex) public static function getColumnLettersFromColumnIndex($columnIndexZeroBased)
{ {
$originalColumnIndex = $columnIndex; $originalColumnIndex = $columnIndexZeroBased;
// Using isset here because it is way faster than array_key_exists... // Using isset here because it is way faster than array_key_exists...
if (!isset(self::$columnIndexToCellIndexCache[$originalColumnIndex])) { if (!isset(self::$columnIndexToColumnLettersCache[$originalColumnIndex])) {
$cellIndex = ''; $columnLetters = '';
$capitalAAsciiValue = ord('A'); $capitalAAsciiValue = \ord('A');
do { do {
$modulus = $columnIndex % 26; $modulus = $columnIndexZeroBased % 26;
$cellIndex = chr($capitalAAsciiValue + $modulus) . $cellIndex; $columnLetters = \chr($capitalAAsciiValue + $modulus) . $columnLetters;
// substracting 1 because it's zero-based // substracting 1 because it's zero-based
$columnIndex = (int) ($columnIndex / 26) - 1; $columnIndexZeroBased = (int) ($columnIndexZeroBased / 26) - 1;
} while ($columnIndex >= 0); } while ($columnIndexZeroBased >= 0);
self::$columnIndexToCellIndexCache[$originalColumnIndex] = $cellIndex; self::$columnIndexToColumnLettersCache[$originalColumnIndex] = $columnLetters;
} }
return self::$columnIndexToCellIndexCache[$originalColumnIndex]; return self::$columnIndexToColumnLettersCache[$originalColumnIndex];
} }
} }
+7 -7
View File
@@ -135,7 +135,7 @@ class ZipHelper
public static function canChooseCompressionMethod() public static function canChooseCompressionMethod()
{ {
// setCompressionName() is a PHP7+ method... // setCompressionName() is a PHP7+ method...
return (method_exists(new \ZipArchive(), 'setCompressionName')); return (\method_exists(new \ZipArchive(), 'setCompressionName'));
} }
/** /**
@@ -151,7 +151,7 @@ class ZipHelper
foreach ($itemIterator as $itemInfo) { foreach ($itemIterator as $itemInfo) {
$itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname()); $itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname());
$itemLocalPath = str_replace($folderRealPath, '', $itemRealPath); $itemLocalPath = \str_replace($folderRealPath, '', $itemRealPath);
if ($itemInfo->isFile() && !$this->shouldSkipFile($zip, $itemLocalPath, $existingFileMode)) { if ($itemInfo->isFile() && !$this->shouldSkipFile($zip, $itemLocalPath, $existingFileMode)) {
$zip->addFile($itemRealPath, $itemLocalPath); $zip->addFile($itemRealPath, $itemLocalPath);
@@ -181,9 +181,9 @@ class ZipHelper
*/ */
protected function getNormalizedRealPath($path) protected function getNormalizedRealPath($path)
{ {
$realPath = realpath($path); $realPath = \realpath($path);
return str_replace(DIRECTORY_SEPARATOR, '/', $realPath); return \str_replace(DIRECTORY_SEPARATOR, '/', $realPath);
} }
/** /**
@@ -210,8 +210,8 @@ class ZipHelper
*/ */
protected function copyZipToStream($zipFilePath, $pointer) protected function copyZipToStream($zipFilePath, $pointer)
{ {
$zipFilePointer = fopen($zipFilePath, 'r'); $zipFilePointer = \fopen($zipFilePath, 'r');
stream_copy_to_stream($zipFilePointer, $pointer); \stream_copy_to_stream($zipFilePointer, $pointer);
fclose($zipFilePointer); \fclose($zipFilePointer);
} }
} }
@@ -45,8 +45,8 @@ class SheetManager
*/ */
public function throwIfNameIsInvalid($name, Sheet $sheet) public function throwIfNameIsInvalid($name, Sheet $sheet)
{ {
if (!is_string($name)) { if (!\is_string($name)) {
$actualType = gettype($name); $actualType = \gettype($name);
$errorMessage = "The sheet's name is invalid. It must be a string ($actualType given)."; $errorMessage = "The sheet's name is invalid. It must be a string ($actualType given).";
throw new InvalidSheetNameException($errorMessage); throw new InvalidSheetNameException($errorMessage);
} }
@@ -74,9 +74,9 @@ class SheetManager
} }
} }
if (count($failedRequirements) !== 0) { if (\count($failedRequirements) !== 0) {
$errorMessage = "The sheet's name (\"$name\") is invalid. It did not respect these rules:\n - "; $errorMessage = "The sheet's name (\"$name\") is invalid. It did not respect these rules:\n - ";
$errorMessage .= implode("\n - ", $failedRequirements); $errorMessage .= \implode("\n - ", $failedRequirements);
throw new InvalidSheetNameException($errorMessage); throw new InvalidSheetNameException($errorMessage);
} }
} }
@@ -90,7 +90,7 @@ class SheetManager
*/ */
private function doesContainInvalidCharacters($name) private function doesContainInvalidCharacters($name)
{ {
return (str_replace(self::$INVALID_CHARACTERS_IN_SHEET_NAME, '', $name) !== $name); return (\str_replace(self::$INVALID_CHARACTERS_IN_SHEET_NAME, '', $name) !== $name);
} }
/** /**
@@ -80,7 +80,7 @@ class StyleManager implements StyleManagerInterface
return $cellStyle; return $cellStyle;
} }
if ($cell->isString() && strpos($cell->getValue(), "\n") !== false) { if ($cell->isString() && \strpos($cell->getValue(), "\n") !== false) {
$cellStyle->setShouldWrapText(); $cellStyle->setShouldWrapText();
} }
@@ -85,9 +85,15 @@ class StyleMerger
if (!$style->hasSetWrapText() && $baseStyle->shouldWrapText()) { if (!$style->hasSetWrapText() && $baseStyle->shouldWrapText()) {
$styleToUpdate->setShouldWrapText(); $styleToUpdate->setShouldWrapText();
} }
if (!$style->hasSetCellAlignment() && $baseStyle->shouldApplyCellAlignment()) {
$styleToUpdate->setCellAlignment($baseStyle->getCellAlignment());
}
if (!$style->getBorder() && $baseStyle->shouldApplyBorder()) { if (!$style->getBorder() && $baseStyle->shouldApplyBorder()) {
$styleToUpdate->setBorder($baseStyle->getBorder()); $styleToUpdate->setBorder($baseStyle->getBorder());
} }
if (!$style->getFormat() && $baseStyle->shouldApplyFormat()) {
$styleToUpdate->setFormat($baseStyle->getFormat());
}
if (!$style->shouldApplyBackgroundColor() && $baseStyle->shouldApplyBackgroundColor()) { if (!$style->shouldApplyBackgroundColor() && $baseStyle->shouldApplyBackgroundColor()) {
$styleToUpdate->setBackgroundColor($baseStyle->getBackgroundColor()); $styleToUpdate->setBackgroundColor($baseStyle->getBackgroundColor());
} }
@@ -37,7 +37,7 @@ class StyleRegistry
$serializedStyle = $this->serialize($style); $serializedStyle = $this->serialize($style);
if (!$this->hasStyleAlreadyBeenRegistered($style)) { if (!$this->hasStyleAlreadyBeenRegistered($style)) {
$nextStyleId = count($this->serializedStyleToStyleIdMappingTable); $nextStyleId = \count($this->serializedStyleToStyleIdMappingTable);
$style->setId($nextStyleId); $style->setId($nextStyleId);
$this->serializedStyleToStyleIdMappingTable[$serializedStyle] = $nextStyleId; $this->serializedStyleToStyleIdMappingTable[$serializedStyle] = $nextStyleId;
@@ -79,7 +79,7 @@ class StyleRegistry
*/ */
public function getRegisteredStyles() public function getRegisteredStyles()
{ {
return array_values($this->styleIdToStyleMappingTable); return \array_values($this->styleIdToStyleMappingTable);
} }
/** /**
@@ -105,7 +105,7 @@ class StyleRegistry
$currentId = $style->getId(); $currentId = $style->getId();
$style->setId(0); $style->setId(0);
$serializedStyle = serialize($style); $serializedStyle = \serialize($style);
$style->setId($currentId); $style->setId($currentId);
@@ -124,7 +124,7 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
{ {
$worksheets = $this->getWorksheets(); $worksheets = $this->getWorksheets();
$newSheetIndex = count($worksheets); $newSheetIndex = \count($worksheets);
$sheetManager = $this->managerFactory->createSheetManager(); $sheetManager = $this->managerFactory->createSheetManager();
$sheet = $this->entityFactory->createSheet($newSheetIndex, $this->workbook->getInternalId(), $sheetManager); $sheet = $this->entityFactory->createSheet($newSheetIndex, $this->workbook->getInternalId(), $sheetManager);
@@ -260,7 +260,7 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
// update max num columns for the worksheet // update max num columns for the worksheet
$currentMaxNumColumns = $worksheet->getMaxNumColumns(); $currentMaxNumColumns = $worksheet->getMaxNumColumns();
$cellsCount = $row->getNumCells(); $cellsCount = $row->getNumCells();
$worksheet->setMaxNumColumns(max($currentMaxNumColumns, $cellsCount)); $worksheet->setMaxNumColumns(\max($currentMaxNumColumns, $cellsCount));
} }
/** /**
@@ -11,6 +11,6 @@ class InvalidNameException extends WriterException
{ {
$msg = '%s is not a valid name identifier for a border. Valid identifiers are: %s.'; $msg = '%s is not a valid name identifier for a border. Valid identifiers are: %s.';
parent::__construct(sprintf($msg, $name, implode(',', BorderPart::getAllowedNames()))); parent::__construct(\sprintf($msg, $name, \implode(',', BorderPart::getAllowedNames())));
} }
} }
@@ -11,6 +11,6 @@ class InvalidStyleException extends WriterException
{ {
$msg = '%s is not a valid style identifier for a border. Valid identifiers are: %s.'; $msg = '%s is not a valid style identifier for a border. Valid identifiers are: %s.';
parent::__construct(sprintf($msg, $name, implode(',', BorderPart::getAllowedStyles()))); parent::__construct(\sprintf($msg, $name, \implode(',', BorderPart::getAllowedStyles())));
} }
} }
@@ -11,6 +11,6 @@ class InvalidWidthException extends WriterException
{ {
$msg = '%s is not a valid width identifier for a border. Valid identifiers are: %s.'; $msg = '%s is not a valid width identifier for a border. Valid identifiers are: %s.';
parent::__construct(sprintf($msg, $name, implode(',', BorderPart::getAllowedWidths()))); parent::__construct(\sprintf($msg, $name, \implode(',', BorderPart::getAllowedWidths())));
} }
} }
+2 -2
View File
@@ -53,14 +53,14 @@ class BorderHelper
$definition = 'fo:border-%s="%s"'; $definition = 'fo:border-%s="%s"';
if ($borderPart->getStyle() === Border::STYLE_NONE) { if ($borderPart->getStyle() === Border::STYLE_NONE) {
$borderPartDefinition = sprintf($definition, $borderPart->getName(), 'none'); $borderPartDefinition = \sprintf($definition, $borderPart->getName(), 'none');
} else { } else {
$attributes = [ $attributes = [
self::$widthMap[$borderPart->getWidth()], self::$widthMap[$borderPart->getWidth()],
self::$styleMap[$borderPart->getStyle()], self::$styleMap[$borderPart->getStyle()],
'#' . $borderPart->getColor(), '#' . $borderPart->getColor(),
]; ];
$borderPartDefinition = sprintf($definition, $borderPart->getName(), implode(' ', $attributes)); $borderPartDefinition = \sprintf($definition, $borderPart->getName(), \implode(' ', $attributes));
} }
return $borderPartDefinition; return $borderPartDefinition;
@@ -89,7 +89,7 @@ class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper impleme
*/ */
protected function createRootFolder() protected function createRootFolder()
{ {
$this->rootFolder = $this->createFolder($this->baseFolderRealPath, uniqid('ods')); $this->rootFolder = $this->createFolder($this->baseFolderRealPath, \uniqid('ods'));
return $this; return $this;
} }
@@ -210,22 +210,22 @@ EOD;
// Append sheets content to "content.xml" // Append sheets content to "content.xml"
$contentXmlFilePath = $this->rootFolder . '/' . self::CONTENT_XML_FILE_NAME; $contentXmlFilePath = $this->rootFolder . '/' . self::CONTENT_XML_FILE_NAME;
$contentXmlHandle = fopen($contentXmlFilePath, 'a'); $contentXmlHandle = \fopen($contentXmlFilePath, 'a');
foreach ($worksheets as $worksheet) { foreach ($worksheets as $worksheet) {
// write the "<table:table>" node, with the final sheet's name // write the "<table:table>" node, with the final sheet's name
fwrite($contentXmlHandle, $worksheetManager->getTableElementStartAsString($worksheet)); \fwrite($contentXmlHandle, $worksheetManager->getTableElementStartAsString($worksheet));
$worksheetFilePath = $worksheet->getFilePath(); $worksheetFilePath = $worksheet->getFilePath();
$this->copyFileContentsToTarget($worksheetFilePath, $contentXmlHandle); $this->copyFileContentsToTarget($worksheetFilePath, $contentXmlHandle);
fwrite($contentXmlHandle, '</table:table>'); \fwrite($contentXmlHandle, '</table:table>');
} }
$contentXmlFileContents = '</office:spreadsheet></office:body></office:document-content>'; $contentXmlFileContents = '</office:spreadsheet></office:body></office:document-content>';
fwrite($contentXmlHandle, $contentXmlFileContents); \fwrite($contentXmlHandle, $contentXmlFileContents);
fclose($contentXmlHandle); \fclose($contentXmlHandle);
return $this; return $this;
} }
@@ -241,9 +241,9 @@ EOD;
*/ */
protected function copyFileContentsToTarget($sourceFilePath, $targetResource) protected function copyFileContentsToTarget($sourceFilePath, $targetResource)
{ {
$sourceHandle = fopen($sourceFilePath, 'r'); $sourceHandle = \fopen($sourceFilePath, 'r');
stream_copy_to_stream($sourceHandle, $targetResource); \stream_copy_to_stream($sourceHandle, $targetResource);
fclose($sourceHandle); \fclose($sourceHandle);
} }
/** /**
@@ -42,7 +42,7 @@ class OptionsManager extends OptionsManagerAbstract
*/ */
protected function setDefaultOptions() protected function setDefaultOptions()
{ {
$this->setOption(Options::TEMP_FOLDER, sys_get_temp_dir()); $this->setOption(Options::TEMP_FOLDER, \sys_get_temp_dir());
$this->setOption(Options::DEFAULT_ROW_STYLE, $this->styleBuilder->build()); $this->setOption(Options::DEFAULT_ROW_STYLE, $this->styleBuilder->build());
$this->setOption(Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, true); $this->setOption(Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, true);
} }
@@ -3,6 +3,7 @@
namespace Box\Spout\Writer\ODS\Manager\Style; namespace Box\Spout\Writer\ODS\Manager\Style;
use Box\Spout\Common\Entity\Style\BorderPart; use Box\Spout\Common\Entity\Style\BorderPart;
use Box\Spout\Common\Entity\Style\CellAlignment;
use Box\Spout\Writer\Common\Entity\Worksheet; use Box\Spout\Writer\Common\Entity\Worksheet;
use Box\Spout\Writer\ODS\Helper\BorderHelper; use Box\Spout\Writer\ODS\Helper\BorderHelper;
@@ -199,6 +200,7 @@ EOD;
$content = '<style:style style:data-style-name="N0" style:family="table-cell" style:name="ce' . $styleIndex . '" style:parent-style-name="Default">'; $content = '<style:style style:data-style-name="N0" style:family="table-cell" style:name="ce' . $styleIndex . '" style:parent-style-name="Default">';
$content .= $this->getTextPropertiesSectionContent($style); $content .= $this->getTextPropertiesSectionContent($style);
$content .= $this->getParagraphPropertiesSectionContent($style);
$content .= $this->getTableCellPropertiesSectionContent($style); $content .= $this->getTableCellPropertiesSectionContent($style);
$content .= '</style:style>'; $content .= '</style:style>';
@@ -214,26 +216,26 @@ EOD;
*/ */
private function getTextPropertiesSectionContent($style) private function getTextPropertiesSectionContent($style)
{ {
$content = ''; if (!$style->shouldApplyFont()) {
return '';
if ($style->shouldApplyFont()) {
$content .= $this->getFontSectionContent($style);
} }
return $content; return '<style:text-properties '
. $this->getFontSectionContent($style)
. '/>';
} }
/** /**
* Returns the contents of the "<style:text-properties>" section, inside "<style:style>" section * Returns the contents of the fonts definition section, inside "<style:text-properties>" section
* *
* @param \Box\Spout\Common\Entity\Style\Style $style * @param \Box\Spout\Common\Entity\Style\Style $style
*
* @return string * @return string
*/ */
private function getFontSectionContent($style) private function getFontSectionContent($style)
{ {
$defaultStyle = $this->getDefaultStyle(); $defaultStyle = $this->getDefaultStyle();
$content = '';
$content = '<style:text-properties';
$fontColor = $style->getFontColor(); $fontColor = $style->getFontColor();
if ($fontColor !== $defaultStyle->getFontColor()) { if ($fontColor !== $defaultStyle->getFontColor()) {
@@ -263,11 +265,60 @@ EOD;
$content .= ' style:text-line-through-style="solid"'; $content .= ' style:text-line-through-style="solid"';
} }
$content .= '/>';
return $content; return $content;
} }
/**
* Returns the contents of the "<style:paragraph-properties>" section, inside "<style:style>" section
*
* @param \Box\Spout\Common\Entity\Style\Style $style
*
* @return string
*/
private function getParagraphPropertiesSectionContent($style)
{
if (!$style->shouldApplyCellAlignment()) {
return '';
}
return '<style:paragraph-properties '
. $this->getCellAlignmentSectionContent($style)
. '/>';
}
/**
* Returns the contents of the cell alignment definition for the "<style:paragraph-properties>" section
*
* @param \Box\Spout\Common\Entity\Style\Style $style
*
* @return string
*/
private function getCellAlignmentSectionContent($style)
{
return \sprintf(
' fo:text-align="%s" ',
$this->transformCellAlignment($style->getCellAlignment())
);
}
/**
* Even though "left" and "right" alignments are part of the spec, and interpreted
* respectively as "start" and "end", using the recommended values increase compatibility
* with software that will read the created ODS file.
*
* @param string $cellAlignment
*
* @return string
*/
private function transformCellAlignment($cellAlignment)
{
switch ($cellAlignment) {
case CellAlignment::LEFT: return 'start';
case CellAlignment::RIGHT: return 'end';
default: return $cellAlignment;
}
}
/** /**
* Returns the contents of the "<style:table-cell-properties>" section, inside "<style:style>" section * Returns the contents of the "<style:table-cell-properties>" section, inside "<style:style>" section
* *
@@ -276,7 +327,7 @@ EOD;
*/ */
private function getTableCellPropertiesSectionContent($style) private function getTableCellPropertiesSectionContent($style)
{ {
$content = ''; $content = '<style:table-cell-properties ';
if ($style->shouldWrapText()) { if ($style->shouldWrapText()) {
$content .= $this->getWrapTextXMLContent(); $content .= $this->getWrapTextXMLContent();
@@ -290,6 +341,8 @@ EOD;
$content .= $this->getBackgroundColorXMLContent($style); $content .= $this->getBackgroundColorXMLContent($style);
} }
$content .= '/>';
return $content; return $content;
} }
@@ -300,7 +353,7 @@ EOD;
*/ */
private function getWrapTextXMLContent() private function getWrapTextXMLContent()
{ {
return '<style:table-cell-properties fo:wrap-option="wrap" style:vertical-align="automatic"/>'; return ' fo:wrap-option="wrap" style:vertical-align="automatic" ';
} }
/** /**
@@ -311,13 +364,11 @@ EOD;
*/ */
private function getBorderXMLContent($style) private function getBorderXMLContent($style)
{ {
$borderProperty = '<style:table-cell-properties %s />'; $borders = \array_map(function (BorderPart $borderPart) {
$borders = array_map(function (BorderPart $borderPart) {
return BorderHelper::serializeBorderPart($borderPart); return BorderHelper::serializeBorderPart($borderPart);
}, $style->getBorder()->getParts()); }, $style->getBorder()->getParts());
return sprintf($borderProperty, implode(' ', $borders)); return \sprintf(' %s ', \implode(' ', $borders));
} }
/** /**
@@ -328,9 +379,6 @@ EOD;
*/ */
private function getBackgroundColorXMLContent($style) private function getBackgroundColorXMLContent($style)
{ {
return sprintf( return \sprintf(' fo:background-color="#%s" ', $style->getBackgroundColor());
'<style:table-cell-properties fo:background-color="#%s"/>',
$style->getBackgroundColor()
);
} }
} }
@@ -33,6 +33,6 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
*/ */
public function getUsedFonts() public function getUsedFonts()
{ {
return array_keys($this->usedFontsSet); return \array_keys($this->usedFontsSet);
} }
} }
@@ -56,7 +56,7 @@ class WorkbookManager extends WorkbookManagerAbstract
protected function writeAllFilesToDiskAndZipThem($finalFilePointer) protected function writeAllFilesToDiskAndZipThem($finalFilePointer)
{ {
$worksheets = $this->getWorksheets(); $worksheets = $this->getWorksheets();
$numWorksheets = count($worksheets); $numWorksheets = \count($worksheets);
$this->fileSystemHelper $this->fileSystemHelper
->createContentFile($this->worksheetManager, $this->styleManager, $worksheets) ->createContentFile($this->worksheetManager, $this->styleManager, $worksheets)
@@ -61,7 +61,7 @@ class WorksheetManager implements WorksheetManagerInterface
*/ */
public function startSheet(Worksheet $worksheet) public function startSheet(Worksheet $worksheet)
{ {
$sheetFilePointer = fopen($worksheet->getFilePath(), 'w'); $sheetFilePointer = \fopen($worksheet->getFilePath(), 'w');
$this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer); $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
$worksheet->setFilePointer($sheetFilePointer); $worksheet->setFilePointer($sheetFilePointer);
@@ -134,7 +134,7 @@ class WorksheetManager implements WorksheetManagerInterface
$data .= '</table:table-row>'; $data .= '</table:table-row>';
$wasWriteSuccessful = fwrite($worksheet->getFilePointer(), $data); $wasWriteSuccessful = \fwrite($worksheet->getFilePointer(), $data);
if ($wasWriteSuccessful === false) { if ($wasWriteSuccessful === false) {
throw new IOException("Unable to write data in {$worksheet->getFilePath()}"); throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
} }
@@ -190,7 +190,7 @@ class WorksheetManager implements WorksheetManagerInterface
if ($cell->isString()) { if ($cell->isString()) {
$data .= ' office:value-type="string" calcext:value-type="string">'; $data .= ' office:value-type="string" calcext:value-type="string">';
$cellValueLines = explode("\n", $cell->getValue()); $cellValueLines = \explode("\n", $cell->getValue());
foreach ($cellValueLines as $cellValueLine) { foreach ($cellValueLines as $cellValueLine) {
$data .= '<text:p>' . $this->stringsEscaper->escape($cellValueLine) . '</text:p>'; $data .= '<text:p>' . $this->stringsEscaper->escape($cellValueLine) . '</text:p>';
} }
@@ -204,10 +204,15 @@ class WorksheetManager implements WorksheetManagerInterface
$data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cell->getValue() . '">'; $data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cell->getValue() . '">';
$data .= '<text:p>' . $cell->getValue() . '</text:p>'; $data .= '<text:p>' . $cell->getValue() . '</text:p>';
$data .= '</table:table-cell>'; $data .= '</table:table-cell>';
} elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
// only writes the error value if it's a string
$data .= ' office:value-type="string" calcext:value-type="error" office:value="">';
$data .= '<text:p>' . $cell->getValueEvenIfError() . '</text:p>';
$data .= '</table:table-cell>';
} elseif ($cell->isEmpty()) { } elseif ($cell->isEmpty()) {
$data .= '/>'; $data .= '/>';
} else { } else {
throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cell->getValue())); throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . \gettype($cell->getValue()));
} }
return $data; return $data;
@@ -223,10 +228,10 @@ class WorksheetManager implements WorksheetManagerInterface
{ {
$worksheetFilePointer = $worksheet->getFilePointer(); $worksheetFilePointer = $worksheet->getFilePointer();
if (!is_resource($worksheetFilePointer)) { if (!\is_resource($worksheetFilePointer)) {
return; return;
} }
fclose($worksheetFilePointer); \fclose($worksheetFilePointer);
} }
} }
+2 -2
View File
@@ -223,7 +223,7 @@ abstract class WriterAbstract implements WriterInterface
$this->closeWriter(); $this->closeWriter();
if (is_resource($this->filePointer)) { if (\is_resource($this->filePointer)) {
$this->globalFunctionsHelper->fclose($this->filePointer); $this->globalFunctionsHelper->fclose($this->filePointer);
} }
@@ -243,7 +243,7 @@ abstract class WriterAbstract implements WriterInterface
// remove output file if it was created // remove output file if it was created
if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) { if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) {
$outputFolderPath = dirname($this->outputFilePath); $outputFolderPath = \dirname($this->outputFilePath);
$fileSystemHelper = $this->helperFactory->createFileSystemHelper($outputFolderPath); $fileSystemHelper = $this->helperFactory->createFileSystemHelper($outputFolderPath);
$fileSystemHelper->deleteFile($this->outputFilePath); $fileSystemHelper->deleteFile($this->outputFilePath);
} }
@@ -43,8 +43,8 @@ class BorderHelper
{ {
$borderStyle = self::getBorderStyle($borderPart); $borderStyle = self::getBorderStyle($borderPart);
$colorEl = $borderPart->getColor() ? sprintf('<color rgb="%s"/>', $borderPart->getColor()) : ''; $colorEl = $borderPart->getColor() ? \sprintf('<color rgb="%s"/>', $borderPart->getColor()) : '';
$partEl = sprintf( $partEl = \sprintf(
'<%s style="%s">%s</%s>', '<%s style="%s">%s</%s>',
$borderPart->getName(), $borderPart->getName(),
$borderStyle, $borderStyle,
@@ -112,7 +112,7 @@ class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper impleme
*/ */
private function createRootFolder() private function createRootFolder()
{ {
$this->rootFolder = $this->createFolder($this->baseFolderRealPath, uniqid('xlsx', true)); $this->rootFolder = $this->createFolder($this->baseFolderRealPath, \uniqid('xlsx', true));
return $this; return $this;
} }
@@ -52,7 +52,7 @@ class OptionsManager extends OptionsManagerAbstract
->setFontName(self::DEFAULT_FONT_NAME) ->setFontName(self::DEFAULT_FONT_NAME)
->build(); ->build();
$this->setOption(Options::TEMP_FOLDER, sys_get_temp_dir()); $this->setOption(Options::TEMP_FOLDER, \sys_get_temp_dir());
$this->setOption(Options::DEFAULT_ROW_STYLE, $defaultRowStyle); $this->setOption(Options::DEFAULT_ROW_STYLE, $defaultRowStyle);
$this->setOption(Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, true); $this->setOption(Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, true);
$this->setOption(Options::SHOULD_USE_INLINE_STRINGS, true); $this->setOption(Options::SHOULD_USE_INLINE_STRINGS, true);
@@ -40,13 +40,13 @@ EOD;
public function __construct($xlFolder, $stringsEscaper) public function __construct($xlFolder, $stringsEscaper)
{ {
$sharedStringsFilePath = $xlFolder . '/' . self::SHARED_STRINGS_FILE_NAME; $sharedStringsFilePath = $xlFolder . '/' . self::SHARED_STRINGS_FILE_NAME;
$this->sharedStringsFilePointer = fopen($sharedStringsFilePath, 'w'); $this->sharedStringsFilePointer = \fopen($sharedStringsFilePath, 'w');
$this->throwIfSharedStringsFilePointerIsNotAvailable(); $this->throwIfSharedStringsFilePointerIsNotAvailable();
// the headers is split into different parts so that we can fseek and put in the correct count and uniqueCount later // the headers is split into different parts so that we can fseek and put in the correct count and uniqueCount later
$header = self::SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER . ' ' . self::DEFAULT_STRINGS_COUNT_PART . '>'; $header = self::SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER . ' ' . self::DEFAULT_STRINGS_COUNT_PART . '>';
fwrite($this->sharedStringsFilePointer, $header); \fwrite($this->sharedStringsFilePointer, $header);
$this->stringsEscaper = $stringsEscaper; $this->stringsEscaper = $stringsEscaper;
} }
@@ -73,7 +73,7 @@ EOD;
*/ */
public function writeString($string) public function writeString($string)
{ {
fwrite($this->sharedStringsFilePointer, '<si><t xml:space="preserve">' . $this->stringsEscaper->escape($string) . '</t></si>'); \fwrite($this->sharedStringsFilePointer, '<si><t xml:space="preserve">' . $this->stringsEscaper->escape($string) . '</t></si>');
$this->numSharedStrings++; $this->numSharedStrings++;
// Shared string ID is zero-based // Shared string ID is zero-based
@@ -87,20 +87,20 @@ EOD;
*/ */
public function close() public function close()
{ {
if (!is_resource($this->sharedStringsFilePointer)) { if (!\is_resource($this->sharedStringsFilePointer)) {
return; return;
} }
fwrite($this->sharedStringsFilePointer, '</sst>'); \fwrite($this->sharedStringsFilePointer, '</sst>');
// Replace the default strings count with the actual number of shared strings in the file header // Replace the default strings count with the actual number of shared strings in the file header
$firstPartHeaderLength = strlen(self::SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER); $firstPartHeaderLength = \strlen(self::SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER);
$defaultStringsCountPartLength = strlen(self::DEFAULT_STRINGS_COUNT_PART); $defaultStringsCountPartLength = \strlen(self::DEFAULT_STRINGS_COUNT_PART);
// Adding 1 to take into account the space between the last xml attribute and "count" // Adding 1 to take into account the space between the last xml attribute and "count"
fseek($this->sharedStringsFilePointer, $firstPartHeaderLength + 1); \fseek($this->sharedStringsFilePointer, $firstPartHeaderLength + 1);
fwrite($this->sharedStringsFilePointer, sprintf("%-{$defaultStringsCountPartLength}s", 'count="' . $this->numSharedStrings . '" uniqueCount="' . $this->numSharedStrings . '"')); \fwrite($this->sharedStringsFilePointer, \sprintf("%-{$defaultStringsCountPartLength}s", 'count="' . $this->numSharedStrings . '" uniqueCount="' . $this->numSharedStrings . '"'));
fclose($this->sharedStringsFilePointer); \fclose($this->sharedStringsFilePointer);
} }
} }
@@ -33,7 +33,10 @@ class StyleManager extends \Box\Spout\Writer\Common\Manager\Style\StyleManager
$associatedBorderId = $this->styleRegistry->getBorderIdForStyleId($styleId); $associatedBorderId = $this->styleRegistry->getBorderIdForStyleId($styleId);
$hasStyleCustomBorders = ($associatedBorderId !== null && $associatedBorderId !== 0); $hasStyleCustomBorders = ($associatedBorderId !== null && $associatedBorderId !== 0);
return ($hasStyleCustomFill || $hasStyleCustomBorders); $associatedFormatId = $this->styleRegistry->getFormatIdForStyleId($styleId);
$hasStyleCustomFormats = ($associatedFormatId !== null && $associatedFormatId !== 0);
return ($hasStyleCustomFill || $hasStyleCustomBorders || $hasStyleCustomFormats);
} }
/** /**
@@ -48,6 +51,7 @@ class StyleManager extends \Box\Spout\Writer\Common\Manager\Style\StyleManager
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"> <styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
EOD; EOD;
$content .= $this->getFormatsSectionContent();
$content .= $this->getFontsSectionContent(); $content .= $this->getFontsSectionContent();
$content .= $this->getFillsSectionContent(); $content .= $this->getFillsSectionContent();
$content .= $this->getBordersSectionContent(); $content .= $this->getBordersSectionContent();
@@ -62,6 +66,35 @@ EOD;
return $content; return $content;
} }
/**
* Returns the content of the "<numFmts>" section.
*
* @return string
*/
protected function getFormatsSectionContent()
{
$tags = [];
$registeredFormats = $this->styleRegistry->getRegisteredFormats();
foreach ($registeredFormats as $styleId) {
$numFmtId = $this->styleRegistry->getFormatIdForStyleId($styleId);
//Built-in formats do not need to be declared, skip them
if ($numFmtId < 164) {
continue;
}
/** @var Style $style */
$style = $this->styleRegistry->getStyleFromStyleId($styleId);
$format = $style->getFormat();
$tags[] = '<numFmt numFmtId="' . $numFmtId . '" formatCode="' . $format . '"/>';
}
$content = '<numFmts count="' . \count($tags) . '">';
$content .= \implode('', $tags);
$content .= '</numFmts>';
return $content;
}
/** /**
* Returns the content of the "<fonts>" section. * Returns the content of the "<fonts>" section.
* *
@@ -71,7 +104,7 @@ EOD;
{ {
$registeredStyles = $this->styleRegistry->getRegisteredStyles(); $registeredStyles = $this->styleRegistry->getRegisteredStyles();
$content = '<fonts count="' . count($registeredStyles) . '">'; $content = '<fonts count="' . \count($registeredStyles) . '">';
/** @var Style $style */ /** @var Style $style */
foreach ($registeredStyles as $style) { foreach ($registeredStyles as $style) {
@@ -112,8 +145,8 @@ EOD;
$registeredFills = $this->styleRegistry->getRegisteredFills(); $registeredFills = $this->styleRegistry->getRegisteredFills();
// Excel reserves two default fills // Excel reserves two default fills
$fillsCount = count($registeredFills) + 2; $fillsCount = \count($registeredFills) + 2;
$content = sprintf('<fills count="%d">', $fillsCount); $content = \sprintf('<fills count="%d">', $fillsCount);
$content .= '<fill><patternFill patternType="none"/></fill>'; $content .= '<fill><patternFill patternType="none"/></fill>';
$content .= '<fill><patternFill patternType="gray125"/></fill>'; $content .= '<fill><patternFill patternType="gray125"/></fill>';
@@ -124,7 +157,7 @@ EOD;
$style = $this->styleRegistry->getStyleFromStyleId($styleId); $style = $this->styleRegistry->getStyleFromStyleId($styleId);
$backgroundColor = $style->getBackgroundColor(); $backgroundColor = $style->getBackgroundColor();
$content .= sprintf( $content .= \sprintf(
'<fill><patternFill patternType="solid"><fgColor rgb="%s"/></patternFill></fill>', '<fill><patternFill patternType="solid"><fgColor rgb="%s"/></patternFill></fill>',
$backgroundColor $backgroundColor
); );
@@ -145,7 +178,7 @@ EOD;
$registeredBorders = $this->styleRegistry->getRegisteredBorders(); $registeredBorders = $this->styleRegistry->getRegisteredBorders();
// There is one default border with index 0 // There is one default border with index 0
$borderCount = count($registeredBorders) + 1; $borderCount = \count($registeredBorders) + 1;
$content = '<borders count="' . $borderCount . '">'; $content = '<borders count="' . $borderCount . '">';
@@ -200,24 +233,32 @@ EOD;
{ {
$registeredStyles = $this->styleRegistry->getRegisteredStyles(); $registeredStyles = $this->styleRegistry->getRegisteredStyles();
$content = '<cellXfs count="' . count($registeredStyles) . '">'; $content = '<cellXfs count="' . \count($registeredStyles) . '">';
foreach ($registeredStyles as $style) { foreach ($registeredStyles as $style) {
$styleId = $style->getId(); $styleId = $style->getId();
$fillId = $this->getFillIdForStyleId($styleId); $fillId = $this->getFillIdForStyleId($styleId);
$borderId = $this->getBorderIdForStyleId($styleId); $borderId = $this->getBorderIdForStyleId($styleId);
$numFmtId = $this->getFormatIdForStyleId($styleId);
$content .= '<xf numFmtId="0" fontId="' . $styleId . '" fillId="' . $fillId . '" borderId="' . $borderId . '" xfId="0"'; $content .= '<xf numFmtId="' . $numFmtId . '" fontId="' . $styleId . '" fillId="' . $fillId . '" borderId="' . $borderId . '" xfId="0"';
if ($style->shouldApplyFont()) { if ($style->shouldApplyFont()) {
$content .= ' applyFont="1"'; $content .= ' applyFont="1"';
} }
$content .= sprintf(' applyBorder="%d"', $style->shouldApplyBorder() ? 1 : 0); $content .= \sprintf(' applyBorder="%d"', $style->shouldApplyBorder() ? 1 : 0);
if ($style->shouldWrapText()) { if ($style->shouldApplyCellAlignment() || $style->shouldWrapText()) {
$content .= ' applyAlignment="1">'; $content .= ' applyAlignment="1">';
$content .= '<alignment wrapText="1"/>'; $content .= '<alignment';
if ($style->shouldApplyCellAlignment()) {
$content .= \sprintf(' horizontal="%s"', $style->getCellAlignment());
}
if ($style->shouldWrapText()) {
$content .= ' wrapText="1"';
}
$content .= '/>';
$content .= '</xf>'; $content .= '</xf>';
} else { } else {
$content .= '/>'; $content .= '/>';
@@ -261,6 +302,22 @@ EOD;
return $isDefaultStyle ? 0 : ($this->styleRegistry->getBorderIdForStyleId($styleId) ?: 0); return $isDefaultStyle ? 0 : ($this->styleRegistry->getBorderIdForStyleId($styleId) ?: 0);
} }
/**
* Returns the format ID associated to the given style ID.
* For the default style use general format.
*
* @param int $styleId
* @return int
*/
private function getFormatIdForStyleId($styleId)
{
// For the default style (ID = 0), we don't want to override the format.
// Otherwise all cells of the spreadsheet will have a format.
$isDefaultStyle = ($styleId === 0);
return $isDefaultStyle ? 0 : ($this->styleRegistry->getFormatIdForStyleId($styleId) ?: 0);
}
/** /**
* Returns the content of the "<cellStyles>" section. * Returns the content of the "<cellStyles>" section.
* *
@@ -10,6 +10,79 @@ use Box\Spout\Common\Entity\Style\Style;
*/ */
class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
{ {
/**
* @see https://msdn.microsoft.com/en-us/library/ff529597(v=office.12).aspx
* @var array Mapping between built-in format and the associated numFmtId
*/
protected static $builtinNumFormatToIdMapping = [
'General' => 0,
'0' => 1,
'0.00' => 2,
'#,##0' => 3,
'#,##0.00' => 4,
'$#,##0,\-$#,##0' => 5,
'$#,##0,[Red]\-$#,##0' => 6,
'$#,##0.00,\-$#,##0.00' => 7,
'$#,##0.00,[Red]\-$#,##0.00' => 8,
'0%' => 9,
'0.00%' => 10,
'0.00E+00' => 11,
'# ?/?' => 12,
'# ??/??' => 13,
'mm-dd-yy' => 14,
'd-mmm-yy' => 15,
'd-mmm' => 16,
'mmm-yy' => 17,
'h:mm AM/PM' => 18,
'h:mm:ss AM/PM' => 19,
'h:mm' => 20,
'h:mm:ss' => 21,
'm/d/yy h:mm' => 22,
'#,##0 ,(#,##0)' => 37,
'#,##0 ,[Red](#,##0)' => 38,
'#,##0.00,(#,##0.00)' => 39,
'#,##0.00,[Red](#,##0.00)' => 40,
'_("$"* #,##0.00_),_("$"* \(#,##0.00\),_("$"* "-"??_),_(@_)' => 44,
'mm:ss' => 45,
'[h]:mm:ss' => 46,
'mm:ss.0' => 47,
'##0.0E+0' => 48,
'@' => 49,
'[$-404]e/m/d' => 27,
'm/d/yy' => 30,
't0' => 59,
't0.00' => 60,
't#,##0' => 61,
't#,##0.00' => 62,
't0%' => 67,
't0.00%' => 68,
't# ?/?' => 69,
't# ??/??' => 70,
];
/**
* @var array
*/
protected $registeredFormats = [];
/**
* @var array [STYLE_ID] => [FORMAT_ID] maps a style to a format declaration
*/
protected $styleIdToFormatsMappingTable = [];
/**
* If the numFmtId is lower than 0xA4 (164 in decimal)
* then it's a built-in number format.
* Since Excel is the dominant vendor - we play along here
*
* @var int The fill index counter for custom fills.
*/
protected $formatIndex = 164;
/** /**
* @var array * @var array
*/ */
@@ -48,11 +121,52 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
{ {
$registeredStyle = parent::registerStyle($style); $registeredStyle = parent::registerStyle($style);
$this->registerFill($registeredStyle); $this->registerFill($registeredStyle);
$this->registerFormat($registeredStyle);
$this->registerBorder($registeredStyle); $this->registerBorder($registeredStyle);
return $registeredStyle; return $registeredStyle;
} }
/**
* Register a format definition
*
* @param Style $style
*/
protected function registerFormat(Style $style)
{
$styleId = $style->getId();
$format = $style->getFormat();
if ($format) {
$isFormatRegistered = isset($this->registeredFormats[$format]);
// We need to track the already registered format definitions
if ($isFormatRegistered) {
$registeredStyleId = $this->registeredFormats[$format];
$registeredFormatId = $this->styleIdToFormatsMappingTable[$registeredStyleId];
$this->styleIdToFormatsMappingTable[$styleId] = $registeredFormatId;
} else {
$this->registeredFormats[$format] = $styleId;
$id = self::$builtinNumFormatToIdMapping[$format] ?? $this->formatIndex++;
$this->styleIdToFormatsMappingTable[$styleId] = $id;
}
} else {
// The formatId maps a style to a format declaration
// When there is no format definition - we default to 0 ( General )
$this->styleIdToFormatsMappingTable[$styleId] = 0;
}
}
/**
* @param int $styleId
* @return int|null Format ID associated to the given style ID
*/
public function getFormatIdForStyleId($styleId)
{
return $this->styleIdToFormatsMappingTable[$styleId] ?? null;
}
/** /**
* Register a fill definition * Register a fill definition
* *
@@ -107,7 +221,7 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
if ($style->shouldApplyBorder()) { if ($style->shouldApplyBorder()) {
$border = $style->getBorder(); $border = $style->getBorder();
$serializedBorder = serialize($border); $serializedBorder = \serialize($border);
$isBorderAlreadyRegistered = isset($this->registeredBorders[$serializedBorder]); $isBorderAlreadyRegistered = isset($this->registeredBorders[$serializedBorder]);
@@ -117,7 +231,7 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
$this->styleIdToBorderMappingTable[$styleId] = $registeredBorderId; $this->styleIdToBorderMappingTable[$styleId] = $registeredBorderId;
} else { } else {
$this->registeredBorders[$serializedBorder] = $styleId; $this->registeredBorders[$serializedBorder] = $styleId;
$this->styleIdToBorderMappingTable[$styleId] = count($this->registeredBorders); $this->styleIdToBorderMappingTable[$styleId] = \count($this->registeredBorders);
} }
} else { } else {
// If no border should be applied - the mapping is the default border: 0 // If no border should be applied - the mapping is the default border: 0
@@ -151,4 +265,12 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
{ {
return $this->registeredBorders; return $this->registeredBorders;
} }
/**
* @return array
*/
public function getRegisteredFormats()
{
return $this->registeredFormats;
}
} }
@@ -44,7 +44,7 @@ class WorkbookManager extends WorkbookManagerAbstract
{ {
$worksheetFilesFolder = $this->fileSystemHelper->getXlWorksheetsFolder(); $worksheetFilesFolder = $this->fileSystemHelper->getXlWorksheetsFolder();
return $worksheetFilesFolder . '/' . strtolower($sheet->getName()) . '.xml'; return $worksheetFilesFolder . '/' . \strtolower($sheet->getName()) . '.xml';
} }
/** /**
@@ -107,13 +107,13 @@ EOD;
*/ */
public function startSheet(Worksheet $worksheet) public function startSheet(Worksheet $worksheet)
{ {
$sheetFilePointer = fopen($worksheet->getFilePath(), 'w'); $sheetFilePointer = \fopen($worksheet->getFilePath(), 'w');
$this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer); $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
$worksheet->setFilePointer($sheetFilePointer); $worksheet->setFilePointer($sheetFilePointer);
fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER); \fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER);
fwrite($sheetFilePointer, '<sheetData>'); \fwrite($sheetFilePointer, '<sheetData>');
} }
/** /**
@@ -153,21 +153,19 @@ EOD;
*/ */
private function addNonEmptyRow(Worksheet $worksheet, Row $row) private function addNonEmptyRow(Worksheet $worksheet, Row $row)
{ {
$cellIndex = 0;
$rowStyle = $row->getStyle(); $rowStyle = $row->getStyle();
$rowIndex = $worksheet->getLastWrittenRowIndex() + 1; $rowIndexOneBased = $worksheet->getLastWrittenRowIndex() + 1;
$numCells = $row->getNumCells(); $numCells = $row->getNumCells();
$rowXML = '<row r="' . $rowIndex . '" spans="1:' . $numCells . '">'; $rowXML = '<row r="' . $rowIndexOneBased . '" spans="1:' . $numCells . '">';
foreach ($row->getCells() as $cell) { foreach ($row->getCells() as $columnIndexZeroBased => $cell) {
$rowXML .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $rowIndex, $cellIndex); $rowXML .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $rowIndexOneBased, $columnIndexZeroBased);
$cellIndex++;
} }
$rowXML .= '</row>'; $rowXML .= '</row>';
$wasWriteSuccessful = fwrite($worksheet->getFilePointer(), $rowXML); $wasWriteSuccessful = \fwrite($worksheet->getFilePointer(), $rowXML);
if ($wasWriteSuccessful === false) { if ($wasWriteSuccessful === false) {
throw new IOException("Unable to write data in {$worksheet->getFilePath()}"); throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
} }
@@ -179,12 +177,13 @@ EOD;
* *
* @param Cell $cell * @param Cell $cell
* @param Style $rowStyle * @param Style $rowStyle
* @param int $rowIndex * @param int $rowIndexOneBased
* @param int $cellIndex * @param int $columnIndexZeroBased
*
* @throws InvalidArgumentException If the given value cannot be processed * @throws InvalidArgumentException If the given value cannot be processed
* @return string * @return string
*/ */
private function applyStyleAndGetCellXML(Cell $cell, Style $rowStyle, $rowIndex, $cellIndex) private function applyStyleAndGetCellXML(Cell $cell, Style $rowStyle, $rowIndexOneBased, $columnIndexZeroBased)
{ {
// Apply row and extra styles // Apply row and extra styles
$mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle); $mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
@@ -193,23 +192,24 @@ EOD;
$registeredStyle = $this->styleManager->registerStyle($newCellStyle); $registeredStyle = $this->styleManager->registerStyle($newCellStyle);
return $this->getCellXML($rowIndex, $cellIndex, $cell, $registeredStyle->getId()); return $this->getCellXML($rowIndexOneBased, $columnIndexZeroBased, $cell, $registeredStyle->getId());
} }
/** /**
* Builds and returns xml for a single cell. * Builds and returns xml for a single cell.
* *
* @param int $rowIndex * @param int $rowIndexOneBased
* @param int $cellNumber * @param int $columnIndexZeroBased
* @param Cell $cell * @param Cell $cell
* @param int $styleId * @param int $styleId
*
* @throws InvalidArgumentException If the given value cannot be processed * @throws InvalidArgumentException If the given value cannot be processed
* @return string * @return string
*/ */
private function getCellXML($rowIndex, $cellNumber, Cell $cell, $styleId) private function getCellXML($rowIndexOneBased, $columnIndexZeroBased, Cell $cell, $styleId)
{ {
$columnIndex = CellHelper::getCellIndexFromColumnIndex($cellNumber); $columnLetters = CellHelper::getColumnLettersFromColumnIndex($columnIndexZeroBased);
$cellXML = '<c r="' . $columnIndex . $rowIndex . '"'; $cellXML = '<c r="' . $columnLetters . $rowIndexOneBased . '"';
$cellXML .= ' s="' . $styleId . '"'; $cellXML .= ' s="' . $styleId . '"';
if ($cell->isString()) { if ($cell->isString()) {
@@ -218,6 +218,9 @@ EOD;
$cellXML .= ' t="b"><v>' . (int) ($cell->getValue()) . '</v></c>'; $cellXML .= ' t="b"><v>' . (int) ($cell->getValue()) . '</v></c>';
} elseif ($cell->isNumeric()) { } elseif ($cell->isNumeric()) {
$cellXML .= '><v>' . $cell->getValue() . '</v></c>'; $cellXML .= '><v>' . $cell->getValue() . '</v></c>';
} elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
// only writes the error value if it's a string
$cellXML .= ' t="e"><v>' . $cell->getValueEvenIfError() . '</v></c>';
} elseif ($cell->isEmpty()) { } elseif ($cell->isEmpty()) {
if ($this->styleManager->shouldApplyStyleOnEmptyCell($styleId)) { if ($this->styleManager->shouldApplyStyleOnEmptyCell($styleId)) {
$cellXML .= '/>'; $cellXML .= '/>';
@@ -227,7 +230,7 @@ EOD;
$cellXML = ''; $cellXML = '';
} }
} else { } else {
throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cell->getValue())); throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . \gettype($cell->getValue()));
} }
return $cellXML; return $cellXML;
@@ -263,12 +266,12 @@ EOD;
{ {
$worksheetFilePointer = $worksheet->getFilePointer(); $worksheetFilePointer = $worksheet->getFilePointer();
if (!is_resource($worksheetFilePointer)) { if (!\is_resource($worksheetFilePointer)) {
return; return;
} }
fwrite($worksheetFilePointer, '</sheetData>'); \fwrite($worksheetFilePointer, '</sheetData>');
fwrite($worksheetFilePointer, '</worksheet>'); \fwrite($worksheetFilePointer, '</worksheet>');
fclose($worksheetFilePointer); \fclose($worksheetFilePointer);
} }
} }
+14
View File
@@ -78,10 +78,24 @@ class RowTest extends \PHPUnit\Framework\TestCase
$row = new Row([], null); $row = new Row([], null);
$cellMock = $this->getCellMock(); $cellMock = $this->getCellMock();
$row->setCellAtIndex($cellMock, 3); $row->setCellAtIndex($cellMock, 3);
$this->assertEquals($cellMock, $row->getCellAtIndex(3)); $this->assertEquals($cellMock, $row->getCellAtIndex(3));
$this->assertNull($row->getCellAtIndex(10)); $this->assertNull($row->getCellAtIndex(10));
} }
/**
* @return void
*/
public function testSetCellAtIndex()
{
$row = new Row([], null);
$cellMock = $this->getCellMock();
$row->setCellAtIndex($cellMock, 1);
$this->assertEquals(2, $row->getNumCells());
$this->assertNull($row->getCellAtIndex(0));
}
/** /**
* @return void * @return void
*/ */
+11
View File
@@ -277,6 +277,17 @@ class ReaderTest extends TestCase
$this->assertEquals([$expectedRow], $allRows); $this->assertEquals([$expectedRow], $allRows);
} }
/**
* @return void
*/
public function testReadShouldSupportWhitespaceAsXML()
{
$allRows = $this->getAllRowsForFile('sheet_with_whitespaces_as_xml.ods');
$expectedRow = ["Lorem ipsum\tdolor sit amet"];
$this->assertEquals([$expectedRow], $allRows);
}
/** /**
* @NOTE: The LIBXML_NOENT is used to ACTUALLY substitute entities (and should therefore not be used) * @NOTE: The LIBXML_NOENT is used to ACTUALLY substitute entities (and should therefore not be used)
* *
@@ -19,6 +19,7 @@ class StyleManagerTest extends TestCase
{ {
$entityFactory = $this->createMock(InternalEntityFactory::class); $entityFactory = $this->createMock(InternalEntityFactory::class);
$workbookRelationshipsManager = $this->createMock(WorkbookRelationshipsManager::class); $workbookRelationshipsManager = $this->createMock(WorkbookRelationshipsManager::class);
$workbookRelationshipsManager->method('hasStylesXMLFile')->willReturn(true);
/** @var StyleManager $styleManager */ /** @var StyleManager $styleManager */
$styleManager = $this->getMockBuilder('\Box\Spout\Reader\XLSX\Manager\StyleManager') $styleManager = $this->getMockBuilder('\Box\Spout\Reader\XLSX\Manager\StyleManager')
+13
View File
@@ -473,6 +473,19 @@ class ReaderTest extends TestCase
$this->assertEquals($expectedRows, $allRows); $this->assertEquals($expectedRows, $allRows);
} }
/**
* @return void
*/
public function testReadShouldSupportMissingStylesXMLFile()
{
$allRows = $this->getAllRowsForFile('file_with_no_styles_in_workbook_xml.xlsx');
$expectedRows = [
['s1--A1', 's1--B1'],
];
$this->assertEquals($expectedRows, $allRows);
}
/** /**
* @return void * @return void
*/ */
@@ -3,7 +3,9 @@
namespace Box\Spout\Writer\Common\Creator\Style; namespace Box\Spout\Writer\Common\Creator\Style;
use Box\Spout\Common\Entity\Style\Border; use Box\Spout\Common\Entity\Style\Border;
use Box\Spout\Common\Entity\Style\CellAlignment;
use Box\Spout\Common\Entity\Style\Color; use Box\Spout\Common\Entity\Style\Color;
use Box\Spout\Common\Exception\InvalidArgumentException;
use Box\Spout\Writer\Common\Manager\Style\StyleMerger; use Box\Spout\Writer\Common\Manager\Style\StyleMerger;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@@ -41,4 +43,22 @@ class StyleBuilderTest extends TestCase
$this->assertInstanceOf(Border::class, $baseStyle->getBorder(), 'Base style has a border'); $this->assertInstanceOf(Border::class, $baseStyle->getBorder(), 'Base style has a border');
$this->assertInstanceOf(Border::class, $mergedStyle->getBorder(), 'Merged style has a border'); $this->assertInstanceOf(Border::class, $mergedStyle->getBorder(), 'Merged style has a border');
} }
/**
* @return void
*/
public function testStyleBuilderShouldApplyCellAlignment()
{
$style = (new StyleBuilder())->setCellAlignment(CellAlignment::CENTER)->build();
$this->assertTrue($style->shouldApplyCellAlignment());
}
/**
* @return void
*/
public function testStyleBuilderShouldThrowOnInvalidCellAlignment()
{
$this->expectException(InvalidArgumentException::class);
(new StyleBuilder())->setCellAlignment('invalid_cell_alignment')->build();
}
} }
@@ -12,7 +12,7 @@ class CellHelperTest extends TestCase
/** /**
* @return array * @return array
*/ */
public function dataProviderForTestGetCellIndexFromColumnIndex() public function dataProviderForTestGetColumnLettersFromColumnIndex()
{ {
return [ return [
[0, 'A'], [0, 'A'],
@@ -24,14 +24,15 @@ class CellHelperTest extends TestCase
} }
/** /**
* @dataProvider dataProviderForTestGetCellIndexFromColumnIndex * @dataProvider dataProviderForTestGetColumnLettersFromColumnIndex
* *
* @param int $columnIndex * @param int $columnIndex
* @param string $expectedCellIndex * @param string $expectedColumnLetters
*
* @return void * @return void
*/ */
public function testGetCellIndexFromColumnIndex($columnIndex, $expectedCellIndex) public function testGetColumnLettersFromColumnIndex($columnIndex, $expectedColumnLetters)
{ {
$this->assertEquals($expectedCellIndex, CellHelper::getCellIndexFromColumnIndex($columnIndex)); $this->assertEquals($expectedColumnLetters, CellHelper::getColumnLettersFromColumnIndex($columnIndex));
} }
} }
@@ -45,6 +45,7 @@ class StyleMergerTest extends TestCase
->setFontBold() ->setFontBold()
->setFontColor(Color::YELLOW) ->setFontColor(Color::YELLOW)
->setBackgroundColor(Color::BLUE) ->setBackgroundColor(Color::BLUE)
->setFormat('0.00')
->build(); ->build();
$currentStyle = (new StyleBuilder())->setFontName('Font')->setFontUnderline()->build(); $currentStyle = (new StyleBuilder())->setFontName('Font')->setFontUnderline()->build();
$mergedStyle = $this->styleMerger->merge($currentStyle, $baseStyle); $mergedStyle = $this->styleMerger->merge($currentStyle, $baseStyle);
@@ -60,6 +61,7 @@ class StyleMergerTest extends TestCase
$this->assertTrue($mergedStyle->isFontUnderline()); $this->assertTrue($mergedStyle->isFontUnderline());
$this->assertEquals(Color::YELLOW, $mergedStyle->getFontColor()); $this->assertEquals(Color::YELLOW, $mergedStyle->getFontColor());
$this->assertEquals(Color::BLUE, $mergedStyle->getBackgroundColor()); $this->assertEquals(Color::BLUE, $mergedStyle->getBackgroundColor());
$this->assertEquals('0.00', $mergedStyle->getFormat());
} }
/** /**
+19
View File
@@ -2,6 +2,7 @@
namespace Box\Spout\Writer\ODS; namespace Box\Spout\Writer\ODS;
use Box\Spout\Common\Entity\Cell;
use Box\Spout\Common\Entity\Row; use Box\Spout\Common\Entity\Row;
use Box\Spout\Common\Exception\InvalidArgumentException; use Box\Spout\Common\Exception\InvalidArgumentException;
use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Exception\IOException;
@@ -321,6 +322,24 @@ class WriterTest extends TestCase
} }
} }
/**
* @return void
*/
public function testAddRowShouldSupportCellInError()
{
$fileName = 'test_add_row_should_support_cell_in_error.ods';
$cell = WriterEntityFactory::createCell('#DIV/0');
$cell->setType(Cell::TYPE_ERROR);
$row = WriterEntityFactory::createRow([$cell]);
$this->writeToODSFile([$row], $fileName);
$this->assertValueWasWritten($fileName, 'calcext:value-type="error"');
$this->assertValueWasWritten($fileName, '<text:p>#DIV/0</text:p>');
}
/** /**
* @return void * @return void
*/ */
@@ -4,6 +4,7 @@ namespace Box\Spout\Writer\ODS;
use Box\Spout\Common\Entity\Row; use Box\Spout\Common\Entity\Row;
use Box\Spout\Common\Entity\Style\Border; use Box\Spout\Common\Entity\Style\Border;
use Box\Spout\Common\Entity\Style\CellAlignment;
use Box\Spout\Common\Entity\Style\Color; use Box\Spout\Common\Entity\Style\Color;
use Box\Spout\Common\Entity\Style\Style; use Box\Spout\Common\Entity\Style\Style;
use Box\Spout\Reader\Wrapper\XMLReader; use Box\Spout\Reader\Wrapper\XMLReader;
@@ -204,6 +205,25 @@ class WriterWithStyleTest extends TestCase
$this->assertFirstChildHasAttributeEquals('wrap', $customStyleElement, 'table-cell-properties', 'fo:wrap-option'); $this->assertFirstChildHasAttributeEquals('wrap', $customStyleElement, 'table-cell-properties', 'fo:wrap-option');
} }
/**
* @return void
*/
public function testAddRowShouldApplyCellAlignment()
{
$fileName = 'test_add_row_should_apply_cell_alignment.xlsx';
$rightAlignedStyle = (new StyleBuilder())->setCellAlignment(CellAlignment::RIGHT)->build();
$dataRows = $this->createStyledRowsFromValues([['ods--11']], $rightAlignedStyle);
$this->writeToODSFile($dataRows, $fileName);
$styleElements = $this->getCellStyleElementsFromContentXmlFile($fileName);
$this->assertCount(2, $styleElements, 'There should be 2 styles (default and custom)');
$customStyleElement = $styleElements[1];
$this->assertFirstChildHasAttributeEquals('end', $customStyleElement, 'paragraph-properties', 'fo:text-align');
}
/** /**
* @return void * @return void
*/ */
@@ -58,20 +58,50 @@ class StyleRegistryTest extends TestCase
$borderRight = (new BorderBuilder())->setBorderRight()->build(); $borderRight = (new BorderBuilder())->setBorderRight()->build();
$styleBorderLeft = (new StyleBuilder())->setBorder($borderLeft)->build(); $styleBorderLeft = (new StyleBuilder())->setBorder($borderLeft)->build();
$styleBoderRight = (new StyleBuilder())->setBorder($borderRight)->build(); $styleBorderRight = (new StyleBuilder())->setBorder($borderRight)->build();
$styleBoderRightBold = (new StyleBuilder())->setBorder($borderRight)->setFontBold()->build(); $styleBorderRightBold = (new StyleBuilder())->setBorder($borderRight)->setFontBold()->build();
$styleNoBorder = (new StyleBuilder())->setFontItalic()->build(); $styleNoBorder = (new StyleBuilder())->setFontItalic()->build();
$styleRegistry->registerStyle($styleBorderLeft); $styleRegistry->registerStyle($styleBorderLeft);
$styleRegistry->registerStyle($styleBoderRight); $styleRegistry->registerStyle($styleBorderRight);
$styleRegistry->registerStyle($styleBoderRightBold); $styleRegistry->registerStyle($styleBorderRightBold);
$styleRegistry->registerStyle($styleNoBorder); $styleRegistry->registerStyle($styleNoBorder);
$this->assertCount(2, $styleRegistry->getRegisteredBorders(), 'There should be 2 registered borders'); $this->assertCount(2, $styleRegistry->getRegisteredBorders(), 'There should be 2 registered borders');
$this->assertEquals(1, $styleRegistry->getBorderIdForStyleId($styleBorderLeft->getId()), 'First style with border set should have index 1 (0 is for the default style)'); $this->assertEquals(1, $styleRegistry->getBorderIdForStyleId($styleBorderLeft->getId()), 'First style with border set should have index 1 (0 is for the default style)');
$this->assertEquals(2, $styleRegistry->getBorderIdForStyleId($styleBoderRight->getId()), 'Second style with border set - different from first style - should have index 2'); $this->assertEquals(2, $styleRegistry->getBorderIdForStyleId($styleBorderRight->getId()), 'Second style with border set - different from first style - should have index 2');
$this->assertEquals(2, $styleRegistry->getBorderIdForStyleId($styleBoderRightBold->getId()), 'Style with border already set should have the same index'); $this->assertEquals(2, $styleRegistry->getBorderIdForStyleId($styleBorderRightBold->getId()), 'Style with border already set should have the same index');
$this->assertEquals(0, $styleRegistry->getBorderIdForStyleId($styleNoBorder->getId()), 'Style with no border should have index 0'); $this->assertEquals(0, $styleRegistry->getBorderIdForStyleId($styleNoBorder->getId()), 'Style with no border should have index 0');
} }
/**
* @return void
*/
public function testRegisterStyleAlsoRegistersFormats()
{
$styleRegistry = $this->getStyleRegistry();
$styleBuiltinFormat = (new StyleBuilder())
->setFontBold()
->setFormat('0.00')//Builtin format
->build();
$styleUserFormat = (new StyleBuilder())
->setFontBold()
->setFormat('0.000')
->build();
$styleNoFormat = (new StyleBuilder())->setFontItalic()->build();
$styleRegistry->registerStyle($styleBuiltinFormat);
$styleRegistry->registerStyle($styleUserFormat);
$styleRegistry->registerStyle($styleNoFormat);
$this->assertCount(2, $styleRegistry->getRegisteredFormats(), 'There should be 2 registered formats');
$this->assertEquals(2, $styleRegistry->getFormatIdForStyleId($styleBuiltinFormat->getId()), 'First style with builtin format set should have index 2 (0 is for the default style)');
$this->assertEquals(164, $styleRegistry->getFormatIdForStyleId($styleUserFormat->getId()), 'Second style with user format set should have index 164 (0 is for the default style)');
$this->assertEquals(0, $styleRegistry->getFormatIdForStyleId($styleNoFormat->getId()), 'Style with no format should have index 0');
}
} }
+18
View File
@@ -2,6 +2,7 @@
namespace Box\Spout\Writer\XLSX; namespace Box\Spout\Writer\XLSX;
use Box\Spout\Common\Entity\Cell;
use Box\Spout\Common\Entity\Row; use Box\Spout\Common\Entity\Row;
use Box\Spout\Common\Exception\InvalidArgumentException; use Box\Spout\Common\Exception\InvalidArgumentException;
use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Exception\IOException;
@@ -375,6 +376,23 @@ class WriterTest extends TestCase
$this->assertInlineDataWasWrittenToSheet($fileName, 1, 10.2); $this->assertInlineDataWasWrittenToSheet($fileName, 1, 10.2);
} }
/**
* @return void
*/
public function testAddRowShouldSupportCellInError()
{
$fileName = 'test_add_row_should_support_cell_in_error.xlsx';
$cell = WriterEntityFactory::createCell('#DIV/0');
$cell->setType(Cell::TYPE_ERROR);
$row = WriterEntityFactory::createRow([$cell]);
$this->writeToXLSXFile([$row], $fileName);
$this->assertInlineDataWasWrittenToSheet($fileName, 1, 't="e"><v>#DIV/0</v>');
}
/** /**
* @return void * @return void
*/ */
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff. Show more