├── clean.bat ├── demo ├── clean.bat ├── 10414104.png └── report.go ├── demo2 ├── clean.bat └── demo2.go ├── 2go.png ├── demo3 ├── deploy.bat ├── .gitignore ├── gopherjs_build.bat ├── README.md ├── web │ ├── styles │ │ └── main.css │ ├── index.html │ ├── main.dart │ └── js │ │ ├── FileSaver.min.js │ │ └── Blob.js ├── pubspec.yaml ├── LICENSE ├── demo.go ├── demo3.go └── report.go ├── xmlns ├── draw │ └── d.go ├── svg │ └── s.go ├── xlink │ └── x.go ├── text │ └── t.go ├── office │ └── o.go ├── table │ └── t.go ├── fo │ └── f.go ├── urn │ └── u.go ├── style │ └── s.go └── x.go ├── model ├── c.go ├── tree.go └── stub │ ├── simple_attrs.go │ ├── simple_tree.go │ ├── simple_nodes.go │ └── simple_riders.go ├── .gitignore ├── LICENSE ├── mappers ├── attr │ ├── extra.go │ ├── para.go │ ├── text.go │ ├── base.go │ └── table.go ├── embed.go ├── para.go ├── fmt.go ├── attr.go └── table.go ├── README.md ├── generators └── gen.go └── odf_test.go /clean.bat: -------------------------------------------------------------------------------- 1 | del *.odf 2 | -------------------------------------------------------------------------------- /demo/clean.bat: -------------------------------------------------------------------------------- 1 | del *.odf 2 | -------------------------------------------------------------------------------- /demo2/clean.bat: -------------------------------------------------------------------------------- 1 | del *.odf 2 | -------------------------------------------------------------------------------- /2go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kpmy/odf/HEAD/2go.png -------------------------------------------------------------------------------- /demo3/deploy.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | xcopy build\web\* gh-pages /E /Y 3 | -------------------------------------------------------------------------------- /demo/10414104.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kpmy/odf/HEAD/demo/10414104.png -------------------------------------------------------------------------------- /demo3/.gitignore: -------------------------------------------------------------------------------- 1 | .buildlog 2 | .DS_Store 3 | .idea 4 | .pub/ 5 | build/ 6 | packages 7 | pubspec.lock 8 | -------------------------------------------------------------------------------- /demo3/gopherjs_build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | gopherjs build 3 | copy demo3.js web\ /Y 4 | copy demo3.js.map web\ /Y -------------------------------------------------------------------------------- /demo3/README.md: -------------------------------------------------------------------------------- 1 | # ODF in your browser DEMO 2 | 3 | golang gopherjs kpmy/odf dartlang html web worker 4 | 5 | [http://kpmy.github.io/odf/](http://kpmy.github.io/odf/) -------------------------------------------------------------------------------- /demo3/web/styles/main.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Roboto); 2 | 3 | html, body { 4 | width: 100%; 5 | height: 100%; 6 | margin: 0; 7 | padding: 0; 8 | font-family: 'Roboto', sans-serif; 9 | } 10 | -------------------------------------------------------------------------------- /xmlns/draw/d.go: -------------------------------------------------------------------------------- 1 | package draw 2 | 3 | import ( 4 | "github.com/kpmy/odf/model" 5 | ) 6 | 7 | const ( 8 | Frame model.LeafName = "draw:frame" 9 | Image model.LeafName = "draw:image" 10 | ) 11 | 12 | const ( 13 | Name model.AttrName = "draw:name" 14 | ) 15 | -------------------------------------------------------------------------------- /demo3/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: 'demo3' 2 | version: 0.0.1 3 | description: An absolute bare-bones web app. 4 | #author: 5 | #homepage: https://www.example.com 6 | environment: 7 | sdk: '>=1.0.0 <2.0.0' 8 | dependencies: 9 | browser: '>=0.10.0 <0.11.0' 10 | crypto: 11 | git: https://github.com/dart-lang/crypto 12 | -------------------------------------------------------------------------------- /xmlns/svg/s.go: -------------------------------------------------------------------------------- 1 | package svg 2 | 3 | import ( 4 | "github.com/kpmy/odf/model" 5 | "github.com/kpmy/odf/xmlns" 6 | ) 7 | 8 | const ( 9 | FontFamily model.AttrName = "svg:font-family" 10 | Width model.AttrName = "svg:width" 11 | Height model.AttrName = "svg:height" 12 | ) 13 | 14 | func init() { 15 | xmlns.Typed[Height] = xmlns.MEASURE 16 | xmlns.Typed[Width] = xmlns.MEASURE 17 | } 18 | -------------------------------------------------------------------------------- /model/c.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | //LeafFactory for pimpl, creates nodes by specified name (names in xmlns/* package and corellate to ODF description 4 | var LeafFactory func(LeafName) Leaf 5 | 6 | //ModelFactory for pimpl that creates model 7 | var ModelFactory func() Model 8 | 9 | //Text is special leaf constructor because Text Leaf has no name in ODF document model 10 | var Text func(s string) Leaf 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | *.odf 26 | 27 | .buildlog 28 | .DS_Store 29 | .idea 30 | .pub/ 31 | build/ 32 | packages 33 | pubspec.lock 34 | /pubspec.lock 35 | gh-pages 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /demo3/LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /demo3/demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "github.com/kpmy/odf/generators" 6 | "github.com/kpmy/odf/mappers" 7 | "github.com/kpmy/odf/model" 8 | "github.com/kpmy/odf/xmlns" 9 | "io" 10 | ) 11 | 12 | func demo() (io.Reader, error) { 13 | output := bytes.NewBuffer(nil) 14 | m := model.ModelFactory() 15 | fm := &mappers.Formatter{} 16 | fm.ConnectTo(m) 17 | fm.MimeType = xmlns.MimeText 18 | fm.Init() 19 | fm.WriteString("Hello, World!") 20 | generators.GeneratePackage(m, nil, output, fm.MimeType) 21 | return output, nil 22 | } 23 | -------------------------------------------------------------------------------- /mappers/attr/extra.go: -------------------------------------------------------------------------------- 1 | package attr 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kpmy/ypk/assert" 6 | "image/color" 7 | "strconv" 8 | ) 9 | 10 | //Border style of table cell 11 | type Border struct { 12 | Width float64 13 | Color color.Color 14 | Style string 15 | } 16 | 17 | func (bb Border) String() string { 18 | assert.For(bb.Style != "", 20) 19 | assert.For(bb.Color != nil, 21) 20 | r, g, b, _ := bb.Color.RGBA() 21 | return fmt.Sprint(strconv.FormatFloat(bb.Width, 'f', 8, 64)+"cm", " ", bb.Style, " ", fmt.Sprintf("#%02X%02X%02X", uint8(r), uint8(g), uint8(b))) 22 | } 23 | -------------------------------------------------------------------------------- /xmlns/xlink/x.go: -------------------------------------------------------------------------------- 1 | package xlink 2 | 3 | import ( 4 | "github.com/kpmy/odf/model" 5 | "github.com/kpmy/odf/xmlns" 6 | ) 7 | 8 | const ( 9 | Href model.AttrName = "xlink:href" 10 | Type model.AttrName = "xlink:type" 11 | Show model.AttrName = "xlink:show" 12 | Actuate model.AttrName = "xlink:actuate" 13 | ) 14 | 15 | const Simple = "simple" 16 | const Embed = "embed" 17 | const OnLoad = "onload" 18 | 19 | func init() { 20 | xmlns.Typed[Type] = xmlns.ENUM 21 | xmlns.Typed[Show] = xmlns.ENUM 22 | xmlns.Typed[Actuate] = xmlns.ENUM 23 | xmlns.Enums[Type] = []string{Simple} 24 | xmlns.Enums[Show] = []string{Embed} 25 | xmlns.Enums[Actuate] = []string{OnLoad} 26 | } 27 | -------------------------------------------------------------------------------- /xmlns/text/t.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "github.com/kpmy/odf/model" 5 | "github.com/kpmy/odf/xmlns" 6 | ) 7 | 8 | const ( 9 | P model.LeafName = "text:p" 10 | S model.LeafName = "text:s" 11 | LineBreak model.LeafName = "text:line-break" 12 | Tab model.LeafName = "text:tab" 13 | Span model.LeafName = "text:span" 14 | ) 15 | 16 | const ( 17 | C model.AttrName = "text:c" 18 | StyleName model.AttrName = "text:style-name" 19 | AnchorType model.AttrName = "text:anchor-type" 20 | ) 21 | 22 | const ( 23 | Paragraph = "parahraph" 24 | ) 25 | 26 | func init() { 27 | xmlns.Typed[C] = xmlns.INT 28 | xmlns.Typed[AnchorType] = xmlns.ENUM 29 | xmlns.Enums[AnchorType] = []string{Paragraph} 30 | } 31 | -------------------------------------------------------------------------------- /xmlns/office/o.go: -------------------------------------------------------------------------------- 1 | package office 2 | 3 | import ( 4 | "github.com/kpmy/odf/model" 5 | ) 6 | 7 | const ( 8 | DocumentStyles model.LeafName = "office:document-styles" 9 | AutomaticStyles model.LeafName = "office:automatic-styles" 10 | MasterStyles model.LeafName = "office:master-styles" 11 | FontFaceDecls model.LeafName = "office:font-face-decls" 12 | DocumentMeta model.LeafName = "office:document-meta" 13 | DocumentContent model.LeafName = "office:document-content" 14 | Body model.LeafName = "office:body" 15 | Text model.LeafName = "office:text" 16 | Spreadsheet model.LeafName = "office:spreadsheet" 17 | Styles model.LeafName = "office:styles" 18 | ) 19 | 20 | const ( 21 | Version model.AttrName = "office:version" 22 | ) 23 | -------------------------------------------------------------------------------- /demo2/demo2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kpmy/odf/generators" 5 | "github.com/kpmy/odf/mappers" 6 | "github.com/kpmy/odf/model" 7 | _ "github.com/kpmy/odf/model/stub" //don't forget pimpl 8 | "github.com/kpmy/odf/xmlns" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | if output, err := os.Create("demo2.odf"); err == nil { 14 | //cleanup 15 | defer output.Close() 16 | //we need an empty model 17 | m := model.ModelFactory() 18 | //standard formatter 19 | fm := &mappers.Formatter{} 20 | //couple them 21 | fm.ConnectTo(m) 22 | //we want text 23 | fm.MimeType = xmlns.MimeText 24 | //yes we can 25 | fm.Init() 26 | //pretty simple 27 | fm.WriteString("Hello, World!") 28 | //store file 29 | generators.GeneratePackage(m, nil, output, fm.MimeType) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /demo3/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | demo kpmy/odf 15 | 16 | 17 | 18 | 19 | 20 | 21 |
golang gopherjs kpmy/odf dartlang html web workers kpmy/odf/demo3 eligrey/filesaver eligrey/blob
22 | In-browser ODF generator: 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /model/tree.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type AttrName string 4 | type LeafName string 5 | 6 | //Attribute of node 7 | type Attribute interface { 8 | String() string 9 | } 10 | 11 | //Leaf is node without children nodes 12 | type Leaf interface { 13 | Name() LeafName 14 | Attr(AttrName, ...Attribute) Attribute 15 | Parent(...Node) Node 16 | } 17 | 18 | //Node holds chidlren nodes, Carrier in CRM pattern 19 | type Node interface { 20 | Leaf 21 | Child(int) Leaf 22 | IndexOf(Leaf) int 23 | NofChild() int 24 | } 25 | 26 | //Model holds root node and constructs special riders 27 | type Model interface { 28 | Root() Node 29 | NewReader(...Reader) Reader 30 | NewWriter(...Writer) Writer 31 | } 32 | 33 | //Reader is a reading rider in CRM pattern 34 | //Reader stands on node and runs above it's child nodes 35 | type Reader interface { 36 | InitFrom(Reader) 37 | Base() Model 38 | Read() Leaf 39 | Eol() bool 40 | Pos(...Leaf) Leaf 41 | } 42 | 43 | //Writer is a modifying rider in CRM pattern 44 | //Writer stands on node and modifies it's children and attributes 45 | type Writer interface { 46 | InitFrom(Writer) 47 | Base() Model 48 | Pos(...Leaf) Leaf 49 | Write(Leaf, ...Leaf) 50 | WritePos(Leaf, ...Leaf) Leaf 51 | Attr(AttrName, interface{}) Writer //for fluid interface 52 | Delete(Leaf) 53 | } 54 | -------------------------------------------------------------------------------- /xmlns/table/t.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | import ( 4 | "github.com/kpmy/odf/model" 5 | "github.com/kpmy/odf/xmlns" 6 | ) 7 | 8 | const ( 9 | Table model.LeafName = "table:table" 10 | TableColumn model.LeafName = "table:table-column" 11 | TableRow model.LeafName = "table:table-row" 12 | TableCell model.LeafName = "table:table-cell" 13 | ) 14 | 15 | const ( 16 | Name model.AttrName = "table:name" 17 | NumberColumnsSpanned = "table:number-columns-spanned" 18 | NumberRowsSpanned = "table:number-rows-spanned" 19 | BorderModel = "table:border-model" 20 | StyleName = "table:style-name" 21 | Align = "table:align" 22 | ) 23 | 24 | const ( 25 | BorderModelCollapsing = "collapsing" 26 | BorderModelSeparating = "separating" 27 | AlignLeft = "left" 28 | AlignRight = "right" 29 | AlignCenter = "center" 30 | ) 31 | 32 | func init() { 33 | xmlns.Typed[NumberColumnsSpanned] = xmlns.INT 34 | xmlns.Typed[NumberRowsSpanned] = xmlns.INT 35 | xmlns.Typed[BorderModel] = xmlns.ENUM 36 | xmlns.Enums[BorderModel] = []string{BorderModelCollapsing, BorderModelSeparating} 37 | xmlns.Typed[Align] = xmlns.ENUM 38 | xmlns.Enums[Align] = []string{AlignCenter, AlignLeft, AlignRight} 39 | } 40 | -------------------------------------------------------------------------------- /mappers/attr/para.go: -------------------------------------------------------------------------------- 1 | package attr 2 | 3 | import ( 4 | "github.com/kpmy/odf/model" 5 | "github.com/kpmy/odf/xmlns/fo" 6 | "github.com/kpmy/odf/xmlns/style" 7 | "github.com/kpmy/odf/xmlns/text" 8 | ) 9 | 10 | //ParagraphAttributes is ODF Paragraph Family style fluent builder 11 | type ParagraphAttributes struct { 12 | named 13 | easy 14 | } 15 | 16 | func (p *ParagraphAttributes) Equal(_a Attributes) (ok bool) { 17 | a, ok := _a.(*ParagraphAttributes) 18 | if ok { 19 | ok = p.equal(&a.easy) 20 | } 21 | return 22 | } 23 | 24 | func (p *ParagraphAttributes) Fit() model.LeafName { return text.P } 25 | 26 | func (p *ParagraphAttributes) Write(wr model.Writer) { 27 | wr.Attr(style.Family, style.FamilyParagraph) 28 | wr.WritePos(New(style.ParagraphProperties)) 29 | p.apply(wr) 30 | } 31 | 32 | //AlignRight on page 33 | func (p *ParagraphAttributes) AlignRight() *ParagraphAttributes { 34 | p.put(fo.TextAlign, fo.Right, nil) 35 | return p 36 | } 37 | 38 | //AlignCenter on page 39 | func (p *ParagraphAttributes) AlignCenter() *ParagraphAttributes { 40 | p.put(fo.TextAlign, fo.Center, nil) 41 | return p 42 | } 43 | 44 | //PageBreak with new paragraph written (it will be first on new page) 45 | func (p *ParagraphAttributes) PageBreak() *ParagraphAttributes { 46 | p.put(fo.BreakBefore, true, func(v value) { 47 | if x := v.data.(bool); x { 48 | v.wr.Attr(fo.BreakBefore, fo.Page) 49 | } 50 | }) 51 | return p 52 | } 53 | -------------------------------------------------------------------------------- /demo3/web/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html'; 2 | import 'package:crypto/crypto.dart'; 3 | import 'dart:typed_data'; 4 | import 'dart:js'; 5 | import 'dart:convert'; 6 | 7 | class OdfWorker{ 8 | 9 | Worker inner; 10 | 11 | void postMessage(message){ 12 | this.inner.postMessage(message); 13 | } 14 | 15 | void listen(handler){ 16 | this.inner.onMessage.listen(handler); 17 | } 18 | 19 | OdfWorker(){ 20 | this.inner = new Worker("demo3.js"); 21 | } 22 | } 23 | 24 | void main() { 25 | var w = new OdfWorker(); 26 | 27 | w.listen((_m){ 28 | print(_m.data); 29 | var msg = JSON.decode(_m.data); 30 | switch(msg["Type"]){ 31 | case "init": 32 | print("worker initialized, sending responce..."); 33 | w.postMessage(JSON.encode({"Type": "init"})); 34 | break; 35 | case "data": 36 | print("data received"); 37 | Uint8List data = new Uint8List.fromList(CryptoUtils.base64StringToBytes(msg["Data"])); 38 | context.callMethod("saveAs", [new Blob([data], "application/octet-stream"), "report.odf"]); 39 | break; 40 | default: throw new ArgumentError(msg["Type"]); 41 | } 42 | }); 43 | 44 | querySelector("#do-demo").onClick.listen((m){ 45 | w.postMessage(JSON.encode({"Type": "get", "Param": "demo"})); 46 | }); 47 | 48 | querySelector("#do-report").onClick.listen((m){ 49 | w.postMessage(JSON.encode({"Type": "get", "Param": "report"})); 50 | }); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /xmlns/fo/f.go: -------------------------------------------------------------------------------- 1 | package fo 2 | 3 | import ( 4 | "github.com/kpmy/odf/model" 5 | "github.com/kpmy/odf/xmlns" 6 | ) 7 | 8 | const ( 9 | FontSize model.AttrName = "fo:font-size" 10 | TextAlign model.AttrName = "fo:text-align" 11 | BreakBefore model.AttrName = "fo:break-before" 12 | Color model.AttrName = "fo:color" 13 | FontWeight model.AttrName = "fo:font-weight" 14 | FontStyle model.AttrName = "fo:font-style" 15 | BorderRight model.AttrName = "fo:border-right" 16 | BorderLeft model.AttrName = "fo:border-left" 17 | BorderTop model.AttrName = "fo:border-top" 18 | BorderBottom model.AttrName = "fo:border-bottom" 19 | ) 20 | 21 | const ( 22 | Page = "page" 23 | Bold = "bold" 24 | Italic = "italic" 25 | ) 26 | 27 | const ( 28 | Start = "start" 29 | End = "end" 30 | Left = "left" 31 | Right = "right" 32 | Center = "center" 33 | Justify = "justify" 34 | ) 35 | 36 | const ( 37 | None = "none" 38 | Solid = "solid" 39 | Dotted = "dotted" 40 | Dash = "dash" 41 | LongDash = "long-dash" 42 | DotDash = "dot-dash" 43 | DotDotDash = "dot-dot-dash" 44 | Wave = "wave" 45 | ) 46 | 47 | func init() { 48 | xmlns.Typed[FontSize] = xmlns.INT 49 | xmlns.Typed[TextAlign] = xmlns.ENUM 50 | xmlns.Enums[TextAlign] = []string{Start, End, Left, Right, Center, Justify} 51 | xmlns.Typed[BreakBefore] = xmlns.ENUM 52 | xmlns.Enums[BreakBefore] = []string{Page} 53 | xmlns.Typed[Color] = xmlns.COLOR 54 | } 55 | -------------------------------------------------------------------------------- /xmlns/urn/u.go: -------------------------------------------------------------------------------- 1 | package urn 2 | 3 | const ( 4 | Office = "urn:oasis:names:tc:opendocument:xmlns:office:1.0" 5 | Meta = "urn:oasis:names:tc:opendocument:xmlns:meta:1.0" 6 | Config = "urn:oasis:names:tc:opendocument:xmlns:config:1.0" 7 | Text = "urn:oasis:names:tc:opendocument:xmlns:text:1.0" 8 | Table = "urn:oasis:names:tc:opendocument:xmlns:table:1.0" 9 | Draw = "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" 10 | Presentation = "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" 11 | Dr3d = "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" 12 | Chart = "urn:oasis:names:tc:opendocument:xmlns:chart:1.0" 13 | Form = "urn:oasis:names:tc:opendocument:xmlns:form:1.0" 14 | Script = "urn:oasis:names:tc:opendocument:xmlns:script:1.0" 15 | Style = "urn:oasis:names:tc:opendocument:xmlns:style:1.0" 16 | Number = "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" 17 | Anim = "urn:oasis:names:tc:opendocument:xmlns:animation:1.0" 18 | Dc = "http://purl.org/dc/elements/1.1/" 19 | Xlink = "http://www.w3.org/1999/xlink" 20 | Math = "http://www.w3.org/1998/Math/MathML" 21 | Xforms = "http://www.w3.org/2002/xforms" 22 | Fo = "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" 23 | Svg = "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" 24 | Smil = "urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0" 25 | Manifest = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" 26 | ) 27 | -------------------------------------------------------------------------------- /xmlns/style/s.go: -------------------------------------------------------------------------------- 1 | package style 2 | 3 | import ( 4 | "github.com/kpmy/odf/model" 5 | "github.com/kpmy/odf/xmlns" 6 | ) 7 | 8 | const ( 9 | Style model.LeafName = "style:style" 10 | 11 | TextProperties model.LeafName = "style:text-properties" 12 | TableProperties model.LeafName = "style:table-properties" 13 | ParagraphProperties model.LeafName = "style:paragraph-properties" 14 | TableRowProperties model.LeafName = "style:table-row-properties" 15 | TableColumnProperties model.LeafName = "style:table-column-properties" 16 | TableCellProperties model.LeafName = "style:table-cell-properties" 17 | 18 | DefaultStyle model.LeafName = "style:default-style" 19 | FontFace model.LeafName = "style:font-face" 20 | ) 21 | 22 | const ( 23 | Family model.AttrName = "style:family" 24 | Name model.AttrName = "style:name" 25 | FontName model.AttrName = "style:font-name" 26 | Width model.AttrName = "style:width" 27 | UseOptimalRowHeight model.AttrName = "style:use-optimal-row-height" 28 | UseOptimalColumnWidth model.AttrName = "style:use-optimal-column-width" 29 | ) 30 | 31 | const ( 32 | FamilyText = "text" 33 | FamilyParagraph = "paragraph" 34 | FamilyTable = "table" 35 | FamilyTableRow = "table-row" 36 | FamilyTableColumn = "table-column" 37 | FamilyTableCell = "table-cell" 38 | ) 39 | 40 | func init() { 41 | xmlns.Typed[Family] = xmlns.ENUM 42 | xmlns.Enums[Family] = []string{FamilyText, FamilyParagraph, FamilyTable, FamilyTableRow, FamilyTableColumn, FamilyTableCell} 43 | xmlns.Typed[Width] = xmlns.MEASURE 44 | xmlns.Typed[UseOptimalColumnWidth] = xmlns.BOOL 45 | xmlns.Typed[UseOptimalRowHeight] = xmlns.BOOL 46 | } 47 | -------------------------------------------------------------------------------- /model/stub/simple_attrs.go: -------------------------------------------------------------------------------- 1 | package stub 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "image/color" 7 | "strconv" 8 | ) 9 | 10 | type StringAttr struct { 11 | Value string 12 | } 13 | 14 | func (a *StringAttr) String() string { 15 | return a.Value 16 | } 17 | 18 | func (a *StringAttr) MarshalXMLAttr(name xml.Name) (xa xml.Attr, err error) { 19 | xa.Name = name 20 | xa.Value = a.String() 21 | return 22 | } 23 | 24 | type IntAttr struct { 25 | Value int 26 | } 27 | 28 | func (a *IntAttr) String() string { 29 | return strconv.Itoa(a.Value) 30 | } 31 | 32 | func (a *IntAttr) MarshalXMLAttr(name xml.Name) (xa xml.Attr, err error) { 33 | xa.Name = name 34 | xa.Value = a.String() 35 | return 36 | } 37 | 38 | type BoolAttr struct { 39 | Value bool 40 | } 41 | 42 | func (a *BoolAttr) String() string { 43 | if a.Value { 44 | return "true" 45 | } else { 46 | return "false" 47 | } 48 | } 49 | 50 | func (a *BoolAttr) MarshalXMLAttr(name xml.Name) (xa xml.Attr, err error) { 51 | xa.Name = name 52 | xa.Value = a.String() 53 | return 54 | } 55 | 56 | type MeasureAttr struct { 57 | Value float64 58 | } 59 | 60 | func (a *MeasureAttr) String() string { 61 | return strconv.FormatFloat(a.Value, 'f', 8, 64) + "cm" 62 | } 63 | 64 | func (a *MeasureAttr) MarshalXMLAttr(name xml.Name) (xa xml.Attr, err error) { 65 | xa.Name = name 66 | xa.Value = a.String() 67 | return 68 | } 69 | 70 | type ColorAttr struct { 71 | Value color.Color 72 | } 73 | 74 | func (a *ColorAttr) String() string { 75 | r, g, b, _ := a.Value.RGBA() 76 | return fmt.Sprintf("#%02X%02X%02X", uint8(r), uint8(g), uint8(b)) 77 | } 78 | 79 | func (a *ColorAttr) MarshalXMLAttr(name xml.Name) (xa xml.Attr, err error) { 80 | xa.Name = name 81 | xa.Value = a.String() 82 | return 83 | } 84 | -------------------------------------------------------------------------------- /mappers/embed.go: -------------------------------------------------------------------------------- 1 | package mappers 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/binary" 6 | "github.com/kpmy/odf/xmlns" 7 | "github.com/kpmy/odf/xmlns/draw" 8 | "github.com/kpmy/odf/xmlns/svg" 9 | "github.com/kpmy/odf/xmlns/text" 10 | "github.com/kpmy/odf/xmlns/xlink" 11 | "io" 12 | "math/rand" 13 | "strconv" 14 | "time" 15 | ) 16 | 17 | //Draw holds image data and it's mimetype 18 | type Draw struct { 19 | rd io.Reader 20 | mime xmlns.Mime 21 | } 22 | 23 | //NewDraw constructor 24 | func NewDraw(source io.Reader, _mime xmlns.Mime) *Draw { 25 | return &Draw{rd: source, mime: _mime} 26 | } 27 | 28 | func nextUrl() (ret string) { 29 | t := time.Now().UnixNano() 30 | seed := rand.Int63() 31 | h := md5.New() 32 | binary.Write(h, binary.LittleEndian, t) 33 | binary.Write(h, binary.LittleEndian, seed) 34 | data := h.Sum(nil) 35 | for _, x := range data { 36 | ret = ret + strconv.FormatInt(int64(x), 16) 37 | } 38 | return 39 | } 40 | 41 | //Reader implements generators.Embeddable 42 | func (d *Draw) Reader() io.Reader { 43 | return d.rd 44 | } 45 | 46 | func (d *Draw) MimeType() xmlns.Mime { 47 | return d.mime 48 | } 49 | 50 | //WriteTo writes image (link to image) to document model, don't forget to pass Draw as Embeddable to package generator 51 | func (d *Draw) WriteTo(fm *Formatter, name string, w, h interface{}) string { 52 | fm.defaultParaMapper.makePara() 53 | wr := fm.m.NewWriter() 54 | wr.Pos(fm.defaultParaMapper.rider.Pos()) 55 | wr.WritePos(New(draw.Frame)) 56 | wr.Attr(draw.Name, name).Attr(text.AnchorType, text.Paragraph).Attr(svg.Width, w).Attr(svg.Height, h) 57 | wr.WritePos(New(draw.Image)) 58 | url := "Pictures/" + nextUrl() 59 | wr.Attr(xlink.Href, url).Attr(xlink.Type, xlink.Simple).Attr(xlink.Show, xlink.Embed).Attr(xlink.Actuate, xlink.OnLoad) 60 | return url 61 | } 62 | -------------------------------------------------------------------------------- /xmlns/x.go: -------------------------------------------------------------------------------- 1 | //Package xmlns contains any constants related to ODF project, and also string names and values of ODF document nodes and attributes 2 | //Also it contains some validation routines and value format descriptions 3 | package xmlns 4 | 5 | import ( 6 | "github.com/kpmy/odf/model" 7 | ) 8 | 9 | const ( 10 | Mimetype = "mimetype" 11 | Manifest = "META-INF/manifest.xml" 12 | Content = "content.xml" 13 | Styles = "styles.xml" 14 | Meta = "meta.xml" 15 | ) 16 | 17 | const ( 18 | NSoffice = "xmlns:office" 19 | NSmeta = "xmlns:meta" 20 | NSconfig = "xmlns:config" 21 | NStext = "xmlns:text" 22 | NStable = "xmlns:table" 23 | NSdraw = "xmlns:draw" 24 | NSpresentation = "xmlns:presentation" 25 | NSdr3d = "xmlns:dr3d" 26 | NSchart = "xmlns:chart" 27 | NSform = "xmlns:form" 28 | NSscript = "xmlns:script" 29 | NSstyle = "xmlns:style" 30 | NSnumber = "xmlns:number" 31 | NSanim = "xmlns:anim" 32 | NSdc = "xmlns:dc" 33 | NSxlink = "xmlns:xlink" 34 | NSmath = "xmlns:math" 35 | NSxforms = "xmlns:xforms" 36 | NSfo = "xmlns:fo" 37 | NSsvg = "xmlns:svg" 38 | NSsmil = "xmlns:smil" 39 | NSmanifest = "xmlns:manifest" 40 | ) 41 | 42 | type AttrType int 43 | 44 | const ( 45 | NONE AttrType = iota 46 | STRING 47 | INT 48 | MEASURE 49 | ENUM 50 | COLOR 51 | BOOL 52 | ) 53 | 54 | type Mime string 55 | 56 | const ( 57 | MimeDefault = "text/xml" 58 | MimeText Mime = "application/vnd.oasis.opendocument.text" 59 | MimeSpreadsheet Mime = "application/vnd.oasis.opendocument.spreadsheet" 60 | ) 61 | 62 | var Typed map[model.AttrName]AttrType 63 | var Enums map[model.AttrName][]string 64 | 65 | func init() { 66 | Typed = make(map[model.AttrName]AttrType) 67 | Enums = make(map[model.AttrName][]string) 68 | } 69 | -------------------------------------------------------------------------------- /mappers/attr/text.go: -------------------------------------------------------------------------------- 1 | package attr 2 | 3 | import ( 4 | "github.com/kpmy/odf/model" 5 | "github.com/kpmy/odf/xmlns/fo" 6 | "github.com/kpmy/odf/xmlns/style" 7 | "github.com/kpmy/odf/xmlns/text" 8 | "image/color" 9 | ) 10 | 11 | //TextAttributes is a Text Family fluent style builder 12 | type TextAttributes struct { 13 | fontFace string 14 | size int 15 | col color.Color 16 | bold bool 17 | italic bool 18 | named 19 | } 20 | 21 | func (t *TextAttributes) Equal(_a Attributes) (ok bool) { 22 | a, ok := _a.(*TextAttributes) 23 | if ok { 24 | ok = t.size == a.size && t.fontFace == a.fontFace && t.col == a.col && t.italic == a.italic && t.bold == a.bold 25 | } 26 | return 27 | } 28 | 29 | func (t *TextAttributes) Fit() model.LeafName { return text.Span } 30 | 31 | func (t *TextAttributes) Write(wr model.Writer) { 32 | wr.Attr(style.Family, style.FamilyText) 33 | wr.WritePos(New(style.TextProperties)) 34 | if t.fontFace != "" { 35 | wr.Attr(style.FontName, t.fontFace) 36 | } 37 | if t.size != 0 { 38 | wr.Attr(fo.FontSize, t.size) 39 | } 40 | wr.Attr(fo.Color, t.col) 41 | if t.bold { 42 | wr.Attr(fo.FontWeight, fo.Bold) 43 | } 44 | if t.italic { 45 | wr.Attr(fo.FontStyle, fo.Italic) 46 | } 47 | } 48 | 49 | //Size of text in points 50 | func (t *TextAttributes) Size(s int) *TextAttributes { 51 | t.size = s 52 | return t 53 | } 54 | 55 | //FontFace of text (font-faces are registered in mappers.Formatter 56 | func (t *TextAttributes) FontFace(name string) *TextAttributes { 57 | t.fontFace = name 58 | return t 59 | } 60 | 61 | //Color of text 62 | func (t *TextAttributes) Color(col color.Color) *TextAttributes { 63 | t.col = col 64 | return t 65 | } 66 | 67 | //Bold style of text 68 | func (t *TextAttributes) Bold() *TextAttributes { 69 | t.bold = true 70 | return t 71 | } 72 | 73 | //Italic style of text 74 | func (t *TextAttributes) Italic() *TextAttributes { 75 | t.italic = true 76 | return t 77 | } 78 | -------------------------------------------------------------------------------- /mappers/attr/base.go: -------------------------------------------------------------------------------- 1 | package attr 2 | 3 | import ( 4 | "github.com/kpmy/odf/model" 5 | "github.com/kpmy/ypk/assert" 6 | ) 7 | 8 | //New is shortcut for factory function model.ModelFactory 9 | var New func(name model.LeafName) model.Leaf 10 | 11 | //Attributes interface that is supported by current version of mappers.Attr container 12 | type Attributes interface { 13 | Name(...string) string 14 | Equal(Attributes) bool 15 | Fit() model.LeafName 16 | Write(model.Writer) 17 | } 18 | 19 | type named struct { 20 | name string 21 | } 22 | 23 | func (n *named) Name(s ...string) string { 24 | if len(s) == 1 { 25 | assert.For(s[0] != "", 20) 26 | n.name = s[0] 27 | } 28 | return n.name 29 | } 30 | 31 | type value struct { 32 | name model.AttrName 33 | wr model.Writer 34 | data interface{} 35 | builder func(value) 36 | } 37 | 38 | type easy struct { 39 | m map[model.AttrName]value 40 | } 41 | 42 | func (e *easy) put(n model.AttrName, x interface{}, foo func(value)) { 43 | if e.m == nil { 44 | e.m = make(map[model.AttrName]value) 45 | } 46 | b := func(v value) { 47 | v.wr.Attr(v.name, v.data) 48 | } 49 | if foo != nil { 50 | b = foo 51 | } 52 | if x != nil { 53 | e.m[n] = value{data: x, builder: b} 54 | } else { 55 | delete(e.m, n) 56 | } 57 | } 58 | 59 | func (e *easy) equal(t *easy) (ok bool) { 60 | ok = (e.m != nil) == (t.m != nil) 61 | if ok && (e.m != nil) { 62 | for k, v := range e.m { 63 | ok = t.m[k].data == v.data 64 | if !ok { 65 | break 66 | } 67 | } 68 | } 69 | return 70 | } 71 | 72 | func (e *easy) apply(wr model.Writer) { 73 | if e.m != nil { 74 | for k, v := range e.m { 75 | v.wr = wr 76 | v.name = k 77 | v.builder(v) 78 | } 79 | } 80 | } 81 | 82 | func triggerBoolAttr(n model.AttrName) func(v value) { 83 | return func(v value) { 84 | if x := v.data.(bool); x { 85 | v.wr.Attr(n, true) 86 | } 87 | } 88 | } 89 | 90 | func init() { 91 | New = func(n model.LeafName) model.Leaf { 92 | return model.LeafFactory(n) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /demo3/demo3.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "github.com/gopherjs/gopherjs/js" 8 | _ "github.com/kpmy/odf/model/stub" //don't forget pimpl 9 | "github.com/kpmy/ypk/assert" 10 | "github.com/kpmy/ypk/halt" 11 | "io" 12 | "log" 13 | "sync" 14 | ) 15 | 16 | type Msg struct { 17 | Type string 18 | Param string 19 | Data string 20 | } 21 | 22 | type Handler func(m *Msg) 23 | 24 | var wg *sync.WaitGroup = &sync.WaitGroup{} 25 | var busChan chan *Msg 26 | 27 | //этот хэндлер только пишет сообщения в канал главной горутины 28 | func busHandler(m *Msg) { 29 | busChan <- m 30 | } 31 | 32 | //этот хэндлер обрабатывает сообщения в рамках главной горутины 33 | func handle(m *Msg) { 34 | switch m.Type { 35 | case "init": 36 | log.Println("message bus connected") 37 | case "get": 38 | var rd io.Reader 39 | if m.Param == "demo" { 40 | rd, _ = demo() 41 | } else if m.Param == "report" { 42 | rd, _ = report() 43 | } 44 | buf := bytes.NewBuffer(nil) 45 | io.Copy(buf, rd) 46 | m := &Msg{Type: "data"} 47 | m.Data = base64.StdEncoding.EncodeToString(buf.Bytes()) 48 | Process(m) 49 | default: 50 | halt.As(100, "not implemented", m.Type) 51 | } 52 | } 53 | 54 | func Process(m *Msg) { 55 | assert.For(m != nil, 20) 56 | s, _ := json.Marshal(m) 57 | js.Global.Call("postMessage", string(s)) 58 | } 59 | 60 | func Init(handler Handler) { 61 | js.Global.Set("onmessage", func(oEvent *js.Object) { 62 | _data := oEvent.Get("data").Interface().(string) 63 | log.Println(_data) 64 | m := &Msg{} 65 | err := json.Unmarshal([]byte(_data), m) 66 | log.Println(m) 67 | assert.For(err == nil, 40) 68 | handler(m) 69 | }) 70 | } 71 | 72 | func main() { 73 | log.Println("odf loading... ") 74 | Init(busHandler) 75 | busChan = make(chan *Msg) 76 | wg.Add(1) 77 | go func(wg *sync.WaitGroup, c chan *Msg) { 78 | log.Println("done") 79 | Process(&Msg{Type: "init"}) 80 | for { 81 | select { 82 | case m := <-c: 83 | handle(m) 84 | } 85 | } 86 | }(wg, busChan) 87 | wg.Wait() 88 | log.Println("odf closed") 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ODF 2 | [Open Document Format](http://docs.oasis-open.org/office/v1.2/OpenDocument-v1.2.html) (ODF) producer library for Go (golang). 3 | 4 | [![Build Status](https://drone.io/github.com/kpmy/odf/status.png)](https://drone.io/github.com/kpmy/odf/latest) 5 | 6 | ## Описание 7 | Формирование документа в формате Open Document (ODF) для языка Go (golang). 8 | 9 | Формирование документа заключается в последовательном вызове инструкций Форматтера, который выполняет модификацию одной или нескольких частей модели документа. 10 | Затем вызывается процедура генерации файла-архива .odf 11 | 12 | Клиентский код изолируется от особенностей структуры документа ODF. 13 | 14 | Необходимость форматтера обсуловлена тем, что стандарт ODF предполагает изменение видимого содержимого документа посредством изменений в нескольких местах модели документа (стили, встроенные файлы, и т.д.) 15 | 16 | ## Пример 17 | go get github.com/kpmy/odf 18 | В пакете demo есть пример использования ODF для формирования отчета. 19 | 20 | ## Description 21 | This library is for generation of ODF document with Go. 22 | 23 | You can produce a document with content by calling the Formatter methods. 24 | Then you can save this document to zip-file .odf 25 | 26 | No need for your code to handle with ODF XML content. 27 | More examples in demo/report.go 28 | 29 | ## Example 30 | 31 | package main 32 | 33 | import ( 34 | "odf/generators" 35 | "odf/mappers" 36 | "odf/model" 37 | _ "odf/model/stub" //don't forget pimpl 38 | "odf/xmlns" 39 | "os" 40 | ) 41 | 42 | func main() { 43 | if output, err := os.Create("demo2.odf"); err == nil { 44 | //we need an empty model 45 | m := model.ModelFactory() 46 | //standard formatter 47 | fm := &mappers.Formatter{} 48 | //couple them 49 | fm.ConnectTo(m) 50 | //we want text 51 | fm.MimeType = xmlns.MimeText 52 | //yes we can 53 | fm.Init() 54 | //pretty simple 55 | fm.WriteString("Hello, World!") 56 | //store file 57 | generators.GeneratePackage(m, nil, output, fm.MimeType) 58 | //cleanup 59 | defer output.Close() 60 | } 61 | } 62 | 63 | ## Moar 64 | 65 | It works in browser now. Got Demo3, GopherJS + Dart. 66 | 67 | [http://kpmy.github.io/odf/](http://kpmy.github.io/odf/) 68 | -------------------------------------------------------------------------------- /demo3/web/js/FileSaver.min.js: -------------------------------------------------------------------------------- 1 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ 2 | var saveAs=saveAs||function(e){"use strict";if("undefined"==typeof navigator||!/MSIE [1-9]\./.test(navigator.userAgent)){var t=e.document,n=function(){return e.URL||e.webkitURL||e},o=t.createElementNS("http://www.w3.org/1999/xhtml","a"),r="download"in o,i=function(n){var o=t.createEvent("MouseEvents");o.initMouseEvent("click",!0,!1,e,0,0,0,0,0,!1,!1,!1,!1,0,null),n.dispatchEvent(o)},a=e.webkitRequestFileSystem,c=e.requestFileSystem||a||e.mozRequestFileSystem,u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},f="application/octet-stream",s=0,d=500,l=function(t){var o=function(){"string"==typeof t?n().revokeObjectURL(t):t.remove()};e.chrome?o():setTimeout(o,d)},v=function(e,t,n){t=[].concat(t);for(var o=t.length;o--;){var r=e["on"+t[o]];if("function"==typeof r)try{r.call(e,n||e)}catch(i){u(i)}}},p=function(e){return/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)?new Blob(["\ufeff",e],{type:e.type}):e},w=function(t,u){t=p(t);var d,w,y,m=this,S=t.type,h=!1,O=function(){v(m,"writestart progress write writeend".split(" "))},E=function(){if((h||!d)&&(d=n().createObjectURL(t)),w)w.location.href=d;else{var o=e.open(d,"_blank");void 0==o&&"undefined"!=typeof safari&&(e.location.href=d)}m.readyState=m.DONE,O(),l(d)},R=function(e){return function(){return m.readyState!==m.DONE?e.apply(this,arguments):void 0}},b={create:!0,exclusive:!1};return m.readyState=m.INIT,u||(u="download"),r?(d=n().createObjectURL(t),o.href=d,o.download=u,i(o),m.readyState=m.DONE,O(),void l(d)):(e.chrome&&S&&S!==f&&(y=t.slice||t.webkitSlice,t=y.call(t,0,t.size,f),h=!0),a&&"download"!==u&&(u+=".download"),(S===f||a)&&(w=e),c?(s+=t.size,void c(e.TEMPORARY,s,R(function(e){e.root.getDirectory("saved",b,R(function(e){var n=function(){e.getFile(u,b,R(function(e){e.createWriter(R(function(n){n.onwriteend=function(t){w.location.href=e.toURL(),m.readyState=m.DONE,v(m,"writeend",t),l(e)},n.onerror=function(){var e=n.error;e.code!==e.ABORT_ERR&&E()},"writestart progress write abort".split(" ").forEach(function(e){n["on"+e]=m["on"+e]}),n.write(t),m.abort=function(){n.abort(),m.readyState=m.DONE},m.readyState=m.WRITING}),E)}),E)};e.getFile(u,{create:!1},R(function(e){e.remove(),n()}),R(function(e){e.code===e.NOT_FOUND_ERR?n():E()}))}),E)}),E)):void E())},y=w.prototype,m=function(e,t){return new w(e,t)};return"undefined"!=typeof navigator&&navigator.msSaveOrOpenBlob?function(e,t){return navigator.msSaveOrOpenBlob(p(e),t)}:(y.abort=function(){var e=this;e.readyState=e.DONE,v(e,"abort")},y.readyState=y.INIT=0,y.WRITING=1,y.DONE=2,y.error=y.onwritestart=y.onprogress=y.onwrite=y.onabort=y.onerror=y.onwriteend=null,m)}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content);"undefined"!=typeof module&&module.exports?module.exports.saveAs=saveAs:"undefined"!=typeof define&&null!==define&&null!=define.amd&&define([],function(){return saveAs}); -------------------------------------------------------------------------------- /generators/gen.go: -------------------------------------------------------------------------------- 1 | package generators 2 | 3 | import ( 4 | "archive/zip" 5 | "bytes" 6 | "encoding/xml" 7 | "github.com/kpmy/odf/model" 8 | "github.com/kpmy/odf/xmlns" 9 | "github.com/kpmy/odf/xmlns/office" 10 | "github.com/kpmy/odf/xmlns/urn" 11 | "github.com/kpmy/ypk/assert" 12 | "github.com/kpmy/ypk/halt" 13 | "io" 14 | ) 15 | 16 | //Embeddable is any object that can provide []byte data for embedding in document package file (pictures for example) 17 | type Embeddable interface { 18 | MimeType() xmlns.Mime 19 | Reader() io.Reader 20 | } 21 | 22 | //Parts is a list of root "files" of document package 23 | type Parts map[string]*bytes.Buffer 24 | 25 | //Entry is a part of Manifest 26 | type Entry struct { 27 | MediaType string `xml:"manifest:media-type,attr"` 28 | FullPath string `xml:"manifest:full-path,attr"` 29 | } 30 | 31 | //Manifest file structure, contains descriptors for any parts of document 32 | type Manifest struct { 33 | XMLName xml.Name 34 | NS string `xml:"xmlns:manifest,attr"` 35 | Entries []Entry `xml:"manifest:file-entry"` 36 | } 37 | 38 | func (m *Manifest) init(mimetype xmlns.Mime) { 39 | m.XMLName.Local = "manifest:manifest" 40 | m.NS = urn.Manifest 41 | m.Entries = append(m.Entries, Entry{MediaType: string(mimetype), FullPath: "/"}) 42 | } 43 | 44 | func docParts(m model.Model) (ret Parts) { 45 | ret = make(map[string]*bytes.Buffer) 46 | rd := m.NewReader() 47 | rd.Pos(m.Root()) 48 | for !rd.Eol() { 49 | l := rd.Read() 50 | buf := new(bytes.Buffer) 51 | buf.WriteString(xml.Header) 52 | switch l.Name() { 53 | case office.DocumentContent: 54 | ret[xmlns.Content] = buf 55 | case office.DocumentStyles: 56 | ret[xmlns.Styles] = buf 57 | case office.DocumentMeta: 58 | ret[xmlns.Meta] = buf 59 | default: 60 | halt.As(100, l.Name()) 61 | } 62 | enc := xml.NewEncoder(buf) 63 | err := enc.Encode(l) 64 | assert.For(err == nil, 60, err) 65 | } 66 | return 67 | } 68 | 69 | //GeneratePackage builds a zip-archived content of document model and embedded files and writes content to target Writer 70 | func GeneratePackage(m model.Model, embed map[string]Embeddable, out io.Writer, mimetype xmlns.Mime) { 71 | z := zip.NewWriter(out) 72 | defer z.Close() 73 | mime := &zip.FileHeader{Name: xmlns.Mimetype, Method: zip.Store} //файл mimetype не надо сжимать, режим Store 74 | if w, err := z.CreateHeader(mime); err == nil { 75 | bytes.NewBufferString(string(mimetype)).WriteTo(w) 76 | } else { 77 | halt.As(100, err) 78 | } 79 | manifest := &Manifest{} 80 | manifest.init(mimetype) 81 | for k, v := range docParts(m) { 82 | if w, err := z.Create(k); err == nil { 83 | v.WriteTo(w) 84 | manifest.Entries = append(manifest.Entries, Entry{MediaType: xmlns.MimeDefault, FullPath: k}) 85 | } else { 86 | halt.As(100, err) 87 | } 88 | } 89 | for k, v := range embed { 90 | if w, err := z.Create(k); err == nil { 91 | _, err = io.Copy(w, v.Reader()) 92 | assert.For(err == nil, 60) 93 | manifest.Entries = append(manifest.Entries, Entry{MediaType: string(v.MimeType()), FullPath: k}) 94 | } else { 95 | halt.As(100, err) 96 | } 97 | } 98 | if w, err := z.Create(xmlns.Manifest); err == nil { 99 | w.Write([]byte(xml.Header)) 100 | enc := xml.NewEncoder(w) 101 | err = enc.Encode(manifest) 102 | assert.For(err == nil, 60, err) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /mappers/para.go: -------------------------------------------------------------------------------- 1 | package mappers 2 | 3 | import ( 4 | "github.com/kpmy/odf/mappers/attr" 5 | "github.com/kpmy/odf/model" 6 | "github.com/kpmy/odf/xmlns/text" 7 | "github.com/kpmy/ypk/assert" 8 | ) 9 | 10 | //ParaMapper writes and controls text content in document model 11 | type ParaMapper struct { 12 | fm *Formatter 13 | rider model.Writer 14 | } 15 | 16 | func (p *ParaMapper) makePara() { 17 | if pos := p.rider.Pos(); pos.Name() == text.P { 18 | p.rider.Pos(p.fm.root) 19 | } 20 | p.rider.WritePos(New(text.P)) 21 | p.fm.attr.Flush() 22 | p.fm.attr.Fit(text.P, func(a attr.Attributes) { 23 | p.rider.Attr(text.StyleName, a.Name()) 24 | }) 25 | } 26 | 27 | //ConnectTo Formatter that holds document model 28 | func (p *ParaMapper) ConnectTo(fm *Formatter) { 29 | p.fm = fm 30 | p.rider = fm.m.NewWriter() 31 | p.rider.Pos(fm.root) 32 | } 33 | 34 | //WritePara writes text in new paragraph with the most latest text and paragraph attributes set to connected Formatter 35 | func (p *ParaMapper) WritePara(s string) { 36 | if pos := p.rider.Pos(); pos.Name() == text.P { 37 | p.rider.Pos(pos.Parent()) 38 | } 39 | p.rider.WritePos(New(text.P)) 40 | p.fm.attr.Flush() 41 | p.fm.attr.Fit(text.P, func(a attr.Attributes) { 42 | p.rider.Attr(text.StyleName, a.Name()) 43 | }) 44 | p.WriteString(s) 45 | 46 | } 47 | 48 | //WriteString writes a text within existing paragraph or creates new paragraph if symbol \r met 49 | func (p *ParaMapper) WriteString(_s string) { 50 | assert.For(p.fm.ready, 20) 51 | 52 | buf := make([]rune, 0) 53 | count := 0 54 | 55 | flush := func(space bool) { 56 | p.rider.Write(model.Text(string(buf))) 57 | buf = make([]rune, 0) 58 | if space && count > 1 { 59 | w := p.fm.m.NewWriter(p.rider) 60 | w.WritePos(New(text.S)) 61 | w.Attr(text.C, count) 62 | } 63 | } 64 | 65 | grow := func() { 66 | if count > 1 { 67 | flush(true) 68 | } else if count == 1 { 69 | buf = append(buf, ' ') 70 | } 71 | if len(buf) > 0 { 72 | flush(false) 73 | } 74 | count = 0 75 | } 76 | 77 | if p.rider.Pos().Name() != text.P { 78 | p.WritePara(_s) 79 | } else { 80 | p.fm.attr.Flush() 81 | p.fm.attr.Fit(text.Span, func(a attr.Attributes) { 82 | p.rider.WritePos(New(text.Span)) 83 | p.rider.Attr(text.StyleName, a.Name()) 84 | }) 85 | s := []rune(_s) 86 | br := false 87 | for pos := 0; pos < len(s) && s[pos] != 0; { 88 | switch s[pos] { 89 | case ' ': 90 | count++ 91 | case '\n': 92 | grow() 93 | p.rider.Write(New(text.LineBreak)) 94 | case '\r': 95 | grow() 96 | p.fm.attr.Fit(text.Span, func(a attr.Attributes) { 97 | p.rider.Pos(p.rider.Pos().Parent()) 98 | }) 99 | //skip cr+lf 100 | if pos+1 < len(s) && s[pos+1] == '\n' { 101 | pos = pos + 1 102 | } 103 | for pos = pos + 1; pos < len(s); pos++ { 104 | buf = append(buf, s[pos]) 105 | } 106 | p.WritePara(string(buf)) 107 | pos-- 108 | br = true 109 | case '\t': 110 | grow() 111 | p.rider.Write(New(text.Tab)) 112 | default: 113 | if count > 1 { 114 | flush(true) 115 | } else if count == 1 { 116 | buf = append(buf, ' ') 117 | } 118 | count = 0 119 | buf = append(buf, s[pos]) 120 | } 121 | pos++ 122 | } 123 | if !br { 124 | grow() 125 | p.fm.attr.Fit(text.Span, func(a attr.Attributes) { 126 | p.rider.Pos(p.rider.Pos().Parent()) 127 | }) 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /model/stub/simple_tree.go: -------------------------------------------------------------------------------- 1 | //Package stub is an implementation of document model, nodes, attributes and riders, it's a lightweight analog of DOM 2 | //There is no need to create separate type for each ODF document node, because most of them are defined only by it's name, that's why we build a simle tree model where only root nodel of document are special, because they are translated into files of document-package. But they still support Node interface and may be used in other implementations as regular nodes 3 | package stub 4 | 5 | /* 6 | простая реализация модели документа 7 | */ 8 | 9 | import ( 10 | "encoding/xml" 11 | "github.com/kpmy/odf/model" 12 | "github.com/kpmy/ypk/assert" 13 | ) 14 | 15 | type sn struct { 16 | name model.LeafName 17 | attr map[model.AttrName]model.Attribute 18 | children []model.Leaf 19 | parent model.Node 20 | } 21 | 22 | func (n *sn) Attr(name model.AttrName, val ...model.Attribute) model.Attribute { 23 | assert.For(len(val) <= 1, 20, "only one attribute accepted") 24 | assert.For(name != "", 21) 25 | if len(val) == 1 { 26 | if val[0] != nil { 27 | n.attr[name] = val[0] 28 | } else { 29 | delete(n.attr, name) 30 | } 31 | } 32 | return n.attr[name] 33 | } 34 | 35 | func (n *sn) Child(i int) model.Leaf { 36 | assert.For(i < len(n.children), 20) 37 | return n.children[i] 38 | } 39 | 40 | func (n *sn) IndexOf(l model.Leaf) (ret int) { 41 | ret = -1 42 | for i := 0; i < len(n.children) && ret == -1; i++ { 43 | if l == n.children[i] { 44 | ret = i 45 | } 46 | } 47 | return 48 | } 49 | 50 | func (n *sn) NofChild() int { 51 | return len(n.children) 52 | } 53 | 54 | func (n *sn) Name() model.LeafName { 55 | return n.name 56 | } 57 | 58 | func (n *sn) init() { 59 | n.attr = make(map[model.AttrName]model.Attribute) 60 | n.children = make([]model.Leaf, 0) 61 | } 62 | 63 | func (n *sn) Parent(p ...model.Node) model.Node { 64 | if len(p) == 1 { 65 | assert.For(n.parent == nil, 20) 66 | n.parent = p[0] 67 | } 68 | return n.parent 69 | } 70 | 71 | func (n *sn) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { 72 | start.Name.Local = string(n.name) 73 | for k, v := range n.attr { 74 | a, err := v.(xml.MarshalerAttr).MarshalXMLAttr(xml.Name{Local: string(k)}) 75 | assert.For(err == nil, 30, err) 76 | start.Attr = append(start.Attr, a) 77 | } 78 | e.EncodeToken(start) 79 | for _, _v := range n.children { 80 | switch v := _v.(type) { 81 | case *text: 82 | err = e.EncodeToken(xml.CharData(v.data)) 83 | default: 84 | err = e.EncodeElement(v, xml.StartElement{Name: xml.Name{Local: string(v.Name())}}) 85 | } 86 | assert.For(err == nil, 30, err) 87 | } 88 | err = e.EncodeToken(start.End()) 89 | assert.For(err == nil, 30, err) 90 | return err 91 | } 92 | 93 | type sm struct { 94 | root *sn 95 | } 96 | 97 | func (m *sm) NewReader(old ...model.Reader) model.Reader { 98 | r := &sr{base: m, eol: true} 99 | if len(old) == 1 { 100 | r.InitFrom(old[0]) 101 | } 102 | return r 103 | } 104 | 105 | func (m *sm) NewWriter(old ...model.Writer) model.Writer { 106 | w := &sw{base: m} 107 | if len(old) == 1 { 108 | w.InitFrom(old[0]) 109 | } 110 | return w 111 | } 112 | 113 | func (m *sm) Root() model.Node { 114 | return m.root 115 | } 116 | 117 | func nf() func() model.Model { 118 | return func() model.Model { 119 | r := &sn{} 120 | r.init() 121 | return &sm{root: r} 122 | } 123 | } 124 | 125 | func init() { 126 | model.ModelFactory = nf() 127 | } 128 | -------------------------------------------------------------------------------- /mappers/fmt.go: -------------------------------------------------------------------------------- 1 | package mappers 2 | 3 | import ( 4 | "github.com/kpmy/odf/mappers/attr" 5 | "github.com/kpmy/odf/model" 6 | "github.com/kpmy/odf/xmlns" 7 | "github.com/kpmy/odf/xmlns/office" 8 | "github.com/kpmy/ypk/assert" 9 | "github.com/kpmy/ypk/halt" 10 | "reflect" 11 | ) 12 | 13 | //New is shortcut for model.ModelFactory or wrapper 14 | var New func(name model.LeafName) model.Leaf 15 | 16 | //Formatter holds model of document and manages attributes and simple text mapper 17 | //Main goal of Formatter conception is setting attributes before writing content. This allows to optimize the real attributes storage in document model 18 | //Anyway, document model can be modified with any other tools, Formatter is my vision of this kind of tools. 19 | //Represents Mapper in CRM pattern 20 | type Formatter struct { 21 | m model.Model 22 | MimeType xmlns.Mime 23 | attr *Attr 24 | root model.Node //root of document content, not model 25 | ready bool 26 | defaultParaMapper *ParaMapper 27 | } 28 | 29 | //ConnectTo document model, for now it can be only newly created model, Formatter does not make existing content analise 30 | func (f *Formatter) ConnectTo(m model.Model) { 31 | assert.For(m.Root().NofChild() == 0, 20, "only new documents for now") 32 | f.m = m 33 | f.attr = &Attr{} 34 | f.ready = false 35 | } 36 | 37 | //Init document model with empty content basing on MimeType 38 | func (f *Formatter) Init() { 39 | assert.For(f.MimeType != "", 20) 40 | wr := f.m.NewWriter() 41 | wr.Pos(f.m.Root()) 42 | wr.Write(New(office.DocumentMeta)) 43 | f.attr.Init(f.m) 44 | wr.WritePos(New(office.DocumentContent)) 45 | wr.Attr(office.Version, "1.0") 46 | wr.Write(f.attr.ffdc) 47 | wr.Write(f.attr.asc) 48 | wr.WritePos(New(office.Body)) 49 | switch f.MimeType { 50 | case xmlns.MimeText: 51 | f.root = wr.WritePos(New(office.Text)).(model.Node) 52 | case xmlns.MimeSpreadsheet: 53 | f.root = wr.WritePos(New(office.Spreadsheet)).(model.Node) 54 | default: 55 | halt.As(100, f.MimeType) 56 | } 57 | f.defaultParaMapper = new(ParaMapper) 58 | f.defaultParaMapper.ConnectTo(f) 59 | f.ready = true 60 | } 61 | 62 | //WritePara writes text in new paragraph with the most latest text and paragraph attributes set 63 | func (f *Formatter) WritePara(s string) { 64 | assert.For(f.ready, 20) 65 | f.defaultParaMapper.WritePara(s) 66 | } 67 | 68 | //WriteLn writes a line break 69 | func (f *Formatter) WriteLn() { 70 | f.WriteString("\n") 71 | } 72 | 73 | //WriteString writes a text within existing paragraph or creates new paragraph if symbol \r met 74 | func (f *Formatter) WriteString(s string) { 75 | assert.For(f.ready, 20) 76 | f.defaultParaMapper.WriteString(s) 77 | } 78 | 79 | //SetAttr sets any type of attributes to be used in future, only one instance of any typed attributes supported. Attributes are flushed only when real content is written. SetAttr can accept nil value for dropping all attributes 80 | func (f *Formatter) SetAttr(a attr.Attributes) *Formatter { 81 | assert.For(f.ready, 20) 82 | if a != nil { 83 | n := reflect.TypeOf(a).String() 84 | if old := f.attr.OldAttr(a); old != nil { 85 | f.attr.current[n] = old 86 | } else { 87 | c := f.attr.current[n] 88 | if (c == nil) || !c.Equal(a) { 89 | f.attr.stored = false 90 | f.attr.current[n] = a 91 | } 92 | } 93 | } else { 94 | f.attr.reset() 95 | } 96 | return f 97 | } 98 | 99 | //RegisterFont sets the Font Face Declaration item, name later can be used in attr.TextAttributes 100 | func (f *Formatter) RegisterFont(name, fontface string) { 101 | f.attr.RegisterFont(name, fontface) 102 | } 103 | 104 | //Sets the default attributes, that will be used by odf consumer to display non-attributed content (after SetAttr(nil)) 105 | func (f *Formatter) SetDefaults(a ...attr.Attributes) { 106 | f.attr.SetDefaults(a...) 107 | } 108 | 109 | func init() { 110 | New = func(n model.LeafName) model.Leaf { 111 | return model.LeafFactory(n) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /mappers/attr.go: -------------------------------------------------------------------------------- 1 | package mappers 2 | 3 | import ( 4 | "github.com/kpmy/odf/mappers/attr" 5 | "github.com/kpmy/odf/model" 6 | "github.com/kpmy/odf/xmlns/office" 7 | "github.com/kpmy/odf/xmlns/style" 8 | "github.com/kpmy/odf/xmlns/svg" 9 | "github.com/kpmy/odf/xmlns/text" 10 | "github.com/kpmy/ypk/halt" 11 | "strconv" 12 | ) 13 | 14 | //Attr holds a number of style nodes of document model and writes attributes. Also it holds cache of recently used attributes for reuse 15 | type Attr struct { 16 | doc model.Model 17 | ds model.Leaf //document styles 18 | ffd model.Leaf //font-face decls 19 | as model.Leaf //automatic styles 20 | ms model.Leaf //master styles 21 | asc model.Leaf //automatic styles 22 | ffdc model.Leaf //font-face decls 23 | s model.Leaf 24 | current map[string]attr.Attributes 25 | old map[string]attr.Attributes 26 | fonts map[string]model.Leaf 27 | stored bool 28 | count int 29 | } 30 | 31 | func (a *Attr) nextName() string { 32 | a.count++ 33 | return "auto" + strconv.Itoa(a.count) 34 | } 35 | 36 | func (a *Attr) reset() { 37 | a.stored = true 38 | a.current = make(map[string]attr.Attributes) 39 | } 40 | 41 | //Init called when empty document is initialized 42 | func (a *Attr) Init(m model.Model) { 43 | a.doc = m 44 | wr := a.doc.NewWriter() 45 | wr.Pos(a.doc.Root()) 46 | a.ds = wr.WritePos(New(office.DocumentStyles)) 47 | wr.Attr(office.Version, "1.0") 48 | a.ffd = wr.WritePos(New(office.FontFaceDecls)) 49 | wr.Pos(a.ds) 50 | a.as = wr.WritePos(New(office.AutomaticStyles)) 51 | wr.Pos(a.ds) 52 | a.ms = wr.WritePos(New(office.MasterStyles)) 53 | wr.Pos(a.ds) 54 | a.asc = New(office.AutomaticStyles) 55 | a.ffdc = New(office.FontFaceDecls) 56 | a.old = make(map[string]attr.Attributes) 57 | a.fonts = make(map[string]model.Leaf) 58 | a.reset() 59 | } 60 | 61 | //Fit finds appropriate attributes for given name and calls closure for applying attributes 62 | func (a *Attr) Fit(n model.LeafName, callback func(a attr.Attributes)) { 63 | fit := make(map[model.LeafName]attr.Attributes) 64 | for _, v := range a.current { 65 | fit[v.Fit()] = v 66 | } 67 | if a := fit[n]; a != nil { 68 | callback(a) 69 | } 70 | } 71 | 72 | //RegisterFont writes font entry to Font Face Declaration section 73 | func (a *Attr) RegisterFont(name, fontface string) { 74 | if a.fonts[name] == nil { 75 | wr := a.doc.NewWriter() 76 | wr.Pos(a.ffd) 77 | wr.WritePos(New(style.FontFace)) 78 | wr.Attr(style.Name, name) 79 | wr.Attr(svg.FontFamily, fontface) 80 | //TODO deep copy fontface node 81 | wr.Pos(a.ffdc) 82 | a.fonts[name] = wr.WritePos(New(style.FontFace)) 83 | wr.Attr(style.Name, name) 84 | wr.Attr(svg.FontFamily, name) 85 | } 86 | } 87 | 88 | //Flush writes styles to document model 89 | func (a *Attr) Flush() { 90 | if !a.stored { 91 | wr := a.doc.NewWriter() 92 | for _, v := range a.current { 93 | if n := v.Name(); n == "" && a.old[n] == nil { 94 | v.Name(a.nextName()) 95 | wr.Pos(a.asc) 96 | wr.WritePos(New(style.Style)) 97 | wr.Attr(style.Name, v.Name()) 98 | v.Write(a.doc.NewWriter(wr)) 99 | a.old[v.Name()] = v 100 | } else if n != "" && a.old[n] == nil { 101 | halt.As(100, v.Name()) 102 | } 103 | } 104 | a.stored = true 105 | } 106 | } 107 | 108 | //OldAttr return already written attributes for new attribute if their contents equals 109 | func (a *Attr) OldAttr(n attr.Attributes) attr.Attributes { 110 | for _, v := range a.old { 111 | if v.Equal(n) { 112 | return v 113 | } 114 | } 115 | return nil 116 | } 117 | 118 | //SetDefaults sets default attributes of document, only TextAttributes and ParagraphAttributes supported by now 119 | func (a *Attr) SetDefaults(al ...attr.Attributes) { 120 | wr := a.doc.NewWriter() 121 | wr.Pos(a.ds) 122 | if a.s == nil { 123 | a.s = wr.WritePos(New(office.Styles)) 124 | for _, x := range al { 125 | switch x.Fit() { 126 | case text.P, text.Span: 127 | wr.WritePos(New(style.DefaultStyle)) 128 | x.Write(a.doc.NewWriter(wr)) 129 | wr.Attr(style.Family, style.FamilyParagraph) 130 | default: 131 | halt.As(100, x.Fit()) 132 | } 133 | } 134 | } else { 135 | wr.Delete(a.s) 136 | a.s = nil 137 | a.SetDefaults(al...) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /model/stub/simple_nodes.go: -------------------------------------------------------------------------------- 1 | package stub 2 | 3 | import ( 4 | "encoding/xml" 5 | "github.com/kpmy/odf/model" 6 | "github.com/kpmy/odf/xmlns" 7 | "github.com/kpmy/odf/xmlns/office" 8 | "github.com/kpmy/odf/xmlns/urn" 9 | "github.com/kpmy/ypk/assert" 10 | ) 11 | 12 | type root struct { 13 | inner *sn 14 | } 15 | 16 | func (r *root) Name() model.LeafName { 17 | return r.inner.Name() 18 | } 19 | 20 | func (r *root) Attr(n model.AttrName, a ...model.Attribute) model.Attribute { 21 | return r.inner.Attr(n, a...) 22 | } 23 | 24 | func (r *root) Child(i int) model.Leaf { 25 | return r.inner.Child(i) 26 | } 27 | 28 | func (r *root) IndexOf(l model.Leaf) int { 29 | return r.inner.IndexOf(l) 30 | } 31 | 32 | func (r *root) NofChild() int { 33 | return r.inner.NofChild() 34 | } 35 | 36 | func (r *root) Parent(...model.Node) model.Node { return nil } 37 | 38 | type text struct { 39 | data string 40 | parent model.Node 41 | } 42 | 43 | func (t *text) Name() model.LeafName { panic(100) } 44 | 45 | func (t *text) Attr(model.AttrName, ...model.Attribute) model.Attribute { panic(100) } 46 | 47 | func (t *text) Parent(p ...model.Node) model.Node { 48 | if len(p) == 1 { 49 | assert.For(t.parent == nil, 20) 50 | t.parent = p[0] 51 | } 52 | return t.parent 53 | } 54 | 55 | func (r *root) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { 56 | start.Name.Local = string(r.inner.Name()) 57 | 58 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSanim}, Value: urn.Anim}) 59 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSchart}, Value: urn.Chart}) 60 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSconfig}, Value: urn.Config}) 61 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSdc}, Value: urn.Dc}) 62 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSdr3d}, Value: urn.Dr3d}) 63 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSdraw}, Value: urn.Draw}) 64 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSfo}, Value: urn.Fo}) 65 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSform}, Value: urn.Form}) 66 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSmath}, Value: urn.Math}) 67 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSmeta}, Value: urn.Meta}) 68 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSnumber}, Value: urn.Number}) 69 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSoffice}, Value: urn.Office}) 70 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSpresentation}, Value: urn.Presentation}) 71 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSscript}, Value: urn.Script}) 72 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSsmil}, Value: urn.Smil}) 73 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSstyle}, Value: urn.Style}) 74 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSsvg}, Value: urn.Svg}) 75 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NStable}, Value: urn.Table}) 76 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NStext}, Value: urn.Text}) 77 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSxforms}, Value: urn.Xforms}) 78 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: xmlns.NSxlink}, Value: urn.Xlink}) 79 | err = e.EncodeElement(r.inner, start) 80 | return err 81 | } 82 | 83 | func lf() func(x model.LeafName) model.Leaf { 84 | return func(x model.LeafName) (ret model.Leaf) { 85 | switch x { 86 | case office.DocumentContent, office.DocumentMeta, office.DocumentStyles: 87 | r := &root{} 88 | r.inner = &sn{name: x} 89 | r.inner.init() 90 | ret = r 91 | default: 92 | r := &sn{name: x} 93 | r.init() 94 | ret = r 95 | } 96 | assert.For(ret != nil, 60) 97 | return ret 98 | } 99 | } 100 | 101 | func tf() func(s string) model.Leaf { 102 | return func(s string) model.Leaf { 103 | return &text{data: s} 104 | } 105 | } 106 | 107 | func init() { 108 | model.LeafFactory = lf() 109 | model.Text = tf() 110 | } 111 | -------------------------------------------------------------------------------- /mappers/attr/table.go: -------------------------------------------------------------------------------- 1 | package attr 2 | 3 | import ( 4 | "github.com/kpmy/odf/model" 5 | "github.com/kpmy/odf/xmlns/fo" 6 | "github.com/kpmy/odf/xmlns/style" 7 | "github.com/kpmy/odf/xmlns/table" 8 | ) 9 | 10 | //TableAttributes is a Table Family style fluent builder 11 | type TableAttributes struct { 12 | named 13 | easy 14 | } 15 | 16 | func (t *TableAttributes) Equal(_a Attributes) (ok bool) { 17 | a, ok := _a.(*TableAttributes) 18 | if ok { 19 | ok = t.equal(&a.easy) 20 | } 21 | return 22 | } 23 | 24 | func (t *TableAttributes) Fit() model.LeafName { return table.Table } 25 | 26 | func (t *TableAttributes) Write(wr model.Writer) { 27 | wr.Attr(style.Family, style.FamilyTable) 28 | wr.WritePos(New(style.TableProperties)) 29 | t.apply(wr) 30 | } 31 | 32 | //BorderModel of table (table.BorderModelCollapsing, table.BorderModelSeparating 33 | func (t *TableAttributes) BorderModel(m string) *TableAttributes { 34 | t.put(table.BorderModel, m, nil) 35 | return t 36 | } 37 | 38 | //AlignLeft table on page 39 | func (t *TableAttributes) AlignLeft() *TableAttributes { 40 | t.put(table.Align, table.AlignLeft, nil) 41 | return t 42 | } 43 | 44 | //AlignRight table on page 45 | func (t *TableAttributes) AlignRight() *TableAttributes { 46 | t.put(table.Align, table.AlignRight, nil) 47 | return t 48 | } 49 | 50 | //AlignCenter table on page 51 | func (t *TableAttributes) AlignCenter() *TableAttributes { 52 | t.put(table.Align, table.AlignCenter, nil) 53 | return t 54 | } 55 | 56 | //Width of whole table 57 | func (t *TableAttributes) Width(w float64) *TableAttributes { 58 | t.put(style.Width, w, nil) 59 | return t 60 | } 61 | 62 | //TableRowAttributes represents Table Row Family style fluent builder 63 | type TableRowAttributes struct { 64 | named 65 | easy 66 | } 67 | 68 | func (t *TableRowAttributes) Equal(_a Attributes) (ok bool) { 69 | a, ok := _a.(*TableRowAttributes) 70 | if ok { 71 | ok = t.equal(&a.easy) 72 | } 73 | return 74 | } 75 | 76 | func (t *TableRowAttributes) Fit() model.LeafName { return table.TableRow } 77 | 78 | func (t *TableRowAttributes) Write(wr model.Writer) { 79 | wr.Attr(style.Family, style.FamilyTableRow) 80 | wr.WritePos(New(style.TableRowProperties)) 81 | t.apply(wr) 82 | } 83 | 84 | //UseOptimalRowHeight allows to auto-height rows when displayed 85 | func (t *TableRowAttributes) UseOptimalRowHeight() *TableRowAttributes { 86 | t.put(style.UseOptimalRowHeight, true, triggerBoolAttr(style.UseOptimalRowHeight)) 87 | return t 88 | } 89 | 90 | type TableColumnAttributes struct { 91 | named 92 | easy 93 | } 94 | 95 | //TableColumnAttributes represents Table Column Family style fluent builder 96 | func (t *TableColumnAttributes) Equal(_a Attributes) (ok bool) { 97 | a, ok := _a.(*TableColumnAttributes) 98 | if ok { 99 | ok = t.equal(&a.easy) 100 | } 101 | return 102 | } 103 | 104 | func (t *TableColumnAttributes) Fit() model.LeafName { return table.TableColumn } 105 | 106 | func (t *TableColumnAttributes) Write(wr model.Writer) { 107 | wr.Attr(style.Family, style.FamilyTableColumn) 108 | wr.WritePos(New(style.TableColumnProperties)) 109 | t.apply(wr) 110 | } 111 | 112 | //UseOptimalColumnWidth allows to auto-width columns when displayed 113 | func (t *TableColumnAttributes) UseOptimalColumnWidth() *TableColumnAttributes { 114 | t.put(style.UseOptimalColumnWidth, true, triggerBoolAttr(style.UseOptimalColumnWidth)) 115 | return t 116 | } 117 | 118 | //TableCellAttributes represents Table Cell Family style fluent builder 119 | type TableCellAttributes struct { 120 | named 121 | easy 122 | } 123 | 124 | func (t *TableCellAttributes) Equal(_a Attributes) (ok bool) { 125 | a, ok := _a.(*TableCellAttributes) 126 | if ok { 127 | ok = t.equal(&a.easy) 128 | } 129 | return 130 | } 131 | 132 | func (t *TableCellAttributes) Fit() model.LeafName { return table.TableCell } 133 | 134 | func (t *TableCellAttributes) Write(wr model.Writer) { 135 | wr.Attr(style.Family, style.FamilyTableCell) 136 | wr.WritePos(New(style.TableCellProperties)) 137 | t.apply(wr) 138 | } 139 | 140 | //Border sets attributes for all borders (left, right, top, bottom) 141 | func (t *TableCellAttributes) Border(b Border) *TableCellAttributes { 142 | t.put(fo.BorderRight, b.String(), nil) 143 | t.put(fo.BorderLeft, b.String(), nil) 144 | t.put(fo.BorderTop, b.String(), nil) 145 | t.put(fo.BorderBottom, b.String(), nil) 146 | return t 147 | } 148 | -------------------------------------------------------------------------------- /demo/report.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "github.com/kpmy/golorem" 6 | "github.com/kpmy/odf/generators" 7 | "github.com/kpmy/odf/mappers" 8 | "github.com/kpmy/odf/mappers/attr" 9 | "github.com/kpmy/odf/model" 10 | _ "github.com/kpmy/odf/model/stub" // необходимо 11 | "github.com/kpmy/odf/xmlns" 12 | "github.com/kpmy/odf/xmlns/fo" 13 | "github.com/kpmy/ypk/assert" 14 | "image/color" 15 | "io" 16 | "math/rand" 17 | "os" 18 | "runtime" 19 | "strconv" 20 | "strings" 21 | "sync" 22 | "time" 23 | ) 24 | 25 | func report(suffix string, fm *mappers.Formatter) { 26 | { //first page 27 | fm.WriteString("\n\n\n\n\n\n\n\n\n\n") 28 | fm.SetAttr(new(attr.TextAttributes).Size(32).Bold()).SetAttr(new(attr.ParagraphAttributes).AlignCenter()) 29 | fm.WritePara("Periodic report") 30 | fm.SetAttr(nil).SetAttr(new(attr.TextAttributes).Italic()) 31 | fm.WritePara("Report:\t") 32 | fm.SetAttr(new(attr.TextAttributes).Bold()) 33 | fm.WriteString(suffix + "\n") 34 | fm.SetAttr(new(attr.TextAttributes).Italic()) 35 | fm.WriteString("Date:\t") 36 | fm.SetAttr(new(attr.TextAttributes).Bold()) 37 | fm.WriteString(time.Now().String()) 38 | fm.SetAttr(new(attr.ParagraphAttributes).PageBreak()) 39 | fm.WritePara("") 40 | fm.SetAttr(nil) 41 | } 42 | para := func() { 43 | fm.SetAttr(new(attr.TextAttributes).Bold().Size(18)) 44 | fm.WritePara(strings.ToUpper(lorem.Word(5, 15))) 45 | fm.WriteLn() 46 | fm.SetAttr(nil) 47 | para := lorem.Paragraph(5, 10) 48 | fm.WritePara("\t" + para + "\n\n") 49 | } 50 | for i := 0; i < 5; i++ { 51 | para() 52 | } 53 | { //huge table 54 | fm.SetAttr(new(attr.TextAttributes).Bold().Size(18)) 55 | fm.WritePara("TABLE 50x5") 56 | fm.SetAttr(nil) 57 | fm.SetAttr(new(attr.TableCellAttributes).Border(attr.Border{Width: 0.01, Color: color.Black, Style: fo.Solid})) 58 | tm := &mappers.TableMapper{} 59 | tm.ConnectTo(fm) 60 | tm.Write("test", 50+1, 5) //50+header row 61 | tt := tm.List["test"] 62 | tm.Span(tt, 0, 0, 1, 5) 63 | fm.SetAttr(new(attr.ParagraphAttributes).AlignCenter()).SetAttr(new(attr.TextAttributes).Bold()) 64 | tm.Pos(tt, 0, 0).WriteString("Header") 65 | fm.SetAttr(nil) 66 | for i := 1; i < 51; i++ { 67 | for j := 0; j < 5; j++ { 68 | if j == 0 { 69 | fm.SetAttr(new(attr.TextAttributes).Bold()) 70 | } else { 71 | fm.SetAttr(nil) 72 | } 73 | tm.Pos(tt, i, j).WriteString(strconv.Itoa(i * j)) 74 | } 75 | } 76 | } 77 | { //appendix 78 | fm.RegisterFont("Courier New", "Courier New") // may not work in Linux/MacOS 79 | fm.SetAttr(nil).SetAttr(new(attr.ParagraphAttributes).PageBreak()) 80 | fm.SetAttr(new(attr.TextAttributes).Size(18).Bold()) 81 | fm.WritePara("Appendix A.\nListing of report.go") 82 | fm.WriteLn() 83 | fm.SetAttr(nil).SetAttr(new(attr.TextAttributes).FontFace("Courier New").Size(6)) 84 | if f, err := os.Open("report.go"); err == nil { 85 | defer f.Close() 86 | buf := bytes.NewBuffer(nil) 87 | io.Copy(buf, f) 88 | fm.WritePara(string(buf.Bytes())) 89 | } else { 90 | fm.WritePara("File not found.") 91 | } 92 | } 93 | } 94 | 95 | func do(wg *sync.WaitGroup) { 96 | src := rand.New(rand.NewSource(time.Now().UnixNano() + rand.Int63())) 97 | suffix := strconv.Itoa(src.Int()) 98 | output, _ := os.OpenFile("test-"+suffix+".odf", os.O_CREATE|os.O_WRONLY, 0666) 99 | m := model.ModelFactory() 100 | fm := &mappers.Formatter{} 101 | fm.ConnectTo(m) 102 | fm.MimeType = xmlns.MimeText 103 | fm.Init() 104 | embed := make(map[string]generators.Embeddable) 105 | { 106 | const ImagePng xmlns.Mime = "image/png" 107 | img, _ := os.Open("10414104.png") 108 | d := mappers.NewDraw(img, ImagePng) 109 | fm.SetAttr(new(attr.ParagraphAttributes).AlignRight()) 110 | url := d.WriteTo(fm, "Two Gophers", 4.0, 4.0) //magic? real size of `.png` in cm 111 | embed[url] = d 112 | } 113 | fm.SetAttr(new(attr.TextAttributes).Bold()) 114 | fm.WriteString("\nSo Strange inc.") 115 | fm.WriteString("\n" + lorem.Email()) 116 | fm.WriteString("\n" + lorem.Url()) 117 | fm.SetAttr(nil) 118 | report(suffix, fm) 119 | generators.GeneratePackage(m, embed, output, fm.MimeType) 120 | assert.For(output.Close() == nil, 20) 121 | wg.Done() 122 | } 123 | 124 | func main() { 125 | runtime.GOMAXPROCS(4) 126 | wg := &sync.WaitGroup{} 127 | for i := 0; i < 1; i++ { // concurrency support 128 | go do(wg) 129 | wg.Add(1) 130 | } 131 | wg.Wait() 132 | } 133 | -------------------------------------------------------------------------------- /model/stub/simple_riders.go: -------------------------------------------------------------------------------- 1 | package stub 2 | 3 | import ( 4 | "github.com/kpmy/odf/model" 5 | "github.com/kpmy/odf/xmlns" 6 | "github.com/kpmy/ypk/assert" 7 | "github.com/kpmy/ypk/halt" 8 | "image/color" 9 | "reflect" 10 | ) 11 | 12 | type sr struct { 13 | base *sm 14 | pos model.Leaf 15 | eol bool 16 | this model.Leaf 17 | } 18 | 19 | func (r *sr) Base() model.Model { 20 | return r.base 21 | } 22 | 23 | func (r *sr) InitFrom(old model.Reader) { 24 | panic(126) 25 | } 26 | 27 | func (r *sr) Pos(p ...model.Leaf) model.Leaf { 28 | if len(p) == 1 { 29 | r.pos = p[0] 30 | if n, ok := r.pos.(model.Node); ok { 31 | r.eol = n.NofChild() == 0 32 | } else { 33 | r.eol = true 34 | } 35 | } 36 | return r.pos 37 | } 38 | 39 | func (r *sr) Read() model.Leaf { 40 | assert.For(r.pos != nil && !r.eol, 20) 41 | n, ok := r.pos.(model.Node) 42 | assert.For(ok, 21) 43 | idx := 0 44 | if r.this != nil { 45 | idx = n.IndexOf(r.this) 46 | idx++ 47 | } 48 | if idx < n.NofChild() { 49 | r.this = n.Child(idx) 50 | } else { 51 | r.eol = true 52 | } 53 | return r.this 54 | } 55 | 56 | func (r *sr) Eol() bool { 57 | return r.eol 58 | } 59 | 60 | type sw struct { 61 | base *sm 62 | pos model.Leaf 63 | } 64 | 65 | func (w *sw) Base() model.Model { 66 | return w.base 67 | } 68 | 69 | func (w *sw) InitFrom(old model.Writer) { 70 | if old != nil { 71 | w.Pos(old.Pos()) 72 | } 73 | } 74 | 75 | func (w *sw) Pos(p ...model.Leaf) model.Leaf { 76 | if len(p) == 1 { 77 | w.pos = p[0] 78 | } 79 | return w.pos 80 | } 81 | 82 | func thisNode(l model.Leaf) model.Node { 83 | if _n, ok := l.(model.Node); ok { 84 | switch n := _n.(type) { 85 | case *sn: 86 | return n 87 | case *root: 88 | return n.inner 89 | default: 90 | halt.As(100, reflect.TypeOf(n)) 91 | } 92 | } 93 | return nil 94 | } 95 | 96 | func (w *sw) Write(l model.Leaf, after ...model.Leaf) { 97 | assert.For(l != nil, 20) 98 | assert.For(w.pos != nil, 21) 99 | var splitter model.Leaf 100 | split := false 101 | if len(after) == 1 { 102 | splitter = after[0] 103 | split = true 104 | } 105 | add := func(source []model.Leaf, x model.Leaf) (ret []model.Leaf) { 106 | var ( 107 | front []model.Leaf 108 | tail []model.Leaf 109 | ) 110 | switch { 111 | case split && splitter == nil: 112 | tail = source 113 | case split && splitter != nil: 114 | found := false 115 | for _, i := range source { 116 | if !found { 117 | front = append(front, i) 118 | found = i == splitter 119 | } else { 120 | tail = append(tail, i) 121 | } 122 | } 123 | default: 124 | front = source 125 | } 126 | ret = append(ret, front...) 127 | ret = append(ret, x) 128 | ret = append(ret, tail...) 129 | return 130 | } 131 | if _n, ok := w.pos.(model.Node); ok { 132 | switch n := _n.(type) { 133 | case *sn: 134 | n.children = add(n.children, l) 135 | l.Parent(n) 136 | case *root: 137 | n.inner.children = add(n.inner.children, l) 138 | l.Parent(n.inner) 139 | default: 140 | halt.As(100, reflect.TypeOf(n)) 141 | } 142 | } 143 | } 144 | 145 | func (w *sw) Delete(l model.Leaf) { 146 | del := func(l []model.Leaf, x model.Leaf) (ret []model.Leaf) { 147 | for _, i := range l { 148 | if i != x { 149 | ret = append(ret, i) 150 | } 151 | } 152 | return 153 | } 154 | assert.For(l != nil, 20) 155 | assert.For(l.Parent() == thisNode(w.pos), 21, l.Parent(), w.pos.(model.Node)) 156 | switch n := thisNode(w.pos).(type) { 157 | case *sn: 158 | n.children = del(n.children, l) 159 | case *root: 160 | n.inner.children = del(n.inner.children, l) 161 | default: 162 | halt.As(100, reflect.TypeOf(n)) 163 | } 164 | 165 | } 166 | 167 | func (w *sw) WritePos(l model.Leaf, after ...model.Leaf) model.Leaf { 168 | w.Write(l, after...) 169 | return w.Pos(l) 170 | } 171 | 172 | func validateAttr(n model.AttrName, val string) { 173 | values := xmlns.Enums[n] 174 | found := false 175 | for _, v := range values { 176 | if v == val { 177 | found = true 178 | } 179 | } 180 | assert.For(found, 60, n, val) 181 | } 182 | 183 | func castAttr(n model.AttrName, i interface{}) (ret model.Attribute) { 184 | if i == nil { 185 | return nil 186 | } 187 | typ := xmlns.Typed[n] 188 | switch typ { 189 | case xmlns.NONE, xmlns.STRING: 190 | ret = &StringAttr{Value: i.(string)} 191 | case xmlns.INT: 192 | ret = &IntAttr{Value: i.(int)} 193 | case xmlns.ENUM: 194 | validateAttr(n, i.(string)) 195 | ret = &StringAttr{Value: i.(string)} 196 | case xmlns.MEASURE: 197 | ret = &MeasureAttr{Value: i.(float64)} 198 | case xmlns.COLOR: 199 | ret = &ColorAttr{Value: i.(color.Color)} 200 | case xmlns.BOOL: 201 | ret = &BoolAttr{Value: i.(bool)} 202 | default: 203 | halt.As(100, typ, reflect.TypeOf(i)) 204 | } 205 | return ret 206 | } 207 | 208 | func (w *sw) Attr(n model.AttrName, val interface{}) model.Writer { 209 | w.pos.Attr(n, castAttr(n, val)) 210 | return w 211 | } 212 | -------------------------------------------------------------------------------- /odf_test.go: -------------------------------------------------------------------------------- 1 | package odf 2 | 3 | import ( 4 | "github.com/kpmy/odf/generators" 5 | "github.com/kpmy/odf/mappers" 6 | "github.com/kpmy/odf/mappers/attr" 7 | "github.com/kpmy/odf/model" 8 | _ "github.com/kpmy/odf/model/stub" 9 | "github.com/kpmy/odf/xmlns" 10 | "github.com/kpmy/odf/xmlns/fo" 11 | "github.com/kpmy/odf/xmlns/table" 12 | "github.com/kpmy/ypk/assert" 13 | "image/color" 14 | "os" 15 | "testing" 16 | ) 17 | 18 | func TestModel(t *testing.T) { 19 | m := model.ModelFactory() 20 | if m == nil { 21 | t.Error("model is nil") 22 | } 23 | w := m.NewWriter() 24 | if w == nil { 25 | t.Error("writer is nil") 26 | } 27 | } 28 | 29 | func TestMappers(t *testing.T) { 30 | m := model.ModelFactory() 31 | fm := &mappers.Formatter{} 32 | fm.ConnectTo(m) 33 | fm.MimeType = xmlns.MimeText 34 | fm.Init() 35 | } 36 | 37 | func TestGenerators(t *testing.T) { 38 | output, _ := os.OpenFile("test-basics.odf", os.O_CREATE|os.O_WRONLY, 0666) 39 | m := model.ModelFactory() 40 | fm := &mappers.Formatter{} 41 | fm.ConnectTo(m) 42 | fm.MimeType = xmlns.MimeText 43 | fm.Init() 44 | generators.GeneratePackage(m, nil, output, fm.MimeType) 45 | assert.For(output.Close() == nil, 20) 46 | } 47 | 48 | func TestStructure(t *testing.T) { 49 | output, _ := os.OpenFile("test-text.odf", os.O_CREATE|os.O_WRONLY, 0666) 50 | m := model.ModelFactory() 51 | fm := &mappers.Formatter{} 52 | fm.ConnectTo(m) 53 | fm.MimeType = xmlns.MimeText 54 | fm.Init() 55 | fm.WriteString("Hello, World! \t \n \r фыва фыва \n фыва") 56 | generators.GeneratePackage(m, nil, output, fm.MimeType) 57 | assert.For(output.Close() == nil, 20) 58 | } 59 | 60 | func TestStylesMechanism(t *testing.T) { 61 | output, _ := os.OpenFile("test-styles.odf", os.O_CREATE|os.O_WRONLY, 0666) 62 | m := model.ModelFactory() 63 | fm := &mappers.Formatter{} 64 | fm.ConnectTo(m) 65 | fm.MimeType = xmlns.MimeText 66 | fm.Init() 67 | fm.RegisterFont("Arial", "Arial") 68 | fm.RegisterFont("Courier New", "Courier New") 69 | fm.SetDefaults(new(attr.TextAttributes).Size(18).FontFace("Courier New")) 70 | fm.SetDefaults(new(attr.TextAttributes).Size(16).FontFace("Courier New")) 71 | fm.WriteString("Hello, World!\n") 72 | fm.SetAttr(new(attr.TextAttributes).Size(32).FontFace("Arial")) 73 | fm.WriteString(`Hello, Go!`) 74 | fm.SetAttr(new(attr.TextAttributes).Size(36).FontFace("Courier New").Bold().Italic()) 75 | fm.WriteString(` Hello, Again!`) 76 | fm.SetAttr(new(attr.TextAttributes).Size(32).FontFace("Arial")) //test attribute cache 77 | fm.SetAttr(new(attr.TextAttributes).Size(32).FontFace("Arial").Color(color.RGBA{0x00, 0xff, 0xff, 0xff})) 78 | fm.WriteString("\nNo, not you again!") 79 | fm.SetAttr(new(attr.ParagraphAttributes).AlignRight().PageBreak()) 80 | fm.WritePara("Page break!\r") 81 | fm.SetAttr(nil) 82 | fm.WriteString(`Hello, Пщ!`) 83 | generators.GeneratePackage(m, nil, output, fm.MimeType) 84 | assert.For(output.Close() == nil, 20) 85 | } 86 | 87 | func TestTables(t *testing.T) { 88 | table := func(fm *mappers.Formatter) { 89 | tm := &mappers.TableMapper{} 90 | tm.ConnectTo(fm) 91 | tm.Write("test", 5, 10) 92 | tt := tm.List["test"] 93 | tm.WriteColumns(tt, 4) 94 | tm.WriteRows(tt, 3) 95 | tm.Span(tt, 1, 2, 1, 3) 96 | tm.Pos(tt, 0, 0).WritePara("Hello, table world!") 97 | tm.Pos(tt, 1, 2).WritePara("Hello, table world!") 98 | } 99 | { 100 | output, _ := os.OpenFile("test-odt-tables.odf", os.O_CREATE|os.O_WRONLY, 0666) 101 | m := model.ModelFactory() 102 | fm := &mappers.Formatter{} 103 | fm.ConnectTo(m) 104 | fm.MimeType = xmlns.MimeText 105 | fm.Init() 106 | table(fm) 107 | generators.GeneratePackage(m, nil, output, fm.MimeType) 108 | assert.For(output.Close() == nil, 20) 109 | } 110 | { 111 | output, _ := os.OpenFile("test-ods-tables.odf", os.O_CREATE|os.O_WRONLY, 0666) 112 | m := model.ModelFactory() 113 | fm := &mappers.Formatter{} 114 | fm.ConnectTo(m) 115 | fm.MimeType = xmlns.MimeSpreadsheet 116 | fm.Init() 117 | table(fm) 118 | generators.GeneratePackage(m, nil, output, fm.MimeType) 119 | assert.For(output.Close() == nil, 20) 120 | } 121 | } 122 | 123 | func TestDraw(t *testing.T) { 124 | const ImagePng xmlns.Mime = "image/png" 125 | output, _ := os.OpenFile("test-draw.odf", os.O_CREATE|os.O_WRONLY, 0666) 126 | m := model.ModelFactory() 127 | fm := &mappers.Formatter{} 128 | fm.ConnectTo(m) 129 | fm.MimeType = xmlns.MimeText 130 | fm.Init() 131 | embed := make(map[string]generators.Embeddable) 132 | { 133 | img, _ := os.Open("2go.png") 134 | d := mappers.NewDraw(img, ImagePng) 135 | url := d.WriteTo(fm, "Two Gophers", 6.07, 3.53) //magic? real size of `project.png` 136 | embed[url] = d 137 | } 138 | generators.GeneratePackage(m, embed, output, fm.MimeType) 139 | assert.For(output.Close() == nil, 20) 140 | } 141 | 142 | func TestTableStyles(t *testing.T) { 143 | output, _ := os.OpenFile("test-table-styles.odf", os.O_CREATE|os.O_WRONLY, 0666) 144 | m := model.ModelFactory() 145 | fm := &mappers.Formatter{} 146 | fm.ConnectTo(m) 147 | fm.MimeType = xmlns.MimeText 148 | fm.Init() 149 | 150 | fm.SetAttr(new(attr.TableAttributes).BorderModel(table.BorderModelCollapsing).AlignCenter().Width(10.0)) 151 | fm.SetAttr(new(attr.TableRowAttributes).UseOptimalRowHeight()).SetAttr(new(attr.TableColumnAttributes).UseOptimalColumnWidth()) 152 | fm.SetAttr(new(attr.TableCellAttributes).Border(attr.Border{Width: 0.01, Color: color.Black, Style: fo.Solid})) 153 | tm := &mappers.TableMapper{} 154 | tm.ConnectTo(fm) 155 | tm.Write("test", 5, 10) 156 | tt := tm.List["test"] 157 | tm.Pos(tt, 0, 0).WriteString("Hello!") 158 | 159 | generators.GeneratePackage(m, nil, output, fm.MimeType) 160 | assert.For(output.Close() == nil, 20) 161 | } 162 | -------------------------------------------------------------------------------- /mappers/table.go: -------------------------------------------------------------------------------- 1 | package mappers 2 | 3 | import ( 4 | "github.com/kpmy/odf/mappers/attr" 5 | "github.com/kpmy/odf/model" 6 | "github.com/kpmy/odf/xmlns/table" 7 | "github.com/kpmy/ypk/assert" 8 | ) 9 | 10 | //Table structure holds the table structure and identifies it for TableMapper 11 | type Table struct { 12 | Rows int 13 | Columns int 14 | Root model.Leaf 15 | rowCache, colsCache []model.Leaf 16 | cellCache [][]model.Leaf 17 | } 18 | 19 | //TableMapper writes and manages tables in document model 20 | type TableMapper struct { 21 | List map[string]*Table 22 | fm *Formatter 23 | } 24 | 25 | //Ready or not 26 | func (t *TableMapper) Ready() bool { 27 | return t.fm != nil && t.fm.ready 28 | } 29 | 30 | func (t *TableMapper) newWriter(old ...model.Writer) model.Writer { 31 | return t.fm.m.NewWriter(old...) 32 | } 33 | 34 | //ConnectTo any valid Formatter and it's document model 35 | func (t *TableMapper) ConnectTo(fm *Formatter) { 36 | t.fm = fm 37 | t.List = make(map[string]*Table) 38 | } 39 | 40 | //Write a Table with given name and dimensions, table object is stored internally and can be accessed by name, latest set TableAttributes, TableRowAttributes, TableColumnAttributes and TableCellAttributes are used 41 | func (t *TableMapper) Write(name string, rows, cols int) { 42 | assert.For(t.Ready(), 20) 43 | assert.For(name != "" && t.List[name] == nil, 21) 44 | t.fm.attr.Flush() 45 | this := &Table{Rows: rows, Columns: cols} 46 | t.List[name] = this 47 | wr := t.newWriter() 48 | wr.Pos(t.fm.root) 49 | this.Root = wr.WritePos(New(table.Table)) 50 | wr.Attr(table.Name, name) 51 | t.fm.attr.Fit(table.Table, func(a attr.Attributes) { 52 | wr.Attr(table.StyleName, a.Name()) 53 | }) 54 | for i := 0; i < this.Columns; i++ { 55 | col := New(table.TableColumn) 56 | this.colsCache = append(this.colsCache, col) 57 | this.cellCache = append(this.cellCache, make([]model.Leaf, 0)) 58 | wr.Write(col) 59 | cwr := t.newWriter(wr) 60 | cwr.Pos(col) 61 | t.fm.attr.Fit(table.TableColumn, func(a attr.Attributes) { 62 | cwr.Attr(table.StyleName, a.Name()) 63 | }) 64 | } 65 | for i := 0; i < this.Rows; i++ { 66 | rwr := t.newWriter(wr) 67 | row := rwr.WritePos(New(table.TableRow)) 68 | t.fm.attr.Fit(table.TableRow, func(a attr.Attributes) { 69 | rwr.Attr(table.StyleName, a.Name()) 70 | }) 71 | this.rowCache = append(this.rowCache, row) 72 | for j := 0; j < this.Columns; j++ { 73 | cell := New(table.TableCell) 74 | this.cellCache[j] = append(this.cellCache[j], cell) 75 | rwr.Write(cell) 76 | cwr := t.newWriter(rwr) 77 | cwr.Pos(cell) 78 | t.fm.attr.Fit(table.TableCell, func(a attr.Attributes) { 79 | cwr.Attr(table.StyleName, a.Name()) 80 | }) 81 | } 82 | } 83 | } 84 | 85 | //WriteRows to existing table latest set TableRowAttributes and TableCellAttributes are used 86 | func (t *TableMapper) WriteRows(this *Table, rows int) { 87 | assert.For(t.Ready(), 20) 88 | t.fm.attr.Flush() 89 | wr := t.newWriter() 90 | for i := 0; i < rows; i++ { 91 | wr.Pos(this.Root) 92 | row := wr.WritePos(New(table.TableRow)) 93 | t.fm.attr.Fit(table.TableRow, func(a attr.Attributes) { 94 | wr.Attr(table.StyleName, a.Name()) 95 | }) 96 | this.rowCache = append(this.rowCache, row) 97 | for j := 0; j < this.Columns; j++ { 98 | cell := New(table.TableCell) 99 | this.cellCache[j] = append(this.cellCache[j], cell) 100 | wr.Write(cell) 101 | cwr := t.newWriter(wr) 102 | cwr.Pos(cell) 103 | t.fm.attr.Fit(table.TableCell, func(a attr.Attributes) { 104 | cwr.Attr(table.StyleName, a.Name()) 105 | }) 106 | } 107 | this.Rows++ 108 | } 109 | } 110 | 111 | //WriteColumns to existing table latest set TableColumnAttributes and TableCellAttributes are used 112 | func (t *TableMapper) WriteColumns(this *Table, cols int) { 113 | assert.For(t.Ready(), 20) 114 | t.fm.attr.Flush() 115 | wr := t.newWriter() 116 | var last model.Leaf 117 | if this.Columns > 0 { 118 | last = this.colsCache[this.Columns-1] 119 | } 120 | for i := 0; i < cols; i++ { 121 | wr.Pos(this.Root) 122 | col := wr.WritePos(New(table.TableColumn), last) 123 | t.fm.attr.Fit(table.TableColumn, func(a attr.Attributes) { 124 | wr.Attr(table.StyleName, a.Name()) 125 | }) 126 | this.colsCache = append(this.colsCache, col) 127 | this.cellCache = append(this.cellCache, make([]model.Leaf, 0)) 128 | this.Columns++ 129 | for j := 0; j < this.Rows; j++ { 130 | t.WriteCells(this, j, 1) 131 | } 132 | } 133 | } 134 | 135 | //Write cells to existing table latest set TableCellAttributes are used 136 | func (t *TableMapper) WriteCells(this *Table, _row int, cells int) { 137 | assert.For(t.Ready(), 20) 138 | t.fm.attr.Flush() 139 | wr := t.newWriter() 140 | row := this.rowCache[_row] 141 | wr.Pos(row) 142 | for i := 0; i < cells; i++ { 143 | cell := New(table.TableCell) 144 | this.cellCache[i] = append(this.cellCache[i], cell) 145 | wr.Write(cell) 146 | cwr := t.newWriter(wr) 147 | cwr.Pos(cell) 148 | t.fm.attr.Fit(table.TableCell, func(a attr.Attributes) { 149 | cwr.Attr(table.StyleName, a.Name()) 150 | }) 151 | } 152 | } 153 | 154 | //Span merges visually 155 | func (t *TableMapper) Span(this *Table, row, col int, rowspan, colspan int) { 156 | assert.For(t.Ready(), 20) 157 | assert.For(rowspan > 0, 21) 158 | assert.For(colspan > 0, 22) 159 | wr := t.newWriter() 160 | wr.Pos(this.cellCache[col][row]) 161 | wr.Attr(table.NumberRowsSpanned, rowspan) 162 | wr.Attr(table.NumberColumnsSpanned, colspan) 163 | } 164 | 165 | //Pos sets mapper to the cell with given coordinates 166 | func (t *TableMapper) Pos(this *Table, row, col int) *ParaMapper { 167 | ret := new(ParaMapper) 168 | ret.ConnectTo(t.fm) 169 | ret.rider.Pos(this.cellCache[col][row]) 170 | return ret 171 | } 172 | -------------------------------------------------------------------------------- /demo3/report.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "github.com/kpmy/golorem" 7 | "github.com/kpmy/odf/generators" 8 | "github.com/kpmy/odf/mappers" 9 | "github.com/kpmy/odf/mappers/attr" 10 | "github.com/kpmy/odf/model" 11 | "github.com/kpmy/odf/xmlns" 12 | "github.com/kpmy/odf/xmlns/fo" 13 | "image/color" 14 | "io" 15 | "math/rand" 16 | "strconv" 17 | "strings" 18 | "time" 19 | ) 20 | 21 | func r(suffix string, fm *mappers.Formatter) { 22 | { //first page 23 | fm.WriteString("\n\n\n\n\n\n\n\n\n\n") 24 | fm.SetAttr(new(attr.TextAttributes).Size(32).Bold()).SetAttr(new(attr.ParagraphAttributes).AlignCenter()) 25 | fm.WritePara("Periodic report") 26 | fm.SetAttr(nil).SetAttr(new(attr.TextAttributes).Italic()) 27 | fm.WritePara("Report:\t") 28 | fm.SetAttr(new(attr.TextAttributes).Bold()) 29 | fm.WriteString(suffix + "\n") 30 | fm.SetAttr(new(attr.TextAttributes).Italic()) 31 | fm.WriteString("Date:\t") 32 | fm.SetAttr(new(attr.TextAttributes).Bold()) 33 | fm.WriteString(time.Now().String()) 34 | fm.SetAttr(new(attr.ParagraphAttributes).PageBreak()) 35 | fm.WritePara("") 36 | fm.SetAttr(nil) 37 | } 38 | para := func() { 39 | fm.SetAttr(new(attr.TextAttributes).Bold().Size(18)) 40 | fm.WritePara(strings.ToUpper(lorem.Word(5, 15))) 41 | fm.WriteLn() 42 | fm.SetAttr(nil) 43 | para := lorem.Paragraph(5, 10) 44 | fm.WritePara("\t" + para + "\n\n") 45 | } 46 | for i := 0; i < 5; i++ { 47 | para() 48 | } 49 | { //huge table 50 | fm.SetAttr(new(attr.TextAttributes).Bold().Size(18)) 51 | fm.WritePara("TABLE 50x5") 52 | fm.SetAttr(nil) 53 | fm.SetAttr(new(attr.TableCellAttributes).Border(attr.Border{Width: 0.01, Color: color.Black, Style: fo.Solid})) 54 | tm := &mappers.TableMapper{} 55 | tm.ConnectTo(fm) 56 | tm.Write("test", 50+1, 5) //50+header row 57 | tt := tm.List["test"] 58 | tm.Span(tt, 0, 0, 1, 5) 59 | fm.SetAttr(new(attr.ParagraphAttributes).AlignCenter()).SetAttr(new(attr.TextAttributes).Bold()) 60 | tm.Pos(tt, 0, 0).WriteString("Header") 61 | fm.SetAttr(nil) 62 | for i := 1; i < 51; i++ { 63 | for j := 0; j < 5; j++ { 64 | if j == 0 { 65 | fm.SetAttr(new(attr.TextAttributes).Bold()) 66 | } else { 67 | fm.SetAttr(nil) 68 | } 69 | tm.Pos(tt, i, j).WriteString(strconv.Itoa(i * j)) 70 | } 71 | } 72 | } 73 | { //appendix 74 | fm.RegisterFont("Courier New", "Courier New") // may not work in Linux/MacOS 75 | fm.SetAttr(nil).SetAttr(new(attr.ParagraphAttributes).PageBreak()) 76 | fm.SetAttr(new(attr.TextAttributes).Size(18).Bold()) 77 | fm.WritePara("Appendix A.\nListing of report.go") 78 | fm.WriteLn() 79 | fm.SetAttr(nil).SetAttr(new(attr.TextAttributes).FontFace("Courier New").Size(6)) 80 | fm.WritePara("File not found because no IO allowed in browser.") 81 | } 82 | } 83 | 84 | func report() (io.Reader, error) { 85 | src := rand.New(rand.NewSource(time.Now().UnixNano() + rand.Int63())) 86 | suffix := strconv.Itoa(src.Int()) 87 | output := bytes.NewBuffer(nil) 88 | m := model.ModelFactory() 89 | fm := &mappers.Formatter{} 90 | fm.ConnectTo(m) 91 | fm.MimeType = xmlns.MimeText 92 | fm.Init() 93 | embed := make(map[string]generators.Embeddable) 94 | { 95 | const ImagePng xmlns.Mime = "image/png" 96 | if data, err := base64.StdEncoding.DecodeString(imgData); err == nil { 97 | img := bytes.NewBuffer(data) 98 | d := mappers.NewDraw(img, ImagePng) 99 | fm.SetAttr(new(attr.ParagraphAttributes).AlignRight()) 100 | url := d.WriteTo(fm, "Two Gophers", 4.0, 4.0) //magic? real size of `.png` in cm 101 | embed[url] = d 102 | } 103 | } 104 | fm.SetAttr(new(attr.TextAttributes).Bold()) 105 | fm.WriteString("\nSo Strange inc.") 106 | fm.WriteString("\n" + lorem.Email()) 107 | fm.WriteString("\n" + lorem.Url()) 108 | fm.SetAttr(nil) 109 | r(suffix, fm) 110 | generators.GeneratePackage(m, embed, output, fm.MimeType) 111 | return output, nil 112 | } 113 | 114 | var imgData = `iVBORw0KGgoAAAANSUhEUgAAAaQAAAGkCAYAAAB+TFE1AAAABmJLR0QA/wD/AP+gvaeTAAAACXBI 115 | WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wULEQENShOIkAAABSxJREFUeNrt18EJAjEURVEj01E6 116 | STuzTjvpJDU914IbEfUr51QwvPlwSUtyAYBvu5oAAEECAEECQJAAQJAAECQAECQABAkABAkAQQIA 117 | QQJAkABAkAAQJAAQJAAECQAECQBBAgBBAkCQAECQABAkABAkAAQJAAQJAEECAEECQJAAQJAAECQA 118 | ECQABAkABAkABAmAIg4T1Df2jBXgdaufzQpeSAAgSAAIEgAIEgCCBACCBIAgAYAgASBIACBIAAgS 119 | AAgSAIIEAIIEgCABgCABIEgAIEgACBIACBIAggQAggSAIAGAIAEgSAAgSAAgSAAIEgAIEgCCBACC 120 | BIAgAYAgASBIACBIAAgSAAgSAIIEAIIEgCABgCABIEgAIEgACBIACBIAggQAggSAIAGAIAEgSAAg 121 | SAAIEgAIEgCCBACCBIAgAYAgASBIACBIACBIAAgSAAgSAIIEAIIEgCABgCABIEgAIEgACBIAPKEl 122 | KfMxY8/4JQCftfrZvJAAQJAAECQAECQABAkABAkAQQIAQQJAkABAkAAQJAAQJAAECQAECQBBAgBB 123 | AkCQAECQABAkABAkAAQJAAQJAEECAEECQJAAQJAAECQAECQABAkABAkAQQIAQQIAQQJAkABAkAAQ 124 | JAAQJAAECQAECQBBAgBBAkCQAECQABAkABAkAAQJAAQJAEECAEECQJAAQJAAECQAECQABAkABAkA 125 | QQIAQQJAkABAkAAQJAAQJAAECQAECQBBAgBBAgBBAkCQAECQABAkABAkAOo6TPAbVj+bFe6NPWMF 126 | 9+JevJAAQJAAECQAECQABAkABAkAQQIAQQJAkABAkAAQJAAQJAAECQAECQAECQBBAgBBAkCQAECQ 127 | ABAkABAkAAQJAAQJAEECAEECQJAAQJAAECQAECQABAkABAkAQQIAQQJAkABAkAAQJAAQJAAECQAE 128 | CQBBAgBBAkCQAECQABAkABAkAAQJAAQJAAQJAEECAEECQJAAQJAAECQAECQABAkABAkAQQIAQQJA 129 | kABAkAAQJAAQJAAECQAECQBBAgBBAkCQAECQABAkAHijlsQKAHghAYAgASBIACBIAAgSAAgSAIIE 130 | AIIEgCABgCABIEgAIEgACBIACBIAggQAggSAIAGAIAEgSAAgSAAIEgAIEgCCBACCBIAgAYAgASBI 131 | ACBIAAgSAAgSAIIEAIIEgCCZAABBAgBBAkCQAECQABAkABAkAAQJAAQJAEECAEECQJAAQJAAECQA 132 | ECQABAkABAkAQQIAQQJAkABAkAAQJAAQJAAECQAECQBBAgBBAkCQAECQABAkABAkAAQJAAQJAAQJ 133 | AEECAEECoJij0seMPeOXPLb62azgXtyLe/nne/FCAqAEQQJAkABAkAAQJAAQJAAECQAECQBBAgBB 134 | AkCQAECQABAkABAkAAQJAAQJAEECAEECQJAAQJAAECQAECQABAkABAkAQQIAQQJAkABAkAAQJAAQ 135 | JAAECQAECQBBAgBBAgBBAkCQAECQABAkABAkAAQJAAQJAEECAEECQJAAQJAAECQAECQABAkABAkA 136 | QQIAQQJAkABAkAAQJAAQJAAECQAECQBBAgBBAkCQAECQABAkABAkAAQJAAQJAEECAEECAEECQJAA 137 | QJAAKKglsQIAXkgAIEgACBIACBIAggQAggSAIAGAIAEgSAAgSAAIEgAIEgCCBACCBIAgAYAgASBI 138 | ACBIAAgSAAgSAIIEAIIEgCABgCABIEgAIEgACBIACBIAggQAggSAIAGAIAEgSCYAoIIb4ogrP3v0 139 | vosAAAAASUVORK5CYII=` 140 | -------------------------------------------------------------------------------- /demo3/web/js/Blob.js: -------------------------------------------------------------------------------- 1 | /* Blob.js 2 | * A Blob implementation. 3 | * 2014-07-24 4 | * 5 | * By Eli Grey, http://eligrey.com 6 | * By Devin Samarin, https://github.com/dsamarin 7 | * License: X11/MIT 8 | * See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md 9 | */ 10 | 11 | /*global self, unescape */ 12 | /*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, 13 | plusplus: true */ 14 | 15 | /*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ 16 | 17 | (function (view) { 18 | "use strict"; 19 | 20 | view.URL = view.URL || view.webkitURL; 21 | 22 | if (view.Blob && view.URL) { 23 | try { 24 | new Blob; 25 | return; 26 | } catch (e) {} 27 | } 28 | 29 | // Internally we use a BlobBuilder implementation to base Blob off of 30 | // in order to support older browsers that only have BlobBuilder 31 | var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) { 32 | var 33 | get_class = function(object) { 34 | return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1]; 35 | } 36 | , FakeBlobBuilder = function BlobBuilder() { 37 | this.data = []; 38 | } 39 | , FakeBlob = function Blob(data, type, encoding) { 40 | this.data = data; 41 | this.size = data.length; 42 | this.type = type; 43 | this.encoding = encoding; 44 | } 45 | , FBB_proto = FakeBlobBuilder.prototype 46 | , FB_proto = FakeBlob.prototype 47 | , FileReaderSync = view.FileReaderSync 48 | , FileException = function(type) { 49 | this.code = this[this.name = type]; 50 | } 51 | , file_ex_codes = ( 52 | "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " 53 | + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR" 54 | ).split(" ") 55 | , file_ex_code = file_ex_codes.length 56 | , real_URL = view.URL || view.webkitURL || view 57 | , real_create_object_URL = real_URL.createObjectURL 58 | , real_revoke_object_URL = real_URL.revokeObjectURL 59 | , URL = real_URL 60 | , btoa = view.btoa 61 | , atob = view.atob 62 | 63 | , ArrayBuffer = view.ArrayBuffer 64 | , Uint8Array = view.Uint8Array 65 | 66 | , origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/ 67 | ; 68 | FakeBlob.fake = FB_proto.fake = true; 69 | while (file_ex_code--) { 70 | FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1; 71 | } 72 | // Polyfill URL 73 | if (!real_URL.createObjectURL) { 74 | URL = view.URL = function(uri) { 75 | var 76 | uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a") 77 | , uri_origin 78 | ; 79 | uri_info.href = uri; 80 | if (!("origin" in uri_info)) { 81 | if (uri_info.protocol.toLowerCase() === "data:") { 82 | uri_info.origin = null; 83 | } else { 84 | uri_origin = uri.match(origin); 85 | uri_info.origin = uri_origin && uri_origin[1]; 86 | } 87 | } 88 | return uri_info; 89 | }; 90 | } 91 | URL.createObjectURL = function(blob) { 92 | var 93 | type = blob.type 94 | , data_URI_header 95 | ; 96 | if (type === null) { 97 | type = "application/octet-stream"; 98 | } 99 | if (blob instanceof FakeBlob) { 100 | data_URI_header = "data:" + type; 101 | if (blob.encoding === "base64") { 102 | return data_URI_header + ";base64," + blob.data; 103 | } else if (blob.encoding === "URI") { 104 | return data_URI_header + "," + decodeURIComponent(blob.data); 105 | } if (btoa) { 106 | return data_URI_header + ";base64," + btoa(blob.data); 107 | } else { 108 | return data_URI_header + "," + encodeURIComponent(blob.data); 109 | } 110 | } else if (real_create_object_URL) { 111 | return real_create_object_URL.call(real_URL, blob); 112 | } 113 | }; 114 | URL.revokeObjectURL = function(object_URL) { 115 | if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) { 116 | real_revoke_object_URL.call(real_URL, object_URL); 117 | } 118 | }; 119 | FBB_proto.append = function(data/*, endings*/) { 120 | var bb = this.data; 121 | // decode data to a binary string 122 | if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) { 123 | var 124 | str = "" 125 | , buf = new Uint8Array(data) 126 | , i = 0 127 | , buf_len = buf.length 128 | ; 129 | for (; i < buf_len; i++) { 130 | str += String.fromCharCode(buf[i]); 131 | } 132 | bb.push(str); 133 | } else if (get_class(data) === "Blob" || get_class(data) === "File") { 134 | if (FileReaderSync) { 135 | var fr = new FileReaderSync; 136 | bb.push(fr.readAsBinaryString(data)); 137 | } else { 138 | // async FileReader won't work as BlobBuilder is sync 139 | throw new FileException("NOT_READABLE_ERR"); 140 | } 141 | } else if (data instanceof FakeBlob) { 142 | if (data.encoding === "base64" && atob) { 143 | bb.push(atob(data.data)); 144 | } else if (data.encoding === "URI") { 145 | bb.push(decodeURIComponent(data.data)); 146 | } else if (data.encoding === "raw") { 147 | bb.push(data.data); 148 | } 149 | } else { 150 | if (typeof data !== "string") { 151 | data += ""; // convert unsupported types to strings 152 | } 153 | // decode UTF-16 to binary string 154 | bb.push(unescape(encodeURIComponent(data))); 155 | } 156 | }; 157 | FBB_proto.getBlob = function(type) { 158 | if (!arguments.length) { 159 | type = null; 160 | } 161 | return new FakeBlob(this.data.join(""), type, "raw"); 162 | }; 163 | FBB_proto.toString = function() { 164 | return "[object BlobBuilder]"; 165 | }; 166 | FB_proto.slice = function(start, end, type) { 167 | var args = arguments.length; 168 | if (args < 3) { 169 | type = null; 170 | } 171 | return new FakeBlob( 172 | this.data.slice(start, args > 1 ? end : this.data.length) 173 | , type 174 | , this.encoding 175 | ); 176 | }; 177 | FB_proto.toString = function() { 178 | return "[object Blob]"; 179 | }; 180 | FB_proto.close = function() { 181 | this.size = 0; 182 | delete this.data; 183 | }; 184 | return FakeBlobBuilder; 185 | }(view)); 186 | 187 | view.Blob = function(blobParts, options) { 188 | var type = options ? (options.type || "") : ""; 189 | var builder = new BlobBuilder(); 190 | if (blobParts) { 191 | for (var i = 0, len = blobParts.length; i < len; i++) { 192 | if (Uint8Array && blobParts[i] instanceof Uint8Array) { 193 | builder.append(blobParts[i].buffer); 194 | } 195 | else { 196 | builder.append(blobParts[i]); 197 | } 198 | } 199 | } 200 | var blob = builder.getBlob(type); 201 | if (!blob.slice && blob.webkitSlice) { 202 | blob.slice = blob.webkitSlice; 203 | } 204 | return blob; 205 | }; 206 | 207 | var getPrototypeOf = Object.getPrototypeOf || function(object) { 208 | return object.__proto__; 209 | }; 210 | view.Blob.prototype = getPrototypeOf(new view.Blob()); 211 | }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this)); 212 | --------------------------------------------------------------------------------