#463, move gh-pages to docs folder

This commit is contained in:
madflow 2017-11-21 10:55:27 +01:00
parent 5b1bcc1303
commit c1a4bbec01
44 changed files with 1981 additions and 0 deletions

9
docs/.gitignore vendored Executable file
View File

@ -0,0 +1,9 @@
/.idea
*.iml
/tests/resources/generated
/tests/coverage
/vendor
/.sass-cache
/_site

7
docs/README.md Executable file
View File

@ -0,0 +1,7 @@
Spout Github pages
====
1. Install `bower`: `npm install -g bower` or `yarn global add bower` or
2. Install dependencies: `bower install`
3. Install `jekyll`: `sudo gem install jekyll`
4. Run the site locally: `jekyll serve`

37
docs/_config.yml Executable file
View File

@ -0,0 +1,37 @@
# Welcome to Jekyll!
#
# This config file is meant for settings that affect your whole blog, values
# which you are expected to set up once and rarely need to edit after that.
# For technical reasons, this file is *NOT* reloaded automatically when you use
# 'jekyll serve'. If you change this file, please restart the server process.
# Site settings
title: Spout
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."
baseurl: "" # the subpath of your site, e.g. /blog
url: "http://opensource.box.com" # the base hostname & protocol for your site
# Build settings
markdown: kramdown
highlighter: rouge
kramdown:
input: GFM
syntax_highlighter: rouge
exclude: ["bower.json", "README.md"]
collections:
pages:
output: true
sections:
output: true
# 3rd parties
#google_analytics:
algolia:
enabled: false
# apiKey:
# indexName:
# Misc
spout_html: <span class="spout">Spout</span>

View File

@ -0,0 +1,9 @@
{% if site.algolia.enabled %}
<script type="text/javascript" src="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.js"></script>
<script type="text/javascript"> docsearch({
apiKey: '{{ site.algolia.apiKey }}',
indexName: '{{ site.algolia.indexName }}',
inputSelector: '#algolia-doc-search'
});
</script>
{% endif %}

12
docs/_includes/analytics.html Executable file
View File

@ -0,0 +1,12 @@
{% if site.google_analytics %}
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', '{{ site.google_analytics }}', 'auto');
ga('send', 'pageview');
</script>
{% endif %}

9
docs/_includes/banner.html Executable file
View File

@ -0,0 +1,9 @@
<div class="site-banner">
<div class="wrapper vertical-align-middle">
<p class="tag-line">
Read and write spreadsheets
<br />
<strong>quickly</strong> and <strong>at scale</strong>
</p>
</div>
</div>

9
docs/_includes/base.html Executable file
View File

@ -0,0 +1,9 @@
<!-- http://ricostacruz.com/til/relative-paths-in-jekyll.html -->
<!-- _includes/base.html -->
{% assign base = '' %}
{% assign depth = page.url | split: '/' | size | minus: 1 %}
{% if depth == 1 %}{% assign base = '.' %}
{% elsif depth == 2 %}{% assign base = '..' %}
{% elsif depth == 3 %}{% assign base = '../..' %}
{% elsif depth == 4 %}{% assign base = '../../..' %}{% endif %}

25
docs/_includes/footer.html Executable file
View File

@ -0,0 +1,25 @@
<footer class="site-footer">
<div class="wrapper">
<div class="footer-col-wrapper">
<div class="footer-col footer-col-1">
<ul class="contact-list">
<li>Need to contact us?<br><a href="https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img src="https://badges.gitter.im/box/spout.svg" alt="Join the chat at https://gitter.im/box/spout" /></a></li>
</ul>
</div>
<div class="footer-col footer-col-2">
<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>
</ul>
</div>
<div class="footer-col footer-col-3">
<p>{{ site.title }} - {{ site.description }}</p>
</div>
</div>
</div>
</footer>

17
docs/_includes/head.html Executable file
View File

@ -0,0 +1,17 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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 %}">
<link rel="stylesheet" href="{{ "/css/main.css" | prepend: site.github.url }}">
{% if site.algolia.enabled %}
<link rel="stylesheet" href="//cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css" />
{% endif %}
{% if jekyll.environment == 'production' %}
{% include analytics.html %}
{% endif %}
</head>

30
docs/_includes/header.html Executable file
View File

@ -0,0 +1,30 @@
<header class="site-header">
<div class="wrapper">
<a class="site-title" href="{{ site.github.url }}/">{{ site.title }}</a>
<nav class="site-nav">
<a href="#" class="menu-icon">
<svg viewBox="0 0 18 15">
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
</svg>
</a>
<div class="trigger">
<a class="page-link" href="{{ site.github.url }}/getting-started/">Getting Started</a>
<a class="page-link" href="{{ site.github.url }}/docs/">Documentation</a>
<a class="page-link" href="{{ site.github.url }}/guides/">Guides</a>
<a class="page-link" href="{{ site.github.url }}/faq/">FAQ</a>
<a class="page-link" href="https://github.com/box/spout">GitHub</a>
{% if site.algolia.enabled %}
<span class="site-search">
<input type="text" id="algolia-doc-search">
</span>
{% endif %}
</div>
</nav>
</div>
</header>

View File

@ -0,0 +1 @@
<a href="https://github.com/{{ include.username }}"><span class="icon icon--github">{% include icon-github.svg %}</span><span class="username">{{ include.username }}</span></a>

1
docs/_includes/icon-github.svg Executable file
View File

@ -0,0 +1 @@
<svg viewBox="0 0 16 16"><path fill="#828282" d="M7.999,0.431c-4.285,0-7.76,3.474-7.76,7.761 c0,3.428,2.223,6.337,5.307,7.363c0.388,0.071,0.53-0.168,0.53-0.374c0-0.184-0.007-0.672-0.01-1.32 c-2.159,0.469-2.614-1.04-2.614-1.04c-0.353-0.896-0.862-1.135-0.862-1.135c-0.705-0.481,0.053-0.472,0.053-0.472 c0.779,0.055,1.189,0.8,1.189,0.8c0.692,1.186,1.816,0.843,2.258,0.645c0.071-0.502,0.271-0.843,0.493-1.037 C4.86,11.425,3.049,10.76,3.049,7.786c0-0.847,0.302-1.54,0.799-2.082C3.768,5.507,3.501,4.718,3.924,3.65 c0,0,0.652-0.209,2.134,0.796C6.677,4.273,7.34,4.187,8,4.184c0.659,0.003,1.323,0.089,1.943,0.261 c1.482-1.004,2.132-0.796,2.132-0.796c0.423,1.068,0.157,1.857,0.077,2.054c0.497,0.542,0.798,1.235,0.798,2.082 c0,2.981-1.814,3.637-3.543,3.829c0.279,0.24,0.527,0.713,0.527,1.437c0,1.037-0.01,1.874-0.01,2.129 c0,0.208,0.14,0.449,0.534,0.373c3.081-1.028,5.302-3.935,5.302-7.362C15.76,3.906,12.285,0.431,7.999,0.431z"/></svg>

