示例#1
0
        /// <summary>
        /// Implements the JSON-LD Framing Algorithm
        /// </summary>
        /// <param name="state"></param>
        /// <param name="subjects"></param>
        /// <param name="frameObjectOrArray"></param>
        /// <param name="parent"></param>
        /// <param name="activeProperty"></param>
        /// <param name="ordered"></param>
        /// <param name="idStack"></param>
        /// <param name="processingMode"></param>
        public static void ProcessFrame(FramingState state, List <string> subjects, JToken frameObjectOrArray,
                                        JToken parent,
                                        string activeProperty, bool ordered = false, Stack <string> idStack = null,
                                        JsonLdProcessingMode processingMode = JsonLdProcessingMode.JsonLd11)
        {
            // Stack to track circular references when processing embedded nodes
            if (idStack == null)
            {
                idStack = new Stack <string>();
            }

            // 1 - If frame is an array, set frame to the first member of the array, which must be a valid frame.
            if (frameObjectOrArray is JArray frameArray)
            {
                frameObjectOrArray = frameArray.Count == 0 ? new JObject() : frameArray[0];
                ValidateFrame(frameObjectOrArray);
            }

            var frame = frameObjectOrArray as JObject;

            // 2 - Initialize flags embed, explicit, and requireAll from object embed flag, explicit inclusion flag, and require all flag in state overriding from any property values for @embed, @explicit, and @requireAll in frame.
            var embed        = GetEmbedOption(frame, state.Embed, processingMode);
            var explicitFlag = GetBooleanOption(frame, "@explicit", state.ExplicitInclusion);
            var requireAll   = GetBooleanOption(frame, "@requireAll", state.RequireAll);

            // 3 - Create a list of matched subjects by filtering subjects against frame using the Frame Matching algorithm with state, subjects, frame, and requireAll.
            var matchedSubjects = MatchFrame(state, subjects, frame, requireAll);

            // 5 - For each id and associated node object node from the set of matched subjects, ordered lexicographically by id if the optional ordered flag is true:
            var matches = (IEnumerable <KeyValuePair <string, JObject> >)matchedSubjects;

            if (ordered)
            {
                matches = matches.OrderBy(x => x.Key, StringComparer.Ordinal);
            }
            foreach (var match in matches)
            {
                var id   = match.Key;
                var node = match.Value;

                // Set up tracking for embedded nodes, clearing the value for each top-level property
                state.TrackEmbeddedNodes(activeProperty == null);

                // 4.1 - Initialize output to a new dictionary with @id and id.
                var output = new JObject(new JProperty("@id", id));

                // 4.2 - If the embedded flag in state is false and there is an existing embedded node in parent associated with graph name and id in state, do not perform additional processing for this node.
                if (!state.Embedded && state.HasEmbeddedNode(id))
                {
                    continue;
                }

                // 4.3 - Otherwise, if the embedded flag in state is true and either embed is @never or if a circular reference would be created by an embed, add output to parent and do not perform additional processing for this node.
                if (state.Embedded && (embed == JsonLdEmbed.Never || idStack.Contains(id)))
                {
                    FramingAppend(parent, output, activeProperty);
                    continue;
                }

                // 4.4 - Otherwise, if the embedded flag in state is true, embed is @once, and there is an existing embedded node in parent associated with graph name and id in state, add output to parent and do not perform additional processing for this node.
                if (state.Embedded && (embed == JsonLdEmbed.First || embed == JsonLdEmbed.Once) && state.HasEmbeddedNode(id))
                {
                    FramingAppend(parent, output, activeProperty);
                    continue;
                }

                if (embed == JsonLdEmbed.Last)
                {
                    if (state.HasEmbeddedNode(id))
                    {
                        RemoveEmbed(state, id);
                    }
                }

                state.AddEmbeddedNode(id, parent, activeProperty);

                idStack.Push(id);
                // 4.5 - If graph map in state has an entry for id:
                if (state.GraphMap.ContainsKey(id))
                {
                    bool    recurse  = false;
                    JObject subframe = null;
                    // 4.5.1 - If frame does not have the key @graph, set recurse to true, unless graph name in state is @merged and set subframe to a new empty dictionary.
                    if (!frame.ContainsKey("@graph"))
                    {
                        recurse  = !state.GraphName.Equals("@merged");
                        subframe = new JObject();
                    }
                    // 4.5.2 - Otherwise, set subframe to the first entry for @graph in frame, or a new empty dictionary, if it does not exist, and set recurse to true, unless id is @merged or @default.
                    else
                    {
                        //var graphEntry = (frame["@graph"] as JArray);
                        //if (graphEntry == null)
                        //{
                        //    graphEntry = new JArray(new JObject());
                        //    frameObjectOrArray["@graph"] = graphEntry;
                        //}
                        //else if (graphEntry.Count == 0)
                        //{
                        //    graphEntry.Add(new JObject());
                        //}
                        // subframe = graphEntry[0] as JObject;
                        if (frame.ContainsKey("@graph"))
                        {
                            subframe = frame["@graph"][0] as JObject;
                        }
                        else
                        {
                            subframe = new JObject();
                        }

                        recurse = !(id.Equals("@merged") || id.Equals("@default"));
                    }

                    // 4.5.3 - If recurse is true:
                    if (recurse)
                    {
                        // 4.5.3.1 - Set the value of graph name in state to id.
                        state.GraphStack.Push(state
                                              .GraphName); // KA: Using stack to track current graph instead of copying state
                        state.GraphName = id;
                        // 4.5.3.2 - Set the value of embedded flag in state to false.
                        state.Embedded = false;
                        // 4.5.3.3 - Invoke the algorithm using a copy of state with the value of graph name set to id and the value of embedded flag set to false,
                        // the keys from the graph map in state associated with id as subjects, subframe as frame, output as parent, and @graph as active property.
                        ProcessFrame(state,
                                     state.Subjects.Properties().Select(p => p.Name).ToList(),
                                     subframe, output, "@graph", ordered, idStack);
                        // Pop the value from graph stack in state and set graph name in state back to that value.
                        state.GraphName = state.GraphStack.Pop();
                    }
                }

                // 4.6  - If frame has an @included entry, invoke the algorithm using a copy of state with the value of embedded flag set to false, subjects, frame, output as parent, and @included as active property.
                if (frame.ContainsKey("@included"))
                {
                    var oldEmbedded = state.Embedded;
                    state.Embedded = false;
                    var includeFrame = frame["@included"];
                    ProcessFrame(state, subjects, includeFrame, output, "@included", ordered, idStack);
                    state.Embedded = oldEmbedded;
                }

                // 4.7 - For each property and objects in node, ordered by property:
                var nodeProperties = node.Properties();
                if (ordered)
                {
                    nodeProperties = nodeProperties.OrderBy(p => p.Name, StringComparer.Ordinal);
                }
                foreach (var p in nodeProperties)
                {
                    var property = p.Name;
                    var objects  = p.Value as JArray;

                    // 4.7.1 - If property is a keyword, add property and objects to output.
                    if (JsonLdUtils.IsKeyword(property))
                    {
                        output[property] = p.Value;
                        continue;
                    }

                    // 4.7.2 - Otherwise, if property is not in frame, and explicit is true, processors must not add any values for property to output, and the following steps are skipped.
                    if (!frame.ContainsKey(property) && explicitFlag)
                    {
                        continue;
                    }

                    // 4.7.3 - For each item in objects:
                    foreach (var item in objects)
                    {
                        // 4.7.3.1 - If item is a dictionary with the property @list,
                        // then each listitem in the list is processed in sequence and
                        // added to a new list dictionary in output:
                        if (JsonLdUtils.IsListObject(item))
                        {
                            var list = new JObject();
                            output[property] =
                                new JArray(list); // KA: Not sure what the correct key is for the list object
                            foreach (var listItem in item["@list"] as JArray)
                            {
                                // 4.7.3.1.1 - If listitem is a node reference, invoke the recursive algorithm using state,
                                // the value of @id from listitem as the sole member of a new subjects array,
                                // the first value from @list in frame as frame, list as parent,
                                // and @list as active property.
                                // If frame does not exist, create a new frame using a new dictionary
                                // with properties for @embed, @explicit and @requireAll taken from embed, explicit and requireAll.
                                if (JsonLdUtils.IsNodeReference(listItem))
                                {
                                    JToken listFrame = null;
                                    if (frame.ContainsKey(property) && frame[property] is JArray fpArray &&
                                        fpArray.Count > 0 && fpArray[0] is JObject subframe &&
                                        subframe.ContainsKey("@list"))
                                    {
                                        listFrame = subframe["@list"] as JArray;
                                    }
                                    if (listFrame == null)
                                    {
                                        listFrame = MakeFrameObject(embed, explicitFlag, requireAll);
                                    }
                                    var oldEmbedded = state.Embedded;
                                    state.Embedded = true;
                                    ProcessFrame(state,
                                                 new List <string> {
                                        listItem["@id"].Value <string>()
                                    },
                                                 listFrame,
                                                 list,
                                                 "@list",
                                                 ordered,
                                                 idStack);
                                    state.Embedded = oldEmbedded;
                                }
                                // 4.7.3.1.2 - Otherwise, append a copy of listitem to @list in list.
                                else
                                {
                                    if (list["@list"] == null)
                                    {
                                        list["@list"] = new JArray();
                                    }

                                    (list["@list"] as JArray).Add(listItem.DeepClone());
                                }
                            }
                        }
                        // 4.7.3.2 - If item is a node reference, invoke the algorithm using
                        // a copy of state with the value of embedded flag set to true,
                        // the value of @id from item as the sole item in a new subjects array,
                        // the first value from property in frame as frame,
                        // output as parent, and property as active property.
                        // If frame does not exist, create a new frame using a new map with properties for @embed, @explicit and @requireAll taken from embed, explicit and requireAll.
                        else if (JsonLdUtils.IsNodeReference(item))
                        {
                            var newFrame = ((frameObjectOrArray[property] as JArray)?[0]) as JObject ??
                                           MakeFrameObject(embed, explicitFlag, requireAll);
                            var oldEmbedded = state.Embedded;
                            state.Embedded = true;
                            ProcessFrame(state,
                                         new List <string> {
                                item["@id"].Value <string>()
                            },
                                         newFrame,
                                         output,
                                         property,
                                         ordered,
                                         idStack,
                                         processingMode);
                            state.Embedded = oldEmbedded;
                        }
                        // 4.7.3.3 - Otherwise, append a copy of item to active property in output.
                        else
                        {
                            // KA - should only append if value pattern matches?
                            if (frameObjectOrArray[property] == null ||
                                frameObjectOrArray[property][0]["@value"] == null ||
                                ValuePatternMatch(frameObjectOrArray[property], item))
                            {
                                FramingAppend(output, item, property);
                            }
                        }
                    }
                }

                // 4.7.4 - For each non-keyword property and objects in frame (other than `@type) that is not in output:
                foreach (var frameProperty in frame.Properties())
                {
                    var property = frameProperty.Name;
                    if (property.Equals("@type"))
                    {
                        if (!IsDefaultObjectArray(frameProperty.Value))
                        {
                            continue;
                        }
                    }
                    else
                    {
                        if (output.ContainsKey(property) || JsonLdUtils.IsKeyword(property))
                        {
                            continue;
                        }
                    }

                    var objects = frameProperty.Value as JArray;
                    if (objects == null || objects.Count == 0)
                    {
                        // KA - Check that this is still needed?
                        // Initialise as an array containing an empty frame
                        objects = new JArray(new JObject());
                    }

                    // 4.7.4.1 - Let item be the first element in objects, which must be a frame object.
                    var item = objects[0];
                    ValidateFrame(item);

                    // 4.7.4.2 - Set property frame to the first item in objects or a newly created frame object if value is objects. property frame must be a dictionary.
                    var propertyFrame = objects[0] as JObject ?? MakeFrameObject(embed, explicitFlag, requireAll); // KA - incomplete as I can't make sense of the spec algorithm here
                    // 4.7.4.3 - Skip property and property frame if property frame contains @omitDefault with a value of true, or does not contain @omitDefault and the value of the omit default flag is true.
                    var frameOmitDefault = GetBooleanOption(propertyFrame, "@omitDefault", state.OmitDefault);
                    if (frameOmitDefault)
                    {
                        continue;
                    }

                    // 4.7.4.4 - Add property to output with a new dictionary having a property @preserve and a value that is a copy of the value of @default in frame if it exists, or the string @null otherwise.
                    var defaultValue = JsonLdUtils.EnsureArray(propertyFrame["@default"]) ?? new JArray("@null");
                    output[property] = new JObject(new JProperty("@preserve", defaultValue));
                    //if (!(defaultValue is JArray)) defaultValue = new JArray(defaultValue);
                    //FramingAppend(output, new JObject(new JProperty("@preserve", defaultValue)), property);
                    // // output[property] = new JObject(new JProperty("@preserve", frame["@default"] ?? "@null"));
                }

                // 4.7.5 - If frame has the property @reverse, then for each reverse property and sub frame that are the values of @reverse in frame:
                if (frame.ContainsKey("@reverse"))
                {
                    foreach (var rp in (frame["@reverse"] as JObject).Properties())
                    {
                        var reverseProperty = rp.Name;
                        var subFrame        = rp.Value;
                        // 4.7.5.1 - Create a @reverse property in output with a new dictionary reverse dict as its value.
                        var reverseDict = new JObject();
                        output["@reverse"] = reverseDict;

                        // 4.7.5.2 - For each reverse id and node in the map of flattened subjects that has the property reverse property containing a node reference with an @id of id:
                        foreach (var p in state.Subjects.Properties())
                        {
                            var n = p.Value as JObject;
                            var reversePropertyValues = n[reverseProperty] as JArray;
                            if (reversePropertyValues == null)
                            {
                                continue;
                            }
                            if (reversePropertyValues.Any(x => x["@id"]?.Value <string>().Equals(id) == true))
                            {
                                // 4.7.5.2.1 - Add reverse property to reverse dict with a new empty array as its value.
                                var reverseId = p.Name;
                                if (reverseDict[reverseProperty] == null)
                                {
                                    reverseDict[reverseProperty] = new JArray();
                                }
                                // 4.7.5.2.2 - Invoke the recursive algorithm using state, the reverse id as the sole member of a new subjects array, sub frame as frame, null as active property, and the array value of reverse property in reverse dict as parent.
                                var oldEmbedded = state.Embedded;
                                state.Embedded = true;
                                ProcessFrame(state,
                                             new List <string> {
                                    reverseId
                                },
                                             subFrame,
                                             reverseDict[reverseProperty],
                                             null,
                                             ordered,
                                             idStack);
                                state.Embedded = oldEmbedded;
                            }
                        }
                    }
                }

                // 4.7.6 - Once output has been set are required in the previous steps, add output to parent.
                FramingAppend(parent, output, activeProperty);
                idStack.Pop();
            }
        }
