44#include < sourcemeta/core/jsonpointer.h>
55#include < sourcemeta/core/uri.h>
66
7- #include < algorithm> // std::ranges::sort, std::ranges::any_of
8- #include < functional> // std::ref
9- #include < tuple> // std::tie
10- #include < unordered_map> // std::unordered_map
11- #include < utility> // std::move
12- #include < variant> // std::variant, std::get, std::holds_alternative
13- #include < vector> // std::vector
7+ #include < algorithm> // std::ranges::sort, std::ranges::any_of
8+ #include < functional> // std::ref
9+ #include < tuple> // std::tie
10+ #include < utility> // std::move
11+ #include < variant> // std::variant, std::get, std::holds_alternative
12+ #include < vector> // std::vector
1413
1514namespace {
1615
17- // The JSON-LD facts accumulated at one instance location, before a descriptor
18- // kind is derived from the instance value shape.
19- struct Facts {
20- std::vector<sourcemeta::core::JSONLDEdge> edges;
21- std::vector<sourcemeta::core::JSON ::String> types;
22- };
23-
24- using Accumulator = std::unordered_map<sourcemeta::core::WeakPointer, Facts,
25- sourcemeta::core::WeakPointer::Hasher>;
26-
2716// Default the undescribed elements of a collection: a scalar becomes a native
2817// literal, and a nested array becomes an inner (unordered) collection, whose
2918// own elements are filled the same way. As unordered collections, nested arrays
30- // flatten into their parent during materialization. Elements that carry their
31- // own annotations already have an accumulator entry and are left untouched.
19+ // flatten into their parent during materialization. Elements that already carry
20+ // a descriptor are left untouched.
3221auto fill_collection (sourcemeta::core::JSONLDWeakAnnotationMap &map,
33- const Accumulator &accumulator,
3422 const sourcemeta::core::WeakPointer &pointer,
3523 const sourcemeta::core::JSON &array) -> void {
3624 for (std::size_t index = 0 ; index < array.size (); index += 1 ) {
3725 auto element_pointer{pointer};
3826 element_pointer.push_back (index);
39- if (accumulator .contains (element_pointer)) {
27+ if (map .contains (element_pointer)) {
4028 continue ;
4129 }
4230
@@ -46,7 +34,7 @@ auto fill_collection(sourcemeta::core::JSONLDWeakAnnotationMap &map,
4634 element_pointer,
4735 sourcemeta::core::JSONLDDescriptor{
4836 .edges = {}, .value = sourcemeta::core::JSONLDCollection{}});
49- fill_collection (map, accumulator, element_pointer, element);
37+ fill_collection (map, element_pointer, element);
5038 } else if (!element.is_object ()) {
5139 map.emplace (std::move (element_pointer),
5240 sourcemeta::core::JSONLDDescriptor{
@@ -99,7 +87,12 @@ auto resolve(const sourcemeta::core::JSON &instance,
9987 const sourcemeta::blaze::SimpleOutput &output)
10088 -> std::variant<sourcemeta::core::JSONLDWeakAnnotationMap,
10189 sourcemeta::blaze::JSONLDResolutionError> {
102- Accumulator accumulator;
90+ // Accumulate straight into the descriptor map so that each instance location
91+ // is hashed once, rather than once into a scratch map and again into the
92+ // output. Edges and provisional types land on the default node descriptor;
93+ // the second pass settles the descriptor kind once the value shape is known.
94+ sourcemeta::core::JSONLDWeakAnnotationMap map;
95+ map.reserve (output.annotations ().size ());
10396
10497 for (const auto &[location, values] : output.annotations ()) {
10598 if (location.evaluate_path .empty ()) {
@@ -108,13 +101,13 @@ auto resolve(const sourcemeta::core::JSON &instance,
108101
109102 const auto &keyword{location.evaluate_path .back ().to_property ()};
110103 // Only the keywords resolved below may contribute facts. An unhandled
111- // keyword must not create an accumulator entry, as the empty facts would
112- // otherwise materialize a spurious node or literal descriptor
104+ // keyword must not create a map entry, as the empty facts would otherwise
105+ // materialize a spurious node or literal descriptor
113106 if (keyword != " x-jsonld-id" && keyword != " x-jsonld-type" ) {
114107 continue ;
115108 }
116109
117- auto &facts{accumulator [location.instance_location ]};
110+ auto &descriptor{map [location.instance_location ]};
118111 if (keyword == " x-jsonld-id" ) {
119112 for (const auto &value : values) {
120113 if (!is_iri_value (value)) {
@@ -125,43 +118,49 @@ auto resolve(const sourcemeta::core::JSON &instance,
125118 .message = " The value of x-jsonld-id must be an absolute IRI" };
126119 }
127120
128- add_edge (facts .edges , value.to_string (), false );
121+ add_edge (descriptor .edges , value.to_string (), false );
129122 }
130123 } else if (keyword == " x-jsonld-type" ) {
124+ auto &types{
125+ std::get<sourcemeta::core::JSONLDNode>(descriptor.value ).types };
131126 for (const auto &value : values) {
132127 if (value.is_array ()) {
133128 for (const auto &element : value.as_array ()) {
134129 if (!is_iri_value (element)) {
135130 return type_iri_error (location.instance_location );
136131 }
137132
138- add_type (facts. types , element.to_string ());
133+ add_type (types, element.to_string ());
139134 }
140135 } else {
141136 if (!is_iri_value (value)) {
142137 return type_iri_error (location.instance_location );
143138 }
144139
145- add_type (facts. types , value.to_string ());
140+ add_type (types, value.to_string ());
146141 }
147142 }
148143 }
149144 }
150145
151- sourcemeta::core::JSONLDWeakAnnotationMap map;
152- for (auto &[pointer, facts] : accumulator) {
153- std::ranges::sort (facts.edges ,
146+ // Settle each descriptor in place, so no location is hashed a second time,
147+ // and note the array locations whose undescribed elements must be filled once
148+ // the map is no longer being iterated.
149+ std::vector<sourcemeta::core::WeakPointer> collections;
150+ for (auto &[pointer, descriptor] : map) {
151+ std::ranges::sort (descriptor.edges ,
154152 [](const sourcemeta::core::JSONLDEdge &left,
155153 const sourcemeta::core::JSONLDEdge &right) -> bool {
156154 return std::tie (left.predicate , left.reverse ) <
157155 std::tie (right.predicate , right.reverse );
158156 });
159- std::ranges::sort (facts.types );
157+ auto &types{std::get<sourcemeta::core::JSONLDNode>(descriptor.value ).types };
158+ std::ranges::sort (types);
160159
161160 const auto &value{sourcemeta::core::get (instance, pointer)};
162161
163162 // A type denotes an rdf:type, which only a node can carry
164- if (!value.is_object () && !facts. types .empty ()) {
163+ if (!value.is_object () && !types.empty ()) {
165164 return sourcemeta::blaze::JSONLDResolutionError{
166165 .instance_location = sourcemeta::core::to_pointer (pointer),
167166 .facet = sourcemeta::blaze::JSONLDFacet::Type,
@@ -173,7 +172,7 @@ auto resolve(const sourcemeta::core::JSON &instance,
173172 // an array element (an index token) inherits the enclosing edge, so a
174173 // predicate on either is an error. A type on such a position is fine when
175174 // it is a node.
176- if (!facts .edges .empty () &&
175+ if (!descriptor .edges .empty () &&
177176 (pointer.empty () || !pointer.back ().is_property ())) {
178177 return sourcemeta::blaze::JSONLDResolutionError{
179178 .instance_location = sourcemeta::core::to_pointer (pointer),
@@ -185,22 +184,18 @@ auto resolve(const sourcemeta::core::JSON &instance,
185184 " element" };
186185 }
187186
188- sourcemeta::core::JSONLDDescriptor descriptor;
189- descriptor.edges = std::move (facts.edges );
190- if (value.is_object ()) {
191- descriptor.value =
192- sourcemeta::core::JSONLDNode{.types = std::move (facts.types )};
193- } else if (value.is_array ()) {
187+ // The descriptor already carries a node with the collected types; only the
188+ // non-object shapes need their kind corrected.
189+ if (value.is_array ()) {
194190 descriptor.value = sourcemeta::core::JSONLDCollection{};
195- } else {
191+ collections.push_back (pointer);
192+ } else if (!value.is_object ()) {
196193 descriptor.value = sourcemeta::core::JSONLDLiteral{};
197194 }
195+ }
198196
199- map.emplace (pointer, std::move (descriptor));
200-
201- if (value.is_array ()) {
202- fill_collection (map, accumulator, pointer, value);
203- }
197+ for (const auto &pointer : collections) {
198+ fill_collection (map, pointer, sourcemeta::core::get (instance, pointer));
204199 }
205200
206201 return map;
0 commit comments