|
| 1 | +#include <tesseract_common/property_tree.h> |
| 2 | + |
| 3 | +const static std::string ATTRIBUTES_KEY{ "attributes" }; |
| 4 | +const static std::string VALUE_KEY{ "value" }; |
| 5 | + |
| 6 | +namespace tesseract_common |
| 7 | +{ |
| 8 | +void PropertyTree::mergeSchema(const PropertyTree& schema) |
| 9 | +{ |
| 10 | + // merge schema attributes |
| 11 | + for (const auto& pair : schema.attributes_) |
| 12 | + { |
| 13 | + if (!hasAttribute(pair.first)) |
| 14 | + attributes_[pair.first] = pair.second; |
| 15 | + } |
| 16 | + |
| 17 | + // apply default value only when property is not required and no explicit value present |
| 18 | + auto default_it = attributes_.find(std::string(property_attribute::DEFAULT)); |
| 19 | + if (default_it != attributes_.end()) |
| 20 | + { |
| 21 | + // determine if property is marked required |
| 22 | + auto it = attributes_.find(std::string(property_attribute::REQUIRED)); |
| 23 | + const bool required = (it != attributes_.end() && it->second.as<bool>()); |
| 24 | + if (!required) |
| 25 | + { |
| 26 | + if (!value_ || value_.IsNull()) |
| 27 | + value_ = YAML::Clone(default_it->second); |
| 28 | + } |
| 29 | + } |
| 30 | + |
| 31 | + // merge schema validators |
| 32 | + for (const auto& fn : schema.validators_) |
| 33 | + validators_.push_back(fn); |
| 34 | + |
| 35 | + // merge children recursively |
| 36 | + for (const auto& pair : schema.children_) |
| 37 | + { |
| 38 | + if (children_.find(pair.first) == children_.end()) |
| 39 | + children_[pair.first] = PropertyTree(); |
| 40 | + |
| 41 | + children_[pair.first].mergeSchema(pair.second); |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +bool PropertyTree::validate(std::vector<std::string>& errors, const std::string& path) const |
| 46 | +{ |
| 47 | + bool ok = true; |
| 48 | + // run custom validators |
| 49 | + for (const auto& vfn : validators_) |
| 50 | + { |
| 51 | + if (!vfn(*this, errors, path)) |
| 52 | + ok = false; |
| 53 | + } |
| 54 | + // recurse children |
| 55 | + for (const auto& kv : children_) |
| 56 | + { |
| 57 | + const std::string nextPath = path.empty() ? kv.first : path + "." + kv.first; |
| 58 | + if (!kv.second.validate(errors, nextPath)) |
| 59 | + ok = false; |
| 60 | + } |
| 61 | + return ok; |
| 62 | +} |
| 63 | + |
| 64 | +/// Add a custom validator for this node |
| 65 | +void PropertyTree::addValidator(ValidatorFn fn) { validators_.push_back(std::move(fn)); } |
| 66 | + |
| 67 | +PropertyTree& PropertyTree::get(std::string_view key) { return children_[std::string(key)]; } |
| 68 | +const PropertyTree& PropertyTree::get(std::string_view key) const { return children_.at(std::string(key)); } |
| 69 | + |
| 70 | +const PropertyTree* PropertyTree::find(std::string_view key) const |
| 71 | +{ |
| 72 | + auto it = children_.find(std::string(key)); |
| 73 | + return it != children_.end() ? &it->second : nullptr; |
| 74 | +} |
| 75 | + |
| 76 | +void PropertyTree::setValue(const YAML::Node& v) { value_ = v; } |
| 77 | +const YAML::Node& PropertyTree::getValue() const { return value_; } |
| 78 | + |
| 79 | +std::vector<std::string> PropertyTree::keys() const |
| 80 | +{ |
| 81 | + std::vector<std::string> res; |
| 82 | + res.reserve(children_.size()); |
| 83 | + for (const auto& pair : children_) |
| 84 | + res.push_back(pair.first); |
| 85 | + return res; |
| 86 | +} |
| 87 | + |
| 88 | +void PropertyTree::setAttribute(std::string_view name, const YAML::Node& attr) |
| 89 | +{ |
| 90 | + attributes_[std::string(name)] = attr; |
| 91 | +} |
| 92 | + |
| 93 | +void PropertyTree::setAttribute(std::string_view name, std::string_view attr) |
| 94 | +{ |
| 95 | + setAttribute(name, YAML::Node(std::string(attr))); |
| 96 | +} |
| 97 | + |
| 98 | +void PropertyTree::setAttribute(std::string_view name, const char* attr) |
| 99 | +{ |
| 100 | + setAttribute(name, YAML::Node(std::string(attr))); |
| 101 | +} |
| 102 | + |
| 103 | +void PropertyTree::setAttribute(std::string_view name, bool attr) { setAttribute(name, YAML::Node(attr)); } |
| 104 | + |
| 105 | +void PropertyTree::setAttribute(std::string_view name, int attr) { setAttribute(name, YAML::Node(attr)); } |
| 106 | + |
| 107 | +void PropertyTree::setAttribute(std::string_view name, double attr) { setAttribute(name, YAML::Node(attr)); } |
| 108 | + |
| 109 | +std::optional<YAML::Node> PropertyTree::getAttribute(std::string_view name) const |
| 110 | +{ |
| 111 | + auto it = attributes_.find(std::string(name)); |
| 112 | + if (it != attributes_.end()) |
| 113 | + return it->second; |
| 114 | + return std::nullopt; |
| 115 | +} |
| 116 | + |
| 117 | +bool PropertyTree::hasAttribute(std::string_view name) const |
| 118 | +{ |
| 119 | + auto it = attributes_.find(std::string(name)); |
| 120 | + return (it != attributes_.end() && it->second && !it->second.IsNull()); |
| 121 | +} |
| 122 | + |
| 123 | +PropertyTree PropertyTree::fromYAML(const YAML::Node& node) |
| 124 | +{ |
| 125 | + PropertyTree tree; |
| 126 | + tree.value_ = node; |
| 127 | + if (node.IsMap()) |
| 128 | + { |
| 129 | + // extract attributes map |
| 130 | + if (node[ATTRIBUTES_KEY] && node[ATTRIBUTES_KEY].IsMap()) |
| 131 | + { |
| 132 | + for (const auto& it : node[ATTRIBUTES_KEY]) |
| 133 | + { |
| 134 | + const auto key = it.first.as<std::string>(); |
| 135 | + tree.attributes_[key] = it.second; |
| 136 | + } |
| 137 | + } |
| 138 | + // extract children |
| 139 | + for (const auto& it : node) |
| 140 | + { |
| 141 | + const auto key = it.first.as<std::string>(); |
| 142 | + if (key == ATTRIBUTES_KEY) |
| 143 | + continue; |
| 144 | + |
| 145 | + tree.children_[key] = fromYAML(it.second); |
| 146 | + } |
| 147 | + } |
| 148 | + else if (node.IsSequence()) |
| 149 | + { |
| 150 | + int idx = 0; |
| 151 | + for (const auto& it : node) |
| 152 | + tree.children_[std::to_string(idx++)] = fromYAML(it); |
| 153 | + } |
| 154 | + return tree; |
| 155 | +} |
| 156 | + |
| 157 | +YAML::Node PropertyTree::toYAML() const |
| 158 | +{ |
| 159 | + // Always emit a mapping if attributes exist or children exist |
| 160 | + if (!attributes_.empty() || !children_.empty()) |
| 161 | + { |
| 162 | + YAML::Node node(YAML::NodeType::Map); |
| 163 | + // emit attributes first |
| 164 | + if (!attributes_.empty()) |
| 165 | + { |
| 166 | + YAML::Node attr_node(YAML::NodeType::Map); |
| 167 | + for (const auto& pair : attributes_) |
| 168 | + attr_node[pair.first] = pair.second; |
| 169 | + |
| 170 | + node[ATTRIBUTES_KEY] = attr_node; |
| 171 | + } |
| 172 | + // emit children |
| 173 | + for (const auto& pair : children_) |
| 174 | + node[pair.first] = pair.second.toYAML(); |
| 175 | + |
| 176 | + // if leaf (no children) but value present, emit under 'value' |
| 177 | + if (children_.empty() && value_) |
| 178 | + node[VALUE_KEY] = value_; |
| 179 | + |
| 180 | + return node; |
| 181 | + } |
| 182 | + |
| 183 | + // pure leaf without attributes: emit scalar/sequence directly |
| 184 | + return value_; |
| 185 | +} |
| 186 | + |
| 187 | +bool validateRequired(const PropertyTree& node, std::vector<std::string>& errors, const std::string& path) |
| 188 | +{ |
| 189 | + auto req_attr = node.getAttribute(property_attribute::REQUIRED); |
| 190 | + if (req_attr && req_attr->as<bool>()) |
| 191 | + { |
| 192 | + // if leaf node with no value or null |
| 193 | + if (!node.getValue() || node.getValue().IsNull()) |
| 194 | + { |
| 195 | + errors.push_back(path + ": required property missing or null"); |
| 196 | + return false; |
| 197 | + } |
| 198 | + } |
| 199 | + return true; |
| 200 | +} |
| 201 | + |
| 202 | +bool validateRange(const PropertyTree& node, std::vector<std::string>& errors, const std::string& path) |
| 203 | +{ |
| 204 | + auto min_attr = node.getAttribute(property_attribute::MINIMUM); |
| 205 | + auto max_attr = node.getAttribute(property_attribute::MAXIMUM); |
| 206 | + if (min_attr && max_attr) |
| 207 | + { |
| 208 | + const auto minv = min_attr->as<double>(); |
| 209 | + const auto maxv = max_attr->as<double>(); |
| 210 | + const auto val = node.getValue().as<double>(); |
| 211 | + if (val < minv || val > maxv) |
| 212 | + { |
| 213 | + errors.push_back(path + ": value " + std::to_string(val) + " out of range [" + std::to_string(minv) + "," + |
| 214 | + std::to_string(maxv) + "]"); |
| 215 | + return false; |
| 216 | + } |
| 217 | + } |
| 218 | + return true; |
| 219 | +} |
| 220 | + |
| 221 | +bool validateEnum(const PropertyTree& node, std::vector<std::string>& errors, const std::string& path) |
| 222 | +{ |
| 223 | + auto enum_attr = node.getAttribute(property_attribute::ENUM); |
| 224 | + if (enum_attr.has_value() && enum_attr->IsSequence()) |
| 225 | + { |
| 226 | + const auto val = node.getValue().as<std::string>(); |
| 227 | + for (const auto& v : enum_attr.value()) |
| 228 | + { |
| 229 | + if (v.as<std::string>() == val) |
| 230 | + return true; |
| 231 | + } |
| 232 | + errors.push_back(path + ": value '" + val + "' not in enum list"); |
| 233 | + return false; |
| 234 | + } |
| 235 | + return true; |
| 236 | +} |
| 237 | + |
| 238 | +} // namespace tesseract_common |
0 commit comments