diff --git a/benchmark/jsonld.cc b/benchmark/jsonld.cc index 43ea9e2bd2..46c72a511e 100644 --- a/benchmark/jsonld.cc +++ b/benchmark/jsonld.cc @@ -10,16 +10,14 @@ #include #include -// The catalog dimensions set how many annotation entries the map carries and -// how many instance positions materialization visits, so they are the knobs -// to turn when stress testing this functionality +// The catalog dimensions set how many entries the annotation list carries +// and how many instance positions materialization visits, so they are the +// knobs to turn when stress testing this functionality static constexpr std::size_t catalog_member_count{256}; static constexpr std::size_t authors_per_member{3}; static constexpr std::size_t keywords_per_member{3}; -static constexpr std::size_t identifiers_per_member{3}; -static constexpr std::size_t annotations_per_member{ - 19 + (authors_per_member * 2) + keywords_per_member + - identifiers_per_member}; +static constexpr std::size_t annotations_per_member{19 + + (authors_per_member * 2)}; static constexpr std::size_t total_annotation_count{ 2 + (catalog_member_count * annotations_per_member)}; @@ -32,8 +30,6 @@ static const sourcemeta::core::JSON::String authors_key{"authors"}; static const sourcemeta::core::JSON::String name_key{"name"}; static const sourcemeta::core::JSON::String keywords_key{"keywords"}; static const sourcemeta::core::JSON::String identifiers_key{"identifiers"}; -static const sourcemeta::core::JSON::String doi_key{"doi"}; -static const sourcemeta::core::JSON::String oclc_key{"oclc"}; static const sourcemeta::core::JSON::String price_key{"price"}; static const sourcemeta::core::JSON::String currency_key{"currency"}; static const sourcemeta::core::JSON::String value_key{"value"}; @@ -168,99 +164,86 @@ static auto make_catalog() -> sourcemeta::core::JSON { return catalog; } -static auto populate_member(sourcemeta::core::JSONLDWeakAnnotationMap &map, - const std::size_t index) -> void { +static auto +populate_member(sourcemeta::core::JSONLDWeakAnnotationList &annotations, + const std::size_t index) -> void { using sourcemeta::core::WeakPointer; - map.emplace(WeakPointer{std::cref(members_key), index}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "urn:isbn:978-0-" + std::to_string(index), - .types = {"https://schema.org/Book"}}}); - map.emplace(WeakPointer{std::cref(members_key), index, std::cref(isbn_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/isbn", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace( + annotations.emplace_back( + WeakPointer{std::cref(members_key), index}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "urn:isbn:978-0-" + std::to_string(index), + .types = {"https://schema.org/Book"}}}); + annotations.emplace_back( + WeakPointer{std::cref(members_key), index, std::cref(isbn_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/isbn", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( WeakPointer{std::cref(members_key), index, std::cref(title_key)}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/name", false}}, .value = sourcemeta::core::JSONLDCollection{ .container = sourcemeta::core::JSONLDContainer::Language}}); - map.emplace( + annotations.emplace_back( WeakPointer{std::cref(members_key), index, std::cref(abstract_key)}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/abstract", false}}, .value = sourcemeta::core::JSONLDLiteral{ .language = "en", .direction = sourcemeta::core::JSONLDDirection::LTR}}); - map.emplace( + annotations.emplace_back( WeakPointer{std::cref(members_key), index, std::cref(date_published_key)}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/datePublished", false}}, .value = sourcemeta::core::JSONLDLiteral{ .datatype = "http://www.w3.org/2001/XMLSchema#date"}}); - map.emplace( + annotations.emplace_back( WeakPointer{std::cref(members_key), index, std::cref(authors_key)}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/author", false}}, .value = sourcemeta::core::JSONLDCollection{ .container = sourcemeta::core::JSONLDContainer::List}}); for (std::size_t offset = 0; offset < authors_per_member; offset += 1) { - map.emplace(WeakPointer{std::cref(members_key), index, - std::cref(authors_key), offset}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/people/person-" + - std::to_string(index * 10 + offset), - .types = {"https://schema.org/Person"}}}); - map.emplace(WeakPointer{std::cref(members_key), index, - std::cref(authors_key), offset, - std::cref(name_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + WeakPointer{std::cref(members_key), index, std::cref(authors_key), + offset}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/people/person-" + + std::to_string(index * 10 + offset), + .types = {"https://schema.org/Person"}}}); + annotations.emplace_back(WeakPointer{std::cref(members_key), index, + std::cref(authors_key), offset, + std::cref(name_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); } - map.emplace( + annotations.emplace_back( WeakPointer{std::cref(members_key), index, std::cref(keywords_key)}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/keywords", false}}, .value = sourcemeta::core::JSONLDCollection{ .container = sourcemeta::core::JSONLDContainer::Set}}); - for (std::size_t offset = 0; offset < keywords_per_member; offset += 1) { - map.emplace(WeakPointer{std::cref(members_key), index, - std::cref(keywords_key), offset}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); - } - map.emplace( + annotations.emplace_back( WeakPointer{std::cref(members_key), index, std::cref(identifiers_key)}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/identifier", false}}, .value = sourcemeta::core::JSONLDCollection{ .container = sourcemeta::core::JSONLDContainer::Index}}); - map.emplace(WeakPointer{std::cref(members_key), index, - std::cref(identifiers_key), std::cref(isbn_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(WeakPointer{std::cref(members_key), index, - std::cref(identifiers_key), std::cref(doi_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(WeakPointer{std::cref(members_key), index, - std::cref(identifiers_key), std::cref(oclc_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); - - map.emplace(WeakPointer{std::cref(members_key), index, std::cref(price_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/offers", false}}, - .value = sourcemeta::core::JSONLDNode{}}); - map.emplace( + + annotations.emplace_back( + WeakPointer{std::cref(members_key), index, std::cref(price_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/offers", false}}, + .value = sourcemeta::core::JSONLDNode{}}); + annotations.emplace_back( WeakPointer{std::cref(members_key), index, std::cref(price_key), std::cref(currency_key)}, sourcemeta::core::JSONLDDescriptor{ @@ -268,59 +251,63 @@ static auto populate_member(sourcemeta::core::JSONLDWeakAnnotationMap &map, .value = sourcemeta::core::JSONLDReference{ .id = "https://www.iso.org/iso-4217/" + currency_code(index), .types = {"https://schema.org/Currency"}}}); - map.emplace(WeakPointer{std::cref(members_key), index, std::cref(price_key), - std::cref(value_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/price", false}}, - .value = sourcemeta::core::JSONLDLiteral{ - .datatype = "http://www.w3.org/2001/XMLSchema#decimal"}}); - - map.emplace( + annotations.emplace_back( + WeakPointer{std::cref(members_key), index, std::cref(price_key), + std::cref(value_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/price", false}}, + .value = sourcemeta::core::JSONLDLiteral{ + .datatype = "http://www.w3.org/2001/XMLSchema#decimal"}}); + + annotations.emplace_back( WeakPointer{std::cref(members_key), index, std::cref(publisher_key)}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/publisher", false}}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/org/" + std::to_string(index), .types = {"https://schema.org/Organization"}}}); - map.emplace(WeakPointer{std::cref(members_key), index, - std::cref(publisher_key), std::cref(name_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(WeakPointer{std::cref(members_key), index, - std::cref(publisher_key), std::cref(url_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/url", false}}, - .value = sourcemeta::core::JSONLDLiteral{ - .datatype = "http://www.w3.org/2001/XMLSchema#anyURI"}}); - - map.emplace( + annotations.emplace_back(WeakPointer{std::cref(members_key), index, + std::cref(publisher_key), + std::cref(name_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + WeakPointer{std::cref(members_key), index, std::cref(publisher_key), + std::cref(url_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/url", false}}, + .value = sourcemeta::core::JSONLDLiteral{ + .datatype = "http://www.w3.org/2001/XMLSchema#anyURI"}}); + + annotations.emplace_back( WeakPointer{std::cref(members_key), index, std::cref(series_key)}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/hasPart", true}}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/series/" + std::to_string(index), .types = {"https://schema.org/CreativeWorkSeries"}}}); - map.emplace(WeakPointer{std::cref(members_key), index, std::cref(series_key), - std::cref(name_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); - - map.emplace( + annotations.emplace_back(WeakPointer{std::cref(members_key), index, + std::cref(series_key), + std::cref(name_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); + + annotations.emplace_back( WeakPointer{std::cref(members_key), index, std::cref(metadata_key)}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/additionalProperty", false}}, .value = sourcemeta::core::JSONLDLiteral{.json = true}}); - map.emplace( + annotations.emplace_back( WeakPointer{std::cref(members_key), index, std::cref(provenance_key)}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://www.w3.org/ns/prov#has_provenance", false}}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/provenance/" + std::to_string(index), .graph = true}}); - map.emplace( + annotations.emplace_back( WeakPointer{std::cref(members_key), index, std::cref(provenance_key), std::cref(generated_by_key)}, sourcemeta::core::JSONLDDescriptor{ @@ -328,48 +315,49 @@ static auto populate_member(sourcemeta::core::JSONLDWeakAnnotationMap &map, .value = sourcemeta::core::JSONLDLiteral{}}); } -static auto -populate_annotation_map(sourcemeta::core::JSONLDWeakAnnotationMap &map) - -> void { - map.reserve(total_annotation_count); - map.emplace(sourcemeta::core::WeakPointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/catalog", - .types = {"https://schema.org/DataCatalog"}}}); - map.emplace(sourcemeta::core::WeakPointer{std::cref(members_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/dataset", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Set}}); +static auto populate_annotation_list( + sourcemeta::core::JSONLDWeakAnnotationList &annotations) -> void { + annotations.reserve(total_annotation_count); + annotations.emplace_back( + sourcemeta::core::WeakPointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/catalog", + .types = {"https://schema.org/DataCatalog"}}}); + annotations.emplace_back( + sourcemeta::core::WeakPointer{std::cref(members_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/dataset", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); for (std::size_t index = 0; index < catalog_member_count; index += 1) { - populate_member(map, index); + populate_member(annotations, index); } } -static void JSONLD_Catalog_Annotation_Map_Populate(benchmark::State &state) { +static void JSONLD_Catalog_Annotation_List_Populate(benchmark::State &state) { for (auto _ : state) { - sourcemeta::core::JSONLDWeakAnnotationMap map; - populate_annotation_map(map); - assert(map.size() == total_annotation_count); - benchmark::DoNotOptimize(map); + sourcemeta::core::JSONLDWeakAnnotationList annotations; + populate_annotation_list(annotations); + assert(annotations.size() == total_annotation_count); + benchmark::DoNotOptimize(annotations); } } static void JSONLD_Catalog_Materialize(benchmark::State &state) { const auto instance{make_catalog()}; - sourcemeta::core::JSONLDWeakAnnotationMap map; - populate_annotation_map(map); - assert(map.size() == total_annotation_count); + sourcemeta::core::JSONLDWeakAnnotationList annotations; + populate_annotation_list(annotations); + assert(annotations.size() == total_annotation_count); for (auto _ : state) { - auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + auto result{sourcemeta::core::jsonld_materialize(instance, annotations)}; assert(result.is_array()); assert(!result.empty()); benchmark::DoNotOptimize(result); } } -BENCHMARK(JSONLD_Catalog_Annotation_Map_Populate); +BENCHMARK(JSONLD_Catalog_Annotation_List_Populate); BENCHMARK(JSONLD_Catalog_Materialize); diff --git a/src/core/json/include/sourcemeta/core/json_object.h b/src/core/json/include/sourcemeta/core/json_object.h index 44db7b902b..49a8b99182 100644 --- a/src/core/json/include/sourcemeta/core/json_object.h +++ b/src/core/json/include/sourcemeta/core/json_object.h @@ -413,6 +413,54 @@ template class JSONObject { std::unreachable(); } + /// Try to access an object entry by its key name + [[nodiscard]] inline auto try_at(const Key &key, const hash_type key_hash) + -> mapped_type * { + assert(this->hash(key) == key_hash); + + // Move the perfect hash condition out of the loop for extra performance + if (this->hasher.is_perfect(key_hash)) { + for (auto &entry : this->data) { + if (entry.hash == key_hash && entry.first.size() == key.size()) { + return &entry.second; + } + } + } else { + for (auto &entry : this->data) { + if (entry.hash == key_hash && entry.first == key) { + return &entry.second; + } + } + } + + return nullptr; + } + + /// Try to access an object entry by its key name + template + requires std::same_as, KeyView> + [[nodiscard]] inline auto try_at(T key, const hash_type key_hash) + -> mapped_type * { + assert(this->hash(key) == key_hash); + + // Move the perfect hash condition out of the loop for extra performance + if (this->hasher.is_perfect(key_hash)) { + for (auto &entry : this->data) { + if (entry.hash == key_hash && entry.first.size() == key.size()) { + return &entry.second; + } + } + } else { + for (auto &entry : this->data) { + if (entry.hash == key_hash && entry.first == key) { + return &entry.second; + } + } + } + + return nullptr; + } + /// Try to access an object entry by its underlying positional index [[nodiscard]] inline auto try_at(const Key &key, const hash_type key_hash) const diff --git a/src/core/json/include/sourcemeta/core/json_value.h b/src/core/json/include/sourcemeta/core/json_value.h index 55cd02c523..f1219176f7 100644 --- a/src/core/json/include/sourcemeta/core/json_value.h +++ b/src/core/json/include/sourcemeta/core/json_value.h @@ -1419,6 +1419,67 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { return object.try_at(key, hash); } + /// This method tries to retrieve a mutable object element by key. For + /// example: + /// + /// ```cpp + /// #include + /// #include + /// + /// sourcemeta::core::JSON document = + /// sourcemeta::core::parse_json("{ \"foo\": 1 }"); + /// auto result{document.try_at("foo")}; + /// assert(result); + /// result->into(sourcemeta::core::JSON{2}); + /// assert(document.at("foo").to_integer() == 2); + /// ``` + [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto try_at(const String &key) + -> JSON * { + assert(this->is_object()); + auto &object{this->data_object}; + return object.try_at(key, object.hash(key)); + } + + /// This method tries to retrieve a mutable object element by string view key + template + requires std::same_as, StringView> + [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto try_at(T key) -> JSON * { + assert(this->is_object()); + auto &object{this->data_object}; + return object.try_at(key, object.hash(key)); + } + + /// This method tries to retrieve a mutable object element given a + /// pre-calculated property hash. For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// sourcemeta::core::JSON document = + /// sourcemeta::core::parse_json("{ \"foo\": 1 }"); + /// auto result{document.try_at("foo", + /// document.as_object().hash("foo"))}; + /// assert(result); + /// result->into(sourcemeta::core::JSON{2}); + /// assert(document.at("foo").to_integer() == 2); + /// ``` + [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto + try_at(const String &key, const typename Object::hash_type hash) -> JSON * { + assert(this->is_object()); + return this->data_object.try_at(key, hash); + } + + /// This method tries to retrieve a mutable object element by string view key + /// given a pre-calculated property hash + template + requires std::same_as, StringView> + [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto + try_at(T key, const typename Object::hash_type hash) -> JSON * { + assert(this->is_object()); + return this->data_object.try_at(key, hash); + } + /// Try to get a property, scanning from a caller-provided start offset. /// On hit, advances `start` past the found index. When looking up multiple /// keys in insertion order, each lookup hits on the first probe, making the diff --git a/src/core/jsonld/include/sourcemeta/core/jsonld_materialize.h b/src/core/jsonld/include/sourcemeta/core/jsonld_materialize.h index 4a0383fa2e..4fcd73200d 100644 --- a/src/core/jsonld/include/sourcemeta/core/jsonld_materialize.h +++ b/src/core/jsonld/include/sourcemeta/core/jsonld_materialize.h @@ -8,11 +8,10 @@ #include #include -#include // std::uint8_t -#include // std::optional -#include // std::unordered_map -#include // std::variant -#include // std::vector +#include // std::uint8_t +#include // std::optional +#include // std::variant +#include // std::vector namespace sourcemeta::core { @@ -108,27 +107,36 @@ struct JSONLDDescriptor { }; /// @ingroup jsonld -/// A resolved mapping of instance positions to their JSON-LD semantics, keyed -/// by a JSON Pointer of the given kind +/// An instance position paired with its JSON-LD semantics +template struct JSONLDBasicAnnotation { + /// The instance position the annotation describes. + PointerT pointer{}; + /// The JSON-LD semantics of the position. + JSONLDDescriptor descriptor{}; +}; + +/// @ingroup jsonld +/// A flat collection of annotated instance positions, in any order. When more +/// than one entry describes the same position, the first one wins. template -using JSONLDBasicAnnotationMap = - std::unordered_map; +using JSONLDBasicAnnotationList = std::vector>; /// @ingroup jsonld -/// A resolved annotation map keyed by an owning JSON Pointer -using JSONLDAnnotationMap = JSONLDBasicAnnotationMap; +/// An annotation list whose positions are owning JSON Pointers +using JSONLDAnnotationList = JSONLDBasicAnnotationList; /// @ingroup jsonld -/// A resolved annotation map keyed by a non-owning weak JSON Pointer. The keys -/// reference strings owned elsewhere that must outlive any materialization -/// call. -using JSONLDWeakAnnotationMap = JSONLDBasicAnnotationMap; +/// An annotation list whose positions are non-owning weak JSON Pointers. The +/// positions reference strings owned elsewhere that must outlive any +/// materialization call. +using JSONLDWeakAnnotationList = JSONLDBasicAnnotationList; /// @ingroup jsonld /// -/// Materialize an instance into expanded JSON-LD using an annotation map that -/// assigns JSON-LD semantics to instance positions. The result is always a JSON -/// array. For example: +/// Materialize an instance into expanded JSON-LD using an annotation list that +/// assigns JSON-LD semantics to instance positions. An undescribed member of a +/// collection defaults to a plain literal, or to an unordered collection for a +/// nested array. The result is always a JSON array. For example: /// /// ```cpp /// #include @@ -138,30 +146,34 @@ using JSONLDWeakAnnotationMap = JSONLDBasicAnnotationMap; /// const auto instance{sourcemeta::core::parse_json( /// R"({ "name": "Sourcemeta" })")}; /// -/// sourcemeta::core::JSONLDAnnotationMap map; -/// map.emplace(sourcemeta::core::Pointer{}, -/// sourcemeta::core::JSONLDDescriptor{ -/// {}, sourcemeta::core::JSONLDNode{ -/// "https://example.com/org", {}, false }}); -/// map.emplace(sourcemeta::core::Pointer{"name"}, -/// sourcemeta::core::JSONLDDescriptor{ -/// { { "https://schema.org/name", false } }, -/// sourcemeta::core::JSONLDLiteral{}}); +/// sourcemeta::core::JSONLDAnnotationList annotations; +/// annotations.push_back( +/// {sourcemeta::core::Pointer{}, +/// sourcemeta::core::JSONLDDescriptor{ +/// {}, sourcemeta::core::JSONLDNode{ +/// "https://example.com/org", {}, false }}}); +/// annotations.push_back( +/// {sourcemeta::core::Pointer{"name"}, +/// sourcemeta::core::JSONLDDescriptor{ +/// { { "https://schema.org/name", false } }, +/// sourcemeta::core::JSONLDLiteral{}}}); /// -/// const auto expanded{sourcemeta::core::jsonld_materialize(instance, map)}; +/// const auto expanded{ +/// sourcemeta::core::jsonld_materialize(instance, annotations)}; /// sourcemeta::core::prettify(expanded, std::cout); /// std::cout << std::endl; /// ``` SOURCEMETA_CORE_JSONLD_EXPORT -auto jsonld_materialize(const JSON &instance, const JSONLDAnnotationMap &map) - -> JSON; +auto jsonld_materialize(const JSON &instance, + const JSONLDAnnotationList &annotations) -> JSON; /// @ingroup jsonld /// -/// Materialize an instance into expanded JSON-LD using a weak annotation map -/// whose keys are non-owning views into strings owned elsewhere. The backing -/// strings must outlive the call. The result is always a JSON array. For -/// example: +/// Materialize an instance into expanded JSON-LD using a weak annotation list +/// whose positions are non-owning views into strings owned elsewhere. The +/// backing strings must outlive the call. An undescribed member of a +/// collection defaults to a plain literal, or to an unordered collection for a +/// nested array. The result is always a JSON array. For example: /// /// ```cpp /// #include @@ -174,23 +186,26 @@ auto jsonld_materialize(const JSON &instance, const JSONLDAnnotationMap &map) /// /// const sourcemeta::core::JSON::String name_key{"name"}; /// -/// sourcemeta::core::JSONLDWeakAnnotationMap map; -/// map.emplace(sourcemeta::core::WeakPointer{}, -/// sourcemeta::core::JSONLDDescriptor{ -/// {}, sourcemeta::core::JSONLDNode{ -/// "https://example.com/org", {}, false }}); -/// map.emplace(sourcemeta::core::WeakPointer{std::cref(name_key)}, -/// sourcemeta::core::JSONLDDescriptor{ -/// { { "https://schema.org/name", false } }, -/// sourcemeta::core::JSONLDLiteral{}}); +/// sourcemeta::core::JSONLDWeakAnnotationList annotations; +/// annotations.push_back( +/// {sourcemeta::core::WeakPointer{}, +/// sourcemeta::core::JSONLDDescriptor{ +/// {}, sourcemeta::core::JSONLDNode{ +/// "https://example.com/org", {}, false }}}); +/// annotations.push_back( +/// {sourcemeta::core::WeakPointer{std::cref(name_key)}, +/// sourcemeta::core::JSONLDDescriptor{ +/// { { "https://schema.org/name", false } }, +/// sourcemeta::core::JSONLDLiteral{}}}); /// -/// const auto expanded{sourcemeta::core::jsonld_materialize(instance, map)}; +/// const auto expanded{ +/// sourcemeta::core::jsonld_materialize(instance, annotations)}; /// sourcemeta::core::prettify(expanded, std::cout); /// std::cout << std::endl; /// ``` SOURCEMETA_CORE_JSONLD_EXPORT auto jsonld_materialize(const JSON &instance, - const JSONLDWeakAnnotationMap &map) -> JSON; + const JSONLDWeakAnnotationList &annotations) -> JSON; } // namespace sourcemeta::core diff --git a/src/core/jsonld/jsonld_materialize.cc b/src/core/jsonld/jsonld_materialize.cc index 7ed01d81fa..c9d7329500 100644 --- a/src/core/jsonld/jsonld_materialize.cc +++ b/src/core/jsonld/jsonld_materialize.cc @@ -4,9 +4,9 @@ #include "jsonld_keywords.h" -#include // std::ranges::sort, std::ranges::none_of -#include // assert -#include // std::size_t +#include // std::ranges::sort, std::ranges::stable_sort, std::ranges::unique, std::ranges::none_of +#include // assert +#include // std::size_t #include // std::reference_wrapper, std::cref #include // std::optional, std::nullopt #include // std::is_same_v @@ -18,18 +18,53 @@ namespace sourcemeta::core { namespace { +template +using AnnotationIndex = std::vector *>; + +// A contiguous run of sorted annotations whose positions are all extensions +// of, or equal to, the position currently being visited +template struct AnnotationRange { + typename AnnotationIndex::const_iterator begin; + typename AnnotationIndex::const_iterator end; +}; + +// The sub-run of annotations that belong to the child position the pointer +// currently names, advancing the scan iterator past it. Annotations sorting +// before the child correspond to positions the walk does not visit and are +// skipped for good. +template +auto child_range(typename AnnotationIndex::const_iterator &iterator, + const typename AnnotationIndex::const_iterator end, + const PointerT &pointer) -> AnnotationRange { + const auto depth{pointer.size() - 1}; + const auto &token{pointer.at(depth)}; + while (iterator != end && (*iterator)->pointer.at(depth) < token) { + iterator += 1; + } + const auto begin{iterator}; + while (iterator != end && (*iterator)->pointer.at(depth) == token) { + iterator += 1; + } + return {begin, iterator}; +} + template auto materialize_value(const JSON &value, PointerT &pointer, - const JSONLDBasicAnnotationMap &map, + AnnotationRange range, std::vector &standalone, const std::vector **matched_edges = nullptr) -> std::optional; template auto fill_node(JSON &node, const JSON &instance_object, PointerT &pointer, - const JSONLDBasicAnnotationMap &map, + const AnnotationRange &range, std::vector &standalone) -> void; +template +auto materialize_member(const JSON &value, PointerT &pointer, + const AnnotationRange &range, + std::vector &standalone) -> std::optional; + // Append an object key to the pointer, copying it for an owning pointer and // taking a non-owning view for a weak pointer. template @@ -70,23 +105,24 @@ auto types_to_array(const std::vector &types) -> JSON { // The property array of node under the given predicate, creating it as needed. auto property_target(JSON &node, const JSON::StringView predicate) -> JSON & { - if (!node.defines(predicate)) { - node.assign_assume_new(JSON::String{predicate}, JSON::make_array()); + const auto hash{node.as_object().hash(predicate)}; + const auto existing{node.try_at(predicate, hash)}; + if (existing != nullptr) { + return *existing; } - return node.at(predicate); + return node.assign_assume_new(JSON::String{predicate}, JSON::make_array(), + hash); } // The property array nested under @reverse and the given predicate. auto reverse_target(JSON &node, const JSON::StringView predicate) -> JSON & { - if (!node.defines(KEYWORD_REVERSE, KEYWORD_REVERSE_HASH)) { - node.assign_assume_new(JSON::String{KEYWORD_REVERSE}, JSON::make_object(), - KEYWORD_REVERSE_HASH); - } - auto &reverse{node.at(KEYWORD_REVERSE, KEYWORD_REVERSE_HASH)}; - if (!reverse.defines(predicate)) { - reverse.assign_assume_new(JSON::String{predicate}, JSON::make_array()); - } - return reverse.at(predicate); + const auto existing{node.try_at(KEYWORD_REVERSE, KEYWORD_REVERSE_HASH)}; + auto &reverse{existing != nullptr + ? *existing + : node.assign_assume_new(JSON::String{KEYWORD_REVERSE}, + JSON::make_object(), + KEYWORD_REVERSE_HASH)}; + return property_target(reverse, predicate); } // Attach a value under a single edge. A set, represented as a bare array, @@ -160,13 +196,16 @@ auto materialize_reference(const JSONLDReference &descriptor) -> JSON { template auto build_collection(const JSON &value, PointerT &pointer, - const JSONLDBasicAnnotationMap &map, + const AnnotationRange &range, std::vector &standalone, const bool ordered) -> JSON { auto elements{JSON::make_array()}; + auto iterator{range.begin}; for (std::size_t index = 0; index < value.size(); index += 1) { pointer.push_back(index); - auto element{materialize_value(value.at(index), pointer, map, standalone)}; + auto element{materialize_member(value.at(index), pointer, + child_range(iterator, range.end, pointer), + standalone)}; pointer.pop_back(); if (!element.has_value()) { continue; @@ -246,13 +285,15 @@ auto build_language_collection(const JSON &value) -> JSON { // The index keys carry no RDF and are dropped. template auto build_index_collection(const JSON &value, PointerT &pointer, - const JSONLDBasicAnnotationMap &map, + const AnnotationRange &range, std::vector &standalone) -> JSON { auto elements{JSON::make_array()}; + auto iterator{range.begin}; for (const auto key : sorted_keys(value)) { push_property(pointer, key.get()); - auto element{ - materialize_value(value.at(key.get()), pointer, map, standalone)}; + auto element{materialize_member(value.at(key.get()), pointer, + child_range(iterator, range.end, pointer), + standalone)}; pointer.pop_back(); if (!element.has_value()) { continue; @@ -270,10 +311,34 @@ auto build_index_collection(const JSON &value, PointerT &pointer, return elements; } +// An undescribed collection member still materializes with a default kind, a +// scalar as a plain literal and a nested array as an unordered collection. +// An undescribed object member keeps the anonymous node treatment of any +// other position. +template +auto materialize_member(const JSON &value, PointerT &pointer, + const AnnotationRange &range, + std::vector &standalone) -> std::optional { + const auto described{range.begin != range.end && + (*range.begin)->pointer.size() == pointer.size()}; + if (described || value.is_object()) { + return materialize_value(value, pointer, range, standalone); + } + + if (value.is_null()) { + return std::nullopt; + } + + if (value.is_array()) { + return build_collection(value, pointer, range, standalone, false); + } + + return materialize_literal(JSONLDLiteral{}, value); +} + template auto materialize_node(const JSONLDNode &descriptor, const JSON &value, - PointerT &pointer, - const JSONLDBasicAnnotationMap &map, + PointerT &pointer, const AnnotationRange &range, std::vector &standalone) -> JSON { auto node{JSON::make_object()}; if (descriptor.id.has_value()) { @@ -298,7 +363,7 @@ auto materialize_node(const JSONLDNode &descriptor, const JSON &value, JSON{descriptor.id.value()}, KEYWORD_ID_HASH); } std::vector graph_nodes; - fill_node(inner, value, pointer, map, graph_nodes); + fill_node(inner, value, pointer, range, graph_nodes); auto graph{JSON::make_array()}; if (inner.object_size() > (descriptor.id.has_value() ? 1 : 0)) { graph.push_back(std::move(inner)); @@ -311,13 +376,13 @@ auto materialize_node(const JSONLDNode &descriptor, const JSON &value, return node; } - fill_node(node, value, pointer, map, standalone); + fill_node(node, value, pointer, range, standalone); return node; } template auto materialize_value(const JSON &value, PointerT &pointer, - const JSONLDBasicAnnotationMap &map, + AnnotationRange range, std::vector &standalone, const std::vector **matched_edges) -> std::optional { @@ -329,14 +394,16 @@ auto materialize_value(const JSON &value, PointerT &pointer, return std::nullopt; } - const auto iterator{map.find(pointer)}; - if (iterator == map.cend()) { + // Every annotation in the range extends the current position, so one of + // equal length is the annotation of the position itself and sorts first + if (range.begin == range.end || + (*range.begin)->pointer.size() != pointer.size()) { // An undescribed object with described descendants becomes an anonymous // blank node so its children have a subject. Anything else is not // annotated. - if (value.is_object()) { + if (value.is_object() && range.begin != range.end) { auto node{JSON::make_object()}; - fill_node(node, value, pointer, map, standalone); + fill_node(node, value, pointer, range, standalone); if (node.empty()) { return std::nullopt; } @@ -345,13 +412,14 @@ auto materialize_value(const JSON &value, PointerT &pointer, return std::nullopt; } - const auto &descriptor{iterator->second}; + const auto &descriptor{(*range.begin)->descriptor}; + range.begin += 1; if (matched_edges != nullptr) { *matched_edges = &descriptor.edges; } if (std::holds_alternative(descriptor.value)) { return materialize_node(std::get(descriptor.value), value, - pointer, map, standalone); + pointer, range, standalone); } if (std::holds_alternative(descriptor.value)) { return materialize_literal(std::get(descriptor.value), @@ -368,7 +436,7 @@ auto materialize_value(const JSON &value, PointerT &pointer, if (!value.is_array()) { return std::nullopt; } - return build_collection(value, pointer, map, standalone, + return build_collection(value, pointer, range, standalone, collection.container == JSONLDContainer::List); case JSONLDContainer::Language: assert(value.is_object()); @@ -379,7 +447,7 @@ auto materialize_value(const JSON &value, PointerT &pointer, return build_language_collection(value); case JSONLDContainer::Index: assert(value.is_object()); - return build_index_collection(value, pointer, map, standalone); + return build_index_collection(value, pointer, range, standalone); } std::unreachable(); @@ -387,7 +455,7 @@ auto materialize_value(const JSON &value, PointerT &pointer, template auto fill_node(JSON &node, const JSON &instance_object, PointerT &pointer, - const JSONLDBasicAnnotationMap &map, + const AnnotationRange &range, std::vector &standalone) -> void { std::vector> keys; keys.reserve(instance_object.object_size()); @@ -398,11 +466,13 @@ auto fill_node(JSON &node, const JSON &instance_object, PointerT &pointer, return left.get() < right.get(); }); + auto iterator{range.begin}; for (const auto key : keys) { push_property(pointer, key.get()); const std::vector *edges{nullptr}; - auto child_value{materialize_value(instance_object.at(key.get()), pointer, - map, standalone, &edges)}; + auto child_value{materialize_value( + instance_object.at(key.get()), pointer, + child_range(iterator, range.end, pointer), standalone, &edges)}; pointer.pop_back(); if (!child_value.has_value()) { continue; @@ -424,10 +494,28 @@ auto fill_node(JSON &node, const JSON &instance_object, PointerT &pointer, template auto materialize_root(const JSON &instance, - const JSONLDBasicAnnotationMap &map) -> JSON { + const JSONLDBasicAnnotationList &annotations) + -> JSON { + AnnotationIndex index; + index.reserve(annotations.size()); + for (const auto &annotation : annotations) { + index.push_back(&annotation); + } + + std::ranges::stable_sort(index, + [](const auto *left, const auto *right) -> bool { + return left->pointer < right->pointer; + }); + const auto duplicates{std::ranges::unique( + index, [](const auto *left, const auto *right) -> bool { + return left->pointer == right->pointer; + })}; + index.erase(duplicates.begin(), duplicates.end()); + std::vector standalone; PointerT pointer; - auto root{materialize_value(instance, pointer, map, standalone)}; + auto root{materialize_value(instance, pointer, {index.cbegin(), index.cend()}, + standalone)}; auto result{JSON::make_array()}; if (root.has_value()) { @@ -454,14 +542,14 @@ auto materialize_root(const JSON &instance, } // namespace -auto jsonld_materialize(const JSON &instance, const JSONLDAnnotationMap &map) - -> JSON { - return materialize_root(instance, map); +auto jsonld_materialize(const JSON &instance, + const JSONLDAnnotationList &annotations) -> JSON { + return materialize_root(instance, annotations); } auto jsonld_materialize(const JSON &instance, - const JSONLDWeakAnnotationMap &map) -> JSON { - return materialize_root(instance, map); + const JSONLDWeakAnnotationList &annotations) -> JSON { + return materialize_root(instance, annotations); } } // namespace sourcemeta::core diff --git a/test/json/json_value_test.cc b/test/json/json_value_test.cc index 94270c637c..1c3251a3a8 100644 --- a/test/json/json_value_test.cc +++ b/test/json/json_value_test.cc @@ -398,6 +398,62 @@ TEST(try_at_fail) { EXPECT_FALSE(result); } +TEST(try_at_mutable) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json("{\"foo\":5}"); + EXPECT_TRUE(document.is_object()); + const auto result = document.try_at("foo"); + EXPECT_TRUE(result); + EXPECT_EQ(result->to_integer(), 5); + result->into(sourcemeta::core::JSON{7}); + EXPECT_EQ(document.at("foo").to_integer(), 7); +} + +TEST(try_at_mutable_fail) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json("{\"foo\":5}"); + EXPECT_TRUE(document.is_object()); + const auto result = document.try_at("boo"); + EXPECT_FALSE(result); +} + +TEST(try_at_mutable_string_view) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json("{\"foo\":5}"); + EXPECT_TRUE(document.is_object()); + const sourcemeta::core::JSON::StringView key{"foo"}; + const auto result = document.try_at(key); + EXPECT_TRUE(result); + EXPECT_EQ(result->to_integer(), 5); + result->into(sourcemeta::core::JSON{7}); + EXPECT_EQ(document.at("foo").to_integer(), 7); +} + +TEST(try_at_mutable_with_hash) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json("{\"foo\":5}"); + EXPECT_TRUE(document.is_object()); + const auto result = document.try_at("foo", document.as_object().hash("foo")); + EXPECT_TRUE(result); + EXPECT_EQ(result->to_integer(), 5); + result->into(sourcemeta::core::JSON{7}); + EXPECT_EQ(document.at("foo").to_integer(), 7); +} + +TEST(try_at_mutable_with_hash_fail) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json("{\"foo\":5}"); + EXPECT_TRUE(document.is_object()); + const auto result = document.try_at("boo", document.as_object().hash("boo")); + EXPECT_FALSE(result); +} + +TEST(try_at_mutable_string_view_with_hash) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json("{\"foo\":5}"); + EXPECT_TRUE(document.is_object()); + const sourcemeta::core::JSON::StringView key{"foo"}; + const auto result = document.try_at(key, document.as_object().hash(key)); + EXPECT_TRUE(result); + EXPECT_EQ(result->to_integer(), 5); + result->into(sourcemeta::core::JSON{7}); + EXPECT_EQ(document.at("foo").to_integer(), 7); +} + TEST(unordered_set_with_custom_hash) { std::unordered_set> diff --git a/test/jsonld/jsonld_materialize_test.cc b/test/jsonld/jsonld_materialize_test.cc index 70254e4206..67acbe6e16 100644 --- a/test/jsonld/jsonld_materialize_test.cc +++ b/test/jsonld/jsonld_materialize_test.cc @@ -9,16 +9,16 @@ TEST(node_with_literal_property) { const auto instance = sourcemeta::core::parse_json(R"({ "name": "Sourcemeta" })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/org"}}); - map.emplace(sourcemeta::core::Pointer{"name"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back(sourcemeta::core::Pointer{"name"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -27,7 +27,8 @@ TEST(node_with_literal_property) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -36,17 +37,18 @@ TEST(literal_with_datatype) { const auto instance = sourcemeta::core::parse_json(R"({ "isbn": "978-0131103627" })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "urn:isbn:978-0131103627"}}); - map.emplace(sourcemeta::core::Pointer{"isbn"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/isbn", false}}, - .value = sourcemeta::core::JSONLDLiteral{ - .datatype = "http://www.w3.org/2001/XMLSchema#string"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"isbn"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/isbn", false}}, + .value = sourcemeta::core::JSONLDLiteral{ + .datatype = "http://www.w3.org/2001/XMLSchema#string"}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -60,7 +62,8 @@ TEST(literal_with_datatype) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -68,18 +71,19 @@ TEST(literal_with_datatype) { TEST(literal_with_language_and_direction) { const auto instance = sourcemeta::core::parse_json(R"({ "title": "مرحبا" })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"title"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{ - .language = "ar", - .direction = sourcemeta::core::JSONLDDirection::RTL}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"title"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{ + .language = "ar", + .direction = sourcemeta::core::JSONLDDirection::RTL}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -90,7 +94,8 @@ TEST(literal_with_language_and_direction) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -99,18 +104,19 @@ TEST(reference_property) { const auto instance = sourcemeta::core::parse_json(R"({ "currency": "USD" })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "urn:isbn:978-0131103627"}}); - map.emplace(sourcemeta::core::Pointer{"currency"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/priceCurrency", false}}, - .value = sourcemeta::core::JSONLDReference{ - .id = "https://www.iso.org/iso-4217/USD", - .types = {"https://schema.org/Currency"}}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"currency"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/priceCurrency", false}}, + .value = sourcemeta::core::JSONLDReference{ + .id = "https://www.iso.org/iso-4217/USD", + .types = {"https://schema.org/Currency"}}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -124,7 +130,8 @@ TEST(reference_property) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -133,27 +140,30 @@ TEST(ordered_collection_becomes_list) { const auto instance = sourcemeta::core::parse_json(R"({ "authors": [ "Ada", "Alan" ] })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/book"}}); - map.emplace(sourcemeta::core::Pointer{"authors"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/author", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::List}}); - map.emplace(sourcemeta::core::Pointer{"authors", 0}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDLiteral{ - .datatype = "http://www.w3.org/2001/XMLSchema#string"}}); - map.emplace(sourcemeta::core::Pointer{"authors", 1}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDLiteral{ - .datatype = "http://www.w3.org/2001/XMLSchema#string"}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/book"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"authors"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/author", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::List}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"authors", 0}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDLiteral{ + .datatype = "http://www.w3.org/2001/XMLSchema#string"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"authors", 1}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDLiteral{ + .datatype = "http://www.w3.org/2001/XMLSchema#string"}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -169,7 +179,8 @@ TEST(ordered_collection_becomes_list) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -178,23 +189,26 @@ TEST(unordered_collection_spreads_into_property) { const auto instance = sourcemeta::core::parse_json(R"({ "tags": [ "a", "b" ] })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"tags"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/keywords", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Set}}); - map.emplace(sourcemeta::core::Pointer{"tags", 0}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(sourcemeta::core::Pointer{"tags", 1}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"tags"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/keywords", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"tags", 0}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"tags", 1}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -203,7 +217,8 @@ TEST(unordered_collection_spreads_into_property) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -211,13 +226,13 @@ TEST(unordered_collection_spreads_into_property) { TEST(field_with_multiple_predicates) { const auto instance = sourcemeta::core::parse_json(R"({ "label": "Hi" })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace( + annotations.emplace_back( sourcemeta::core::Pointer{"label"}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/name", false}, @@ -232,7 +247,8 @@ TEST(field_with_multiple_predicates) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -241,20 +257,20 @@ TEST(reverse_edge) { const auto instance = sourcemeta::core::parse_json(R"({ "series": { "name": "Trilogy" } })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/book"}}); - map.emplace(sourcemeta::core::Pointer{"series"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/hasPart", true}}, - .value = sourcemeta::core::JSONLDNode{}}); - map.emplace(sourcemeta::core::Pointer{"series", "name"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/book"}}); + annotations.emplace_back(sourcemeta::core::Pointer{"series"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/hasPart", true}}, + .value = sourcemeta::core::JSONLDNode{}}); + annotations.emplace_back(sourcemeta::core::Pointer{"series", "name"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -267,7 +283,8 @@ TEST(reverse_edge) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -276,20 +293,22 @@ TEST(shared_blank_node_identifier_co_refers) { const auto instance = sourcemeta::core::parse_json(R"({ "billing": {}, "shipping": {} })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/order"}}); - map.emplace(sourcemeta::core::Pointer{"billing"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/billing", false}}, - .value = sourcemeta::core::JSONLDNode{.id = "_:address"}}); - map.emplace(sourcemeta::core::Pointer{"shipping"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/shipping", false}}, - .value = sourcemeta::core::JSONLDNode{.id = "_:address"}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/order"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"billing"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/billing", false}}, + .value = sourcemeta::core::JSONLDNode{.id = "_:address"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"shipping"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/shipping", false}}, + .value = sourcemeta::core::JSONLDNode{.id = "_:address"}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -299,7 +318,8 @@ TEST(shared_blank_node_identifier_co_refers) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -308,16 +328,17 @@ TEST(json_literal_preserved_verbatim) { const auto instance = sourcemeta::core::parse_json(R"({ "payload": { "a": [ 1, 2 ] } })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"payload"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/raw", false}}, - .value = sourcemeta::core::JSONLDLiteral{.json = true}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"payload"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/raw", false}}, + .value = sourcemeta::core::JSONLDLiteral{.json = true}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -328,7 +349,8 @@ TEST(json_literal_preserved_verbatim) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -337,27 +359,30 @@ TEST(nested_ordered_collection_is_list_of_lists) { const auto instance = sourcemeta::core::parse_json(R"({ "matrix": [ [ 5 ] ] })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"matrix"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/rows", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::List}}); - map.emplace(sourcemeta::core::Pointer{"matrix", 0}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::List}}); - map.emplace(sourcemeta::core::Pointer{"matrix", 0, 0}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDLiteral{ - .datatype = "http://www.w3.org/2001/XMLSchema#integer"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"matrix"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/rows", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::List}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"matrix", 0}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::List}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"matrix", 0, 0}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDLiteral{ + .datatype = "http://www.w3.org/2001/XMLSchema#integer"}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -379,7 +404,8 @@ TEST(nested_ordered_collection_is_list_of_lists) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -388,20 +414,20 @@ TEST(undescribed_object_with_described_child) { const auto instance = sourcemeta::core::parse_json( R"({ "name": "Doc", "meta": { "title": "T" } })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"name"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(sourcemeta::core::Pointer{"meta", "title"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/title", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back(sourcemeta::core::Pointer{"name"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back(sourcemeta::core::Pointer{"meta", "title"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/title", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -411,7 +437,8 @@ TEST(undescribed_object_with_described_child) { { "https://example.com/title": [ { "@value": "T" } ] } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -420,21 +447,21 @@ TEST(described_node_without_edge_is_standalone) { const auto instance = sourcemeta::core::parse_json(R"({ "extra": { "x": "v" } })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"extra"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/other"}}); - map.emplace(sourcemeta::core::Pointer{"extra", "x"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/x", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back(sourcemeta::core::Pointer{"extra"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/other"}}); + annotations.emplace_back(sourcemeta::core::Pointer{"extra", "x"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/x", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { "@id": "https://example.com/doc" }, @@ -444,7 +471,8 @@ TEST(described_node_without_edge_is_standalone) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -452,22 +480,23 @@ TEST(described_node_without_edge_is_standalone) { TEST(null_value_produces_no_triple) { const auto instance = sourcemeta::core::parse_json(R"({ "maybe": null })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"maybe"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back(sourcemeta::core::Pointer{"maybe"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { "@id": "https://example.com/doc" } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -476,21 +505,22 @@ TEST(named_graph) { const auto instance = sourcemeta::core::parse_json(R"({ "prov": { "who": "X" } })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"prov"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/statedIn", false}}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/graph", .graph = true}}); - map.emplace(sourcemeta::core::Pointer{"prov", "who"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"prov"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/statedIn", false}}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/graph", .graph = true}}); + annotations.emplace_back(sourcemeta::core::Pointer{"prov", "who"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -509,7 +539,8 @@ TEST(named_graph) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -517,13 +548,13 @@ TEST(named_graph) { TEST(scalar_root_reference) { const auto instance = sourcemeta::core::parse_json(R"("USD")"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDReference{ - .id = "https://www.iso.org/iso-4217/USD", - .types = {"https://schema.org/Currency"}}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDReference{ + .id = "https://www.iso.org/iso-4217/USD", + .types = {"https://schema.org/Currency"}}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -532,18 +563,20 @@ TEST(scalar_root_reference) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } TEST(empty_map_yields_empty_array) { const auto instance = sourcemeta::core::parse_json(R"({ "a": 1 })"); - const sourcemeta::core::JSONLDAnnotationMap map; + const sourcemeta::core::JSONLDAnnotationList annotations; const auto expected = sourcemeta::core::parse_json("[]"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -551,17 +584,17 @@ TEST(empty_map_yields_empty_array) { TEST(node_with_type) { const auto instance = sourcemeta::core::parse_json(R"({ "name": "Ada" })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/ada", - .types = {"https://schema.org/Person"}}}); - map.emplace(sourcemeta::core::Pointer{"name"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/ada", + .types = {"https://schema.org/Person"}}}); + annotations.emplace_back(sourcemeta::core::Pointer{"name"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -571,7 +604,8 @@ TEST(node_with_type) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -579,14 +613,14 @@ TEST(node_with_type) { TEST(node_with_multiple_types) { const auto instance = sourcemeta::core::parse_json(R"({})"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/thing", - .types = {"https://schema.org/Person", - "https://schema.org/Author"}}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/thing", + .types = {"https://schema.org/Person", + "https://schema.org/Author"}}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -595,7 +629,8 @@ TEST(node_with_multiple_types) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -604,17 +639,18 @@ TEST(reference_without_types) { const auto instance = sourcemeta::core::parse_json(R"({ "currency": "USD" })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/book"}}); - map.emplace(sourcemeta::core::Pointer{"currency"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/priceCurrency", false}}, - .value = sourcemeta::core::JSONLDReference{ - .id = "https://www.iso.org/iso-4217/USD"}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/book"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"currency"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/priceCurrency", false}}, + .value = sourcemeta::core::JSONLDReference{ + .id = "https://www.iso.org/iso-4217/USD"}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -625,7 +661,8 @@ TEST(reference_without_types) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -633,16 +670,17 @@ TEST(reference_without_types) { TEST(literal_with_language_only) { const auto instance = sourcemeta::core::parse_json(R"({ "title": "Hello" })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"title"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{.language = "en"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"title"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{.language = "en"}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -651,7 +689,8 @@ TEST(literal_with_language_only) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -659,18 +698,19 @@ TEST(literal_with_language_only) { TEST(literal_with_ltr_direction) { const auto instance = sourcemeta::core::parse_json(R"({ "title": "Hello" })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"title"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{ - .language = "en", - .direction = sourcemeta::core::JSONLDDirection::LTR}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"title"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{ + .language = "en", + .direction = sourcemeta::core::JSONLDDirection::LTR}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -681,7 +721,8 @@ TEST(literal_with_ltr_direction) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -689,16 +730,16 @@ TEST(literal_with_ltr_direction) { TEST(boolean_literal) { const auto instance = sourcemeta::core::parse_json(R"({ "active": true })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"active"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/active", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back(sourcemeta::core::Pointer{"active"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/active", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -707,7 +748,8 @@ TEST(boolean_literal) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -715,16 +757,16 @@ TEST(boolean_literal) { TEST(integer_literal_without_datatype) { const auto instance = sourcemeta::core::parse_json(R"({ "count": 42 })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"count"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/count", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back(sourcemeta::core::Pointer{"count"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/count", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -733,7 +775,8 @@ TEST(integer_literal_without_datatype) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -742,24 +785,26 @@ TEST(ordered_collection_of_nodes) { const auto instance = sourcemeta::core::parse_json(R"({ "authors": [ { "name": "Ada" } ] })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/book"}}); - map.emplace(sourcemeta::core::Pointer{"authors"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/author", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::List}}); - map.emplace(sourcemeta::core::Pointer{"authors", 0}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDNode{}}); - map.emplace(sourcemeta::core::Pointer{"authors", 0, "name"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/book"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"authors"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/author", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::List}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"authors", 0}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDNode{}}); + annotations.emplace_back(sourcemeta::core::Pointer{"authors", 0, "name"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -774,7 +819,8 @@ TEST(ordered_collection_of_nodes) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -783,31 +829,34 @@ TEST(unordered_collection_of_nodes) { const auto instance = sourcemeta::core::parse_json( R"({ "items": [ { "sku": "1" }, { "sku": "2" } ] })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/order"}}); - map.emplace(sourcemeta::core::Pointer{"items"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/item", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Set}}); - map.emplace(sourcemeta::core::Pointer{"items", 0}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDNode{}}); - map.emplace(sourcemeta::core::Pointer{"items", 0, "sku"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/sku", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(sourcemeta::core::Pointer{"items", 1}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDNode{}}); - map.emplace(sourcemeta::core::Pointer{"items", 1, "sku"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/sku", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/order"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"items"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/item", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"items", 0}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDNode{}}); + annotations.emplace_back(sourcemeta::core::Pointer{"items", 0, "sku"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/sku", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"items", 1}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDNode{}}); + annotations.emplace_back(sourcemeta::core::Pointer{"items", 1, "sku"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/sku", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -819,7 +868,8 @@ TEST(unordered_collection_of_nodes) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -827,17 +877,18 @@ TEST(unordered_collection_of_nodes) { TEST(empty_ordered_collection_is_rdf_nil) { const auto instance = sourcemeta::core::parse_json(R"({ "authors": [] })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/book"}}); - map.emplace(sourcemeta::core::Pointer{"authors"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/author", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::List}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/book"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"authors"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/author", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::List}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -846,7 +897,8 @@ TEST(empty_ordered_collection_is_rdf_nil) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -854,59 +906,229 @@ TEST(empty_ordered_collection_is_rdf_nil) { TEST(empty_unordered_collection_adds_no_property) { const auto instance = sourcemeta::core::parse_json(R"({ "tags": [] })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"tags"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/keywords", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Set}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"tags"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/keywords", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); const auto expected = sourcemeta::core::parse_json(R"([ { "@id": "https://example.com/doc" } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } -TEST(collection_with_undescribed_elements_skips_them) { +TEST(collection_defaults_undescribed_scalar_elements) { const auto instance = sourcemeta::core::parse_json( R"({ "authors": [ "Ada", "Unknown", "Alan" ] })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/book"}}); - map.emplace(sourcemeta::core::Pointer{"authors"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/author", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::List}}); - map.emplace(sourcemeta::core::Pointer{"authors", 0}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(sourcemeta::core::Pointer{"authors", 2}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/book"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"authors"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/author", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::List}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"authors", 0}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"authors", 2}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { "@id": "https://example.com/book", "https://schema.org/author": [ - { "@list": [ { "@value": "Ada" }, { "@value": "Alan" } ] } + { "@list": [ + { "@value": "Ada" }, { "@value": "Unknown" }, { "@value": "Alan" } + ] } + ] + } + ])"); + + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; + EXPECT_EQ(result, expected); + EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); +} + +TEST(unordered_collection_defaults_all_undescribed_elements) { + const auto instance = sourcemeta::core::parse_json( + R"({ "keywords": [ "reference", "standards", true, 5 ] })"); + + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/book"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"keywords"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/keywords", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + + const auto expected = sourcemeta::core::parse_json(R"([ + { + "@id": "https://example.com/book", + "https://schema.org/keywords": [ + { "@value": "reference" }, + { "@value": "standards" }, + { "@value": true }, + { "@value": 5 } + ] + } + ])"); + + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; + EXPECT_EQ(result, expected); + EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); +} + +TEST(index_collection_defaults_undescribed_members) { + const auto instance = sourcemeta::core::parse_json( + R"({ "identifiers": { "doi": "10.1000/1", "isbn": "978-0" } })"); + + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/book"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"identifiers"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/identifier", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Index}}); + + const auto expected = sourcemeta::core::parse_json(R"([ + { + "@id": "https://example.com/book", + "https://schema.org/identifier": [ + { "@value": "10.1000/1" }, + { "@value": "978-0" } + ] + } + ])"); + + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; + EXPECT_EQ(result, expected); + EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); +} + +TEST(collection_defaults_nested_undescribed_array_as_set) { + const auto instance = + sourcemeta::core::parse_json(R"({ "groups": [ [ "a", "b" ], "c" ] })"); + + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( + sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{.edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/doc"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"groups"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/member", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + + const auto expected = sourcemeta::core::parse_json(R"([ + { + "@id": "https://example.com/doc", + "https://example.com/member": [ + { "@value": "a" }, { "@value": "b" }, { "@value": "c" } ] } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; + EXPECT_EQ(result, expected); + EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); +} + +TEST(collection_undescribed_object_element_is_still_dropped) { + const auto instance = sourcemeta::core::parse_json( + R"({ "keywords": [ "reference", { "label": "extra" } ] })"); + + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/book"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"keywords"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/keywords", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + + const auto expected = sourcemeta::core::parse_json(R"([ + { + "@id": "https://example.com/book", + "https://schema.org/keywords": [ { "@value": "reference" } ] + } + ])"); + + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; + EXPECT_EQ(result, expected); + EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); +} + +TEST(collection_null_element_is_still_dropped) { + const auto instance = sourcemeta::core::parse_json( + R"({ "keywords": [ "reference", null, "standards" ] })"); + + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/book"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"keywords"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/keywords", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + + const auto expected = sourcemeta::core::parse_json(R"([ + { + "@id": "https://example.com/book", + "https://schema.org/keywords": [ + { "@value": "reference" }, { "@value": "standards" } + ] + } + ])"); + + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -915,36 +1137,42 @@ TEST(nested_unordered_collection_flattens) { const auto instance = sourcemeta::core::parse_json( R"({ "groups": [ [ "a", "b" ], [ "c" ] ] })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"groups"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/member", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Set}}); - map.emplace(sourcemeta::core::Pointer{"groups", 0}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Set}}); - map.emplace(sourcemeta::core::Pointer{"groups", 1}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Set}}); - map.emplace(sourcemeta::core::Pointer{"groups", 0, 0}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(sourcemeta::core::Pointer{"groups", 0, 1}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(sourcemeta::core::Pointer{"groups", 1, 0}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"groups"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/member", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"groups", 0}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"groups", 1}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"groups", 0, 0}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"groups", 0, 1}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"groups", 1, 0}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -955,7 +1183,8 @@ TEST(nested_unordered_collection_flattens) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -964,30 +1193,31 @@ TEST(array_root_spreads_into_default_graph) { const auto instance = sourcemeta::core::parse_json(R"([ { "name": "A" }, { "name": "B" } ])"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Set}}); - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( + sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + annotations.emplace_back( sourcemeta::core::Pointer{0}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/a"}}); - map.emplace(sourcemeta::core::Pointer{0, "name"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace( + annotations.emplace_back(sourcemeta::core::Pointer{0, "name"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( sourcemeta::core::Pointer{1}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/b"}}); - map.emplace(sourcemeta::core::Pointer{1, "name"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back(sourcemeta::core::Pointer{1, "name"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -1000,7 +1230,8 @@ TEST(array_root_spreads_into_default_graph) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1008,22 +1239,26 @@ TEST(array_root_spreads_into_default_graph) { TEST(root_collection_of_literals_is_dropped) { const auto instance = sourcemeta::core::parse_json(R"([ "a", "b" ])"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Set}}); - map.emplace(sourcemeta::core::Pointer{0}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(sourcemeta::core::Pointer{1}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( + sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + annotations.emplace_back( + sourcemeta::core::Pointer{0}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + sourcemeta::core::Pointer{1}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json("[]"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1032,24 +1267,26 @@ TEST(root_collection_keeps_only_node_elements) { const auto instance = sourcemeta::core::parse_json(R"([ { "name": "A" }, "loose" ])"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Set}}); - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( + sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + annotations.emplace_back( sourcemeta::core::Pointer{0}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/a"}}); - map.emplace(sourcemeta::core::Pointer{0, "name"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(sourcemeta::core::Pointer{1}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back(sourcemeta::core::Pointer{0, "name"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + sourcemeta::core::Pointer{1}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -1058,7 +1295,8 @@ TEST(root_collection_keeps_only_node_elements) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1066,14 +1304,16 @@ TEST(root_collection_keeps_only_node_elements) { TEST(scalar_root_literal_is_dropped) { const auto instance = sourcemeta::core::parse_json(R"("hello")"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( + sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json("[]"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1081,23 +1321,25 @@ TEST(scalar_root_literal_is_dropped) { TEST(collection_descriptor_on_non_array_is_dropped) { const auto instance = sourcemeta::core::parse_json(R"({ "x": "scalar" })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"x"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/x", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::List}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"x"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/x", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::List}}); const auto expected = sourcemeta::core::parse_json(R"([ { "@id": "https://example.com/doc" } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1109,34 +1351,37 @@ TEST(output_is_a_fixed_point_of_expansion) { "books": [ "Notes" ] })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/ada", - .types = {"https://schema.org/Person"}}}); - map.emplace(sourcemeta::core::Pointer{"name"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(sourcemeta::core::Pointer{"homepage"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/url", false}}, - .value = sourcemeta::core::JSONLDReference{ - .id = "https://ada.example"}}); - map.emplace(sourcemeta::core::Pointer{"books"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/author", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::List}}); - map.emplace(sourcemeta::core::Pointer{"books", 0}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDLiteral{ - .datatype = "http://www.w3.org/2001/XMLSchema#string"}}); - - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/ada", + .types = {"https://schema.org/Person"}}}); + annotations.emplace_back(sourcemeta::core::Pointer{"name"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back(sourcemeta::core::Pointer{"homepage"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/url", false}}, + .value = sourcemeta::core::JSONLDReference{ + .id = "https://ada.example"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"books"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/author", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::List}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"books", 0}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDLiteral{ + .datatype = "http://www.w3.org/2001/XMLSchema#string"}}); + + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); // Expanded form is a fixed point of expansion, so re-expanding the output of @@ -1147,17 +1392,17 @@ TEST(output_is_a_fixed_point_of_expansion) { TEST(node_descriptor_on_scalar_ignores_value) { const auto instance = sourcemeta::core::parse_json(R"({ "x": "scalar" })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"x"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://example.com/x", false}}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/other"}}); + annotations.emplace_back(sourcemeta::core::Pointer{"x"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/x", false}}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/other"}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -1166,7 +1411,8 @@ TEST(node_descriptor_on_scalar_ignores_value) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1177,16 +1423,16 @@ TEST(weak_node_with_literal_property) { const sourcemeta::core::JSON::String name_key{"name"}; - sourcemeta::core::JSONLDWeakAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDWeakAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::WeakPointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/org"}}); - map.emplace(sourcemeta::core::WeakPointer{std::cref(name_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back(sourcemeta::core::WeakPointer{std::cref(name_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -1195,7 +1441,8 @@ TEST(weak_node_with_literal_property) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1206,18 +1453,19 @@ TEST(weak_reference_property) { const sourcemeta::core::JSON::String currency_key{"currency"}; - sourcemeta::core::JSONLDWeakAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDWeakAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::WeakPointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "urn:isbn:978-0131103627"}}); - map.emplace(sourcemeta::core::WeakPointer{std::cref(currency_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/priceCurrency", false}}, - .value = sourcemeta::core::JSONLDReference{ - .id = "https://www.iso.org/iso-4217/USD", - .types = {"https://schema.org/Currency"}}}); + annotations.emplace_back( + sourcemeta::core::WeakPointer{std::cref(currency_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/priceCurrency", false}}, + .value = sourcemeta::core::JSONLDReference{ + .id = "https://www.iso.org/iso-4217/USD", + .types = {"https://schema.org/Currency"}}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -1231,7 +1479,8 @@ TEST(weak_reference_property) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1242,27 +1491,30 @@ TEST(weak_ordered_collection_becomes_list) { const sourcemeta::core::JSON::String authors_key{"authors"}; - sourcemeta::core::JSONLDWeakAnnotationMap map; - map.emplace(sourcemeta::core::WeakPointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/book"}}); - map.emplace(sourcemeta::core::WeakPointer{std::cref(authors_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/author", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::List}}); - map.emplace(sourcemeta::core::WeakPointer{std::cref(authors_key), 0}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDLiteral{ - .datatype = "http://www.w3.org/2001/XMLSchema#string"}}); - map.emplace(sourcemeta::core::WeakPointer{std::cref(authors_key), 1}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDLiteral{ - .datatype = "http://www.w3.org/2001/XMLSchema#string"}}); + sourcemeta::core::JSONLDWeakAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::WeakPointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/book"}}); + annotations.emplace_back( + sourcemeta::core::WeakPointer{std::cref(authors_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/author", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::List}}); + annotations.emplace_back( + sourcemeta::core::WeakPointer{std::cref(authors_key), 0}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDLiteral{ + .datatype = "http://www.w3.org/2001/XMLSchema#string"}}); + annotations.emplace_back( + sourcemeta::core::WeakPointer{std::cref(authors_key), 1}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDLiteral{ + .datatype = "http://www.w3.org/2001/XMLSchema#string"}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -1278,7 +1530,8 @@ TEST(weak_ordered_collection_becomes_list) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1290,17 +1543,17 @@ TEST(weak_reverse_edge) { const sourcemeta::core::JSON::String series_key{"series"}; const sourcemeta::core::JSON::String name_key{"name"}; - sourcemeta::core::JSONLDWeakAnnotationMap map; - map.emplace(sourcemeta::core::WeakPointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/book"}}); - map.emplace(sourcemeta::core::WeakPointer{std::cref(series_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/hasPart", true}}, - .value = sourcemeta::core::JSONLDNode{}}); - map.emplace( + sourcemeta::core::JSONLDWeakAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::WeakPointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/book"}}); + annotations.emplace_back(sourcemeta::core::WeakPointer{std::cref(series_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/hasPart", true}}, + .value = sourcemeta::core::JSONLDNode{}}); + annotations.emplace_back( sourcemeta::core::WeakPointer{std::cref(series_key), std::cref(name_key)}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/name", false}}, @@ -1317,7 +1570,8 @@ TEST(weak_reverse_edge) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1328,30 +1582,33 @@ TEST(weak_array_root_spreads_into_default_graph) { const sourcemeta::core::JSON::String name_key{"name"}; - sourcemeta::core::JSONLDWeakAnnotationMap map; - map.emplace(sourcemeta::core::WeakPointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Set}}); - map.emplace( + sourcemeta::core::JSONLDWeakAnnotationList annotations; + annotations.emplace_back( + sourcemeta::core::WeakPointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + annotations.emplace_back( sourcemeta::core::WeakPointer{0}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/a"}}); - map.emplace(sourcemeta::core::WeakPointer{0, std::cref(name_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace( + annotations.emplace_back( + sourcemeta::core::WeakPointer{0, std::cref(name_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( sourcemeta::core::WeakPointer{1}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/b"}}); - map.emplace(sourcemeta::core::WeakPointer{1, std::cref(name_key)}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/name", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + sourcemeta::core::WeakPointer{1, std::cref(name_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -1364,7 +1621,8 @@ TEST(weak_array_root_spreads_into_default_graph) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1373,13 +1631,13 @@ TEST(language_container) { const auto instance = sourcemeta::core::parse_json( R"({ "label": { "en": "Colour", "fr": [ "Couleur", "Teinte" ], "@none": "Color" } })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/thing"}}); - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/thing"}}); + annotations.emplace_back( sourcemeta::core::Pointer{"label"}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/name", false}}, @@ -1398,7 +1656,8 @@ TEST(language_container) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1406,13 +1665,13 @@ TEST(language_container) { TEST(language_container_empty) { const auto instance = sourcemeta::core::parse_json(R"({ "label": {} })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/thing"}}); - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/thing"}}); + annotations.emplace_back( sourcemeta::core::Pointer{"label"}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/name", false}}, @@ -1423,7 +1682,8 @@ TEST(language_container_empty) { { "@id": "https://example.com/thing" } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1432,13 +1692,13 @@ TEST(language_container_null_value) { const auto instance = sourcemeta::core::parse_json( R"({ "label": { "en": null, "fr": "Bonjour" } })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/thing"}}); - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/thing"}}); + annotations.emplace_back( sourcemeta::core::Pointer{"label"}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/name", false}}, @@ -1454,7 +1714,8 @@ TEST(language_container_null_value) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1463,13 +1724,13 @@ TEST(language_container_null_array_element) { const auto instance = sourcemeta::core::parse_json( R"({ "label": { "en": [ "Hi", null, "Hello" ] } })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/thing"}}); - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/thing"}}); + annotations.emplace_back( sourcemeta::core::Pointer{"label"}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/name", false}}, @@ -1486,7 +1747,8 @@ TEST(language_container_null_array_element) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1495,13 +1757,13 @@ TEST(language_container_null_none) { const auto instance = sourcemeta::core::parse_json( R"({ "label": { "@none": null, "en": "Colour" } })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/thing"}}); - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/thing"}}); + annotations.emplace_back( sourcemeta::core::Pointer{"label"}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/name", false}}, @@ -1517,7 +1779,8 @@ TEST(language_container_null_none) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1526,13 +1789,13 @@ TEST(language_container_all_null) { const auto instance = sourcemeta::core::parse_json( R"({ "label": { "en": null, "fr": [ null ] } })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/thing"}}); - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/thing"}}); + annotations.emplace_back( sourcemeta::core::Pointer{"label"}, sourcemeta::core::JSONLDDescriptor{ .edges = {{"https://schema.org/name", false}}, @@ -1543,7 +1806,8 @@ TEST(language_container_all_null) { { "@id": "https://example.com/thing" } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1552,35 +1816,38 @@ TEST(index_container_of_nodes) { const auto instance = sourcemeta::core::parse_json( R"({ "posts": { "2023": { "title": "A" }, "2024": { "title": "B" } } })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/blog"}}); - map.emplace(sourcemeta::core::Pointer{"posts"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/blogPost", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Index}}); - map.emplace(sourcemeta::core::Pointer{"posts", "2023"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/post/2023"}}); - map.emplace(sourcemeta::core::Pointer{"posts", "2023", "title"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/headline", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(sourcemeta::core::Pointer{"posts", "2024"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/post/2024"}}); - map.emplace(sourcemeta::core::Pointer{"posts", "2024", "title"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/headline", false}}, - .value = sourcemeta::core::JSONLDLiteral{}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/blog"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"posts"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/blogPost", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Index}}); + annotations.emplace_back(sourcemeta::core::Pointer{"posts", "2023"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/post/2023"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"posts", "2023", "title"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/headline", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back(sourcemeta::core::Pointer{"posts", "2024"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/post/2024"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"posts", "2024", "title"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/headline", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -1598,7 +1865,8 @@ TEST(index_container_of_nodes) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1607,23 +1875,26 @@ TEST(index_container_of_literals) { const auto instance = sourcemeta::core::parse_json( R"({ "scores": { "math": 90, "art": 80 } })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/report"}}); - map.emplace(sourcemeta::core::Pointer{"scores"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/value", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Index}}); - map.emplace(sourcemeta::core::Pointer{"scores", "math"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(sourcemeta::core::Pointer{"scores", "art"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/report"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"scores"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/value", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Index}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"scores", "math"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"scores", "art"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -1632,7 +1903,8 @@ TEST(index_container_of_literals) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1641,28 +1913,32 @@ TEST(index_container_with_nested_set) { const auto instance = sourcemeta::core::parse_json(R"({ "groups": { "a": [ "x", "y" ] } })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace( + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back( sourcemeta::core::Pointer{}, sourcemeta::core::JSONLDDescriptor{.edges = {}, .value = sourcemeta::core::JSONLDNode{ .id = "https://example.com/doc"}}); - map.emplace(sourcemeta::core::Pointer{"groups"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/item", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Index}}); - map.emplace(sourcemeta::core::Pointer{"groups", "a"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Set}}); - map.emplace(sourcemeta::core::Pointer{"groups", "a", 0}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); - map.emplace(sourcemeta::core::Pointer{"groups", "a", 1}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"groups"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/item", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Index}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"groups", "a"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"groups", "a", 0}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"groups", "a", 1}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, .value = sourcemeta::core::JSONLDLiteral{}}); const auto expected = sourcemeta::core::parse_json(R"([ { @@ -1671,7 +1947,8 @@ TEST(index_container_with_nested_set) { } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); } @@ -1679,23 +1956,97 @@ TEST(index_container_with_nested_set) { TEST(index_container_empty) { const auto instance = sourcemeta::core::parse_json(R"({ "posts": {} })"); - sourcemeta::core::JSONLDAnnotationMap map; - map.emplace(sourcemeta::core::Pointer{}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {}, - .value = sourcemeta::core::JSONLDNode{ - .id = "https://example.com/blog"}}); - map.emplace(sourcemeta::core::Pointer{"posts"}, - sourcemeta::core::JSONLDDescriptor{ - .edges = {{"https://schema.org/blogPost", false}}, - .value = sourcemeta::core::JSONLDCollection{ - .container = sourcemeta::core::JSONLDContainer::Index}}); + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/blog"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"posts"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/blogPost", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Index}}); const auto expected = sourcemeta::core::parse_json(R"([ { "@id": "https://example.com/blog" } ])"); - const auto result{sourcemeta::core::jsonld_materialize(instance, map)}; + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; + EXPECT_EQ(result, expected); + EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); +} + +TEST(weak_collection_defaults_undescribed_scalar_elements) { + const auto instance = sourcemeta::core::parse_json( + R"({ "keywords": [ "reference", "standards" ] })"); + + const sourcemeta::core::JSON::String keywords_key{"keywords"}; + + sourcemeta::core::JSONLDWeakAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::WeakPointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/book"}}); + annotations.emplace_back( + sourcemeta::core::WeakPointer{std::cref(keywords_key)}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/keywords", false}}, + .value = sourcemeta::core::JSONLDCollection{ + .container = sourcemeta::core::JSONLDContainer::Set}}); + + const auto expected = sourcemeta::core::parse_json(R"([ + { + "@id": "https://example.com/book", + "https://schema.org/keywords": [ + { "@value": "reference" }, { "@value": "standards" } + ] + } + ])"); + + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; + EXPECT_EQ(result, expected); + EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); +} + +TEST(duplicate_annotations_first_one_wins) { + const auto instance = sourcemeta::core::parse_json(R"({ "name": "Ada" })"); + + sourcemeta::core::JSONLDAnnotationList annotations; + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/person"}}); + annotations.emplace_back(sourcemeta::core::Pointer{"name"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://schema.org/name", false}}, + .value = sourcemeta::core::JSONLDLiteral{}}); + annotations.emplace_back(sourcemeta::core::Pointer{}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {}, + .value = sourcemeta::core::JSONLDNode{ + .id = "https://example.com/ignored"}}); + annotations.emplace_back( + sourcemeta::core::Pointer{"name"}, + sourcemeta::core::JSONLDDescriptor{ + .edges = {{"https://example.com/ignored", false}}, + .value = sourcemeta::core::JSONLDLiteral{ + .datatype = "https://example.com/ignored"}}); + + const auto expected = sourcemeta::core::parse_json(R"([ + { + "@id": "https://example.com/person", + "https://schema.org/name": [ { "@value": "Ada" } ] + } + ])"); + + const auto result{ + sourcemeta::core::jsonld_materialize(instance, annotations)}; EXPECT_EQ(result, expected); EXPECT_TRUE(sourcemeta::core::jsonld_is_expanded(result)); }