After

Width:  |  Height:  |  Size: 926 B

View File

@ -0,0 +1,11 @@
{% assign sectionClass = "section-even centered" %}
{% assign sectionTitle = "Fast and Scalable" %}
{% assign sectionIcons = "icon-lightning-bolt.png" %}
{% capture sectionContent %}
Reading a small CSV file? No problem!<br>
Reading a huge XLSX file? No extra code needed!<br>
Writing an ODS file with millions of rows? {{ site.spout_html }} can do it in no time!
{% endcapture %}
{% include section.html %}

View File

@ -0,0 +1,14 @@
{% assign sectionClass = "section-odd centered" %}
{% assign sectionTitle = "Supported Spreadsheet Types" %}
{% assign sectionIcons = "icon-xlsx.png~~icon-ods.png~~icon-csv.png" %}
{% assign emS = '<span class="light-em">' %}
{% assign emE = '</span>' %}
{% capture sectionContent %}
{{ site.spout_html }} supports 3 types of spreadsheets: {{emS}}XLSX{{emE}}, {{emS}}ODS{{emE}} and {{emS}}CSV{{emE}}.<br>
{{ site.spout_html }} provides a simple and unified API to read or create these different types of spreadsheets.
Switching from one type to another is ridiculously easy!
{% endcapture %}
{% include section.html %}

View File

@ -0,0 +1,20 @@
{% assign sectionClass = "section-odd last-section" %}
{% capture sectionTitle %}Why use {{ site.spout_html }}?{% endcapture %}
{% assign sectionIcons = "" %}
{% assign emS = '<span class="light-em">' %}
{% assign emE = '</span>' %}
{% capture sectionContent %}
<ul class="feature-list">
<li class="feature-check">{{ site.spout_html }} is capable of processing files of {{emS}}any size{{emE}}.</li>
<li class="feature-check">{{ site.spout_html }} needs {{emS}}only 3MB{{emE}} of memory to process any file.</li>
<li class="feature-check">{{ site.spout_html }}'s streaming mechanism makes it {{emS}}incredibly fast{{emE}}.</li>
<li class="feature-check">{{ site.spout_html }}'s API is {{emS}}developer-friendly{{emE}}.</li>
</ul><br>
<div class="btn-wrapper">
<a href="{{ site.github.url }}/getting-started/" class="page-link"><button class="btn">Get started</button></a>
</div>
{% endcapture %}
{% include section.html %}

17
docs/_includes/section.html Executable file
View File

@ -0,0 +1,17 @@
<div class="section {{ sectionClass }} vertical-align-middle">
<div class="wrapper">
<div class="description mbl">
<h2 class="section-title">{{ sectionTitle }}</h2>
{{ sectionContent }}
</div>
{% if sectionIcons %}
{% assign sectionIconsAsArray = sectionIcons | split:'~~' %}
<div class="icons">
{% for icon in sectionIconsAsArray %}
<img src="{{ site.github.url }}/images/{{ icon }}"/>
{% endfor %}
</div>
{% endif %}
</div>
</div>

29
docs/_layouts/default.html Executable file
View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
{% include head.html %}
<body>
{% include header.html %}
{% if page.banner %}
{% include banner.html %}
{% endif %}
{{ content }}
<!--
<div class="page-content">
<div class="wrapper">
</div>
</div>
-->
{% include footer.html %}
{% include algolia.html %}
</body>
</html>

40
docs/_layouts/doc.html Executable file
View File

@ -0,0 +1,40 @@
---
layout: default
---
<script src="{{site.github.url}}/bower_components/jquery/dist/jquery.min.js"></script>
<script src="{{site.github.url}}/bower_components/anchor-js/anchor.js"></script>
<script src="{{site.github.url}}/bower_components/bootstrap/js/affix.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
anchors.add();
$('.post-title, .doc-content h1, .doc-content h2, .doc-content h3').each(function(index, element) {
var el = $(element);
var title = el.text();
var id = el.attr('id');
var tag = el.prop('tagName');
var titleEl = $('<div>').append(
$('<a>')
.attr('href', '#' + id)
.addClass(tag.toLowerCase())
.text(title));
$('.table-of-content').append(titleEl);
});
});
</script>
<div class="table-of-content" data-spy="affix" data-offset-top="40" data-offset-bottom="300">
</div>
<article class="post">
<div class="wrapper">
<header class="post-header">
<h1 class="post-title">{{ page.title }}</h1>
</header>
<div class="post-content">
<div class="doc-content">
{{ content }}
</div>
</div>
</div>

14
docs/_layouts/page.html Executable file
View File

@ -0,0 +1,14 @@
---
layout: default
---
<article class="post">
<div class="wrapper">
<header class="post-header">
<h1 class="post-title">{{ page.title }}</h1>
</header>
<div class="post-content">
{{ content }}
</div>
</div>
</article>

15
docs/_layouts/post.html Executable file
View File

@ -0,0 +1,15 @@
---
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>

245
docs/_pages/documentation.md Executable file
View File

