/**************************************************************************** ** Copyright (c) 2021, Fougue Ltd. ** All rights reserved. ** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt ****************************************************************************/ #include "property_value_conversion.h" #include "cpp_utils.h" #include "filepath_conv.h" #include "math_utils.h" #include "property_builtins.h" #include "property_enumeration.h" #include "string_conv.h" #include "tkernel_utils.h" #include "unit_system.h" #include #if __cpp_lib_to_chars # include #endif #include #include #include #include #include namespace Mayo { namespace { // Helper that converts a double number into a string static std::string toString(double value, int prec = 6) { #if __cpp_lib_to_chars char buff[64] = {}; auto toCharsFormat = std::chars_format::general; auto resToChars = std::to_chars(std::begin(buff), std::end(buff), value, toCharsFormat, prec); if (resToChars.ec != std::errc()) throw std::runtime_error("value_too_large"); return std::string(buff, resToChars.ptr - buff); #else std::stringstream sstr; sstr.precision(prec); sstr << value; return sstr.str(); #endif } static std::string toString(const gp_XYZ& coords, int prec = 6) { const std::string strX = toString(coords.X(), prec); const std::string strY = toString(coords.Y(), prec); const std::string strZ = toString(coords.Z(), prec); return strX + ", " + strY + ", " + strZ; } static gp_XYZ xyzFromString(std::string_view str) { const char* ptrCoord = str.data(); const char* const ptrCoordEnd = str.data() + str.size(); auto fnParseNextCoord = [&]{ const std::locale& locale = std::locale::classic(); ptrCoord = std::find_if(ptrCoord, ptrCoordEnd, [&](char ch) { return !std::isspace(ch, locale); }); auto ptrComma = std::find(ptrCoord, ptrCoordEnd, L','); double coord = 0; #if __cpp_lib_to_chars auto [ptr, err] = std::from_chars(ptrCoord, ptrComma, coord); if (err != std::errc()) throw std::runtime_error(std::make_error_code(err).message()); #else errno = 0; coord = std::strtod(ptrCoord, nullptr); if (errno != 0) throw std::runtime_error(std::strerror(errno)); #endif ptrCoord = ptrComma + 1; return coord; }; gp_XYZ coords; coords.SetX(fnParseNextCoord()); coords.SetY(fnParseNextCoord()); coords.SetZ(fnParseNextCoord()); return coords; } } // namespace PropertyValueConversion::Variant PropertyValueConversion::toVariant(const Property& prop) const { auto fnError = [&](std::string_view text) { // TODO Use other output stream(dedicated Messenger object?) std::cerr << fmt::format("PropertyValueConversion::toVariant() {} [ propertyName={}, propertyType={} ]", text, prop.name().key, prop.dynTypeName()) << std::endl; return PropertyValueConversion::Variant{}; }; if (isType(prop)) { return constRef(prop).value(); } else if (isType(prop)) { return constRef(prop).value(); } else if (isType(prop)) { return constRef(prop).value(); } else if (isType(prop)) { return fnError("Support of this property type not yet implemented"); } else if (isType(prop)) { return constRef(prop).value(); } else if (isType(prop)) { return constRef(prop).value().u8string(); } else if (isType(prop)) { try { return toString(constRef(prop).value().XYZ(), m_doubleToStringPrecision); } catch (const std::exception& err) { return fnError(fmt::format("Failed with error: '{}'", err.what())); } } else if (isType(prop)) { try { return toString(constRef(prop).value().XYZ(), m_doubleToStringPrecision); } catch (const std::exception& err) { return fnError(fmt::format("Failed with error: '{}'", err.what())); } } else if (isType(prop)) { return fnError("Support of this property type not yet implemented"); } else if (isType(prop)) { return TKernelUtils::colorToHex(constRef(prop)); } else if (isType(prop)) { return std::string(constRef(prop).valueName()); } else if (isType(prop)) { const auto& qtyProp = constRef(prop); const auto trRes = UnitSystem::translate(UnitSystem::SI, qtyProp.quantityValue(), qtyProp.quantityUnit()); try { const double value = trRes.value * trRes.factor; return toString(value, m_doubleToStringPrecision) + trRes.strUnit; } catch (const std::exception& err) { return fnError(fmt::format("Failed with error: '{}'", err.what())); } } return {}; } bool PropertyValueConversion::fromVariant(Property* prop, const Variant& variant) const { if (!prop) return false; auto fnError = [=](std::string_view text) { // TODO Use other output stream(dedicated Messenger object?) std::cerr << fmt::format("PropertyValueConversion::fromVariant() {} [ propertyName={}, propertyType={} ]", text, prop->name().key, prop->dynTypeName()) << std::endl; return false; }; if (isType(prop)) { bool okConversion = false; const bool on = variant.toBool(&okConversion); if (okConversion) return ptr(prop)->setValue(on); return fnError("Variant not convertible to bool"); } else if (isType(prop)) { bool okConversion = false; const int v = variant.toInt(&okConversion); if (okConversion) return ptr(prop)->setValue(v); return fnError("Variant not convertible to int"); } else if (isType(prop)) { bool okConversion = false; const double v = variant.toDouble(&okConversion); if (okConversion) return ptr(prop)->setValue(v); return fnError("Variant not convertible to double"); } else if (isType(prop)) { return fnError("Support of this property type not yet implemented"); } else if (isType(prop)) { if (variant.isConvertibleToConstRefString()) return ptr(prop)->setValue(variant.toConstRefString()); bool okConversion = false; const std::string str = variant.toString(&okConversion); if (okConversion) return ptr(prop)->setValue(str); return fnError("Variant not convertible to string"); } else if (isType(prop)) { // Note: explicit conversion from utf8 std::string to std::filesystem::path // If character type of the source string is "char" then FilePath constructor assumes // native narrow encoding(which might cause encoding issues) if (variant.isConvertibleToConstRefString()) return ptr(prop)->setValue(filepathFrom(variant.toConstRefString())); else return fnError("Variant expected to hold string"); } else if (isType(prop)) { if (variant.isConvertibleToConstRefString()) { try { return ptr(prop)->setValue(xyzFromString(variant.toConstRefString())); } catch (const std::exception& err) { return fnError(fmt::format("Failed with error '{}'", err.what())); } } return fnError("Variant expected to hold string"); } else if (isType(prop)) { if (variant.isConvertibleToConstRefString()) { try { return ptr(prop)->setValue(xyzFromString(variant.toConstRefString())); } catch (const std::exception& err) { return fnError(fmt::format("Failed with error '{}'", err.what())); } } return fnError("Variant expected to hold string"); } else if (isType(prop)) { return fnError("Support of this property type not yet implemented"); } else if (isType(prop)) { if (variant.isConvertibleToConstRefString()) { const std::string& strColorHex = variant.toConstRefString(); Quantity_Color color; if (!TKernelUtils::colorFromHex(strColorHex, &color)) return fnError(fmt::format("Not hexadecimal format '{}'", strColorHex)); return ptr(prop)->setValue(color); } return fnError("Variant expected to hold string"); } else if (isType(prop)) { if (variant.isConvertibleToConstRefString()) { const std::string& name = variant.toConstRefString(); const Enumeration::Item* ptrItem = ptr(prop)->enumeration().findItemByName(name); if (ptrItem) return ptr(prop)->setValue(ptrItem->value); return fnError(fmt::format("Found no enumeration item for '{}'", name)); } return fnError("Variant expected to hold string"); } else if (isType(prop)) { if (variant.isConvertibleToConstRefString()) { const std::string& strQty = variant.toConstRefString(); Unit unit; const UnitSystem::TranslateResult trRes = UnitSystem::parseQuantity(strQty, &unit); if (!trRes.strUnit || MathUtils::fuzzyIsNull(trRes.factor)) return fnError(fmt::format("Failed to parse quantity string '{}'", strQty)); if (unit != Unit::None && unit != ptr(prop)->quantityUnit()) return fnError(fmt::format("Unit mismatch with quantity string '{}'", strQty)); return ptr(prop)->setQuantityValue(trRes.value * trRes.factor); } return fnError("Variant expected to hold string"); } return false; } static void assignBoolPtr(bool* value, bool on) { if (value) *value = on; } bool PropertyValueConversion::Variant::toBool(bool* ok) const { assignBoolPtr(ok, true); if (std::holds_alternative(*this)) return std::get(*this); else if (std::holds_alternative(*this)) return std::get(*this) != 0; assignBoolPtr(ok, false); return false; } int PropertyValueConversion::Variant::toInt(bool* ok) const { assignBoolPtr(ok, true); if (std::holds_alternative(*this)) { return std::get(*this); } else if (std::holds_alternative(*this)) { auto dval = std::floor(std::get(*this)); if (std::isgreaterequal(dval, INT_MIN) && std::islessequal(dval, INT_MAX)) return static_cast(dval); } else if (std::holds_alternative(*this)) { return std::stoi(std::get(*this)); } assignBoolPtr(ok, false); return 0; } double PropertyValueConversion::Variant::toDouble(bool* ok) const { assignBoolPtr(ok, true); if (std::holds_alternative(*this)) return std::get(*this); else if (std::holds_alternative(*this)) return std::get(*this); else if (std::holds_alternative(*this)) return std::stod(std::get(*this)); assignBoolPtr(ok, false); return 0.; } std::string PropertyValueConversion::Variant::toString(bool* ok) const { assignBoolPtr(ok, true); if (std::holds_alternative(*this)) return std::to_string(std::get(*this)); else if (std::holds_alternative(*this)) return std::to_string(std::get(*this)); else return this->toConstRefString(); } const std::string& PropertyValueConversion::Variant::toConstRefString(bool* ok) const { assignBoolPtr(ok, true); if (std::holds_alternative(*this)) { static const std::string strTrue = "true"; static const std::string strFalse = "false"; return std::get(*this) ? strTrue : strFalse; } else if (std::holds_alternative(*this)) { return std::get(*this); } assignBoolPtr(ok, false); return CppUtils::nullString(); } bool PropertyValueConversion::Variant::isConvertibleToConstRefString() const { return std::holds_alternative(*this) || std::holds_alternative(*this); } bool PropertyValueConversion::Variant::isByteArray() const { return m_isByteArray && std::holds_alternative(*this); } void PropertyValueConversion::Variant::setByteArray(bool on) { m_isByteArray = on; } } // namespace Mayo