- commit
- 2e9e581bcf9fc1cfa1befc7c70b16401b714cf45
- parent
- bb5eec9a32f7437967a32e1e548f7d67041495b9
- author
- Dami <dami.etoile@gmail.com>
| M | internal/ | 149 | +++++++++++++++++------------- |
@@ -20,74 +20,97 @@ var xlfn = map[string]bool{
2020 func toExcelFormula(input string) (string, bool) {2121 src := strings.TrimPrefix(input, "=")2222 var b strings.Builder23- for i := 0; i < len(src); i++ {24- c := src[i]25- switch {26- case c == '\'':27- // A quoted sheet name, 'Q3 plan'!A1, is the same in Excel.28- j := quoteEnd(src, i)29- b.WriteString(src[i:j])30- i = j - 131- case c == '"':32- j := i + 133- for j < len(src) {34- if src[j] == '"' {35- if j+1 < len(src) && src[j+1] == '"' {36- j += 237- continue38- }39- break40- }23+ for i := 0; i < len(src); {24+ next, ok := excelToken(&b, src, i)25+ if !ok {26+ return "", false27+ }28+ i = next29+ }30+ return b.String(), true31+}32+33+// excelToken writes the token starting at src[i] in Excel's syntax and34+// returns the index just past it, or false when Excel has no equivalent.35+func excelToken(b *strings.Builder, src string, i int) (int, bool) {36+ c := src[i]37+ switch {38+ case c == '\'':39+ // A quoted sheet name, 'Q3 plan'!A1, is the same in Excel.40+ j := quoteEnd(src, i)41+ b.WriteString(src[i:j])42+ return j, true43+ case c == '"':44+ j := stringEnd(src, i)45+ if j < 0 {46+ return 0, false47+ }48+ b.WriteString(src[i:j])49+ return j, true50+ case c == '#':51+ if p := prefixFold(src[i:], "#REF!"); p > 0 {52+ b.WriteString("#REF!")53+ return i + p, true54+ }55+ return 0, false // #AND#, #OR#, #NOT#56+ case c == '.' && i+1 < len(src) && src[i+1] == '.':57+ b.WriteByte(':') // 1-2-3's A1..B358+ return i + 2, true59+ case c == '@' || isIdentByte(c) && !isDigitByte(c) && c != '.':60+ return excelName(b, src, i)61+ }62+ b.WriteByte(c)63+ return i + 1, true64+}65+66+// stringEnd returns the index just past the string literal starting at67+// s[i], where a quote inside is written twice, or -1 if it isn't closed.68+func stringEnd(s string, i int) int {69+ for j := i + 1; j < len(s); j++ {70+ if s[j] == '"' {71+ if j+1 < len(s) && s[j+1] == '"' {4172 j++42- }43- if j >= len(src) {44- return "", false45- }46- b.WriteString(src[i : j+1])47- i = j48- case c == '#':49- if strings.HasPrefix(strings.ToUpper(src[i:]), "#REF!") {50- b.WriteString("#REF!")51- i += len("#REF!") - 15273 continue5374 }54- return "", false // #AND#, #OR#, #NOT#55- case c == '.' && i+1 < len(src) && src[i+1] == '.':56- b.WriteByte(':')57- i++58- case c == '@' || isIdentByte(c) && !isDigitByte(c) && c != '.':59- j := i60- if c == '@' {61- j++62- }63- start := j64- for j < len(src) && isIdentByte(src[j]) && !strings.HasPrefix(src[j:], "..") {65- j++66- }67- name := src[start:j]68- k := j69- for k < len(src) && src[k] == ' ' {70- k++71- }72- if k < len(src) && src[k] == '(' || c == '@' {73- name = strings.ToUpper(name)74- if strings.HasPrefix(name, "JEV.") {75- return "", false76- }77- if xlfn[name] {78- name = "_xlfn." + name79- }80- if c == '@' && (k >= len(src) || src[k] != '(') {81- name += "()" // @PI82- }83- }84- b.WriteString(name)85- i = j - 186- default:87- b.WriteByte(c)75+ return j + 18876 }8977 }90- return b.String(), true78+ return -179+}80+81+// excelName writes the name starting at src[i]: a function name is82+// upper-cased and given Excel's _xlfn. prefix where it needs one, and83+// 1-2-3's @PI becomes PI(); references and named ranges stay as they are.84+func excelName(b *strings.Builder, src string, i int) (int, bool) {85+ at := src[i] == '@'86+ j := i87+ if at {88+ j++89+ }90+ start := j91+ for j < len(src) && isIdentByte(src[j]) && !strings.HasPrefix(src[j:], "..") {92+ j++93+ }94+ name := src[start:j]95+ k := j96+ for k < len(src) && src[k] == ' ' {97+ k++98+ }99+ call := k < len(src) && src[k] == '('100+ if call || at {101+ name = strings.ToUpper(name)102+ if strings.HasPrefix(name, "JEV.") {103+ return 0, false104+ }105+ if xlfn[name] {106+ name = "_xlfn." + name107+ }108+ if at && !call {109+ name += "()" // @PI110+ }111+ }112+ b.WriteString(name)113+ return j, true91114 }92115 93116 // fromExcelFormula translates an Excel formula (without its =) to a 012