denotesrv/pkg/encoding/results/results.go

89 lines
2.4 KiB
Go

package results
import (
"bytes"
"fmt"
"log"
"regexp"
"strings"
"denotesrv/pkg/metadata"
)
// Marshal serializes Results to a pipe-delimited byte format.
// Format: identifier | title | tags (comma-separated)
func Marshal(rs metadata.Results) []byte {
var buf strings.Builder
for _, e := range rs {
title := e.Title
if title == "" {
title = "(untitled)"
}
tags := strings.Join(e.Tags, ",")
fmt.Fprintf(&buf, "%s | %s | %s\n", e.Identifier, title, tags)
}
return []byte(buf.String())
}
// Unmarshal parses pipe-delimited byte data into Results.
// Format: identifier | title | tags (comma-separated)
// Invalid tags produce warnings but parsing continues.
func Unmarshal(data []byte) (metadata.Results, error) {
return unmarshal(data, false)
}
// UnmarshalStrict parses pipe-delimited byte data into Results with strict tag validation.
// Returns an error if any tags are invalid.
func UnmarshalStrict(data []byte) (metadata.Results, error) {
return unmarshal(data, true)
}
func unmarshal(data []byte, strict bool) (metadata.Results, error) {
var results metadata.Results
lines := bytes.Split(bytes.TrimSpace(data), []byte("\n"))
// Allow lowercase Latin letters, other letters (CJK, etc.), and digits, no spaces
tagPattern := regexp.MustCompile(`^([\p{Ll}\p{Lo}\p{Nd}]+,)*[\p{Ll}\p{Lo}\p{Nd}]+$`)
for lineNum, line := range lines {
line = bytes.TrimSpace(line)
if len(line) == 0 {
continue
}
parts := bytes.Split(line, []byte("|"))
if len(parts) != 3 {
return nil, fmt.Errorf("line %d: expected 3 columns, got %d (line: %q)", lineNum+1, len(parts), line)
}
identifier := string(bytes.TrimSpace(parts[0]))
title := string(bytes.TrimSpace(parts[1]))
tagsStr := string(bytes.TrimSpace(parts[2]))
if identifier == "" {
return nil, fmt.Errorf("line %d: identifier cannot be empty", lineNum+1)
}
var tags []string
if tagsStr != "" {
if !tagPattern.MatchString(tagsStr) {
if strict {
return nil, fmt.Errorf("line %d: tags must be comma-delimited lowercase unicode words (no spaces): got '%s'", lineNum+1, tagsStr)
}
log.Printf("warning: line %d: invalid tags '%s' (must be lowercase alphanumeric)", lineNum+1, tagsStr)
}
tags = strings.Split(tagsStr, ",")
} else {
tags = []string{}
}
results = append(results, &metadata.Metadata{
Identifier: identifier,
Title: title,
Tags: tags,
})
}
return results, nil
}