1package headless23import (4 "fmt"5 "strings"67 "src.rest/cat/012/internal/sheet"8)910// Target is what a reference names: a range of one sheet, or the whole11// sheet (from A1 to its last cell with contents).12type Target struct {13 Sheet *sheet.Sheet14 Range sheet.Rect15 Whole bool16}1718// Cell reports whether the target is one cell.19func (t Target) Cell() bool { return !t.Whole && t.Range.From == t.Range.To }2021// String names the target as a formula would, with its sheet.22func (t Target) String() string {23 if t.Whole {24 return sheet.QuoteSheet(t.Sheet.Name())25 }26 return sheet.Qualified(t.Sheet.Name(), t.Range)27}2829// Resolve finds what ref names in w: a cell or range (B7, A1:C9, A:A),30// on a sheet (Q3!B7, 'Q3 plan'!A1:C9) or else the sheet shown when the31// file was saved; a named range (Sales); a table by name, all of it32// with its header row, or by a structured reference as formulas read it33// (Sales[Amount]); or a sheet by name (Q3, or Q3!), meaning all of it.34// "" is the whole sheet shown.35func Resolve(w *sheet.Workbook, ref string) (Target, error) {36 ref = strings.TrimSpace(ref)37 shown := w.Sheet(w.Active())38 if ref == "" {39 return Target{Sheet: shown, Whole: true}, nil40 }41 name, rest := sheet.SplitSheet(ref)42 s := shown43 if name != "" {44 if s = w.Lookup(name); s == nil {45 return Target{}, noSheet(w, name)46 }47 if rest == "" {48 return Target{Sheet: s, Whole: true}, nil49 }50 }51 if r, ok := sheet.ParseRange(strings.ReplaceAll(rest, "$", "")); ok {52 return Target{Sheet: s, Range: r}, nil53 }54 if name == "" {55 if n, ok := w.LookupName(rest); ok {56 if n.Gone() {57 return Target{}, fmt.Errorf("the named range %s lost its cells (#REF!)", n.Name)58 }59 return Target{Sheet: n.Sheet, Range: n.Range}, nil60 }61 if s, r, ok, err := w.TableRange(rest); ok {62 return Target{Sheet: s, Range: r}, err63 }64 if s := w.Lookup(rest); s != nil {65 return Target{Sheet: s, Whole: true}, nil66 }67 }68 return Target{}, fmt.Errorf("%q isn't a cell, a range, a named range, a table or a sheet (sheets: %s)", ref, sheetList(w))69}7071// ResolveCell is Resolve for a reference that must be one cell.72func ResolveCell(w *sheet.Workbook, ref string) (*sheet.Sheet, sheet.Addr, error) {73 t, err := Resolve(w, ref)74 if err != nil {75 return nil, sheet.Addr{}, err76 }77 if !t.Cell() {78 return nil, sheet.Addr{}, fmt.Errorf("%s is %s: name one cell", ref, describe(t))79 }80 return t.Sheet, t.Range.From, nil81}8283func describe(t Target) string {84 if t.Whole {85 return "a whole sheet"86 }87 return "the range " + t.Range.String()88}8990func noSheet(w *sheet.Workbook, name string) error {91 return fmt.Errorf("no sheet named %q (sheets: %s)", name, sheetList(w))92}9394func sheetList(w *sheet.Workbook) string {95 names := make([]string, 0, w.Len())96 for _, s := range w.Sheets() {97 names = append(names, s.Name())98 }99 return strings.Join(names, ", ")100}