document: support inserting paragraphs within a document

This commit is contained in:
Todd 2017-09-28 18:12:22 -05:00
parent 89b1416b8f
commit 1b53d772ee
4 changed files with 119 additions and 33 deletions

View File

@ -39,6 +39,15 @@ func main() {
r.ClearContent() r.ClearContent()
r.AddText("John ") r.AddText("John ")
r.AddBreak() r.AddBreak()
para := doc.InsertParagraphBefore(p)
para.AddRun().AddText("Mr.")
para.SetStyle("Name") // Name is a default style in this template file
para = doc.InsertParagraphAfter(p)
para.AddRun().AddText("III")
para.SetStyle("Name")
case "LAST NAME": case "LAST NAME":
r.ClearContent() r.ClearContent()
r.AddText("Smith") r.AddText("Smith")

View File

@ -484,23 +484,23 @@ func (d *Document) FormFields() []FormField {
return ret return ret
} }
func (doc *Document) onNewRelationship(decMap *zippkg.DecodeMap, target, typ string, files []*zip.File, rel *relationships.Relationship, src zippkg.Target) error { func (d *Document) onNewRelationship(decMap *zippkg.DecodeMap, target, typ string, files []*zip.File, rel *relationships.Relationship, src zippkg.Target) error {
dt := gooxml.DocTypeDocument dt := gooxml.DocTypeDocument
switch typ { switch typ {
case gooxml.OfficeDocumentType: case gooxml.OfficeDocumentType:
doc.x = wml.NewDocument() d.x = wml.NewDocument()
decMap.AddTarget(target, doc.x, typ, 0) decMap.AddTarget(target, d.x, typ, 0)
// look for the document relationships file as well // look for the document relationships file as well
decMap.AddTarget(zippkg.RelationsPathFor(target), doc.docRels.X(), typ, 0) decMap.AddTarget(zippkg.RelationsPathFor(target), d.docRels.X(), typ, 0)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0) rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0)
case gooxml.CorePropertiesType: case gooxml.CorePropertiesType:
decMap.AddTarget(target, doc.CoreProperties.X(), typ, 0) decMap.AddTarget(target, d.CoreProperties.X(), typ, 0)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0) rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0)
case gooxml.ExtendedPropertiesType: case gooxml.ExtendedPropertiesType:
decMap.AddTarget(target, doc.AppProperties.X(), typ, 0) decMap.AddTarget(target, d.AppProperties.X(), typ, 0)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0) rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0)
case gooxml.ThumbnailType: case gooxml.ThumbnailType:
@ -514,7 +514,7 @@ func (doc *Document) onNewRelationship(decMap *zippkg.DecodeMap, target, typ str
if err != nil { if err != nil {
return fmt.Errorf("error reading thumbnail: %s", err) return fmt.Errorf("error reading thumbnail: %s", err)
} }
doc.Thumbnail, _, err = image.Decode(rc) d.Thumbnail, _, err = image.Decode(rc)
rc.Close() rc.Close()
if err != nil { if err != nil {
return fmt.Errorf("error decoding thumbnail: %s", err) return fmt.Errorf("error decoding thumbnail: %s", err)
@ -524,55 +524,55 @@ func (doc *Document) onNewRelationship(decMap *zippkg.DecodeMap, target, typ str
} }
case gooxml.SettingsType: case gooxml.SettingsType:
decMap.AddTarget(target, doc.Settings.X(), typ, 0) decMap.AddTarget(target, d.Settings.X(), typ, 0)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0) rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0)
case gooxml.NumberingType: case gooxml.NumberingType:
doc.Numbering = NewNumbering() d.Numbering = NewNumbering()
decMap.AddTarget(target, doc.Numbering.X(), typ, 0) decMap.AddTarget(target, d.Numbering.X(), typ, 0)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0) rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0)
case gooxml.StylesType: case gooxml.StylesType:
doc.Styles.Clear() d.Styles.Clear()
decMap.AddTarget(target, doc.Styles.X(), typ, 0) decMap.AddTarget(target, d.Styles.X(), typ, 0)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0) rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0)
case gooxml.HeaderType: case gooxml.HeaderType:
hdr := wml.NewHdr() hdr := wml.NewHdr()
decMap.AddTarget(target, hdr, typ, uint32(len(doc.headers))) decMap.AddTarget(target, hdr, typ, uint32(len(d.headers)))
doc.headers = append(doc.headers, hdr) d.headers = append(d.headers, hdr)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, len(doc.headers)) rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, len(d.headers))
case gooxml.FooterType: case gooxml.FooterType:
ftr := wml.NewFtr() ftr := wml.NewFtr()
decMap.AddTarget(target, ftr, typ, uint32(len(doc.footers))) decMap.AddTarget(target, ftr, typ, uint32(len(d.footers)))
doc.footers = append(doc.footers, ftr) d.footers = append(d.footers, ftr)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, len(doc.footers)) rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, len(d.footers))
case gooxml.ThemeType: case gooxml.ThemeType:
thm := dml.NewTheme() thm := dml.NewTheme()
decMap.AddTarget(target, thm, typ, uint32(len(doc.themes))) decMap.AddTarget(target, thm, typ, uint32(len(d.themes)))
doc.themes = append(doc.themes, thm) d.themes = append(d.themes, thm)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, len(doc.themes)) rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, len(d.themes))
case gooxml.WebSettingsType: case gooxml.WebSettingsType:
doc.webSettings = wml.NewWebSettings() d.webSettings = wml.NewWebSettings()
decMap.AddTarget(target, doc.webSettings, typ, 0) decMap.AddTarget(target, d.webSettings, typ, 0)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0) rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0)
case gooxml.FontTableType: case gooxml.FontTableType:
doc.fontTable = wml.NewFonts() d.fontTable = wml.NewFonts()
decMap.AddTarget(target, doc.fontTable, typ, 0) decMap.AddTarget(target, d.fontTable, typ, 0)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0) rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0)
case gooxml.EndNotesType: case gooxml.EndNotesType:
doc.endNotes = wml.NewEndnotes() d.endNotes = wml.NewEndnotes()
decMap.AddTarget(target, doc.endNotes, typ, 0) decMap.AddTarget(target, d.endNotes, typ, 0)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0) rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0)
case gooxml.FootNotesType: case gooxml.FootNotesType:
doc.footNotes = wml.NewFootnotes() d.footNotes = wml.NewFootnotes()
decMap.AddTarget(target, doc.footNotes, typ, 0) decMap.AddTarget(target, d.footNotes, typ, 0)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0) rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0)
case gooxml.ImageType: case gooxml.ImageType:
@ -581,7 +581,7 @@ func (doc *Document) onNewRelationship(decMap *zippkg.DecodeMap, target, typ str
continue continue
} }
if f.Name == target { if f.Name == target {
path, err := zippkg.ExtractToDiskTmp(f, doc.TmpPath) path, err := zippkg.ExtractToDiskTmp(f, d.TmpPath)
if err != nil { if err != nil {
return err return err
} }
@ -589,14 +589,72 @@ func (doc *Document) onNewRelationship(decMap *zippkg.DecodeMap, target, typ str
if err != nil { if err != nil {
return err return err
} }
iref := common.MakeImageRef(img, &doc.DocBase, doc.docRels) iref := common.MakeImageRef(img, &d.DocBase, d.docRels)
doc.Images = append(doc.Images, iref) d.Images = append(d.Images, iref)
files[i] = nil files[i] = nil
} }
} }
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, len(doc.Images)) rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, len(d.Images))
default: default:
log.Printf("unsupported relationship type: %s tgt: %s", typ, target) log.Printf("unsupported relationship type: %s tgt: %s", typ, target)
} }
return nil return nil
} }
// InsertParagraphAfter adds a new empty paragraph after the relativeTo
// paragraph.
func (d *Document) InsertParagraphAfter(relativeTo Paragraph) Paragraph {
return d.insertParagraph(relativeTo, false)
}
// InsertParagraphBefore adds a new empty paragraph before the relativeTo
// paragraph.
func (d *Document) InsertParagraphBefore(relativeTo Paragraph) Paragraph {
return d.insertParagraph(relativeTo, true)
}
func (d *Document) insertParagraph(relativeTo Paragraph, before bool) Paragraph {
if d.x.Body == nil {
return d.AddParagraph()
}
for _, ble := range d.x.Body.EG_BlockLevelElts {
for _, c := range ble.EG_ContentBlockContent {
for i, p := range c.P {
// foudn the paragraph
if p == relativeTo.X() {
p := wml.NewCT_P()
if before {
c.P = append(c.P, nil)
copy(c.P[i+1:], c.P[i:])
c.P[i] = p
} else {
c.P = append(c.P, nil)
copy(c.P[i+2:], c.P[i+1:])
c.P[i+1] = p
}
return Paragraph{d, p}
}
}
if c.Sdt != nil && c.Sdt.SdtContent != nil && c.Sdt.SdtContent.P != nil {
for i, p := range c.Sdt.SdtContent.P {
if p == relativeTo.X() {
p := wml.NewCT_P()
if before {
c.Sdt.SdtContent.P = append(c.Sdt.SdtContent.P, nil)
copy(c.Sdt.SdtContent.P[i+1:], c.Sdt.SdtContent.P[i:])
c.Sdt.SdtContent.P[i] = p
} else {
c.Sdt.SdtContent.P = append(c.Sdt.SdtContent.P, nil)
copy(c.Sdt.SdtContent.P[i+2:], c.Sdt.SdtContent.P[i+1:])
c.Sdt.SdtContent.P[i+1] = p
}
return Paragraph{d, p}
}
}
}
}
}
return d.AddParagraph()
}

View File

@ -82,3 +82,22 @@ func TestOpenWord2016(t *testing.T) {
} }
testhelper.CompareGoldenZipFilesOnly(t, "../../testdata/Office2016/Word-Windows.docx", got.Bytes()) testhelper.CompareGoldenZipFilesOnly(t, "../../testdata/Office2016/Word-Windows.docx", got.Bytes())
} }
func TestInsertParagraph(t *testing.T) {
doc := document.New()
if len(doc.Paragraphs()) != 0 {
t.Errorf("expected 0 paragraphs, got %d", len(doc.Paragraphs()))
}
p := doc.AddParagraph()
before := doc.InsertParagraphBefore(p)
after := doc.InsertParagraphAfter(p)
if len(doc.Paragraphs()) != 3 {
t.Errorf("expected 3 paragraphs, got %d", len(doc.Paragraphs()))
}
if doc.Paragraphs()[0].X() != before.X() {
t.Error("InsertParagraphBefore failed")
}
if doc.Paragraphs()[2].X() != after.X() {
t.Error("InsertParagraphAfter failed")
}
}