From 06cb9b6a4bea388c4cf6852529e925d761304948 Mon Sep 17 00:00:00 2001 From: gomutex Date: Tue, 2 Jul 2024 00:44:25 +0530 Subject: [PATCH 1/2] table wrapper --- docx/body.go | 5 +- docx/docx_test.go | 24 +++++++ docx/paragraph.go | 90 ++++++++++++++++--------- docx/paragraph_test.go | 141 ++++++++++++++++++++++++++++++++++++++++ docx/table.go | 107 ++++++++++++++++++++++++++++-- go.mod | 8 +++ go.sum | 10 +++ wml/ctypes/cell.go | 11 ---- wml/ctypes/row.go | 8 --- wml/ctypes/text.go | 18 ++--- wml/ctypes/text_test.go | 14 ++-- 11 files changed, 361 insertions(+), 75 deletions(-) create mode 100644 docx/docx_test.go create mode 100644 docx/paragraph_test.go create mode 100644 go.sum diff --git a/docx/body.go b/docx/body.go index 009f280..7b1a988 100644 --- a/docx/body.go +++ b/docx/body.go @@ -77,13 +77,13 @@ func (body *Body) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err erro switch elem.Name.Local { case "p": para := newParagraph(body.root) - if err := d.DecodeElement(¶.ct, &elem); err != nil { + if err := para.unmarshalXML(d, elem); err != nil { return err } body.Children = append(body.Children, DocumentChild{Para: para}) case "tbl": tbl := NewTable(body.root) - if err := d.DecodeElement(&tbl.ct, &elem); err != nil { + if err := tbl.unmarshalXML(d, elem); err != nil { return err } body.Children = append(body.Children, DocumentChild{Table: tbl}) @@ -101,5 +101,4 @@ func (body *Body) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err erro return nil } } - } diff --git a/docx/docx_test.go b/docx/docx_test.go new file mode 100644 index 0000000..61ff8e4 --- /dev/null +++ b/docx/docx_test.go @@ -0,0 +1,24 @@ +package docx + +import ( + "log" + "testing" + + "github.com/gomutex/godocx/wml/ctypes" +) + +func setupRootDoc(t *testing.T) *RootDoc { + log.Println("setting root doc") + + return &RootDoc{ + Path: "/tmp/test", + RootRels: Relationships{}, + ContentType: ContentTypes{}, + Document: &Document{ + Body: &Body{}, + }, + DocStyles: &ctypes.Styles{}, + rID: 1, + ImageCount: 1, + } +} diff --git a/docx/paragraph.go b/docx/paragraph.go index 4de9656..5200ce4 100644 --- a/docx/paragraph.go +++ b/docx/paragraph.go @@ -1,6 +1,7 @@ package docx import ( + "encoding/xml" "errors" "fmt" @@ -18,11 +19,43 @@ type Paragraph struct { ct ctypes.Paragraph // ct holds the underlying Paragraph Complex Type. } -// newParagraph creates and initializes a new Paragraph instance. -func newParagraph(root *RootDoc) *Paragraph { - return &Paragraph{ +func (p *Paragraph) unmarshalXML(d *xml.Decoder, start xml.StartElement) error { + return p.ct.UnmarshalXML(d, start) +} + +type paraOpts struct { + text string +} + +func newParaOpts() *paraOpts { + return ¶Opts{} +} + +// paraOption defines a type for functions that can configure a Paragraph. +type paraOption func(*Paragraph) + +// newParagraph creates and initializes a new Paragraph instance with given options. +func newParagraph(root *RootDoc, opts ...paraOption) *Paragraph { + p := &Paragraph{ root: root, } + for _, opt := range opts { + opt(p) + } + return p +} + +// paraWithText is an option for adding text to a Paragraph. +func paraWithText(text string) paraOption { + return func(p *Paragraph) { + p.AddText(text) + } +} + +func (p *Paragraph) ensureProp() { + if p.ct.Property == nil { + p.ct.Property = ctypes.DefaultParaProperty() + } } // GetCT returns a pointer to the underlying Paragraph Complex Type. @@ -59,9 +92,7 @@ func (rd *RootDoc) AddParagraph(text string) *Paragraph { // p1 := document.AddParagraph("Example para") // paragraph.Style("List Number") func (p *Paragraph) Style(value string) { - if p.ct.Property == nil { - p.ct.Property = ctypes.DefaultParaProperty() - } + p.ensureProp() p.ct.Property.Style = ctypes.NewParagraphStyle(value) } @@ -76,9 +107,7 @@ func (p *Paragraph) Style(value string) { // p1 := document.AddParagraph("Example justified para") // p1.Justification(stypes.JustificationCenter) // Center justification func (p *Paragraph) Justification(value stypes.Justification) { - if p.ct.Property == nil { - p.ct.Property = ctypes.DefaultParaProperty() - } + p.ensureProp() p.ct.Property.Justification = ctypes.NewGenSingleStrVal(value) } @@ -99,15 +128,14 @@ func (p *Paragraph) Justification(value stypes.Justification) { // // In this example, the paragraph p1 is assigned the numbering properties // defined by numbering definition ID 1 and level 0. -func (p Paragraph) Numbering(id int, level int) { +func (p *Paragraph) Numbering(id int, level int) { - if p.ct.Property == nil { - p.ct.Property = ctypes.DefaultParaProperty() - } + p.ensureProp() if p.ct.Property.NumProp == nil { p.ct.Property.NumProp = &ctypes.NumProp{} } + p.ct.Property.NumProp.NumID = ctypes.NewDecimalNum(id) p.ct.Property.NumProp.ILvl = ctypes.NewDecimalNum(level) } @@ -164,6 +192,24 @@ func (p *Paragraph) AddRun() *Run { return newRun(p.root, run) } +// GetStyle retrieves the style information applied to the Paragraph. +// +// Returns: +// - *ctypes.Style: The style information of the Paragraph. +// - error: An error if the style information is not found. +func (p *Paragraph) GetStyle() (*ctypes.Style, error) { + if p.ct.Property == nil || p.ct.Property.Style == nil { + return nil, errors.New("No property for the style") + } + + style := p.root.GetStyleByID(p.ct.Property.Style.Val, stypes.StyleTypeParagraph) + if style == nil { + return nil, errors.New("No style found for the paragraph") + } + + return style, nil +} + // func (p *Paragraph) AddLink(text string, link string) *Hyperlink { // rId := p.rootRef.addLinkRelation(link) @@ -234,21 +280,3 @@ func (p *Paragraph) addDrawing(rID string, imgCount uint, width units.Inch, heig return &inline } - -// GetStyle retrieves the style information applied to the Paragraph. -// -// Returns: -// - *ctypes.Style: The style information of the Paragraph. -// - error: An error if the style information is not found. -func (p *Paragraph) GetStyle() (*ctypes.Style, error) { - if p.ct.Property == nil || p.ct.Property.Style == nil { - return nil, errors.New("No property for the style") - } - - style := p.root.GetStyleByID(p.ct.Property.Style.Val, stypes.StyleTypeParagraph) - if style == nil { - return nil, errors.New("No style found for the paragraph") - } - - return style, nil -} diff --git a/docx/paragraph_test.go b/docx/paragraph_test.go new file mode 100644 index 0000000..3eb7b87 --- /dev/null +++ b/docx/paragraph_test.go @@ -0,0 +1,141 @@ +package docx + +import ( + "testing" + + "github.com/gomutex/godocx/wml/ctypes" + "github.com/gomutex/godocx/wml/stypes" + "github.com/stretchr/testify/assert" +) + +func assertParaText(t *testing.T, para *Paragraph, expected string) { + t.Helper() + + assert.NotNil(t, para) + ct := para.GetCT() + assert.NotNil(t, ct) + assert.GreaterOrEqual(t, len(ct.Children), 1) + run := ct.Children[0].Run + assert.NotNil(t, run) + assert.GreaterOrEqual(t, len(run.Children), 1) + text := run.Children[0].Text + assert.NotNil(t, text) + + assert.Equal(t, text.Text, expected) + +} + +func TestAddParagraph(t *testing.T) { + rd := setupRootDoc(t) + para := rd.AddParagraph("Test paragraph") + assertParaText(t, para, "Test paragraph") +} + +func TestEmptyParagraph(t *testing.T) { + rd := setupRootDoc(t) + para := rd.AddEmptyParagraph() + para.AddText("Test paragraph") + assertParaText(t, para, "Test paragraph") +} + +func TestParagraph_Style(t *testing.T) { + f := func(styleValue string, expectedStyleValue string) { + t.Helper() + + p := &Paragraph{} + + p.Style(styleValue) + + assert.NotNil(t, p.ct.Property) + assert.NotNil(t, p.ct.Property.Style) + assert.Equal(t, p.ct.Property.Style.Val, expectedStyleValue) + } + + f("Heading1", "Heading1") + f("Normal", "Normal") +} +func TestParagraph_Justification(t *testing.T) { + f := func(justificationValue, expectedJustificationValue stypes.Justification) { + t.Helper() + + p := &Paragraph{} + + p.Justification(justificationValue) + + assert.NotNil(t, p.ct.Property, "Expected ct.Property to be non-nil") + assert.NotNil(t, p.ct.Property.Justification, "Expected ct.Property.Justification to be non-nil") + assert.Equal(t, p.ct.Property.Justification.Val, expectedJustificationValue, "Paragraph.Justification() value mismatch") + } + + f(stypes.JustificationCenter, stypes.JustificationCenter) + f(stypes.JustificationLeft, stypes.JustificationLeft) + f(stypes.JustificationRight, stypes.JustificationRight) + f(stypes.JustificationBoth, stypes.JustificationBoth) +} + +func TestParagraph_Numbering(t *testing.T) { + f := func(id int, level int, expectedNumID int, expectedILvl int) { + t.Helper() + + p := &Paragraph{} + + p.Numbering(id, level) + + assert.NotNil(t, p.ct.Property, "Expected ct.Property to be non-nil") + assert.NotNil(t, p.ct.Property.NumProp, "Expected ct.Property.NumProp to be non-nil") + assert.Equal(t, expectedNumID, p.ct.Property.NumProp.NumID.Val, "Paragraph.Numbering() NumID value mismatch") + assert.Equal(t, expectedILvl, p.ct.Property.NumProp.ILvl.Val, "Paragraph.Numbering() ILvl value mismatch") + } + + f(1, 0, 1, 0) + f(2, 1, 2, 1) + f(3, 2, 3, 2) + f(4, 3, 4, 3) +} + +func TestParagraph_AddText(t *testing.T) { + f := func(text string, expectedText string) { + t.Helper() + + p := &Paragraph{ + ct: ctypes.Paragraph{ + Children: []ctypes.ParagraphChild{}, + }, + } + + run := p.AddText(text) + + assert.NotNil(t, run, "Expected AddText() to return a non-nil Run") + assert.Equal(t, len(p.ct.Children), 1, "Expected one Run to be added to Paragraph") + + assert.NotNil(t, p.ct.Children[0].Run, "Expected ct.Children[0].Run to be non-nil") + assert.GreaterOrEqual(t, len(p.ct.Children[0].Run.Children), 1, "Expected at least one Text element in Run") + assert.NotNil(t, p.ct.Children[0].Run.Children[0].Text, "Expected Text element in Run to be non-nil") + assert.Equal(t, p.ct.Children[0].Run.Children[0].Text.Text, expectedText, "Paragraph.AddText() Text value mismatch") + } + + f("Hello, World!", "Hello, World!") + f("Another test", "Another test") + f("A third text", "A third text") + f("Sample text", "Sample text") +} + +func TestParagraph_AddRun(t *testing.T) { + p := &Paragraph{ + ct: ctypes.Paragraph{ + Children: []ctypes.ParagraphChild{}, + }, + } + + run := p.AddRun() + + assert.NotNil(t, run, "Expected AddRun() to return a non-nil Run") + + assert.Equal(t, 1, len(p.ct.Children), "Expected one Run to be added to Paragraph") + assert.NotNil(t, p.ct.Children[0].Run, "Expected ct.Children[0].Run to be non-nil") + assert.Equal(t, run.ct, p.ct.Children[0].Run, "Expected the Run returned by AddRun() to match the Run added to Paragraph") + + assert.Empty(t, run.ct.Children, "Expected new Run to have no initial Children") + + assert.Equal(t, 0, len(p.ct.Children[0].Run.Children), "Expected the new Run to have no initial Children") +} diff --git a/docx/table.go b/docx/table.go index 6aa257a..81275a7 100644 --- a/docx/table.go +++ b/docx/table.go @@ -1,6 +1,8 @@ package docx import ( + "encoding/xml" + "github.com/gomutex/godocx/wml/ctypes" "github.com/gomutex/godocx/wml/stypes" ) @@ -9,10 +11,14 @@ type Table struct { // Reverse inheriting the Rootdoc into paragraph to access other elements root *RootDoc - // Paragraph Complex Type + // Table Complex Type ct ctypes.Table } +func (t *Table) unmarshalXML(d *xml.Decoder, start xml.StartElement) error { + return t.ct.UnmarshalXML(d, start) +} + func NewTable(root *RootDoc) *Table { return &Table{ root: root, @@ -42,7 +48,10 @@ func NewTable(root *RootDoc) *Table { // - *elements.Table: A pointer to the newly added table. func (rd *RootDoc) AddTable() *Table { - tbl := Table{} + tbl := Table{ + root: rd, + ct: *ctypes.DefaultTable(), + } rd.Document.Body.Children = append(rd.Document.Body.Children, DocumentChild{ Table: &tbl, @@ -59,12 +68,17 @@ func (rd *RootDoc) AddTable() *Table { // Returns: // - *ctypes.Row: A pointer to the newly added row. -func (t *Table) AddRow() *ctypes.Row { - row := ctypes.DefaultRow() +func (t *Table) AddRow() *Row { + row := Row{ + root: t.root, + ct: *ctypes.DefaultRow(), + } + t.ct.RowContents = append(t.ct.RowContents, ctypes.RowContent{ - Row: row, + Row: &row.ct, }) - return row + + return &row } func (t *Table) ensureProp() { @@ -126,3 +140,84 @@ func (t *Table) Indent(indent int) { func (t *Table) Style(value string) { t.ct.TableProp.Style = ctypes.NewCTString(value) } + +type Row struct { + // Reverse inheriting the Rootdoc into paragraph to access other elements + root *RootDoc + + // Row Complex Type + ct ctypes.Row +} + +// func (r *Row) unmarshalXML(d *xml.Decoder, start xml.StartElement) error { +// return r.ct.UnmarshalXML(d, start) +// } + +type RunPropOpts func(*Run) + +func WithFontSize(size uint64) RunPropOpts { + return func(r *Run) { + r.getProp().Size = ctypes.NewFontSize(size * 2) + } +} + +func (r *Row) AddTextCell(text string, opts ...RunPropOpts) *Cell { + // Wrapper cell + cell := Cell{ + root: r.root, + ct: *ctypes.DefaultCell(), + } + + // add paragraph with text & get Run obj + p := newParagraph(r.root) + run := p.AddText(text) + + // Table cell block content + tblContent := ctypes.TCBlockContent{ + Paragraph: &p.ct, + } + + cell.ct.Contents = append(cell.ct.Contents, tblContent) + + r.ct.Contents = append(r.ct.Contents, ctypes.TRCellContent{ + Cell: &cell.ct, + }) + + for _, opt := range opts { + opt(run) + } + + return &cell +} + +func (r *Row) AddCell() *Cell { + cell := Cell{ + root: r.root, + ct: *ctypes.DefaultCell(), + } + + r.ct.Contents = append(r.ct.Contents, ctypes.TRCellContent{ + Cell: &cell.ct, + }) + + return &cell +} + +type Cell struct { + // Reverse inheriting the Rootdoc into paragraph to access other elements + root *RootDoc + + // Cell Complex Type + ct ctypes.Cell +} + +func (c *Cell) AddParagraph(text string) *Paragraph { + p := newParagraph(c.root, paraWithText(text)) + tblContent := ctypes.TCBlockContent{ + Paragraph: &p.ct, + } + + c.ct.Contents = append(c.ct.Contents, tblContent) + + return p +} diff --git a/go.mod b/go.mod index 8cad6a9..b9f6e72 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module github.com/gomutex/godocx go 1.18 + +require github.com/stretchr/testify v1.9.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..60ce688 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/wml/ctypes/cell.go b/wml/ctypes/cell.go index 18c4a73..8a4cd83 100644 --- a/wml/ctypes/cell.go +++ b/wml/ctypes/cell.go @@ -19,17 +19,6 @@ func DefaultCell() *Cell { return &Cell{} } -func (c *Cell) AddParagraph(text string) *Paragraph { - p := AddParagraph(text) - tblContent := TCBlockContent{ - Paragraph: p, - } - - c.Contents = append(c.Contents, tblContent) - - return p -} - func (c Cell) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { start.Name.Local = "w:tc" diff --git a/wml/ctypes/row.go b/wml/ctypes/row.go index b992251..76805c4 100644 --- a/wml/ctypes/row.go +++ b/wml/ctypes/row.go @@ -21,14 +21,6 @@ func DefaultRow() *Row { } } -func (r *Row) AddCell() *Cell { - cell := DefaultCell() - r.Contents = append(r.Contents, TRCellContent{ - Cell: cell, - }) - return cell -} - // TODO Implement Marshal and Unmarshal properly for all fields func (r Row) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { diff --git a/wml/ctypes/text.go b/wml/ctypes/text.go index 223099f..934c4e5 100644 --- a/wml/ctypes/text.go +++ b/wml/ctypes/text.go @@ -7,8 +7,8 @@ import ( ) type Text struct { - text string - space *string + Text string + Space *string } const ( @@ -21,21 +21,21 @@ func NewText() *Text { } func TextFromString(text string) *Text { - t := &Text{text: text} + t := &Text{Text: text} if strings.TrimSpace(text) != text { xmlSpace := "preserve" - t.space = &xmlSpace + t.Space = &xmlSpace } return t } func (t Text) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { - if t.space != nil { - start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xml:space"}, Value: *t.space}) + if t.Space != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xml:space"}, Value: *t.Space}) } - if err = e.EncodeElement(t.text, start); err != nil { + if err = e.EncodeElement(t.Text, start); err != nil { return err } @@ -47,7 +47,7 @@ func (t *Text) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) for _, attr := range start.Attr { if attr.Name.Local == "space" { - t.space = &attr.Value + t.Space = &attr.Value break } } @@ -63,7 +63,7 @@ func (t *Text) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) buf.Write([]byte(tokenElem)) case xml.EndElement: if tokenElem == start.End() { - t.text = buf.String() + t.Text = buf.String() return nil } } diff --git a/wml/ctypes/text_test.go b/wml/ctypes/text_test.go index 2d467a4..c8ecdb3 100644 --- a/wml/ctypes/text_test.go +++ b/wml/ctypes/text_test.go @@ -46,12 +46,12 @@ func TestTextUnmarshalXML(t *testing.T) { }{ {``, NewText()}, {`Hello, World!`, &Text{ - text: "Hello, World!", - space: internal.ToPtr("preserve"), + Text: "Hello, World!", + Space: internal.ToPtr("preserve"), }}, {`Some text`, &Text{ - text: "Some text", - space: internal.ToPtr("preserve"), + Text: "Some text", + Space: internal.ToPtr("preserve"), }}, } @@ -70,11 +70,11 @@ func TestTextUnmarshalXML(t *testing.T) { t.Fatalf("Error during UnmarshalXML: %v", err) } - if result.text != tc.expected.text { - t.Errorf("Expected text %q, but got %q", tc.expected.text, result.text) + if result.Text != tc.expected.Text { + t.Errorf("Expected text %q, but got %q", tc.expected.Text, result.Text) } - if err := internal.ComparePtr("space", tc.expected.space, result.space); err != nil { + if err := internal.ComparePtr("space", tc.expected.Space, result.Space); err != nil { t.Errorf(err.Error()) } }) From 6b6ce69f91422d7d9720c19c7f2f8263345880f6 Mon Sep 17 00:00:00 2001 From: gomutex Date: Tue, 2 Jul 2024 10:42:09 +0530 Subject: [PATCH 2/2] empty para function --- docx/table.go | 57 +++++++++++++++------------------------------------ 1 file changed, 16 insertions(+), 41 deletions(-) diff --git a/docx/table.go b/docx/table.go index 81275a7..7ac1d9e 100644 --- a/docx/table.go +++ b/docx/table.go @@ -141,6 +141,7 @@ func (t *Table) Style(value string) { t.ct.TableProp.Style = ctypes.NewCTString(value) } +// Row Wrapper type Row struct { // Reverse inheriting the Rootdoc into paragraph to access other elements root *RootDoc @@ -149,47 +150,7 @@ type Row struct { ct ctypes.Row } -// func (r *Row) unmarshalXML(d *xml.Decoder, start xml.StartElement) error { -// return r.ct.UnmarshalXML(d, start) -// } - -type RunPropOpts func(*Run) - -func WithFontSize(size uint64) RunPropOpts { - return func(r *Run) { - r.getProp().Size = ctypes.NewFontSize(size * 2) - } -} - -func (r *Row) AddTextCell(text string, opts ...RunPropOpts) *Cell { - // Wrapper cell - cell := Cell{ - root: r.root, - ct: *ctypes.DefaultCell(), - } - - // add paragraph with text & get Run obj - p := newParagraph(r.root) - run := p.AddText(text) - - // Table cell block content - tblContent := ctypes.TCBlockContent{ - Paragraph: &p.ct, - } - - cell.ct.Contents = append(cell.ct.Contents, tblContent) - - r.ct.Contents = append(r.ct.Contents, ctypes.TRCellContent{ - Cell: &cell.ct, - }) - - for _, opt := range opts { - opt(run) - } - - return &cell -} - +// Add Cell to row and returns Cell func (r *Row) AddCell() *Cell { cell := Cell{ root: r.root, @@ -203,6 +164,7 @@ func (r *Row) AddCell() *Cell { return &cell } +// Cell Wrapper type Cell struct { // Reverse inheriting the Rootdoc into paragraph to access other elements root *RootDoc @@ -211,6 +173,7 @@ type Cell struct { ct ctypes.Cell } +// Adds paragraph with text and returns Paragraph func (c *Cell) AddParagraph(text string) *Paragraph { p := newParagraph(c.root, paraWithText(text)) tblContent := ctypes.TCBlockContent{ @@ -221,3 +184,15 @@ func (c *Cell) AddParagraph(text string) *Paragraph { return p } + +// Add empty paragraph without any text and returns Paragraph +func (c *Cell) AddEmptyPara() *Paragraph { + p := newParagraph(c.root) + tblContent := ctypes.TCBlockContent{ + Paragraph: &p.ct, + } + + c.ct.Contents = append(c.ct.Contents, tblContent) + + return p +}