2019-01-01 13:20:14 +08:00
|
|
|
// Copyright 2016 - 2019 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.
|
|
|
|
//
|
|
|
|
// Package excelize providing a set of functions that allow you to write to
|
|
|
|
// and read from XLSX files. Support reads and writes XLSX file generated by
|
|
|
|
// Microsoft Excel™ 2007 and later. Support save file without losing original
|
|
|
|
// charts of XLSX. This library needs Go version 1.8 or later.
|
2018-09-14 00:58:48 +08:00
|
|
|
|
2017-05-13 13:28:21 +08:00
|
|
|
package excelize
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"encoding/xml"
|
2018-06-23 19:35:27 +08:00
|
|
|
"fmt"
|
2017-05-13 13:28:21 +08:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2018-08-06 10:21:24 +08:00
|
|
|
// parseFormatCommentsSet provides a function to parse the format settings of
|
|
|
|
// the comment with default value.
|
2018-05-27 11:25:55 +08:00
|
|
|
func parseFormatCommentsSet(formatSet string) (*formatComment, error) {
|
2017-05-13 13:28:21 +08:00
|
|
|
format := formatComment{
|
|
|
|
Author: "Author:",
|
|
|
|
Text: " ",
|
|
|
|
}
|
2018-07-17 15:28:22 +08:00
|
|
|
err := json.Unmarshal([]byte(formatSet), &format)
|
2018-05-27 11:25:55 +08:00
|
|
|
return &format, err
|
2017-05-13 13:28:21 +08:00
|
|
|
}
|
|
|
|
|
2018-08-06 10:21:24 +08:00
|
|
|
// GetComments retrieves all comments and returns a map of worksheet name to
|
|
|
|
// the worksheet comments.
|
2018-09-14 00:24:49 +08:00
|
|
|
func (f *File) GetComments() (comments map[string][]Comment) {
|
|
|
|
comments = map[string][]Comment{}
|
2018-06-30 18:37:14 +08:00
|
|
|
for n := range f.sheetMap {
|
2019-02-23 16:20:44 +08:00
|
|
|
c, ok := f.XLSX["xl"+strings.TrimPrefix(f.getSheetComments(f.GetSheetIndex(n)), "..")]
|
2018-06-30 18:37:14 +08:00
|
|
|
if ok {
|
|
|
|
d := xlsxComments{}
|
|
|
|
xml.Unmarshal([]byte(c), &d)
|
2018-09-14 00:24:49 +08:00
|
|
|
sheetComments := []Comment{}
|
|
|
|
for _, comment := range d.CommentList.Comment {
|
|
|
|
sheetComment := Comment{}
|
|
|
|
if comment.AuthorID < len(d.Authors) {
|
|
|
|
sheetComment.Author = d.Authors[comment.AuthorID].Author
|
|
|
|
}
|
|
|
|
sheetComment.Ref = comment.Ref
|
|
|
|
sheetComment.AuthorID = comment.AuthorID
|
|
|
|
for _, text := range comment.Text.R {
|
|
|
|
sheetComment.Text += text.T
|
|
|
|
}
|
|
|
|
sheetComments = append(sheetComments, sheetComment)
|
|
|
|
}
|
|
|
|
comments[n] = sheetComments
|
2018-06-30 18:37:14 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-02-23 16:20:44 +08:00
|
|
|
// getSheetComments provides the method to get the target comment reference by
|
|
|
|
// given worksheet index.
|
|
|
|
func (f *File) getSheetComments(sheetID int) string {
|
|
|
|
var rels = "xl/worksheets/_rels/sheet" + strconv.Itoa(sheetID) + ".xml.rels"
|
|
|
|
var sheetRels xlsxWorkbookRels
|
|
|
|
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(rels)), &sheetRels)
|
|
|
|
for _, v := range sheetRels.Relationships {
|
|
|
|
if v.Type == SourceRelationshipComments {
|
|
|
|
return v.Target
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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:
|
|
|
|
//
|
2017-05-13 14:12:43 +08:00
|
|
|
// xlsx.AddComment("Sheet1", "A30", `{"author":"Excelize: ","text":"This is a comment."}`)
|
2017-05-13 13:28:21 +08:00
|
|
|
//
|
2018-05-27 11:25:55 +08:00
|
|
|
func (f *File) AddComment(sheet, cell, format string) error {
|
|
|
|
formatSet, err := parseFormatCommentsSet(format)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-05-13 13:28:21 +08:00
|
|
|
// Read sheet data.
|
|
|
|
xlsx := f.workSheetReader(sheet)
|
|
|
|
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"
|
|
|
|
if xlsx.LegacyDrawing != nil {
|
|
|
|
// The worksheet already has a comments relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
|
|
|
|
sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, xlsx.LegacyDrawing.RID)
|
|
|
|
commentID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
|
|
|
|
drawingVML = strings.Replace(sheetRelationshipsDrawingVML, "..", "xl", -1)
|
|
|
|
} else {
|
|
|
|
// Add first comment for given sheet.
|
|
|
|
rID := f.addSheetRelationships(sheet, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
|
|
|
|
f.addSheetRelationships(sheet, SourceRelationshipComments, sheetRelationshipsComments, "")
|
|
|
|
f.addSheetLegacyDrawing(sheet, rID)
|
|
|
|
}
|
|
|
|
commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml"
|
|
|
|
f.addComment(commentsXML, cell, formatSet)
|
2018-06-23 19:35:27 +08:00
|
|
|
var colCount int
|
|
|
|
for i, l := range strings.Split(formatSet.Text, "\n") {
|
|
|
|
if ll := len(l); ll > colCount {
|
|
|
|
if i == 0 {
|
|
|
|
ll += len(formatSet.Author)
|
|
|
|
}
|
|
|
|
colCount = ll
|
|
|
|
}
|
|
|
|
}
|
|
|
|
f.addDrawingVML(commentID, drawingVML, cell, strings.Count(formatSet.Text, "\n")+1, colCount)
|
2017-05-24 14:17:35 +08:00
|
|
|
f.addContentTypePart(commentID, "comments")
|
2018-05-27 11:25:55 +08:00
|
|
|
return err
|
2017-05-13 13:28:21 +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.
|
2018-06-23 19:35:27 +08:00
|
|
|
func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, colCount int) {
|
2017-05-13 13:28:21 +08:00
|
|
|
col := string(strings.Map(letterOnlyMapF, cell))
|
|
|
|
row, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
|
|
|
|
xAxis := row - 1
|
2017-06-27 17:53:06 +08:00
|
|
|
yAxis := TitleToNumber(col)
|
2017-05-13 13:28:21 +08:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
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",
|
2018-06-23 19:35:27 +08:00
|
|
|
Connecttype: "miter",
|
2017-05-13 13:28:21 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
sp := encodeShape{
|
|
|
|
Fill: &vFill{
|
|
|
|
Color2: "#fbfe82",
|
|
|
|
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",
|
|
|
|
Fillcolor: "#fbf6d6",
|
|
|
|
Strokecolor: "#edeaa1",
|
|
|
|
Val: string(s[13 : len(s)-14]),
|
|
|
|
}
|
|
|
|
c, ok := f.XLSX[drawingVML]
|
|
|
|
if ok {
|
|
|
|
d := decodeVmlDrawing{}
|
2018-10-18 10:23:08 +08:00
|
|
|
_ = xml.Unmarshal(namespaceStrictToTransitional(c), &d)
|
2017-05-13 13:28:21 +08:00
|
|
|
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",
|
|
|
|
Fillcolor: "#fbf6d6",
|
|
|
|
Strokecolor: "#edeaa1",
|
|
|
|
Val: v.Val,
|
|
|
|
}
|
|
|
|
vml.Shape = append(vml.Shape, s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
vml.Shape = append(vml.Shape, shape)
|
|
|
|
v, _ := xml.Marshal(vml)
|
2018-05-07 16:12:51 +08:00
|
|
|
f.XLSX[drawingVML] = v
|
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.
|
2017-05-13 13:28:21 +08:00
|
|
|
func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
|
2017-05-13 14:12:43 +08:00
|
|
|
a := formatSet.Author
|
|
|
|
t := formatSet.Text
|
|
|
|
if len(a) > 255 {
|
|
|
|
a = a[0:255]
|
|
|
|
}
|
|
|
|
if len(t) > 32512 {
|
|
|
|
t = t[0:32512]
|
|
|
|
}
|
2017-05-13 13:28:21 +08:00
|
|
|
comments := xlsxComments{
|
|
|
|
Authors: []xlsxAuthor{
|
2017-05-16 20:42:01 +08:00
|
|
|
{
|
2017-05-13 13:28:21 +08:00
|
|
|
Author: formatSet.Author,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
cmt := xlsxComment{
|
|
|
|
Ref: cell,
|
|
|
|
AuthorID: 0,
|
|
|
|
Text: xlsxText{
|
|
|
|
R: []xlsxR{
|
2017-05-16 20:42:01 +08:00
|
|
|
{
|
2017-05-13 13:28:21 +08:00
|
|
|
RPr: &xlsxRPr{
|
|
|
|
B: " ",
|
2017-09-01 12:59:15 +08:00
|
|
|
Sz: &attrValFloat{Val: 9},
|
2017-05-13 13:28:21 +08:00
|
|
|
Color: &xlsxColor{
|
|
|
|
Indexed: 81,
|
|
|
|
},
|
|
|
|
RFont: &attrValString{Val: "Calibri"},
|
|
|
|
Family: &attrValInt{Val: 2},
|
|
|
|
},
|
2017-05-13 14:12:43 +08:00
|
|
|
T: a,
|
2017-05-13 13:28:21 +08:00
|
|
|
},
|
2017-05-16 20:42:01 +08:00
|
|
|
{
|
2017-05-13 13:28:21 +08:00
|
|
|
RPr: &xlsxRPr{
|
2017-09-01 12:59:15 +08:00
|
|
|
Sz: &attrValFloat{Val: 9},
|
2017-05-13 13:28:21 +08:00
|
|
|
Color: &xlsxColor{
|
|
|
|
Indexed: 81,
|
|
|
|
},
|
|
|
|
RFont: &attrValString{Val: "Calibri"},
|
|
|
|
Family: &attrValInt{Val: 2},
|
|
|
|
},
|
2017-05-13 14:12:43 +08:00
|
|
|
T: t,
|
2017-05-13 13:28:21 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
c, ok := f.XLSX[commentsXML]
|
|
|
|
if ok {
|
|
|
|
d := xlsxComments{}
|
2018-10-18 10:23:08 +08:00
|
|
|
_ = xml.Unmarshal(namespaceStrictToTransitional(c), &d)
|
2017-05-13 13:28:21 +08:00
|
|
|
comments.CommentList.Comment = append(comments.CommentList.Comment, d.CommentList.Comment...)
|
|
|
|
}
|
|
|
|
comments.CommentList.Comment = append(comments.CommentList.Comment, cmt)
|
|
|
|
v, _ := xml.Marshal(comments)
|
2018-05-07 16:12:51 +08:00
|
|
|
f.saveFileList(commentsXML, v)
|
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 {
|
|
|
|
count := 0
|
|
|
|
for k := range f.XLSX {
|
|
|
|
if strings.Contains(k, "xl/comments") {
|
|
|
|
count++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return count
|
|
|
|
}
|