From c1a4bbec0180a9bcacd49ca2cb3a38ede5a30bd6 Mon Sep 17 00:00:00 2001 From: madflow Date: Tue, 21 Nov 2017 10:55:27 +0100 Subject: [PATCH] #463, move gh-pages to docs folder --- docs/.gitignore | 9 + docs/README.md | 7 + docs/_config.yml | 37 ++ docs/_includes/algolia.html | 9 + docs/_includes/analytics.html | 12 + docs/_includes/banner.html | 9 + docs/_includes/base.html | 9 + docs/_includes/footer.html | 25 ++ docs/_includes/head.html | 17 + docs/_includes/header.html | 30 ++ docs/_includes/icon-github.html | 1 + docs/_includes/icon-github.svg | 1 + docs/_includes/section-fast-and-scalable.html | 11 + .../section-supported-spreadsheet-types.html | 14 + docs/_includes/section-why-use-spout.html | 20 + docs/_includes/section.html | 17 + docs/_layouts/default.html | 29 ++ docs/_layouts/doc.html | 40 ++ docs/_layouts/page.html | 14 + docs/_layouts/post.html | 15 + docs/_pages/documentation.md | 245 +++++++++++ docs/_pages/faq.md | 30 ++ docs/_pages/getting-started.md | 96 +++++ docs/_pages/guides.md | 19 + docs/_pages/guides/.DS_Store | Bin 0 -> 6148 bytes .../guides/1-add-data-existing-spreadsheet.md | 61 +++ .../guides/2-edit-existing-spreadsheet.md | 88 ++++ .../guides/3-read-data-from-specific-sheet.md | 46 ++ ...ymfony-stream-content-large-spreadsheet.md | 110 +++++ docs/_pages/index.md | 10 + docs/_sass/_base.scss | 278 ++++++++++++ docs/_sass/_index.scss | 79 ++++ docs/_sass/_layout.scss | 406 ++++++++++++++++++ docs/_sass/_syntax-highlighting.scss | 109 +++++ docs/bower.json | 22 + docs/css/main.scss | 56 +++ docs/favicon.ico | Bin 0 -> 120022 bytes docs/images/.DS_Store | Bin 0 -> 6148 bytes docs/images/blue-check-mark.png | Bin 0 -> 2549 bytes docs/images/icon-csv.png | Bin 0 -> 7914 bytes docs/images/icon-lightning-bolt.png | Bin 0 -> 6245 bytes docs/images/icon-ods.png | Bin 0 -> 8554 bytes docs/images/icon-xlsx.png | Bin 0 -> 7252 bytes docs/images/logo.png | Bin 0 -> 6839 bytes 44 files changed, 1981 insertions(+) create mode 100755 docs/.gitignore create mode 100755 docs/README.md create mode 100755 docs/_config.yml create mode 100644 docs/_includes/algolia.html create mode 100755 docs/_includes/analytics.html create mode 100755 docs/_includes/banner.html create mode 100755 docs/_includes/base.html create mode 100755 docs/_includes/footer.html create mode 100755 docs/_includes/head.html create mode 100755 docs/_includes/header.html create mode 100755 docs/_includes/icon-github.html create mode 100755 docs/_includes/icon-github.svg create mode 100644 docs/_includes/section-fast-and-scalable.html create mode 100644 docs/_includes/section-supported-spreadsheet-types.html create mode 100644 docs/_includes/section-why-use-spout.html create mode 100755 docs/_includes/section.html create mode 100755 docs/_layouts/default.html create mode 100755 docs/_layouts/doc.html create mode 100755 docs/_layouts/page.html create mode 100755 docs/_layouts/post.html create mode 100755 docs/_pages/documentation.md create mode 100644 docs/_pages/faq.md create mode 100755 docs/_pages/getting-started.md create mode 100644 docs/_pages/guides.md create mode 100644 docs/_pages/guides/.DS_Store create mode 100644 docs/_pages/guides/1-add-data-existing-spreadsheet.md create mode 100644 docs/_pages/guides/2-edit-existing-spreadsheet.md create mode 100644 docs/_pages/guides/3-read-data-from-specific-sheet.md create mode 100644 docs/_pages/guides/4-symfony-stream-content-large-spreadsheet.md create mode 100644 docs/_pages/index.md create mode 100755 docs/_sass/_base.scss create mode 100755 docs/_sass/_index.scss create mode 100755 docs/_sass/_layout.scss create mode 100755 docs/_sass/_syntax-highlighting.scss create mode 100755 docs/bower.json create mode 100755 docs/css/main.scss create mode 100644 docs/favicon.ico create mode 100644 docs/images/.DS_Store create mode 100644 docs/images/blue-check-mark.png create mode 100644 docs/images/icon-csv.png create mode 100644 docs/images/icon-lightning-bolt.png create mode 100644 docs/images/icon-ods.png create mode 100644 docs/images/icon-xlsx.png create mode 100644 docs/images/logo.png diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100755 index 0000000..2ad955a --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,9 @@ +/.idea +*.iml + +/tests/resources/generated +/tests/coverage +/vendor + +/.sass-cache +/_site diff --git a/docs/README.md b/docs/README.md new file mode 100755 index 0000000..5ff10af --- /dev/null +++ b/docs/README.md @@ -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` diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100755 index 0000000..66d421a --- /dev/null +++ b/docs/_config.yml @@ -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: Spout diff --git a/docs/_includes/algolia.html b/docs/_includes/algolia.html new file mode 100644 index 0000000..74506fe --- /dev/null +++ b/docs/_includes/algolia.html @@ -0,0 +1,9 @@ +{% if site.algolia.enabled %} + + +{% endif %} diff --git a/docs/_includes/analytics.html b/docs/_includes/analytics.html new file mode 100755 index 0000000..f6e2e02 --- /dev/null +++ b/docs/_includes/analytics.html @@ -0,0 +1,12 @@ +{% if site.google_analytics %} + +{% endif %} diff --git a/docs/_includes/banner.html b/docs/_includes/banner.html new file mode 100755 index 0000000..382937d --- /dev/null +++ b/docs/_includes/banner.html @@ -0,0 +1,9 @@ +
+
+

