2023-01-02 11:47:31 +08:00
|
|
|
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
2018-09-14 00:44:23 +08:00
|
|
|
// this source code is governed by a BSD-style license that can be found in
|
|
|
|
// the LICENSE file.
|
|
|
|
//
|
2022-02-17 00:09:11 +08:00
|
|
|
// Package excelize providing a set of functions that allow you to write to and
|
|
|
|
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
|
|
|
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
|
|
|
// Supports complex components by high compatibility, and provided streaming
|
|
|
|
// API for generating or reading data from a worksheet with huge amounts of
|
2023-01-02 11:47:31 +08:00
|
|
|
// data. This library needs Go version 1.16 or later.
|
2018-09-14 00:58:48 +08:00
|
|
|
|
2017-05-13 13:28:21 +08:00
|
|
|
package excelize
|
|
|
|
|
|
|
|
import (
|
2019-12-20 00:30:48 +08:00
|
|
|
"bytes"
|
2017-05-13 13:28:21 +08:00
|
|
|
"encoding/xml"
|
2018-06-23 19:35:27 +08:00
|
|
|
"fmt"
|
2019-12-20 00:30:48 +08:00
|
|
|
"io"
|
2020-03-31 00:02:00 +08:00
|
|
|
"path/filepath"
|
2017-05-13 13:28:21 +08:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2023-03-25 13:30:13 +08:00
|
|
|
// GetComments retrieves all comments in a worksheet by given worksheet name.
|
|
|
|
func (f *File) GetComments(sheet string) ([]Comment, error) {
|
|
|
|
var comments []Comment
|
|
|
|
sheetXMLPath, ok := f.getSheetXMLPath(sheet)
|
|
|
|
if !ok {
|
|
|
|
return comments, newNoExistSheetError(sheet)
|
|
|
|
}
|
|
|
|
commentsXML := f.getSheetComments(filepath.Base(sheetXMLPath))
|
|
|
|
if !strings.HasPrefix(commentsXML, "/") {
|
|
|
|
commentsXML = "xl" + strings.TrimPrefix(commentsXML, "..")
|
|
|
|
}
|
|
|
|
commentsXML = strings.TrimPrefix(commentsXML, "/")
|
|
|
|
cmts, err := f.commentsReader(commentsXML)
|
|
|
|
if err != nil {
|
|
|
|
return comments, err
|
|
|
|
}
|
|
|
|
if cmts != nil {
|
|
|
|
for _, cmt := range cmts.CommentList.Comment {
|
|
|
|
comment := Comment{}
|
|
|
|
if cmt.AuthorID < len(cmts.Authors.Author) {
|
|
|
|
comment.Author = cmts.Authors.Author[cmt.AuthorID]
|
|
|
|
}
|
|
|
|
comment.Cell = cmt.Ref
|
|
|
|
comment.AuthorID = cmt.AuthorID
|
|
|
|
if cmt.Text.T != nil {
|
|
|
|
comment.Text += *cmt.Text.T
|
|
|
|
}
|
|
|
|
for _, text := range cmt.Text.R {
|
|
|
|
if text.T != nil {
|
|
|
|
run := RichTextRun{Text: text.T.Val}
|
|
|
|
if text.RPr != nil {
|
|
|
|
run.Font = newFont(text.RPr)
|
2020-04-06 00:23:27 +08:00
|
|
|
}
|
2023-03-25 13:30:13 +08:00
|
|
|
comment.Runs = append(comment.Runs, run)
|
2018-09-14 00:24:49 +08:00
|
|
|
}
|
|
|
|
}
|
2023-03-25 13:30:13 +08:00
|
|
|
comments = append(comments, comment)
|
2018-06-30 18:37:14 +08:00
|
|
|
}
|
|
|
|
}
|
2022-11-12 00:02:11 +08:00
|
|
|
return comments, nil
|
2018-06-30 18:37:14 +08:00
|
|
|
}
|
|
|
|
|
2019-02-23 16:20:44 +08:00
|
|
|
// getSheetComments provides the method to get the target comment reference by
|
2020-03-31 00:02:00 +08:00
|
|
|
// given worksheet file path.
|
|
|
|
func (f *File) getSheetComments(sheetFile string) string {
|
2022-11-13 00:40:04 +08:00
|
|
|
rels, _ := f.relsReader("xl/worksheets/_rels/" + sheetFile + ".rels")
|
|
|
|
if sheetRels := rels; sheetRels != nil {
|
2023-04-24 00:02:13 +08:00
|
|
|
sheetRels.mu.Lock()
|
|
|
|
defer sheetRels.mu.Unlock()
|
2019-02-26 14:21:44 +08:00
|
|
|
for _, v := range sheetRels.Relationships {
|
|
|
|
if v.Type == SourceRelationshipComments {
|
|
|
|
return v.Target
|
|
|
|
}
|
2019-02-23 16:20:44 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2017-05-13 13:28:21 +08:00
|
|
|
// AddComment provides the method to add comment in a sheet by given worksheet
|
2017-05-13 14:12:43 +08:00
|
|
|
// index, cell and format set (such as author and text). Note that the max
|
|
|
|
// author length is 255 and the max text length is 32512. For example, add a
|
2017-05-13 13:28:21 +08:00
|
|
|
// comment in Sheet1!$A$30:
|
|
|
|
//
|
2023-01-02 11:47:31 +08:00
|
|
|
// err := f.AddComment("Sheet1", excelize.Comment{
|
2022-11-02 08:42:00 +08:00
|
|
|
// Cell: "A12",
|
|
|
|
// Author: "Excelize",
|
|
|
|
// Runs: []excelize.RichTextRun{
|
|
|
|
// {Text: "Excelize: ", Font: &excelize.Font{Bold: true}},
|
|
|
|
// {Text: "This is a comment."},
|
|
|
|
// },
|
|
|
|
// })
|
|
|
|
func (f *File) AddComment(sheet string, comment Comment) error {
|
2017-05-13 13:28:21 +08:00
|
|
|
// Read sheet data.
|
2020-11-10 23:48:09 +08:00
|
|
|
ws, err := f.workSheetReader(sheet)
|
2019-04-15 11:22:57 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-05-13 13:28:21 +08:00
|
|
|
commentID := f.countComments() + 1
|
|
|
|
drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml"
|
|
|
|
sheetRelationshipsComments := "../comments" + strconv.Itoa(commentID) + ".xml"
|
|
|
|
sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml"
|
2020-11-10 23:48:09 +08:00
|
|
|
if ws.LegacyDrawing != nil {
|
2017-05-13 13:28:21 +08:00
|
|
|
// The worksheet already has a comments relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
|
2020-11-10 23:48:09 +08:00
|
|
|
sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID)
|
2017-05-13 13:28:21 +08:00
|
|
|
commentID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
|
2022-06-12 00:19:12 +08:00
|
|
|
drawingVML = strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl")
|
2017-05-13 13:28:21 +08:00
|
|
|
} else {
|
|
|
|
// Add first comment for given sheet.
|
2022-07-18 00:21:34 +08:00
|
|
|
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
|
|
|
|
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
|
2019-09-16 01:17:35 +08:00
|
|
|
rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
|
|
|
|
f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "")
|
2020-07-18 15:15:16 +08:00
|
|
|
f.addSheetNameSpace(sheet, SourceRelationship)
|
2017-05-13 13:28:21 +08:00
|
|
|
f.addSheetLegacyDrawing(sheet, rID)
|
|
|
|
}
|
|
|
|
commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml"
|
2022-11-02 08:42:00 +08:00
|
|
|
var rows, cols int
|
|
|
|
for _, runs := range comment.Runs {
|
|
|
|
for _, subStr := range strings.Split(runs.Text, "\n") {
|
|
|
|
rows++
|
|
|
|
if chars := len(subStr); chars > cols {
|
|
|
|
cols = chars
|
2018-06-23 19:35:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-04-17 08:48:30 +08:00
|
|
|
if len(comment.Runs) == 0 {
|
|
|
|
rows, cols = 1, len(comment.Text)
|
|
|
|
}
|
2022-11-02 08:42:00 +08:00
|
|
|
if err = f.addDrawingVML(commentID, drawingVML, comment.Cell, rows+1, cols); err != nil {
|
2019-03-23 20:08:06 +08:00
|
|
|
return err
|
|
|
|
}
|
2022-11-12 00:02:11 +08:00
|
|
|
if err = f.addComment(commentsXML, comment); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-13 00:40:04 +08:00
|
|
|
return f.addContentTypePart(commentID, "comments")
|
2017-05-13 13:28:21 +08:00
|
|
|
}
|
|
|
|
|
2022-08-19 23:24:13 +08:00
|
|
|
// DeleteComment provides the method to delete comment in a sheet by given
|
2022-09-18 00:07:15 +08:00
|
|
|
// worksheet name. For example, delete the comment in Sheet1!$A$30:
|
2022-08-19 23:24:13 +08:00
|
|
|
//
|
|
|
|
// err := f.DeleteComment("Sheet1", "A30")
|
This closes #1358, made a refactor with breaking changes, see details:
This made a refactor with breaking changes:
Motivation and Context
When I decided to add set horizontal centered support for this library to resolve #1358, the reason I made this huge breaking change was:
- There are too many exported types for set sheet view, properties, and format properties, although a function using the functional options pattern can be optimized by returning an anonymous function, these types or property set or get function has no binding categorization, so I change these functions like `SetAppProps` to accept a pointer of options structure.
- Users can not easily find out which properties should be in the `SetSheetPrOptions` or `SetSheetFormatPr` categories
- Nested properties cannot proceed modify easily
Introduce 5 new export data types:
`HeaderFooterOptions`, `PageLayoutMarginsOptions`, `PageLayoutOptions`, `SheetPropsOptions`, and `ViewOptions`
Rename 4 exported data types:
- Rename `PivotTableOption` to `PivotTableOptions`
- Rename `FormatHeaderFooter` to `HeaderFooterOptions`
- Rename `FormatSheetProtection` to `SheetProtectionOptions`
- Rename `SparklineOption` to `SparklineOptions`
Remove 54 exported types:
`AutoPageBreaks`, `BaseColWidth`, `BlackAndWhite`, `CodeName`, `CustomHeight`, `Date1904`, `DefaultColWidth`, `DefaultGridColor`, `DefaultRowHeight`, `EnableFormatConditionsCalculation`, `FilterPrivacy`, `FirstPageNumber`, `FitToHeight`, `FitToPage`, `FitToWidth`, `OutlineSummaryBelow`, `PageLayoutOption`, `PageLayoutOptionPtr`, `PageLayoutOrientation`, `PageLayoutPaperSize`, `PageLayoutScale`, `PageMarginBottom`, `PageMarginFooter`, `PageMarginHeader`, `PageMarginLeft`, `PageMarginRight`, `PageMarginsOptions`, `PageMarginsOptionsPtr`, `PageMarginTop`, `Published`, `RightToLeft`, `SheetFormatPrOptions`, `SheetFormatPrOptionsPtr`, `SheetPrOption`, `SheetPrOptionPtr`, `SheetViewOption`, `SheetViewOptionPtr`, `ShowFormulas`, `ShowGridLines`, `ShowRowColHeaders`, `ShowRuler`, `ShowZeros`, `TabColorIndexed`, `TabColorRGB`, `TabColorTheme`, `TabColorTint`, `ThickBottom`, `ThickTop`, `TopLeftCell`, `View`, `WorkbookPrOption`, `WorkbookPrOptionPtr`, `ZeroHeight` and `ZoomScale`
Remove 2 exported constants:
`OrientationPortrait` and `OrientationLandscape`
Change 8 functions:
- Change the `func (f *File) SetPageLayout(sheet string, opts ...PageLayoutOption) error` to `func (f *File) SetPageLayout(sheet string, opts *PageLayoutOptions) error`
- Change the `func (f *File) GetPageLayout(sheet string, opts ...PageLayoutOptionPtr) error` to `func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error)`
- Change the `func (f *File) SetPageMargins(sheet string, opts ...PageMarginsOptions) error` to `func (f *File) SetPageMargins(sheet string, opts *PageLayoutMarginsOptions) error`
- Change the `func (f *File) GetPageMargins(sheet string, opts ...PageMarginsOptionsPtr) error` to `func (f *File) GetPageMargins(sheet string) (PageLayoutMarginsOptions, error)`
- Change the `func (f *File) SetSheetViewOptions(sheet string, viewIndex int, opts ...SheetViewOption) error` to `func (f *File) SetSheetView(sheet string, viewIndex int, opts *ViewOptions) error`
- Change the `func (f *File) GetSheetViewOptions(sheet string, viewIndex int, opts ...SheetViewOptionPtr) error` to `func (f *File) GetSheetView(sheet string, viewIndex int) (ViewOptions, error)`
- Change the `func (f *File) SetWorkbookPrOptions(opts ...WorkbookPrOption) error` to `func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error`
- Change the `func (f *File) GetWorkbookPrOptions(opts ...WorkbookPrOptionPtr) error` to `func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error)`
Introduce new function to instead of existing functions:
- New function `func (f *File) SetSheetProps(sheet string, opts *SheetPropsOptions) error` instead of `func (f *File) SetSheetPrOptions(sheet string, opts ...SheetPrOption) error` and `func (f *File) SetSheetFormatPr(sheet string, opts ...SheetFormatPrOption
2022-09-29 22:00:21 +08:00
|
|
|
func (f *File) DeleteComment(sheet, cell string) error {
|
This closes #1425, breaking changes for sheet name (#1426)
- Checking and return error for invalid sheet name instead of trim invalid characters
- Add error return for the 4 functions: `DeleteSheet`, `GetSheetIndex`, `GetSheetVisible` and `SetSheetName`
- Export new error 4 constants: `ErrSheetNameBlank`, `ErrSheetNameInvalid`, `ErrSheetNameLength` and `ErrSheetNameSingleQuote`
- Rename exported error constant `ErrExistsWorksheet` to `ErrExistsSheet`
- Update unit tests for 90 functions: `AddChart`, `AddChartSheet`, `AddComment`, `AddDataValidation`, `AddPicture`, `AddPictureFromBytes`, `AddPivotTable`, `AddShape`, `AddSparkline`, `AddTable`, `AutoFilter`, `CalcCellValue`, `Cols`, `DeleteChart`, `DeleteComment`, `DeleteDataValidation`, `DeletePicture`, `DeleteSheet`, `DuplicateRow`, `DuplicateRowTo`, `GetCellFormula`, `GetCellHyperLink`, `GetCellRichText`, `GetCellStyle`, `GetCellType`, `GetCellValue`, `GetColOutlineLevel`, `GetCols`, `GetColStyle`, `GetColVisible`, `GetColWidth`, `GetConditionalFormats`, `GetDataValidations`, `GetMergeCells`, `GetPageLayout`, `GetPageMargins`, `GetPicture`, `GetRowHeight`, `GetRowOutlineLevel`, `GetRows`, `GetRowVisible`, `GetSheetIndex`, `GetSheetProps`, `GetSheetVisible`, `GroupSheets`, `InsertCol`, `InsertPageBreak`, `InsertRows`, `MergeCell`, `NewSheet`, `NewStreamWriter`, `ProtectSheet`, `RemoveCol`, `RemovePageBreak`, `RemoveRow`, `Rows`, `SearchSheet`, `SetCellBool`, `SetCellDefault`, `SetCellFloat`, `SetCellFormula`, `SetCellHyperLink`, `SetCellInt`, `SetCellRichText`, `SetCellStr`, `SetCellStyle`, `SetCellValue`, `SetColOutlineLevel`, `SetColStyle`, `SetColVisible`, `SetColWidth`, `SetConditionalFormat`, `SetHeaderFooter`, `SetPageLayout`, `SetPageMargins`, `SetPanes`, `SetRowHeight`, `SetRowOutlineLevel`, `SetRowStyle`, `SetRowVisible`, `SetSheetBackground`, `SetSheetBackgroundFromBytes`, `SetSheetCol`, `SetSheetName`, `SetSheetProps`, `SetSheetRow`, `SetSheetVisible`, `UnmergeCell`, `UnprotectSheet` and
`UnsetConditionalFormat`
- Update documentation of the set style functions
Co-authored-by: guoweikuang <weikuang.guo@shopee.com>
2022-12-23 00:54:40 +08:00
|
|
|
if err := checkSheetName(sheet); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-08-19 23:24:13 +08:00
|
|
|
sheetXMLPath, ok := f.getSheetXMLPath(sheet)
|
|
|
|
if !ok {
|
2022-11-12 00:02:11 +08:00
|
|
|
return newNoExistSheetError(sheet)
|
2022-08-19 23:24:13 +08:00
|
|
|
}
|
|
|
|
commentsXML := f.getSheetComments(filepath.Base(sheetXMLPath))
|
|
|
|
if !strings.HasPrefix(commentsXML, "/") {
|
|
|
|
commentsXML = "xl" + strings.TrimPrefix(commentsXML, "..")
|
|
|
|
}
|
|
|
|
commentsXML = strings.TrimPrefix(commentsXML, "/")
|
2022-11-12 00:02:11 +08:00
|
|
|
cmts, err := f.commentsReader(commentsXML)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if cmts != nil {
|
|
|
|
for i := 0; i < len(cmts.CommentList.Comment); i++ {
|
|
|
|
cmt := cmts.CommentList.Comment[i]
|
2022-09-10 13:05:34 +08:00
|
|
|
if cmt.Ref != cell {
|
|
|
|
continue
|
|
|
|
}
|
2022-11-12 00:02:11 +08:00
|
|
|
if len(cmts.CommentList.Comment) > 1 {
|
|
|
|
cmts.CommentList.Comment = append(
|
|
|
|
cmts.CommentList.Comment[:i],
|
|
|
|
cmts.CommentList.Comment[i+1:]...,
|
2022-09-10 13:05:34 +08:00
|
|
|
)
|
|
|
|
i--
|
|
|
|
continue
|
2022-08-19 23:24:13 +08:00
|
|
|
}
|
2022-11-12 00:02:11 +08:00
|
|
|
cmts.CommentList.Comment = nil
|
2022-08-19 23:24:13 +08:00
|
|
|
}
|
2022-11-12 00:02:11 +08:00
|
|
|
f.Comments[commentsXML] = cmts
|
2022-08-19 23:24:13 +08:00
|
|
|
}
|
This closes #1358, made a refactor with breaking changes, see details:
This made a refactor with breaking changes:
Motivation and Context
When I decided to add set horizontal centered support for this library to resolve #1358, the reason I made this huge breaking change was:
- There are too many exported types for set sheet view, properties, and format properties, although a function using the functional options pattern can be optimized by returning an anonymous function, these types or property set or get function has no binding categorization, so I change these functions like `SetAppProps` to accept a pointer of options structure.
- Users can not easily find out which properties should be in the `SetSheetPrOptions` or `SetSheetFormatPr` categories
- Nested properties cannot proceed modify easily
Introduce 5 new export data types:
`HeaderFooterOptions`, `PageLayoutMarginsOptions`, `PageLayoutOptions`, `SheetPropsOptions`, and `ViewOptions`
Rename 4 exported data types:
- Rename `PivotTableOption` to `PivotTableOptions`
- Rename `FormatHeaderFooter` to `HeaderFooterOptions`
- Rename `FormatSheetProtection` to `SheetProtectionOptions`
- Rename `SparklineOption` to `SparklineOptions`
Remove 54 exported types:
`AutoPageBreaks`, `BaseColWidth`, `BlackAndWhite`, `CodeName`, `CustomHeight`, `Date1904`, `DefaultColWidth`, `DefaultGridColor`, `DefaultRowHeight`, `EnableFormatConditionsCalculation`, `FilterPrivacy`, `FirstPageNumber`, `FitToHeight`, `FitToPage`, `FitToWidth`, `OutlineSummaryBelow`, `PageLayoutOption`, `PageLayoutOptionPtr`, `PageLayoutOrientation`, `PageLayoutPaperSize`, `PageLayoutScale`, `PageMarginBottom`, `PageMarginFooter`, `PageMarginHeader`, `PageMarginLeft`, `PageMarginRight`, `PageMarginsOptions`, `PageMarginsOptionsPtr`, `PageMarginTop`, `Published`, `RightToLeft`, `SheetFormatPrOptions`, `SheetFormatPrOptionsPtr`, `SheetPrOption`, `SheetPrOptionPtr`, `SheetViewOption`, `SheetViewOptionPtr`, `ShowFormulas`, `ShowGridLines`, `ShowRowColHeaders`, `ShowRuler`, `ShowZeros`, `TabColorIndexed`, `TabColorRGB`, `TabColorTheme`, `TabColorTint`, `ThickBottom`, `ThickTop`, `TopLeftCell`, `View`, `WorkbookPrOption`, `WorkbookPrOptionPtr`, `ZeroHeight` and `ZoomScale`
Remove 2 exported constants:
`OrientationPortrait` and `OrientationLandscape`
Change 8 functions:
- Change the `func (f *File) SetPageLayout(sheet string, opts ...PageLayoutOption) error` to `func (f *File) SetPageLayout(sheet string, opts *PageLayoutOptions) error`
- Change the `func (f *File) GetPageLayout(sheet string, opts ...PageLayoutOptionPtr) error` to `func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error)`
- Change the `func (f *File) SetPageMargins(sheet string, opts ...PageMarginsOptions) error` to `func (f *File) SetPageMargins(sheet string, opts *PageLayoutMarginsOptions) error`
- Change the `func (f *File) GetPageMargins(sheet string, opts ...PageMarginsOptionsPtr) error` to `func (f *File) GetPageMargins(sheet string) (PageLayoutMarginsOptions, error)`
- Change the `func (f *File) SetSheetViewOptions(sheet string, viewIndex int, opts ...SheetViewOption) error` to `func (f *File) SetSheetView(sheet string, viewIndex int, opts *ViewOptions) error`
- Change the `func (f *File) GetSheetViewOptions(sheet string, viewIndex int, opts ...SheetViewOptionPtr) error` to `func (f *File) GetSheetView(sheet string, viewIndex int) (ViewOptions, error)`
- Change the `func (f *File) SetWorkbookPrOptions(opts ...WorkbookPrOption) error` to `func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error`
- Change the `func (f *File) GetWorkbookPrOptions(opts ...WorkbookPrOptionPtr) error` to `func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error)`
Introduce new function to instead of existing functions:
- New function `func (f *File) SetSheetProps(sheet string, opts *SheetPropsOptions) error` instead of `func (f *File) SetSheetPrOptions(sheet string, opts ...SheetPrOption) error` and `func (f *File) SetSheetFormatPr(sheet string, opts ...SheetFormatPrOption
2022-09-29 22:00:21 +08:00
|
|
|
return err
|
2022-08-19 23:24:13 +08:00
|
|
|
}
|
|
|
|
|
2018-08-06 10:21:24 +08:00
|
|
|
// addDrawingVML provides a function to create comment as
|
2017-05-13 13:28:21 +08:00
|
|
|
// xl/drawings/vmlDrawing%d.vml by given commit ID and cell.
|
2019-03-23 20:08:06 +08:00
|
|
|
func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, colCount int) error {
|
|
|
|
col, row, err := CellNameToCoordinates(cell)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
Huge refactorig for consistent col/row numbering (#356)
* Huge refactorig for consistent col/row numbering
Started from simply changing ToALphaString()/TitleToNumber() logic and related fixes.
But have to go deeper, do fixes, after do related fixes and again and again.
Major improvements:
1. Tests made stronger again (But still be weak).
2. "Empty" returns for incorrect input replaces with panic.
3. Check for correct col/row/cell naming & addressing by default.
4. Removed huge amount of duplicated code.
5. Removed ToALphaString(), TitleToNumber() and it helpers functions at all,
and replaced with SplitCellName(), JoinCellName(), ColumnNameToNumber(), ColumnNumberToName(), CellNameToCoordinates(), CoordinatesToCellName().
6. Minor fixes for internal variable naming for code readability (ex. col, row for input params, colIdx, rowIdx for slice indexes etc).
* Formatting fixes
2019-03-20 00:14:41 +08:00
|
|
|
yAxis := col - 1
|
2017-05-13 13:28:21 +08:00
|
|
|
xAxis := row - 1
|
2019-02-25 00:29:58 +08:00
|
|
|
vml := f.VMLDrawing[drawingVML]
|
|
|
|
if vml == nil {
|
|
|
|
vml = &vmlDrawing{
|
|
|
|
XMLNSv: "urn:schemas-microsoft-com:vml",
|
|
|
|
XMLNSo: "urn:schemas-microsoft-com:office:office",
|
|
|
|
XMLNSx: "urn:schemas-microsoft-com:office:excel",
|
|
|
|
XMLNSmv: "http://macVmlSchemaUri",
|
|
|
|
Shapelayout: &xlsxShapelayout{
|
|
|
|
Ext: "edit",
|
|
|
|
IDmap: &xlsxIDmap{
|
|
|
|
Ext: "edit",
|
|
|
|
Data: commentID,
|
|
|
|
},
|
2017-05-13 13:28:21 +08:00
|
|
|
},
|
2019-02-25 00:29:58 +08:00
|
|
|
Shapetype: &xlsxShapetype{
|
|
|
|
ID: "_x0000_t202",
|
|
|
|
Coordsize: "21600,21600",
|
|
|
|
Spt: 202,
|
|
|
|
Path: "m0,0l0,21600,21600,21600,21600,0xe",
|
|
|
|
Stroke: &xlsxStroke{
|
|
|
|
Joinstyle: "miter",
|
|
|
|
},
|
|
|
|
VPath: &vPath{
|
|
|
|
Gradientshapeok: "t",
|
2020-07-22 20:20:00 +08:00
|
|
|
Connecttype: "rect",
|
2019-02-25 00:29:58 +08:00
|
|
|
},
|
2017-05-13 13:28:21 +08:00
|
|
|
},
|
2019-02-25 00:29:58 +08:00
|
|
|
}
|
2022-08-17 10:59:52 +08:00
|
|
|
// load exist comment shapes from xl/drawings/vmlDrawing%d.vml
|
2022-11-12 00:02:11 +08:00
|
|
|
d, err := f.decodeVMLDrawingReader(drawingVML)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-08-10 10:35:33 +08:00
|
|
|
if d != nil {
|
|
|
|
for _, v := range d.Shape {
|
|
|
|
s := xlsxShape{
|
|
|
|
ID: "_x0000_s1025",
|
|
|
|
Type: "#_x0000_t202",
|
|
|
|
Style: "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden",
|
2023-02-27 00:05:36 +08:00
|
|
|
Fillcolor: "#FBF6D6",
|
|
|
|
Strokecolor: "#EDEAA1",
|
2022-08-10 10:35:33 +08:00
|
|
|
Val: v.Val,
|
|
|
|
}
|
|
|
|
vml.Shape = append(vml.Shape, s)
|
|
|
|
}
|
|
|
|
}
|
2017-05-13 13:28:21 +08:00
|
|
|
}
|
|
|
|
sp := encodeShape{
|
|
|
|
Fill: &vFill{
|
2023-02-27 00:05:36 +08:00
|
|
|
Color2: "#FBFE82",
|
2017-05-13 13:28:21 +08:00
|
|
|
Angle: -180,
|
|
|
|
Type: "gradient",
|
|
|
|
Fill: &oFill{
|
|
|
|
Ext: "view",
|
|
|
|
Type: "gradientUnscaled",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Shadow: &vShadow{
|
|
|
|
On: "t",
|
|
|
|
Color: "black",
|
|
|
|
Obscured: "t",
|
|
|
|
},
|
|
|
|
Path: &vPath{
|
|
|
|
Connecttype: "none",
|
|
|
|
},
|
|
|
|
Textbox: &vTextbox{
|
|
|
|
Style: "mso-direction-alt:auto",
|
|
|
|
Div: &xlsxDiv{
|
|
|
|
Style: "text-align:left",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ClientData: &xClientData{
|
|
|
|
ObjectType: "Note",
|
2018-06-23 19:35:27 +08:00
|
|
|
Anchor: fmt.Sprintf(
|
|
|
|
"%d, 23, %d, 0, %d, %d, %d, 5",
|
2018-06-28 10:03:53 +08:00
|
|
|
1+yAxis, 1+xAxis, 2+yAxis+lineCount, colCount+yAxis, 2+xAxis+lineCount),
|
2018-06-23 19:35:27 +08:00
|
|
|
AutoFill: "True",
|
|
|
|
Row: xAxis,
|
|
|
|
Column: yAxis,
|
2017-05-13 13:28:21 +08:00
|
|
|
},
|
|
|
|
}
|
|
|
|
s, _ := xml.Marshal(sp)
|
|
|
|
shape := xlsxShape{
|
|
|
|
ID: "_x0000_s1025",
|
|
|
|
Type: "#_x0000_t202",
|
|
|
|
Style: "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden",
|
2023-02-27 00:05:36 +08:00
|
|
|
Fillcolor: "#FBF6D6",
|
|
|
|
Strokecolor: "#EDEAA1",
|
2017-05-13 13:28:21 +08:00
|
|
|
Val: string(s[13 : len(s)-14]),
|
|
|
|
}
|
|
|
|
vml.Shape = append(vml.Shape, shape)
|
2019-02-25 00:29:58 +08:00
|
|
|
f.VMLDrawing[drawingVML] = vml
|
2019-03-23 20:08:06 +08:00
|
|
|
return err
|
2017-05-13 13:28:21 +08:00
|
|
|
}
|
|
|
|
|
2018-08-06 10:21:24 +08:00
|
|
|
// addComment provides a function to create chart as xl/comments%d.xml by
|
|
|
|
// given cell and format sets.
|
2022-11-12 00:02:11 +08:00
|
|
|
func (f *File) addComment(commentsXML string, comment Comment) error {
|
2022-11-02 08:42:00 +08:00
|
|
|
if comment.Author == "" {
|
|
|
|
comment.Author = "Author"
|
2017-05-13 14:12:43 +08:00
|
|
|
}
|
2022-11-02 08:42:00 +08:00
|
|
|
if len(comment.Author) > MaxFieldLength {
|
|
|
|
comment.Author = comment.Author[:MaxFieldLength]
|
2017-05-13 14:12:43 +08:00
|
|
|
}
|
2022-11-12 00:02:11 +08:00
|
|
|
cmts, err := f.commentsReader(commentsXML)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
var authorID int
|
|
|
|
if cmts == nil {
|
|
|
|
cmts = &xlsxComments{Authors: xlsxAuthor{Author: []string{comment.Author}}}
|
|
|
|
}
|
|
|
|
if inStrSlice(cmts.Authors.Author, comment.Author, true) == -1 {
|
|
|
|
cmts.Authors.Author = append(cmts.Authors.Author, comment.Author)
|
|
|
|
authorID = len(cmts.Authors.Author) - 1
|
2021-04-27 12:46:51 +08:00
|
|
|
}
|
2022-11-12 00:02:11 +08:00
|
|
|
defaultFont, err := f.GetDefaultFont()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2017-05-13 13:28:21 +08:00
|
|
|
}
|
2022-11-12 00:02:11 +08:00
|
|
|
chars, cmt := 0, xlsxComment{
|
2022-11-02 08:42:00 +08:00
|
|
|
Ref: comment.Cell,
|
2021-04-27 12:46:51 +08:00
|
|
|
AuthorID: authorID,
|
2022-11-02 08:42:00 +08:00
|
|
|
Text: xlsxText{R: []xlsxR{}},
|
|
|
|
}
|
|
|
|
if comment.Text != "" {
|
|
|
|
if len(comment.Text) > TotalCellChars {
|
|
|
|
comment.Text = comment.Text[:TotalCellChars]
|
|
|
|
}
|
|
|
|
cmt.Text.T = stringPtr(comment.Text)
|
|
|
|
chars += len(comment.Text)
|
|
|
|
}
|
|
|
|
for _, run := range comment.Runs {
|
|
|
|
if chars == TotalCellChars {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if chars+len(run.Text) > TotalCellChars {
|
|
|
|
run.Text = run.Text[:TotalCellChars-chars]
|
|
|
|
}
|
|
|
|
chars += len(run.Text)
|
|
|
|
r := xlsxR{
|
|
|
|
RPr: &xlsxRPr{
|
|
|
|
Sz: &attrValFloat{Val: float64Ptr(9)},
|
|
|
|
Color: &xlsxColor{
|
|
|
|
Indexed: 81,
|
2017-05-13 13:28:21 +08:00
|
|
|
},
|
2022-11-02 08:42:00 +08:00
|
|
|
RFont: &attrValString{Val: stringPtr(defaultFont)},
|
|
|
|
Family: &attrValInt{Val: intPtr(2)},
|
2017-05-13 13:28:21 +08:00
|
|
|
},
|
2022-11-02 08:42:00 +08:00
|
|
|
T: &xlsxT{Val: run.Text, Space: xml.Attr{
|
|
|
|
Name: xml.Name{Space: NameSpaceXML, Local: "space"},
|
|
|
|
Value: "preserve",
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
if run.Font != nil {
|
|
|
|
r.RPr = newRpr(run.Font)
|
|
|
|
}
|
|
|
|
cmt.Text.R = append(cmt.Text.R, r)
|
2017-05-13 13:28:21 +08:00
|
|
|
}
|
2022-11-12 00:02:11 +08:00
|
|
|
cmts.CommentList.Comment = append(cmts.CommentList.Comment, cmt)
|
|
|
|
f.Comments[commentsXML] = cmts
|
|
|
|
return err
|
2017-05-13 13:28:21 +08:00
|
|
|
}
|
|
|
|
|
2018-08-06 10:21:24 +08:00
|
|
|
// countComments provides a function to get comments files count storage in
|
|
|
|
// the folder xl.
|
2017-05-13 13:28:21 +08:00
|
|
|
func (f *File) countComments() int {
|
2019-05-11 09:46:20 +08:00
|
|
|
c1, c2 := 0, 0
|
2021-07-05 00:03:56 +08:00
|
|
|
f.Pkg.Range(func(k, v interface{}) bool {
|
|
|
|
if strings.Contains(k.(string), "xl/comments") {
|
2019-05-11 09:46:20 +08:00
|
|
|
c1++
|
2017-05-13 13:28:21 +08:00
|
|
|
}
|
2021-07-05 00:03:56 +08:00
|
|
|
return true
|
|
|
|
})
|
2019-05-11 09:46:20 +08:00
|
|
|
for rel := range f.Comments {
|
|
|
|
if strings.Contains(rel, "xl/comments") {
|
|
|
|
c2++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if c1 < c2 {
|
|
|
|
return c2
|
|
|
|
}
|
|
|
|
return c1
|
2017-05-13 13:28:21 +08:00
|
|
|
}
|
2019-02-25 00:29:58 +08:00
|
|
|
|
|
|
|
// decodeVMLDrawingReader provides a function to get the pointer to the
|
|
|
|
// structure after deserialization of xl/drawings/vmlDrawing%d.xml.
|
2022-11-12 00:02:11 +08:00
|
|
|
func (f *File) decodeVMLDrawingReader(path string) (*decodeVmlDrawing, error) {
|
2019-02-25 00:29:58 +08:00
|
|
|
if f.DecodeVMLDrawing[path] == nil {
|
2021-07-05 00:03:56 +08:00
|
|
|
c, ok := f.Pkg.Load(path)
|
|
|
|
if ok && c != nil {
|
2019-12-20 00:30:48 +08:00
|
|
|
f.DecodeVMLDrawing[path] = new(decodeVmlDrawing)
|
2022-11-12 00:02:11 +08:00
|
|
|
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c.([]byte)))).
|
2019-12-20 00:30:48 +08:00
|
|
|
Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF {
|
2022-11-12 00:02:11 +08:00
|
|
|
return nil, err
|
2019-12-20 00:30:48 +08:00
|
|
|
}
|
2019-02-25 00:29:58 +08:00
|
|
|
}
|
|
|
|
}
|
2022-11-12 00:02:11 +08:00
|
|
|
return f.DecodeVMLDrawing[path], nil
|
2019-02-25 00:29:58 +08:00
|
|
|
}
|
|
|
|
|
2019-02-25 22:14:34 +08:00
|
|
|
// vmlDrawingWriter provides a function to save xl/drawings/vmlDrawing%d.xml
|
2019-02-25 00:29:58 +08:00
|
|
|
// after serialize structure.
|
|
|
|
func (f *File) vmlDrawingWriter() {
|
|
|
|
for path, vml := range f.VMLDrawing {
|
|
|
|
if vml != nil {
|
|
|
|
v, _ := xml.Marshal(vml)
|
2021-07-05 00:03:56 +08:00
|
|
|
f.Pkg.Store(path, v)
|
2019-02-25 00:29:58 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// commentsReader provides a function to get the pointer to the structure
|
|
|
|
// after deserialization of xl/comments%d.xml.
|
2022-11-12 00:02:11 +08:00
|
|
|
func (f *File) commentsReader(path string) (*xlsxComments, error) {
|
2019-02-25 00:29:58 +08:00
|
|
|
if f.Comments[path] == nil {
|
2021-07-05 00:03:56 +08:00
|
|
|
content, ok := f.Pkg.Load(path)
|
|
|
|
if ok && content != nil {
|
2019-12-20 00:30:48 +08:00
|
|
|
f.Comments[path] = new(xlsxComments)
|
2022-11-12 00:02:11 +08:00
|
|
|
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
|
2019-12-20 00:30:48 +08:00
|
|
|
Decode(f.Comments[path]); err != nil && err != io.EOF {
|
2022-11-12 00:02:11 +08:00
|
|
|
return nil, err
|
2019-12-20 00:30:48 +08:00
|
|
|
}
|
2019-02-25 00:29:58 +08:00
|
|
|
}
|
|
|
|
}
|
2022-11-12 00:02:11 +08:00
|
|
|
return f.Comments[path], nil
|
2019-02-25 00:29:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// commentsWriter provides a function to save xl/comments%d.xml after
|
|
|
|
// serialize structure.
|
|
|
|
func (f *File) commentsWriter() {
|
|
|
|
for path, c := range f.Comments {
|
|
|
|
if c != nil {
|
|
|
|
v, _ := xml.Marshal(c)
|
|
|
|
f.saveFileList(path, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|