mirror of
https://github.com/fumiama/go-docx.git
synced 2026-07-01 08:30:24 +08:00
add AddInlineDrawingFrom
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@ docxlib
|
|||||||
.vscode/
|
.vscode/
|
||||||
*.docx
|
*.docx
|
||||||
/*.xml
|
/*.xml
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@@ -1,7 +1,95 @@
|
|||||||
package docxlib
|
package docxlib
|
||||||
|
|
||||||
// AddDrawing adds drawing to paragraph
|
import (
|
||||||
func (p *Paragraph) AddDrawing(pic []byte) *Run {
|
"bytes"
|
||||||
//TODO: finish add drawing
|
"fmt"
|
||||||
return nil
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/fumiama/imgsz"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddInlineDrawing adds inline drawing to paragraph
|
||||||
|
func (p *Paragraph) AddInlineDrawing(pic []byte) (*Run, error) {
|
||||||
|
sz, format, err := imgsz.DecodeSize(bytes.NewReader(pic))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
id := strconv.Itoa(int(atomic.AddUintptr(&p.file.imageId, 1)))
|
||||||
|
rId := p.file.addImage(Media{Name: "image" + id + "." + format, Data: pic})
|
||||||
|
w, h := sz.Width, sz.Height
|
||||||
|
if float64(w)/float64(h) > 1.2 {
|
||||||
|
h = A4_EMU_MAX_WIDTH * h / w
|
||||||
|
w = A4_EMU_MAX_WIDTH
|
||||||
|
} else {
|
||||||
|
h = A4_EMU_MAX_WIDTH * h / w / 2
|
||||||
|
w = A4_EMU_MAX_WIDTH / 2
|
||||||
|
}
|
||||||
|
d := &Drawing{
|
||||||
|
Inline: &WPInline{
|
||||||
|
AnchorID: fmt.Sprintf("%08X", rand.Uint32()),
|
||||||
|
EditID: fmt.Sprintf("%08X", rand.Uint32()),
|
||||||
|
|
||||||
|
Extent: &WPExtent{
|
||||||
|
CX: w,
|
||||||
|
CY: h,
|
||||||
|
},
|
||||||
|
EffectExtent: &WPEffectExtent{},
|
||||||
|
DocPr: &WPDocPr{
|
||||||
|
ID: id,
|
||||||
|
Name: "图片 " + id,
|
||||||
|
},
|
||||||
|
CNvGraphicFramePr: &WPCNvGraphicFramePr{
|
||||||
|
Locks: &AGraphicFrameLocks{
|
||||||
|
NoChangeAspect: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Graphic: &AGraphic{
|
||||||
|
GraphicData: &AGraphicData{
|
||||||
|
URI: XMLNS_PICTURE,
|
||||||
|
Pic: &PICPic{
|
||||||
|
NonVisualPicProperties: &PICNonVisualPicProperties{
|
||||||
|
NonVisualDrawingProperties: PICNonVisualDrawingProperties{
|
||||||
|
ID: id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BlipFill: &PICBlipFill{
|
||||||
|
Blip: ABlip{
|
||||||
|
Embed: rId,
|
||||||
|
Cstate: "print",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SpPr: &PICSpPr{
|
||||||
|
Xfrm: AXfrm{
|
||||||
|
Ext: AExt{
|
||||||
|
CX: w,
|
||||||
|
CY: h,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PrstGeom: APrstGeom{
|
||||||
|
Prst: "rect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
run := &Run{
|
||||||
|
Drawing: d,
|
||||||
|
RunProperties: &RunProperties{},
|
||||||
|
}
|
||||||
|
p.Children = append(p.Children, ParagraphChild{Run: run})
|
||||||
|
return run, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddInlineDrawingFrom adds drawing from file to paragraph
|
||||||
|
func (p *Paragraph) AddInlineDrawingFrom(file string) (*Run, error) {
|
||||||
|
data, err := os.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p.AddInlineDrawing(data)
|
||||||
}
|
}
|
||||||
|
|||||||
19
apilink.go
19
apilink.go
@@ -1,28 +1,9 @@
|
|||||||
package docxlib
|
package docxlib
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HYPERLINK_STYLE = "a1"
|
HYPERLINK_STYLE = "a1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// when adding an hyperlink we need to store a reference in the relationship field
|
|
||||||
func (f *Docx) addLinkRelation(link string) string {
|
|
||||||
rel := &Relationship{
|
|
||||||
ID: "rId" + strconv.Itoa(int(atomic.AddUintptr(&f.rId, 1))),
|
|
||||||
Type: REL_HYPERLINK,
|
|
||||||
Target: link,
|
|
||||||
TargetMode: REL_TARGETMODE,
|
|
||||||
}
|
|
||||||
|
|
||||||
f.DocRelation.Relationships = append(f.DocRelation.Relationships, rel)
|
|
||||||
|
|
||||||
return rel.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddLink adds an hyperlink to paragraph
|
// AddLink adds an hyperlink to paragraph
|
||||||
func (p *Paragraph) AddLink(text string, link string) *Hyperlink {
|
func (p *Paragraph) AddLink(text string, link string) *Hyperlink {
|
||||||
rId := p.file.addLinkRelation(link)
|
rId := p.file.addLinkRelation(link)
|
||||||
|
|||||||
29
apitext.go
29
apitext.go
@@ -1,7 +1,5 @@
|
|||||||
package docxlib
|
package docxlib
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// AddText adds text to paragraph
|
// AddText adds text to paragraph
|
||||||
func (p *Paragraph) AddText(text string) *Run {
|
func (p *Paragraph) AddText(text string) *Run {
|
||||||
t := &Text{
|
t := &Text{
|
||||||
@@ -17,30 +15,3 @@ func (p *Paragraph) AddText(text string) *Run {
|
|||||||
|
|
||||||
return run
|
return run
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Paragraph) String() string {
|
|
||||||
sb := strings.Builder{}
|
|
||||||
for _, c := range p.Children {
|
|
||||||
switch {
|
|
||||||
case c.Link != nil:
|
|
||||||
id := c.Link.ID
|
|
||||||
text := c.Link.Run.InstrText
|
|
||||||
link, err := p.file.Refer(id)
|
|
||||||
sb.WriteString(text)
|
|
||||||
sb.WriteByte('(')
|
|
||||||
if err != nil {
|
|
||||||
sb.WriteString(id)
|
|
||||||
} else {
|
|
||||||
sb.WriteString(link)
|
|
||||||
}
|
|
||||||
sb.WriteByte(')')
|
|
||||||
case c.Run != nil:
|
|
||||||
sb.WriteString("run") //TODO: implement
|
|
||||||
case c.Properties != nil:
|
|
||||||
sb.WriteString("prop") //TODO: implement
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.String()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -39,13 +39,13 @@ func main() {
|
|||||||
fmt.Printf("\tWe've found a new run with the text ->%s\n", child.Run.Text.Text)
|
fmt.Printf("\tWe've found a new run with the text ->%s\n", child.Run.Text.Text)
|
||||||
}
|
}
|
||||||
if child.Run.Drawing != nil {
|
if child.Run.Drawing != nil {
|
||||||
fmt.Printf("\tWe've found a new run with the drawing ->%s\n", child.Run.Drawing.Inline.DistT) // TODO: replace to refid
|
fmt.Printf("\tWe've found a new run with the drawing ->%d\n", child.Run.Drawing.Inline.DistT) // TODO: replace to refid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if child.Link != nil {
|
if child.Link != nil {
|
||||||
id := child.Link.ID
|
id := child.Link.ID
|
||||||
text := child.Link.Run.InstrText
|
text := child.Link.Run.InstrText
|
||||||
link, err := doc.Refer(id)
|
link, err := doc.ReferHref(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("\tWe found a link with id %s and text %s without target\n", id, text)
|
fmt.Printf("\tWe found a link with id %s and text %s without target\n", id, text)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -31,6 +31,17 @@ func main() {
|
|||||||
nextPara := w.AddParagraph()
|
nextPara := w.AddParagraph()
|
||||||
nextPara.AddLink("google", `http://google.com`)
|
nextPara.AddLink("google", `http://google.com`)
|
||||||
|
|
||||||
|
para3 := w.AddParagraph()
|
||||||
|
// add text
|
||||||
|
para3.AddText("直接粘贴 inline")
|
||||||
|
|
||||||
|
para4 := w.AddParagraph()
|
||||||
|
para4.AddInlineDrawingFrom("testdata/fumiama.JPG")
|
||||||
|
para4.AddInlineDrawingFrom("testdata/fumiama2x.webp")
|
||||||
|
|
||||||
|
para5 := w.AddParagraph()
|
||||||
|
para5.AddInlineDrawingFrom("testdata/fumiamayoko.png")
|
||||||
|
|
||||||
f, err := os.Create(*fileLocation)
|
f, err := os.Create(*fileLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -66,13 +77,13 @@ func main() {
|
|||||||
fmt.Printf("\tWe've found a new run with the text ->%s\n", child.Run.Text.Text)
|
fmt.Printf("\tWe've found a new run with the text ->%s\n", child.Run.Text.Text)
|
||||||
}
|
}
|
||||||
if child.Run.Drawing != nil {
|
if child.Run.Drawing != nil {
|
||||||
fmt.Printf("\tWe've found a new run with the drawing ->%s\n", child.Run.Drawing.Inline.DistT) // TODO: replace to refid
|
fmt.Printf("\tWe've found a new run with the drawing ->%d\n", child.Run.Drawing.Inline.DistT) // TODO: replace to refid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if child.Link != nil {
|
if child.Link != nil {
|
||||||
id := child.Link.ID
|
id := child.Link.ID
|
||||||
text := child.Link.Run.InstrText
|
text := child.Link.Run.InstrText
|
||||||
link, err := doc.Refer(id)
|
link, err := doc.ReferHref(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("\tWe found a link with id %s and text %s without target\n", id, text)
|
fmt.Printf("\tWe found a link with id %s and text %s without target\n", id, text)
|
||||||
} else {
|
} else {
|
||||||
@@ -83,7 +94,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
fmt.Print("End of paragraph\n\n")
|
fmt.Print("End of paragraph\n\n")
|
||||||
}
|
}
|
||||||
f, err = os.Create("tmp.docx")
|
f, err = os.Create("unmarshal_" + *fileLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
16
docxlib.go
16
docxlib.go
@@ -18,7 +18,11 @@ type Docx struct {
|
|||||||
Document Document
|
Document Document
|
||||||
DocRelation Relationships
|
DocRelation Relationships
|
||||||
|
|
||||||
|
media []Media
|
||||||
|
mediaNameIdx map[string]int
|
||||||
|
|
||||||
rId uintptr
|
rId uintptr
|
||||||
|
imageId uintptr
|
||||||
|
|
||||||
buf *bytes.Buffer
|
buf *bytes.Buffer
|
||||||
isbufempty bool
|
isbufempty bool
|
||||||
@@ -93,15 +97,3 @@ func (f *Docx) Read(p []byte) (n int, err error) {
|
|||||||
f.isbufempty = false
|
f.isbufempty = false
|
||||||
return f.buf.Read(p)
|
return f.buf.Read(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refer gets the url for a reference
|
|
||||||
func (f *Docx) Refer(id string) (href string, err error) {
|
|
||||||
for _, a := range f.DocRelation.Relationships {
|
|
||||||
if a.ID == id {
|
|
||||||
href = a.Target
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = ErrRefIDNotFound
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|||||||
2
empty.go
2
empty.go
@@ -42,6 +42,8 @@ func newEmptyFile() *Docx {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
media: make([]Media, 0, 64),
|
||||||
|
mediaNameIdx: make(map[string]int, 64),
|
||||||
rId: 3,
|
rId: 3,
|
||||||
buf: bytes.NewBuffer(make([]byte, 0, 1024*1024*4)),
|
buf: bytes.NewBuffer(make([]byte, 0, 1024*1024*4)),
|
||||||
}
|
}
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -1,3 +1,5 @@
|
|||||||
module github.com/fumiama/docxlib
|
module github.com/fumiama/docxlib
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
|
require github.com/fumiama/imgsz v0.0.2
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -0,0 +1,2 @@
|
|||||||
|
github.com/fumiama/imgsz v0.0.2 h1:fAkC0FnIscdKOXwAxlyw3EUba5NzxZdSxGaq3Uyfxak=
|
||||||
|
github.com/fumiama/imgsz v0.0.2/go.mod h1:dR71mI3I2O5u6+PCpd47M9TZptzP+39tRBcbdIkoqM4=
|
||||||
|
|||||||
7
image.go
Normal file
7
image.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package docxlib
|
||||||
|
|
||||||
|
// addImage add image to docx and return its rId
|
||||||
|
func (f *Docx) addImage(m Media) string {
|
||||||
|
f.addMedia(m)
|
||||||
|
return f.addImageRelation(m)
|
||||||
|
}
|
||||||
51
link.go
Normal file
51
link.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package docxlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// when adding an hyperlink we need to store a reference in the relationship field
|
||||||
|
//
|
||||||
|
// this func is not thread-safe
|
||||||
|
func (f *Docx) addLinkRelation(link string) string {
|
||||||
|
rel := &Relationship{
|
||||||
|
ID: "rId" + strconv.Itoa(int(atomic.AddUintptr(&f.rId, 1))),
|
||||||
|
Type: REL_HYPERLINK,
|
||||||
|
Target: link,
|
||||||
|
TargetMode: REL_TARGETMODE,
|
||||||
|
}
|
||||||
|
|
||||||
|
f.DocRelation.Relationships = append(f.DocRelation.Relationships, rel)
|
||||||
|
|
||||||
|
return rel.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// when adding an image we need to store a reference in the relationship field
|
||||||
|
//
|
||||||
|
// this func is not thread-safe
|
||||||
|
func (f *Docx) addImageRelation(m Media) string {
|
||||||
|
rel := &Relationship{
|
||||||
|
ID: "rId" + strconv.Itoa(int(atomic.AddUintptr(&f.rId, 1))),
|
||||||
|
Type: REL_IMAGE,
|
||||||
|
Target: "media/" + m.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
f.DocRelation.Relationships = append(f.DocRelation.Relationships, rel)
|
||||||
|
|
||||||
|
return rel.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReferHref gets the url for a reference
|
||||||
|
func (f *Docx) ReferHref(id string) (href string, err error) {
|
||||||
|
f.DocRelation.mu.RLock()
|
||||||
|
defer f.DocRelation.mu.RUnlock()
|
||||||
|
for _, a := range f.DocRelation.Relationships {
|
||||||
|
if a.ID == id {
|
||||||
|
href = a.Target
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = ErrRefIDNotFound
|
||||||
|
return
|
||||||
|
}
|
||||||
29
media.go
Normal file
29
media.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package docxlib
|
||||||
|
|
||||||
|
const MEDIA_FOLDER = `word/media/`
|
||||||
|
|
||||||
|
// Media is in word/media
|
||||||
|
type Media struct {
|
||||||
|
Name string // Name is for word/media/Name
|
||||||
|
Data []byte // Data is data of this media
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is the full path of the media
|
||||||
|
func (m *Media) String() string {
|
||||||
|
return MEDIA_FOLDER + m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Media get media struct pointer (or nil on notfound) by name
|
||||||
|
func (f *Docx) Media(name string) *Media {
|
||||||
|
i, ok := f.mediaNameIdx[name]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &f.media[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// addMedia append the media to docx's media list
|
||||||
|
func (f *Docx) addMedia(m Media) {
|
||||||
|
f.mediaNameIdx[m.Name] = len(f.media)
|
||||||
|
f.media = append(f.media, m)
|
||||||
|
}
|
||||||
7
pack.go
7
pack.go
@@ -2,6 +2,7 @@ package docxlib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -27,9 +28,13 @@ func (f *Docx) pack(zipWriter *zip.Writer) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
files["word/_rels/document.xml.rels"] = marshaller{data: f.DocRelation}
|
files["word/_rels/document.xml.rels"] = marshaller{data: &f.DocRelation}
|
||||||
files["word/document.xml"] = marshaller{data: f.Document}
|
files["word/document.xml"] = marshaller{data: f.Document}
|
||||||
|
|
||||||
|
for _, m := range f.media {
|
||||||
|
files[m.String()] = bytes.NewReader(m.Data)
|
||||||
|
}
|
||||||
|
|
||||||
for path, r := range files {
|
for path, r := range files {
|
||||||
w, err := zipWriter.Create(path)
|
w, err := zipWriter.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ const (
|
|||||||
XMLNS_R = `http://schemas.openxmlformats.org/officeDocument/2006/relationships`
|
XMLNS_R = `http://schemas.openxmlformats.org/officeDocument/2006/relationships`
|
||||||
XMLNS_WP = `http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing`
|
XMLNS_WP = `http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing`
|
||||||
XMLNS_WP14 = `http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing`
|
XMLNS_WP14 = `http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing`
|
||||||
|
|
||||||
|
XMLNS_PICTURE = `http://schemas.openxmlformats.org/drawingml/2006/picture`
|
||||||
)
|
)
|
||||||
|
|
||||||
func getAtt(atts []xml.Attr, name string) string {
|
func getAtt(atts []xml.Attr, name string) string {
|
||||||
@@ -48,6 +50,9 @@ func (doc *Document) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
|
|||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
switch tt := t.(type) {
|
switch tt := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package docxlib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"hash/crc64"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -113,7 +114,7 @@ const drawing_doc = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|||||||
<w:noProof/>
|
<w:noProof/>
|
||||||
</w:rPr>
|
</w:rPr>
|
||||||
<w:drawing>
|
<w:drawing>
|
||||||
<wp:inline distT="T-mock-inline-p1-c0" distB="B-mock-inline-p1-c0" distL="L-mock-inline-p1-c0" distR="R-mock-inline-p1-c0" wp14:anchorId="mock-anchor-p1-c0" wp14:editId="mock-edit-p1-c0">
|
<wp:inline distT="0" distB="0" distL="0" distR="0" wp14:anchorId="mock-anchor-p1-c0" wp14:editId="mock-edit-p1-c0">
|
||||||
<wp:extent cx="5274310" cy="3369310"/>
|
<wp:extent cx="5274310" cy="3369310"/>
|
||||||
<wp:effectExtent l="0" t="0" r="0" b="0"/>
|
<wp:effectExtent l="0" t="0" r="0" b="0"/>
|
||||||
<wp:docPr id="1" name="图片 1"/>
|
<wp:docPr id="1" name="图片 1"/>
|
||||||
@@ -177,7 +178,7 @@ const drawing_doc = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|||||||
<w:noProof/>
|
<w:noProof/>
|
||||||
</w:rPr>
|
</w:rPr>
|
||||||
<w:drawing>
|
<w:drawing>
|
||||||
<wp:inline distT="T-mock-inline-p3-c0" distB="B-mock-inline-p3-c0" distL="L-mock-inline-p3-c0" distR="R-mock-inline-p3-c0" wp14:anchorId="mock-anchor-p3-c0" wp14:editId="mock-edit-p3-c0">
|
<wp:inline distT="0" distB="0" distL="0" distR="0" wp14:anchorId="mock-anchor-p3-c0" wp14:editId="mock-edit-p3-c0">
|
||||||
<wp:extent cx="2339163" cy="1494293"/>
|
<wp:extent cx="2339163" cy="1494293"/>
|
||||||
<wp:effectExtent l="0" t="0" r="0" b="4445"/>
|
<wp:effectExtent l="0" t="0" r="0" b="4445"/>
|
||||||
<wp:docPr id="2" name="图片 2"/>
|
<wp:docPr id="2" name="图片 2"/>
|
||||||
@@ -223,7 +224,7 @@ const drawing_doc = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|||||||
<w:noProof/>
|
<w:noProof/>
|
||||||
</w:rPr>
|
</w:rPr>
|
||||||
<w:drawing>
|
<w:drawing>
|
||||||
<wp:inline distT="T-mock-inline-p3-c1" distB="B-mock-inline-p3-c1" distL="L-mock-inline-p3-c1" distR="R-mock-inline-p3-c1" wp14:anchorId="mock-anchor-p3-c1" wp14:editId="mock-edit-p3-c1">
|
<wp:inline distT="0" distB="0" distL="0" distR="0" wp14:anchorId="mock-anchor-p3-c1" wp14:editId="mock-edit-p3-c1">
|
||||||
<wp:extent cx="2339163" cy="1494293"/>
|
<wp:extent cx="2339163" cy="1494293"/>
|
||||||
<wp:effectExtent l="0" t="0" r="0" b="4445"/>
|
<wp:effectExtent l="0" t="0" r="0" b="4445"/>
|
||||||
<wp:docPr id="4" name="图片 4"/>
|
<wp:docPr id="4" name="图片 4"/>
|
||||||
@@ -290,7 +291,7 @@ const drawing_doc = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|||||||
<mc:AlternateContent>
|
<mc:AlternateContent>
|
||||||
<mc:Choice Requires="wpg">
|
<mc:Choice Requires="wpg">
|
||||||
<w:drawing>
|
<w:drawing>
|
||||||
<wp:inline distT="T-mock-inline-p5-c0" distB="B-mock-inline-p5-c0" distL="L-mock-inline-p5-c0" distR="R-mock-inline-p5-c0" wp14:anchorId="mock-anchor-p5-c0" wp14:editId="mock-edit-p5-c0">
|
<wp:inline distT="0" distB="0" distL="0" distR="0" wp14:anchorId="mock-anchor-p5-c0" wp14:editId="mock-edit-p5-c0">
|
||||||
<wp:extent cx="4677868" cy="1494155"/>
|
<wp:extent cx="4677868" cy="1494155"/>
|
||||||
<wp:effectExtent l="0" t="0" r="0" b="4445"/>
|
<wp:effectExtent l="0" t="0" r="0" b="4445"/>
|
||||||
<wp:docPr id="7" name="组合 7"/>
|
<wp:docPr id="7" name="组合 7"/>
|
||||||
@@ -545,21 +546,8 @@ func TestUnmarshalDrawingStructure(t *testing.T) {
|
|||||||
if child.Run != nil && child.Run.Drawing != nil {
|
if child.Run != nil && child.Run.Drawing != nil {
|
||||||
t.Log("fild drawing at aragraph", i, ", child", j)
|
t.Log("fild drawing at aragraph", i, ", child", j)
|
||||||
if child.Run.Drawing.Inline != nil {
|
if child.Run.Drawing.Inline != nil {
|
||||||
tail := "-mock-inline-p" + string(rune('0'+i)) + "-c" + string(rune('0'+j))
|
|
||||||
anchor := "mock-anchor-p" + string(rune('0'+i)) + "-c" + string(rune('0'+j))
|
anchor := "mock-anchor-p" + string(rune('0'+i)) + "-c" + string(rune('0'+j))
|
||||||
edit := "mock-edit-p" + string(rune('0'+i)) + "-c" + string(rune('0'+j))
|
edit := "mock-edit-p" + string(rune('0'+i)) + "-c" + string(rune('0'+j))
|
||||||
if "T"+tail != child.Run.Drawing.Inline.DistT {
|
|
||||||
t.Fatal("expect", "T"+tail, "but got", child.Run.Drawing.Inline.DistT)
|
|
||||||
}
|
|
||||||
if "B"+tail != child.Run.Drawing.Inline.DistB {
|
|
||||||
t.Fatal("expect", "B"+tail, "but got", child.Run.Drawing.Inline.DistB)
|
|
||||||
}
|
|
||||||
if "L"+tail != child.Run.Drawing.Inline.DistL {
|
|
||||||
t.Fatal("expect", "L"+tail, "but got", child.Run.Drawing.Inline.DistL)
|
|
||||||
}
|
|
||||||
if "R"+tail != child.Run.Drawing.Inline.DistR {
|
|
||||||
t.Fatal("expect", "R"+tail, "but got", child.Run.Drawing.Inline.DistR)
|
|
||||||
}
|
|
||||||
if anchor != child.Run.Drawing.Inline.AnchorID {
|
if anchor != child.Run.Drawing.Inline.AnchorID {
|
||||||
t.Fatal("expect", anchor, "but got", child.Run.Drawing.Inline.AnchorID)
|
t.Fatal("expect", anchor, "but got", child.Run.Drawing.Inline.AnchorID)
|
||||||
}
|
}
|
||||||
@@ -577,7 +565,6 @@ func TestUnmarshalDrawingStructure(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.Fail()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarshalDrawingStructure(t *testing.T) {
|
func TestMarshalDrawingStructure(t *testing.T) {
|
||||||
@@ -588,14 +575,13 @@ func TestMarshalDrawingStructure(t *testing.T) {
|
|||||||
para1.AddText("直接粘贴 inline")
|
para1.AddText("直接粘贴 inline")
|
||||||
|
|
||||||
para2 := w.AddParagraph()
|
para2 := w.AddParagraph()
|
||||||
para2.AddText("test font size and color").Size("44").Color("ff0000")
|
para2.AddInlineDrawingFrom("testdata/fumiama.JPG")
|
||||||
para2.AddText("test font size and color").Size("44").Color("ff0000")
|
para2.AddInlineDrawingFrom("testdata/fumiama2x.webp")
|
||||||
para2.AddText("test font size and color").Size("44").Color("ff0000")
|
|
||||||
|
|
||||||
nextPara := w.AddParagraph()
|
para3 := w.AddParagraph()
|
||||||
nextPara.AddLink("google", `http://google.com`)
|
para3.AddInlineDrawingFrom("testdata/fumiamayoko.png")
|
||||||
|
|
||||||
f, err := os.Create("test.xml")
|
f, err := os.Create("TestMarshalDrawingStructure_Marshal.xml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -613,7 +599,7 @@ func TestMarshalDrawingStructure(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
f1, err := os.Create("test1.xml")
|
f1, err := os.Create("TestMarshalDrawingStructure_Unmarshal.xml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -622,5 +608,30 @@ func TestMarshalDrawingStructure(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
_, err = f.Seek(0, io.SeekStart)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = f1.Seek(0, io.SeekStart)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
h := crc64.New(crc64.MakeTable(crc64.ECMA))
|
||||||
|
_, err = io.Copy(h, f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
md51 := h.Sum64()
|
||||||
|
h.Reset()
|
||||||
|
_, err = io.Copy(h, f1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
md52 := h.Sum64()
|
||||||
|
if md51 != md52 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
|
} /* else {
|
||||||
|
_ = os.Remove("TestMarshalDrawingStructure_Marshal.xml")
|
||||||
|
_ = os.Remove("TestMarshalDrawingStructure_Unmarshal.xml")
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
|
|||||||
319
structdrawing.go
319
structdrawing.go
@@ -3,6 +3,12 @@ package docxlib
|
|||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"io"
|
"io"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// A4_EMU_MAX_WIDTH is the max display width of an A4 paper
|
||||||
|
A4_EMU_MAX_WIDTH = 5274310
|
||||||
)
|
)
|
||||||
|
|
||||||
// Drawing element contains photos
|
// Drawing element contains photos
|
||||||
@@ -17,16 +23,31 @@ func (r *Drawing) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
switch tt := t.(type) {
|
switch tt := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
switch tt.Name.Local {
|
switch tt.Name.Local {
|
||||||
case "inline":
|
case "inline":
|
||||||
r.Inline = new(WPInline)
|
r.Inline = new(WPInline)
|
||||||
r.Inline.DistT = getAtt(tt.Attr, "distT")
|
r.Inline.DistT, err = strconv.Atoi(getAtt(tt.Attr, "distT"))
|
||||||
r.Inline.DistB = getAtt(tt.Attr, "distB")
|
if err != nil {
|
||||||
r.Inline.DistL = getAtt(tt.Attr, "distL")
|
return err
|
||||||
r.Inline.DistR = getAtt(tt.Attr, "distR")
|
}
|
||||||
|
r.Inline.DistB, err = strconv.Atoi(getAtt(tt.Attr, "distB"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Inline.DistL, err = strconv.Atoi(getAtt(tt.Attr, "distL"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Inline.DistR, err = strconv.Atoi(getAtt(tt.Attr, "distR"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
r.Inline.AnchorID = getAtt(tt.Attr, "anchorId")
|
r.Inline.AnchorID = getAtt(tt.Attr, "anchorId")
|
||||||
r.Inline.EditID = getAtt(tt.Attr, "editId")
|
r.Inline.EditID = getAtt(tt.Attr, "editId")
|
||||||
d.DecodeElement(r.Inline, &start)
|
d.DecodeElement(r.Inline, &start)
|
||||||
@@ -43,16 +64,17 @@ func (r *Drawing) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
// WPInline wp:inline
|
// WPInline wp:inline
|
||||||
type WPInline struct {
|
type WPInline struct {
|
||||||
XMLName xml.Name `xml:"wp:inline,omitempty"`
|
XMLName xml.Name `xml:"wp:inline,omitempty"`
|
||||||
DistT string `xml:"distT,attr"`
|
DistT int `xml:"distT,attr"`
|
||||||
DistB string `xml:"distB,attr"`
|
DistB int `xml:"distB,attr"`
|
||||||
DistL string `xml:"distL,attr"`
|
DistL int `xml:"distL,attr"`
|
||||||
DistR string `xml:"distR,attr"`
|
DistR int `xml:"distR,attr"`
|
||||||
AnchorID string `xml:"wp14:anchorId,attr"`
|
AnchorID string `xml:"wp14:anchorId,attr,omitempty"`
|
||||||
EditID string `xml:"wp14:editId,attr"`
|
EditID string `xml:"wp14:editId,attr,omitempty"`
|
||||||
|
|
||||||
Extent *WPExtent
|
Extent *WPExtent
|
||||||
EffectExtent *WPEffectExtent
|
EffectExtent *WPEffectExtent
|
||||||
DocPr *WPDocPr
|
DocPr *WPDocPr
|
||||||
|
CNvGraphicFramePr *WPCNvGraphicFramePr
|
||||||
Graphic *AGraphic
|
Graphic *AGraphic
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,26 +84,51 @@ func (r *WPInline) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
switch tt := t.(type) {
|
switch tt := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
switch tt.Name.Local {
|
switch tt.Name.Local {
|
||||||
case "extent":
|
case "extent":
|
||||||
r.Extent = new(WPExtent)
|
r.Extent = new(WPExtent)
|
||||||
r.Extent.CX = getAtt(tt.Attr, "cx")
|
r.Extent.CX, err = strconv.Atoi(getAtt(tt.Attr, "cx"))
|
||||||
r.Extent.CY = getAtt(tt.Attr, "cy")
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Extent.CY, err = strconv.Atoi(getAtt(tt.Attr, "cy"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case "effectExtent":
|
case "effectExtent":
|
||||||
r.EffectExtent = new(WPEffectExtent)
|
r.EffectExtent = new(WPEffectExtent)
|
||||||
r.EffectExtent.L = getAtt(tt.Attr, "l")
|
r.EffectExtent.L, err = strconv.Atoi(getAtt(tt.Attr, "l"))
|
||||||
r.EffectExtent.T = getAtt(tt.Attr, "t")
|
if err != nil {
|
||||||
r.EffectExtent.R = getAtt(tt.Attr, "r")
|
return err
|
||||||
r.EffectExtent.B = getAtt(tt.Attr, "b")
|
}
|
||||||
|
r.EffectExtent.T, err = strconv.Atoi(getAtt(tt.Attr, "t"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.EffectExtent.R, err = strconv.Atoi(getAtt(tt.Attr, "r"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.EffectExtent.B, err = strconv.Atoi(getAtt(tt.Attr, "b"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case "docPr":
|
case "docPr":
|
||||||
r.DocPr = new(WPDocPr)
|
r.DocPr = new(WPDocPr)
|
||||||
r.DocPr.ID = getAtt(tt.Attr, "id")
|
r.DocPr.ID = getAtt(tt.Attr, "id")
|
||||||
r.DocPr.Name = getAtt(tt.Attr, "name")
|
r.DocPr.Name = getAtt(tt.Attr, "name")
|
||||||
r.DocPr.Macro = getAtt(tt.Attr, "macro")
|
r.DocPr.Macro = getAtt(tt.Attr, "macro")
|
||||||
r.DocPr.Hidden = getAtt(tt.Attr, "hidden")
|
r.DocPr.Hidden = getAtt(tt.Attr, "hidden")
|
||||||
|
case "cNvGraphicFramePr":
|
||||||
|
var value WPCNvGraphicFramePr
|
||||||
|
d.DecodeElement(&value, &start)
|
||||||
|
r.CNvGraphicFramePr = &value
|
||||||
case "graphic":
|
case "graphic":
|
||||||
var value AGraphic
|
var value AGraphic
|
||||||
d.DecodeElement(&value, &start)
|
d.DecodeElement(&value, &start)
|
||||||
@@ -97,19 +144,21 @@ func (r *WPInline) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WPExtent represents the extent of a drawing in a Word document.
|
// WPExtent represents the extent of a drawing in a Word document.
|
||||||
|
//
|
||||||
|
// CX CY 's unit is English Metric Units, which is 1/914400 inch
|
||||||
type WPExtent struct {
|
type WPExtent struct {
|
||||||
XMLName xml.Name `xml:"wp:extent,omitempty"`
|
XMLName xml.Name `xml:"wp:extent,omitempty"`
|
||||||
CX string `xml:"cx,attr"`
|
CX int `xml:"cx,attr"`
|
||||||
CY string `xml:"cy,attr"`
|
CY int `xml:"cy,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WPEffectExtent represents the effect extent of a drawing in a Word document.
|
// WPEffectExtent represents the effect extent of a drawing in a Word document.
|
||||||
type WPEffectExtent struct {
|
type WPEffectExtent struct {
|
||||||
XMLName xml.Name `xml:"wp:effectExtent,omitempty"`
|
XMLName xml.Name `xml:"wp:effectExtent,omitempty"`
|
||||||
L string `xml:"l,attr"`
|
L int `xml:"l,attr"`
|
||||||
T string `xml:"t,attr"`
|
T int `xml:"t,attr"`
|
||||||
R string `xml:"r,attr"`
|
R int `xml:"r,attr"`
|
||||||
B string `xml:"b,attr"`
|
B int `xml:"b,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WPDocPr represents the document properties of a drawing in a Word document.
|
// WPDocPr represents the document properties of a drawing in a Word document.
|
||||||
@@ -121,6 +170,48 @@ type WPDocPr struct {
|
|||||||
Hidden string `xml:"hidden,attr,omitempty"`
|
Hidden string `xml:"hidden,attr,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WPCNvGraphicFramePr represents the non-visual properties of a graphic frame.
|
||||||
|
type WPCNvGraphicFramePr struct {
|
||||||
|
XMLName xml.Name `xml:"wp:cNvGraphicFramePr,omitempty"`
|
||||||
|
Locks *AGraphicFrameLocks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WPCNvGraphicFramePr) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
for {
|
||||||
|
t, err := d.Token()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tt := t.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
switch tt.Name.Local {
|
||||||
|
case "graphicFrameLocks":
|
||||||
|
var value AGraphicFrameLocks
|
||||||
|
d.DecodeElement(&value, &start)
|
||||||
|
value.NoChangeAspect, err = strconv.Atoi(getAtt(tt.Attr, "noChangeAspect"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.Locks = &value
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AGraphicFrameLocks represents the locks applied to a graphic frame.
|
||||||
|
type AGraphicFrameLocks struct {
|
||||||
|
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main graphicFrameLocks,omitempty"`
|
||||||
|
NoChangeAspect int `xml:"noChangeAspect,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
// AGraphic represents a graphic in a Word document.
|
// AGraphic represents a graphic in a Word document.
|
||||||
type AGraphic struct {
|
type AGraphic struct {
|
||||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main graphic,omitempty"`
|
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main graphic,omitempty"`
|
||||||
@@ -133,6 +224,9 @@ func (a *AGraphic) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
switch tt := t.(type) {
|
switch tt := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
@@ -164,6 +258,9 @@ func (a *AGraphicData) UnmarshalXML(d *xml.Decoder, start xml.StartElement) erro
|
|||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
switch tt := t.(type) {
|
switch tt := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
@@ -186,7 +283,7 @@ type PICPic struct {
|
|||||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/picture pic,omitempty"`
|
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/picture pic,omitempty"`
|
||||||
NonVisualPicProperties *PICNonVisualPicProperties
|
NonVisualPicProperties *PICNonVisualPicProperties
|
||||||
BlipFill *PICBlipFill
|
BlipFill *PICBlipFill
|
||||||
// <pic:spPr> is unecessary
|
SpPr *PICSpPr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PICPic) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (p *PICPic) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
@@ -195,6 +292,9 @@ func (p *PICPic) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
switch tt := t.(type) {
|
switch tt := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
@@ -207,6 +307,10 @@ func (p *PICPic) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
var value PICBlipFill
|
var value PICBlipFill
|
||||||
d.DecodeElement(&value, &start)
|
d.DecodeElement(&value, &start)
|
||||||
p.BlipFill = &value
|
p.BlipFill = &value
|
||||||
|
case "spPr":
|
||||||
|
var value PICSpPr
|
||||||
|
d.DecodeElement(&value, &start)
|
||||||
|
p.SpPr = &value
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -228,6 +332,9 @@ func (p *PICNonVisualPicProperties) UnmarshalXML(d *xml.Decoder, start xml.Start
|
|||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
switch tt := t.(type) {
|
switch tt := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
@@ -253,6 +360,7 @@ type PICNonVisualDrawingProperties struct {
|
|||||||
type PICBlipFill struct {
|
type PICBlipFill struct {
|
||||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/picture blipFill,omitempty"`
|
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/picture blipFill,omitempty"`
|
||||||
Blip ABlip
|
Blip ABlip
|
||||||
|
Stretch AStretch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PICBlipFill) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (p *PICBlipFill) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
@@ -261,12 +369,18 @@ func (p *PICBlipFill) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
|
|||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
switch tt := t.(type) {
|
switch tt := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
switch tt.Name.Local {
|
switch tt.Name.Local {
|
||||||
case "blip":
|
case "blip":
|
||||||
p.Blip.Embed = getAtt(tt.Attr, "embed")
|
p.Blip.Embed = getAtt(tt.Attr, "embed")
|
||||||
|
p.Blip.Cstate = getAtt(tt.Attr, "cstate")
|
||||||
|
case "stretch":
|
||||||
|
d.DecodeElement(&p.Stretch, &start)
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -280,4 +394,163 @@ func (p *PICBlipFill) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
|
|||||||
type ABlip struct {
|
type ABlip struct {
|
||||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main blip,omitempty"`
|
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main blip,omitempty"`
|
||||||
Embed string `xml:"r:embed,attr"`
|
Embed string `xml:"r:embed,attr"`
|
||||||
|
Cstate string `xml:"cstate,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AStretch ...
|
||||||
|
type AStretch struct {
|
||||||
|
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main stretch,omitempty"`
|
||||||
|
FillRect AFillRect
|
||||||
|
}
|
||||||
|
|
||||||
|
// AFillRect ...
|
||||||
|
type AFillRect struct {
|
||||||
|
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main fillRect,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PICSpPr is a struct representing the <pic:spPr> element in OpenXML,
|
||||||
|
// which describes the shape properties for a picture.
|
||||||
|
type PICSpPr struct {
|
||||||
|
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/picture spPr,omitempty"`
|
||||||
|
Xfrm AXfrm
|
||||||
|
PrstGeom APrstGeom
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PICSpPr) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
for {
|
||||||
|
t, err := d.Token()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tt := t.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
switch tt.Name.Local {
|
||||||
|
case "xfrm":
|
||||||
|
d.DecodeElement(&p.Xfrm, &start)
|
||||||
|
case "prstGeom":
|
||||||
|
d.DecodeElement(&p.PrstGeom, &start)
|
||||||
|
p.PrstGeom.Prst = getAtt(tt.Attr, "prst")
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AXfrm is a struct representing the <a:xfrm> element in OpenXML,
|
||||||
|
// which describes the position and size of a shape.
|
||||||
|
type AXfrm struct {
|
||||||
|
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main xfrm,omitempty"`
|
||||||
|
Off AOff
|
||||||
|
Ext AExt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AXfrm) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
for {
|
||||||
|
t, err := d.Token()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tt := t.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
switch tt.Name.Local {
|
||||||
|
case "off":
|
||||||
|
a.Off.X, err = strconv.Atoi(getAtt(tt.Attr, "x"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.Off.Y, err = strconv.Atoi(getAtt(tt.Attr, "y"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "ext":
|
||||||
|
a.Ext.CX, err = strconv.Atoi(getAtt(tt.Attr, "cx"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.Ext.CY, err = strconv.Atoi(getAtt(tt.Attr, "cy"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AOff is a struct representing the <a:off> element in OpenXML,
|
||||||
|
// which describes the offset of a shape from its original position.
|
||||||
|
type AOff struct {
|
||||||
|
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main off,omitempty"`
|
||||||
|
X int `xml:"x,attr"`
|
||||||
|
Y int `xml:"y,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AExt is a struct representing the <a:ext> element in OpenXML,
|
||||||
|
// which describes the size of a shape.
|
||||||
|
type AExt struct {
|
||||||
|
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main ext,omitempty"`
|
||||||
|
CX int `xml:"cx,attr"`
|
||||||
|
CY int `xml:"cy,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// APrstGeom is a struct representing the <a:prstGeom> element in OpenXML,
|
||||||
|
// which describes the preset shape geometry for a shape.
|
||||||
|
type APrstGeom struct {
|
||||||
|
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main prstGeom,omitempty"`
|
||||||
|
Prst string `xml:"prst,attr"`
|
||||||
|
AvLst AAvLst
|
||||||
|
}
|
||||||
|
|
||||||
|
// AAvLst is a struct representing the <a:avLst> element in OpenXML,
|
||||||
|
// which describes the adjustments to the shape's preset geometry.
|
||||||
|
type AAvLst struct {
|
||||||
|
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main avLst,omitempty"`
|
||||||
|
RawXML string `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AAvLst) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) {
|
||||||
|
var content []byte
|
||||||
|
|
||||||
|
if content, err = xml.Marshal(start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
t, err := d.Token()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if end, ok := t.(xml.EndElement); ok && end == start.End() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := xml.Marshal(t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
content = append(content, b...)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.RawXML = BytesToString(content)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ func (r *Hyperlink) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
switch tt := t.(type) {
|
switch tt := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package docxlib
|
|||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ParagraphChild struct {
|
type ParagraphChild struct {
|
||||||
@@ -18,6 +19,33 @@ type Paragraph struct {
|
|||||||
file *Docx
|
file *Docx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Paragraph) String() string {
|
||||||
|
sb := strings.Builder{}
|
||||||
|
for _, c := range p.Children {
|
||||||
|
switch {
|
||||||
|
case c.Link != nil:
|
||||||
|
id := c.Link.ID
|
||||||
|
text := c.Link.Run.InstrText
|
||||||
|
link, err := p.file.ReferHref(id)
|
||||||
|
sb.WriteString(text)
|
||||||
|
sb.WriteByte('(')
|
||||||
|
if err != nil {
|
||||||
|
sb.WriteString(id)
|
||||||
|
} else {
|
||||||
|
sb.WriteString(link)
|
||||||
|
}
|
||||||
|
sb.WriteByte(')')
|
||||||
|
case c.Run != nil:
|
||||||
|
sb.WriteString("run") //TODO: implement
|
||||||
|
case c.Properties != nil:
|
||||||
|
sb.WriteString("prop") //TODO: implement
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Paragraph) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
func (p *Paragraph) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||||
err := e.EncodeToken(start)
|
err := e.EncodeToken(start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -48,6 +76,9 @@ func (p *Paragraph) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
switch tt := t.(type) {
|
switch tt := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
var elem ParagraphChild
|
var elem ParagraphChild
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
package docxlib
|
package docxlib
|
||||||
|
|
||||||
import "encoding/xml"
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
XMLNS_REL = `http://schemas.openxmlformats.org/package/2006/relationships`
|
XMLNS_REL = `http://schemas.openxmlformats.org/package/2006/relationships`
|
||||||
REL_HYPERLINK = `http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink`
|
REL_HYPERLINK = `http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink`
|
||||||
|
REL_IMAGE = `http://schemas.openxmlformats.org/officeDocument/2006/relationships/image`
|
||||||
|
|
||||||
REL_TARGETMODE = "External"
|
REL_TARGETMODE = "External"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Relationships struct {
|
type Relationships struct {
|
||||||
|
mu sync.RWMutex
|
||||||
XMLName xml.Name `xml:"Relationships"`
|
XMLName xml.Name `xml:"Relationships"`
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
Relationships []*Relationship `xml:"Relationship"`
|
Relationships []*Relationship `xml:"Relationship"`
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ func (r *Run) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
switch tt := t.(type) {
|
switch tt := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
@@ -67,6 +70,9 @@ func (r *RunProperties) UnmarshalXML(d *xml.Decoder, start xml.StartElement) err
|
|||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
switch tt := t.(type) {
|
switch tt := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
// Text object contains the actual text
|
// Text object contains the actual text
|
||||||
type Text struct {
|
type Text struct {
|
||||||
XMLName xml.Name `xml:"w:t"`
|
XMLName xml.Name `xml:"w:t,omitempty"`
|
||||||
XMLSpace string `xml:"xml:space,attr,omitempty"`
|
XMLSpace string `xml:"xml:space,attr,omitempty"`
|
||||||
Text string `xml:",chardata"`
|
Text string `xml:",chardata"`
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,9 @@ func (r *Text) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
switch tt := t.(type) {
|
switch tt := t.(type) {
|
||||||
case xml.CharData:
|
case xml.CharData:
|
||||||
|
|||||||
BIN
testdata/fumiama.JPG
vendored
Normal file
BIN
testdata/fumiama.JPG
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
BIN
testdata/fumiama2x.webp
vendored
Normal file
BIN
testdata/fumiama2x.webp
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 478 KiB |
BIN
testdata/fumiamayoko.png
vendored
Normal file
BIN
testdata/fumiamayoko.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 666 KiB |
27
unpack.go
27
unpack.go
@@ -4,6 +4,8 @@ import (
|
|||||||
"archive/zip"
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// unpack receives a zip file (word documents are a zip with multiple xml inside)
|
// unpack receives a zip file (word documents are a zip with multiple xml inside)
|
||||||
@@ -13,6 +15,7 @@ import (
|
|||||||
// 2. Relationships
|
// 2. Relationships
|
||||||
func unpack(zipReader *zip.Reader) (docx *Docx, err error) {
|
func unpack(zipReader *zip.Reader) (docx *Docx, err error) {
|
||||||
docx = new(Docx)
|
docx = new(Docx)
|
||||||
|
docx.mediaNameIdx = make(map[string]int, 64)
|
||||||
for _, f := range zipReader.File {
|
for _, f := range zipReader.File {
|
||||||
if f.Name == "word/_rels/document.xml.rels" {
|
if f.Name == "word/_rels/document.xml.rels" {
|
||||||
err = docx.parseDocRelation(f)
|
err = docx.parseDocRelation(f)
|
||||||
@@ -26,6 +29,10 @@ func unpack(zipReader *zip.Reader) (docx *Docx, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
err = docx.checkAndParseMedia(f)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
docx.buf = bytes.NewBuffer(make([]byte, 0, 1024*1024*4))
|
docx.buf = bytes.NewBuffer(make([]byte, 0, 1024*1024*4))
|
||||||
return
|
return
|
||||||
@@ -61,6 +68,24 @@ func (f *Docx) parseDocRelation(file *zip.File) error {
|
|||||||
defer zf.Close()
|
defer zf.Close()
|
||||||
|
|
||||||
f.DocRelation.Xmlns = XMLNS_R
|
f.DocRelation.Xmlns = XMLNS_R
|
||||||
//TODO: find last rId
|
//TODO: find last rId & imageId
|
||||||
return xml.NewDecoder(zf).Decode(&f.DocRelation)
|
return xml.NewDecoder(zf).Decode(&f.DocRelation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Docx) checkAndParseMedia(file *zip.File) error {
|
||||||
|
if !strings.HasPrefix(file.Name, MEDIA_FOLDER) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
name := file.Name[len(MEDIA_FOLDER):]
|
||||||
|
zf, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(zf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.mediaNameIdx[name] = len(f.media)
|
||||||
|
f.media = append(f.media, Media{Name: name, Data: data})
|
||||||
|
return zf.Close()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user