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(() @trusted { return val.val.array.rows; }() > 0 &&
198            () @trusted { return val.val.array.columns; }() > 0,
199            "Multi opers may not have 0 rows or columns");
200 
201     // This case has to be handled differently since we're converting to a 1D array
202     // of structs from a 2D array in Excel.
203     static if(isRegularStruct!T)
204         return fromXlOperMultiStruct!(dim, T)(val, allocator);
205     else
206         return fromXlOperMultiStandard!(dim, T)(val, allocator);
207 }
208 
209 
210 // convert a number to an array
211 private auto fromXlOperMultiNumber(Dimensions dim, T, A)(XLOPER12* val, ref A allocator)
212 {
213 
214     static if(dim == Dimensions.Two) {
215         import std.experimental.allocator: makeMultidimensionalArray;
216         auto ret = allocator.makeMultidimensionalArray!T(1, 1);
217         ret[0][0] = val.fromXlOper!T(allocator);
218         return ret;
219     } else static if(dim == Dimensions.One) {
220         import std.experimental.allocator: makeArray;
221         auto ret = allocator.makeArray!T(1);
222         ret[0] = val.fromXlOper!T(allocator);
223         return ret;
224     } else
225         static assert(0, "Unknown number of dimensions in fromXlOperMulti");
226 }
227 
228 // no-frills fromXlOperMulti
229 private auto fromXlOperMultiStandard(Dimensions dim, T, A)
230                                     (XLOPER12* val, ref A allocator)
231     in(from!"xlld.conv.misc".stripMemoryBitmask(val.xltype) ==
232        from!"xlld.sdk.xlcall".XlType.xltypeMulti)
233 {
234     import xlld.memorymanager: makeArray2D;
235     import std.experimental.allocator: makeArray;
236 
237     // @trusted because of the `in` contract
238     const rows = () @trusted { return val.val.array.rows; }();
239     const cols = () @trusted { return val.val.array.columns; }();
240 
241     static if(dim == Dimensions.Two)
242         auto ret = allocator.makeArray2D!T(*val);
243     else static if(dim == Dimensions.One)
244         auto ret = allocator.makeArray!T(rows * cols);
245     else
246         static assert(0, "Unknown number of dimensions in fromXlOperMulti");
247 
248     if(() @trusted { return ret.ptr; }() is null)
249         throw fromXlOperMultiMemoryException;
250 
251     (*val).apply!(T, (shouldConvert, row, col, cellVal) {
252 
253         auto value = shouldConvert ? cellVal.fromXlOper!T(allocator) : T.init;
254 
255         static if(dim == Dimensions.Two)
256             ret[row][col] = value;
257         else
258             ret[row * cols + col] = value;
259     });
260 
261     return ret;
262 }
263 
264 // return an array of structs
265 private auto fromXlOperMultiStruct(Dimensions dim, T, A)(XLOPER12* val, ref A allocator)
266     in(from!"xlld.conv.misc".stripMemoryBitmask(val.xltype) ==
267        from!"xlld.sdk.xlcall".XlType.xltypeMulti)
268 {
269     import xlld.sdk.xlcall: XlType;
270     import std.experimental.allocator: makeArray;
271 
272     static assert(dim == Dimensions.One, "Only 1D struct arrays are supported");
273 
274     const rows = () @trusted { return val.val.array.rows; }();
275     // The length of the struct array has -1 because the first row are names
276     auto ret = allocator.makeArray!T(rows - 1);
277     if(() @trusted { return ret.ptr; }() is null)
278         throw fromXlOperMultiMemoryException;
279 
280     foreach(r, ref elt; ret) {
281         XLOPER12 array1d;
282         array1d.xltype = XlType.xltypeMulti;
283         () @trusted {
284             array1d.val.array.rows = 1;
285             array1d.val.array.columns = T.tupleof.length;
286             array1d.val.array.lparray = val.val.array.lparray + (r + 1) * T.tupleof.length;
287         }();
288         elt = array1d.fromXlOper!T(allocator);
289     }
290 
291     return ret;
292 }
293 
294 // apply a function to an oper of type xltypeMulti
295 // the function must take a boolean value indicating if the cell value
296 // is to be converted or not, the row index, the column index,
297 // and a reference to the cell value itself
298 private void apply(T, alias F)(ref XLOPER12 oper)
299     in(from!"xlld.conv.misc".stripMemoryBitmask(oper.xltype) ==
300        from!"xlld.sdk.xlcall".XlType.xltypeMulti)
301 {
302     import xlld.sdk.xlcall: XlType;
303     import xlld.func.xl: coerce, free;
304     import xlld.any: Any;
305     import xlld.conv.misc: stripMemoryBitmask;
306     version(unittest) import xlld.test.util: gNumXlAllocated, gNumXlFree;
307 
308     // @trusted due to the contract
309     const rows = () @trusted { return oper.val.array.rows; }();
310     const cols = () @trusted { return oper.val.array.columns; }();
311     auto values = () @trusted { return oper.val.array.lparray[0 .. (rows * cols)]; }();
312 
313     foreach(const row; 0 .. rows) {
314         foreach(const col; 0 .. cols) {
315 
316             auto cellVal = coerce(&values[row * cols + col]);
317 
318             // Issue 22's unittest ends up coercing more than xlld.test.util can handle
319             // so we undo the side-effect here
320             version(unittest) --gNumXlAllocated; // ignore this for testing
321 
322             scope(exit) {
323                 free(&cellVal);
324                 // see comment above about gNumXlCoerce
325                 version(unittest) --gNumXlFree;
326             }
327 
328             const isExpectedType = cellVal.xltype.stripMemoryBitmask == dlangToXlOperType!T.Type;
329             const isConvertibleToDouble =
330                 cellVal.xltype == XlType.xltypeNum &&
331                 (dlangToXlOperType!T.Type == XlType.xltypeStr || dlangToXlOperType!T.Type == XlType.xltypeInt);
332             const isAny = is(Unqual!T == Any);
333 
334             const shouldConvert = isExpectedType || isConvertibleToDouble || isAny;
335 
336             F(shouldConvert, row, col, cellVal);
337         }
338     }
339 }
340 
341 
342 __gshared immutable fromXlOperDateTimeTypeException = new Exception("Wrong type for fromXlOper!DateTime");
343 
344 ///
345 T fromXlOper(T, A)(XLOPER12* oper, ref A allocator)
346     if(is(Unqual!T == DateTime))
347 {
348     import xlld.conv.misc: stripMemoryBitmask;
349     import xlld.sdk.framework: Excel12f;
350     import xlld.sdk.xlcall: XlType, xlretSuccess, xlfYear, xlfMonth, xlfDay, xlfHour, xlfMinute, xlfSecond;
351 
352     if(oper.xltype.stripMemoryBitmask != XlType.xltypeNum)
353         throw fromXlOperDateTimeTypeException;
354 
355     XLOPER12 ret;
356 
357     auto get(int fn) @trusted {
358         const code = Excel12f(fn, &ret, oper);
359         if(code != xlretSuccess)
360             throw new Exception("Error calling xlf datetime part function");
361 
362         // for some reason the Excel API returns doubles
363         assert(ret.xltype == XlType.xltypeNum, "xlf datetime part return not xltypeNum");
364         return cast(int)ret.val.num;
365     }
366 
367     return T(get(xlfYear), get(xlfMonth), get(xlfDay),
368              get(xlfHour), get(xlfMinute), get(xlfSecond));
369 }
370 
371 T fromXlOper(T, A)(XLOPER12* oper, ref A allocator) if(is(Unqual!T == bool)) {
372 
373     import xlld.sdk.xlcall: XlType;
374     import std.uni: toLower;
375 
376     if(oper.xltype == XlType.xltypeStr) {
377         return oper.fromXlOper!string(allocator).toLower == "true";
378     }
379 
380     return cast(T)oper.val.bool_;
381 }
382 
383 T fromXlOper(T, A)(XLOPER12* oper, ref A allocator) if(is(T == enum)) {
384     import xlld.conv.misc: stripMemoryBitmask;
385     import xlld.sdk.xlcall: XlType;
386     import std.conv: to;
387     import std.traits: fullyQualifiedName;
388 
389     static immutable typeException = new Exception("Wrong type for fromXlOper!" ~ T.stringof);
390     if(oper.xltype.stripMemoryBitmask != XlType.xltypeStr)
391         throw typeException;
392 
393     enum name = fullyQualifiedName!T;
394     auto str = oper.fromXlOper!string(allocator);
395 
396     return () @trusted {
397         assert(gToEnumMutex !is null, "gToEnumMutex is null");
398 
399         gToEnumMutex.lock_nothrow;
400         scope(exit) gToEnumMutex.unlock_nothrow;
401 
402         return name in gToEnumConversions
403                            ? cast(T) gToEnumConversions[name](str)
404                            : str.to!T;
405     }();
406 }
407 
408 
409 // convert a user-defined struct
410 T fromXlOper(T, A)(XLOPER12* oper, ref A allocator)
411     if(isRegularStruct!T)
412 {
413     import xlld.conv.misc: stripMemoryBitmask;
414     import xlld.sdk.xlcall: XlType;
415     import nogc: enforce;
416 
417     static immutable multiException = new Exception("Can only convert arrays to structs. Must be either 1xN, Nx1, 2xN or Nx2");
418     if(oper.xltype.stripMemoryBitmask != XlType.xltypeMulti)
419         throw multiException;
420 
421     // @trusted due to check above
422     const rows = () @trusted { return oper.val.array.rows; }();
423     const cols = () @trusted { return oper.val.array.columns; }();
424     const length = rows * cols;
425 
426     if(rows == 1 || cols == 1)
427         enforce(length == T.tupleof.length,
428                "1D array length must match number of members in ", T.stringof,
429                 ". Expected ", T.tupleof.length, ", got ", length);
430     else
431         enforce((rows == 2 && cols == T.tupleof.length) ||
432                (rows == T.tupleof.length && cols == 2),
433                "2D array must be 2x", T.tupleof.length, " or ", T.tupleof.length, "x2 for ", T.stringof);
434     T ret;
435 
436     size_t ptrIndex(size_t i) {
437 
438         if(rows == 1 || cols == 1)
439             return i;
440 
441         // ignore column headers
442         if(rows == 2)
443             return i + cols;
444 
445         // ignore row headers (+ 1)
446         if(cols == 2)
447             return i * 2 + 1;
448 
449         assert(0);
450     }
451 
452     static immutable wrongTypeException = new Exception("Wrong type converting oper to " ~ T.stringof);
453 
454     foreach(i, ref member; ret.tupleof) {
455         try {
456             auto elt = () @trusted { return oper.val.array.lparray[ptrIndex(i)]; }();
457             member = elt.fromXlOper!(typeof(member))(allocator);
458         } catch(Exception _)
459             throw wrongTypeException;
460     }
461 
462     return ret;
463 }
464 
465 
466 ///
467 auto fromXlOperCoerce(T)(XLOPER12* val) {
468     return fromXlOperCoerce(*val);
469 }
470 
471 ///
472 auto fromXlOperCoerce(T, A)(XLOPER12* val, auto ref A allocator) {
473     return fromXlOperCoerce!T(*val, allocator);
474 }
475 
476 
477 ///
478 auto fromXlOperCoerce(T)(ref XLOPER12 val) {
479     import xlld.memorymanager: allocator;
480     return fromXlOperCoerce!T(val, allocator);
481 }
482 
483 
484 ///
485 auto fromXlOperCoerce(T, A)(ref XLOPER12 val, auto ref A allocator) {
486     import xlld.func.xl: coerce, free;
487 
488     auto coerced = coerce(&val);
489     scope(exit) free(&coerced);
490 
491     return coerced.fromXlOper!T(allocator);
492 }
493 
494 
495 private enum invalidXlOperType = 0xdeadbeef;
496 
497 /**
498  Maps a D type to two integer xltypes from XLOPER12.
499  InputType is the type actually passed in by the spreadsheet,
500  whilst Type is the Type that it gets coerced to.
501  */
502 template dlangToXlOperType(T) {
503     import xlld.sdk.xlcall: XlType;
504     static if(is(Unqual!T == string[])   || is(Unqual!T == string[][]) ||
505               is(Unqual!T == double[])   || is(Unqual!T == double[][]) ||
506               is(Unqual!T == int[])      || is(Unqual!T == int[][]) ||
507               is(Unqual!T == DateTime[]) || is(Unqual!T == DateTime[][]))
508     {
509         enum InputType = XlType.xltypeSRef;
510         enum      Type = XlType.xltypeMulti;
511     } else static if(is(Unqual!T == double)) {
512         enum InputType = XlType.xltypeNum;
513         enum      Type = XlType.xltypeNum;
514     } else static if(is(Unqual!T == int)) {
515         enum InputType = XlType.xltypeInt;
516         enum      Type = XlType.xltypeInt;
517     } else static if(is(Unqual!T == string)) {
518         enum InputType = XlType.xltypeStr;
519         enum      Type = XlType.xltypeStr;
520     } else static if(is(Unqual!T == DateTime)) {
521         enum InputType = XlType.xltypeNum;
522         enum      Type = XlType.xltypeNum;
523     } else {
524         enum InputType = invalidXlOperType;
525         enum      Type = invalidXlOperType;
526     }
527 }
528 
529 /**
530    If an oper is of multi type
531  */
532 bool isMulti(ref const(XLOPER12) oper) @safe @nogc pure nothrow {
533     import xlld.conv.misc: stripMemoryBitmask;
534     import xlld.sdk.xlcall: XlType;
535 
536     return stripMemoryBitmask(oper.xltype) == XlType.xltypeMulti;
537 }
538 
539 /**
540    Register a custom conversion from string to an enum type. This function will
541    be called before converting any enum arguments to be passed to a wrapped
542    D function.
543  */
544 void registerConversionTo(T)(ToEnumConversionFunction func) @trusted {
545     import std.traits: fullyQualifiedName;
546 
547     assert(gToEnumMutex !is null, "gToEnumMutex is null");
548 
549     gToEnumMutex.lock_nothrow;
550     scope(exit)gToEnumMutex.unlock_nothrow;
551 
552     gToEnumConversions[fullyQualifiedName!T] = func;
553 }
554 
555 void unregisterConversionTo(T)() @trusted {
556     import std.traits: fullyQualifiedName;
557 
558     assert(gToEnumMutex !is null, "gToEnumMutex is null");
559 
560     gToEnumMutex.lock_nothrow;
561     scope(exit)gToEnumMutex.unlock_nothrow;
562 
563     gToEnumConversions.remove(fullyQualifiedName!T);
564 }