173 lines
5.2 KiB
C++
173 lines
5.2 KiB
C++
#include <mbgl/style/conversion_impl.hpp>
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
namespace mbgl {
|
|
namespace style {
|
|
namespace conversion {
|
|
|
|
// A wrapper class for `id`, so as not to confuse ARC.
|
|
class Holder {
|
|
public:
|
|
Holder(const id v) : value(v) {}
|
|
const id value;
|
|
};
|
|
|
|
template <>
|
|
class ConversionTraits<Holder> {
|
|
public:
|
|
static bool isUndefined(const Holder& holder) {
|
|
const id value = holder.value;
|
|
return !value || value == [NSNull null];
|
|
}
|
|
|
|
static bool isArray(const Holder& holder) {
|
|
const id value = holder.value;
|
|
return [value isKindOfClass:[NSArray class]];
|
|
}
|
|
|
|
static bool isObject(const Holder& holder) {
|
|
const id value = holder.value;
|
|
return [value isKindOfClass:[NSDictionary class]];
|
|
}
|
|
|
|
static std::size_t arrayLength(const Holder& holder) {
|
|
const id value = holder.value;
|
|
NSCAssert([value isKindOfClass:[NSArray class]], @"Value must be an NSArray for getLength().");
|
|
NSArray* array = value;
|
|
auto length = [array count];
|
|
NSCAssert(length <= std::numeric_limits<size_t>::max(), @"Array length out of bounds.");
|
|
return length;
|
|
}
|
|
|
|
static Holder arrayMember(const Holder& holder, std::size_t i) {
|
|
const id value = holder.value;
|
|
NSCAssert([value isKindOfClass:[NSArray class]], @"Value must be an NSArray for get(int).");
|
|
NSCAssert(i < NSUIntegerMax, @"Index must be less than NSUIntegerMax");
|
|
return {[value objectAtIndex:i]};
|
|
}
|
|
|
|
static std::optional<Holder> objectMember(const Holder& holder, const char* key) {
|
|
const id value = holder.value;
|
|
NSCAssert([value isKindOfClass:[NSDictionary class]],
|
|
@"Value must be an NSDictionary for get(string).");
|
|
NSObject* member = [value objectForKey:@(key)];
|
|
if (member && member != [NSNull null]) {
|
|
return {member};
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// Compiler is wrong about `Fn` parameter missing a nullability specifier.
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wnullability-completeness"
|
|
template <class Fn>
|
|
static std::optional<Error> eachMember(const Holder& holder, Fn&& visit) {
|
|
#pragma clang diagnostic pop
|
|
[holder.value
|
|
enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL* _Nonnull stop) {
|
|
auto result = visit(std::string(static_cast<const char*>([key UTF8String])), obj);
|
|
if (result) {
|
|
*stop = YES;
|
|
}
|
|
}];
|
|
return {};
|
|
}
|
|
|
|
static std::optional<bool> toBool(const Holder& holder) {
|
|
const id value = holder.value;
|
|
if (_isBool(value)) {
|
|
return ((NSNumber*)value).boolValue;
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
static std::optional<float> toNumber(const Holder& holder) {
|
|
const id value = holder.value;
|
|
if (_isNumber(value)) {
|
|
return ((NSNumber*)value).floatValue;
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
static std::optional<double> toDouble(const Holder& holder) {
|
|
const id value = holder.value;
|
|
if (_isNumber(value)) {
|
|
return ((NSNumber*)value).doubleValue;
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
static std::optional<std::string> toString(const Holder& holder) {
|
|
const id value = holder.value;
|
|
if (_isString(value)) {
|
|
return std::string(static_cast<const char*>([value UTF8String]));
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
static std::optional<mbgl::Value> toValue(const Holder& holder) {
|
|
const id value = holder.value;
|
|
if (isUndefined(value)) {
|
|
return {};
|
|
} else if (_isBool(value)) {
|
|
return {*toBool(holder)};
|
|
} else if (_isString(value)) {
|
|
return {*toString(holder)};
|
|
} else if (_isNumber(value)) {
|
|
return {*toDouble(holder)};
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
static std::optional<GeoJSON> toGeoJSON(const Holder& holder, Error& error) {
|
|
id object = holder.value;
|
|
if ([object isKindOfClass:[NSDictionary class]]) {
|
|
NSError* serializationError;
|
|
NSData* data = [NSJSONSerialization dataWithJSONObject:object
|
|
options:0
|
|
error:&serializationError];
|
|
if (serializationError) {
|
|
error = {std::string(serializationError.localizedDescription.UTF8String)};
|
|
return {};
|
|
}
|
|
NSString* string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
return mapbox::geojson::parse(string.UTF8String);
|
|
} else {
|
|
error = {std::string(
|
|
[NSString stringWithFormat:@"%@ is not a GeoJSON object.", object].UTF8String)};
|
|
return {};
|
|
}
|
|
}
|
|
|
|
private:
|
|
static bool _isBool(const id value) {
|
|
if (![value isKindOfClass:[NSNumber class]]) return false;
|
|
// char: 32-bit boolean
|
|
// BOOL: 64-bit boolean
|
|
NSNumber* number = value;
|
|
return ((strcmp([number objCType], @encode(char)) == 0) ||
|
|
(strcmp([number objCType], @encode(BOOL)) == 0));
|
|
}
|
|
|
|
static bool _isNumber(const id value) {
|
|
return [value isKindOfClass:[NSNumber class]] && !_isBool(value);
|
|
}
|
|
|
|
static bool _isString(const id value) { return [value isKindOfClass:[NSString class]]; }
|
|
};
|
|
|
|
inline Convertible makeConvertible(const id value) { return Convertible(Holder(value)); }
|
|
|
|
} // namespace conversion
|
|
} // namespace style
|
|
} // namespace mbgl
|
|
|
|
NS_ASSUME_NONNULL_END
|