@ -0,0 +1,245 @@
---
layout: doc
title: Documentation
permalink: /docs/
---
## Configuring for CSV
It is possible to configure both the CSV reader and writer to adapt them to your requirements:
```php?start_inline=1
use Box\Spout\Reader\ReaderFactory;
use Box\Spout\Common\Type;
$reader = ReaderFactory::create(Type::CSV);
$reader->setFieldDelimiter('|');
$reader->setFieldEnclosure('@');
$reader->setEndOfLineCharacter("\r");
```
Additionally, if you need to read non UTF-8 files, you can specify the encoding of your file this way:
```php?start_inline=1
$reader->setEncoding('UTF-16LE');
```
By default, the writer generates CSV files encoded in UTF-8, with a BOM.
It is however possible to not include the BOM:
```php?start_inline=1
use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;
$writer = WriterFactory::create(Type::CSV);
$writer->setShouldAddBOM(false);
```
## Configuring for XLSX and ODS
### New sheet creation
It is possible to change the behavior of the writers when the maximum number of rows (*1,048,576*) has been written in the current sheet. By default, a new sheet is automatically created so that writing can keep going but that may not always be preferable.
```php?start_inline=1
use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;
$writer = WriterFactory::create(Type::ODS);
$writer->setShouldCreateNewSheetsAutomatically(true); // default value
$writer->setShouldCreateNewSheetsAutomatically(false); // will stop writing new data when limit is reached
```
### Using custom temporary folder
Processing XLSX and ODS files requires temporary files to be created. By default, {{ site.spout_html }} will use the system default temporary folder (as returned by `sys_get_temp_dir()`). It is possible to override this by explicitly setting it on the reader or writer:
```php?start_inline=1
use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;
$writer = WriterFactory::create(Type::XLSX);
$writer->setTempFolder($customTempFolderPath);
```
### Strings storage (XLSX writer)
XLSX files support different ways to store the string values:
* Shared strings are meant to optimize file size by separating strings from the sheet representation and ignoring strings duplicates (if a string is used three times, only one string will be stored)
* Inline strings are less optimized (as duplicate strings are all stored) but is faster to process
In order to keep the memory usage really low, {{ site.spout_html }} does not de-duplicate strings when using shared strings. It is nevertheless possible to use this mode.
```php?start_inline=1
use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;
$writer = WriterFactory::create(Type::XLSX);
$writer->setShouldUseInlineStrings(true); // default (and recommended) value
$writer->setShouldUseInlineStrings(false); // will use shared strings
```
> #### Note on Apple Numbers and iOS support
>
> Apple's products (Numbers and the iOS previewer) don't support inline strings and display empty cells instead. Therefore, if these platforms need to be supported, make sure to use shared strings!
### Date/Time formatting
When reading a spreadsheet containing dates or times, {{ site.spout_html }} returns the values by default as DateTime objects.
It is possible to change this behavior and have a formatted date returned instead (e.g. "2016-11-29 1:22 AM"). The format of the date corresponds to what is specified in the spreadsheet.
```php?start_inline=1
use Box\Spout\Reader\ReaderFactory;
use Box\Spout\Common\Type;
$reader = ReaderFactory::create(Type::XLSX);
$reader->setShouldFormatDates(false); // default value
$reader->setShouldFormatDates(true); // will return formatted dates
```
## Styling rows
It is possible to apply some formatting options to a row. {{ site.spout_html }} supports fonts, background, borders as well as alignment styles.
```php?start_inline=1
use Box\Spout\Common\Type;
use Box\Spout\Writer\WriterFactory;
use Box\Spout\Writer\Style\StyleBuilder;
use Box\Spout\Writer\Style\Color;
$style = (new StyleBuilder())
->setFontBold()
->setFontSize(15)
->setFontColor(Color::BLUE)
->setShouldWrapText()
->setBackgroundColor(Color::YELLOW)
->build();
$writer = WriterFactory::create(Type::XLSX);
$writer->openToFile($filePath);
$writer->addRowWithStyle($singleRow, $style); // style will only be applied to this row
$writer->addRow($otherSingleRow); // no style will be applied
$writer->addRowsWithStyle($multipleRows, $style); // style will be applied to all given rows
$writer->close();
```
Adding borders to a row requires a ```Border``` object.
```php?start_inline=1
use Box\Spout\Common\Type;
use Box\Spout\Writer\Style\Border;
use Box\Spout\Writer\Style\BorderBuilder;
use Box\Spout\Writer\Style\Color;
use Box\Spout\Writer\Style\StyleBuilder;
use Box\Spout\Writer\WriterFactory;
$border = (new BorderBuilder())
->setBorderBottom(Color::GREEN, Border::WIDTH_THIN, Border::STYLE_DASHED)
->build();
$style = (new StyleBuilder())
->setBorder($border)
->build();
$writer = WriterFactory::create(Type::XLSX);
$writer->openToFile($filePath);
$writer->addRowWithStyle(['Border Bottom Green Thin Dashed'], $style);
$writer->close();
```
{{ site.spout_html }} will use a default style for all created rows. This style can be overridden this way:
```php?start_inline=1
$defaultStyle = (new StyleBuilder())
->setFontName('Arial')
->setFontSize(11)
->build();
$writer = WriterFactory::create(Type::XLSX);
$writer->setDefaultRowStyle($defaultStyle)
->openToFile($filePath);
```
Unfortunately, {{ site.spout_html }} does not support all the possible formatting options yet. But you can find the most important ones:
| Category | Property | API
|:----------|:--------------|:--------------------------------------
| Font | Bold | `StyleBuilder::setFontBold()`
| | Italic | `StyleBuilder::setFontItalic()`
| | Underline | `StyleBuilder::setFontUnderline()`
| | Strikethrough | `StyleBuilder::setFontStrikethrough()`
| | Font name | `StyleBuilder::setFontName('Arial')`
| | Font size | `StyleBuilder::setFontSize(14)`
| | Font color | `StyleBuilder::setFontColor(Color::BLUE)`<br>`StyleBuilder::setFontColor(Color::rgb(0, 128, 255))`
| Alignment | Wrap text | `StyleBuilder::setShouldWrapText(true|false)`
## Playing with sheets
When creating a XLSX or ODS file, it is possible to control which sheet the data will be written into. At any time, you can retrieve or set the current sheet:
```php?start_inline=1
$firstSheet = $writer->getCurrentSheet();
$writer->addRow($rowForSheet1); // writes the row to the first sheet
$newSheet = $writer->addNewSheetAndMakeItCurrent();
$writer->addRow($rowForSheet2); // writes the row to the new sheet
$writer->setCurrentSheet($firstSheet);
$writer->addRow($anotherRowForSheet1); // append the row to the first sheet
```
It is also possible to retrieve all the sheets currently created:
```php?start_inline=1
$sheets = $writer->getSheets();
```
If you rely on the sheet's name in your application, you can access it and customize it this way:
```php?start_inline=1
// Accessing the sheet name when reading
foreach ($reader->getSheetIterator() as $sheet) {
$sheetName = $sheet->getName();
}
// Accessing the sheet name when writing
$sheet = $writer->getCurrentSheet();
$sheetName = $sheet->getName();
// Customizing the sheet name when writing
$sheet = $writer->getCurrentSheet();
$sheet->setName('My custom name');
```
> Please note that Excel has some restrictions on the sheet's name:
> * it must not be blank
> * it must not exceed 31 characters
> * it must not contain these characters: \ / ? * : [ or ]
> * it must not start or end with a single quote
> * it must be unique
>
> Handling these restrictions is the developer's responsibility. {{ site.spout_html }} does not try to automatically change the sheet's name, as one may rely on this name to be exactly what was passed in.
Finally, it is possible to know which sheet was active when the spreadsheet was last saved. This can be useful if you are only interested in processing the one sheet that was last focused.
```php?start_inline=1
foreach ($reader->getSheetIterator() as $sheet) {
// only process data for the active sheet
if ($sheet->isActive()) {
// do something...
}
}
```
## Fluent interface
Because fluent interfaces are great, you can use them with {{ site.spout_html }}:
```php?start_inline=1
use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;
$writer = WriterFactory::create(Type::XLSX);
$writer->setTempFolder($customTempFolderPath)
->setShouldUseInlineStrings(true)
->openToFile($filePath)
->addRow($headerRow)
->addRows($dataRows)
->close();
```

