diff --git a/_examples/document/oleobject/main.go b/_examples/document/oleobject/main.go new file mode 100644 index 0000000000..ba04275106 --- /dev/null +++ b/_examples/document/oleobject/main.go @@ -0,0 +1,79 @@ +// oleObject using demo + +// $ cd unioffice/_examples/document/oleobject +// $ go run main.go + +// print: +//=============oleObject info=========== +//Data path = C:\Users\ADMINI~1\AppData\Local\Temp\gooxml-docx131058703\zz524008729 +//Wmf path = C:\Users\ADMINI~1\AppData\Local\Temp\gooxml-docx131058703\zz731151394 +//===============end============= +//=============oleObject info=========== +//Data path = C:\Users\ADMINI~1\AppData\Local\Temp\gooxml-docx131058703\zz304145587 +//Wmf path = C:\Users\ADMINI~1\AppData\Local\Temp\gooxml-docx131058703\zz059105188 +//===============end============= +//=============oleObject info=========== +//Data path = C:\Users\ADMINI~1\AppData\Local\Temp\gooxml-docx131058703\zz462161757 +//Wmf path = C:\Users\ADMINI~1\AppData\Local\Temp\gooxml-docx131058703\zz101524598 +//===============end============= +// + +// Comments: +// I developed a project can do convert the oleObject file to LaTeX data +// project path: https://github.com/zhexiao/mtef-go + +// code Demo: +//import "github.com/zhexiao/mtef-go/eqn" +//latexData := eqn.Convert(path) + +package main + +import ( + "fmt" + "github.com/unidoc/unioffice/document" + "log" +) + +func main() { + filepath := "oleobject.docx" + + doc, err := document.Open(filepath) + if err != nil { + log.Panicf("Open file failed, err=%s", err) + } + + //save oleObject data rid/filepath relationship + oleObjectDataMap := make(map[string]string) + for _, oleData := range doc.OleObjectPaths { + path := oleData.Path() + rid := oleData.Rid() + + oleObjectDataMap[rid] = path + } + + //save oleObject wmf rid/filepath relationship + oleObjectWmfMap := make(map[string]string) + for _, oleData := range doc.OleObjectWmfPath { + path := oleData.Path() + rid := oleData.Rid() + + oleObjectWmfMap[rid] = path + } + + // read the oleObject file information from the word data + for _, paragraph := range doc.Paragraphs() { + for _, run := range paragraph.Runs() { + if run.OleObjects() != nil { + for _, ole := range run.OleObjects() { + dataRid := ole.OleRid() + wmfRid := ole.ImagedataRid() + + fmt.Println("=============oleObject info===========") + fmt.Printf("Data path = %s \n", oleObjectDataMap[dataRid]) + fmt.Printf("Wmf path = %s \n", oleObjectWmfMap[wmfRid]) + fmt.Println("===============end=============") + } + } + } + } +} diff --git a/_examples/document/oleobject/oleobject.docx b/_examples/document/oleobject/oleobject.docx new file mode 100644 index 0000000000..ed8e4ba40e Binary files /dev/null and b/_examples/document/oleobject/oleobject.docx differ diff --git a/document/document.go b/document/document.go index b2d921e482..0656afcb93 100644 --- a/document/document.go +++ b/document/document.go @@ -56,6 +56,9 @@ type Document struct { fontTable *wml.Fonts endNotes *wml.Endnotes footNotes *wml.Footnotes + + OleObjectPaths []OleObjectPath + OleObjectWmfPath []OleObjectWmfPath } // New constructs an empty document that content can be added to. @@ -858,16 +861,30 @@ func (d *Document) onNewRelationship(decMap *zippkg.DecodeMap, target, typ strin if f == nil { continue } + if f.Name == target { path, err := zippkg.ExtractToDiskTmp(f, d.TmpPath) if err != nil { return err } + + if strings.Contains(rel.TargetAttr, ".wmf") { + d.OleObjectWmfPath = append(d.OleObjectWmfPath, OleObjectWmfPath{ + rid: rel.IdAttr, + path: path, + }) + + continue + } + img, err := common.ImageFromFile(path) if err != nil { return err } + iref = common.MakeImageRef(img, &d.DocBase, d.docRels) + iref.SetRelID(rel.IdAttr) + d.Images = append(d.Images, iref) files[i] = nil } @@ -880,6 +897,24 @@ func (d *Document) onNewRelationship(decMap *zippkg.DecodeMap, target, typ strin rel.TargetAttr = rel.TargetAttr[0:len(rel.TargetAttr)-len(newExt)] + ext } + case unioffice.OleObjectType: + for _, f := range files { + if f == nil { + continue + } + + if f.Name == target { + path, err := zippkg.ExtractToDiskTmp(f, d.TmpPath) + if err != nil { + return err + } + + d.OleObjectPaths = append(d.OleObjectPaths, OleObjectPath{ + rid: rel.IdAttr, + path: path, + }) + } + } default: unioffice.Log("unsupported relationship type: %s tgt: %s", typ, target) } diff --git a/document/oleobject.go b/document/oleobject.go new file mode 100644 index 0000000000..a14cd33f72 --- /dev/null +++ b/document/oleobject.go @@ -0,0 +1,56 @@ +// Copyright 2017 FoxyUtils ehf. All rights reserved. +// +// Use of this source code is governed by the terms of the Affero GNU General +// Public License version 3.0 as published by the Free Software Foundation and +// appearing in the file LICENSE included in the packaging of this file. A +// commercial license can be purchased by contacting sales@baliance.com. +package document + +import "github.com/unidoc/unioffice/schema/soo/wml" + +type OleObjectPath struct { + rid string + path string +} + +type OleObjectWmfPath struct { + rid string + path string +} + +type OleObject struct { + oleobject *wml.CT_OleObject + shape *wml.CT_Shape +} + +func (o OleObject) Shape() *wml.CT_Shape { + return o.shape +} + +func (o OleObject) OleObject() *wml.CT_OleObject { + return o.oleobject +} + +func (o OleObject) OleRid() string { + return *o.oleobject.IdAttr +} + +func (o OleObject) ImagedataRid() string { + return *o.shape.Imagedata.IdAttr +} + +func (o OleObjectPath) Rid() string { + return o.rid +} + +func (o OleObjectPath) Path() string { + return o.path +} + +func (o OleObjectWmfPath) Rid() string { + return o.rid +} + +func (o OleObjectWmfPath) Path() string { + return o.path +} diff --git a/document/run.go b/document/run.go index b467cc5c2c..8ad3ac9fc7 100644 --- a/document/run.go +++ b/document/run.go @@ -281,3 +281,38 @@ func (r Run) AddDrawingInline(img common.ImageRef) (InlineDrawing, error) { return inline, nil } + +func (r Run) DrawingInline() []InlineDrawing { + var ret []InlineDrawing + for _, ic := range r.x.EG_RunInnerContent { + if ic.Drawing == nil { + continue + } + + for _, inl := range ic.Drawing.Inline { + ret = append(ret, InlineDrawing{r.d, inl}) + } + } + return ret +} + +func (r Run) OleObjects() []OleObject { + var ret []OleObject + for _, ic := range r.x.EG_RunInnerContent { + if ic.Object == nil { + continue + } + + //读取对象 + oleObject := ic.Object.OleObject + shape := ic.Object.Shape + + if oleObject == nil || shape == nil { + continue + } + + ret = append(ret, OleObject{oleobject: oleObject, shape: shape}) + } + + return ret +} diff --git a/go.mod b/go.mod index 234a1742f8..8867b93fe8 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,3 @@ module github.com/unidoc/unioffice + +go 1.13 diff --git a/schema/soo/dml/CT_PositiveSize2D.go b/schema/soo/dml/CT_PositiveSize2D.go index f9537c27ed..ce1d52078a 100644 --- a/schema/soo/dml/CT_PositiveSize2D.go +++ b/schema/soo/dml/CT_PositiveSize2D.go @@ -93,3 +93,26 @@ func (m *CT_PositiveSize2D) ValidateWithPath(path string) error { } return nil } + +//calculate image width and height +//convention: https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.drawing.wordprocessing.extent?view=openxml-2.8.1 +type EmuSize struct { + Width string + Height string +} + +const ( + mm = 36000 + cm = 360000 + in = 914400 + pt = 12700 + pc = 152400 + pi = 152400 +) + +//return width and height by using pt +func (m *CT_PositiveSize2D) Size() EmuSize { + return EmuSize{ + Width: fmt.Sprintf("%spt", strconv.FormatInt(m.CxAttr/pt, 10)), + Height: fmt.Sprintf("%spt", strconv.FormatInt(m.CyAttr/pt, 10))} +} diff --git a/schema/soo/wml/CT_Imagedata.go b/schema/soo/wml/CT_Imagedata.go new file mode 100644 index 0000000000..865d2ba7b3 --- /dev/null +++ b/schema/soo/wml/CT_Imagedata.go @@ -0,0 +1,95 @@ +// Copyright 2017 FoxyUtils ehf. All rights reserved. +// +// Use of this source code is governed by the terms of the Affero GNU General +// Public License version 3.0 as published by the Free Software Foundation and +// appearing in the file LICENSE included in the packaging of this file. A +// commercial license can be purchased by contacting sales@baliance.com. +package wml + +import ( + "encoding/xml" + "fmt" + "github.com/unidoc/unioffice" +) + +type CT_Imagedata struct { + //r:id + IdAttr *string + + TitleAttr *string +} + +func NewCT_Imagedata() *CT_Imagedata { + ret := &CT_Imagedata{} + return ret +} + +func (m *CT_Imagedata) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + if m.IdAttr != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "r:id"}, + Value: fmt.Sprintf("%v", *m.IdAttr)}) + } + + if m.TitleAttr != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "title"}, + Value: fmt.Sprintf("%v", *m.TitleAttr)}) + } + + _ = e.EncodeToken(start) + _ = e.EncodeToken(xml.EndElement{Name: start.Name}) + return nil +} + +func (m *CT_Imagedata) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + // initialize to default + for _, attr := range start.Attr { + if attr.Name.Space == "http://schemas.openxmlformats.org/officeDocument/2006/relationships" && attr.Name.Local == "id" { + parsed, err := attr.Value, error(nil) + if err != nil { + return err + } + m.IdAttr = &parsed + continue + } + + if attr.Name.Space == "urn:schemas-microsoft-com:office:office" && attr.Name.Local == "title" { + parsed, err := attr.Value, error(nil) + if err != nil { + return err + } + m.TitleAttr = &parsed + continue + } + } +lCT_Imagedata: + for { + tok, err := d.Token() + if err != nil { + return err + } + switch el := tok.(type) { + case xml.StartElement: + switch el.Name { + default: + unioffice.Log("skipping unsupported element on CT_Imagedata %v", el.Name) + if err := d.Skip(); err != nil { + return err + } + } + case xml.EndElement: + break lCT_Imagedata + case xml.CharData: + } + } + return nil +} + +// Validate validates the CT_OleObject and its children +func (m *CT_Imagedata) Validate() error { + return m.ValidateWithPath("CT_Imagedata") +} + +// ValidateWithPath validates the CT_OleObject and its children, prefixing error messages with path +func (m *CT_Imagedata) ValidateWithPath(path string) error { + return nil +} diff --git a/schema/soo/wml/CT_Object.go b/schema/soo/wml/CT_Object.go index 1171a568ed..142eb083a5 100644 --- a/schema/soo/wml/CT_Object.go +++ b/schema/soo/wml/CT_Object.go @@ -12,7 +12,6 @@ package wml import ( "encoding/xml" "fmt" - "github.com/unidoc/unioffice" "github.com/unidoc/unioffice/schema/soo/ofc/sharedTypes" ) @@ -24,6 +23,9 @@ type CT_Object struct { DyaOrigAttr *sharedTypes.ST_TwipsMeasure Drawing *CT_Drawing Choice *CT_ObjectChoice + + OleObject *CT_OleObject + Shape *CT_Shape } func NewCT_Object() *CT_Object { @@ -111,6 +113,19 @@ lCT_Object: if err := d.DecodeElement(&m.Choice.Movie, &el); err != nil { return err } + + case xml.Name{Space: "urn:schemas-microsoft-com:vml", Local: "shape"}: + m.Shape = NewCT_Shape() + if err := d.DecodeElement(&m.Shape, &el); err != nil { + return err + } + + case xml.Name{Space: "urn:schemas-microsoft-com:office:office", Local: "OLEObject"}: + m.OleObject = NewCT_OleObject() + if err := d.DecodeElement(&m.OleObject, &el); err != nil { + return err + } + default: unioffice.Log("skipping unsupported element on CT_Object %v", el.Name) if err := d.Skip(); err != nil { diff --git a/schema/soo/wml/CT_OleObject.go b/schema/soo/wml/CT_OleObject.go new file mode 100644 index 0000000000..6915e80f02 --- /dev/null +++ b/schema/soo/wml/CT_OleObject.go @@ -0,0 +1,128 @@ +// Copyright 2017 FoxyUtils ehf. All rights reserved. +// +// Use of this source code is governed by the terms of the Affero GNU General +// Public License version 3.0 as published by the Free Software Foundation and +// appearing in the file LICENSE included in the packaging of this file. A +// commercial license can be purchased by contacting sales@baliance.com. +package wml + +import ( + "encoding/xml" + "fmt" + "github.com/unidoc/unioffice" +) + +type CT_OleObject struct { + TypeAttr *string + + // Embedded Object ProgId + ProgIdAttr *string + + // Shape Id + ShapeIdAttr *string + + //r:id + IdAttr *string +} + +func NewCT_OleObject() *CT_OleObject { + ret := &CT_OleObject{} + return ret +} + +func (m *CT_OleObject) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + if m.TypeAttr != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "Type"}, + Value: fmt.Sprintf("%v", *m.TypeAttr)}) + } + + if m.ProgIdAttr != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "ProgID"}, + Value: fmt.Sprintf("%v", *m.ProgIdAttr)}) + } + + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "ShapeID"}, + Value: fmt.Sprintf("%v", m.ShapeIdAttr)}) + + if m.IdAttr != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "r:id"}, + Value: fmt.Sprintf("%v", *m.IdAttr)}) + } + + _ = e.EncodeToken(start) + _ = e.EncodeToken(xml.EndElement{Name: start.Name}) + return nil +} + +func (m *CT_OleObject) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + // initialize to default + for _, attr := range start.Attr { + if attr.Name.Local == "Type" { + parsed, err := attr.Value, error(nil) + if err != nil { + return err + } + m.TypeAttr = &parsed + continue + } + + if attr.Name.Local == "ProgID" { + parsed, err := attr.Value, error(nil) + if err != nil { + return err + } + m.ProgIdAttr = &parsed + continue + } + + if attr.Name.Local == "ShapeID" { + parsed, err := attr.Value, error(nil) + if err != nil { + return err + } + m.ShapeIdAttr = &parsed + continue + } + + if attr.Name.Space == "http://schemas.openxmlformats.org/officeDocument/2006/relationships" && attr.Name.Local == "id" || + attr.Name.Space == "http://purl.oclc.org/ooxml/officeDocument/relationships" && attr.Name.Local == "id" { + parsed, err := attr.Value, error(nil) + if err != nil { + return err + } + m.IdAttr = &parsed + continue + } + } +lCT_OleObject: + for { + tok, err := d.Token() + if err != nil { + return err + } + switch el := tok.(type) { + case xml.StartElement: + switch el.Name { + default: + unioffice.Log("skipping unsupported element on CT_OleObject %v", el.Name) + if err := d.Skip(); err != nil { + return err + } + } + case xml.EndElement: + break lCT_OleObject + case xml.CharData: + } + } + return nil +} + +// Validate validates the CT_OleObject and its children +func (m *CT_OleObject) Validate() error { + return m.ValidateWithPath("CT_OleObject") +} + +// ValidateWithPath validates the CT_OleObject and its children, prefixing error messages with path +func (m *CT_OleObject) ValidateWithPath(path string) error { + return nil +} diff --git a/schema/soo/wml/CT_Shape.go b/schema/soo/wml/CT_Shape.go new file mode 100644 index 0000000000..ac6c845368 --- /dev/null +++ b/schema/soo/wml/CT_Shape.go @@ -0,0 +1,130 @@ +// Copyright 2017 FoxyUtils ehf. All rights reserved. +// +// Use of this source code is governed by the terms of the Affero GNU General +// Public License version 3.0 as published by the Free Software Foundation and +// appearing in the file LICENSE included in the packaging of this file. A +// commercial license can be purchased by contacting sales@baliance.com. +package wml + +import ( + "encoding/xml" + "fmt" + "github.com/unidoc/unioffice" +) + +type CT_Shape struct { + IdAttr *string + TypeAttr *string + StyleAttr *string + OleAttr *string + + Imagedata *CT_Imagedata +} + +func NewCT_Shape() *CT_Shape { + ret := &CT_Shape{} + return ret +} + +func (m *CT_Shape) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + if m.TypeAttr != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "type"}, + Value: fmt.Sprintf("%v", *m.TypeAttr)}) + } + + if m.IdAttr != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "id"}, + Value: fmt.Sprintf("%v", *m.IdAttr)}) + } + + if m.StyleAttr != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "style"}, + Value: fmt.Sprintf("%v", *m.StyleAttr)}) + } + + if m.OleAttr != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "o:ole"}, + Value: fmt.Sprintf("%v", *m.OleAttr)}) + } + + _ = e.EncodeToken(start) + _ = e.EncodeToken(xml.EndElement{Name: start.Name}) + return nil +} + +func (m *CT_Shape) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + // initialize to default + for _, attr := range start.Attr { + if attr.Name.Local == "id" { + parsed, err := attr.Value, error(nil) + if err != nil { + return err + } + m.IdAttr = &parsed + continue + } + + if attr.Name.Local == "type" { + parsed, err := attr.Value, error(nil) + if err != nil { + return err + } + m.TypeAttr = &parsed + continue + } + + if attr.Name.Local == "style" { + parsed, err := attr.Value, error(nil) + if err != nil { + return err + } + m.StyleAttr = &parsed + continue + } + + if attr.Name.Space == "urn:schemas-microsoft-com:office:office" && attr.Name.Local == "ole" { + parsed, err := attr.Value, error(nil) + if err != nil { + return err + } + m.OleAttr = &parsed + continue + } + } +lCT_Shape: + for { + tok, err := d.Token() + if err != nil { + return err + } + switch el := tok.(type) { + case xml.StartElement: + switch el.Name { + case xml.Name{Space: "urn:schemas-microsoft-com:vml", Local: "imagedata"}: + m.Imagedata = NewCT_Imagedata() + if err := d.DecodeElement(&m.Imagedata, &el); err != nil { + return err + } + default: + unioffice.Log("skipping unsupported element on CT_Shape %v", el.Name) + if err := d.Skip(); err != nil { + return err + } + } + case xml.EndElement: + break lCT_Shape + case xml.CharData: + } + } + return nil +} + +// Validate validates the CT_OleObject and its children +func (m *CT_Shape) Validate() error { + return m.ValidateWithPath("CT_Shape") +} + +// ValidateWithPath validates the CT_OleObject and its children, prefixing error messages with path +func (m *CT_Shape) ValidateWithPath(path string) error { + return nil +} diff --git a/schemas.go b/schemas.go index af028e6c3a..28b100fb4a 100644 --- a/schemas.go +++ b/schemas.go @@ -61,6 +61,7 @@ const ( CorePropertiesType = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" CustomPropertiesType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties" CustomXMLType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml" + OleObjectType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject" // SML WorksheetType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"