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 }