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