Beispiel #1
0
        private static void RemoveEmbed(FramingState state, string id)
        {
            var embed = state.GetEmbeddedNode(id);

            if (embed == null)
            {
                return;
            }
            var parent   = embed.Item1;
            var property = embed.Item2;
            var subject  = new JObject(new JProperty("@id", id));

            if (parent is JArray parentArray)
            {
                for (var i = 0; i < parentArray.Count; i++)
                {
                    var item = parentArray[i];
                    if (item is JObject itemObject && itemObject.ContainsKey("@id") && itemObject["@id"].Value <string>().Equals(id))
                    {
                        parent[i] = subject;
                        break;
                    }
                }
            }
            else
            {
                var useArray = JsonLdUtils.IsArray(parent[property]);
                JsonLdUtils.RemoveValue(parent as JObject, property, subject, useArray);
                JsonLdUtils.AddValue(parent as JObject, property, subject, useArray);
            }
        }
Beispiel #2
0
        private static bool NodePatternMatch(FramingState state, JObject frame, JToken value, bool requireAll)
        {
            if (!(value is JObject valueObject) || !valueObject.ContainsKey("@id"))
            {
                return(false);
            }
            var matches = MatchFrame(state, new[] { valueObject["@id"].Value <string>() }, frame, requireAll);

            return(matches.Any());
        }
Beispiel #3
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);
        }
Beispiel #4
0
        private static MatchType MatchProperty(FramingState state, JObject node, string property, JToken frameValue, bool requireAll)
        {
            var  nodeValues = node[property] as JArray;
            var  frameArray = frameValue as JArray;
            bool isMatchNone = false, isWildcard = false, hasDefault = false;

            if (frameArray.Count == 0)
            {
                isMatchNone = true;
            }
            else if (frameArray.Count == 1 && JsonLdUtils.IsDefaultObject(frameArray[0]))
            {
                hasDefault = true;
            }
            else if (frameArray.Count == 1 && IsWildcard(frameArray[0]))
            {
                isWildcard = true;
            }

            if (nodeValues == null || nodeValues.Count == 0)
            {
                if (hasDefault)
                {
                    return(MatchType.DefaultMatch);
                }
            }

            // Non-existant property cannot match frame
            if (nodeValues == null)
            {
                return(MatchType.NoMatch);
            }

            // Frame specifies match none - nodeValues must be empty
            if (isMatchNone && nodeValues.Count != 0)
            {
                return(MatchType.Abort);
            }

            // Frame specifies match wildcard - nodeValues must be non-empty
            if (isWildcard && nodeValues.Count > 0)
            {
                return(MatchType.Match);
            }

            if (JsonLdUtils.IsValueObject(frameArray[0]))
            {
                // frameArray is a value pattern array
                foreach (var valuePattern in frameArray)
                {
                    foreach (var value in nodeValues)
                    {
                        if (ValuePatternMatch(valuePattern, value))
                        {
                            return(MatchType.Match);
                        }
                    }
                }
                return(MatchType.NoMatch);
            }

            if (JsonLdUtils.IsListObject(frameArray[0]))
            {
                var frameListValue = frameArray[0]["@list"][0];
                if (JsonLdUtils.IsListObject(nodeValues[0]))
                {
                    var nodeListValues = nodeValues[0]["@list"];
                    if (JsonLdUtils.IsValueObject(frameListValue))
                    {
                        if (nodeListValues.Any(v => ValuePatternMatch(frameListValue, v)))
                        {
                            return(MatchType.Match);
                        }
                    }
                    else if (JsonLdUtils.IsSubject(frameListValue) || JsonLdUtils.IsSubjectReference(frameListValue))
                    {
                        if (nodeListValues.Any(v => NodePatternMatch(state, frameListValue as JObject, v, requireAll)))
                        {
                            return(MatchType.Match);
                        }
                    }
                }
            }

            // frameArray is a node pattern array
            var valueSubjects = nodeValues.Where(x => x["@id"] != null).Select(x => x["@id"].Value <string>()).ToList();

            if (valueSubjects.Any())
            {
                foreach (var subframe in frameArray)
                {
                    var matchedSubjects = MatchFrame(state, valueSubjects, subframe as JObject, requireAll);
                    if (matchedSubjects.Any())
                    {
                        return(MatchType.Match);
                    }
                }
            }

            return(hasDefault ? MatchType.Match : MatchType.NoMatch);
        }
Beispiel #5
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();
            }
        }