/////////////////////////////////////////////////////////
// cn5apinet - ConceptNet Microsoft .NET API v1.9.0.0
// by Joseph P. Socoloski III
// Copyright 2011. All Rights Reserved.
// NOTE: Designed for ConceptNet5
// LIMITS: - Work with downloaded json file [Can't too big throws memoryerrors]
// NEW : - ConceptNet 5.1 API [Lookup, Search, and Association each produce difference return json]
// -
//LICENSE
//BY DOWNLOADING AND USING, YOU AGREE TO THE FOLLOWING TERMS:
//If it is your intent to use this software for non-commercial purposes,
//such as in academic research, this software is free and is covered under
//the GNU GPL License, given here: <http://www.gnu.org/licenses/gpl.txt>
//You agree with 3RDPARTY's Terms Of Service
//given here: Json.NET Copyright (c) 2007 James Newton-King v4.0r2
// http://james.newtonking.com/pages/json-net.aspx
// ConceptNet5 http://conceptnet5.media.mit.edu/
// http://github.com/commonsense/conceptnet5/wiki
////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Collections;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.IO;
using System.Xml;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace cn5apinet
{
#region enums
public enum ReturnTYPE { assertion, concept, frame, frequency, raw_assertion, surface, leftfeature, rightfeature };
public enum LANG { es, en, fr, hu, it, ja, ko, nl, pt };
public enum DIRECTION { all, forward, backward };
/* Relational Ontology
http://csc.media.mit.edu/docs/conceptnet/conceptnet4.html
• Things: IsA, PartOf, HasProperty, DefinedAs, MadeOf, HasA
• Spatial: LocatedNear, ObstructedBy, AtLocation
• Events: HasSubevent, HasFirstSubevent, HasLastSubevent, HasPrerequisite, CreatedBy
• Causal: Causes, CausesDesire
• Affective: MotivatedByGoal, Desires
• Functional: ReceivesAction, UsedFor, SymbolOf
• Agents: CapableOf
*/
/// <summary>
/// Things about the object
/// </summary>
public enum Things
{
IsA, PartOf, HasProperty, DefinedAs, MadeOf, HasA
};
/// <summary>
/// Spetial: Location Of object
/// </summary>
public enum Spatial
{
LocatedNear, ObstructedBy, AtLocation
};
/// <summary>
/// Events explain any event related to the object.
/// </summary>
public enum Events
{
HasSubevent, HasFirstSubevent, HasLastSubevent, HasPrerequisite, CreatedBy
};
/// <summary>
/// Causal: EffectOf, DesirousEffectOf
/// </summary>
public enum Causal
{
Causes, CausesDesire
};
/// <summary>
/// Affective: MotivationOf, DesireOf
/// (Drives of the object)
/// </summary>
public enum Affective
{
MotivatedByGoal, Desires
};
/// <summary>
/// Functional: CapableOfReceivingAction, UsedFor
/// </summary>
public enum Functional
{
ReceivesAction, UsedFor, SymbolOf
};
/// <summary>
/// Agents: CapableOf
/// </summary>
public enum Agents
{
CapableOf
};
#endregion enums
#region ConceptNet Return Object
/// <summary>
/// Static variables related to Conceptnet5: API_URL, API_URLSEARCH, etc.
/// </summary>
public class Common
{
//public static string SERVER_URL = @"http://openmind.media.mit.edu";
public static string API_URL = @"http://conceptnet5.media.mit.edu/data/5.1/c/";
public static string API_URLSEARCH = @"http://conceptnet5.media.mit.edu/data/5.1/search?";
public static string API_URLASSOCIATION = @"http://conceptnet5.media.mit.edu/data/5.1/assoc";
public static string RESTDOC_URL = @"https://github.com/commonsense/conceptnet5/wiki/API";
public static string CLIENT_VERSION = "1";
}
/// <summary>
/// Lookup is for when you know the URI of an object in ConceptNet,
/// and want to see a list of edges that include it
/// </summary>
public class Lookup
{
public int numFound { get; set; }
public object edges { get; set; }
public float maxScore { get; set; }
public List<edgeLookup> ToEdgeList()
{
List<edgeLookup> list = new List<edgeLookup>();
JArray obj = JArray.FromObject(this.edges);
foreach (object item in obj.Children())
{
edgeLookup sResult = new edgeLookup();
sResult = JsonConvert.DeserializeObject<edgeLookup>(item.ToString());
list.Add(sResult);
}
return list;
}
/// <summary>
/// Get the raw pretty print string of this object.
/// </summary>
/// <returns>String, null if no value found</returns>
public String PPrint()
{
if (this != null)
return JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented);
else
return "null";
}
}
/// <summary>
/// These are the underlying edges used to implement the ConceptNet 5 hypergraph.
/// They are meant to be a small, closed class; you use them to express Assertions
/// involving the interesting open class of Relations.
/// Ref: https://github.com/commonsense/conceptnet5/wiki/API
/// Modified for CN5.1
/// </summary>
public class edgeLookup
{
public String endLemmas { get; set; }
public String rel { get; set; }
public String end { get; set; }
public List<String> features { get; set; }
public String license { get; set; }
public List<String> sources { get; set; }
public String startLemmas { get; set; }
public List<String> text { get; set; }
public String uri { get; set; }
public float weight { get; set; }
public String dataset { get; set; }
public String start { get; set; }
public float score { get; set; }
public String context { get; set; }
public String timestamp { get; set; }
public List<String> nodes { get; set; }
public String id { get; set; }
}
/// <summary>
/// The base URL for searching is http://conceptnet5.media.mit.edu/data/5.1/search.
/// You add GET arguments to this to specify what to search for.
/// {id, uri, rel, start, end, context, dataset, license}=URI: giving a ConceptNet URI for any of these parameters will return edges whose corresponding fields start with the given path.
/// nodes=URI: returns edges whose rel, start, or end start with the given URI.
/// {startLemmas, endLemmas, relLemmas}=word: returns edges containing the given lemmatized word anywhere in their start, end, or rel respectively.
/// text=word: matches any of startLemmas, endLemmas, or relLemmas.
/// surfaceText=word: matches edges with the given word in their surface text. The word is not lemmatized, but it is a case-insensitive match.
/// minWeight=weight: filters for edges whose weight is at least weight.
/// limit=n: change the number of results from the default of 50.
/// offset=n: skip the first n results.
/// features=str: Takes in a feature string (an assertion with one open slot), and returns edges having exactly that string as one of their features. Look at the features field of returned results for examples.
/// filter=core|core-assertions: filter the returned results -- see "Filters" on the CN5 wiki.
/// </summary>
public class Search
{
public int numFound { get; set; }
public object edges { get; set; }
public float maxScore { get; set; }
public List<edgeSearch> ToEdgeList()
{
List<edgeSearch> list = new List<edgeSearch>();
JArray obj = JArray.FromObject(this.edges);
foreach (object item in obj.Children())
{
edgeSearch sResult = new edgeSearch();
sResult = JsonConvert.DeserializeObject<edgeSearch>(item.ToString());
list.Add(sResult);
}
return list;
}
/// <summary>
/// Get the raw pretty print string of this object.
/// </summary>
/// <returns>String, null if no value found</returns>
public String PPrint()
{
if (this != null)
return JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented);
else
return "null";
}
}
/// <summary>
/// pair object used to hold URIWord and a score.
/// Used in Association.
/// </summary>
public class pair
{
public String URIWord { get; set; }
public float score { get; set; }
}
/// <summary>
/// These are the underlying edges used to implement the ConceptNet 5 hypergraph.
/// They are meant to be a small, closed class; you use them to express Assertions
/// involving the interesting open class of Relations.
/// Ref: https://github.com/commonsense/conceptnet5/wiki/API
/// Modified for CN5.1
/// </summary>
public class edgeSearch
{
public String endLemmas { get; set; }
public String rel { get; set; }
public String end { get; set; }
public List<String> features { get; set; }
public String license { get; set; }
public List<String> sources { get; set; }
public String startLemmas { get; set; }
public List<String> text { get; set; }
public String uri { get; set; }
public float weight { get; set; }
public String dataset { get; set; }
public String start { get; set; }
public float score { get; set; }
public String context { get; set; }
public String timestamp { get; set; }
public List<String> nodes { get; set; }
public String id { get; set; }
public String surfaceText { get; set; }
}
#endregion ConceptNet Return Object
/// <summary>
/// The Main ConceptNet5 ReST Client
/// https://github.com/commonsense/conceptnet5/wiki/API
/// </summary>
public class ConceptNetAPI
{
/// <summary>
/// Used to build the full url before making the query to json server
/// For CN5 it should be "http://conceptnet5.media.mit.edu/data/concept/"
/// this can be found and changed in cn5apinet.Common.API_URL
/// </summary>
public string url_base { get; set; }
/// <summary>
/// ConceptNetAPI Object Client
/// </summary>
public ConceptNetAPI()
{
this.url_base = cn5apinet.Common.API_URL;
}
/// <summary>
/// ConceptNetAPI Object Client
/// </summary>
/// <param name="api_baseurl">Overide CN5 url here [default: 'http://conceptnet5.media.mit.edu/data/concept/']</param>
public ConceptNetAPI(string api_baseurl)
{
this.url_base = api_baseurl;
}
#region Public Statics
/// <summary>
/// Convert a query.xml to json (ConceptNet4)
/// but will take any valid System.Xml.XmlDocument
/// and convert it to json.
/// </summary>
/// <param name="xmldoc">XmlDocument</param>
/// <returns>json string</returns>
public static string ConvertToJSON(XmlDocument xmldoc)
{
try
{
return JsonConvert.SerializeXmlNode(xmldoc);
}
catch (Exception)
{
throw;
}
}
#endregion Public Statics
/// <summary>
/// For testing purposes
/// </summary>
public void test()
{
//Newtonsoft.Json.Linq.JObject o = new JObject(File.ReadAllText("C:\\burnit\\conceptnet5-json\\nodes.json"));
//JsonReader reader = new JsonReader(new InputStreamReader(instream, "UNICODE"));
//while (reader.Read())
//{
// Console.WriteLine(reader.TokenType + "tt" + WriteValue(reader.ValueType) + "tt" + WriteValue(reader.Value));
//}
String title = "";
}
/// <summary>
/// Lookup is for when you know the URI of an object in ConceptNet, and want to see a list of edges that include it.
/// </summary>
/// <param name="language">language</param>
/// <param name="concept_name">Expects entirely built suffix for url. For example, '/contributor/omcs/rspeer', 'toast?offset=5&limit=5', etc.</param>
/// <returns>Lookup object</returns>
public Lookup Lookup(LANG language, String concept_name)
{
Lookup searchResult = new cn5apinet.Lookup();
string fullURL = "";
//For some reason 'contributor' calls have a url http://conceptnet5.media.mit.edu/data/5.1/s/contributor/omcs/rspeer with no more 'c' it is 's' instead so change that
if(concept_name.Contains(@"/contributor/"))
{
if(concept_name.StartsWith(@"/"))
concept_name = concept_name.Remove(0,1);
fullURL = this.url_base.Replace(@"/c/", @"/s/") + concept_name;
}
else
fullURL = this.url_base + language + "/" + concept_name;
try
{
var request = WebRequest.Create(fullURL) as HttpWebRequest;
request.UserAgent = "cnapinet sample";
request.KeepAlive = false;
request.Timeout = 15 * 1000;
var response = request.GetResponse() as HttpWebResponse;
if (request.HaveResponse == true && response != null)
{
var reader = new StreamReader(response.GetResponseStream());
//Console.WriteLine(reader.ReadToEnd());
//string[] keyroot = new string[] {""};
string keyword = "";
if (concept_name.Contains('?'))
{
keyword = concept_name.Split('?')[0];
}
else
keyword = concept_name;
string outp = "{" + "json" + ": ";
outp += reader.ReadToEnd();
outp += "}";
///string outp = "{json: { \"numFound\": 847, \"edges\": [ { \"endLemmas\": \"drink consume alcohol\", \"rel\": \"/r/Entails\", \"end\": \"/c/en/drink/v/consume_alcohol\", \"features\": [ \"/c/en/toast/v/propose_a_toast_to /r/Entails -\", \"/c/en/toast/v/propose_a_toast_to - /c/en/drink/v/consume_alcohol\", \"- /r/Entails /c/en/drink/v/consume_alcohol\" ], \"license\": \"/l/CC/By\", \"sources\": [ \"/s/wordnet/3.0\" ], \"startLemmas\": \"toast propose toast to\", \"text\": [ \"drink consume alcohol\", \"toast propose toast to\" ], \"uri\": \"/a/[/r/Entails/,/c/en/toast/v/propose_a_toast_to/,/c/en/drink/v/consume_alcohol/]\", \"weight\": 2.0, \"dataset\": \"/d/wordnet/3.0\", \"start\": \"/c/en/toast/v/propose_a_toast_to\", \"score\": 10.605541000000001, \"context\": \"/ctx/all\", \"timestamp\": \"2012-05-25T01:27:09.75Z\", \"nodes\": [ \"/c/en/drink/v/consume_alcohol\", \"/c/en/toast/v/propose_a_toast_to\", \"/r/Entails\" ], \"id\": \"/e/830e8a50011ee57aee04b92e846b31c11aebeda7\" }], \"maxScore\": 10.605541000000001}}";//test
///string outp = "{json: {\"edges\": [ { \"endLemmas\": \"drink consume alcohol\", \"rel\": \"/r/Entails\", \"end\": \"/c/en/drink/v/consume_alcohol\", \"features\": [ \"/c/en/toast/v/propose_a_toast_to /r/Entails -\", \"/c/en/toast/v/propose_a_toast_to - /c/en/drink/v/consume_alcohol\", \"- /r/Entails /c/en/drink/v/consume_alcohol\" ], \"license\": \"/l/CC/By\", \"sources\": [ \"/s/wordnet/3.0\" ], \"startLemmas\": \"toast propose toast to\", \"text\": [ \"drink consume alcohol\", \"toast propose toast to\" ], \"uri\": \"/a/[/r/Entails/,/c/en/toast/v/propose_a_toast_to/,/c/en/drink/v/consume_alcohol/]\", \"weight\": 2.0, \"dataset\": \"/d/wordnet/3.0\", \"start\": \"/c/en/toast/v/propose_a_toast_to\", \"score\": 10.605541000000001, \"context\": \"/ctx/all\", \"timestamp\": \"2012-05-25T01:27:09.75Z\", \"nodes\": [ \"/c/en/drink/v/consume_alcohol\", \"/c/en/toast/v/propose_a_toast_to\", \"/r/Entails\" ], \"id\": \"/e/830e8a50011ee57aee04b92e846b31c11aebeda7\" }] }}";
JObject CNSearch = JObject.Parse(outp);
// get JSON result objects into a list
JToken results = CNSearch["json"];
// serialize JSON results into .NET objects
Lookup sResult = new Lookup();
sResult = JsonConvert.DeserializeObject<Lookup>(results.ToString());
//There are at least two 'types' found here 'web_concept' and 'concept'
string[] seperators = new string[] { "}," };
searchResult = sResult;
}
}
catch (Exception ex)
{
//throw new Exception("Error fetching data.");
Console.WriteLine("cnapinet Lookup_json: [" + ex.Message + "] Check for valid relationship key variable. URL=" + fullURL);
if (ex.Message == "The remote server returned an error: (404) Not Found.")
searchResult.numFound = -1;
}
return searchResult;
}
/// <summary>
/// Search finds a list of edges that match certain criteria.
/// </summary>
/// <param name="language">language</param>
/// <param name="rel">relationship</param>
/// <param name="start">start</param>
/// <param name="end">end</param>
/// <param name="text">text=word: matches any of startLemmas, endLemmas, or relLemmas.</param>
/// <param name="limit">limit=n: change the number of results from the default of 50.</param>
/// <returns>Search object</returns>
public Search Search(LANG language, String rel, String start, String end, String text, int limit)
{
// http://conceptnet5.media.mit.edu/data/5.1/search?end=/c/en/car&rel=/r/PartOf&limit=10
Search searchResult = new cn5apinet.Search();
string fullURL = "";
StringCollection scURL = new StringCollection();
if (rel != "")
scURL.Add(@"rel=/r/" + rel);
if (start != "")
scURL.Add(@"start=/c/"+language+"/" + start);
if (end != "")
scURL.Add(@"end=/c/" + language + "/" + end);
if (limit != null || limit != -1)
scURL.Add(@"limit=" + Convert.ToString(limit));
if (text != "")
{
scURL.Clear();
scURL.Add(@"text=" + text);
}
string urlEnd = "";
int i = 0;
foreach (string item in scURL)
{
if (i == 0)
urlEnd += item;
else
urlEnd += "&" + item;
i = i + 1;
}
fullURL = Common.API_URLSEARCH + urlEnd;
try
{
var request = WebRequest.Create(fullURL) as HttpWebRequest;
request.UserAgent = "cnapinet sample";
request.KeepAlive = false;
request.Timeout = 15 * 1000;
var response = request.GetResponse() as HttpWebResponse;
if (request.HaveResponse == true && response != null)
{
var reader = new StreamReader(response.GetResponseStream());
string outp = "{" + "json" + ": ";
outp += reader.ReadToEnd();
outp += "}";
JObject CNSearch = JObject.Parse(outp);
// get JSON result objects into a list
JToken results = CNSearch["json"];
// serialize JSON results into .NET objects
Search sResult = new Search();
sResult = JsonConvert.DeserializeObject<Search>(results.ToString());
//There are at least two 'types' found here 'web_concept' and 'concept'
string[] seperators = new string[] { "}," };
searchResult = sResult;
}
}
catch (Exception ex)
{
//throw new Exception("Error fetching data.");
Console.WriteLine("cnapinet Search_json: [" + ex.Message + "] Check for valid relationship key variable. URL=" + fullURL);
if (ex.Message == "The remote server returned an error: (404) Not Found.")
searchResult.numFound = -1;
}
return searchResult;
}
/// <summary>
/// Association is for finding concepts similar to a particular concept or a list of concepts.
/// </summary>
/// <param name="language">language</param>
/// <param name="word">If you wish multi, start with use ',' between each word. ie: 'toast,cereal,juice,egg'.</param>
/// <param name="filter">filter=URI: return only results that start with the given URI. For example, filter=/c/en returns results in English.</param>
/// <param name="limit">limit=n: change the number of results from the default of 10.</param>
/// <param name="multipleterms">If true, then expecting a list like 'toast,cereal,juice,egg' etc.</param>
/// <returns>Association object</returns>
public Association Associations(LANG language, String word, String filter, int limit, bool multipleterms, int weight)
{
Association searchResult = new cn5apinet.Association();
string fullURL = "";
StringCollection scURL = new StringCollection();
if (word != "")
{
if(!word.Contains(','))
scURL.Add("/c/" + language + "/" + word);
else
scURL.Add("/list/" + language + "/" + word);
}
if (filter != "")
{
//This filter item needs to be in this position to be successfull; eg: http://conceptnet5.media.mit.edu/data/5.1/assoc/c/en/cat?filter=/c/en/dog&limit=1
if (filter != "en")
scURL.Add("filter=/c/" + language + "/" + filter);
}
if (weight != 0 && multipleterms)
scURL.Add("@" + Convert.ToString(weight));
if (limit != 0 && limit != -1)
scURL.Add(@"limit=" + Convert.ToString(limit));
if (filter != "")
{
//This filter item needs to be in this position to be successfull; eg: http://conceptnet5.media.mit.edu/data/5.1/assoc/list/en/happy,sad@-1?limit=20&filter=/c/en
if (filter == "en")
scURL.Add("filter=/c/" + language);
}
string urlEnd = "";
int i = 0;
foreach (string item in scURL)
{
if (i == 0)
urlEnd += item;
else
{
if ((item.Contains("limit=")) && (!multipleterms))
{
urlEnd += "&" + item;
}
else if ((item.Contains("filter=")) && (multipleterms))
{
urlEnd += "&" + item;
}
else
{
if (!item.StartsWith("@"))
urlEnd += "?" + item;
else
{
//this is a @-1 value so only skip it if it has a 0 value
if(item != "@0")
urlEnd += item;
}
}
}
i = i + 1;
}
//http://conceptnet5.media.mit.edu/data/5.1/assoc/list/en/happy,sad@-1?limit=25?filter=/c/en
fullURL = Common.API_URLASSOCIATION + urlEnd;
try
{
var request = WebRequest.Create(fullURL) as HttpWebRequest;
request.UserAgent = "cnapinet sample";
request.KeepAlive = false;
request.Timeout = 15 * 1000;
var response = request.GetResponse() as HttpWebResponse;
if (request.HaveResponse == true && response != null)
{
var reader = new StreamReader(response.GetResponseStream());
string outp = "{" + "json" + ": ";
outp += reader.ReadToEnd();
outp += "}";
JObject CNSearch = JObject.Parse(outp);
// get JSON result objects into a list
JToken results = CNSearch["json"];
// serialize JSON results into .NET objects
Association sResult = new Association();
sResult = JsonConvert.DeserializeObject<Association>(results.ToString());
//There are at least two 'types' found here 'web_concept' and 'concept'
string[] seperators = new string[] { "}," };
searchResult = sResult;
}
}
catch (Exception ex)
{
//throw new Exception("Error fetching data.");
Console.WriteLine("cnapinet Search_json: [" + ex.Message + "] Check for valid relationship key variable. URL=" + fullURL);
}
return searchResult;
}
}
/// <summary>
///The base URL is http://conceptnet5.media.mit.edu/data/5.1/assoc . This URL can be followed by:
///A concept URI, in which case it will show you the most similar concepts to that concept.
///A path of the form /list/[language]/[term list], which finds the most similar concepts to a list of terms, as described below.
///You can set the following GET arguments to modify the returned results:
///limit=n: change the number of results from the default of 10.
///filter=URI: return only results that start with the given URI. For example, filter=/c/en returns results in English.
/// </summary>
public class Association
{
public object terms { get; set; }
public object similar { get; set; }
public List<pair> ToEdgeList()
{
List<pair> list = new List<pair>();
if (this.similar != null)
{
JArray obj = JArray.FromObject(this.similar);
foreach (object item in obj.Children())
{
string[] splitter = Convert.ToString(item).Replace("[", "").Replace("\r\n", "").Replace("\"", "").Replace("]", "").Split(',');
pair Pair = new pair();
Pair.URIWord = splitter[0].Trim();
if (splitter.Length == 2)
{
Pair.score = float.Parse(splitter[1].Trim());
}
else
{
Pair.score = float.Parse(splitter[splitter.Length-1].Trim());
}
list.Add(Pair);
}
}
return list;
}
/// <summary>
/// Get the raw pretty print string of this object.
/// </summary>
/// <returns>String, null if no value found</returns>
public String PPrint()
{
if (this != null)
return JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented);
else
return "null";
}
}
}