oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package calc
2
3import "strings"
4
5func init() {
6 Register("CONCAT", fnConcat)
7 Register("LEFT", fnLeft)
8 Register("RIGHT", fnRight)
9 Register("MID", fnMid)
10 Register("LEN", fnLen)
11 Register("TRIM", fnTrim)
12 Register("UPPER", fnUpper)
13 Register("LOWER", fnLower)
14}
15
16func fnConcat(args []Value) Value {
17 var sb strings.Builder
18 for _, a := range args {
19 if a.Kind == KindErr {
20 return a
21 }
22 sb.WriteString(toStr(a))
23 }
24 return Value{Kind: KindStr, Str: sb.String()}
25}
26
27// textAndCount pulls the (text, count) shape shared by LEFT and RIGHT:
28// text is required, count defaults to defaultCount and, when given, must
29// coerce to a non-negative integer.
30func textAndCount(args []Value, defaultCount int) (text string, count int, errv Value, ok bool) {
31 if len(args) < 1 || len(args) > 2 {
32 return "", 0, errValue(ErrValue), false
33 }
34 if args[0].Kind == KindErr {
35 return "", 0, args[0], false
36 }
37 text = toStr(args[0])
38 count = defaultCount
39 if len(args) == 2 {
40 if args[1].Kind == KindErr {
41 return "", 0, args[1], false
42 }
43 n, ek := toNumber(args[1])
44 if ek != ErrNone {
45 return "", 0, errValue(ek), false
46 }
47 count = int(n)
48 }
49 if count < 0 {
50 return "", 0, errValue(ErrValue), false
51 }
52 return text, count, Value{}, true
53}
54
55// fnLeft and fnRight operate on runes, not bytes, so a multi-byte
56// character counts as one character the way Excel's own LEN/LEFT/RIGHT
57// do (their unit is "character," not "byte").
58func fnLeft(args []Value) Value {
59 text, n, errv, ok := textAndCount(args, 1)
60 if !ok {
61 return errv
62 }
63 r := []rune(text)
64 if n > len(r) {
65 n = len(r)
66 }
67 return Value{Kind: KindStr, Str: string(r[:n])}
68}
69
70func fnRight(args []Value) Value {
71 text, n, errv, ok := textAndCount(args, 1)
72 if !ok {
73 return errv
74 }
75 r := []rune(text)
76 if n > len(r) {
77 n = len(r)
78 }
79 return Value{Kind: KindStr, Str: string(r[len(r)-n:])}
80}
81
82// fnMid implements MID(text, start_num, num_chars): start_num is
83// 1-based, per Excel; a start past the end of text returns "" rather
84// than an error, matching Excel.
85func fnMid(args []Value) Value {
86 if len(args) != 3 {
87 return errValue(ErrValue)
88 }
89 if args[0].Kind == KindErr {
90 return args[0]
91 }
92 if args[1].Kind == KindErr {
93 return args[1]
94 }
95 if args[2].Kind == KindErr {
96 return args[2]
97 }
98 text := toStr(args[0])
99 startF, ek := toNumber(args[1])
100 if ek != ErrNone {
101 return errValue(ek)
102 }
103 countF, ek := toNumber(args[2])
104 if ek != ErrNone {
105 return errValue(ek)
106 }
107 start, count := int(startF), int(countF)
108 if start < 1 || count < 0 {
109 return errValue(ErrValue)
110 }
111 r := []rune(text)
112 if start > len(r) {
113 return Value{Kind: KindStr, Str: ""}
114 }
115 end := start - 1 + count
116 if end > len(r) {
117 end = len(r)
118 }
119 return Value{Kind: KindStr, Str: string(r[start-1 : end])}
120}
121
122func fnLen(args []Value) Value {
123 if len(args) != 1 {
124 return errValue(ErrValue)
125 }
126 if args[0].Kind == KindErr {
127 return args[0]
128 }
129 return Value{Kind: KindNum, Num: float64(len([]rune(toStr(args[0]))))}
130}
131
132// fnTrim collapses runs of whitespace to a single space and strips
133// leading/trailing whitespace, via strings.Fields — slightly broader
134// than Excel's own TRIM (which targets the plain ASCII space character
135// specifically, not tabs or newlines); close enough for this task's
136// scope, and flagged here rather than left to be discovered as a silent
137// divergence.
138func fnTrim(args []Value) Value {
139 if len(args) != 1 {
140 return errValue(ErrValue)
141 }
142 if args[0].Kind == KindErr {
143 return args[0]
144 }
145 return Value{Kind: KindStr, Str: strings.Join(strings.Fields(toStr(args[0])), " ")}
146}
147
148func fnUpper(args []Value) Value {
149 if len(args) != 1 {
150 return errValue(ErrValue)
151 }
152 if args[0].Kind == KindErr {
153 return args[0]
154 }
155 return Value{Kind: KindStr, Str: strings.ToUpper(toStr(args[0]))}
156}
157
158func fnLower(args []Value) Value {
159 if len(args) != 1 {
160 return errValue(ErrValue)
161 }
162 if args[0].Kind == KindErr {
163 return args[0]
164 }
165 return Value{Kind: KindStr, Str: strings.ToLower(toStr(args[0]))}
166}
167