#463, move gh-pages to docs folder
9
docs/.gitignore
vendored
Executable file
@ -0,0 +1,9 @@
|
||||
/.idea
|
||||
*.iml
|
||||
|
||||
/tests/resources/generated
|
||||
/tests/coverage
|
||||
/vendor
|
||||
|
||||
/.sass-cache
|
||||
/_site
|
7
docs/README.md
Executable 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
@ -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>
|
9
docs/_includes/algolia.html
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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>
|
1
docs/_includes/icon-github.html
Executable 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
@ -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 |
11
docs/_includes/section-fast-and-scalable.html
Normal 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 %}
|
14
docs/_includes/section-supported-spreadsheet-types.html
Normal 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 %}
|
20
docs/_includes/section-why-use-spout.html
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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 strings* | < 1 second | 35-40 seconds | 18-20 minutes |
|
||||
| | Read<br>*shared 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
@ -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
@ -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
61
docs/_pages/guides/1-add-data-existing-spreadsheet.md
Normal 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.
|
88
docs/_pages/guides/2-edit-existing-spreadsheet.md
Normal 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.
|
46
docs/_pages/guides/3-read-data-from-specific-sheet.md
Normal 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();
|
||||
```
|
110
docs/_pages/guides/4-symfony-stream-content-large-spreadsheet.md
Normal 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
@ -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
@ -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
@ -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
@ -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%);
|
||||
}
|
||||
}
|
109
docs/_sass/_syntax-highlighting.scss
Executable 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
@ -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
@ -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
After Width: | Height: | Size: 117 KiB |
BIN
docs/images/.DS_Store
vendored
Normal file
BIN
docs/images/blue-check-mark.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
docs/images/icon-csv.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
docs/images/icon-lightning-bolt.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
docs/images/icon-ods.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
docs/images/icon-xlsx.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
docs/images/logo.png
Normal file
After Width: | Height: | Size: 6.7 KiB |