+ Read and write spreadsheets +
+ quickly and at scale +

+
+
diff --git a/docs/_includes/base.html b/docs/_includes/base.html new file mode 100755 index 0000000..a5ec555 --- /dev/null +++ b/docs/_includes/base.html @@ -0,0 +1,9 @@ + + + +{% 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 %} \ No newline at end of file diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html new file mode 100755 index 0000000..b296089 --- /dev/null +++ b/docs/_includes/footer.html @@ -0,0 +1,25 @@ + diff --git a/docs/_includes/head.html b/docs/_includes/head.html new file mode 100755 index 0000000..81da47d --- /dev/null +++ b/docs/_includes/head.html @@ -0,0 +1,17 @@ + + + + + + {% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %} + + + + {% if site.algolia.enabled %} + + {% endif %} + + {% if jekyll.environment == 'production' %} + {% include analytics.html %} + {% endif %} + diff --git a/docs/_includes/header.html b/docs/_includes/header.html new file mode 100755 index 0000000..856b5e0 --- /dev/null +++ b/docs/_includes/header.html @@ -0,0 +1,30 @@ + diff --git a/docs/_includes/icon-github.html b/docs/_includes/icon-github.html new file mode 100755 index 0000000..e501a16 --- /dev/null +++ b/docs/_includes/icon-github.html @@ -0,0 +1 @@ +{% include icon-github.svg %}{{ include.username }} diff --git a/docs/_includes/icon-github.svg b/docs/_includes/icon-github.svg new file mode 100755 index 0000000..4422c4f --- /dev/null +++ b/docs/_includes/icon-github.svg @@ -0,0 +1 @@ + diff --git a/docs/_includes/section-fast-and-scalable.html b/docs/_includes/section-fast-and-scalable.html new file mode 100644 index 0000000..87fafe1 --- /dev/null +++ b/docs/_includes/section-fast-and-scalable.html @@ -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!
+ Reading a huge XLSX file? No extra code needed!
+ Writing an ODS file with millions of rows? {{ site.spout_html }} can do it in no time! +{% endcapture %} + +{% include section.html %} diff --git a/docs/_includes/section-supported-spreadsheet-types.html b/docs/_includes/section-supported-spreadsheet-types.html new file mode 100644 index 0000000..2fefcea --- /dev/null +++ b/docs/_includes/section-supported-spreadsheet-types.html @@ -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 = '' %} +{% assign emE = '' %} + +{% capture sectionContent %} + {{ site.spout_html }} supports 3 types of spreadsheets: {{emS}}XLSX{{emE}}, {{emS}}ODS{{emE}} and {{emS}}CSV{{emE}}.
+ {{ 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 %} diff --git a/docs/_includes/section-why-use-spout.html b/docs/_includes/section-why-use-spout.html new file mode 100644 index 0000000..65e45e3 --- /dev/null +++ b/docs/_includes/section-why-use-spout.html @@ -0,0 +1,20 @@ +{% assign sectionClass = "section-odd last-section" %} +{% capture sectionTitle %}Why use {{ site.spout_html }}?{% endcapture %} +{% assign sectionIcons = "" %} + +{% assign emS = '' %} +{% assign emE = '' %} + +{% capture sectionContent %} +
+
+ +
+{% endcapture %} + +{% include section.html %} diff --git a/docs/_includes/section.html b/docs/_includes/section.html new file mode 100755 index 0000000..3528de6 --- /dev/null +++ b/docs/_includes/section.html @@ -0,0 +1,17 @@ + +
+
+
+

{{ sectionTitle }}

+ {{ sectionContent }} +
+ {% if sectionIcons %} + {% assign sectionIconsAsArray = sectionIcons | split:'~~' %} +
+ {% for icon in sectionIconsAsArray %} + + {% endfor %} +
+ {% endif %} +
+
diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html new file mode 100755 index 0000000..3ed14b2 --- /dev/null +++ b/docs/_layouts/default.html @@ -0,0 +1,29 @@ + + + + {% include head.html %} + + + + {% include header.html %} + + {% if page.banner %} + {% include banner.html %} + {% endif %} + + {{ content }} + + + + {% include footer.html %} + {% include algolia.html %} + + + + diff --git a/docs/_layouts/doc.html b/docs/_layouts/doc.html new file mode 100755 index 0000000..b46fdaa --- /dev/null +++ b/docs/_layouts/doc.html @@ -0,0 +1,40 @@ +--- +layout: default +--- + + + + + +
+
+ +
+
+
+

{{ page.title }}

+
+
+
+ {{ content }} +
+
+
diff --git a/docs/_layouts/page.html b/docs/_layouts/page.html new file mode 100755 index 0000000..d8028d5 --- /dev/null +++ b/docs/_layouts/page.html @@ -0,0 +1,14 @@ +--- +layout: default +--- +
+
+
+

{{ page.title }}

+
+
+ {{ content }} +
+
+ +
diff --git a/docs/_layouts/post.html b/docs/_layouts/post.html new file mode 100755 index 0000000..3a0fb52 --- /dev/null +++ b/docs/_layouts/post.html @@ -0,0 +1,15 @@ +--- +layout: default +--- +
+ +
+

{{ page.title }}

+ +
+ +
+ {{ content }} +
+ +
diff --git a/docs/_pages/documentation.md b/docs/_pages/documentation.md new file mode 100755 index 0000000..e0af592 --- /dev/null +++ b/docs/_pages/documentation.md @@ -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)`
`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(); +``` diff --git a/docs/_pages/faq.md b/docs/_pages/faq.md new file mode 100644 index 0000000..aa21806 --- /dev/null +++ b/docs/_pages/faq.md @@ -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
*inline strings* | < 1 second | 35-40 seconds | 18-20 minutes | +| | Read
*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. diff --git a/docs/_pages/getting-started.md b/docs/_pages/getting-started.md new file mode 100755 index 0000000..49fe0fc --- /dev/null +++ b/docs/_pages/getting-started.md @@ -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. diff --git a/docs/_pages/guides.md b/docs/_pages/guides.md new file mode 100644 index 0000000..5746c8c --- /dev/null +++ b/docs/_pages/guides.md @@ -0,0 +1,19 @@ +--- +layout: page +title: Guides +permalink: /guides/ +--- + +These guides focus on common and more advanced usages of {{ site.spout_html }}.
+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' %} + diff --git a/docs/_pages/guides/.DS_Store b/docs/_pages/guides/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0open($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. diff --git a/docs/_pages/guides/2-edit-existing-spreadsheet.md b/docs/_pages/guides/2-edit-existing-spreadsheet.md new file mode 100644 index 0000000..4c59c76 --- /dev/null +++ b/docs/_pages/guides/2-edit-existing-spreadsheet.md @@ -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 +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. diff --git a/docs/_pages/guides/3-read-data-from-specific-sheet.md b/docs/_pages/guides/3-read-data-from-specific-sheet.md new file mode 100644 index 0000000..59b6af5 --- /dev/null +++ b/docs/_pages/guides/3-read-data-from-specific-sheet.md @@ -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(); +``` diff --git a/docs/_pages/guides/4-symfony-stream-content-large-spreadsheet.md b/docs/_pages/guides/4-symfony-stream-content-large-spreadsheet.md new file mode 100644 index 0000000..1efb4db --- /dev/null +++ b/docs/_pages/guides/4-symfony-stream-content-large-spreadsheet.md @@ -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 .= ''; + foreach ($sheet->getRowIterator() as $row) { + $content .= ''; + $content .= implode(array_map(function($cell) { + return ''; + }, $row)); + $content .= ''; + } + $content .= '
' . $cell . '

