Skip to content

Commit

Permalink
Merge tag 'v1.0.1' into develop
Browse files Browse the repository at this point in the history
Tagging version v1.0.1 v1.0.1
  • Loading branch information
plandem committed Jul 18, 2019
2 parents c877aef + d50a29c commit 39362ed
Show file tree
Hide file tree
Showing 17 changed files with 97 additions and 102 deletions.
56 changes: 9 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgit.luolix.top%2Fplandem%2Fxlsx.svg?type=shield)](https://app.fossa.io/projects/git%2Bgit.luolix.top%2Fplandem%2Fxlsx?ref=badge_shield)
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/gayvoronsky)

**Note:** Github repository was renamed from `xlsx` to `xlsx2go` to make it more easier to distinct existing xlsx libraries. Previous address will be auto redirected, package will be named as before - xlsx.
***
**Note:**

Github repository was renamed from `xlsx` to `xlsx2go` to make it more easier to distinct existing xlsx libraries. Previous address will be auto redirected, package will be named as before - xlsx.
***

```go
package main
Expand Down Expand Up @@ -76,57 +80,15 @@ func main() {
}
```

# Introduction
Why another library to work with Excel XLSX in GO?

Truth be told, developing of any library starts with some personal goals of author. Someone wants simple library to read Excel files, someone wants to create a new file, other wants to add charts.

So what were the goals that time? It's a great pity, but I could not get a library that:

1) respects existing data/formatting - no corrupted files or lost formatting

> What if I need to open a well formatted file created with my favorite desktop application and update only one value?! I must get almost same file with just one updated value. None of existing library was able to do it. Corrupted file or lost formatting is common issue.
2) works with big files - reasonable speed and memory footprint

> Same here, someone could not open, others took forever to open with anomaly memory usage.
3) consistent and as small API as possible with enough features set to do most common tasks - learning curve means something

> Why?! Because it's not rocket science - open/create file, create/read/update/delete sheets/rows/cols and use styles. XLSX is quite simple format to read/write and GO has quite powerful xml encoder/decoder, so the hardest part - that API.
4) easy to read/understand source code, easy to maintain, easy to contribute - no shadow places/hacks/magic, just read and understand

