comment.go 6.8 KB
Newer Older
1 2 3 4 5
package excelize

import (
	"encoding/json"
	"encoding/xml"
R
Rad Cirskis 已提交
6
	"fmt"
7 8 9 10 11 12
	"strconv"
	"strings"
)

// parseFormatCommentsSet provides function to parse the format settings of the
// comment with default value.
13
func parseFormatCommentsSet(formatSet string) (*formatComment, error) {
14 15 16 17
	format := formatComment{
		Author: "Author:",
		Text:   " ",
	}
18
	err := json.Unmarshal([]byte(formatSet), &format)
19
	return &format, err
20 21
}

xurime's avatar
xurime 已提交
22
// GetComments retrieves all comments and returns a map
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
// of worksheet name to the worksheet comments.
func (f *File) GetComments() (comments map[string]*xlsxComments) {
	comments = map[string]*xlsxComments{}
	for n := range f.sheetMap {
		commentID := f.GetSheetIndex(n)
		commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml"
		c, ok := f.XLSX[commentsXML]
		if ok {
			d := xlsxComments{}
			xml.Unmarshal([]byte(c), &d)
			comments[n] = &d
		}
	}
	return
}

39
// AddComment provides the method to add comment in a sheet by given worksheet
40 41
// 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
42 43
// comment in Sheet1!$A$30:
//
44
//    xlsx.AddComment("Sheet1", "A30", `{"author":"Excelize: ","text":"This is a comment."}`)
45
//
46 47 48 49 50
func (f *File) AddComment(sheet, cell, format string) error {
	formatSet, err := parseFormatCommentsSet(format)
	if err != nil {
		return err
	}
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
	// 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)
R
Rad Cirskis 已提交
70 71 72 73 74 75 76 77 78 79
	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)
xurime's avatar
xurime 已提交
80
	f.addContentTypePart(commentID, "comments")
81
	return err
82 83 84 85
}

// addDrawingVML provides function to create comment as
// xl/drawings/vmlDrawing%d.vml by given commit ID and cell.
R
Rad Cirskis 已提交
86
func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, colCount int) {
87 88 89
	col := string(strings.Map(letterOnlyMapF, cell))
	row, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
	xAxis := row - 1
90
	yAxis := TitleToNumber(col)
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
	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",
R
Rad Cirskis 已提交
113
				Connecttype:     "miter",
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
			},
		},
	}
	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",
R
Rad Cirskis 已提交
143 144
			Anchor: fmt.Sprintf(
				"%d, 23, %d, 0, %d, %d, %d, 5",
xurime's avatar
xurime 已提交
145
				1+yAxis, 1+xAxis, 2+yAxis+lineCount, colCount+yAxis, 2+xAxis+lineCount),
R
Rad Cirskis 已提交
146 147 148
			AutoFill: "True",
			Row:      xAxis,
			Column:   yAxis,
149 150 151 152 153 154 155 156 157 158 159 160 161 162
		},
	}
	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{}
163
		_ = xml.Unmarshal([]byte(c), &d)
164 165 166 167 168 169 170 171 172 173 174 175 176 177
		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)
178
	f.XLSX[drawingVML] = v
179 180 181 182 183
}

// addComment provides function to create chart as xl/comments%d.xml by given
// cell and format sets.
func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
184 185 186 187 188 189 190 191
	a := formatSet.Author
	t := formatSet.Text
	if len(a) > 255 {
		a = a[0:255]
	}
	if len(t) > 32512 {
		t = t[0:32512]
	}
192 193
	comments := xlsxComments{
		Authors: []xlsxAuthor{
194
			{
195 196 197 198 199 200 201 202 203
				Author: formatSet.Author,
			},
		},
	}
	cmt := xlsxComment{
		Ref:      cell,
		AuthorID: 0,
		Text: xlsxText{
			R: []xlsxR{
204
				{
205 206
					RPr: &xlsxRPr{
						B:  " ",
xurime's avatar
xurime 已提交
207
						Sz: &attrValFloat{Val: 9},
208 209 210 211 212 213
						Color: &xlsxColor{
							Indexed: 81,
						},
						RFont:  &attrValString{Val: "Calibri"},
						Family: &attrValInt{Val: 2},
					},
214
					T: a,
215
				},
216
				{
217
					RPr: &xlsxRPr{
xurime's avatar
xurime 已提交
218
						Sz: &attrValFloat{Val: 9},
219 220 221 222 223 224
						Color: &xlsxColor{
							Indexed: 81,
						},
						RFont:  &attrValString{Val: "Calibri"},
						Family: &attrValInt{Val: 2},
					},
225
					T: t,
226 227 228 229 230 231 232
				},
			},
		},
	}
	c, ok := f.XLSX[commentsXML]
	if ok {
		d := xlsxComments{}
233
		_ = xml.Unmarshal([]byte(c), &d)
234 235 236 237
		comments.CommentList.Comment = append(comments.CommentList.Comment, d.CommentList.Comment...)
	}
	comments.CommentList.Comment = append(comments.CommentList.Comment, cmt)
	v, _ := xml.Marshal(comments)
238
	f.saveFileList(commentsXML, v)
239 240 241 242 243 244 245 246 247 248 249 250 251
}

// countComments provides function to get comments files count storage in the
// folder xl.
func (f *File) countComments() int {
	count := 0
	for k := range f.XLSX {
		if strings.Contains(k, "xl/comments") {
			count++
		}
	}
	return count
}