From a18d6dda5de5247b14611a3bd901fdf65e13beeb Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sun, 19 Jun 2016 20:05:05 -0700 Subject: [core] Rewrite style parsing logic for reuse in node bindings --- include/mbgl/style/conversion.hpp | 263 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 include/mbgl/style/conversion.hpp (limited to 'include/mbgl/style/conversion.hpp') diff --git a/include/mbgl/style/conversion.hpp b/include/mbgl/style/conversion.hpp new file mode 100644 index 0000000000..0507d452fd --- /dev/null +++ b/include/mbgl/style/conversion.hpp @@ -0,0 +1,263 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace mbgl { +namespace style { +namespace conversion { + +/* + This namespace defines a generic conversion from a templated type `V` representing a JSON object conforming + to a style property from the Mapbox Style Specification, to `PropertyValue`: + + template + Result> convertPropertyValue(const V& value); + + This is used concretely for conversions from RapidJSON types in mbgl core, and from v8 types in + the node bindings. + + The requirements are that the following are legal expressions for a value `v` of type `const V&`: + + * `isArray(v)` -- returns a boolean indicating whether `v` represents a JSON array + * `arrayLength(v)` -- called only if `isArray(v)`; returns a size_t length + * `arrayMember(v)` -- called only if `isArray(v)`; returns `V` or `V&` + + * `isObject(v)` -- returns a boolean indicating whether `v` represents a JSON object + * `objectMember(v, name)` -- called only if `isObject(v)`; `name` is `const char *`; return value: + * is true when evaluated in a boolean context iff the named member exists + * is convertable to a `V` or `V&` when dereferenced + + * `toBool(v)` -- returns `optional`, absence indicating `v` is not a JSON boolean + * `toNumber(v)` -- returns `optional`, absence indicating `v` is not a JSON number + * `toString(v)` -- returns `optional`, absence indicating `v` is not a JSON string + + If for any reason the conversion fails, the result of `convertPropertyValue` will be the `Error` variant, + which includes explanatory text. +*/ + +struct Error { const char * message; }; + +template +using Result = variant; + +template +struct ConstantConverter {}; + +template +struct ConstantConverter { + Result operator()(const V& value) const { + optional converted = toBool(value); + if (!converted) { + return Error { "value must be a boolean" }; + } + return *converted; + } +}; + +template +struct ConstantConverter { + Result operator()(const V& value) const { + optional converted = toNumber(value); + if (!converted) { + return Error { "value must be a number" }; + } + return *converted; + } +}; + +template +struct ConstantConverter { + Result operator()(const V& value) const { + optional converted = toString(value); + if (!converted) { + return Error { "value must be a string" }; + } + return *converted; + } +}; + +template +struct ConstantConverter::value>> { + Result operator()(const V& value) const { + optional string = toString(value); + if (!string) { + return Error { "value must be a string" }; + } + + const auto result = Enum::toEnum(*string); + if (!result) { + return Error { "value must be a valid enumeration value" }; + } + + return *result; + } +}; + +template +struct ConstantConverter { + Result operator()(const V& value) const { + optional string = toString(value); + if (!string) { + return Error { "value must be a string" }; + } + + optional color = Color::parse(*string); + if (!color) { + return Error { "value must be a valid color" }; + } + + return *color; + } +}; + +template +struct ConstantConverter> { + Result> operator()(const V& value) const { + if (!isArray(value) || arrayLength(value) != 2) { + return Error { "value must be an array of two numbers" }; + } + + optional first = toNumber(arrayMember(value, 0)); + optional second = toNumber(arrayMember(value, 1)); + if (!first || !second) { + return Error { "value must be an array of two numbers" }; + } + + return std::array {{ *first, *second }}; + } +}; + +template +struct ConstantConverter> { + Result> operator()(const V& value) const { + if (!isArray(value) || arrayLength(value) != 4) { + return Error { "value must be an array of four numbers" }; + } + + optional first = toNumber(arrayMember(value, 0)); + optional second = toNumber(arrayMember(value, 1)); + optional third = toNumber(arrayMember(value, 2)); + optional fourth = toNumber(arrayMember(value, 3)); + if (!first || !second) { + return Error { "value must be an array of four numbers" }; + } + + return std::array {{ *first, *second, *third, *fourth }}; + } +}; + +template +struct ConstantConverter> { + Result> operator()(const V& value) const { + if (!isArray(value)) { + return Error { "value must be an array" }; + } + + std::vector result; + result.reserve(arrayLength(value)); + + for (std::size_t i = 0; i < arrayLength(value); ++i) { + optional number = toNumber(arrayMember(value, i)); + if (!number) { + return Error { "value must be an array of numbers" }; + } + result.push_back(*number); + } + + return result; + } +}; + +template +struct ConstantConverter> { + Result> operator()(const V& value) const { + if (!isArray(value)) { + return Error { "value must be an array" }; + } + + std::vector result; + result.reserve(arrayLength(value)); + + for (std::size_t i = 0; i < arrayLength(value); ++i) { + optional string = toString(arrayMember(value, i)); + if (!string) { + return Error { "value must be an array of strings" }; + } + result.push_back(*string); + } + + return result; + } +}; + +template +Result> convertPropertyValue(const V& value) { + if (!isObject(value)) { + Result constant = ConstantConverter()(value); + if (constant.template is()) { + return constant.template get(); + } + return constant.template get(); + } + + auto stopsValue = objectMember(value, "stops"); + if (!stopsValue) { + return Error { "function value must specify stops" }; + } + + if (!isArray(*stopsValue)) { + return Error { "function stops must be an array" }; + } + + std::vector> stops; + for (std::size_t i = 0; i < arrayLength(*stopsValue); ++i) { + const auto& stopValue = arrayMember(*stopsValue, i); + + if (!isArray(stopValue)) { + return Error { "function stop must be an array" }; + } + + if (arrayLength(stopValue) != 2) { + return Error { "function stop must have two elements" }; + } + + optional z = toNumber(arrayMember(stopValue, 0)); + if (!z) { + return Error { "function stop zoom level must be a number" }; + } + + Result v = ConstantConverter()(arrayMember(stopValue, 1)); + if (v.template is()) { + return v.template get(); + } + + stops.emplace_back(*z, v.template get()); + } + + auto baseValue = objectMember(value, "base"); + if (!baseValue) { + return Function(stops, 1.0f); + } + + optional base = toNumber(*baseValue); + if (!base) { + return Error { "function base must be a number"}; + } + + return Function(stops, *base); +} + +} // namespace conversion +} // namespace style +} // namespace mbgl -- cgit v1.2.1