30
docs/_pages/faq.md Normal file
View File

@ -0,0 +1,30 @@
---
layout: page
title: Frequently Asked Questions
permalink: /faq/
---
### How can Spout handle such large data sets and still use less than 3MB of memory?
When writing data, Spout is streaming the data to files, one or few lines at a time. That means that it only keeps in memory the few rows that it needs to write. Once written, the memory is freed.
Same goes with reading. Only one row at a time is stored in memory. A special technique is used to handle shared strings in XLSX, storing them - if needed - into several small temporary files that allows fast access.
### How long does it take to generate a file with X rows?
Here are a few numbers regarding the performance of Spout:
| Type | Action | 2,000 rows (6,000 cells) | 200,000 rows (600,000 cells) | 2,000,000 rows (6,000,000 cells) |
|------|-------------------------------|--------------------------|------------------------------|----------------------------------|
| CSV | Read | < 1 second | 4 seconds | 2-3 minutes |
| | Write | < 1 second | 2 seconds | 2-3 minutes |
| XLSX | Read<br>*inline&nbsp;strings* | < 1 second | 35-40 seconds | 18-20 minutes |
| | Read<br>*shared&nbsp;strings* | 1 second | 1-2 minutes | 35-40 minutes |
| | Write | 1 second | 20-25 seconds | 8-10 minutes |
| ODS | Read | 1 second | 1-2 minutes | 5-6 minutes |
| | Write | < 1 second | 35-40 seconds | 5-6 minutes |
### Does Spout support charts or formulas?
No. This is a compromise to keep memory usage low. Charts and formulas requires data to be kept in memory in order to be used.
So the larger the file would be, the more memory would be consumed, preventing your code to scale well.

96
docs/_pages/getting-started.md Executable file
View File

