├── .gitignore
├── examplesbasic-xlsx.xlsx
├── examples
├── basic-xlsx.xlsx
├── basic-xml.php
├── basic-xlsx.php
└── basic-xml.xml
├── .editorconfig
├── .travis.yml
├── phpcs.xml.dist
├── composer.json
├── src
├── AbstractBuilder.php
├── XmlBuilder.php
└── XlsxBuilder.php
├── LICENSE
├── readme.md
└── composer.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 |
--------------------------------------------------------------------------------
/examplesbasic-xlsx.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kasparsd/mini-sheets-php/master/examplesbasic-xlsx.xlsx
--------------------------------------------------------------------------------
/examples/basic-xlsx.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kasparsd/mini-sheets-php/master/examples/basic-xlsx.xlsx
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 | indent_style = tab
10 |
11 | [*.{json,yml,yaml}]
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.md]
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - '5.6'
5 | - '7.2'
6 |
7 | install:
8 | - composer install
9 |
10 | script:
11 | - composer lint
12 |
13 | # Pull requests are built by default.
14 | branches:
15 | only:
16 | - master
17 |
18 | notifications:
19 | email: false
20 |
21 | cache:
22 | directories:
23 | - $HOME/.composer/cache
24 |
--------------------------------------------------------------------------------
/examples/basic-xml.php:
--------------------------------------------------------------------------------
1 | add_props(
10 | [
11 | 'Author' => 'Name Surname',
12 | ]
13 | );
14 |
15 | $builder->add_rows(
16 | [
17 | [
18 | 'row 1, col1',
19 | 'row 1, col2',
20 | ],
21 | [
22 | 'row 2, col1',
23 | 'row 2, col2',
24 | ],
25 | ]
26 | );
27 |
28 | echo $builder->build(); // See basic-xml.xml for the generated output.
29 |
--------------------------------------------------------------------------------
/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | /vendor/
16 |
17 |
--------------------------------------------------------------------------------
/examples/basic-xlsx.php:
--------------------------------------------------------------------------------
1 | open( $filename, \ZipArchive::CREATE | \ZipArchive::OVERWRITE );
10 |
11 | $builder = new XlsxBuilder( $zipper );
12 | $builder->add_rows(
13 | [
14 | [
15 | 'row 1, col1',
16 | 'row 1, col2',
17 | ],
18 | [
19 | 'row 2, col1',
20 | 'row 2, col2',
21 | ],
22 | ]
23 | );
24 |
25 | $builder->build();
26 |
27 | $zipper->close(); // See basic-xlsx.xlsx for the generated output.
28 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kasparsd/mini-sheets-php",
3 | "description": "Library for creating the most basic Office Open XML and Excel XLSX files.",
4 | "type": "library",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Kaspars Dambis",
9 | "email": "hi@kaspars.net"
10 | }
11 | ],
12 | "autoload": {
13 | "psr-4": {
14 | "kasparsd\\MiniSheets\\": "src/"
15 | }
16 | },
17 | "require": {},
18 | "require-dev": {
19 | "wp-coding-standards/wpcs": "^2.1"
20 | },
21 | "suggest": {
22 | "ext-zip": "For generating XLSX files which are actually ZIP files."
23 | },
24 | "scripts": {
25 | "lint": [
26 | "phpcs ."
27 | ]
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/AbstractBuilder.php:
--------------------------------------------------------------------------------
1 | rows[] = $fields;
11 | }
12 |
13 | public function add_rows( $rows ) {
14 | $this->rows = array_merge( $this->rows, $rows );
15 | }
16 |
17 | public function rows() {
18 | return $this->rows;
19 | }
20 |
21 | /**
22 | * Return the created timestamp in W3CDTF format.
23 | *
24 | * @return string
25 | */
26 | public function created() {
27 | return gmdate( 'Y-m-d\TH:i:s\Z' );
28 | }
29 |
30 | public function escape_xml( $string ) {
31 | return htmlspecialchars( $string, ENT_XML1 | ENT_COMPAT, 'UTF-8' );
32 | }
33 |
34 | abstract public function build();
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/examples/basic-xml.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 | Name Surname
10 | 2019-06-06T06:49:06Z
11 |
12 |
13 | | row 1, col1 |
14 | row 1, col2 |
15 | | row 2, col1 |
16 | row 2, col2 |
17 |
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Kaspars Dambis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # MiniSheets
2 |
3 | [](https://travis-ci.com/kasparsd/mini-sheets-php)
4 |
5 | Extremely minimal and limited PHP library for creating the most basic Office Open XML and Microsoft Excel XLSX files.
6 |
7 | Created as a learning excerise for creating the most basic possible XLSX file. See [this blog post for the origin story](https://kaspars.net/blog/excel-xlsx-xml-php).
8 |
9 |
10 | ## Requirements
11 |
12 | - [`ZipArchive`](https://www.php.net/manual/en/class.ziparchive.php) for creating XLSX files which are actually ZIP files (TODO: add support for any ZIP library).
13 |
14 |
15 | ## Usage
16 |
17 | Install it as a Composer dependency:
18 |
19 | composer require kasparsd/mini-sheets-php
20 |
21 | See [examples](examples) for how to use it.
22 |
23 |
24 | ## Contribute
25 |
26 | All contributions are welcome! Please create [an issue](https://github.com/kasparsd/mini-sheets-php/issues/new) or [open a pull request](https://github.com/kasparsd/mini-sheets-php/pulls) with the suggested changes.
27 |
28 |
29 | ## Credits
30 |
31 | Created by [Kaspars Dambis](https://kaspars.net).
32 |
--------------------------------------------------------------------------------
/src/XmlBuilder.php:
--------------------------------------------------------------------------------
1 | props = array_merge( $this->props, $props );
11 | }
12 |
13 | public function props() {
14 | return array_merge(
15 | $this->props,
16 | [
17 | 'Created' => $this->created(),
18 | ]
19 | );
20 | }
21 |
22 | public function build() {
23 | $props_fields = [];
24 |
25 | foreach ( $this->props() as $prop_key => $prop_value ) {
26 | $props_fields[] = sprintf(
27 | '<%1$s>%2$s%1$s>',
28 | $this->escape_xml( $prop_key ),
29 | $this->escape_xml( $prop_value )
30 | );
31 | }
32 |
33 | return sprintf(
34 | '
35 |
36 |
41 |
42 | %s
43 |
44 |
45 |
46 |
47 | ',
48 | implode( "\n", $props_fields ),
49 | implode( "\n", $this->rows_formatted() )
50 | );
51 | }
52 |
53 | protected function rows_formatted() {
54 | $rows = [];
55 |
56 | foreach ( $this->rows() as $row ) {
57 | $cells = [];
58 |
59 | foreach ( $row as $field_value ) {
60 | $field_type = 'String';
61 |
62 | if ( is_numeric( $field_value ) ) {
63 | $field_type = 'Number';
64 | }
65 |
66 | $cells[] = sprintf(
67 | '%s | ',
68 | $this->escape_xml( $field_type ),
69 | $this->escape_xml( $field_value )
70 | );
71 | }
72 |
73 | $rows[] = sprintf(
74 | '%s
',
75 | implode( "\n", $cells )
76 | );
77 | }
78 |
79 | return $rows;
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "d8172d48635d35acb78b0a33f495433c",
8 | "packages": [],
9 | "packages-dev": [
10 | {
11 | "name": "squizlabs/php_codesniffer",
12 | "version": "3.4.2",
13 | "source": {
14 | "type": "git",
15 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
16 | "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8"
17 | },
18 | "dist": {
19 | "type": "zip",
20 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8",
21 | "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8",
22 | "shasum": ""
23 | },
24 | "require": {
25 | "ext-simplexml": "*",
26 | "ext-tokenizer": "*",
27 | "ext-xmlwriter": "*",
28 | "php": ">=5.4.0"
29 | },
30 | "require-dev": {
31 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
32 | },
33 | "bin": [
34 | "bin/phpcs",
35 | "bin/phpcbf"
36 | ],
37 | "type": "library",
38 | "extra": {
39 | "branch-alias": {
40 | "dev-master": "3.x-dev"
41 | }
42 | },
43 | "notification-url": "https://packagist.org/downloads/",
44 | "license": [
45 | "BSD-3-Clause"
46 | ],
47 | "authors": [
48 | {
49 | "name": "Greg Sherwood",
50 | "role": "lead"
51 | }
52 | ],
53 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
54 | "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
55 | "keywords": [
56 | "phpcs",
57 | "standards"
58 | ],
59 | "time": "2019-04-10T23:49:02+00:00"
60 | },
61 | {
62 | "name": "wp-coding-standards/wpcs",
63 | "version": "2.1.1",
64 | "source": {
65 | "type": "git",
66 | "url": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards.git",
67 | "reference": "bd9c33152115e6741e3510ff7189605b35167908"
68 | },
69 | "dist": {
70 | "type": "zip",
71 | "url": "https://api.github.com/repos/WordPress-Coding-Standards/WordPress-Coding-Standards/zipball/bd9c33152115e6741e3510ff7189605b35167908",
72 | "reference": "bd9c33152115e6741e3510ff7189605b35167908",
73 | "shasum": ""
74 | },
75 | "require": {
76 | "php": ">=5.4",
77 | "squizlabs/php_codesniffer": "^3.3.1"
78 | },
79 | "require-dev": {
80 | "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
81 | "phpcompatibility/php-compatibility": "^9.0",
82 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
83 | },
84 | "suggest": {
85 | "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically."
86 | },
87 | "type": "phpcodesniffer-standard",
88 | "notification-url": "https://packagist.org/downloads/",
89 | "license": [
90 | "MIT"
91 | ],
92 | "authors": [
93 | {
94 | "name": "Contributors",
95 | "homepage": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/graphs/contributors"
96 | }
97 | ],
98 | "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions",
99 | "keywords": [
100 | "phpcs",
101 | "standards",
102 | "wordpress"
103 | ],
104 | "time": "2019-05-21T02:50:00+00:00"
105 | }
106 | ],
107 | "aliases": [],
108 | "minimum-stability": "stable",
109 | "stability-flags": [],
110 | "prefer-stable": false,
111 | "prefer-lowest": false,
112 | "platform": [],
113 | "platform-dev": []
114 | }
115 |
--------------------------------------------------------------------------------
/src/XlsxBuilder.php:
--------------------------------------------------------------------------------
1 | zipper = $zipper;
13 | }
14 |
15 | public function build() {
16 | $this->zipper->addEmptyDir( 'docProps' );
17 | $this->zipper->addFromString( 'docProps/app.xml', $this->app_xml() );
18 | $this->zipper->addFromString( 'docProps/core.xml', $this->core_xml() );
19 |
20 | $this->zipper->addEmptyDir( '_rels' );
21 | $this->zipper->addFromString( '_rels/.rels', $this->rels_xml() );
22 |
23 | $this->zipper->addEmptyDir( 'xl/worksheets' );
24 | $this->zipper->addFromString( 'xl/worksheets/sheet1.xml', $this->sheet_xml() );
25 | $this->zipper->addFromString( 'xl/workbook.xml', $this->workbook_xml() );
26 | $this->zipper->addFromString( 'xl/sharedStrings.xml', $this->shared_strings_xml() );
27 |
28 | $this->zipper->addEmptyDir( 'xl/_rels' );
29 | $this->zipper->addFromString( 'xl/_rels/workbook.xml.rels', self::workbook_rels_xml() );
30 |
31 | $this->zipper->addFromString( '[Content_Types].xml', $this->content_types_xml() );
32 |
33 | return $this->zipper;
34 | }
35 |
36 | public function xlsx_get_shared_string_no( $string ) {
37 | static $string_pos = [];
38 |
39 | if ( isset( $this->shared_strings[ $string ] ) ) {
40 | $this->shared_strings[ $string ] += 1;
41 | } else {
42 | $this->shared_strings[ $string ] = 1;
43 | }
44 |
45 | if ( ! isset( $string_pos[ $string ] ) ) {
46 | $string_pos[ $string ] = array_search( $string, array_keys( $this->shared_strings ), true );
47 | }
48 |
49 | return $string_pos[ $string ];
50 | }
51 |
52 | public function xlsx_cell_name( $row_no, $column_no ) {
53 | $n = $column_no;
54 |
55 | for ( $r = ''; $n >= 0; $n = intval( $n / 26 ) - 1 ) {
56 | $r = chr( $n % 26 + 0x41 ) . $r;
57 | }
58 |
59 | return $r . ( $row_no + 1 );
60 | }
61 |
62 | public function sheet_xml() {
63 | $rows = [];
64 |
65 | foreach ( $this->rows as $row_no => $row ) {
66 | $cells = [];
67 | $row = array_values( $row );
68 |
69 | foreach ( $row as $col_no => $field_value ) {
70 | $field_type = 's';
71 |
72 | if ( is_numeric( $field_value ) ) {
73 | $field_type = 'n';
74 | }
75 |
76 | $field_value_no = $this->xlsx_get_shared_string_no( $field_value );
77 |
78 | $cells[] = sprintf(
79 | '%d',
80 | $this->escape_xml( $this->xlsx_cell_name( $row_no, $col_no ) ),
81 | $this->escape_xml( $field_type ),
82 | $this->escape_xml( $field_value_no )
83 | );
84 | }
85 |
86 | $rows[] = sprintf(
87 | '
88 | %s
89 |
',
90 | $this->escape_xml( $row_no + 1 ),
91 | implode( "\n", $cells )
92 | );
93 | }
94 |
95 | return sprintf(
96 | '
97 |
98 |
99 | %s
100 |
101 | ',
102 | implode( "\n", $rows )
103 | );
104 | }
105 |
106 | public function shared_strings_xml() {
107 | $shared_strings = [];
108 |
109 | foreach ( $this->shared_strings as $string => $string_count ) {
110 | $shared_strings[] = sprintf(
111 | '%s',
112 | $this->escape_xml( $string )
113 | );
114 | }
115 |
116 | return sprintf(
117 | '
118 |
119 | %s
120 | ',
121 | array_sum( $this->shared_strings ),
122 | count( $this->shared_strings ),
123 | implode( "\n", $shared_strings )
124 | );
125 | }
126 |
127 |
128 | public function workbook_xml() {
129 | return sprintf(
130 | '
131 |
132 |
133 |
134 |
135 | '
136 | );
137 | }
138 |
139 | public function content_types_xml() {
140 | return sprintf(
141 | '
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 | '
151 | );
152 | }
153 |
154 | public function workbook_rels_xml() {
155 | return sprintf(
156 | '
157 |
158 |
159 |
160 | '
161 | );
162 | }
163 |
164 | public function app_xml() {
165 | return sprintf(
166 | '
167 |
168 | MiniSheets
169 | '
170 | );
171 | }
172 |
173 | public function core_xml() {
174 | return sprintf(
175 | '
176 |
177 | %s
178 | MiniSheets
179 | ',
180 | $this->escape_xml( $this->created() )
181 | );
182 | }
183 |
184 | public function rels_xml() {
185 | return sprintf(
186 | '
187 |
188 |
189 |
190 |
191 | '
192 | );
193 | }
194 |
195 | }
196 |
--------------------------------------------------------------------------------