示例#2
0
        private static Dictionary <string, JObject> MatchFrame(FramingState state, IEnumerable <string> subjects,
                                                               JObject frame, bool requireAll)
        {
            var matches         = new Dictionary <string, JObject>();
            var idMatches       = frame.ContainsKey("@id") ? JsonLdUtils.EnsureArray(frame["@id"]) : null;
            var typeMatches     = frame.ContainsKey("@type") ? JsonLdUtils.EnsureArray(frame["@type"]) : null;
            var propertyMatches = new Dictionary <string, JArray>();

            foreach (var p in frame)
            {
                if (JsonLdUtils.IsKeyword(p.Key))
                {
                    continue;
                }
                propertyMatches[p.Key] = JsonLdUtils.EnsureArray(p.Value);
            }
            foreach (var subject in subjects)
            {
                var node = state.Subjects[subject] as JObject;
                // Check @id and @type first
                if (idMatches != null)
                {
                    if (!(IsWildcard(idMatches) || IsMatchNone(idMatches) || idMatches.Any(v => v.Value <string>().Equals(subject))))
                    {
                        // No match on id. Continue to next subject
                        continue;
                    }

                    if (!requireAll)
                    {
                        matches.Add(subject, node);
                        continue;
                    }
                }
                if (typeMatches != null)
                {
                    var nodeTypes    = node.ContainsKey("@type") ? JsonLdUtils.EnsureArray(node["@type"]) : null;
                    var hasTypeMatch =
                        IsMatchNone(typeMatches) && (nodeTypes == null || nodeTypes.Count == 0) ||
                        IsWildcard(typeMatches) && nodeTypes != null && nodeTypes.Count > 0 ||
                        IsDefaultObjectArray(typeMatches) ||
                        nodeTypes != null && typeMatches.Any(t => nodeTypes.Any(nt => nt.Value <string>().Equals(t.Value <string>())));
                    if (hasTypeMatch)
                    {
                        if (!requireAll)
                        {
                            matches.Add(subject, node);
                            continue;
                        }
                    }
                    else
                    {
                        // Failed to match on type - always fails the node
                        continue;
                    }
                    //if (IsMatchNone(typeMatches) && nodeTypes != null && nodeTypes.Count > 0) continue;
                    //if (IsWildcard(typeMatches) && (nodeTypes == null || nodeTypes.Count == 0)) continue;
                    //if (nodeTypes == null) continue;
                    //if (!typeMatches.Any(t => nodeTypes.Any(nt=>nt.Value<string>().Equals(t.Value<string>())))) continue;
                    //if (!requireAll)
                    //{
                    //    matches.Add(subject, node);
                    //    continue;
                    //}
                }


                if (propertyMatches.Count == 0)
                {
                    // No additional properties to match
                    matches.Add(subject, node);
                    continue;
                }

                // If requireAll, assume there is a match and break when disproven
                // If !requireAll, assume there is no match and break when disproven
                var match = requireAll;
                var hasNonDefaultMatch = false;
                foreach (var pm in propertyMatches)
                {
                    var propertyMatch = MatchProperty(state, node, pm.Key, pm.Value, requireAll);
                    if (propertyMatch == MatchType.Abort)
                    {
                        match = false;
                        break;
                    }
                    if (propertyMatch == MatchType.NoMatch)
                    {
                        if (requireAll)
                        {
                            match = false;
                            break;
                        }
                    }
                    else if (propertyMatch == MatchType.Match)
                    {
                        hasNonDefaultMatch = true;
                        if (!requireAll)
                        {
                            match = true;
                            break;
                        }
                    }
                    // A default match is inconclusive until the end of the loop
                }
                if (match && hasNonDefaultMatch)
                {
                    matches.Add(subject, node);
                }
            }

            return(matches);
        }
