feat(docs): oo docs comment — add Word comments to a .docx (#88)
Release / GoReleaser (push) Skipped
Tests / Secret scan (gitleaks) (push) Skipped
Tests / Test (Go 1.25) (push) Skipped
Tests / Test (Go stable) (push) Skipped
Tests / Secret scan (gitleaks) (pull_request) Successful in 5s
Tests / Test (Go stable) (pull_request) Successful in 2m51s
Tests / Test (Go 1.25) (pull_request) Successful in 2m51s
Release / GoReleaser (push) Skipped
Tests / Secret scan (gitleaks) (push) Skipped
Tests / Test (Go 1.25) (push) Skipped
Tests / Test (Go stable) (push) Skipped
Tests / Secret scan (gitleaks) (pull_request) Successful in 5s
Tests / Test (Go stable) (pull_request) Successful in 2m51s
Tests / Test (Go 1.25) (pull_request) Successful in 2m51s
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
package onlyoffice
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// buildTestDocx returns a minimal valid .docx with two paragraphs.
|
||||
func buildTestDocx(t *testing.T) []byte {
|
||||
t.Helper()
|
||||
const (
|
||||
contentTypes = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/></Types>`
|
||||
document = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:pPr><w:jc w:val="both"/></w:pPr><w:r><w:t>The term is two years, ending 8 October 2028.</w:t></w:r></w:p><w:p><w:r><w:t>Nothing relevant here.</w:t></w:r></w:p></w:body></w:document>`
|
||||
rels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>`
|
||||
)
|
||||
var buf bytes.Buffer
|
||||
zw := zip.NewWriter(&buf)
|
||||
for name, body := range map[string]string{
|
||||
"[Content_Types].xml": contentTypes,
|
||||
"word/document.xml": document,
|
||||
"word/_rels/document.xml.rels": rels,
|
||||
} {
|
||||
w, err := zw.Create(name)
|
||||
if err != nil {
|
||||
t.Fatalf("zip create: %v", err)
|
||||
}
|
||||
if _, err := w.Write([]byte(body)); err != nil {
|
||||
t.Fatalf("zip write: %v", err)
|
||||
}
|
||||
}
|
||||
if err := zw.Close(); err != nil {
|
||||
t.Fatalf("zip close: %v", err)
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func unzipPart(t *testing.T, data []byte, name string) string {
|
||||
t.Helper()
|
||||
zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
|
||||
if err != nil {
|
||||
t.Fatalf("zip reader: %v", err)
|
||||
}
|
||||
for _, f := range zr.File {
|
||||
if f.Name != name {
|
||||
continue
|
||||
}
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
t.Fatalf("open part: %v", err)
|
||||
}
|
||||
defer rc.Close()
|
||||
b, err := io.ReadAll(rc)
|
||||
if err != nil {
|
||||
t.Fatalf("read part: %v", err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
t.Fatalf("part %s not found", name)
|
||||
return ""
|
||||
}
|
||||
|
||||
func TestInjectDocxComments(t *testing.T) {
|
||||
src := buildTestDocx(t)
|
||||
comments := []DocxComment{
|
||||
{Anchor: "8 October 2028", Text: "Change to two years."},
|
||||
{Anchor: "two years", Text: "second anchor, same paragraph"},
|
||||
{Anchor: "no such text anywhere", Text: "skipped"},
|
||||
}
|
||||
// First anchor matches the first paragraph; the second also matches that
|
||||
// paragraph but must be skipped (one comment per paragraph).
|
||||
out, applied, err := InjectDocxComments(src, "Andriy Oblivantsev", comments)
|
||||
if err != nil {
|
||||
t.Fatalf("inject: %v", err)
|
||||
}
|
||||
if applied != 1 {
|
||||
t.Fatalf("applied = %d, want 1", applied)
|
||||
}
|
||||
|
||||
doc := unzipPart(t, out, "word/document.xml")
|
||||
for _, want := range []string{"commentRangeStart", "commentRangeEnd", "commentReference"} {
|
||||
if !strings.Contains(doc, want) {
|
||||
t.Errorf("document.xml missing %s", want)
|
||||
}
|
||||
}
|
||||
if strings.Contains(doc, `<w:pPr><w:commentRangeStart`) {
|
||||
t.Error("commentRangeStart inserted inside pPr")
|
||||
}
|
||||
|
||||
cmts := unzipPart(t, out, "word/comments.xml")
|
||||
if strings.Count(cmts, "<w:comment ") != 1 {
|
||||
t.Fatalf("comments count = %d, want 1", strings.Count(cmts, "<w:comment "))
|
||||
}
|
||||
if !strings.Contains(cmts, `w:author="Andriy Oblivantsev"`) {
|
||||
t.Error("author missing in comments.xml")
|
||||
}
|
||||
if !strings.Contains(cmts, "Change to two years.") {
|
||||
t.Error("comment text missing")
|
||||
}
|
||||
|
||||
ct := unzipPart(t, out, "[Content_Types].xml")
|
||||
if !strings.Contains(ct, "/word/comments.xml") {
|
||||
t.Error("content types missing comments override")
|
||||
}
|
||||
rels := unzipPart(t, out, "word/_rels/document.xml.rels")
|
||||
if !strings.Contains(rels, "relationships/comments") {
|
||||
t.Error("document rels missing comments relationship")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInjectDocxCommentsNoMatch(t *testing.T) {
|
||||
src := buildTestDocx(t)
|
||||
out, applied, err := InjectDocxComments(src, "X", []DocxComment{{Anchor: "absent", Text: "t"}})
|
||||
if err != nil {
|
||||
t.Fatalf("inject: %v", err)
|
||||
}
|
||||
if applied != 0 {
|
||||
t.Fatalf("applied = %d, want 0", applied)
|
||||
}
|
||||
if out == nil {
|
||||
t.Fatal("output nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDocxComments(t *testing.T) {
|
||||
got, err := ParseDocxComments([]byte(`[{"anchor":"a","text":"b"}]`))
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if len(got) != 1 || got[0].Anchor != "a" || got[0].Text != "b" {
|
||||
t.Fatalf("unexpected: %+v", got)
|
||||
}
|
||||
if _, err := ParseDocxComments([]byte(`{"anchor":"a"}`)); err == nil {
|
||||
t.Fatal("expected error for object input")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user