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 }