Friend of a Friend (FOAF) in C#

12/3/2008

I just want to say up front that I'm not a big fan of formats like this and I'll get into why in a bit but I had to create a simple parser so I decided to share the code. Anyway, FOAF (otherwise known as Friend of a Friend) is a very basic, machine readable markup using RDF/XML to describe people, whom they are friends with, groups they're in, etc. In theory it could be used to allow social networks connect to people outside of their network, connect IM clients, connect... well... anything where a person's basic information is needed. Thankfully you can start using the format immediately:

FOAF.cs

   1: /*
   2: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21:  
  22: #region Usings
  23: using System;
  24: using System.Text;
  25: using System.Xml;
  26: #endregion
  27:  
  28: namespace Utilities.FileFormats.FOAF
  29: {
  30:     /// <summary>
  31:     /// FOAF Parser/Generator
  32:     /// </summary>
  33:     public class FOAF
  34:     {
  35:         #region Constructors
  36:         /// <summary>
  37:         /// Constructor
  38:         /// </summary>
  39:         public FOAF()
  40:         {
  41:  
  42:         }
  43:  
  44:         /// <summary>
  45:         /// Constructor
  46:         /// </summary>
  47:         /// <param name="Location">Location of the FOAF file</param>
  48:         public FOAF(string Location)
  49:         {
  50:             XmlDocument Document = new XmlDocument();
  51:             Document.Load(Location);
  52:             foreach (XmlNode Children in Document.ChildNodes)
  53:             {
  54:                 if (Children.Name.Equals("rdf:RDF", StringComparison.CurrentCultureIgnoreCase))
  55:                 {
  56:                     foreach (XmlNode Child in Children.ChildNodes)
  57:                     {
  58:                         if (Child.Name.Equals("foaf:Person", StringComparison.CurrentCultureIgnoreCase))
  59:                         {
  60:                             Person = new Person((XmlElement)Child);
  61:                         }
  62:                     }
  63:                 }
  64:             }
  65:         }
  66:  
  67:         /// <summary>
  68:         /// Constructor
  69:         /// </summary>
  70:         /// <param name="Document">XmlDocument containing the FOAF file</param>
  71:         public FOAF(XmlDocument Document)
  72:         {
  73:             foreach (XmlNode Children in Document.ChildNodes)
  74:             {
  75:                 if (Children.Name.Equals("rdf:RDF", StringComparison.CurrentCultureIgnoreCase))
  76:                 {
  77:                     foreach (XmlNode Child in Children.ChildNodes)
  78:                     {
  79:                         if (Child.Name.Equals("foaf:Person", StringComparison.CurrentCultureIgnoreCase))
  80:                         {
  81:                             Person = new Person((XmlElement)Child);
  82:                         }
  83:                     }
  84:                 }
  85:             }
  86:         }
  87:         #endregion
  88:  
  89:         #region Public Properties
  90:         /// <summary>
  91:         /// Person object
  92:         /// </summary>
  93:         public Person Person
  94:         {
  95:             get { return _Person; }
  96:             set { _Person = value; }
  97:         }
  98:         private Person _Person = null;
  99:         #endregion
 100:  
 101:         #region Public Overridden Functions
 102:  
 103:         /// <summary>
 104:         /// Outputs the FOAF object to a string
 105:         /// </summary>
 106:         /// <returns>Outputs an xml/rdf formatted output of the object</returns>
 107:         public override string ToString()
 108:         {
 109:             StringBuilder Builder = new StringBuilder();
 110:             Builder.Append("<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:rdfs=\"http://www.w3.org/2000/01/rdf-schema#\" xmlns:foaf=\"http://xmlns.com/foaf/0.1/\" xmlns:admin=\"http://webns.net/mvcb/\"><foaf:PersonalProfileDocument rdf:about=\"\"><foaf:maker rdf:resource=\"#me\"/><foaf:primaryTopic rdf:resource=\"#me\"/><admin:generatorAgent rdf:resource=\"http://www.gutgames.com\"/><admin:errorReportsTo rdf:resource=\"\"/></foaf:PersonalProfileDocument><foaf:Person rdf:ID=\"me\">");
 111:             Builder.Append(Person.ToString());
 112:             Builder.Append("</foaf:Person></rdf:RDF>");
 113:             return Builder.ToString();
 114:         }
 115:         #endregion
 116:     }
 117: }

Person.cs

   1: /*
   2: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21:  
  22: #region Usings
  23: using System;
  24: using System.Collections.Generic;
  25: using System.Text;
  26: using System.Xml;
  27: #endregion
  28:  
  29: namespace Utilities.FileFormats.FOAF
  30: {
  31:     /// <summary>
  32:     /// Container of an individual's information
  33:     /// </summary>
  34:     public class Person
  35:     {
  36:         #region Constructors
  37:         /// <summary>
  38:         /// Constructor
  39:         /// </summary>
  40:         public Person()
  41:         {
  42:         }
  43:  
  44:         /// <summary>
  45:         /// Constructor
  46:         /// </summary>
  47:         /// <param name="Element">Element containing the proper information</param>
  48:         public Person(XmlElement Element)
  49:         {
  50:             if (Element.Name.Equals("foaf:Person", StringComparison.CurrentCultureIgnoreCase))
  51:             {
  52:                 foreach (XmlNode Child in Element.ChildNodes)
  53:                 {
  54:                     if (Child.Name.Equals("foaf:name", StringComparison.CurrentCultureIgnoreCase))
  55:                     {
  56:                         Name = Child.InnerText;
  57:                     }
  58:                     else if (Child.Name.Equals("foaf:title", StringComparison.CurrentCultureIgnoreCase))
  59:                     {
  60:                         Title = Child.InnerText;
  61:                     }
  62:                     else if (Child.Name.Equals("foaf:givenname", StringComparison.CurrentCultureIgnoreCase))
  63:                     {
  64:                         GivenName = Child.InnerText;
  65:                     }
  66:                     else if (Child.Name.Equals("foaf:family_name", StringComparison.CurrentCultureIgnoreCase))
  67:                     {
  68:                         FamilyName = Child.InnerText;
  69:                     }
  70:                     else if (Child.Name.Equals("foaf:mbox_sha1sum", StringComparison.CurrentCultureIgnoreCase) || Child.Name.Equals("foaf:mbox", StringComparison.CurrentCultureIgnoreCase))
  71:                     {
  72:                         Email.Add(Child.InnerText);
  73:                     }
  74:                     else if (Child.Name.Equals("foaf:homepage", StringComparison.CurrentCultureIgnoreCase))
  75:                     {
  76:                         if (Child.Attributes["rdf:resource"] != null)
  77:                         {
  78:                             Homepage.Add(Child.Attributes["rdf:resource"].Value);
  79:                         }
  80:                     }
  81:                     else if (Child.Name.Equals("foaf:depiction", StringComparison.CurrentCultureIgnoreCase))
  82:                     {
  83:                         if (Child.Attributes["rdf:resource"] != null)
  84:                         {
  85:                             Depiction.Add(Child.Attributes["rdf:resource"].Value);
  86:                         }
  87:                     }
  88:                     else if (Child.Name.Equals("foaf:phone", StringComparison.CurrentCultureIgnoreCase))
  89:                     {
  90:                         if (Child.Attributes["rdf:resource"] != null)
  91:                         {
  92:                             Phone.Add(Child.Attributes["rdf:resource"].Value);
  93:                         }
  94:                     }
  95:                     else if (Child.Name.Equals("foaf:workplacehomepage", StringComparison.CurrentCultureIgnoreCase))
  96:                     {
  97:                         if (Child.Attributes["rdf:resource"] != null)
  98:                         {
  99:                             WorkplaceHomepage = Child.Attributes["rdf:resource"].Value;
 100:                         }
 101:                     }
 102:                     else if (Child.Name.Equals("foaf:workinfohomepage", StringComparison.CurrentCultureIgnoreCase))
 103:                     {
 104:                         if (Child.Attributes["rdf:resource"] != null)
 105:                         {
 106:                             WorkInfoHomepage = Child.Attributes["rdf:resource"].Value;
 107:                         }
 108:                     }
 109:                     else if (Child.Name.Equals("foaf:schoolhomepage", StringComparison.CurrentCultureIgnoreCase))
 110:                     {
 111:                         if (Child.Attributes["rdf:resource"] != null)
 112:                         {
 113:                             SchoolHomepage = Child.Attributes["rdf:resource"].Value;
 114:                         }
 115:                     }
 116:                     else if (Child.Name.Equals("foaf:knows", StringComparison.CurrentCultureIgnoreCase))
 117:                     {
 118:                         foreach (XmlNode Child2 in Child.ChildNodes)
 119:                         {
 120:                             PeopleKnown.Add(new Person((XmlElement)Child2));
 121:                         }
 122:                     }
 123:                     else if (Child.Name.Equals("rdfs:seeAlso", StringComparison.CurrentCultureIgnoreCase))
 124:                     {
 125:                         if (Child.Attributes["rdf:resource"] != null)
 126:                         {
 127:                             SeeAlso = Child.Attributes["rdf:resource"].Value;
 128:                         }
 129:                     }
 130:                 }
 131:             }
 132:         }
 133:         #endregion
 134:  
 135:         #region Public Properties
 136:         private string _SeeAlso = "";
 137:         private string _Name = "";
 138:         private string _Title = "";
 139:         private string _GivenName = "";
 140:         private string _FamilyName = "";
 141:         private string _NickName = "";
 142:         private List<string> _Email = new List<string>();
 143:         private List<string> _Homepage = new List<string>();
 144:         private List<string> _Depiction = new List<string>();
 145:         private List<string> _Phone = new List<string>();
 146:         private string _WorkplaceHomepage = "";
 147:         private string _WorkInfoHomepage = "";
 148:         private string _SchoolHomepage = "";
 149:         private List<Person> _PeopleKnown = new List<Person>();
 150:  
 151:         /// <summary>
 152:         /// Points to a person's FOAF file
 153:         /// </summary>
 154:         public string SeeAlso
 155:         {
 156:             get { return _SeeAlso; }
 157:             set { _SeeAlso = value; }
 158:         }
 159:  
 160:         /// <summary>
 161:         /// Name of the individual
 162:         /// </summary>
 163:         public string Name
 164:         {
 165:             get { return _Name; }
 166:             set { _Name = value; }
 167:         }
 168:  
 169:         /// <summary>
 170:         /// Title (such as Mr, Ms., etc.)
 171:         /// </summary>
 172:         public string Title
 173:         {
 174:             get { return _Title; }
 175:             set { _Title = value; }
 176:         }
 177:  
 178:         /// <summary>
 179:         /// Their given name
 180:         /// </summary>
 181:         public string GivenName
 182:         {
 183:             get { return _GivenName; }
 184:             set { _GivenName = value; }
 185:         }
 186:  
 187:         /// <summary>
 188:         /// Last name/Family name
 189:         /// </summary>
 190:         public string FamilyName
 191:         {
 192:             get { return _FamilyName; }
 193:             set { _FamilyName = value; }
 194:         }
 195:  
 196:         /// <summary>
 197:         /// Any sort of nick name
 198:         /// </summary>
 199:         public string NickName
 200:         {
 201:             get { return _NickName; }
 202:             set { _NickName = value; }
 203:         }
 204:  
 205:         /// <summary>
 206:         /// Their home pages
 207:         /// </summary>
 208:         public List<string> Homepage
 209:         {
 210:             get { return _Homepage; }
 211:             set { _Homepage = value; }
 212:         }
 213:  
 214:         /// <summary>
 215:         /// Image of the person
 216:         /// </summary>
 217:         public List<string> Depiction
 218:         {
 219:             get { return _Depiction; }
 220:             set { _Depiction = value; }
 221:         }
 222:  
 223:         /// <summary>
 224:         /// Their phone number
 225:         /// </summary>
 226:         public List<string> Phone
 227:         {
 228:             get { return _Phone; }
 229:             set { _Phone = value; }
 230:         }
 231:  
 232:         /// <summary>
 233:         /// Workplace home page
 234:         /// </summary>
 235:         public string WorkplaceHomepage
 236:         {
 237:             get { return _WorkplaceHomepage; }
 238:             set { _WorkplaceHomepage = value; }
 239:         }
 240:  
 241:         /// <summary>
 242:         /// Information about what the person does (link to it)
 243:         /// </summary>
 244:         public string WorkInfoHomepage
 245:         {
 246:             get { return _WorkInfoHomepage; }
 247:             set { _WorkInfoHomepage = value; }
 248:         }
 249:  
 250:         /// <summary>
 251:         /// Link to the school they went/currently going to
 252:         /// </summary>
 253:         public string SchoolHomepage
 254:         {
 255:             get { return _SchoolHomepage; }
 256:             set { _SchoolHomepage = value; }
 257:         }
 258:  
 259:         /// <summary>
 260:         /// Email addresses associated with the person (may be SHA1 hashes)
 261:         /// </summary>
 262:         public List<string> Email
 263:         {
 264:             get { return _Email; }
 265:             set { _Email = value; }
 266:         }
 267:  
 268:         /// <summary>
 269:         /// People that this person knows
 270:         /// </summary>
 271:         public List<Person> PeopleKnown
 272:         {
 273:             get { return _PeopleKnown; }
 274:             set { _PeopleKnown = value; }
 275:         }
 276:         #endregion
 277:  
 278:         #region Public Overridden Functions
 279:         /// <summary>
 280:         /// Outputs the person's information
 281:         /// </summary>
 282:         /// <returns>An rdf/xml formatted string of the person's info</returns>
 283:         public override string ToString()
 284:         {
 285:             StringBuilder Builder = new StringBuilder();
 286:             if(!string.IsNullOrEmpty(Name))
 287:                 Builder.Append("<foaf:name>" + Name + "</foaf:name>");
 288:             if(!string.IsNullOrEmpty(Title))
 289:                 Builder.Append("<foaf:title>" + Title + "</foaf:title>");
 290:             if(!string.IsNullOrEmpty(GivenName))
 291:                 Builder.Append("<foaf:givenname>" + GivenName + "</foaf:givenname>");
 292:             if(!string.IsNullOrEmpty(FamilyName))
 293:                 Builder.Append("<foaf:family_name>" + FamilyName + "</foaf:family_name>");
 294:             if(!string.IsNullOrEmpty(NickName))
 295:                 Builder.Append("<foaf:nick>" + NickName + "</foaf:nickname>");
 296:             foreach (string CurrentEmail in Email)
 297:             {
 298:                 if (!string.IsNullOrEmpty(CurrentEmail))
 299:                 {
 300:                     if (CurrentEmail.Contains("@"))
 301:                     {
 302:                         Builder.Append("<foaf:mbox>" + CurrentEmail + "</foaf:mbox>");
 303:                     }
 304:                     else
 305:                     {
 306:                         Builder.Append("<foaf:mbox_sha1sum>" + CurrentEmail + "</foaf:mbox_sha1sum>");
 307:                     }
 308:                 }
 309:             }
 310:             foreach (string CurrentHomePage in Homepage)
 311:             {
 312:                 if(!string.IsNullOrEmpty(CurrentHomePage))
 313:                     Builder.Append("<foaf:homepage rdf:resource=\"" + CurrentHomePage + "\" />");
 314:             }
 315:             foreach (string CurrentDepiction in Depiction)
 316:             {
 317:                 if(!string.IsNullOrEmpty(CurrentDepiction))
 318:                     Builder.Append("<foaf:depiction rdf:resource=\"" + CurrentDepiction + "\" />");
 319:             }
 320:             foreach (string CurrentPhone in Phone)
 321:             {
 322:                 if(!string.IsNullOrEmpty(CurrentPhone))
 323:                     Builder.Append("<foaf:phone rdf:resource=\"" + CurrentPhone + "\" />");
 324:             }
 325:             if(!string.IsNullOrEmpty(WorkplaceHomepage))
 326:                 Builder.Append("<foaf:workplaceHomepage rdf:resource=\"" + WorkplaceHomepage + "\" />");
 327:             if(!string.IsNullOrEmpty(WorkInfoHomepage))
 328:                 Builder.Append("<foaf:workInfoHomepage rdf:resource=\"" + WorkInfoHomepage + "\" />");
 329:             if(!string.IsNullOrEmpty(SchoolHomepage))
 330:                 Builder.Append("<foaf:schoolHomepage rdf:resource=\"" + SchoolHomepage + "\" />");
 331:             foreach (Person CurrentPerson in PeopleKnown)
 332:             {
 333:                 if (CurrentPerson != null)
 334:                 {
 335:                     Builder.Append("<foaf:knows><foaf:Person>");
 336:                     Builder.Append(CurrentPerson.ToString());
 337:                     Builder.Append("</foaf:Person></foaf:knows>");
 338:                 }
 339:             }
 340:             if(!string.IsNullOrEmpty(SeeAlso))
 341:                 Builder.Append("<rdfs:seeAlso rdf:resource=\"" + SeeAlso + "\"/>");
 342:             return Builder.ToString();
 343:         }
 344:         #endregion
 345:     }
 346: }

And yes, it has the MIT license at the top (which means it's going in my utility library). Anyway, it's commented and it does work for basic items. However, I didn't implement certain items, such as IM client IDs, blog pages, etc. (nor the DNA checksum...) But it does the basic phone number, email address, name, people known, etc. It also works with the FOAF-a-matic.  So if you created a document using that, it will read it.

Now before you get all happy with it and think that you're going to create a social networking site and it will talk to everyone... I've yet to see it used by a large enough groups of sites to make it worth while. You add in the fact that the format is mostly designed for machines only (unlike hCard and XFN), and that there are more widely used concepts like OpenID, and you end up with a format that is OK but you can live without it. I mean Google's Social Graph API works with the format (which is why I created the parser), but it also works with XFN, and XFN can be stylized using CSS, embedded in a web page, etc. And like I said, you have hCards which are gaining momentum, so I just don't see the need yet for FOAF... Never know though as it might catch on more and more as time goes on (I mean I remember a time when people said that XML wasn't going anywhere). Anyway, try out the code, leave feedback, and happy coding.



Comments