> I was trying to contribute to existing libraries, but...actually it's much faster to create it from ground zero than to refactor existing and get satisfied results or fix some issues.
## Documentation
* [Guide](https://plandem.github.io/xlsx2go/)
* [API Documentation](https://godoc.org/github.com/plandem/xlsx)

# Benchmarks
It was not a goal to make best of the best, but the same time it's interesting to know pros/cons.
For some cases this library is second, for other - best, but in case of reading huge files - **the only**.

| | tealeg | excelize | xlsx |
|----------------|:------:|:--------:|:----:|
| RandomGet | 1! | 3 | 2 |
| RandomSet | 1! | 3 | 2 |
| RandomSetStyle | 1! | 3 | 2 |
| ReadBigFile | 2 | 3 | 1 |
| UpdateBigFile | 2!! | 3 | 1 |
| ReadHugeFile | - | - | 1 |
| UpdateHugeFile | - | - | 1 |

* ! - does not mutate information directly, so faster get/set, but slower read/write files - sometimes it can take forever to open file.
* !! - corrupted file after saving, lost styles/formatting

[Benchmarks report](BENCHMARKS.md)
* [Benchmarks](https://github.com/plandem/xlsx-benchmarks)

# Roadmap
- [ ] sheet: copy
- [x] sheet: read as stream
- [ ] sheet: custom filters
- [x] sheet: write as stream
- [x] sheet: streaming
- [x] merged cells: merge/split for ranges, cols, rows
- [x] hyperlinks: for cells, ranges, cols, rows
- [x] range: copy
Expand All @@ -138,8 +100,8 @@ For some cases this library is second, for other - best, but in case of reading
- [x] other: conditional formatting
- [x] other: rich texts
- [ ] other: drawing
- [ ] other: unpack package to temp folder to reduce memory usage
- [x] other: more tests
- [ ] other: more optimization
- [ ] other: more tests

# Contribution
- To prevent mess, sources have strict separation of markup and functionality. Document that describes OOXML is quite huge (about 6K pages), but the same time - functionality is not.
Expand Down
18 changes: 13 additions & 5 deletions docs/src/.vuepress/config.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
const mdList = require('markdown-it-task-lists');
const path = require('path');

module.exports = {
base: '/xlsx2go/',
title: 'Xlsx2Go',
description: 'Fast and Reliable way to work with xlsx in Golang',
markdown: {
extendMarkdown: md => md.use(mdList)
},
themeConfig: {
logo: '/logo.png',
repo: 'plandem/xlsx',
repoLabel: 'GitHub',
editLinks: true,
editLinkText: 'Help to improve this page!',
editLinks: false,
// editLinkText: 'Help to improve this page!',
lastUpdated: 'Last Updated',
displayAllHeaders: true,
// algolia: {
// apiKey: '<API_KEY>',
// indexName: '<INDEX_NAME>'
// },
nav: [
{text: 'Home', link: '/'},
{text: 'Guide', link: '/guide/'},
Expand Down Expand Up @@ -48,6 +50,12 @@ module.exports = {
}
]
},
markdown: {
extendMarkdown: md => md.use(mdList)
},
chainWebpack: (config, isServer) => {
config.resolve.alias.set('@images', path.resolve(__dirname, "./public"))
},
plugins: [
'@vuepress/back-to-top',
'@vuepress/last-updated',
Expand Down
2 changes: 1 addition & 1 deletion docs/src/code/stream_read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ func Example_readStream() {
fmt.Println(cell.String())
}
}
}
}
8 changes: 2 additions & 6 deletions docs/src/code/stream_write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,14 @@ import (

func Example_writeStream() {
xl := xlsx.New()
defer xl.Close()

sheet := xl.AddSheet("Sheet1", xlsx.SheetModeStream)

for iRow, iRowMax := 0, 100; iRow < iRowMax; iRow++ {
for iCol, iColMax := 0, 9; iCol < iColMax; iCol++ {
sheet.Cell(iCol, iRow).SetValue(string(types.CellRefFromIndexes(iCol, iRow)))
sheet.Cell(iCol, iRow).SetValue(types.CellRefFromIndexes(iCol, iRow))
}
}

//you should close sheet by self to flush last data
defer sheet.Close()

xl.SaveAs("./foo.xlsx")
}
}
4 changes: 2 additions & 2 deletions docs/src/guide/comments.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ As was shown, the simplest way to add comment is to use string version. At the s
sheet.CellByRef("B2").SetValue("Any comments?")
sheet.CellByRef("B2").SetComment("No comment!")
```
![](/comments.png)
![](~@images/comments.png)

### Custom comment
While with string version of comment you can add comments really easy, sometimes we need additional settings like width, height, author or even rich text. For these cases you can use special type and configure comment as you wish.

![](/comments-custom.png)
![](~@images/comments-custom.png)

#### Example

Expand Down
3 changes: 2 additions & 1 deletion docs/src/guide/conditional-formatting.md
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
# Conditional Formatting
# Conditional Formatting
[[toc]]
2 changes: 1 addition & 1 deletion docs/src/guide/merged-cells.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

Short example is better any words

![](/merged-cells.png)
![](~@images/merged-cells.png)

<<< @/src/code/merged_cells_test.go

Expand Down
2 changes: 1 addition & 1 deletion docs/src/guide/rich-text.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ Excel has built-in limits for cell's text value. Check [Excel Limits](/guide/lim
:::

### Example
![](/rich-text.png)
![](~@images/rich-text.png)

<<< @/src/code/rich_text_test.go
13 changes: 5 additions & 8 deletions docs/src/guide/streams.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,13 @@ By default, there is no access to merged cells, hyperlinks and conditional forma
### Writing sheet
To stream out sheet, you should create it with `Stream` mode. After that you can access cells in sheet in a normal way.

::: danger N.B.
Sheets that were created as stream, should be closed to flush last chunks of data.
:::

```go
sheet := xl.AddSheet("new sheet", xlsx.SheetModeStream)

//close sheet to free allocated resources
defer sheet.Close()
sheet := xl.AddSheet("new sheet", xlsx.SheetModeStream)
```

::: note N.B.
Sheets that were created as stream, will auto close self during save phase, so no need to close it manually.
:::

#### Example
<<< @/src/code/stream_write_test.go
6 changes: 5 additions & 1 deletion docs/src/guide/values.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Working with cells' values is dead simple and in most cases it will be more than

// get raw value
var val string = sheet.CellByRef("A1").Value()

// get string presentation of value
val = sheet.CellByRef("A1").String()

```
::: tip Unification
Xlsx2Go will automagically detect type of value and call required typed getter/setter. Read below about typed values.
Expand Down Expand Up @@ -71,7 +75,7 @@ Technically, any date or time related value is stored as number with additional
### Texts
Excel uses mechanism to reduce required memory while working with texts, due to fact that some text values can be repeated as is multiply times. So when string will be used more than once, Excel stores it as `Shared String` and in that case does not matter how many times user used that string, only one will be stored in memory and cell will hold only reference to that string. But for some cases, user wants to enforce Excel to store text directly in cell and for these cases Excel stores text as `Inline String`.

Xlsx2Go supports both types and user user should decide by own, what type to use.
Xlsx2Go supports both types and user should decide by own, what type to use.
```go
// set text as `Shared String`
sheet.CellByRef("A1").SetText("string")
Expand Down
1 change: 0 additions & 1 deletion example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,6 @@ func Example_gettersAndSetters() {
sheet.CellByRef("D2").SetValue(123.123)
sheet.CellByRef("E2").SetValue(now)


//get raw values that were set via typed setter
fmt.Println(sheet.CellByRef("A1").Value())
fmt.Println(sheet.CellByRef("B1").Value())
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ module github.com/plandem/xlsx
go 1.12

require (
github.com/plandem/ooxml v1.1.0
github.com/plandem/ooxml v1.1.1
github.com/stretchr/testify v1.3.0
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/plandem/ooxml v1.1.0 h1:A5wWAcTleoqSF8W5qGL6TJf5oa7TXm5S+0+HtHKEMZo=
github.com/plandem/ooxml v1.1.0/go.mod h1:6ZGylBk9B60EDlMS2DjcXt5laACXuY2huRgQME1KX2A=
github.com/plandem/ooxml v1.1.1 h1:YfCx3WOfyrIJBajs9V3PU4DVpgLvZDrQsH6Q0v0bwIs=
github.com/plandem/ooxml v1.1.1/go.mod h1:6ZGylBk9B60EDlMS2DjcXt5laACXuY2huRgQME1KX2A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
39 changes: 26 additions & 13 deletions rich_text.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
package xlsx

import (
"errors"
"fmt"
"github.com/plandem/xlsx/format/styles"
"github.com/plandem/xlsx/internal"
"github.com/plandem/xlsx/internal/ml"
"github.com/plandem/xlsx/internal/ml/primitives"
"reflect"

// to link unexported
_ "unsafe"
Expand All @@ -35,26 +35,39 @@ func toRichText(parts ...interface{}) (*ml.StringItem, *styles.Info, error) {
richText := make([]*ml.RichText, 0, len(parts))
fontPart := true

attachText := func(i int, v string) {
length += len(v)

if !fontPart || i == 0 {
//previous part was string or it's first part - add new block with a string and 'default format'
richText = append(richText, &ml.RichText{
Text: primitives.Text(v),
})
} else {
//previous part was a format, so attach a string to prev block
richText[len(richText)-1].Text = primitives.Text(v)
}

fontPart = false
}

for i, p := range parts {
switch v := p.(type) {
case string:
length += len(v)

if !fontPart || i == 0 {
//previous part was string or it's first part - add new block with a string and 'default format'
richText = append(richText, &ml.RichText{
Text: primitives.Text(v),
})
default:
if v != nil && reflect.TypeOf(v).Kind() == reflect.String {
attachText(i, reflect.ValueOf(v).String())
} else {
//previous part was a format, so attach a string to prev block
richText[len(richText)-1].Text = primitives.Text(v)
return nil, nil, fmt.Errorf("unsupported type `%s` for rich text part", reflect.TypeOf(v).Name())
}
case fmt.Stringer:
attachText(i, v.String())

fontPart = false
case string:
attachText(i, v)

case *styles.Info:
if fontPart && i > 0 {
return nil, nil, errors.New("two styles in row is not allowed")
return nil, nil, fmt.Errorf("two styles in row is not allowed")
}

richText = append(richText, &ml.RichText{
Expand Down
18 changes: 17 additions & 1 deletion rich_text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package xlsx
import (
"github.com/plandem/xlsx/format/styles"
"github.com/plandem/xlsx/internal/ml"
"github.com/plandem/xlsx/types"
"github.com/stretchr/testify/require"
"testing"
)
Expand All @@ -31,7 +32,16 @@ func TestToRichText(t *testing.T) {
styles.Alignment.HAlign(styles.HAlignCenter),
)

text, cellStyles, err := toRichText("1", "2", "3", s)
text, cellStyles, err := toRichText(
//normal strings
"1", "2", "3",
//fmt.Stringer
types.BoundsFromIndexes(0, 0, 1, 1),
//custom type with underlying type as string
types.CellRefFromIndexes(2, 2),
//cell styles
s,
)
require.Nil(t, err)
require.Equal(t, &ml.StringItem{
RichText: []*ml.RichText{
Expand All @@ -44,6 +54,12 @@ func TestToRichText(t *testing.T) {
{
Text: "3",
},
{
Text: "A1:B2",
},
{
Text: "C3",
},
},
}, text)
require.Equal(t, s, cellStyles)
Expand Down
Loading

0 comments on commit 39362ed

Please sign in to comment.