'; + } + + $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 ''; + foreach ($sheet->getRowIterator() as $row) { + echo ''; + echo implode(array_map(function($cell) { + return ''; + }, $row)); + echo ''; + + $i++; + // Flushing the buffer every N rows to stream echo'ed content. + if ($i % FLUSH_THRESHOLD === 0) { + flush(); + } + } + echo '
' . $cell . '

'; + } + + $reader->close(); + }); + + return $response; + } +} +``` diff --git a/docs/_pages/index.md b/docs/_pages/index.md new file mode 100644 index 0000000..a2da586 --- /dev/null +++ b/docs/_pages/index.md @@ -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 %} diff --git a/docs/_sass/_base.scss b/docs/_sass/_base.scss new file mode 100755 index 0000000..29116c5 --- /dev/null +++ b/docs/_sass/_base.scss @@ -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; +} diff --git a/docs/_sass/_index.scss b/docs/_sass/_index.scss new file mode 100755 index 0000000..a3b9744 --- /dev/null +++ b/docs/_sass/_index.scss @@ -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; +} diff --git a/docs/_sass/_layout.scss b/docs/_sass/_layout.scss new file mode 100755 index 0000000..b6e6b8b --- /dev/null +++ b/docs/_sass/_layout.scss @@ -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%); + } +} diff --git a/docs/_sass/_syntax-highlighting.scss b/docs/_sass/_syntax-highlighting.scss new file mode 100755 index 0000000..b1e7faf --- /dev/null +++ b/docs/_sass/_syntax-highlighting.scss @@ -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 +} diff --git a/docs/bower.json b/docs/bower.json new file mode 100755 index 0000000..1c7a59c --- /dev/null +++ b/docs/bower.json @@ -0,0 +1,22 @@ +{ + "name": "Spout", + "version": "0.0.1", + "authors": [ + "Adrien Loison " + ], + "license": "Apache 2", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "anchor-js": "~3.1.1" + }, + "devDependencies": { + "bootstrap": "~3.3.6" + } +} diff --git a/docs/css/main.scss b/docs/css/main.scss new file mode 100755 index 0000000..0d57b8f --- /dev/null +++ b/docs/css/main.scss @@ -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" +; diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3af769cafe1203da6a4c9813cc5083a62bc87da1 GIT binary patch literal 120022 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 literal 0 HcmV?d00001 diff --git a/docs/images/.DS_Store b/docs/images/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 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 HrlJ1=O@Z;O literal 0 HcmV?d00001 diff --git a/docs/images/icon-ods.png b/docs/images/icon-ods.png new file mode 100644 index 0000000000000000000000000000000000000000..2bde710082f5aacaf9e19559ffd248d07f5fe38a GIT binary patch literal 8554 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 abFr=7XPNzB+4$!N2xWNs(EkC_5FRD~ literal 0 HcmV?d00001 diff --git a/docs/images/icon-xlsx.png b/docs/images/icon-xlsx.png new file mode 100644 index 0000000000000000000000000000000000000000..466833649aba6c2ede9d7c8ed18397a585393253 GIT binary patch literal 7252 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 b{*V(K>Fe0MVqf{^t4~EiQ@&Qt^4<RcVvWBw literal 0 HcmV?d00001 diff --git a/docs/images/logo.png b/docs/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..854944e820f9919d1e7810cf2b6ae82aabff6e67 GIT binary patch literal 6839 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 Wr<)o-nk3!9gV3rvD$PjC=>G%%oB8tq literal 0 HcmV?d00001