@ -0,0 +1,96 @@
---
layout: doc
title: Getting Started
permalink: /getting-started/
---
This guide will help you install {{ site.spout_html }} and teach you how to use it.
## Requirements
* PHP version 5.4.0 or higher
* PHP extension `php_zip` enabled
* PHP extension `php_xmlreader` enabled
## Installation
### Composer (recommended)
{{ site.spout_html }} can be installed directly from [Composer](https://getcomposer.org/).
Run the following command:
```powershell
$ composer require box/spout
```
### Manual installation
If you can't use Composer, no worries! You can still install {{ site.spout_html }} manually.
> Before starting, make sure your system meets the [requirements](#requirements).
1. Download the source code from the [Releases page](https://github.com/box/spout/releases)
2. Extract the downloaded content into your project.
3. Add this code to the top controller (e.g. index.php) or wherever it may be more appropriate:
```php?start_inline=1
// don't forget to change the path!
require_once '[PATH/TO]/src/Spout/Autoloader/autoload.php';
```
## Basic usage
### Reader
Regardless of the file type, the interface to read a file is always the same:
```php?start_inline=1
use Box\Spout\Reader\ReaderFactory;
use Box\Spout\Common\Type;
$reader = ReaderFactory::create(Type::XLSX); // for XLSX files
//$reader = ReaderFactory::create(Type::CSV); // for CSV files
//$reader = ReaderFactory::create(Type::ODS); // for ODS files
$reader->open($filePath);
foreach ($reader->getSheetIterator() as $sheet) {
foreach ($sheet->getRowIterator() as $row) {
// do stuff with the row
}
}
$reader->close();
```
If there are multiple sheets in the file, the reader will read all of them sequentially.
### Writer
As with the reader, there is one common interface to write data to a file:
```php?start_inline=1
use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;
$writer = WriterFactory::create(Type::XLSX); // for XLSX files
//$writer = WriterFactory::create(Type::CSV); // for CSV files
//$writer = WriterFactory::create(Type::ODS); // for ODS files
$writer->openToFile($filePath); // write data to a file or to a PHP stream
//$writer->openToBrowser($fileName); // stream data directly to the browser
$writer->addRow($singleRow); // add a row at a time
$writer->addRows($multipleRows); // add multiple rows at a time
$writer->close();
```
For XLSX and ODS files, the number of rows per sheet is limited to *1,048,576*. By default, once this limit is reached, the writer will automatically create a new sheet and continue writing data into it.
## 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.

19
docs/_pages/guides.md Normal file
View File

@ -0,0 +1,19 @@
---
layout: page
title: Guides
permalink: /guides/
---
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.
{% assign pages=site.pages | sort: 'path' %}
<ul>
{% for page in pages %}
{% if page.title and page.category contains 'guide' %}
<li>
<a class="page-link" href="{{ page.url | prepend: site.github.url }}">{{ page.title }}</a>
</li>
{% endif %}
{% endfor %}
</ul>

BIN
docs/_pages/guides/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,61 @@
---
layout: page
title: "Add data to an existing spreadsheet"
category: guide
permalink: /guides/add-data-to-existing-spreadsheet/
---
A common use case when using spreadsheets is to add data to an existing spreadsheet. For instance, let's assume you built a spreadsheet containing the last orders on your favorite website and want to update it as you make a new order.
We'll start with a file called "orders.xlsx" and add a new row, containing the last order's info, at the end.
In order to avoid memory issues when dealing with large spreadsheets, {{ site.spout_html }} does not hold the whole representation of the spreadsheet in memory. So to alter an existing spreadsheet, we'll have to create a new one that is similar to the existing one and add the new data in the new one.
```php
<?php
use Box\Spout\Reader\ReaderFactory;
use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;
$existingFilePath = 'path/to/orders.xlsx';
$newFilePath = 'path/to/new-orders.xlsx';
// we need a reader to read the existing file...
$reader = ReaderFactory::create(Type::XLSX);
$reader->open($existingFilePath);
$reader->setShouldFormatDates(true); // this is to be able to copy dates
// ... and a writer to create the new file
$writer = WriterFactory::create(Type::XLSX);
$writer->openToFile($newFilePath);
// let's read the entire spreadsheet...
foreach ($reader->getSheetIterator() as $sheetIndex => $sheet) {
// Add sheets in the new file, as we read new sheets in the existing one
if ($sheetIndex !== 1) {
$writer->addNewSheetAndMakeItCurrent();
}
foreach ($sheet->getRowIterator() as $row) {
// ... and copy each row into the new spreadsheet
$writer->addRow($row);
}
}
// At this point, the new spreadsheet contains the same data as the existing one.
// So let's add the new data:
$writer->addRow(['2015-12-25', 'Christmas gift', 29, 'USD']);
$reader->close();
$writer->close();
```
Optionally, if you rely on the file name or want to keep only one file, simple remove the old file and rename the new one:
```php?start_inline=1
unlink($existingFilePath);
rename($newFilePath, $existingFilePath);
```
That's it! The created file now contains the updated data and is ready to be used.

View File

@ -0,0 +1,88 @@
---
layout: page
title: "Edit an existing spreadsheet"
category: guide
permalink: /guides/edit-existing-spreadsheet/
---
Editing an existing spreadsheet is a pretty common task that {{ site.spout_html }} is totally capable of doing.
With {{ site.spout_html }}, it is not possible to do things like `deleteRow(3)` or `insertRowAfter(5, $newRow)`. This is because {{ site.spout_html }} does not keep an in-memory representation of the entire spreadsheet, to avoid consuming all the memory available with large spreadsheets. This means, {{ site.spout_html }} does not know how to jump to the 3rd row directly and has especially no way of moving backwards (changing row 3 after having changed row 5). So let's see how this can be done, in a scalable way.
For this example, let's assume we have an existing ODS spreadsheet called "my-music.ods" that looks like this:
| Song title | Artist | Album | Year |
| ---------------- | --------------- | --------------- | ---- |
| Yesterday | The Beatles | The White Album | 1968 |
| Yellow Submarine | The Beatles | Unknown | 1968 |
| Space Oddity | David Bowie | David Bowie | 1969 |
| Thriller | Michael Jackson | Thriller | 1982 |
| No Woman No Cry | Bob Marley | Legend | 1984 |
| Buffalo Soldier | Bob Marley | Legend | 1984 |
> Note that the album for "Yellow Submarine" is "Unknown" and that the songs are ordered by year (most recent last).
We'd like to update the missing album for "Yellow Submarine", remove the Bob Marley's songs and add a new song: "Hotel California" from "The Eagles", released in 1976. Here is how this can be done:
```php
<?php
use Box\Spout\Reader\ReaderFactory;
use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;
$existingFilePath = '/path/to/my-music.ods';
$newFilePath = '/path/to/my-new-music.ods';
// we need a reader to read the existing file...
$reader = ReaderFactory::create(Type::ODS);
$reader->open($existingFilePath);
$reader->setShouldFormatDates(true); // this is to be able to copy dates
// ... and a writer to create the new file
$writer = WriterFactory::create(Type::ODS);
$writer->openToFile($newFilePath);
// let's read the entire spreadsheet
foreach ($reader->getSheetIterator() as $sheetIndex => $sheet) {
// Add sheets in the new file, as you read new sheets in the existing one
if ($sheetIndex !== 1) {
$writer->addNewSheetAndMakeItCurrent();
}
foreach ($sheet->getRowIterator() as $rowIndex => $row) {
$songTitle = $row[0];
$artist = $row[1];
// Change the album name for "Yellow Submarine"
if ($songTitle === 'Yellow Submarine') {
$row[2] = 'The White Album';
}
// skip Bob Marley's songs
if ($artist === 'Bob Marley') {
continue;
}
// write the edited row to the new file
$writer->addRow($row);
// insert new song at the right position, between the 3rd and 4th rows
if ($rowIndex === 3) {
$writer->addRow(['Hotel California', 'The Eagles', 'Hotel California', 1976]);
}
}
}
$reader->close();
$writer->close();
```
Optionally, if you rely on the file name or want to keep only one file, simple remove the old file and rename the new one:
```php?start_inline=1
unlink($existingFilePath);
rename($newFilePath, $existingFilePath);
```
That's it! The created file now contains the updated data and is ready to be used.

View File

@ -0,0 +1,46 @@
---
layout: page
title: "Read data from a specific sheet only"
category: guide
permalink: /guides/read-data-from-specific-sheet/
---
Even though a spreadsheet contains multiple sheets, you may be interested in reading only one of them and skip the other ones. Here is how you can do it with Spout:
* If you know the name of the sheet
```php?start_inline=1
$reader = ReaderFactory::create(Type:XLSX);
$reader->open($filePath);
foreach ($reader->getSheetIterator() as $sheet) {
// only read data from "summary" sheet
if ($sheet->getName() === 'summary') {
foreach ($sheet->getRowIterator() as $row) {
// do something with the row
}
break; // no need to read more sheets
}
}
$reader->close();
```
* If you know the position of the sheet
```php?start_inline=1
$reader = ReaderFactory::create(Type:XLSX);
$reader->open($filePath);
foreach ($reader->getSheetIterator() as $sheet) {
// only read data from 3rd sheet
if ($sheet->getIndex() === 2) { // index is 0-based
foreach ($sheet->getRowIterator() as $row) {
// do something with the row
}
break; // no need to read more sheets
}
}
$reader->close();
```

View File

@ -0,0 +1,110 @@
---
layout: page
title: "[Symfony] Stream content of a large spreadsheet"
category: guide
permalink: /guides/symfony-stream-content-large-spreadsheet/
---
> This tutorial is for the PHP framework [Symfony](http://symfony.com/).
The main benefit of streaming content is that this content can be rendered as soon as it is available. No matter how big the content is, the browser will be able to start rendering it as soon as the first byte is sent.
Reading a static spreadsheet to display its content to a user is a great use case for streaming. The spreadsheet can contain from a few rows to thousands of them and we don't want to wait until the whole file has been read (which can take a long time) before showing something to the user. Let's see how [Symfony's StreamedResponse](http://symfony.com/doc/current/components/http_foundation/introduction.html#streaming-a-response) let us easily stream the content of the spreadsheet.
A regular controller usually builds the content to be displayed and encapsulate it into a `Response` object. Everything happens synchronously. Such a controller may look like this:
```php?start_inline=1
class MyRegularController extends Controller
{
/**
* @Route("/spreadsheet/read")
*/
public function readAction()
{
$filePath = '/path/to/static/file.xlsx';
// The content to be displayed has to be built entirely
// before it can be sent to the browser.
$content = '';
$reader = ReaderFactory::create(Type::XLSX);
$reader->open($filePath);
foreach ($reader->getSheetIterator() as $sheet) {
$content .= '<table>';
foreach ($sheet->getRowIterator() as $row) {
$content .= '<tr>';
$content .= implode(array_map(function($cell) {
return '<td>' . $cell . '</td>';
}, $row));
$content .= '</tr>';
}
$content .= '</table><br>';
}
$reader->close();
// The response is sent to the browser
// once the entire file has been read.
$response = new Response($content);
$response->headers->set('Content-Type', 'text/html');
return $response;
}
}
```
Converting a regular controller to return a `StreamedResponse` is super easy! This is what it looks like after conversion:
```php?start_inline=1
class MyStreamController extends Controller
{
// See below how it is used.
const FLUSH_THRESHOLD = 100;
/**
* @Route("/spreadsheet/stream")
*/
public function readAction()
{
$filePath = '/path/to/static/file.xlsx';
// We'll now return a StreamedResponse.
$response = new StreamedResponse();
$response->headers->set('Content-Type', 'text/html');
// Instead of a string, the streamed response will execute
// a callback function to retrieve data chunks.
$response->setCallback(function() use ($filePath) {
// Same code goes inside the callback.
$reader = ReaderFactory::create(Type::XLSX);
$reader->open($filePath);
$i = 0;
foreach ($reader->getSheetIterator() as $sheet) {
// The main difference with the regular response is
// that the content is now echo'ed, not appended.
echo '<table>';
foreach ($sheet->getRowIterator() as $row) {
echo '<tr>';
echo implode(array_map(function($cell) {
return '<td>' . $cell . '</td>';
}, $row));
echo '</tr>';
$i++;
// Flushing the buffer every N rows to stream echo'ed content.
if ($i % FLUSH_THRESHOLD === 0) {
flush();
}
}
echo '</table><br>';
}
$reader->close();
});
return $response;
}
}
```

10
docs/_pages/index.md Normal file
View File

@ -0,0 +1,10 @@
---
layout: default
banner: true
title: Spout - Read and write spreadsheets, quickly and at scale
permalink: /
---
{% include section-supported-spreadsheet-types.html %}
{% include section-fast-and-scalable.html %}
{% include section-why-use-spout.html %}

278
docs/_sass/_base.scss Executable file
View File

@ -0,0 +1,278 @@
/**
* Reset some basic elements
*/
body, h1, h2, h3, h4, h5, h6,
p, blockquote, pre, hr,
dl, dd, ol, ul, figure {
margin: 0;
padding: 0;
}
/**
* Basic styling
*/
body {
font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family;
color: $text-color;
background-color: $background-color;
-webkit-text-size-adjust: 100%;
-webkit-font-feature-settings: "kern" 1;
-moz-font-feature-settings: "kern" 1;
-o-font-feature-settings: "kern" 1;
font-feature-settings: "kern" 1;
font-kerning: normal;
}
/**
* Set `margin-bottom` to maintain vertical rhythm
*/
h1, h2, h3, h4, h5, h6,
p, blockquote, pre,
ul, ol, dl, figure,
%vertical-rhythm {
margin-bottom: $spacing-unit / 2;
}
h1:not(:nth-child(1)), h2:not(:nth-child(1)), h3:not(:nth-child(1)), h4:not(:nth-child(1)), h5:not(:nth-child(1)), h6:not(:nth-child(1)) {
margin-top: $spacing-unit;
}
/**
* Tables
*/
table {
display: block;
width: 100%;
overflow: auto;
margin-top: 0;
margin-bottom: 16px;
border-spacing: 0;
border-collapse: collapse;
th, td {
padding: 6px 13px;
border: 1px solid #dfe2e5;
}
tr {
background-color: #fff;
border-top: 1px solid #c6cbd1;
&:nth-child(2n) {
background-color: #f6f8fa;
}
}
th {
font-weight: 500;
}
}
/**
* Images
*/
img {
max-width: 100%;
vertical-align: middle;
}
/**
* Figures
*/
figure > img {
display: block;
}
figcaption {
font-size: $small-font-size;
}
/**
* Lists
*/
ul, ol {
margin-left: $spacing-unit;
}
li {
> ul,
> ol {
margin-bottom: 0;
}
}
/**
* Headings
*/
h1, h2, h3, h4, h5, h6 {
font-weight: $base-font-weight;
}
h1 {
font-size: 3em;
}
h2 {
font-size: 2em;
}
/**
* Links
*/
a {
color: $brand-color;
text-decoration: none;
font-weight: $base-font-weight;
&:visited {
color: lighten($brand-color, 15%);
}
&:hover {
color: $text-color;
text-decoration: underline;
}
}
/**
* Blockquotes
*/
blockquote {
color: $grey-color;
border-left: 4px solid lighten($brand-color, 65%);
padding: $spacing-unit / 3 $spacing-unit / 2;
font-style: italic;
font-weight: 200;
> :last-child {
margin-bottom: 0;
}
}
/**
* Code formatting
*/
pre,
code {
font-size: 15px;
// border: 1px solid darken($code-color, 5%);
border-radius: 3px;
// background-color: $code-color;
}
code {
padding: 1px 5px;
}
pre {
padding: 8px 12px;
overflow-x: auto;
> code {
border: 0;
padding-right: 0;
padding-left: 0;
}
}
/**
* Wrapper
*/
.wrapper {
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit} * 2));
max-width: calc(#{$content-width} - (#{$spacing-unit} * 2));
margin-right: auto;
margin-left: auto;
padding-right: $spacing-unit;
padding-left: $spacing-unit;
@extend %clearfix;
@include media-query($on-laptop) {
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit}));
max-width: calc(#{$content-width} - (#{$spacing-unit}));
padding-right: $spacing-unit / 2;
padding-left: $spacing-unit / 2;
}
}
/**
* Clearfix
*/
%clearfix {
&:after {
content: "";
display: table;
clear: both;
}
}
/**
* Icons
*/
.icon {
> svg {
display: inline-block;
width: 16px;
height: 16px;
vertical-align: middle;
path {
fill: $grey-color;
}
}
}
.pull-left {
float: left;
}
.pull-right {
float: right;
}
.vertical-align-middle {
// position: relative;
// top: 50%;
// transform: translateY(50%);
display: flex;
justify-content: center;
align-items: center;
}
.mrl { margin-right: 20px; }
.mll { margin-left: 20px; }
.mbl { margin-bottom: 20px; }
.text-center { text-align: center; }
.light-em {
font-weight: $base-font-weight + 100;
}

79
docs/_sass/_index.scss Executable file
View File

@ -0,0 +1,79 @@
.spout {
font-variant: small-caps;
}
.section {
padding: $spacing-unit 0;
border-bottom: 1px solid $grey-color-light;
&.last-section {
border-bottom: none;
}
.section-title {
text-align: center;
}
.description {
width: 600px;
}
&.centered .description {
text-align: center;
}
.icons {
text-align: center;
img {
margin: 10px 20px;
}
}
&.section-odd {
background-color: rgba($grey-color, 0.0);
}
&.section-even {
background-color: $grey-color-very-light;
}
.btn-wrapper {
margin: $spacing-unit 0;
text-align: center;
.btn {
border-radius: 5px;
border: 1px solid;
background: $brand-color;
box-shadow: none;
color: white;
font-size: 20px;
padding: 15px 30px;
text-decoration: none;
cursor: pointer;
font-weight: 500;
}
.btn:hover {
background: darken($brand-color, 10%);
}
.page-link:hover {
text-decoration: none;
}
}
}
.feature-list {
list-style: none;
padding: 0;
margin: 0;
}
.feature-check {
background: url("../images/blue-check-mark.png") no-repeat left top;
background-size: 15px 15px;
background-position-y: 4px;
padding-left: 25px;
}

406
docs/_sass/_layout.scss Executable file
View File

@ -0,0 +1,406 @@
/**
* Site header
*/
.site-header {
border-bottom: 1px solid lighten($brand-color, 15%);
min-height: 56px;
// Positioning context for the mobile navigation icon
position: relative;
}
.site-search {
padding-right: 10px;
}
.site-title {
font-size: 26px;
font-weight: 300;
line-height: 56px;
letter-spacing: -1px;
margin-bottom: 0;
float: left;
&,
&:visited {
color: $grey-color-dark;
}
}
.site-nav {
float: right;
line-height: 56px;
.menu-icon {
display: none;
}
.page-link {
color: $grey-color;
line-height: $base-line-height;
// Gaps between nav items, but not on the last one
&:not(:last-child) {
margin-right: 20px;
}
}
@include media-query($on-palm) {
position: absolute;
top: 9px;
right: $spacing-unit / 2;
background-color: $background-color;
border: 1px solid $grey-color-light;
border-radius: 5px;
text-align: right;
.menu-icon {
display: block;
float: right;
width: 36px;
height: 26px;
line-height: 0;
padding-top: 10px;
text-align: center;
> svg {
width: 18px;
height: 15px;
path {
fill: $grey-color-dark;
}
}
}
.trigger {
clear: both;
display: none;
}
&:hover .trigger {
display: block;
padding-bottom: 5px;
}
.page-link {
display: block;
padding: 5px 10px;
&:not(:last-child) {
margin-right: 0;
}
margin-left: 20px;
}
}
}
.site-banner {
width: 100%;
background: $brand-color;
height: 340px;
color: white;
.wrapper {
height: 100%;
background: url('../images/logo.png');
background-size: 30%;
background-repeat: no-repeat;
background-position: 95% center;
justify-content: flex-start;
}
.tag-line {
font-size: 2.8em;
line-height: 1.1em;
width: 60%;
}
@include media-query($on-palm) {
height: 240px;
.tag-line {
font-size: 1.8em;
}
}
}
/**
* Site footer
*/
.site-footer {
background: $grey-color-dark;
color: $grey-color-light;
border-top: 1px solid $grey-color-light;
padding: $spacing-unit 0;
margin-top: $spacing-unit;
a {
color: $grey-color-very-light;
}
.contact-list,
.social-media-list {
list-style: none;
margin-left: 0;
li {
margin-bottom: 2px;
}
}
.footer-col-wrapper {
font-size: 15px;
// color: $grey-color;
margin-left: -$spacing-unit / 2;
@extend %clearfix;
}
.footer-col {
float: left;
margin-bottom: $spacing-unit / 2;
padding-left: $spacing-unit / 2;
}
.footer-col-1 {
width: -webkit-calc(35% - (#{$spacing-unit} / 2));
width: calc(35% - (#{$spacing-unit} / 2));
}
.footer-col-2 {
width: -webkit-calc(25% - (#{$spacing-unit} / 2));
width: calc(25% - (#{$spacing-unit} / 2));
}
.footer-col-3 {
width: -webkit-calc(40% - (#{$spacing-unit} / 2));
width: calc(40% - (#{$spacing-unit} / 2));
}
@include media-query($on-laptop) {
.footer-col-1,
.footer-col-2 {
width: -webkit-calc(50% - (#{$spacing-unit} / 2));
width: calc(50% - (#{$spacing-unit} / 2));
}
.footer-col-3 {
width: -webkit-calc(100% - (#{$spacing-unit} / 2));
width: calc(100% - (#{$spacing-unit} / 2));
}
}
@include media-query($on-palm) {
.footer-col {
float: none;
width: -webkit-calc(100% - (#{$spacing-unit} / 2));
width: calc(100% - (#{$spacing-unit} / 2));
}
}
}
/**
* Page content
*/
.page-content {
padding: $spacing-unit 0;
}
.page-heading {
font-size: 20px;
}
.post-list {
margin-left: 0;
list-style: none;
> li {
margin-bottom: $spacing-unit;
}
}
.post-meta {
font-size: $small-font-size;
color: $grey-color;
}
.post-link {
display: block;
font-size: 24px;
}
/**
* Posts
*/
.post-header {
margin-bottom: $spacing-unit;
padding-top: $spacing-unit;
}
.post-title {
font-size: 42px;
letter-spacing: -1px;
line-height: 1;
@include media-query($on-laptop) {
font-size: 36px;
}
}
.post-content {
margin-bottom: $spacing-unit;
h2 {
font-size: 32px;
@include media-query($on-laptop) {
font-size: 28px;
}
}
h3 {
font-size: 26px;
@include media-query($on-laptop) {
font-size: 22px;
}
}
h4 {
font-size: 20px;
@include media-query($on-laptop) {
font-size: 18px;
}
}
}
$table-of-content-width: 250px;
.table-of-content {
position: absolute;
left: 100px;
width: $table-of-content-width;
}
.table-of-content.affix-top {
position: absolute;
top: 70px;
}
.table-of-content.affix-bottom {
position: absolute;
bottom: 300;
}
.table-of-content.affix {
position: fixed;
top: 30px;
}
.table-of-content a.h1 {
font-weight: 600;
}
.table-of-content a.h2 {
font-weight: 400;
font-size: 14px;
position: relative;
left: 10px;
}
.table-of-content a.h3 {
font-size: 12px;
position: relative;
left: 20px;
}
// TODO
@include media-query($on-laptop-big+$table-of-content-width) {
.table-of-content {
left: 20px;
}
}
@include media-query($on-laptop-big) {
.table-of-content {
display: none;
}
}
@include media-query($on-laptop) {
.table-of-content {
display: none;
}
}
/* search */
input#algolia-doc-search {
background: transparent url("/images/search.png") no-repeat 10px center;
background-size: 16px 16px;
position: relative;
vertical-align: top;
margin-left: 10px;
padding: 0 10px;
padding-left: 35px;
height: 30px;
margin-top: 10px;
font-size: 16px;
line-height: 20px;
background-color: #fff;
border-radius: 4px;
color: #333;
outline: none;
width: 100px;
transition: width .2s ease;
box-shadow: none;
border: 1px solid #ddd;
}
input#algolia-doc-search:focus {
width: 200px;
}
/* Bottom border of each suggestion */
.algolia-docsearch-suggestion {
border-bottom-color: $brand-color;
}
/* Main category headers */
.algolia-docsearch-suggestion--category-header {
background-color: lighten($brand-color, 20%);
}
/* Highlighted search terms */
.algolia-docsearch-suggestion--highlight {
color: lighten($brand-color, 10%);
}
/* Highligted search terms in the main category headers */
.algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--highlight {
background-color: $brand-color;
}
/* Currently selected suggestion */
.aa-cursor .algolia-docsearch-suggestion--content {
color: darken($brand-color, 10%);
}
.aa-cursor .algolia-docsearch-suggestion {
background: ligten($brand-color, 30%);
}
/* For bigger screens, when displaying results in two columns */
@media (min-width: 768px) {
/* Bottom border of each suggestion */
.algolia-docsearch-suggestion {
border-bottom-color: lighten($brand-color, 15%);
}
/* Left column, with secondary category header */
.algolia-docsearch-suggestion--subcategory-column {
border-right-color: lighten($brand-color, 25%);
background-color: lighten($brand-color, 45%);
color: darken($brand-color, 35%);
}
}

View File

@ -0,0 +1,109 @@
/**
* Syntax highlighting styles
*/
.highlight {
// markdown editing
$default: #989898;
$bg: #f5f5f5;
$caret: #00bdff;
$black: #000;
$white: #fff;
$invisible: #E0E0E0;
$highlight: #e6e6e6;
$inserted: #DDFFDD;
$output: #7F7F7F;
$promt: #555;
$traceback: #F93232;
$deleted: #fdd;
$selection: #C2E8FF;
$found: #FFE792;
$shadow: #808080;
$comment: #bbbaba;
$invalid: #F9F2CE;
$operator: #626FC9;
$keyword: #7aad36;
$symbol: #E8FFD5;
$type: #6700B9;
$constant: #9870EC;
$var: #4C8FC7;
$attribute: #d42a57;
$function: $keyword;
$built-in: $attribute;
$class: #3A1D72;
$exception: #F93232;
$section: #333333;
$number: $constant;
$literal: #de8325;
$string_re: #699D36;
$tag: $var;
$name-class: #3A77BF;
$entity: #6d98cf;
$punctuation: $black;
color: $default;
background-color: $bg;
border: 1px solid darken($bg, 5%);
.bp { color: $caret; } // Name.Builtin.Pseudo
.c { color: $comment; } // Comment
.c1 { @extend .c; } // Comment.Single
.cm { @extend .c; } // Comment.Multiline
.cp { color: $shadow } // Comment.Preproc
.cs { @extend .c; } // Comment.Special
.err { color: $exception; background-color: $invalid } // Error
.gd { color: $black; background-color: $deleted; } // Generic.Deleted
.ge { color: $default; background-color: $highlight; } // Generic.Emph
.gh { color: $section;} // Generic.Heading
.gi { color: $black; background-color: $inserted; } // Generic.Inserted
.go { color: $output } // Generic.Output
.gp { color: $promt } // Generic.Prompt
.gr { color: $exception } // Generic.Error
.gs { background-color: $white; } // Generic.Strong
.gt { color: $traceback } // Generic.Traceback
.gu { color: $black; } // Generic.Subheading
.hll { background-color: $found }
.k { color: $keyword } // Keyword
.kc { color: $constant } // Keyword.Constant
.kd { @extend .k; } // Keyword.Declaration
.kn { @extend .k; } // Keyword.Namespace
.kp { @extend .k; } // Keyword.Pseudo
.kr { @extend .k; } // Keyword.Reserved
.kt { color: $type } // Keyword.Type
.m { color: $number; } // Literal.Number
.mf { @extend .m; } // Literal.Number.Float
.mh { @extend .m; } // Literal.Number.Hex
.mi { @extend .m; } // Literal.Number.Integer
.mo { @extend .m; } // Literal.Number.Oct
.il { @extend .m; } // Literal.Number.Integer.Long
.n { color: $default } // Name
.na { color: $attribute } // Name.Attribute
.nb { color: $built-in } // Name.Builtin
.nc { color: $name-class; } // Name.Class
.nd { color: $shadow } // Name.Decorator
.nf { color: $function } // Name.Function
.ni { color: $entity;} // Name.Entity
.nn { color: $class; text-decoration: underline } // Name.Namespace
.no { color: $constant } // Name.Constant
.nt { color: $tag;} // Name.Tag
.nv { @extend .v; } // Name.Variable
.nx { color: $default; }
.o { color: $punctuation } // Operator.Word
.ow { color: $operator } // Operator.Word
.s { color: $literal } // Literal.String
.s1 { @extend .s; } // Literal.String.Single
.s2 { @extend .s; } // Literal.String.Double
.sb { @extend .s; } // Literal.String.Backtick
.sc { @extend .s; } // Literal.String.Char
.sd { @extend .s; } // Literal.String.Doc
.se { @extend .s; } // Literal.String.Escape
.sh { @extend .s; } // Literal.String.Heredoc
.si { @extend .s; } // Literal.String.Interpol
.sr { color: $string_re; } // Literal.String.Regex
.ss { color: $caret; } // Literal.String.Symbol
.sx { @extend .s; } // Literal.String.Other
.v { color: $var }
.vc { color: $class; } // Name.Variable.Class
.vg { @extend .v; } // Name.Variable.Global
.vi { @extend .v; } // Name.Variable.Instance
.w { color: $invisible; } // Text.Whitespace
}

22
docs/bower.json Executable file
View File

@ -0,0 +1,22 @@
{
"name": "Spout",
"version": "0.0.1",
"authors": [
"Adrien Loison <aloison@box.com>"
],
"license": "Apache 2",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"anchor-js": "~3.1.1"
},
"devDependencies": {
"bootstrap": "~3.3.6"
}
}

56
docs/css/main.scss Executable file
View File

@ -0,0 +1,56 @@
---
# Only the main Sass file needs front matter (the dashes are enough)
---
@charset "utf-8";
// Our variables
$base-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
$base-font-size: 16px;
$base-font-weight: 300;
$small-font-size: $base-font-size * 0.875;
$base-line-height: 1.5;
$spacing-unit: 30px;
$text-color: #111;
$background-color: #fdfdfd;
$brand-color: #104c81;
$grey-color: #828282;
$grey-color-light: lighten($grey-color, 40%);
$grey-color-very-light: lighten($grey-color, 45%);
$grey-color-dark: darken($grey-color, 25%);
// Width of the content area
$content-width: 800px;
$on-palm: 600px;
$on-laptop: 800px;
$on-laptop-big: 1400px;
// Use media queries like this:
// @include media-query($on-palm) {
// .wrapper {
// padding-right: $spacing-unit / 2;
// padding-left: $spacing-unit / 2;
// }
// }
@mixin media-query($device) {
@media screen and (max-width: $device) {
@content;
}
}
// Import partials from `sass_dir` (defaults to `_sass`)
@import
"base",
"layout",
"syntax-highlighting",
"index"
;

BIN
docs/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
docs/images/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
docs/images/icon-csv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
docs/images/icon-ods.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
docs/images/icon-xlsx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
docs/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB