Merge pull request 'fix(docs): pair comment ids with the document order (#92)' (#93) from fix/docs-comment-id#92 into main
Release / GoReleaser (push) Skipped
Tests / Secret scan (gitleaks) (push) Successful in 5s
Tests / Test (Go 1.25) (push) Successful in 28s
Tests / Test (Go stable) (push) Successful in 34s

This commit was merged in pull request #93.
This commit is contained in:
2026-10-07 18:45:20 +01:00
2 changed files with 47 additions and 6 deletions
+4 -6
View File
@@ -65,6 +65,7 @@ func InjectDocxComments(src []byte, author string, comments []DocxComment) ([]by
}
used := make([]bool, len(comments))
applied := make([]DocxComment, 0, len(comments))
id := 0
newDoc := docxParaRe.ReplaceAllStringFunc(string(doc), func(p string) string {
txt := docxPlain(p)
@@ -73,6 +74,9 @@ func InjectDocxComments(src []byte, author string, comments []DocxComment) ([]by
continue
}
used[i] = true
// Record in document order so comments.xml id N matches the marker
// with id N (the spec order may differ from the document order).
applied = append(applied, c)
p = docxAddMarkers(p, id)
id++
break
@@ -80,12 +84,6 @@ func InjectDocxComments(src []byte, author string, comments []DocxComment) ([]by
return p
})
applied := make([]DocxComment, 0, len(comments))
for i, c := range comments {
if used[i] {
applied = append(applied, c)
}
}
parts["word/document.xml"] = []byte(newDoc)
parts["word/comments.xml"] = docxBuildComments(author, applied)
parts["[Content_Types].xml"] = docxAddContentType(parts["[Content_Types].xml"])
+43
View File
@@ -4,6 +4,7 @@ import (
"archive/zip"
"bytes"
"io"
"regexp"
"strings"
"testing"
)
@@ -124,6 +125,48 @@ func TestInjectDocxCommentsNoMatch(t *testing.T) {
}
}
// TestInjectDocxCommentsOrder guards the marker-id ↔ comments.xml pairing when
// the spec order differs from the document order.
func TestInjectDocxCommentsOrder(t *testing.T) {
src := buildTestDocx(t)
spec := []DocxComment{
{Anchor: "Nothing relevant", Text: "comment B"},
{Anchor: "The term is two years", Text: "comment A"},
}
out, applied, err := InjectDocxComments(src, "A", spec)
if err != nil || applied != 2 {
t.Fatalf("inject: applied=%d err=%v", applied, err)
}
doc := unzipPart(t, out, "word/document.xml")
cmts := unzipPart(t, out, "word/comments.xml")
// id -> text from comments.xml
byID := map[string]string{}
for _, m := range regexp.MustCompile(`(?s)<w:comment w:id="(\d+)"[^>]*>(.*?)</w:comment>`).FindAllStringSubmatch(cmts, -1) {
txt := strings.TrimSpace(stripTags(m[2]))
byID[m[1]] = txt
}
// paragraph that starts with the term text must point at "comment A"
for _, p := range regexp.MustCompile(`(?s)<w:p\b[^>]*>.*?</w:p>`).FindAllString(doc, -1) {
if !strings.Contains(stripTags(p), "The term is two years") {
continue
}
m := regexp.MustCompile(`<w:commentRangeStart w:id="(\d+)"/>`).FindStringSubmatch(p)
if m == nil {
t.Fatal("no commentRangeStart in the term paragraph")
}
if got := byID[m[1]]; got != "comment A" {
t.Fatalf("term paragraph points at id %s = %q, want \"comment A\"", m[1], got)
}
return
}
t.Fatal("term paragraph not found")
}
func stripTags(s string) string {
return regexp.MustCompile(`<[^>]*>`).ReplaceAllString(s, "")
}
func TestParseDocxComments(t *testing.T) {
got, err := ParseDocxComments([]byte(`[{"anchor":"a","text":"b"}]`))
if err != nil {