示例#3
0
        private void GenerateNodeMapAlgorithm(JToken element, JObject nodeMap,
                                              string activeGraph    = "@default", JToken activeSubject = null,
                                              string activeProperty = null, JObject list               = null)
        {
            // 1 - If element is an array, process each item in element as follows and then return:
            if (element is JArray elementArray)
            {
                foreach (var item in elementArray)
                {
                    // 1.1 - Run this algorithm recursively by passing item for element, node map, active graph, active subject, active property, and list.
                    GenerateNodeMapAlgorithm(item, nodeMap, activeGraph, activeSubject, activeProperty, list);
                }
                return;
            }
            // 2 - Otherwise element is a map.
            // Reference the map which is the value of the active graph entry of node map using the variable graph.
            // If the active subject is null, set node to null otherwise reference the active subject entry of graph
            // using the variable subject node.
            var     elementObject = element as JObject;
            var     graph = nodeMap[activeGraph] as JObject;
            JObject node = null, subjectNode = null;

            if (activeSubject != null && activeSubject is JValue)
            {
                subjectNode = node = graph[activeSubject.Value <string>()] as JObject;
            }

            // 3 - For each item in the @type entry of element, if any, or for the value of @type, if the value of @type exists and is not an array:
            if (elementObject.ContainsKey("@type"))
            {
                // 3.1 - If item is a blank node identifier, replace it with a newly generated blank node identifier passing item for identifier.
                if (elementObject["@type"] is JArray typeArray)
                {
                    for (var ix = 0; ix < typeArray.Count; ix++)
                    {
                        var typeId = typeArray[ix].Value <string>();
                        if (JsonLdUtils.IsBlankNodeIdentifier(typeId))
                        {
                            typeArray[ix] = _blankNodeGenerator.GenerateBlankNodeIdentifier(typeId);
                        }
                    }
                }
                else if (elementObject["@type"] is JValue)
                {
                    var typeId = elementObject["@type"].Value <string>();
                    if (JsonLdUtils.IsBlankNodeIdentifier(typeId))
                    {
                        elementObject["@type"] = _blankNodeGenerator.GenerateBlankNodeIdentifier(typeId);
                    }
                }
            }

            // 4 - If element has an @value entry, perform the following steps:
            if (elementObject.ContainsKey("@value"))
            {
                // 4.1 - If list is null:
                if (list == null)
                {
                    // 4.1.1 - If subject node does not have an active property entry,
                    // create one and initialize its value to an array containing element.
                    if (!subjectNode.ContainsKey(activeProperty))
                    {
                        subjectNode[activeProperty] = new JArray(element);
                    }
                    // 4.1.2 - Otherwise, compare element against every item in the array associated with the active property member of node. If there is no item equivalent to element, append element to the array. Two dictionaries are considered equal if they have equivalent key-value pairs.
                    var existingItems = node[activeProperty] as JArray;
                    if (!existingItems.Any(x => JToken.DeepEquals(x, element)))
                    {
                        existingItems.Add(element);
                    }
                }
                else
                {
                    // 4.2 - Otherwise, append element to the @list member of list.
                    var listArray = list["@list"] as JArray;
                    listArray.Add(element);
                }
            }
            // 5 - Otherwise, if element has an @list member, perform the following steps:
            else if (elementObject.ContainsKey("@list"))
            {
                // 5.1 - Initialize a new map result consisting of a single entry @list whose value is initialized to an empty array.
                var result = new JObject(new JProperty("@list", new JArray()));

                // 5.2 - Recursively call this algorithm passing the value of element's @list entry for element,
                // node map, active graph, active subject, active property, and result for list.
                GenerateNodeMapAlgorithm(element["@list"], nodeMap, activeGraph, activeSubject, activeProperty, result);

                // 5.3 - If list is null, append result to the value of the active property entry of subject node.
                if (list == null)
                {
                    (subjectNode[activeProperty] as JArray).Add(result);
                }
                else
                {
                    // 5.4 - Otherwise, append result to the @list entry of list.
                    (list["@list"] as JArray).Add(result);
                }
            }
            // 6 - Otherwise element is a node object, perform the following steps:
            else
            {
                string id;
                // 6.1 - If element has an @id member, set id to its value and remove the member from element. If id is a blank node identifier, replace it with a newly generated blank node identifier passing id for identifier.
                if (elementObject.ContainsKey("@id"))
                {
                    id = elementObject["@id"]?.Value <string>();
                    elementObject.Remove("@id");
                    if (id == null)
                    {
                        return;             // Required to pass W3C test e122
                    }
                    if (JsonLdUtils.IsBlankNodeIdentifier(id))
                    {
                        id = _blankNodeGenerator.GenerateBlankNodeIdentifier(id);
                    }
                }
                // 6.2 - Otherwise, set id to the result of the Generate Blank Node Identifier algorithm passing null for identifier.
                else
                {
                    id = _blankNodeGenerator.GenerateBlankNodeIdentifier(null);
                }
                // 6.3 - If graph does not contain a member id, create one and initialize its value to a dictionary consisting of a single member @id whose value is id.
                if (!graph.ContainsKey(id))
                {
                    graph[id] = new JObject(new JProperty("@id", id));
                }
                // 6.4 - Reference the value of the id member of graph using the variable node.
                node = graph[id] as JObject;
                // 6.5 - If active subject is a dictionary, a reverse property relationship is being processed. Perform the following steps:
                if (activeSubject is JObject)
                {
                    // 6.5.1 - If node does not have an active property member, create one and initialize its value to an array containing active subject.
                    if (!node.ContainsKey(activeProperty))
                    {
                        node[activeProperty] = new JArray(activeSubject);
                    }
                    // 6.5.2 - Otherwise, compare active subject against every item in the array associated with the active property member of node.
                    // If there is no item equivalent to active subject, append active subject to the array.
                    // Two dictionaries are considered equal if they have equivalent key-value pairs.
                    else
                    {
                        AppendUniqueElement(activeSubject, node[activeProperty] as JArray);
                    }
                }
                // 6.6 - Otherwise, if active property is not null, perform the following steps:
                else if (activeProperty != null)
                {
                    // 6.6.1 - Create a new dictionary reference consisting of a single member @id whose value is id.
                    var reference = new JObject(new JProperty("@id", id));
                    // 6.6.2 - If list is null:
                    if (list == null)
                    {
                        // 6.6.2.1 - If subject node does not have an active property member, create one and initialize its value to an array containing reference.
                        if (!subjectNode.ContainsKey(activeProperty))
                        {
                            subjectNode[activeProperty] = new JArray(reference);
                        }
                        // 6.6.2.2 - Otherwise, compare reference against every item in the array associated with the active property member of node. If there is no item equivalent to reference, append reference to the array. Two dictionaries are considered equal if they have equivalent key-value pairs.
                        AppendUniqueElement(reference, subjectNode[activeProperty] as JArray);
                    }
                    else
                    {
                        // 6.6.3 - Otherwise, append reference to the @list member of list.
                        var listArray = list["@list"] as JArray;
                        listArray.Add(reference);
                    }
                }
                // 6.7 - If element has an @type entry, append each item of its associated array to the array associated with the @type entry of node unless it is already in that array.
                // Finally remove the @type entry from element.
                if (elementObject.ContainsKey("@type"))
                {
                    if (node.Property("@type") == null)
                    {
                        node["@type"] = new JArray();
                    }
                    foreach (var item in JsonLdUtils.EnsureArray(elementObject["@type"]))
                    {
                        AppendUniqueElement(item, node["@type"] as JArray);
                    }
                    elementObject.Remove("@type");
                }

                // 6.8 - If element has an @index entry, set the @index entry of node to its value.
                // If node already has an @index entry with a different value, a conflicting indexes error has been detected and processing is aborted.
                // Otherwise, continue by removing the @index entry from element.
                if (elementObject.ContainsKey("@index"))
                {
                    if (node.ContainsKey("@index") && !JToken.DeepEquals(elementObject["@index"], node["@index"]))
                    {
                        throw new JsonLdProcessorException(JsonLdErrorCode.ConflictingIndexes,
                                                           $"Conflicting indexes for node with id {id}.");
                    }
                    node["@index"] = elementObject["@index"];
                    elementObject.Remove("@index");
                }

                // 6.9 - If element has an @reverse entry:
                if (elementObject.ContainsKey("@reverse"))
                {
                    // 6.9.1 - Create a map referenced node with a single entry @id whose value is id.
                    var referencedNode = new JObject(new JProperty("@id", id));
                    // 6.9.2 - Initialize reverse map to the value of the @reverse entry of element.
                    var reverseMap = elementObject["@reverse"] as JObject;
                    // 6.9.3 - For each key-value pair property-values in reverse map:
                    foreach (var p in reverseMap.Properties())
                    {
                        var property = p.Name;
                        var values   = p.Value as JArray;
                        // 6.9.3.1 - For each value of values:
                        foreach (var value in values)
                        {
                            // 6.9.3.1.1 - Recursively invoke this algorithm passing value for element, node map,
                            // active graph, referenced node for active subject, and property for active property.
                            // Passing a map for active subject indicates to the algorithm that a reverse property
                            // relationship is being processed.
                            GenerateNodeMapAlgorithm(value, nodeMap, activeGraph, referencedNode, property);
                        }
                    }
                    // 6.9.4 - Remove the @reverse entry from element.
                    elementObject.Remove("@reverse");
                }
                // 6.10 - If element has an @graph entry, recursively invoke this algorithm passing the value of the
                // @graph entry for element, node map, and id for active graph before removing the @graph entry from element.
                if (elementObject.ContainsKey("@graph"))
                {
                    // KA: Ensure nodeMap contains a dictionary for the graph
                    if (!nodeMap.ContainsKey(id))
                    {
                        nodeMap.Add(id, new JObject());
                    }
                    GenerateNodeMapAlgorithm(elementObject["@graph"], nodeMap, id);
                    elementObject.Remove("@graph");
                }
                // 6.11 - If element has an @included entry, recursively invoke this algorithm passing the value of the
                // @included entry for element, node map, and active graph before removing the @included entry from element.
                if (elementObject.ContainsKey("@included"))
                {
                    GenerateNodeMapAlgorithm(elementObject["@included"], nodeMap, activeGraph);
                    elementObject.Remove("@included");
                }
                // 6.12 - Finally, for each key-value pair property-value in element ordered by property perform the following steps:
                foreach (var p in elementObject.Properties().OrderBy(p => p.Name).ToList())
                {
                    var property = p.Name;
                    var value    = p.Value;
                    // 6.12.1 - If property is a blank node identifier, replace it with a newly generated blank node identifier passing property for identifier.
                    if (JsonLdUtils.IsBlankNodeIdentifier(property))
                    {
                        property = _blankNodeGenerator.GenerateBlankNodeIdentifier(property);
                    }
                    // 6.12.2 - If node does not have a property entry, create one and initialize its value to an empty array.
                    if (!node.ContainsKey(property))
                    {
                        node[property] = new JArray();
                    }
                    // 6.12.3 - Recursively invoke this algorithm passing value for element, node map, active graph, id for active subject, and property for active property.
                    GenerateNodeMapAlgorithm(value, nodeMap, activeGraph, id, property);
                }
            }
        }