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 }