1package body Spreadsheet_references is
2
3  ---------------
4  -- Reference --
5  ---------------
6
7  function Reference(
8    row, column: Positive;
9    style      : Reference_style:= A1
10  )
11  return String
12  is
13    rs: constant String:= Positive'Image(row);
14    cs: constant String:= Positive'Image(column);
15    -- Skip the @#*$! leading space...
16    r: constant String:= rs(rs'First+1..rs'Last);
17    c: constant String:= cs(cs'First+1..cs'Last);
18    -- A bit tricky: 'A'..'Z' are not like digits,
19    -- or rather like digits without a zero (1..9)!
20    --
21    -- You have exactly (26**n - 1) * 26 / 25 combinations
22    -- for a code with 1 to n letters!
23
24    function Base_26(n: Natural) return String is
25    begin
26      if n <= 25 then
27        return (1 => Character'Val(Character'Pos('A') + n));
28      else
29        return Base_26(n / 26 - 1) & Base_26(n mod 26);
30      end if;
31    end Base_26;
32  begin
33    case style is
34      when A1 =>
35        return Base_26(column - 1) & r;
36      when R1C1 =>
37        return 'R' & r & 'C' & c;
38    end case;
39  end Reference;
40
41  ---------
42  -- Row --
43  ---------
44
45  function Row (reference: String) return Positive is
46    r, c: Positive;
47  begin
48    Split(reference, r, c);
49    return r;
50  end Row;
51
52  ------------
53  -- Column --
54  ------------
55
56  function Column (reference: String) return Positive is
57    r, c: Positive;
58  begin
59    Split(reference, r, c);
60    return c;
61  end Column;
62
63  -----------
64  -- Split --
65  -----------
66
67  procedure Split (reference: String; row, column: out Positive) is
68    phase: Positive range 1..4:= 1;
69    r, c, d, cc: Natural:= 0;
70    s: Character;
71  begin
72    if reference = "" then
73      raise Invalid_spreadsheet_reference;
74    end if;
75    for i in reference'Range loop
76      s:= reference(i);
77      if s in 'a'..'z' then
78        s:= Character'Val(Character'Pos(s) - Character'Pos('a') + Character'Pos('A'));
79      end if;
80      case s is
81        when '0'..'9' =>
82          case phase is
83            when 1 =>
84              if i = reference'First then
85                raise Invalid_spreadsheet_reference; -- cannot start with a digit
86              end if;
87              phase:= 2;
88            when 2 | 4 =>
89              null; -- already in a digit
90            when 3 => -- We begin the column in R1C1 style
91              if c /= 18 or cc /= 3 then
92                -- 1st letter code must be exactly "R" and 2nd must be "C"
93                raise Invalid_spreadsheet_reference;
94              end if;
95              c:= 0;
96              phase:= 4;
97          end case;
98          d:= Character'Pos(s) - Character'Pos('0');
99          case phase is
100            when 1 | 3 =>
101              null; -- we never get here.
102            when 2 =>
103              r:= r * 10 + d;
104            when 4 =>
105              c:= c * 10 + d;
106          end case;
107        when 'A'..'Z' =>
108          case phase is
109            when 1 | 3 =>
110              null; -- already in a letter code
111            when 2 =>
112              phase:= 3;
113            when 4 => -- not a 3rd letter code!
114              raise Invalid_spreadsheet_reference;
115          end case;
116          d:= Character'Pos(s) - Character'Pos('A') + 1;
117          case phase is
118            when 2 | 4 =>
119              null; -- we never get here.
120            when 1 =>
121              c:= c * 26 + d;
122            when 3 =>
123              cc:= cc * 26 + d;
124          end case;
125        when others =>
126          raise Invalid_spreadsheet_reference;
127      end case;
128    end loop;
129    row:= r;
130    column:= c;
131  end Split;
132
133end Spreadsheet_references;
134