diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1f1b28 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +.idea/ +.vscode/ +.DS_Store +output/ + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out \ No newline at end of file diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..a0c415f --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 gingfrederik + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a0e774f --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# docx + +## Introduction +docx is a simple library to creating DOCX file in Go. \ No newline at end of file diff --git a/docx.go b/docx.go new file mode 100644 index 0000000..e1eaa0c --- /dev/null +++ b/docx.go @@ -0,0 +1,20 @@ +package docx + +import "encoding/xml" + +const ( + XMLNS_W = `http://schemas.openxmlformats.org/wordprocessingml/2006/main` + XMLNS_R = `http://schemas.openxmlformats.org/officeDocument/2006/relationships` +) + +type Document struct { + XMLName xml.Name `xml:"w:document"` + XMLW string `xml:"xmlns:w,attr"` + XMLR string `xml:"xmlns:r,attr"` + Body *Body +} + +type Body struct { + XMLName xml.Name `xml:"w:body"` + Paragraph []*Paragraph +} diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..c4e64bf --- /dev/null +++ b/example/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/gingfrederik/docx" +) + +func main() { + f := docx.NewFile() + // add new paragraph + para := f.AddParagraph() + // add text + para.AddText("test") + + para.AddText("test font size").Size(22) + para.AddText("test color").Color("808080") + para.AddText("test font size and color").Size(22).Color("121212") + + nextPara := f.AddParagraph() + nextPara.AddLink("google", `http://google.com`) + + f.Save("./test.docx") +} diff --git a/file.go b/file.go new file mode 100644 index 0000000..efadef0 --- /dev/null +++ b/file.go @@ -0,0 +1,146 @@ +package docx + +import ( + "archive/zip" + "encoding/xml" + "io" + "os" + "strconv" +) + +type File struct { + Document Document + DocRelation DocRelation + + rId int +} + +func NewFile() *File { + defaultRel := []*RelationShip{ + { + ID: "rId1", + Type: `http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles`, + Target: "styles.xml", + }, + { + ID: "rId2", + Type: `http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme`, + Target: "theme/theme1.xml", + }, + { + ID: "rId3", + Type: `http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable`, + Target: "fontTable.xml", + }, + } + + f := &File{ + Document: Document{ + XMLName: xml.Name{ + Space: "w", + }, + XMLW: XMLNS_W, + XMLR: XMLNS_R, + Body: &Body{ + XMLName: xml.Name{ + Space: "w", + }, + Paragraph: make([]*Paragraph, 0), + }, + }, + DocRelation: DocRelation{ + Xmlns: XMLNS, + Relationship: defaultRel, + }, + rId: 4, + } + + return f +} + +// Save save file to path +func (f *File) Save(path string) (err error) { + fzip, _ := os.Create(path) + defer fzip.Close() + + zipWriter := zip.NewWriter(fzip) + defer zipWriter.Close() + + return f.pack(zipWriter) +} + +func (f *File) Write(writer io.Writer) (err error) { + zipWriter := zip.NewWriter(writer) + defer zipWriter.Close() + + return f.pack(zipWriter) +} + +// AddParagraph add new paragraph +func (f *File) AddParagraph() *Paragraph { + p := &Paragraph{ + Data: make([]interface{}, 0), + file: f, + } + + f.Document.Body.Paragraph = append(f.Document.Body.Paragraph, p) + return p +} + +func (f *File) addLinkRelation(link string) string { + rel := &RelationShip{ + ID: "rId" + strconv.Itoa(f.rId), + Type: REL_HYPERLINK, + Target: link, + TargetMode: REL_TARGETMODE, + } + + f.rId += 1 + + f.DocRelation.Relationship = append(f.DocRelation.Relationship, rel) + + return rel.ID +} + +func (f *File) pack(zipWriter *zip.Writer) (err error) { + files := map[string]string{} + + files["_rels/.rels"] = TEMP_REL + files["docProps/app.xml"] = TEMP_DOCPROPS_APP + files["docProps/core.xml"] = TEMP_DOCPROPS_CORE + files["word/theme/theme1.xml"] = TEMP_WORD_THEME_THEME + files["word/styles.xml"] = TEMP_WORD_STYLE + files["[Content_Types].xml"] = TEMP_CONTENT + files["word/_rels/document.xml.rels"], err = marshal(f.DocRelation) + if err != nil { + return err + } + files["word/document.xml"], err = marshal(f.Document) + if err != nil { + return err + } + + for path, data := range files { + w, err := zipWriter.Create(path) + if err != nil { + return err + } + + _, err = w.Write([]byte(data)) + if err != nil { + return err + } + } + + return +} + +func marshal(data interface{}) (out string, err error) { + body, err := xml.Marshal(data) + if err != nil { + return + } + + out = xml.Header + string(body) + return +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b637fc0 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/gingfrederik/docx + +go 1.13 diff --git a/paragraph.go b/paragraph.go new file mode 100644 index 0000000..ebe04ae --- /dev/null +++ b/paragraph.go @@ -0,0 +1,46 @@ +package docx + +import "encoding/xml" + +type Paragraph struct { + XMLName xml.Name `xml:"w:p"` + Data []interface{} + + file *File +} + +// AddText add text to paragraph +func (p *Paragraph) AddText(text string) *Run { + t := &Text{ + Text: text, + } + + run := &Run{ + Text: t, + RunProperties: &RunProperties{}, + } + + p.Data = append(p.Data, run) + + return run +} + +// AddLink add hyperlink to paragraph +func (p *Paragraph) AddLink(text string, link string) *Hyperlink { + rId := p.file.addLinkRelation(link) + hyperlink := &Hyperlink{ + ID: rId, + Run: Run{ + RunProperties: &RunProperties{ + RunStyle: &RunStyle{ + Val: HYPERLINK_STYLE, + }, + }, + InstrText: text, + }, + } + + p.Data = append(p.Data, hyperlink) + + return hyperlink +} diff --git a/relationship.go b/relationship.go new file mode 100644 index 0000000..887e515 --- /dev/null +++ b/relationship.go @@ -0,0 +1,24 @@ +package docx + +import "encoding/xml" + +const ( + XMLNS = `http://schemas.openxmlformats.org/package/2006/relationships` + REL_HYPERLINK = `http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink` + + REL_TARGETMODE = "External" +) + +type DocRelation struct { + XMLName xml.Name `xml:"Relationships"` + Xmlns string `xml:"xmlns,attr"` + Relationship []*RelationShip `xml:"Relationship"` +} + +type RelationShip struct { + XMLName xml.Name `xml:"Relationship"` + ID string `xml:"Id,attr"` + Type string `xml:"Type,attr"` + Target string `xml:"Target,attr"` + TargetMode string `xml:"TargetMode,attr,omitempty"` +} diff --git a/run.go b/run.go new file mode 100644 index 0000000..77b6183 --- /dev/null +++ b/run.go @@ -0,0 +1,39 @@ +package docx + +import "encoding/xml" + +type Run struct { + XMLName xml.Name `xml:"w:r"` + RunProperties *RunProperties `xml:"w:rPr,omitempty"` + InstrText string `xml:"w:instrText,omitempty"` + Text *Text +} + +type Text struct { + XMLName xml.Name `xml:"w:t"` + XMLSpace string `xml:"xml:space,attr,omitempty"` + Text string `xml:",chardata"` +} + +// Color set run color +func (r *Run) Color(color string) *Run { + r.RunProperties.Color = &Color{ + Val: color, + } + + return r +} + +// Size set run size +func (r *Run) Size(size int) *Run { + r.RunProperties.Size = &Size{ + Val: size * 2, + } + return r +} + +type Hyperlink struct { + XMLName xml.Name `xml:"w:hyperlink"` + ID string `xml:"r:id,attr"` + Run Run +} diff --git a/run_properties.go b/run_properties.go new file mode 100644 index 0000000..d647d18 --- /dev/null +++ b/run_properties.go @@ -0,0 +1,29 @@ +package docx + +import "encoding/xml" + +const ( + HYPERLINK_STYLE = "a1" +) + +type RunProperties struct { + XMLName xml.Name `xml:"w:rPr"` + Color *Color `xml:"w:color,omitempty"` + Size *Size `xml:"w:sz,omitempty"` + RunStyle *RunStyle `xml:"w:rStyle,omitempty"` +} + +type RunStyle struct { + XMLName xml.Name `xml:"w:rStyle"` + Val string `xml:"w:val,attr"` +} + +type Color struct { + XMLName xml.Name `xml:"w:color"` + Val string `xml:"w:val,attr"` +} + +type Size struct { + XMLName xml.Name `xml:"w:sz"` + Val int `xml:"w:val,attr"` +} diff --git a/template.go b/template.go new file mode 100644 index 0000000..0dd2ab6 --- /dev/null +++ b/template.go @@ -0,0 +1,389 @@ +package docx + +const ( + TEMP_REL = ` + + + + + ` + TEMP_DOCPROPS_APP = `Go DOCX` + TEMP_DOCPROPS_CORE = `` + TEMP_CONTENT = ` + + + + + + + + + ` + TEMP_WORD_STYLE = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ` + TEMP_WORD_THEME_THEME = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ` +)