From c715921b00514147cf00a58cbbfe9ab6aa8ddeb5 Mon Sep 17 00:00:00 2001 From: 8thgencore Date: Tue, 9 Jul 2024 18:14:25 +0300 Subject: [PATCH] add: format parameters --- Param.go | 53 ++++++++++++++++++++----- ParamFormatter.go | 85 +++++++++++++++++++++++++++++++++++++++++ ParamList.go | 1 + ParamTrigger.go | 38 ++++++++++-------- Template.stage.funcs.go | 17 +++++++-- go.mod | 5 ++- go.sum | 2 + 7 files changed, 170 insertions(+), 31 deletions(-) create mode 100644 ParamFormatter.go diff --git a/Param.go b/Param.go index 15c101f..cfe749d 100644 --- a/Param.go +++ b/Param.go @@ -46,7 +46,8 @@ type Param struct { Separator string // {{Usernames SEPERATOR}} - Trigger *ParamTrigger + Trigger *ParamTrigger + Formatter *ParamFormatter RowPlaceholder string Index int //slice data index,expandPlaceholders function needs @@ -74,6 +75,8 @@ func NewParamFromRaw(raw []byte) *Param { p := NewParam(string(matches[0][2])) p.Separator = strings.TrimSpace(string(matches[0][3])) p.Trigger = NewParamTrigger(matches[0][4]) + p.Formatter = NewFormatter(matches[0][4]) + return p } @@ -97,20 +100,28 @@ func (p *Param) SetValue(val any) { // Placeholder .. {{Key}} func (p *Param) Placeholder() string { + var formatter string = "" + if p.Formatter != nil { + formatter = p.Formatter.String() + } var trigger string = "" if p.Trigger != nil { - trigger = " " + p.Trigger.String() + trigger = p.Trigger.String() } - return "{{" + p.AbsoluteKey + trigger + "}}" + return "{{" + p.AbsoluteKey + " " + formatter + trigger + "}}" } // PlaceholderKey .. {{#Key}} func (p *Param) PlaceholderKey() string { - var trigger string + var formatter string = "" + if p.Formatter != nil { + formatter = p.Formatter.String() + } + var trigger string = "" if p.Trigger != nil { - trigger = " " + p.Trigger.String() + trigger = p.Trigger.String() } - return "{{#" + p.AbsoluteKey + trigger + "}}" + return "{{#" + p.AbsoluteKey + " " + formatter + trigger + "}}" } // PlaceholderInline .. {{Key ,}} @@ -213,6 +224,31 @@ func (p *Param) extractTriggerFrom(buf []byte) *ParamTrigger { return nil } +// Try to extract trigger from raw contents specific to this param +func (p *Param) extractFormatter(buf []byte) *ParamFormatter { + prefixes := []string{ + p.PlaceholderInline(), + p.PlaceholderKeyInline(), + } + for _, pref := range prefixes { + bpref := []byte(pref) + if !bytes.Contains(buf, bpref) { + continue + } + + // Get part where trigger is (remove plaheolder prefix) + buf := bytes.SplitN(buf, bpref, 2)[1] + + // Remove placeholder suffix and only raw trigger part left + buf = bytes.SplitN(buf, []byte("}}"), 2)[0] + + p.Formatter = NewFormatter(buf) + return p.Formatter + } + + return nil +} + // RunTrigger - execute trigger func (p *Param) RunTrigger(xnode *xmlNode) { if p == nil || p.Trigger == nil { @@ -240,7 +276,6 @@ func (p *Param) RunTrigger(xnode *xmlNode) { n := xnode.closestUp(ntypes) if n == nil { - // aurora.Red("EMPTY parent of %v", xnode.Tag()) return } @@ -254,7 +289,6 @@ func (p *Param) RunTrigger(xnode *xmlNode) { n.parent.childFirst.iterate(func(wpNode *xmlNode) bool { isitem, listid := wpNode.IsListItem() if !isitem || listid != listID { - // aurora.Red("--- %s [%s]", wpNode, wpNode.AllContents()) return false } if p.Trigger.Command == TriggerCommandRemove { @@ -267,7 +301,6 @@ func (p *Param) RunTrigger(xnode *xmlNode) { // Simple cases if p.Trigger.Command == TriggerCommandRemove { - // fmt.Printf("Trigger: [%s] [%s]\t Command=[%s]\n", aurora.Blue(p.AbsoluteKey), aurora.Magenta(p.Trigger.String()), aurora.BgMagenta(p.Trigger.Command)) n.delete() return } @@ -279,13 +312,13 @@ func (p *Param) RunTrigger(xnode *xmlNode) { }) return } - } // String - compact debug information as string func (p *Param) String() string { s := fmt.Sprintf("%34s=%-20s", p.AbsoluteKey, p.Value) s += fmt.Sprintf("\tSeparator[%s]", p.Separator) + s += fmt.Sprintf("\tFormatter[%s]", p.Formatter) s += fmt.Sprintf("\tTrigger[%s]", p.Trigger) return s } diff --git a/ParamFormatter.go b/ParamFormatter.go new file mode 100644 index 0000000..154f850 --- /dev/null +++ b/ParamFormatter.go @@ -0,0 +1,85 @@ +package docxplate + +import ( + "bytes" + "strings" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +// Format - how to format an element +const ( + FormatLower = ":lower" + FormatUpper = ":upper" + FormatTitle = ":title" + FormatCapitalize = ":capitalize" +) + +type ParamFormatter struct { + raw string + + Format string +} + +// NewFormatter - take raw ":empty:remove:list" and make formatter and its fields from it +func NewFormatter(raw []byte) *ParamFormatter { + raw = bytes.TrimSpace(raw) + raw = bytes.ToLower(raw) + + // init with defaults + f := &ParamFormatter{ + raw: string(raw), + } + + // Always must start with ":" + if !strings.HasPrefix(f.raw, ":") { + return nil + } + + // Remove the first ":" so split parts counting is more readable + // Split into parts + parts := strings.Split(f.raw[1:], ":") + + for _, part := range parts { + switch part { + case "lower", "upper", "title", "capitalize": + f.Format = ":" + part + } + } + + return f +} + +// applyFormat - apply formatting to the given content based on the formatter +func (p *ParamFormatter) ApplyFormat(format string, content []byte) []byte { + switch format { + case FormatLower: + return bytes.ToLower(content) + case FormatUpper: + return bytes.ToUpper(content) + case FormatTitle: + titleCaser := cases.Title(language.Und) + return []byte(titleCaser.String(string(content))) + case FormatCapitalize: + content = bytes.TrimSpace(content) + if len(content) > 0 { + content[0] = bytes.ToUpper([]byte{content[0]})[0] + if len(content) > 1 { + content = append([]byte{content[0]}, bytes.ToLower(content[1:])...) + } + } + return content + default: + return content + } +} + +// String - return rebuilt formatter string +func (p *ParamFormatter) String() string { + if p == nil { + return "" + } + s := p.Format + return s +} diff --git a/ParamList.go b/ParamList.go index b781f2d..bb8dd80 100644 --- a/ParamList.go +++ b/ParamList.go @@ -232,6 +232,7 @@ func rowParams(row []byte) ParamList { p.RowPlaceholder = string(match[0]) p.Separator = string(match[3]) p.Trigger = NewParamTrigger(match[4]) + p.Formatter = NewFormatter(match[4]) list = append(list, p) } return list diff --git a/ParamTrigger.go b/ParamTrigger.go index 345b3d6..9270473 100644 --- a/ParamTrigger.go +++ b/ParamTrigger.go @@ -41,8 +41,7 @@ type ParamTrigger struct { Scope string } -// NewParamTrigger - take raw ":empty:remove:list" and make -// trigger and it's fields from it +// NewParamTrigger - take raw ":empty:remove:list" and make trigger and its fields from it func NewParamTrigger(raw []byte) *ParamTrigger { raw = bytes.TrimSpace(raw) raw = bytes.ToLower(raw) @@ -59,28 +58,34 @@ func NewParamTrigger(raw []byte) *ParamTrigger { if !strings.HasPrefix(tr.raw, ":") { return nil } - // remove first ":" so split parts counting is more readable - tr.raw = strings.TrimPrefix(tr.raw, ":") - // Must be at least set two parts ":empty:remove" - if strings.Count(tr.raw, ":") < 2 { - return nil + // Remove the first ":" so split parts counting is more readable + // Split into parts + parts := strings.Split(tr.raw[1:], ":") + + var countCommandParts = 0 + for _, part := range parts { + switch part { + case "unknown", "empty", "=": + countCommandParts++ + tr.On = ":" + part + case "remove", "clear": + countCommandParts++ + tr.Command = ":" + part + case "placeholder", "cell", "row", "list", "table", "section": + countCommandParts++ + tr.Scope = ":" + part + } } - // Extract fields - arr := strings.SplitN(tr.raw, ":", 3) - tr.On = ":" + arr[0] - tr.Command = ":" + arr[1] - - // Scope: set default if not found - if len(arr) >= 3 { - tr.Scope = ":" + arr[2] + + if countCommandParts != 3 { + return nil } if !tr.isValid() { return nil } - // fmt.Printf("TRIGGER: %+v", tr) return tr } @@ -89,6 +94,7 @@ func (tr *ParamTrigger) isValid() bool { // On if !inSlice(tr.On, []string{ + TriggerOnUnknown, TriggerOnEmpty, TriggerOnValue, }) { diff --git a/Template.stage.funcs.go b/Template.stage.funcs.go index 92b0b54..fb22f14 100644 --- a/Template.stage.funcs.go +++ b/Template.stage.funcs.go @@ -21,7 +21,6 @@ func (t *Template) triggerMissingParams(xnode *xmlNode) { if !n.isRowElement() || !n.HaveParams() { return } - p := NewParamFromRaw(n.AllContents()) if p != nil && p.Trigger != nil { triggerParams = append(triggerParams, p) @@ -68,9 +67,14 @@ func (t *Template) expandPlaceholders(xnode *xmlNode) { placeholderType = rowPlaceholder } + var formatter string + if rowParam.Formatter != nil { + formatter = rowParam.Formatter.String() + } + var trigger string if rowParam.Trigger != nil { - trigger = " " + rowParam.Trigger.String() + trigger = rowParam.Trigger.String() } paramData := t.params.FindAllByKey(rowParam.AbsoluteKey) @@ -80,7 +84,7 @@ func (t *Template) expandPlaceholders(xnode *xmlNode) { placeholders := make([]string, paramData[len(paramData)-1].Index) for _, param := range paramData { - placeholders[param.Index-1] = "{{" + param.AbsoluteKey + trigger + "}}" + placeholders[param.Index-1] = "{{" + param.AbsoluteKey + " " + formatter + trigger + "}}" } rowPlaceholders[rowParam.RowPlaceholder] = &placeholder{ Type: placeholderType, @@ -109,16 +113,17 @@ func (t *Template) expandPlaceholders(xnode *xmlNode) { if nnews[i] == nil { nnews[i] = nrow.cloneAndAppend() } + nnews[i].Walk(func(n *xmlNode) { if !inSlice(n.XMLName.Local, []string{"w-t"}) || len(n.Content) == 0 { return } + replaceData := oldPlaceholder if i < len(newPlaceholder.Placeholders) && newPlaceholder.Placeholders[i] != "" { replaceData = newPlaceholder.Placeholders[i] } n.Content = bytes.ReplaceAll(n.Content, []byte(oldPlaceholder), []byte(replaceData)) - }) } } @@ -169,6 +174,10 @@ func (t *Template) replaceAndRunTrigger(p *Param, n *xmlNode, triggerParamOnly b // Repalce by type switch p.Type { case StringParam: + if p.Formatter = p.extractFormatter(n.Content); p.Formatter != nil { + result := p.Formatter.ApplyFormat(p.Formatter.Format, []byte(p.Value)) + p.Value = string(result) + } t.replaceTextParam(n, p) case ImageParam: t.replaceImageParams(n, p) diff --git a/go.mod b/go.mod index ac29722..2f3d007 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/bobiverse/docxplate go 1.22.4 -require github.com/logrusorgru/aurora/v4 v4.0.0 +require ( + github.com/logrusorgru/aurora/v4 v4.0.0 + golang.org/x/text v0.16.0 +) diff --git a/go.sum b/go.sum index 0ea1602..ae2982b 100644 --- a/go.sum +++ b/go.sum @@ -6,5 +6,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=