/// <summary> /// Compaction Algorithm /// http://json-ld.org/spec/latest/json-ld-api/#compaction-algorithm /// </summary> /// <param name="activeCtx"></param> /// <param name="activeProperty"></param> /// <param name="element"></param> /// <param name="compactArrays"></param> /// <returns></returns> /// <exception cref="JsonLD.Core.JsonLdError"></exception> public virtual JToken Compact(Context activeCtx, string activeProperty, JToken element , bool compactArrays) { // 2) if (element is JArray) { // 2.1) JArray result = new JArray(); // 2.2) foreach (JToken item in element) { // 2.2.1) JToken compactedItem = Compact(activeCtx, activeProperty, item, compactArrays); // 2.2.2) if (!compactedItem.IsNull()) { result.Add(compactedItem); } } // 2.3) if (compactArrays && result.Count == 1 && activeCtx.GetContainer(activeProperty) == null) { return result[0]; } // 2.4) return result; } // 3) if (element is JObject) { // access helper IDictionary<string, JToken> elem = (IDictionary<string, JToken>)element; // 4 if (elem.ContainsKey("@value") || elem.ContainsKey("@id")) { JToken compactedValue = activeCtx.CompactValue(activeProperty, (JObject)element); if (!(compactedValue is JObject || compactedValue is JArray)) { return compactedValue; } } // 5) bool insideReverse = ("@reverse".Equals(activeProperty)); // 6) JObject result = new JObject(); // 7) JArray keys = new JArray(element.GetKeys()); keys.SortInPlace(); foreach (string expandedProperty in keys) { JToken expandedValue = elem[expandedProperty]; // 7.1) if ("@id".Equals(expandedProperty) || "@type".Equals(expandedProperty)) { JToken compactedValue; // 7.1.1) if (expandedValue.Type == JTokenType.String) { compactedValue = activeCtx.CompactIri((string)expandedValue, "@type".Equals(expandedProperty )); } else { // 7.1.2) JArray types = new JArray(); // 7.1.2.2) foreach (string expandedType in (JArray)expandedValue) { types.Add(activeCtx.CompactIri(expandedType, true)); } // 7.1.2.3) if (types.Count == 1) { compactedValue = types[0]; } else { compactedValue = types; } } // 7.1.3) string alias = activeCtx.CompactIri(expandedProperty, true); // 7.1.4) result[alias] = compactedValue; continue; } // TODO: old add value code, see if it's still relevant? // addValue(rval, alias, compactedValue, // isArray(compactedValue) // && ((List<Object>) expandedValue).size() == 0); // 7.2) if ("@reverse".Equals(expandedProperty)) { // 7.2.1) JObject compactedValue = (JObject)Compact(activeCtx, "@reverse", expandedValue, compactArrays); // 7.2.2) List<string> properties = new List<string>(compactedValue.GetKeys()); foreach (string property in properties) { JToken value = compactedValue[property]; // 7.2.2.1) if (activeCtx.IsReverseProperty(property)) { // 7.2.2.1.1) if (("@set".Equals(activeCtx.GetContainer(property)) || !compactArrays) && !(value is JArray)) { JArray tmp = new JArray(); tmp.Add(value); result[property] = tmp; } // 7.2.2.1.2) if (!result.ContainsKey(property)) { result[property] = value; } else { // 7.2.2.1.3) if (!(result[property] is JArray)) { JArray tmp = new JArray(); tmp.Add(result[property]); result[property] = tmp; } if (value is JArray) { JsonLD.Collections.AddAll(((JArray)result[property]), (JArray)value ); } else { ((JArray)result[property]).Add(value); } } // 7.2.2.1.4) TODO: this doesn't seem safe (i.e. // modifying the map being used to drive the loop)! JsonLD.Collections.Remove(compactedValue, property); } } // 7.2.3) if (compactedValue.Count != 0) { // 7.2.3.1) string alias = activeCtx.CompactIri("@reverse", true); // 7.2.3.2) result[alias] = compactedValue; } // 7.2.4) continue; } // 7.3) if ("@index".Equals(expandedProperty) && "@index".Equals(activeCtx.GetContainer(activeProperty ))) { continue; } else { // 7.4) if ("@index".Equals(expandedProperty) || "@value".Equals(expandedProperty) || "@language" .Equals(expandedProperty)) { // 7.4.1) string alias = activeCtx.CompactIri(expandedProperty, true); // 7.4.2) result[alias] = expandedValue; continue; } } // NOTE: expanded value must be an array due to expansion // algorithm. // 7.5) if (((JArray)expandedValue).Count == 0) { // 7.5.1) string itemActiveProperty = activeCtx.CompactIri(expandedProperty, expandedValue, true, insideReverse); // 7.5.2) if (!result.ContainsKey(itemActiveProperty)) { result[itemActiveProperty] = new JArray(); } else { JToken value = result[itemActiveProperty]; if (!(value is JArray)) { JArray tmp = new JArray(); tmp.Add(value); result[itemActiveProperty] = tmp; } } } // 7.6) foreach (JToken expandedItem in (JArray)expandedValue) { // 7.6.1) string itemActiveProperty = activeCtx.CompactIri(expandedProperty, expandedItem, true, insideReverse); // 7.6.2) string container = activeCtx.GetContainer(itemActiveProperty); // get @list value if appropriate bool isList = (expandedItem is JObject && ((IDictionary<string, JToken>)expandedItem ).ContainsKey("@list")); JToken list = null; if (isList) { list = ((IDictionary<string, JToken>)expandedItem)["@list"]; } // 7.6.3) JToken compactedItem = Compact(activeCtx, itemActiveProperty, isList ? list : expandedItem , compactArrays); // 7.6.4) if (isList) { // 7.6.4.1) if (!(compactedItem is JArray)) { JArray tmp = new JArray(); tmp.Add(compactedItem); compactedItem = tmp; } // 7.6.4.2) if (!"@list".Equals(container)) { // 7.6.4.2.1) JObject wrapper = new JObject(); // TODO: SPEC: no mention of vocab = true wrapper[activeCtx.CompactIri("@list", true)] = compactedItem; compactedItem = wrapper; // 7.6.4.2.2) if (((IDictionary<string, JToken>)expandedItem).ContainsKey("@index")) { ((IDictionary<string, JToken>)compactedItem)[activeCtx.CompactIri("@index", true) ] = ((IDictionary<string, JToken>)expandedItem)["@index"]; } } else { // TODO: SPEC: no mention of vocab = // true // 7.6.4.3) if (result.ContainsKey(itemActiveProperty)) { throw new JsonLdError(JsonLdError.Error.CompactionToListOfLists, "There cannot be two list objects associated with an active property that has a container mapping" ); } } } // 7.6.5) if ("@language".Equals(container) || "@index".Equals(container)) { // 7.6.5.1) JObject mapObject; if (result.ContainsKey(itemActiveProperty)) { mapObject = (JObject)result[itemActiveProperty]; } else { mapObject = new JObject(); result[itemActiveProperty] = mapObject; } // 7.6.5.2) if ("@language".Equals(container) && (compactedItem is JObject && ((IDictionary <string, JToken>)compactedItem).ContainsKey("@value"))) { compactedItem = compactedItem["@value"]; } // 7.6.5.3) string mapKey = (string)expandedItem[container]; // 7.6.5.4) if (!mapObject.ContainsKey(mapKey)) { mapObject[mapKey] = compactedItem; } else { JArray tmp; if (!(mapObject[mapKey] is JArray)) { tmp = new JArray(); tmp.Add(mapObject[mapKey]); mapObject[mapKey] = tmp; } else { tmp = (JArray)mapObject[mapKey]; } tmp.Add(compactedItem); } } else { // 7.6.6) // 7.6.6.1) bool check = (!compactArrays || "@set".Equals(container) || "@list".Equals(container ) || "@list".Equals(expandedProperty) || "@graph".Equals(expandedProperty)) && ( !(compactedItem is JArray)); if (check) { JArray tmp = new JArray(); tmp.Add(compactedItem); compactedItem = tmp; } // 7.6.6.2) if (!result.ContainsKey(itemActiveProperty)) { result[itemActiveProperty] = compactedItem; } else { if (!(result[itemActiveProperty] is JArray)) { JArray tmp = new JArray(); tmp.Add(result[itemActiveProperty]); result[itemActiveProperty] = tmp; } if (compactedItem is JArray) { JsonLD.Collections.AddAll(((JArray)result[itemActiveProperty]), (JArray)compactedItem); } else { ((JArray)result[itemActiveProperty]).Add(compactedItem); } } } } } // 8) return result; } // 2) return element; }
/// <summary> /// Expansion Algorithm /// http://json-ld.org/spec/latest/json-ld-api/#expansion-algorithm /// </summary> /// <param name="activeCtx"></param> /// <param name="activeProperty"></param> /// <param name="element"></param> /// <returns></returns> /// <exception cref="JsonLdError">JsonLdError</exception> /// <exception cref="JsonLD.Core.JsonLdError"></exception> public virtual JToken Expand(Context activeCtx, string activeProperty, JToken element ) { // 1) if (element.IsNull()) { return null; } // 3) if (element is JArray) { // 3.1) JArray result = new JArray(); // 3.2) foreach (JToken item in (JArray)element) { // 3.2.1) JToken v = Expand(activeCtx, activeProperty, item); // 3.2.2) if (("@list".Equals(activeProperty) || "@list".Equals(activeCtx.GetContainer(activeProperty ))) && (v is JArray || (v is JObject && ((IDictionary<string, JToken>)v).ContainsKey ("@list")))) { throw new JsonLdError(JsonLdError.Error.ListOfLists, "lists of lists are not permitted." ); } else { // 3.2.3) if (!v.IsNull()) { if (v is JArray) { JsonLD.Collections.AddAll(result, (JArray)v); } else { result.Add(v); } } } } // 3.3) return result; } else { // 4) if (element is JObject) { // access helper IDictionary<string, JToken> elem = (JObject)element; // 5) if (elem.ContainsKey("@context")) { activeCtx = activeCtx.Parse(elem["@context"]); } // 6) JObject result = new JObject(); // 7) JArray keys = new JArray(element.GetKeys()); keys.SortInPlace(); foreach (string key in keys) { JToken value = elem[key]; // 7.1) if (key.Equals("@context")) { continue; } // 7.2) string expandedProperty = activeCtx.ExpandIri(key, false, true, null, null); JToken expandedValue = null; // 7.3) if (expandedProperty == null || (!expandedProperty.Contains(":") && !JsonLdUtils.IsKeyword (expandedProperty))) { continue; } // 7.4) if (JsonLdUtils.IsKeyword(expandedProperty)) { // 7.4.1) if ("@reverse".Equals(activeProperty)) { throw new JsonLdError(JsonLdError.Error.InvalidReversePropertyMap, "a keyword cannot be used as a @reverse propery" ); } // 7.4.2) if (result.ContainsKey(expandedProperty)) { throw new JsonLdError(JsonLdError.Error.CollidingKeywords, expandedProperty + " already exists in result" ); } // 7.4.3) if ("@id".Equals(expandedProperty)) { if (!(value.Type == JTokenType.String)) { throw new JsonLdError(JsonLdError.Error.InvalidIdValue, "value of @id must be a string" ); } expandedValue = activeCtx.ExpandIri((string)value, true, false, null, null); } else { // 7.4.4) if ("@type".Equals(expandedProperty)) { if (value is JArray) { expandedValue = new JArray(); foreach (JToken v in (JArray)value) { if (v.Type != JTokenType.String) { throw new JsonLdError(JsonLdError.Error.InvalidTypeValue, "@type value must be a string or array of strings" ); } ((JArray)expandedValue).Add(activeCtx.ExpandIri((string)v, true, true, null , null)); } } else { if (value.Type == JTokenType.String) { expandedValue = activeCtx.ExpandIri((string)value, true, true, null, null); } else { // TODO: SPEC: no mention of empty map check if (value is JObject) { if (((JObject)value).Count != 0) { throw new JsonLdError(JsonLdError.Error.InvalidTypeValue, "@type value must be a an empty object for framing" ); } expandedValue = value; } else { throw new JsonLdError(JsonLdError.Error.InvalidTypeValue, "@type value must be a string or array of strings" ); } } } } else { // 7.4.5) if ("@graph".Equals(expandedProperty)) { expandedValue = Expand(activeCtx, "@graph", value); } else { // 7.4.6) if ("@value".Equals(expandedProperty)) { if (!value.IsNull() && (value is JObject || value is JArray)) { throw new JsonLdError(JsonLdError.Error.InvalidValueObjectValue, "value of " + expandedProperty + " must be a scalar or null"); } expandedValue = value; if (expandedValue.IsNull()) { result["@value"] = null; continue; } } else { // 7.4.7) if ("@language".Equals(expandedProperty)) { if (!(value.Type == JTokenType.String)) { throw new JsonLdError(JsonLdError.Error.InvalidLanguageTaggedString, "Value of " + expandedProperty + " must be a string"); } expandedValue = ((string)value).ToLower(); } else { // 7.4.8) if ("@index".Equals(expandedProperty)) { if (!(value.Type == JTokenType.String)) { throw new JsonLdError(JsonLdError.Error.InvalidIndexValue, "Value of " + expandedProperty + " must be a string"); } expandedValue = value; } else { // 7.4.9) if ("@list".Equals(expandedProperty)) { // 7.4.9.1) if (activeProperty == null || "@graph".Equals(activeProperty)) { continue; } // 7.4.9.2) expandedValue = Expand(activeCtx, activeProperty, value); // NOTE: step not in the spec yet if (!(expandedValue is JArray)) { JArray tmp = new JArray(); tmp.Add(expandedValue); expandedValue = tmp; } // 7.4.9.3) foreach (JToken o in (JArray)expandedValue) { if (o is JObject && ((JObject)o).ContainsKey("@list")) { throw new JsonLdError(JsonLdError.Error.ListOfLists, "A list may not contain another list" ); } } } else { // 7.4.10) if ("@set".Equals(expandedProperty)) { expandedValue = Expand(activeCtx, activeProperty, value); } else { // 7.4.11) if ("@reverse".Equals(expandedProperty)) { if (!(value is JObject)) { throw new JsonLdError(JsonLdError.Error.InvalidReverseValue, "@reverse value must be an object" ); } // 7.4.11.1) expandedValue = Expand(activeCtx, "@reverse", value); // NOTE: algorithm assumes the result is a map // 7.4.11.2) if (((IDictionary<string, JToken>)expandedValue).ContainsKey("@reverse")) { JObject reverse = (JObject)((JObject)expandedValue)["@reverse"]; foreach (string property in reverse.GetKeys()) { JToken item = reverse[property]; // 7.4.11.2.1) if (!result.ContainsKey(property)) { result[property] = new JArray(); } // 7.4.11.2.2) if (item is JArray) { JsonLD.Collections.AddAll(((JArray)result[property]), (JArray)item); } else { ((JArray)result[property]).Add(item); } } } // 7.4.11.3) if (((JObject)expandedValue).Count > (((JObject)expandedValue).ContainsKey("@reverse") ? 1 : 0)) { // 7.4.11.3.1) if (!result.ContainsKey("@reverse")) { result["@reverse"] = new JObject(); } // 7.4.11.3.2) JObject reverseMap = (JObject)result["@reverse"]; // 7.4.11.3.3) foreach (string property in expandedValue.GetKeys()) { if ("@reverse".Equals(property)) { continue; } // 7.4.11.3.3.1) JArray items = (JArray)((JObject)expandedValue)[property]; foreach (JToken item in items) { // 7.4.11.3.3.1.1) if (item is JObject && (((JObject)item).ContainsKey("@value") || ((JObject)item).ContainsKey("@list"))) { throw new JsonLdError(JsonLdError.Error.InvalidReversePropertyValue); } // 7.4.11.3.3.1.2) if (!reverseMap.ContainsKey(property)) { reverseMap[property] = new JArray(); } // 7.4.11.3.3.1.3) ((JArray)reverseMap[property]).Add(item); } } } // 7.4.11.4) continue; } else { // TODO: SPEC no mention of @explicit etc in spec if ("@explicit".Equals(expandedProperty) || "@default".Equals(expandedProperty) || "@embed".Equals(expandedProperty) || "@embedChildren".Equals(expandedProperty) || "@omitDefault".Equals(expandedProperty)) { expandedValue = Expand(activeCtx, expandedProperty, value); } } } } } } } } } } // 7.4.12) if (!expandedValue.IsNull()) { result[expandedProperty] = expandedValue; } // 7.4.13) continue; } else { // 7.5 if ("@language".Equals(activeCtx.GetContainer(key)) && value is JObject) { // 7.5.1) expandedValue = new JArray(); // 7.5.2) foreach (string language in value.GetKeys()) { JToken languageValue = ((IDictionary<string, JToken>)value)[language]; // 7.5.2.1) if (!(languageValue is JArray)) { JToken tmp = languageValue; languageValue = new JArray(); ((JArray)languageValue).Add(tmp); } // 7.5.2.2) foreach (JToken item in (JArray)languageValue) { // 7.5.2.2.1) if (!(item.Type == JTokenType.String)) { throw new JsonLdError(JsonLdError.Error.InvalidLanguageMapValue, "Expected " + item .ToString() + " to be a string"); } // 7.5.2.2.2) JObject tmp = new JObject(); tmp["@value"] = item; tmp["@language"] = language.ToLower(); ((JArray)expandedValue).Add(tmp); } } } else { // 7.6) if ("@index".Equals(activeCtx.GetContainer(key)) && value is JObject) { // 7.6.1) expandedValue = new JArray(); // 7.6.2) JArray indexKeys = new JArray(value.GetKeys()); indexKeys.SortInPlace(); foreach (string index in indexKeys) { JToken indexValue = ((JObject)value)[index]; // 7.6.2.1) if (!(indexValue is JArray)) { JToken tmp = indexValue; indexValue = new JArray(); ((JArray)indexValue).Add(tmp); } // 7.6.2.2) indexValue = Expand(activeCtx, key, indexValue); // 7.6.2.3) foreach (JObject item in (JArray)indexValue) { // 7.6.2.3.1) if (!item.ContainsKey("@index")) { item["@index"] = index; } // 7.6.2.3.2) ((JArray)expandedValue).Add(item); } } } else { // 7.7) expandedValue = Expand(activeCtx, key, value); } } } // 7.8) if (expandedValue.IsNull()) { continue; } // 7.9) if ("@list".Equals(activeCtx.GetContainer(key))) { if (!(expandedValue is JObject) || !((JObject)expandedValue).ContainsKey("@list")) { JToken tmp = expandedValue; if (!(tmp is JArray)) { tmp = new JArray(); ((JArray)tmp).Add(expandedValue); } expandedValue = new JObject(); ((JObject)expandedValue)["@list"] = tmp; } } // 7.10) if (activeCtx.IsReverseProperty(key)) { // 7.10.1) if (!result.ContainsKey("@reverse")) { result["@reverse"] = new JObject(); } // 7.10.2) JObject reverseMap = (JObject)result["@reverse"]; // 7.10.3) if (!(expandedValue is JArray)) { JToken tmp = expandedValue; expandedValue = new JArray(); ((JArray)expandedValue).Add(tmp); } // 7.10.4) foreach (JToken item in (JArray)expandedValue) { // 7.10.4.1) if (item is JObject && (((JObject)item).ContainsKey("@value") || ((JObject)item).ContainsKey("@list"))) { throw new JsonLdError(JsonLdError.Error.InvalidReversePropertyValue); } // 7.10.4.2) if (!reverseMap.ContainsKey(expandedProperty)) { reverseMap[expandedProperty] = new JArray(); } // 7.10.4.3) if (item is JArray) { JsonLD.Collections.AddAll(((JArray)reverseMap[expandedProperty]), (JArray)item); } else { ((JArray)reverseMap[expandedProperty]).Add(item); } } } else { // 7.11) // 7.11.1) if (!result.ContainsKey(expandedProperty)) { result[expandedProperty] = new JArray(); } // 7.11.2) if (expandedValue is JArray) { JsonLD.Collections.AddAll(((JArray)result[expandedProperty]), (JArray)expandedValue); } else { ((JArray)result[expandedProperty]).Add(expandedValue); } } } // 8) if (result.ContainsKey("@value")) { // 8.1) // TODO: is this method faster than just using containsKey for // each? ICollection<string> keySet = new HashSet<string>(result.GetKeys()); keySet.Remove("@value"); keySet.Remove("@index"); bool langremoved = keySet.Remove("@language"); bool typeremoved = keySet.Remove("@type"); if ((langremoved && typeremoved) || !keySet.IsEmpty()) { throw new JsonLdError(JsonLdError.Error.InvalidValueObject, "value object has unknown keys" ); } // 8.2) JToken rval = result["@value"]; if (rval.IsNull()) { // nothing else is possible with result if we set it to // null, so simply return it return null; } // 8.3) if (!(rval.Type == JTokenType.String) && result.ContainsKey("@language")) { throw new JsonLdError(JsonLdError.Error.InvalidLanguageTaggedValue, "when @language is used, @value must be a string" ); } else { // 8.4) if (result.ContainsKey("@type")) { // TODO: is this enough for "is an IRI" if (!(result["@type"].Type == JTokenType.String) || ((string)result["@type"]).StartsWith("_:") || !((string)result["@type"]).Contains(":")) { throw new JsonLdError(JsonLdError.Error.InvalidTypedValue, "value of @type must be an IRI" ); } } } } else { // 9) if (result.ContainsKey("@type")) { JToken rtype = result["@type"]; if (!(rtype is JArray)) { JArray tmp = new JArray(); tmp.Add(rtype); result["@type"] = tmp; } } else { // 10) if (result.ContainsKey("@set") || result.ContainsKey("@list")) { // 10.1) if (result.Count > (result.ContainsKey("@index") ? 2 : 1)) { throw new JsonLdError(JsonLdError.Error.InvalidSetOrListObject, "@set or @list may only contain @index" ); } // 10.2) if (result.ContainsKey("@set")) { // result becomes an array here, thus the remaining checks // will never be true from here on // so simply return the value rather than have to make // result an object and cast it with every // other use in the function. return result["@set"]; } } } } // 11) if (result.ContainsKey("@language") && result.Count == 1) { result = null; } // 12) if (activeProperty == null || "@graph".Equals(activeProperty)) { // 12.1) if (result != null && (result.Count == 0 || result.ContainsKey("@value") || result .ContainsKey("@list"))) { result = null; } else { // 12.2) if (result != null && result.ContainsKey("@id") && result.Count == 1) { result = null; } } } // 13) return result; } else { // 2) If element is a scalar // 2.1) if (activeProperty == null || "@graph".Equals(activeProperty)) { return null; } return activeCtx.ExpandValue(activeProperty, element); } } }