146 Star 857 Fork 175

GVPxuri/excelize

加入 Gitee
与超过 1400万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
vml.go 37.85 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// 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 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
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"path/filepath"
"strconv"
"strings"
)
// FormControlType is the type of supported form controls.
type FormControlType byte
// This section defines the currently supported form control types enumeration.
const (
FormControlNote FormControlType = iota
FormControlButton
FormControlOptionButton
FormControlSpinButton
FormControlCheckBox
FormControlGroupBox
FormControlLabel
FormControlScrollBar
)
// HeaderFooterImagePositionType is the type of header and footer image position.
type HeaderFooterImagePositionType byte
// Worksheet header and footer image position types enumeration.
const (
HeaderFooterImagePositionLeft HeaderFooterImagePositionType = iota
HeaderFooterImagePositionCenter
HeaderFooterImagePositionRight
)
// 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, ErrSheetNotExist{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 = text.RPr.getFont()
}
comment.Paragraph = append(comment.Paragraph, run)
}
}
comments = append(comments, comment)
}
}
return comments, nil
}
// getSheetComments provides the method to get the target comment reference by
// given worksheet file path.
func (f *File) getSheetComments(sheetFile string) string {
rels, _ := f.relsReader("xl/worksheets/_rels/" + sheetFile + ".rels")
if sheetRels := rels; sheetRels != nil {
sheetRels.mu.Lock()
defer sheetRels.mu.Unlock()
for _, v := range sheetRels.Relationships {
if v.Type == SourceRelationshipComments {
return v.Target
}
}
}
return ""
}
// AddComment provides the method to add comments in a sheet by giving the
// worksheet name, cell reference, and format set (such as author and text).
// Note that the maximum author name length is 255 and the max text length is
// 32512, and each cell can only have one comment, an error will return if
// adding comment on a cell which already exist comment. For example, add a
// rich-text comment with a specified comments box size in Sheet1!A5:
//
// err := f.AddComment("Sheet1", excelize.Comment{
// Cell: "A5",
// Author: "Excelize",
// Paragraph: []excelize.RichTextRun{
// {Text: "Excelize: ", Font: &excelize.Font{Bold: true}},
// {Text: "This is a comment."},
// },
// Height: 40,
// Width: 180,
// })
func (f *File) AddComment(sheet string, opts Comment) error {
return f.addVMLObject(vmlOptions{
sheet: sheet, Comment: opts,
FormControl: FormControl{
Cell: opts.Cell,
Type: FormControlNote,
Text: opts.Text,
Paragraph: opts.Paragraph,
Width: opts.Width,
Height: opts.Height,
},
})
}
// DeleteComment provides the method to delete comment in a worksheet by given
// worksheet name and cell reference. For example, delete the comment in
// Sheet1!$A$30:
//
// err := f.DeleteComment("Sheet1", "A30")
func (f *File) DeleteComment(sheet, cell string) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if ws.LegacyDrawing == nil {
return err
}
sheetXMLPath, _ := f.getSheetXMLPath(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 err
}
if cmts != nil {
for i := 0; i < len(cmts.CommentList.Comment); i++ {
cmt := cmts.CommentList.Comment[i]
if cmt.Ref != cell {
continue
}
if len(cmts.CommentList.Comment) > 1 {
cmts.CommentList.Comment = append(
cmts.CommentList.Comment[:i],
cmts.CommentList.Comment[i+1:]...,
)
if idx := inStrSlice(cmts.cells, cell, true); idx != -1 {
cmts.cells = append(
cmts.cells[:idx],
cmts.cells[idx+1:]...,
)
}
i--
continue
}
cmts.CommentList.Comment = nil
cmts.cells = nil
}
f.Comments[commentsXML] = cmts
}
sheetRelationshipsDrawingVML := f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID)
return f.deleteFormControl(sheetRelationshipsDrawingVML, cell, true)
}
// deleteFormControl provides the method to delete shape from
// xl/drawings/vmlDrawing%d.xml by giving path, cell and shape type.
func (f *File) deleteFormControl(sheetRelationshipsDrawingVML, cell string, isComment bool) error {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return err
}
vmlID, _ := strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
drawingVML := strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl")
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: vmlID},
},
ShapeType: &xlsxShapeType{
Stroke: &xlsxStroke{JoinStyle: "miter"},
VPath: &vPath{GradientShapeOK: "t", ConnectType: "rect"},
},
}
// Load exist VML shapes from xl/drawings/vmlDrawing%d.vml
d, err := f.decodeVMLDrawingReader(drawingVML)
if err != nil {
return err
}
if d != nil {
vml.ShapeType.ID = d.ShapeType.ID
vml.ShapeType.CoordSize = d.ShapeType.CoordSize
vml.ShapeType.Spt = d.ShapeType.Spt
vml.ShapeType.Path = d.ShapeType.Path
for _, v := range d.Shape {
s := xlsxShape{
ID: v.ID,
Type: v.Type,
Style: v.Style,
Button: v.Button,
Filled: v.Filled,
FillColor: v.FillColor,
InsetMode: v.InsetMode,
Stroked: v.Stroked,
StrokeColor: v.StrokeColor,
Val: v.Val,
}
vml.Shape = append(vml.Shape, s)
}
}
}
cond := func(objectType string) bool {
if isComment {
return objectType == "Note"
}
return objectType != "Note"
}
for i, sp := range vml.Shape {
var shapeVal decodeShapeVal
if err = xml.Unmarshal([]byte(fmt.Sprintf("<shape>%s</shape>", sp.Val)), &shapeVal); err == nil &&
cond(shapeVal.ClientData.ObjectType) && shapeVal.ClientData.Anchor != "" {
leftCol, topRow, err := extractAnchorCell(shapeVal.ClientData.Anchor)
if err != nil {
return err
}
if leftCol == col-1 && topRow == row-1 {
vml.Shape = append(vml.Shape[:i], vml.Shape[i+1:]...)
break
}
}
}
f.VMLDrawing[drawingVML] = vml
return err
}
// addComment provides a function to create chart as xl/comments%d.xml by
// given cell and format sets.
func (f *File) addComment(commentsXML string, opts vmlOptions) error {
if opts.Author == "" {
opts.Author = "Author"
}
if countUTF16String(opts.Author) > MaxFieldLength {
opts.Author = truncateUTF16Units(opts.Author, MaxFieldLength)
}
cmts, err := f.commentsReader(commentsXML)
if err != nil {
return err
}
if cmts == nil {
cmts = &xlsxComments{Authors: xlsxAuthor{Author: []string{opts.Author}}}
}
if inStrSlice(cmts.cells, opts.Comment.Cell, true) != -1 {
return newAddCommentError(opts.Comment.Cell)
}
authorID := inStrSlice(cmts.Authors.Author, opts.Author, true)
if authorID == -1 {
cmts.Authors.Author = append(cmts.Authors.Author, opts.Author)
authorID = len(cmts.Authors.Author) - 1
}
defaultFont, err := f.GetDefaultFont()
if err != nil {
return err
}
chars, cmt := 0, xlsxComment{
Ref: opts.Comment.Cell,
AuthorID: authorID,
Text: xlsxText{R: []xlsxR{}},
}
if opts.Comment.Text != "" {
if countUTF16String(opts.Comment.Text) > TotalCellChars {
opts.Comment.Text = truncateUTF16Units(opts.Comment.Text, TotalCellChars)
}
cmt.Text.T = stringPtr(opts.Comment.Text)
chars += countUTF16String(opts.Comment.Text)
}
for _, run := range opts.Comment.Paragraph {
if chars == TotalCellChars {
break
}
if chars+countUTF16String(run.Text) > TotalCellChars {
run.Text = truncateUTF16Units(run.Text, TotalCellChars-chars)
}
chars += countUTF16String(run.Text)
r := xlsxR{
RPr: &xlsxRPr{
Sz: &attrValFloat{Val: float64Ptr(9)},
Color: &xlsxColor{
Indexed: 81,
},
RFont: &attrValString{Val: stringPtr(defaultFont)},
Family: &attrValInt{Val: intPtr(2)},
},
T: &xlsxT{Val: run.Text, Space: xml.Attr{
Name: xml.Name{Space: NameSpaceXML, Local: "space"},
Value: "preserve",
}},
}
if run.Font != nil {
r.RPr = run.Font.newRpr()
}
cmt.Text.R = append(cmt.Text.R, r)
}
cmts.CommentList.Comment = append(cmts.CommentList.Comment, cmt)
cmts.cells = append(cmts.cells, cmt.Ref)
f.Comments[commentsXML] = cmts
return err
}
// countComments provides a function to get comments files count storage in
// the folder xl.
func (f *File) countComments() int {
comments := map[string]struct{}{}
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/comments") {
comments[k.(string)] = struct{}{}
}
return true
})
for rel := range f.Comments {
if strings.Contains(rel, "xl/comments") {
comments[rel] = struct{}{}
}
}
return len(comments)
}
// commentsReader provides a function to get the pointer to the structure
// after deserialization of xl/comments%d.xml.
func (f *File) commentsReader(path string) (*xlsxComments, error) {
if f.Comments[path] == nil {
content, ok := f.Pkg.Load(path)
if ok && content != nil {
f.Comments[path] = new(xlsxComments)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(f.Comments[path]); err != nil && err != io.EOF {
return nil, err
}
}
}
if cmts := f.Comments[path]; cmts != nil && len(cmts.cells) == 0 {
for idx := 0; idx < len(cmts.CommentList.Comment); idx++ {
cmts.cells = append(cmts.cells, cmts.CommentList.Comment[idx].Ref)
}
f.Comments[path] = cmts
}
return f.Comments[path], nil
}
// 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)
}
}
}
// AddFormControl provides the method to add form control object in a worksheet
// by given worksheet name and form control options. Supported form control
// type: button, check box, group box, label, option button, scroll bar and
// spinner. If set macro for the form control, the workbook extension should be
// XLSM or XLTM. Scroll value must be between 0 and 30000. Please note that if a
// cell link is set for a checkbox form control, Excelize will not assign a
// value to the linked cell when the checkbox is checked. To reflect the
// checkbox state, please use the 'SetCellValue' function to manually set the
// linked cell's value to true.
//
// Example 1, add button form control with macro, rich-text, custom button size,
// print property on Sheet1!A2, and let the button do not move or size with
// cells:
//
// enable := true
// err := f.AddFormControl("Sheet1", excelize.FormControl{
// Cell: "A2",
// Type: excelize.FormControlButton,
// Macro: "Button1_Click",
// Width: 140,
// Height: 60,
// Text: "Button 1\r\n",
// Paragraph: []excelize.RichTextRun{
// {
// Font: &excelize.Font{
// Bold: true,
// Italic: true,
// Underline: "single",
// Family: "Times New Roman",
// Size: 14,
// Color: "777777",
// },
// Text: "C1=A1+B1",
// },
// },
// Format: excelize.GraphicOptions{
// PrintObject: &enable,
// Positioning: "absolute",
// },
// })
//
// Example 2, add option button form control with checked status and text on
// Sheet1!A1:
//
// err := f.AddFormControl("Sheet1", excelize.FormControl{
// Cell: "A1",
// Type: excelize.FormControlOptionButton,
// Text: "Option Button 1",
// Checked: true,
// })
//
// Example 3, add spin button form control on Sheet1!B1 to increase or decrease
// the value of Sheet1!A1:
//
// err := f.AddFormControl("Sheet1", excelize.FormControl{
// Cell: "B1",
// Type: excelize.FormControlSpinButton,
// Width: 15,
// Height: 40,
// CurrentVal: 7,
// MinVal: 5,
// MaxVal: 10,
// IncChange: 1,
// CellLink: "A1",
// })
//
// Example 4, add horizontally scroll bar form control on Sheet1!A2 to change
// the value of Sheet1!A1 by click the scroll arrows or drag the scroll box:
//
// err := f.AddFormControl("Sheet1", excelize.FormControl{
// Cell: "A2",
// Type: excelize.FormControlScrollBar,
// Width: 140,
// Height: 20,
// CurrentVal: 50,
// MinVal: 10,
// MaxVal: 100,
// IncChange: 1,
// PageChange: 1,
// CellLink: "A1",
// Horizontally: true,
// })
func (f *File) AddFormControl(sheet string, opts FormControl) error {
return f.addVMLObject(vmlOptions{
formCtrl: true, sheet: sheet, FormControl: opts,
})
}
// DeleteFormControl provides the method to delete form control in a worksheet
// by given worksheet name and cell reference. For example, delete the form
// control in Sheet1!$A$1:
//
// err := f.DeleteFormControl("Sheet1", "A1")
func (f *File) DeleteFormControl(sheet, cell string) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if ws.LegacyDrawing == nil {
return err
}
sheetRelationshipsDrawingVML := f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID)
return f.deleteFormControl(sheetRelationshipsDrawingVML, cell, false)
}
// countVMLDrawing provides a function to get VML drawing files count storage
// in the folder xl/drawings.
func (f *File) countVMLDrawing() int {
drawings := map[string]struct{}{}
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/drawings/vmlDrawing") {
drawings[k.(string)] = struct{}{}
}
return true
})
for rel := range f.VMLDrawing {
if strings.Contains(rel, "xl/drawings/vmlDrawing") {
drawings[rel] = struct{}{}
}
}
return len(drawings)
}
// decodeVMLDrawingReader provides a function to get the pointer to the
// structure after deserialization of xl/drawings/vmlDrawing%d.xml.
func (f *File) decodeVMLDrawingReader(path string) (*decodeVmlDrawing, error) {
if f.DecodeVMLDrawing[path] == nil {
c, ok := f.Pkg.Load(path)
if ok && c != nil {
f.DecodeVMLDrawing[path] = new(decodeVmlDrawing)
if err := f.xmlNewDecoder(bytes.NewReader(bytesReplace(namespaceStrictToTransitional(c.([]byte)), []byte("<br>\r\n"), []byte("<br></br>\r\n"), -1))).
Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF {
return nil, err
}
}
}
return f.DecodeVMLDrawing[path], nil
}
// vmlDrawingWriter provides a function to save xl/drawings/vmlDrawing%d.xml
// after serialize structure.
func (f *File) vmlDrawingWriter() {
for path, vml := range f.VMLDrawing {
if vml != nil {
v, _ := xml.Marshal(vml)
f.Pkg.Store(path, v)
}
}
}
// addVMLObject provides a function to create VML drawing parts and
// relationships for comments and form controls.
func (f *File) addVMLObject(opts vmlOptions) error {
// Read sheet data
ws, err := f.workSheetReader(opts.sheet)
if err != nil {
return err
}
vmlID := f.countComments() + 1
if opts.formCtrl {
if opts.Type > FormControlScrollBar {
return ErrParameterInvalid
}
vmlID = f.countVMLDrawing() + 1
}
sheetID := f.getSheetID(opts.sheet)
drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
sheetXMLPath, _ := f.getSheetXMLPath(opts.sheet)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
if ws.LegacyDrawing != nil {
// The worksheet already has a VML relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(opts.sheet, ws.LegacyDrawing.RID)
vmlID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
drawingVML = strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl")
} else {
// Add first VML drawing for given sheet.
rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
f.addSheetNameSpace(opts.sheet, SourceRelationship)
f.addSheetLegacyDrawing(opts.sheet, rID)
}
if err = f.addDrawingVML(sheetID, drawingVML, prepareFormCtrlOptions(&opts)); err != nil {
return err
}
if !opts.formCtrl {
commentsXML := "xl/comments" + strconv.Itoa(vmlID) + ".xml"
if err = f.addComment(commentsXML, opts); err != nil {
return err
}
if sheetXMLPath, ok := f.getSheetXMLPath(opts.sheet); ok && f.getSheetComments(filepath.Base(sheetXMLPath)) == "" {
sheetRelationshipsComments := "../comments" + strconv.Itoa(vmlID) + ".xml"
f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "")
}
}
return f.addContentTypePart(vmlID, "comments")
}
// prepareFormCtrlOptions provides a function to parse the format settings of
// the form control with default value.
func prepareFormCtrlOptions(opts *vmlOptions) *vmlOptions {
if opts.Format.ScaleX == 0 {
opts.Format.ScaleX = 1
}
if opts.Format.ScaleY == 0 {
opts.Format.ScaleY = 1
}
if opts.FormControl.Width == 0 {
opts.FormControl.Width = 140
}
if opts.FormControl.Height == 0 {
opts.FormControl.Height = 60
}
return opts
}
// formCtrlText returns font element in the VML for control form text.
func formCtrlText(opts *vmlOptions) []vmlFont {
var font []vmlFont
if opts.FormControl.Text != "" {
font = append(font, vmlFont{Content: opts.FormControl.Text})
}
for _, run := range opts.FormControl.Paragraph {
fnt := vmlFont{
Content: run.Text + "<br></br>\r\n",
}
if run.Font != nil {
fnt.Face = run.Font.Family
fnt.Color = run.Font.Color
if !strings.HasPrefix(run.Font.Color, "#") {
fnt.Color = "#" + fnt.Color
}
if run.Font.Size != 0 {
fnt.Size = uint(run.Font.Size * 20)
}
if run.Font.Underline == "single" {
fnt.Content = "<u>" + fnt.Content + "</u>"
}
if run.Font.Underline == "double" {
fnt.Content = "<u class=\"font1\">" + fnt.Content + "</u>"
}
if run.Font.Italic {
fnt.Content = "<i>" + fnt.Content + "</i>"
}
if run.Font.Bold {
fnt.Content = "<b>" + fnt.Content + "</b>"
}
}
font = append(font, fnt)
}
return font
}
var formCtrlPresets = map[FormControlType]formCtrlPreset{
FormControlNote: {
objectType: "Note",
autoFill: "True",
filled: "",
fillColor: "#FBF6D6",
stroked: "",
strokeColor: "#EDEAA1",
strokeButton: "",
fill: &vFill{
Color2: "#FBFE82",
Angle: -180,
Type: "gradient",
Fill: &oFill{Ext: "view", Type: "gradientUnscaled"},
},
textHAlign: "",
textVAlign: "",
noThreeD: nil,
firstButton: nil,
shadow: &vShadow{On: "t", Color: "black", Obscured: "t"},
},
FormControlButton: {
objectType: "Button",
autoFill: "True",
filled: "",
fillColor: "buttonFace [67]",
stroked: "",
strokeColor: "windowText [64]",
strokeButton: "t",
fill: &vFill{
Color2: "buttonFace [67]",
Angle: -180,
Type: "gradient",
Fill: &oFill{Ext: "view", Type: "gradientUnscaled"},
},
textHAlign: "Center",
textVAlign: "Center",
noThreeD: nil,
firstButton: nil,
shadow: nil,
},
FormControlCheckBox: {
objectType: "Checkbox",
autoFill: "True",
filled: "f",
fillColor: "window [65]",
stroked: "f",
strokeColor: "windowText [64]",
strokeButton: "",
fill: nil,
textHAlign: "",
textVAlign: "Center",
noThreeD: stringPtr(""),
firstButton: nil,
shadow: nil,
},
FormControlGroupBox: {
objectType: "GBox",
autoFill: "False",
filled: "f",
fillColor: "",
stroked: "f",
strokeColor: "windowText [64]",
strokeButton: "",
fill: nil,
textHAlign: "",
textVAlign: "",
noThreeD: stringPtr(""),
firstButton: nil,
shadow: nil,
},
FormControlLabel: {
objectType: "Label",
autoFill: "False",
filled: "f",
fillColor: "window [65]",
stroked: "f",
strokeColor: "windowText [64]",
strokeButton: "",
fill: nil,
textHAlign: "",
textVAlign: "",
noThreeD: nil,
firstButton: nil,
shadow: nil,
},
FormControlOptionButton: {
objectType: "Radio",
autoFill: "False",
filled: "f",
fillColor: "window [65]",
stroked: "f",
strokeColor: "windowText [64]",
strokeButton: "",
fill: nil,
textHAlign: "",
textVAlign: "Center",
noThreeD: stringPtr(""),
firstButton: stringPtr(""),
shadow: nil,
},
FormControlScrollBar: {
objectType: "Scroll",
autoFill: "",
filled: "",
fillColor: "",
stroked: "f",
strokeColor: "windowText [64]",
strokeButton: "",
fill: nil,
textHAlign: "",
textVAlign: "",
noThreeD: nil,
firstButton: nil,
shadow: nil,
},
FormControlSpinButton: {
objectType: "Spin",
autoFill: "False",
filled: "",
fillColor: "",
stroked: "f",
strokeColor: "windowText [64]",
strokeButton: "",
fill: nil,
textHAlign: "",
textVAlign: "",
noThreeD: nil,
firstButton: nil,
shadow: nil,
},
}
// addFormCtrl check and add scroll bar or spinner form control by given options.
func (sp *encodeShape) addFormCtrl(opts *vmlOptions) error {
if opts.Type != FormControlScrollBar && opts.Type != FormControlSpinButton {
return nil
}
if opts.CurrentVal > MaxFormControlValue ||
opts.MinVal > MaxFormControlValue ||
opts.MaxVal > MaxFormControlValue ||
opts.IncChange > MaxFormControlValue ||
opts.PageChange > MaxFormControlValue {
return ErrFormControlValue
}
if opts.CellLink != "" {
if _, _, err := CellNameToCoordinates(opts.CellLink); err != nil {
return err
}
}
sp.ClientData.FmlaLink = opts.CellLink
sp.ClientData.Val = opts.CurrentVal
sp.ClientData.Min = opts.MinVal
sp.ClientData.Max = opts.MaxVal
sp.ClientData.Inc = opts.IncChange
sp.ClientData.Page = opts.PageChange
if opts.Type == FormControlScrollBar {
if opts.Horizontally {
sp.ClientData.Horiz = stringPtr("")
}
sp.ClientData.Dx = 15
}
return nil
}
// addFormCtrlShape returns a VML shape by given preset and options.
func (f *File) addFormCtrlShape(preset formCtrlPreset, col, row int, anchor string, opts *vmlOptions) (*encodeShape, error) {
sp := encodeShape{
Fill: preset.fill,
Shadow: preset.shadow,
Path: &vPath{ConnectType: "none"},
TextBox: &vTextBox{
Style: "mso-direction-alt:auto",
Div: &xlsxDiv{Style: "text-align:left"},
},
ClientData: &xClientData{
ObjectType: preset.objectType,
Anchor: anchor,
AutoFill: preset.autoFill,
Row: intPtr(row - 1),
Column: intPtr(col - 1),
TextHAlign: preset.textHAlign,
TextVAlign: preset.textVAlign,
NoThreeD: preset.noThreeD,
FirstButton: preset.firstButton,
},
}
if opts.Format.PrintObject != nil && !*opts.Format.PrintObject {
sp.ClientData.PrintObject = "False"
}
if opts.Format.Positioning != "" {
idx := inStrSlice(supportedPositioning, opts.Format.Positioning, true)
if idx == -1 {
return &sp, newInvalidOptionalValue("Positioning", opts.Format.Positioning, supportedPositioning)
}
sp.ClientData.MoveWithCells = []*string{stringPtr(""), nil, nil}[idx]
sp.ClientData.SizeWithCells = []*string{stringPtr(""), stringPtr(""), nil}[idx]
}
if opts.Type == FormControlNote {
sp.ClientData.MoveWithCells = stringPtr("")
sp.ClientData.SizeWithCells = stringPtr("")
}
if !opts.formCtrl {
return &sp, nil
}
sp.TextBox.Div.Font = formCtrlText(opts)
sp.ClientData.FmlaMacro = opts.Macro
if (opts.Type == FormControlCheckBox || opts.Type == FormControlOptionButton) && opts.Checked {
sp.ClientData.Checked = 1
}
if opts.Type == FormControlCheckBox {
sp.ClientData.FmlaLink = opts.CellLink
}
return &sp, sp.addFormCtrl(opts)
}
// addDrawingVML provides a function to create VML drawing XML as
// xl/drawings/vmlDrawing%d.vml by given data ID, XML path and VML options. The
// anchor value is a comma-separated list of data written out as: LeftColumn,
// LeftOffset, TopRow, TopOffset, RightColumn, RightOffset, BottomRow,
// BottomOffset.
func (f *File) addDrawingVML(sheetID int, drawingVML string, opts *vmlOptions) error {
col, row, err := CellNameToCoordinates(opts.FormControl.Cell)
if err != nil {
return err
}
leftOffset, vmlID, vml, preset := 23, 202, f.VMLDrawing[drawingVML], formCtrlPresets[opts.Type]
style := "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden"
if opts.formCtrl {
leftOffset, vmlID = 0, 201
style = "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;mso-wrap-style:tight"
}
colStart, rowStart, colEnd, rowEnd, _, _, x2, y2 := f.positionObjectPixels(opts.sheet, col, row, int(opts.FormControl.Width), int(opts.FormControl.Height), &opts.Format)
anchor := fmt.Sprintf("%d, %d, %d, 0, %d, %d, %d, %d", colStart, leftOffset, rowStart, colEnd, x2, rowEnd, y2)
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: sheetID},
},
ShapeType: &xlsxShapeType{
ID: fmt.Sprintf("_x0000_t%d", vmlID),
CoordSize: "21600,21600",
Spt: 202,
Path: "m0,0l0,21600,21600,21600,21600,0xe",
Stroke: &xlsxStroke{JoinStyle: "miter"},
VPath: &vPath{GradientShapeOK: "t", ConnectType: "rect"},
},
}
// Load exist VML shapes from xl/drawings/vmlDrawing%d.vml
d, err := f.decodeVMLDrawingReader(drawingVML)
if err != nil {
return err
}
if d != nil {
vml.ShapeType.ID = d.ShapeType.ID
vml.ShapeType.CoordSize = d.ShapeType.CoordSize
vml.ShapeType.Spt = d.ShapeType.Spt
vml.ShapeType.Path = d.ShapeType.Path
for _, v := range d.Shape {
s := xlsxShape{
ID: v.ID,
Type: v.Type,
Style: v.Style,
Button: v.Button,
Filled: v.Filled,
FillColor: v.FillColor,
InsetMode: v.InsetMode,
Stroked: v.Stroked,
StrokeColor: v.StrokeColor,
Val: v.Val,
}
vml.Shape = append(vml.Shape, s)
}
}
}
sp, err := f.addFormCtrlShape(preset, col, row, anchor, opts)
if err != nil {
return err
}
s, _ := xml.Marshal(sp)
shape := xlsxShape{
ID: "_x0000_s1025",
Type: fmt.Sprintf("#_x0000_t%d", vmlID),
Style: style,
Button: preset.strokeButton,
Filled: preset.filled,
FillColor: preset.fillColor,
Stroked: preset.stroked,
StrokeColor: preset.strokeColor,
Val: string(s[13 : len(s)-14]),
}
vml.Shape = append(vml.Shape, shape)
f.VMLDrawing[drawingVML] = vml
return err
}
// GetFormControls retrieves all form controls in a worksheet by a given
// worksheet name. Note that, this function does not support getting the width
// and height of the form controls currently.
func (f *File) GetFormControls(sheet string) ([]FormControl, error) {
var formControls []FormControl
// Read sheet data
ws, err := f.workSheetReader(sheet)
if err != nil {
return formControls, err
}
if ws.LegacyDrawing == nil {
return formControls, err
}
target := f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID)
drawingVML := strings.ReplaceAll(target, "..", "xl")
vml := f.VMLDrawing[drawingVML]
if vml == nil {
// Load exist VML shapes from xl/drawings/vmlDrawing%d.vml
d, err := f.decodeVMLDrawingReader(drawingVML)
if err != nil {
return formControls, err
}
for _, sp := range d.Shape {
if sp.Type != "#_x0000_t201" {
continue
}
formControl, err := extractFormControl(sp.Val)
if err != nil {
return formControls, err
}
if formControl.Type == FormControlNote || formControl.Cell == "" {
continue
}
formControls = append(formControls, formControl)
}
return formControls, err
}
for _, sp := range vml.Shape {
if sp.Type != "#_x0000_t201" {
continue
}
formControl, err := extractFormControl(sp.Val)
if err != nil {
return formControls, err
}
if formControl.Type == FormControlNote || formControl.Cell == "" {
continue
}
formControls = append(formControls, formControl)
}
return formControls, err
}
// extractFormControl provides a function to extract form controls for a
// worksheets by given client data.
func extractFormControl(clientData string) (FormControl, error) {
var (
err error
formControl FormControl
shapeVal decodeShapeVal
)
if err = xml.Unmarshal([]byte(fmt.Sprintf("<shape>%s</shape>", clientData)), &shapeVal); err != nil {
return formControl, err
}
for formCtrlType, preset := range formCtrlPresets {
if shapeVal.ClientData.ObjectType == preset.objectType && shapeVal.ClientData.Anchor != "" {
formControl.Paragraph = extractVMLFont(shapeVal.TextBox.Div.Font)
if len(formControl.Paragraph) > 0 && formControl.Paragraph[0].Font == nil {
formControl.Text = formControl.Paragraph[0].Text
formControl.Paragraph = formControl.Paragraph[1:]
}
formControl.Type = formCtrlType
col, row, err := extractAnchorCell(shapeVal.ClientData.Anchor)
if err != nil {
return formControl, err
}
if formControl.Cell, err = CoordinatesToCellName(col+1, row+1); err != nil {
return formControl, err
}
formControl.Macro = shapeVal.ClientData.FmlaMacro
formControl.Checked = shapeVal.ClientData.Checked != 0
formControl.CellLink = shapeVal.ClientData.FmlaLink
formControl.CurrentVal = shapeVal.ClientData.Val
formControl.MinVal = shapeVal.ClientData.Min
formControl.MaxVal = shapeVal.ClientData.Max
formControl.IncChange = shapeVal.ClientData.Inc
formControl.PageChange = shapeVal.ClientData.Page
formControl.Horizontally = shapeVal.ClientData.Horiz != nil
}
}
return formControl, err
}
// extractAnchorCell extract left-top cell coordinates from given VML anchor
// comma-separated list values.
func extractAnchorCell(anchor string) (int, int, error) {
var (
leftCol, topRow int
err error
pos = strings.Split(anchor, ",")
)
if len(pos) != 8 {
return leftCol, topRow, ErrParameterInvalid
}
leftCol, err = strconv.Atoi(strings.TrimSpace(pos[0]))
if err != nil {
return leftCol, topRow, ErrColumnNumber
}
topRow, err = strconv.Atoi(strings.TrimSpace(pos[2]))
return leftCol, topRow, err
}
// extractVMLFont extract rich-text and font format from given VML font element.
func extractVMLFont(font []decodeVMLFont) []RichTextRun {
var runs []RichTextRun
extractU := func(u *decodeVMLFontU, run *RichTextRun) {
if u == nil {
return
}
run.Text += u.Val
if run.Font == nil {
run.Font = &Font{}
}
run.Font.Underline = "single"
if u.Class == "font1" {
run.Font.Underline = "double"
}
}
extractI := func(i *decodeVMLFontI, run *RichTextRun) {
if i == nil {
return
}
extractU(i.U, run)
run.Text += i.Val
if run.Font == nil {
run.Font = &Font{}
}
run.Font.Italic = true
}
extractB := func(b *decodeVMLFontB, run *RichTextRun) {
if b == nil {
return
}
extractI(b.I, run)
run.Text += b.Val
if run.Font == nil {
run.Font = &Font{}
}
run.Font.Bold = true
}
for _, fnt := range font {
var run RichTextRun
extractB(fnt.B, &run)
extractI(fnt.I, &run)
extractU(fnt.U, &run)
run.Text += fnt.Val
if fnt.Face != "" || fnt.Size > 0 || fnt.Color != "" {
if run.Font == nil {
run.Font = &Font{}
}
run.Font.Family = fnt.Face
run.Font.Size = float64(fnt.Size / 20)
run.Font.Color = fnt.Color
}
runs = append(runs, run)
}
return runs
}
// AddHeaderFooterImage provides a mechanism to set the graphics that can be
// referenced in the header and footer definitions via &G, supported image
// types: EMF, EMZ, GIF, ICO, JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ.
//
// The extension should be provided with a "." in front, e.g. ".png".
// The width and height should have units in them, e.g. "100pt".
func (f *File) AddHeaderFooterImage(sheet string, opts *HeaderFooterImageOptions) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ext, ok := supportedImageTypes[strings.ToLower(opts.Extension)]
if !ok {
return ErrImgExt
}
sheetID := f.getSheetID(sheet)
vmlID := f.countVMLDrawing() + 1
drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
if ws.LegacyDrawingHF != nil {
// The worksheet already has a VML relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawingHF.RID)
vmlID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
drawingVML = strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl")
} else {
// Add first VML drawing for given sheet.
rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
f.addSheetNameSpace(sheet, SourceRelationship)
f.addSheetLegacyDrawingHF(sheet, rID)
}
shapeID := map[HeaderFooterImagePositionType]string{
HeaderFooterImagePositionLeft: "L",
HeaderFooterImagePositionCenter: "C",
HeaderFooterImagePositionRight: "R",
}[opts.Position] +
map[bool]string{false: "H", true: "F"}[opts.IsFooter] +
map[bool]string{false: "", true: "FIRST"}[opts.FirstPage]
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",
ShapeLayout: &xlsxShapeLayout{
Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: sheetID},
},
ShapeType: &xlsxShapeType{
ID: "_x0000_t75",
CoordSize: "21600,21600",
Spt: 75,
PreferRelative: "t",
Path: "m@4@5l@4@11@9@11@9@5xe",
Filled: "f",
Stroked: "f",
Stroke: &xlsxStroke{JoinStyle: "miter"},
VFormulas: &vFormulas{
Formulas: []vFormula{
{Equation: "if lineDrawn pixelLineWidth 0"},
{Equation: "sum @0 1 0"},
{Equation: "sum 0 0 @1"},
{Equation: "prod @2 1 2"},
{Equation: "prod @3 21600 pixelWidth"},
{Equation: "prod @3 21600 pixelHeight"},
{Equation: "sum @0 0 1"},
{Equation: "prod @6 1 2"},
{Equation: "prod @7 21600 pixelWidth"},
{Equation: "sum @8 21600 0"},
{Equation: "prod @7 21600 pixelHeight"},
{Equation: "sum @10 21600 0"},
},
},
VPath: &vPath{ExtrusionOK: "f", GradientShapeOK: "t", ConnectType: "rect"},
Lock: &oLock{Ext: "edit", AspectRatio: "t"},
},
}
// Load exist VML shapes from xl/drawings/vmlDrawing%d.vml
d, err := f.decodeVMLDrawingReader(drawingVML)
if err != nil {
return err
}
if d != nil {
vml.ShapeType.ID = d.ShapeType.ID
vml.ShapeType.CoordSize = d.ShapeType.CoordSize
vml.ShapeType.Spt = d.ShapeType.Spt
vml.ShapeType.PreferRelative = d.ShapeType.PreferRelative
vml.ShapeType.Path = d.ShapeType.Path
vml.ShapeType.Filled = d.ShapeType.Filled
vml.ShapeType.Stroked = d.ShapeType.Stroked
for _, v := range d.Shape {
s := xlsxShape{
ID: v.ID,
SpID: v.SpID,
Type: v.Type,
Style: v.Style,
Val: v.Val,
}
vml.Shape = append(vml.Shape, s)
}
}
}
for idx, shape := range vml.Shape {
if shape.ID == shapeID {
vml.Shape = append(vml.Shape[:idx], vml.Shape[idx+1:]...)
}
}
style := fmt.Sprintf("position:absolute;margin-left:0;margin-top:0;width:%s;height:%s;z-index:1", opts.Width, opts.Height)
drawingVMLRels := "xl/drawings/_rels/vmlDrawing" + strconv.Itoa(vmlID) + ".vml.rels"
mediaStr := ".." + strings.TrimPrefix(f.addMedia(opts.File, ext), "xl")
imageID := f.addRels(drawingVMLRels, SourceRelationshipImage, mediaStr, "")
shape := xlsxShape{
ID: shapeID,
SpID: "_x0000_s1025",
Type: "#_x0000_t75",
Style: style,
}
sp, _ := xml.Marshal(encodeShape{
ImageData: &vImageData{RelID: "rId" + strconv.Itoa(imageID)},
Lock: &oLock{Ext: "edit", Rotation: "t"},
})
shape.Val = string(sp[13 : len(sp)-14])
vml.Shape = append(vml.Shape, shape)
f.VMLDrawing[drawingVML] = vml
if err := f.setContentTypePartImageExtensions(); err != nil {
return err
}
return f.setContentTypePartVMLExtensions()
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/xurime/excelize.git
git@gitee.com:xurime/excelize.git
xurime
excelize
excelize
master

搜索帮助