1 /**
2    Conversions from XLOPER12 to D types
3  */
4 module xlld.conv.from;
5 
6 import xlld.from;
7 import xlld.sdk.xlcall: XLOPER12;
8 import xlld.any: Any;
9 import xlld.wrap.wrap: isWantedType;
10 import std.traits: Unqual;
11 import std.datetime: DateTime;
12 
13 
14 alias ToEnumConversionFunction = int delegate(string);
15 package __gshared ToEnumConversionFunction[string] gToEnumConversions;
16 shared from!"core.sync.mutex".Mutex gToEnumMutex;
17 
18 
19 ///
20 auto fromXlOper(T, A)(ref XLOPER12 val, ref A allocator) {
21     return (&val).fromXlOper!T(allocator);
22 }
23 
24 /// RValue overload
25 auto fromXlOper(T, A)(XLOPER12 val, ref A allocator) {
26     return fromXlOper!T(val, allocator);
27 }
28 
29 __gshared immutable fromXlOperDoubleWrongTypeException = new Exception("Wrong type for fromXlOper!double");
30 ///
31 auto fromXlOper(T, A)(XLOPER12* val, ref A allocator) if(is(Unqual!T == double)) {
32     import xlld.sdk.xlcall: XlType;
33     import xlld.conv.misc: stripMemoryBitmask;
34 
35     if(val.xltype.stripMemoryBitmask == XlType.xltypeMissing)
36         return double.init;
37 
38     if(val.xltype.stripMemoryBitmask == XlType.xltypeInt)
39         return cast(T)val.val.w;
40 
41     if(val.xltype.stripMemoryBitmask != XlType.xltypeNum)
42         throw fromXlOperDoubleWrongTypeException;
43 
44     return val.val.num;
45 }
46 
47 __gshared immutable fromXlOperIntWrongTypeException = new Exception("Wrong type for fromXlOper!int");
48 
49 ///
50 auto fromXlOper(T, A)(XLOPER12* val, ref A allocator) if(is(Unqual!T == int)) {
51     import xlld.conv.misc: stripMemoryBitmask;
52     import xlld.sdk.xlcall: XlType;
53 
54     if(val.xltype.stripMemoryBitmask == XlType.xltypeMissing)
55         return int.init;
56 
57     if(val.xltype.stripMemoryBitmask == XlType.xltypeNum)
58         return cast(typeof(return))val.val.num;
59 
60     if(val.xltype.stripMemoryBitmask != XlType.xltypeInt)
61         throw fromXlOperIntWrongTypeException;
62 
63     return val.val.w;
64 }
65 
66 
67 ///
68 __gshared immutable fromXlOperMemoryException = new Exception("Could not allocate memory for array of char");
69 ///
70 __gshared immutable fromXlOperConvException = new Exception("Could not convert double to string");
71 
72 __gshared immutable fromXlOperStringTypeException = new Exception("Wrong type for fromXlOper!string");
73 
74 ///
75 auto fromXlOper(T, A)(XLOPER12* val, ref A allocator) if(is(Unqual!T == string)) {
76 
77     import xlld.conv.misc: stripMemoryBitmask;
78     import xlld.sdk.xlcall: XlType;
79     import std.experimental.allocator: makeArray;
80     import std.utf: byChar;
81     import std.range: walkLength;
82 
83     const stripType = stripMemoryBitmask(val.xltype);
84 
85     if(stripType == XlType.xltypeMissing)
86         return null;
87 
88     if(stripType != XlType.xltypeStr && stripType != XlType.xltypeNum)
89         throw fromXlOperStringTypeException;
90 
91 
92     if(stripType == XlType.xltypeStr) {
93 
94         auto chars = () @trusted { return val.val.str[1 .. val.val.str[0] + 1].byChar; }();
95         const length = chars.save.walkLength;
96         auto ret = () @trusted { return allocator.makeArray!char(length); }();
97 
98         if(ret is null && length > 0)
99             throw fromXlOperMemoryException;
100 
101         int i;
102         foreach(ch; () @trusted { return val.val.str[1 .. val.val.str[0] + 1].byChar; }())
103             ret[i++] = ch;
104 
105         return () @trusted {  return cast(string)ret; }();
106     } else {
107 
108         // if a double, try to convert it to a string
109         import std.math: isNaN;
110         import core.stdc.stdio: snprintf;
111 
112         char[1024] buffer;
113         const numChars = () @trusted {
114             if(val.val.num.isNaN)
115                 return snprintf(&buffer[0], buffer.length, "#NaN");
116             else
117                 return snprintf(&buffer[0], buffer.length, "%lf", val.val.num);
118         }();
119         if(numChars > buffer.length - 1)
120             throw fromXlOperConvException;
121         auto ret = () @trusted { return allocator.makeArray!char(numChars); }();
122 
123         if(ret is null && numChars > 0)
124             throw fromXlOperMemoryException;
125 
126         ret[] = buffer[0 .. numChars];
127         return () @trusted { return cast(string)ret; }();
128     }
129 }
130 
131 
132 ///
133 T fromXlOper(T, A)(XLOPER12* oper, ref A allocator) if(is(Unqual!T == Any)) {
134     import xlld.conv.misc: dup;
135     return Any((*oper).dup(allocator));
136 }
137 
138 private enum Dimensions {
139     One,
140     Two,
141 }
142 
143 ///
144 auto fromXlOper(T, A)(XLOPER12* val, ref A allocator)
145     if(is(T: E[][], E) &&
146        !(is(Unqual!T == string[])) &&
147        (!is(T: EE[][][], EE) || is(Unqual!(typeof(T.init[0][0])) == string)))
148 {
149     return val.fromXlOperMulti!(Dimensions.Two, typeof(T.init[0][0]))(allocator);
150 }
151 
152 
153 /// 1D slices
154 auto fromXlOper(T, A)(XLOPER12* val, ref A allocator)
155     if(is(T: E[], E) &&
156        !(is(Unqual!T == string)) &&
157        (!is(T: EE[][], EE) || is(Unqual!(typeof(T.init[0])) == string)))
158 {
159     return val.fromXlOperMulti!(Dimensions.One, typeof(T.init[0]))(allocator);
160 }
161 
162 
163 ///
164 __gshared immutable fromXlOperMultiOperException = new Exception("fromXlOper: oper not of multi type");
165 ///
166 __gshared immutable fromXlOperMultiMemoryException = new Exception("fromXlOper: Could not allocate memory in fromXlOperMulti");
167 
168 private auto fromXlOperMulti(Dimensions dim, T, A)(XLOPER12* val, ref A allocator) {
169     import xlld.conv.misc: stripMemoryBitmask;
170     import xlld.func.xl: coerce, free;
171     import xlld.memorymanager: makeArray2D;
172     import xlld.sdk.xlcall: XlType;
173     import std.experimental.allocator: makeArray;
174 
175     if(stripMemoryBitmask(val.xltype) == XlType.xltypeNil) {
176         static if(dim == Dimensions.Two)
177             return T[][].init;
178         else static if(dim == Dimensions.One)
179             return T[].init;
180         else
181             static assert(0, "Unknown number of dimensions in fromXlOperMulti");
182     }
183 
184     // See ut.wrap.wrap.xltypeNum can convert to array
185     if(stripMemoryBitmask(val.xltype) == XlType.xltypeNum) {
186         static if(dim == Dimensions.Two) {
187             import std.experimental.allocator: makeMultidimensionalArray;
188             auto ret = allocator.makeMultidimensionalArray!T(1, 1);
189             ret[0][0] = val.fromXlOper!T(allocator);
190             return ret;
191         } else static if(dim == Dimensions.One) {
192             auto ret = allocator.makeArray!T(1);
193             ret[0] = val.fromXlOper!T(allocator);
194             return ret;
195         } else
196             static assert(0, "Unknown number of dimensions in fromXlOperMulti");
197     }
198 
199     if(!isMulti(*val)) {
200         throw fromXlOperMultiOperException;
201     }
202 
203     const rows = val.val.array.rows;
204     const cols = val.val.array.columns;
205 
206     assert(rows > 0 && cols > 0, "Multi opers may not have 0 rows or columns");
207 
208     static if(dim == Dimensions.Two) {
209         auto ret = allocator.makeArray2D!T(*val);
210     } else static if(dim == Dimensions.One) {
211         auto ret = allocator.makeArray!T(rows * cols);
212     } else
213         static assert(0, "Unknown number of dimensions in fromXlOperMulti");
214 
215     if(&ret[0] is null)
216         throw fromXlOperMultiMemoryException;
217 
218     (*val).apply!(T, (shouldConvert, row, col, cellVal) {
219 
220         auto value = shouldConvert ? cellVal.fromXlOper!T(allocator) : T.init;
221 
222         static if(dim == Dimensions.Two)
223             ret[row][col] = value;
224         else
225             ret[row * cols + col] = value;
226     });
227 
228     return ret;
229 }
230 
231 
232 // apply a function to an oper of type xltypeMulti
233 // the function must take a boolean value indicating if the cell value
234 // is to be converted or not, the row index, the column index,
235 // and a reference to the cell value itself
236 private void apply(T, alias F)(ref XLOPER12 oper) {
237     import xlld.sdk.xlcall: XlType;
238     import xlld.func.xl: coerce, free;
239     import xlld.any: Any;
240     version(unittest) import xlld.test.util: gNumXlAllocated, gNumXlFree;
241 
242     const rows = oper.val.array.rows;
243     const cols = oper.val.array.columns;
244     auto values = oper.val.array.lparray[0 .. (rows * cols)];
245 
246     foreach(const row; 0 .. rows) {
247         foreach(const col; 0 .. cols) {
248 
249             auto cellVal = coerce(&values[row * cols + col]);
250 
251             // Issue 22's unittest ends up coercing more than xlld.test.util can handle
252             // so we undo the side-effect here
253             version(unittest) --gNumXlAllocated; // ignore this for testing
254 
255             scope(exit) {
256                 free(&cellVal);
257                 // see comment above about gNumXlCoerce
258                 version(unittest) --gNumXlFree;
259             }
260 
261             // try to convert doubles to string if trying to convert everything to an
262             // array of strings
263             const shouldConvert =
264                 (cellVal.xltype == dlangToXlOperType!T.Type) ||
265                 (cellVal.xltype == XlType.xltypeNum && dlangToXlOperType!T.Type == XlType.xltypeStr) ||
266                 is(Unqual!T == Any);
267 
268             F(shouldConvert, row, col, cellVal);
269         }
270     }
271 }
272 
273 
274 __gshared immutable fromXlOperDateTimeTypeException = new Exception("Wrong type for fromXlOper!DateTime");
275 
276 ///
277 T fromXlOper(T, A)(XLOPER12* oper, ref A allocator)
278     if(is(Unqual!T == DateTime))
279 {
280     import xlld.conv.misc: stripMemoryBitmask;
281     import xlld.sdk.framework: Excel12f;
282     import xlld.sdk.xlcall: XlType, xlretSuccess, xlfYear, xlfMonth, xlfDay, xlfHour, xlfMinute, xlfSecond;
283 
284     if(oper.xltype.stripMemoryBitmask != XlType.xltypeNum)
285         throw fromXlOperDateTimeTypeException;
286 
287     XLOPER12 ret;
288 
289     auto get(int fn) @trusted {
290         const code = Excel12f(fn, &ret, oper);
291         if(code != xlretSuccess)
292             throw new Exception("Error calling xlf datetime part function");
293 
294         // for some reason the Excel API returns doubles
295         assert(ret.xltype == XlType.xltypeNum, "xlf datetime part return not xltypeNum");
296         return cast(int)ret.val.num;
297     }
298 
299     return T(get(xlfYear), get(xlfMonth), get(xlfDay),
300              get(xlfHour), get(xlfMinute), get(xlfSecond));
301 }
302 
303 T fromXlOper(T, A)(XLOPER12* oper, ref A allocator) if(is(Unqual!T == bool)) {
304 
305     import xlld.sdk.xlcall: XlType;
306     import std.uni: toLower;
307 
308     if(oper.xltype == XlType.xltypeStr) {
309         return oper.fromXlOper!string(allocator).toLower == "true";
310     }
311 
312     return cast(T)oper.val.bool_;
313 }
314 
315 T fromXlOper(T, A)(XLOPER12* oper, ref A allocator) if(is(T == enum)) {
316     import xlld.conv.misc: stripMemoryBitmask;
317     import xlld.sdk.xlcall: XlType;
318     import std.conv: to;
319     import std.traits: fullyQualifiedName;
320 
321     static immutable typeException = new Exception("Wrong type for fromXlOper!" ~ T.stringof);
322     if(oper.xltype.stripMemoryBitmask != XlType.xltypeStr)
323         throw typeException;
324 
325     enum name = fullyQualifiedName!T;
326     auto str = oper.fromXlOper!string(allocator);
327 
328     return () @trusted {
329         assert(gToEnumMutex !is null, "gToEnumMutex is null");
330 
331         gToEnumMutex.lock_nothrow;
332         scope(exit) gToEnumMutex.unlock_nothrow;
333 
334         return name in gToEnumConversions
335                            ? cast(T) gToEnumConversions[name](str)
336                            : str.to!T;
337     }();
338 }
339 
340 
341 T fromXlOper(T, A)(XLOPER12* oper, ref A allocator)
342     if(is(T == struct) && !is(Unqual!T == Any) && !is(Unqual!T == DateTime))
343 {
344     import xlld.conv.misc: stripMemoryBitmask;
345     import xlld.sdk.xlcall: XlType;
346     import std.conv: text;
347     import std.exception: enforce;
348 
349     static immutable multiException = new Exception("Can only convert arrays to structs. Must be either 1xN, Nx1, 2xN or Nx2");
350     if(oper.xltype.stripMemoryBitmask != XlType.xltypeMulti)
351         throw multiException;
352 
353     const length =  oper.val.array.rows * oper.val.array.columns;
354 
355     if(oper.val.array.rows == 1 || oper.val.array.columns == 1)
356         enforce(length == T.tupleof.length,
357                text("1D array length must match number of members in ", T.stringof,
358                     ". Expected ", T.tupleof.length, ", got ", length));
359     else
360         enforce((oper.val.array.rows == 2 && oper.val.array.columns == T.tupleof.length) ||
361                (oper.val.array.rows == T.tupleof.length && oper.val.array.columns == 2),
362                text("2D array must be 2x", T.tupleof.length, " or ", T.tupleof.length, "x2 for ", T.stringof));
363 
364     T ret;
365 
366     size_t ptrIndex(size_t i) {
367 
368         if(oper.val.array.rows == 1 || oper.val.array.columns == 1)
369             return i;
370 
371         if(oper.val.array.rows == 2)
372             return i + oper.val.array.columns;
373 
374         if(oper.val.array.columns == 2)
375             return i * 2 + 1;
376 
377         assert(0);
378     }
379 
380     static immutable wrongTypeException = new Exception("Wrong type converting oper to " ~ T.stringof);
381 
382     foreach(i, ref member; ret.tupleof) {
383         try
384             member = oper.val.array.lparray[ptrIndex(i)].fromXlOper!(typeof(member))(allocator);
385         catch(Exception _)
386             throw wrongTypeException;
387     }
388 
389     return ret;
390 }
391 
392 
393 ///
394 auto fromXlOperCoerce(T)(XLOPER12* val) {
395     return fromXlOperCoerce(*val);
396 }
397 
398 ///
399 auto fromXlOperCoerce(T, A)(XLOPER12* val, auto ref A allocator) {
400     return fromXlOperCoerce!T(*val, allocator);
401 }
402 
403 
404 ///
405 auto fromXlOperCoerce(T)(ref XLOPER12 val) {
406     import xlld.memorymanager: allocator;
407     return fromXlOperCoerce!T(val, allocator);
408 }
409 
410 
411 ///
412 auto fromXlOperCoerce(T, A)(ref XLOPER12 val, auto ref A allocator) {
413     import xlld.func.xl: coerce, free;
414 
415     auto coerced = coerce(&val);
416     scope(exit) free(&coerced);
417 
418     return coerced.fromXlOper!T(allocator);
419 }
420 
421 
422 private enum invalidXlOperType = 0xdeadbeef;
423 
424 /**
425  Maps a D type to two integer xltypes from XLOPER12.
426  InputType is the type actually passed in by the spreadsheet,
427  whilst Type is the Type that it gets coerced to.
428  */
429 template dlangToXlOperType(T) {
430     import xlld.sdk.xlcall: XlType;
431     static if(is(Unqual!T == string[])   || is(Unqual!T == string[][]) ||
432               is(Unqual!T == double[])   || is(Unqual!T == double[][]) ||
433               is(Unqual!T == int[])      || is(Unqual!T == int[][]) ||
434               is(Unqual!T == DateTime[]) || is(Unqual!T == DateTime[][]))
435     {
436         enum InputType = XlType.xltypeSRef;
437         enum      Type = XlType.xltypeMulti;
438     } else static if(is(Unqual!T == double)) {
439         enum InputType = XlType.xltypeNum;
440         enum      Type = XlType.xltypeNum;
441     } else static if(is(Unqual!T == int)) {
442         enum InputType = XlType.xltypeInt;
443         enum      Type = XlType.xltypeInt;
444     } else static if(is(Unqual!T == string)) {
445         enum InputType = XlType.xltypeStr;
446         enum      Type = XlType.xltypeStr;
447     } else static if(is(Unqual!T == DateTime)) {
448         enum InputType = XlType.xltypeNum;
449         enum      Type = XlType.xltypeNum;
450     } else {
451         enum InputType = invalidXlOperType;
452         enum      Type = invalidXlOperType;
453     }
454 }
455 
456 /**
457    If an oper is of multi type
458  */
459 bool isMulti(ref const(XLOPER12) oper) @safe @nogc pure nothrow {
460     import xlld.conv.misc: stripMemoryBitmask;
461     import xlld.sdk.xlcall: XlType;
462 
463     return stripMemoryBitmask(oper.xltype) == XlType.xltypeMulti;
464 }
465 
466 /**
467    Register a custom conversion from string to an enum type. This function will
468    be called before converting any enum arguments to be passed to a wrapped
469    D function.
470  */
471 void registerConversionTo(T)(ToEnumConversionFunction func) @trusted {
472     import std.traits: fullyQualifiedName;
473 
474     assert(gToEnumMutex !is null, "gToEnumMutex is null");
475 
476     gToEnumMutex.lock_nothrow;
477     scope(exit)gToEnumMutex.unlock_nothrow;
478 
479     gToEnumConversions[fullyQualifiedName!T] = func;
480 }
481 
482 void unregisterConversionTo(T)() @trusted {
483     import std.traits: fullyQualifiedName;
484 
485     assert(gToEnumMutex !is null, "gToEnumMutex is null");
486 
487     gToEnumMutex.lock_nothrow;
488     scope(exit)gToEnumMutex.unlock_nothrow;
489 
490     gToEnumConversions.remove(fullyQualifiedName!T);
491 }