//************************************************************************* // Method: GetUserNetworkInternal() // /// <summary> /// Gets a network of Twitter users, given a GraphMLXmlDocument. /// </summary> /// /// <param name="sScreenNameToAnalyze"> /// The screen name of the Twitter user whose network should be analyzed. /// </param> /// /// <param name="eWhatToInclude"> /// Specifies what should be included in the network. /// </param> /// /// <param name="eNetworkLevel"> /// Network level to include. Must be NetworkLevel.One, OnePointFive, or /// Two. /// </param> /// /// <param name="iMaximumPeoplePerRequest"> /// Maximum number of people to request for each query, or Int32.MaxValue /// for no limit. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// The GraphMLXmlDocument to populate with the requested network. /// </param> //************************************************************************* protected void GetUserNetworkInternal( String sScreenNameToAnalyze, WhatToInclude eWhatToInclude, NetworkLevel eNetworkLevel, Int32 iMaximumPeoplePerRequest, RequestStatistics oRequestStatistics, GraphMLXmlDocument oGraphMLXmlDocument ) { Debug.Assert( !String.IsNullOrEmpty(sScreenNameToAnalyze) ); Debug.Assert(eNetworkLevel == NetworkLevel.One || eNetworkLevel == NetworkLevel.OnePointFive || eNetworkLevel == NetworkLevel.Two); Debug.Assert(iMaximumPeoplePerRequest > 0); Debug.Assert(oRequestStatistics != null); Debug.Assert(oGraphMLXmlDocument != null); AssertValid(); // The key is the screen name and the value is the corresponding // TwitterVertex. This is used to prevent the same screen name from // being added to the XmlDocument twice. Dictionary<String, TwitterVertex> oScreenNameDictionary = new Dictionary<String, TwitterVertex>(); // Include followed, followers, both, or neither. Boolean [] abIncludes = new Boolean[] { WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.FollowedVertices), WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.FollowerVertices), }; for (Int32 i = 0; i < 2; i++) { if ( abIncludes[i] ) { GetUserNetworkRecursive(sScreenNameToAnalyze, eWhatToInclude, (i == 0), eNetworkLevel, iMaximumPeoplePerRequest, 1, oGraphMLXmlDocument, oScreenNameDictionary, oRequestStatistics); } } Boolean bIncludeLatestStatus = WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.LatestStatuses); AppendMissingGraphMLAttributeValues(oGraphMLXmlDocument, oScreenNameDictionary, true, bIncludeLatestStatus, oRequestStatistics); AppendRepliesToAndMentionsXmlNodes(oGraphMLXmlDocument, oScreenNameDictionary, WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.RepliesToEdges), WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.MentionsEdges) ); }
//************************************************************************* // Method: AppendSharedResponderEdges() // /// <summary> /// Appends an edge XML node for each pair of videos for which the same /// person responded. /// </summary> /// /// <param name="oGraphMLXmlDocument"> /// The GraphMLXmlDocument being populated. /// </param> /// /// <param name="oVideoIDs"> /// The set of unique video IDs. /// </param> /// /// <param name="iMaximumResponses"> /// Maximum number of responses to request, or Int32.MaxValue for no limit. /// </param> /// /// <param name="sUrlPattern"> /// URL to get the responses for one video, with "{0}" where the video ID /// goes. /// </param> /// /// <param name="sResponderTitle"> /// Title describing the person who has responded. Samples: "commenter", /// "video responder". /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> //************************************************************************* protected void AppendSharedResponderEdges( GraphMLXmlDocument oGraphMLXmlDocument, HashSet<String> oVideoIDs, Int32 iMaximumResponses, String sUrlPattern, String sResponderTitle, RequestStatistics oRequestStatistics ) { Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oVideoIDs != null); Debug.Assert(iMaximumResponses > 0); Debug.Assert( !String.IsNullOrEmpty(sUrlPattern) ); Debug.Assert(sUrlPattern.IndexOf("{0}") >= 0); Debug.Assert( !String.IsNullOrEmpty(sResponderTitle) ); Debug.Assert(oRequestStatistics != null); AssertValid(); // The key is the name of an author and the value is a LinkedList of // the video IDs to which the author has responded. Dictionary< String, LinkedList<String> > oAuthorUserNameDictionary = new Dictionary< String, LinkedList<String> >(); foreach (String sVideoID in oVideoIDs) { ReportProgress(String.Format( "Getting {0}s for the video with the ID \"{1}\"." , sResponderTitle, sVideoID ) ); // This is to prevent self-loop edges that would result when the // same author responds to the same video more than once. HashSet<String> oAuthorUserNames = new HashSet<String>(); String sUrl = String.Format(sUrlPattern, sVideoID); // The document consists of an "entry" XML node for each response. foreach ( XmlNode oEntryXmlNode in EnumerateXmlNodes(sUrl, "a:feed/a:entry", iMaximumResponses, true, oRequestStatistics) ) { XmlNamespaceManager oXmlNamespaceManager = CreateXmlNamespaceManager(oEntryXmlNode.OwnerDocument); String sAuthorUserName; if ( XmlUtil2.TrySelectSingleNodeAsString(oEntryXmlNode, "a:author/a:name/text()", oXmlNamespaceManager, out sAuthorUserName) && !oAuthorUserNames.Contains(sAuthorUserName) ) { AddVideoIDToDictionary(sAuthorUserName, sVideoID, oAuthorUserNameDictionary); oAuthorUserNames.Add(sAuthorUserName); } } } ReportProgress("Adding edges for shared " + sResponderTitle + "s."); AppendEdgesFromDictionary(oAuthorUserNameDictionary, oGraphMLXmlDocument, "Shared " + sResponderTitle); }
//************************************************************************* // Method: CreateGraphMLXmlDocument() // /// <summary> /// Creates a GraphMLXmlDocument representing a network of YouTube videos. /// </summary> /// /// <returns> /// A GraphMLXmlDocument representing a network of YouTube videos. The /// document includes GraphML-attribute definitions but no vertices or /// edges. /// </returns> //************************************************************************* protected GraphMLXmlDocument CreateGraphMLXmlDocument() { AssertValid(); GraphMLXmlDocument oGraphMLXmlDocument = new GraphMLXmlDocument(false); DefineRelationshipGraphMLAttribute(oGraphMLXmlDocument); oGraphMLXmlDocument.DefineGraphMLAttribute(false, TitleID, "Title", "string", null); oGraphMLXmlDocument.DefineGraphMLAttribute(false, RatingID, "Rating", "double", null); oGraphMLXmlDocument.DefineGraphMLAttribute(false, ViewsID, "Views", "int", null); oGraphMLXmlDocument.DefineGraphMLAttribute(false, FavoritedID, "Favorited", "int", null); oGraphMLXmlDocument.DefineGraphMLAttribute(false, CommentsID, "Comments", "int", null); oGraphMLXmlDocument.DefineGraphMLAttribute(false, CreatedDateUtcID, "Created Date (UTC)", "string", null); DefineImageFileGraphMLAttribute(oGraphMLXmlDocument); DefineCustomMenuGraphMLAttributes(oGraphMLXmlDocument); return (oGraphMLXmlDocument); }
//************************************************************************* // Method: CreateGraphMLXmlDocument() // /// <summary> /// Creates a GraphMLXmlDocument representing a network of Flickr related /// tags. /// </summary> /// /// <param name="bIncludeSampleThumbnails"> /// true to include a sample thumbnail for each tag. /// photos. /// </param> /// /// <returns> /// A GraphMLXmlDocument representing a network of Flickr related tags. /// The document includes GraphML-attribute definitions but no vertices or /// edges. /// </returns> //************************************************************************* protected GraphMLXmlDocument CreateGraphMLXmlDocument( Boolean bIncludeSampleThumbnails ) { AssertValid(); GraphMLXmlDocument oGraphMLXmlDocument = new GraphMLXmlDocument(true); DefineLabelGraphMLAttribute(oGraphMLXmlDocument); DefineCustomMenuGraphMLAttributes(oGraphMLXmlDocument); if (bIncludeSampleThumbnails) { DefineImageFileGraphMLAttribute(oGraphMLXmlDocument); } return (oGraphMLXmlDocument); }
//************************************************************************* // Method: GetRelatedTagsRecursive() // /// <summary> /// Recursively gets a tag's related tags. /// </summary> /// /// <param name="sTag"> /// Tag to get related tags for. /// </param> /// /// <param name="eWhatToInclude"> /// Specifies what should be included in the network. /// </param> /// /// <param name="eNetworkLevel"> /// Network level to include. Must be NetworkLevel.One, OnePointFive, or /// Two. /// </param> /// /// <param name="sApiKey"> /// Flickr API key. /// </param> /// /// <param name="iRecursionLevel"> /// Recursion level for this call. Must be 1 or 2. Gets incremented when /// recursing. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="oTagDictionary"> /// The key is the tag name and the value is the corresponding GraphML XML /// node that represents the tag. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> //************************************************************************* protected void GetRelatedTagsRecursive( String sTag, WhatToInclude eWhatToInclude, NetworkLevel eNetworkLevel, String sApiKey, Int32 iRecursionLevel, GraphMLXmlDocument oGraphMLXmlDocument, Dictionary<String, XmlNode> oTagDictionary, RequestStatistics oRequestStatistics ) { Debug.Assert( !String.IsNullOrEmpty(sTag) ); Debug.Assert(eNetworkLevel == NetworkLevel.One || eNetworkLevel == NetworkLevel.OnePointFive || eNetworkLevel == NetworkLevel.Two); Debug.Assert( !String.IsNullOrEmpty(sApiKey) ); Debug.Assert(iRecursionLevel == 1 || iRecursionLevel == 2); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oTagDictionary != null); Debug.Assert(oRequestStatistics != null); AssertValid(); /* Here is what this method should do, based on the eNetworkLevel and iRecursionLevel parameters. eNetworkLevel |One | OnePointFive | Two ---|------------------| ------------------| ----------------- i 1 |Add all vertices. | Add all vertices. | Add all vertices. R | | | e |Add all edges. | Add all edges. | Add all edges. c | | | u |Do not recurse. | Recurse. | Recurse. r | | | s ---|------------------|-------------------|------------------ i 2 |Impossible. | Do not add | Add all vertices. o | | vertices. | n | | | L | | Add edges only if | Add all edges. e | | vertices are | v | | already included. | e | | | l | | Do not recurse. | Do not recurse. | | | ---|------------------|-------------------|------------------ */ Boolean bNeedToRecurse = GetNeedToRecurse(eNetworkLevel, iRecursionLevel); Boolean bNeedToAppendVertices = GetNeedToAppendVertices( eNetworkLevel, iRecursionLevel); ReportProgress("Getting tags related to \"" + sTag + "\"."); String sUrl = GetFlickrMethodUrl( "flickr.tags.getRelated", sApiKey, "&tag=" + UrlUtil.EncodeUrlParameter(sTag) ); XmlDocument oXmlDocument; try { oXmlDocument = GetXmlDocument(sUrl, oRequestStatistics); } catch (Exception oException) { // If the exception is not a WebException or XmlException, or if // none of the network has been obtained yet, throw the exception. if (!ExceptionIsWebOrXml(oException) || !oGraphMLXmlDocument.HasVertexXmlNode) { throw oException; } return; } // The document consists of a single "tags" node with zero or more // "tag" child nodes. String sOtherTag = null; XmlNodeList oTagNodes = oXmlDocument.DocumentElement.SelectNodes( "tags/tag"); if (oTagNodes.Count > 0) { AppendVertexXmlNode(sTag, oGraphMLXmlDocument, oTagDictionary); } foreach (XmlNode oTagNode in oTagNodes) { sOtherTag = XmlUtil2.SelectRequiredSingleNodeAsString(oTagNode, "text()", null); if (bNeedToAppendVertices) { AppendVertexXmlNode(sOtherTag, oGraphMLXmlDocument, oTagDictionary); } if ( bNeedToAppendVertices || oTagDictionary.ContainsKey(sOtherTag) ) { oGraphMLXmlDocument.AppendEdgeXmlNode(sTag, sOtherTag); } } if (bNeedToRecurse) { foreach (XmlNode oTagNode in oTagNodes) { sOtherTag = XmlUtil2.SelectRequiredSingleNodeAsString(oTagNode, "text()", null); GetRelatedTagsRecursive(sOtherTag, eWhatToInclude, eNetworkLevel, sApiKey, 2, oGraphMLXmlDocument, oTagDictionary, oRequestStatistics); } } }
//************************************************************************* // Method: AppendVertexXmlNodes() // /// <summary> /// Appends a vertex XML node for each person who has tweeted a specified /// search term. /// </summary> /// /// <param name="sSearchTerm"> /// The term to search for. /// </param> /// /// <param name="eWhatToInclude"> /// Specifies what should be included in the network. /// </param> /// /// <param name="iMaximumPeoplePerRequest"> /// Maximum number of people to request for each query, or Int32.MaxValue /// for no limit. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// The GraphMLXmlDocument being populated. /// </param> /// /// <param name="oScreenNameDictionary"> /// The key is the screen name in lower case and the value is the /// corresponding TwitterVertex. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> //************************************************************************* protected void AppendVertexXmlNodes( String sSearchTerm, WhatToInclude eWhatToInclude, Int32 iMaximumPeoplePerRequest, GraphMLXmlDocument oGraphMLXmlDocument, Dictionary<String, TwitterVertex> oScreenNameDictionary, RequestStatistics oRequestStatistics ) { Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) ); Debug.Assert(iMaximumPeoplePerRequest > 0); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oScreenNameDictionary != null); Debug.Assert(oRequestStatistics != null); AssertValid(); // Convert spaces in the search term to a plus sign. // // (Note: Don't try to use Twitter's "show_user" URL parameter in an // attempt to get a "user" XML node for each author. That's not what // the URL parameter does.) String sUrl = String.Format( "http://search.twitter.com/search.atom?q={0}&rpp=100" , UrlUtil.EncodeUrlParameter(sSearchTerm).Replace("%20", "+") ); ReportProgress("Getting a list of tweets."); Boolean bIncludeStatistics = WhatToIncludeFlagIsSet( eWhatToInclude, WhatToInclude.Statistics); Boolean bIncludeStatuses = WhatToIncludeFlagIsSet( eWhatToInclude, WhatToInclude.Statuses); // The document consists of an "entry" XML node for each tweet that // contains the search term. Multiple tweets may have the same author, // so we have to enumerate until the requested maximum number of unique // authors is reached. foreach ( XmlNode oAuthorNameXmlNode in EnumerateXmlNodes( sUrl, "a:feed/a:entry/a:author/a:name", "a", AtomNamespaceUri, 15, Int32.MaxValue, true, false, oRequestStatistics) ) { if (oScreenNameDictionary.Count == iMaximumPeoplePerRequest) { break; } // The author name is in this format: // // james_laker (James Laker) String sAuthorName; if ( !XmlUtil2.TrySelectSingleNodeAsString(oAuthorNameXmlNode, "text()", null, out sAuthorName) ) { continue; } Int32 iIndexOfSpace = sAuthorName.IndexOf(' '); String sScreenName = sAuthorName; if (iIndexOfSpace != -1) { sScreenName = sAuthorName.Substring(0, iIndexOfSpace); } sScreenName = sScreenName.ToLower(); TwitterVertex oTwitterVertex; if ( !TryAppendVertexXmlNode(sScreenName, null, oGraphMLXmlDocument, oScreenNameDictionary, bIncludeStatistics, false, out oTwitterVertex) ) { // A tweet for the author has already been found. continue; } XmlNode oVertexXmlNode = oTwitterVertex.VertexXmlNode; XmlNode oEntryXmlNode = oAuthorNameXmlNode.ParentNode.ParentNode; XmlNamespaceManager oXmlNamespaceManager = new XmlNamespaceManager( oEntryXmlNode.OwnerDocument.NameTable); oXmlNamespaceManager.AddNamespace("a", AtomNamespaceUri); // Get the image URL and status for the tweet's author. AppendStringGraphMLAttributeValue(oEntryXmlNode, "a:link[@rel='image']/@href", oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode, ImageFileID); String sStatus; if ( XmlUtil2.TrySelectSingleNodeAsString(oEntryXmlNode, "a:title/text()", oXmlNamespaceManager, out sStatus) ) { String sStatusDateUtc; if ( XmlUtil2.TrySelectSingleNodeAsString(oEntryXmlNode, "a:published/text()", oXmlNamespaceManager, out sStatusDateUtc) ) { sStatusDateUtc = TwitterDateParser.ParseTwitterDate( sStatusDateUtc); } oTwitterVertex.StatusForAnalysis = sStatus; oTwitterVertex.StatusForAnalysisDateUtc = sStatusDateUtc; if (bIncludeStatuses) { oGraphMLXmlDocument.AppendGraphMLAttributeValue( oVertexXmlNode, StatusID, sStatus); if ( !String.IsNullOrEmpty(sStatusDateUtc) ) { oGraphMLXmlDocument.AppendGraphMLAttributeValue( oVertexXmlNode, StatusDateUtcID, sStatusDateUtc); } } } } }
//************************************************************************* // Method: AppendSampleThumbnails() // /// <summary> /// Appends sample thumbnails to the GraphMLXmlDocument being populated. /// </summary> /// /// <param name="oTagDictionary"> /// The key is the tag name and the value is the corresponding GraphML XML /// node that represents the tag. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="sApiKey"> /// Flickr API key. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> //************************************************************************* protected void AppendSampleThumbnails( Dictionary<String, XmlNode> oTagDictionary, GraphMLXmlDocument oGraphMLXmlDocument, String sApiKey, RequestStatistics oRequestStatistics ) { Debug.Assert(oTagDictionary != null); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert( !String.IsNullOrEmpty(sApiKey) ); Debug.Assert(oRequestStatistics != null); AssertValid(); foreach (KeyValuePair<String, XmlNode> oKeyValuePair in oTagDictionary) { String sTag = oKeyValuePair.Key; ReportProgress("Getting sample image file for \"" + sTag + "\"."); String sSampleImageUrl; if ( TryGetSampleImageUrl(sTag, sApiKey, oRequestStatistics, out sSampleImageUrl) ) { oGraphMLXmlDocument.AppendGraphMLAttributeValue( oKeyValuePair.Value, ImageFileID, sSampleImageUrl); } } }
//************************************************************************* // Method: DefineImageFileGraphMLAttribute() // /// <summary> /// Defines a GraphML-Attribute for vertex image files. /// </summary> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> //************************************************************************* protected void DefineImageFileGraphMLAttribute( GraphMLXmlDocument oGraphMLXmlDocument ) { Debug.Assert(oGraphMLXmlDocument != null); AssertValid(); oGraphMLXmlDocument.DefineGraphMLAttribute(false, ImageFileID, ImageColumnName, "string", null); }
//************************************************************************* // Method: DefineLabelGraphMLAttribute() // /// <summary> /// Defines a GraphML-Attribute for vertex labels. /// </summary> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> //************************************************************************* protected void DefineLabelGraphMLAttribute( GraphMLXmlDocument oGraphMLXmlDocument ) { Debug.Assert(oGraphMLXmlDocument != null); AssertValid(); oGraphMLXmlDocument.DefineGraphMLAttribute(false, LabelID, LabelColumnName, "string", null); }
//************************************************************************* // Method: AppendStringGraphMLAttributeValue() // /// <summary> /// Appends a String GraphML-Attribute value to an edge or vertex XML node. /// </summary> /// /// <param name="oXmlNodeToSelectFrom"> /// Node to select from. /// </param> /// /// <param name="sXPath"> /// XPath expression to a String descendant of <paramref /// name="oXmlNodeToSelectFrom" />. /// </param> /// /// <param name="oXmlNamespaceManager"> /// NamespaceManager to use, or null to not use one. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="oEdgeOrVertexXmlNode"> /// The edge or vertex XML node from <paramref /// name="oGraphMLXmlDocument" /> to add the GraphML attribute value to. /// </param> /// /// <param name="sGraphMLAttributeID"> /// GraphML ID of the attribute. /// </param> /// /// <returns> /// true if the GraphML-Attribute was appended. /// </returns> /// /// <remarks> /// This method selects from <paramref name="oXmlNodeToSelectFrom" /> using /// the <paramref name="sXPath" /> expression. If the selection is /// successful, the specified String value gets stored on <paramref /// name="oEdgeOrVertexXmlNode" /> as a Graph-ML Attribute. /// </remarks> //************************************************************************* protected Boolean AppendStringGraphMLAttributeValue( XmlNode oXmlNodeToSelectFrom, String sXPath, XmlNamespaceManager oXmlNamespaceManager, GraphMLXmlDocument oGraphMLXmlDocument, XmlNode oEdgeOrVertexXmlNode, String sGraphMLAttributeID ) { Debug.Assert(oXmlNodeToSelectFrom != null); Debug.Assert( !String.IsNullOrEmpty(sXPath) ); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oEdgeOrVertexXmlNode != null); Debug.Assert( !String.IsNullOrEmpty(sGraphMLAttributeID) ); AssertValid(); String sAttributeValue; if ( XmlUtil2.TrySelectSingleNodeAsString(oXmlNodeToSelectFrom, sXPath, oXmlNamespaceManager, out sAttributeValue) ) { oGraphMLXmlDocument.AppendGraphMLAttributeValue( oEdgeOrVertexXmlNode, sGraphMLAttributeID, sAttributeValue); return (true); } return (false); }
//************************************************************************* // Method: DefineCustomMenuGraphMLAttributes() // /// <summary> /// Defines the GraphML-Attributes for custom menu items. /// </summary> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> //************************************************************************* protected void DefineCustomMenuGraphMLAttributes( GraphMLXmlDocument oGraphMLXmlDocument ) { Debug.Assert(oGraphMLXmlDocument != null); AssertValid(); oGraphMLXmlDocument.DefineGraphMLAttribute(false, MenuTextID, MenuTextColumnName, "string", null); oGraphMLXmlDocument.DefineGraphMLAttribute(false, MenuActionID, MenuActionColumnName, "string", null); }
//************************************************************************* // Method: AppendEdgeXmlNode() // /// <summary> /// Appends an edge XML node to a GraphML document. /// </summary> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="sVertex1ID"> /// ID of the edge's first vertex. /// </param> /// /// <param name="sVertex2ID"> /// ID of the edge's second vertex. /// </param> /// /// <param name="sRelationship"> /// The value of the edge's RelationshipID GraphML-attribute. /// </param> /// /// <returns> /// The new edge XML node. /// </returns> //************************************************************************* protected XmlNode AppendEdgeXmlNode( GraphMLXmlDocument oGraphMLXmlDocument, String sVertex1ID, String sVertex2ID, String sRelationship ) { Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert( !String.IsNullOrEmpty(sVertex1ID) ); Debug.Assert( !String.IsNullOrEmpty(sVertex2ID) ); Debug.Assert( !String.IsNullOrEmpty(sRelationship) ); AssertValid(); XmlNode oEdgeXmlNode = oGraphMLXmlDocument.AppendEdgeXmlNode( sVertex1ID, sVertex2ID); oGraphMLXmlDocument.AppendGraphMLAttributeValue(oEdgeXmlNode, RelationshipID, sRelationship); return (oEdgeXmlNode); }
//************************************************************************* // Method: OnNetworkObtainedWithoutTerminatingException() // /// <summary> /// Call this when part or all of the network has been obtained without a /// terminating exception occurring. /// </summary> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> /// /// <remarks> /// If the entire network has been obtained, this method does nothing. /// Otherwise, a PartialNetworkException is thrown. /// </remarks> //************************************************************************* protected void OnNetworkObtainedWithoutTerminatingException( GraphMLXmlDocument oGraphMLXmlDocument, RequestStatistics oRequestStatistics ) { Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oRequestStatistics != null); AssertValid(); if (oRequestStatistics.UnexpectedExceptions > 0) { // The network is partial. throw new PartialNetworkException(oGraphMLXmlDocument, oRequestStatistics); } }
//************************************************************************* // Method: GetUserNetworkRecursive() // /// <summary> /// Recursively gets a network of Twitter users. /// </summary> /// /// <param name="sScreenName"> /// The screen name to use in this call. /// </param> /// /// <param name="eWhatToInclude"> /// Specifies what should be included in the network. /// </param> /// /// <param name="bIncludeFollowedThisCall"> /// true to include the people followed by the user, false to include the /// people following the user. /// </param> /// /// <param name="eNetworkLevel"> /// Network level to include. Must be NetworkLevel.One, OnePointFive, or /// Two. /// </param> /// /// <param name="iMaximumPeoplePerRequest"> /// Maximum number of people to request for each query, or Int32.MaxValue /// for no limit. /// </param> /// /// <param name="iRecursionLevel"> /// Recursion level for this call. Must be 1 or 2. Gets incremented when /// recursing. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="oScreenNameDictionary"> /// The key is the screen name in lower case and the value is the /// corresponding TwitterVertex. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> //************************************************************************* protected void GetUserNetworkRecursive( String sScreenName, WhatToInclude eWhatToInclude, Boolean bIncludeFollowedThisCall, NetworkLevel eNetworkLevel, Int32 iMaximumPeoplePerRequest, Int32 iRecursionLevel, GraphMLXmlDocument oGraphMLXmlDocument, Dictionary<String, TwitterVertex> oScreenNameDictionary, RequestStatistics oRequestStatistics ) { Debug.Assert( !String.IsNullOrEmpty(sScreenName) ); Debug.Assert(eNetworkLevel == NetworkLevel.One || eNetworkLevel == NetworkLevel.OnePointFive || eNetworkLevel == NetworkLevel.Two); Debug.Assert(iMaximumPeoplePerRequest > 0); Debug.Assert(iRecursionLevel == 1 || iRecursionLevel == 2); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oScreenNameDictionary != null); Debug.Assert(oRequestStatistics != null); AssertValid(); /* Here is what this method should do, based on the eNetworkLevel and iRecursionLevel parameters. It's assumed that eWhatToInclude.FollowedFollowerEdge is set. eNetworkLevel |One | OnePointFive | Two ---|------------------| ------------------| ----------------- i 1 |Add all vertices. | Add all vertices. | Add all vertices. R | | | e |Add all edges. | Add all edges. | Add all edges. c | | | u |Do not recurse. | Recurse. | Recurse. r | | | s ---|------------------|-------------------|------------------ i 2 |Impossible. | Do not add | Add all vertices. o | | vertices. | n | | | L | | Add edges only if | Add all edges. e | | vertices are | v | | already included. | e | | | l | | Do not recurse. | Do not recurse. | | | ---|------------------|-------------------|------------------ */ Boolean bNeedToRecurse = GetNeedToRecurse(eNetworkLevel, iRecursionLevel); List<String> oScreenNamesToRecurse = new List<String>(); Boolean bIncludeLatestStatuses = WhatToIncludeFlagIsSet( eWhatToInclude, WhatToInclude.LatestStatuses); ReportProgressForFollowedOrFollowing(sScreenName, bIncludeFollowedThisCall); Boolean bThisUserAppended = false; // If the GraphMLXmlDocument already contains at least one vertex node, // then this is the second time that this method has been called, a // partial network has already been obtained, and most errors should // now be skipped. However, if none of the network has been obtained // yet, errors on page 1 should throw an immediate exception. Boolean bSkipMostPage1Errors = oGraphMLXmlDocument.HasVertexXmlNode; // The document consists of a single "users" node with zero or more // "user" child nodes. foreach ( XmlNode oUserXmlNode in EnumerateXmlNodes( GetFollowedOrFollowingUrl(sScreenName, bIncludeFollowedThisCall), "users_list/users/user", null, null, Int32.MaxValue, iMaximumPeoplePerRequest, false, bSkipMostPage1Errors, oRequestStatistics) ) { String sOtherScreenName; if ( !TryGetScreenName(oUserXmlNode, out sOtherScreenName) ) { // Nothing can be done without a screen name. continue; } if (!bThisUserAppended) { // Append a vertex node for this request's user. // // This used to be done after the foreach loop, which avoided // the need for a "bThisUserAppended" flag. That caused the // following bug: If a Twitter error occurred within // EnumerateXmlNodes() after some edges had been added, and the // user decided to import the resulting partial network, the // GraphML might contain edges that referenced "this user" // without containing a vertex for "this user." That is an // illegal state for GraphML, which the ExcelTemplate project // caught and reported as an error. TryAppendVertexXmlNode(sScreenName, null, oGraphMLXmlDocument, oScreenNameDictionary, true, bIncludeLatestStatuses); bThisUserAppended = true; } Boolean bNeedToAppendVertices = GetNeedToAppendVertices( eNetworkLevel, iRecursionLevel); if (bNeedToAppendVertices) { if ( TryAppendVertexXmlNode(sOtherScreenName, oUserXmlNode, oGraphMLXmlDocument, oScreenNameDictionary, true, bIncludeLatestStatuses) && bNeedToRecurse ) { oScreenNamesToRecurse.Add(sOtherScreenName); } } if ( WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.FollowedFollowerEdges) ) { if ( bNeedToAppendVertices || oScreenNameDictionary.ContainsKey(sOtherScreenName) ) { XmlNode oEdgeXmlNode; if (bIncludeFollowedThisCall) { oEdgeXmlNode = AppendEdgeXmlNode(oGraphMLXmlDocument, sScreenName, sOtherScreenName, "Followed"); } else { oEdgeXmlNode = AppendEdgeXmlNode(oGraphMLXmlDocument, sOtherScreenName, sScreenName, "Follower"); } AppendRelationshipDateUtcGraphMLAttributeValue( oGraphMLXmlDocument, oEdgeXmlNode, oRequestStatistics); } } } if (bNeedToRecurse) { foreach (String sScreenNameToRecurse in oScreenNamesToRecurse) { GetUserNetworkRecursive(sScreenNameToRecurse, eWhatToInclude, bIncludeFollowedThisCall, eNetworkLevel, iMaximumPeoplePerRequest, 2, oGraphMLXmlDocument, oScreenNameDictionary, oRequestStatistics); } } }
//************************************************************************* // Method: GetUserNetworkRecursive() // /// <summary> /// Recursively gets a network of YouTube users. /// </summary> /// /// <param name="sUserName"> /// The user name to use in this call. /// </param> /// /// <param name="eWhatToInclude"> /// Specifies what should be included in the network. /// </param> /// /// <param name="bIncludeFriendsThisCall"> /// true to include the user's friends, false to include the people /// subscribed to by the user. /// </param> /// /// <param name="eNetworkLevel"> /// Network level to include. Must be NetworkLevel.One, OnePointFive, or /// Two. /// </param> /// /// <param name="iMaximumPeoplePerRequest"> /// Maximum number of people to request for each query, or Int32.MaxValue /// for no limit. /// </param> /// /// <param name="iRecursionLevel"> /// Recursion level for this call. Must be 1 or 2. Gets incremented when /// recursing. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="oUserNameDictionary"> /// The key is the user name and the value is the corresponding GraphML XML /// node that represents the user. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> //************************************************************************* protected void GetUserNetworkRecursive( String sUserName, WhatToInclude eWhatToInclude, Boolean bIncludeFriendsThisCall, NetworkLevel eNetworkLevel, Int32 iMaximumPeoplePerRequest, Int32 iRecursionLevel, GraphMLXmlDocument oGraphMLXmlDocument, Dictionary<String, XmlNode> oUserNameDictionary, RequestStatistics oRequestStatistics ) { Debug.Assert( !String.IsNullOrEmpty(sUserName) ); Debug.Assert(eNetworkLevel == NetworkLevel.One || eNetworkLevel == NetworkLevel.OnePointFive || eNetworkLevel == NetworkLevel.Two); Debug.Assert(iMaximumPeoplePerRequest > 0); Debug.Assert(iRecursionLevel == 1 || iRecursionLevel == 2); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oUserNameDictionary != null); Debug.Assert(oRequestStatistics != null); AssertValid(); /* Here is what this method should do, based on the eNetworkLevel and iRecursionLevel parameters. eNetworkLevel |One | OnePointFive | Two ---|------------------| ------------------| ----------------- i 1 |Add all vertices. | Add all vertices. | Add all vertices. R | | | e |Add all edges. | Add all edges. | Add all edges. c | | | u |Do not recurse. | Recurse. | Recurse. r | | | s ---|------------------|-------------------|------------------ i 2 |Impossible. | Do not add | Add all vertices. o | | vertices. | n | | | L | | Add edges only if | Add all edges. e | | vertices are | v | | already included. | e | | | l | | Do not recurse. | Do not recurse. | | | ---|------------------|-------------------|------------------ */ Boolean bNeedToRecurse = GetNeedToRecurse(eNetworkLevel, iRecursionLevel); List<String> oUserNamesToRecurse = new List<String>(); ReportProgressForFriendsOrSubscriptions(sUserName, bIncludeFriendsThisCall); Boolean bThisUserAppended = false; // If the GraphMLXmlDocument already contains at least one vertex node, // then this is the second time that this method has been called, a // partial network has already been obtained, and most errors should // now be skipped. However, if none of the network has been obtained // yet, errors on page 1 should throw an immediate exception. Boolean bSkipMostPage1Errors = oGraphMLXmlDocument.HasVertexXmlNode; // The document consists of a single "feed" node with zero or more // "entry" child nodes. foreach ( XmlNode oEntryXmlNode in EnumerateXmlNodes( GetFriendsOrSubscriptionsUrl(sUserName, bIncludeFriendsThisCall), "a:feed/a:entry", iMaximumPeoplePerRequest, bSkipMostPage1Errors, oRequestStatistics) ) { XmlNamespaceManager oXmlNamespaceManager = CreateXmlNamespaceManager(oEntryXmlNode.OwnerDocument); String sOtherUserName; if ( !XmlUtil2.TrySelectSingleNodeAsString(oEntryXmlNode, "yt:username/text()", oXmlNamespaceManager, out sOtherUserName) ) { continue; } if (!bThisUserAppended) { // Append a vertex node for this request's user. // // This used to be done after the foreach loop, which avoided // the need for a "bThisUserAppended" flag. That caused the // following bug: If a YouTube error occurred within // EnumerateXmlNodes() after some edges had been added, and the // user decided to import the resulting partial network, the // GraphML might contain edges that referenced "this user" // without containing a vertex for "this user." That is an // illegal state for GraphML, which the ExcelTemplate project // caught and reported as an error. TryAppendVertexXmlNode(sUserName, null, oGraphMLXmlDocument, oUserNameDictionary); bThisUserAppended = true; } Boolean bNeedToAppendVertices = GetNeedToAppendVertices( eNetworkLevel, iRecursionLevel); if (bNeedToAppendVertices) { if ( TryAppendVertexXmlNode(sOtherUserName, oEntryXmlNode, oGraphMLXmlDocument, oUserNameDictionary) && bNeedToRecurse ) { oUserNamesToRecurse.Add(sOtherUserName); } } if ( bNeedToAppendVertices || oUserNameDictionary.ContainsKey(sOtherUserName) ) { String sRelationship; if (bIncludeFriendsThisCall) { sRelationship = "Friend of"; } else { sRelationship = "Subscribes to"; String sSubscriptionType = null; if ( XmlUtil2.TrySelectSingleNodeAsString(oEntryXmlNode, "a:category[@scheme='http://gdata.youtube.com/schemas/" + "2007/subscriptiontypes.cat']/@term", oXmlNamespaceManager, out sSubscriptionType) ) { sRelationship += " " + sSubscriptionType; } } AppendEdgeXmlNode(oGraphMLXmlDocument, sUserName, sOtherUserName, sRelationship); } } if (bNeedToRecurse) { foreach (String sUserNameToRecurse in oUserNamesToRecurse) { GetUserNetworkRecursive(sUserNameToRecurse, eWhatToInclude, bIncludeFriendsThisCall, eNetworkLevel, iMaximumPeoplePerRequest, 2, oGraphMLXmlDocument, oUserNameDictionary, oRequestStatistics); } } }
//************************************************************************* // Method: DefineRelationshipGraphMLAttribute() // /// <summary> /// Defines a GraphML-Attribute for edge relationships. /// </summary> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> //************************************************************************* protected void DefineRelationshipGraphMLAttribute( GraphMLXmlDocument oGraphMLXmlDocument ) { Debug.Assert(oGraphMLXmlDocument != null); AssertValid(); oGraphMLXmlDocument.DefineGraphMLAttribute(true, RelationshipID, "Relationship", "string", null); }
//************************************************************************* // Method: AppendFollowedEdgeXmlNodes() // /// <summary> /// Appends an edge XML node for each pair of people who have tweeted a /// specified search term and one follows the other. /// </summary> /// /// <param name="eWhatToInclude"> /// Specifies what should be included in the network. /// </param> /// /// <param name="iMaximumPeoplePerRequest"> /// Maximum number of people to request for each query, or Int32.MaxValue /// for no limit. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="oScreenNameDictionary"> /// The key is the screen name in lower case and the value is the /// corresponding TwitterVertex. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> //************************************************************************* protected void AppendFollowedEdgeXmlNodes( WhatToInclude eWhatToInclude, Int32 iMaximumPeoplePerRequest, GraphMLXmlDocument oGraphMLXmlDocument, Dictionary<String, TwitterVertex> oScreenNameDictionary, RequestStatistics oRequestStatistics ) { Debug.Assert(iMaximumPeoplePerRequest > 0); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oScreenNameDictionary != null); Debug.Assert(oRequestStatistics != null); AssertValid(); Boolean bIncludeStatistics = WhatToIncludeFlagIsSet( eWhatToInclude, WhatToInclude.Statistics); // Look at the people followed by each author, and if a followed has // also tweeted the search term, add an edge between the author and the // followed. foreach (String sScreenName in oScreenNameDictionary.Keys) { ReportProgressForFollowedOrFollowing(sScreenName, true); foreach ( XmlNode oOtherUserXmlNode in EnumerateXmlNodes( GetFollowedOrFollowingUrl(sScreenName, true), "users_list/users/user", null, null, Int32.MaxValue, iMaximumPeoplePerRequest, false, true, oRequestStatistics) ) { String sOtherScreenName; if ( !TryGetScreenName(oOtherUserXmlNode, out sOtherScreenName) ) { continue; } TwitterVertex oOtherTwitterVertex; if ( oScreenNameDictionary.TryGetValue(sOtherScreenName, out oOtherTwitterVertex) ) { XmlNode oOtherVertexXmlNode = oOtherTwitterVertex.VertexXmlNode; XmlNode oEdgeXmlNode = AppendEdgeXmlNode( oGraphMLXmlDocument, sScreenName, sOtherScreenName, "Followed"); AppendRelationshipDateUtcGraphMLAttributeValue( oGraphMLXmlDocument, oEdgeXmlNode, oRequestStatistics); if (bIncludeStatistics && !AppendFromUserXmlNodeCalled(oGraphMLXmlDocument, oOtherVertexXmlNode) ) { // The other vertex node has no statistics. Add them. AppendFromUserXmlNode(oOtherUserXmlNode, oGraphMLXmlDocument, oOtherTwitterVertex, bIncludeStatistics, false); } } } } }
//************************************************************************* // Method: TryAppendVertexXmlNode() // /// <summary> /// Appends a vertex XML node to the GraphML document for a user if such a /// node doesn't already exist. /// </summary> /// /// <param name="sUserName"> /// User name to add a vertex XML node for. /// </param> /// /// <param name="oEntryXmlNode"> /// The "entry" XML node returned by YouTube, or null if an entry node /// isn't available. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="oUserNameDictionary"> /// The key is the user name and the value is the corresponding GraphML XML /// node that represents the user. /// </param> /// /// <returns> /// true if a vertex XML node was added, false if a vertex XML node already /// exists. /// </returns> //************************************************************************* protected Boolean TryAppendVertexXmlNode( String sUserName, XmlNode oEntryXmlNode, GraphMLXmlDocument oGraphMLXmlDocument, Dictionary<String, XmlNode> oUserNameDictionary ) { Debug.Assert( !String.IsNullOrEmpty(sUserName) ); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oUserNameDictionary != null); XmlNode oVertexXmlNode; if ( oUserNameDictionary.TryGetValue(sUserName, out oVertexXmlNode) ) { return (false); } oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode(sUserName); oUserNameDictionary.Add(sUserName, oVertexXmlNode); oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, MenuTextID, "Open YouTube Page for This Person"); oGraphMLXmlDocument.AppendGraphMLAttributeValue( oVertexXmlNode, MenuActionID, String.Format(WebPageUrlPattern, sUserName) ); return (true); }
//************************************************************************* // Method: GetSearchNetworkInternal() // /// <summary> /// Gets the network of people who have tweeted a specified search term, /// given a GraphMLXmlDocument. /// </summary> /// /// <param name="sSearchTerm"> /// The term to search for. /// </param> /// /// <param name="eWhatToInclude"> /// Specifies what should be included in the network. /// </param> /// /// <param name="iMaximumPeoplePerRequest"> /// Maximum number of people to request for each query, or Int32.MaxValue /// for no limit. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// The GraphMLXmlDocument to populate with the requested network. /// </param> //************************************************************************* protected void GetSearchNetworkInternal( String sSearchTerm, WhatToInclude eWhatToInclude, Int32 iMaximumPeoplePerRequest, RequestStatistics oRequestStatistics, GraphMLXmlDocument oGraphMLXmlDocument ) { Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) ); Debug.Assert(iMaximumPeoplePerRequest > 0); Debug.Assert(oRequestStatistics != null); Debug.Assert(oGraphMLXmlDocument != null); AssertValid(); if ( WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.Statuses) ) { oGraphMLXmlDocument.DefineGraphMLAttribute(false, StatusID, "Tweet", "string", null); oGraphMLXmlDocument.DefineGraphMLAttribute(false, StatusDateUtcID, "Tweet Date (UTC)", "string", null); } // The key is the screen name and the value is the corresponding // TwitterVertex. This is used to prevent the same screen name from // being added to the XmlDocument twice. Dictionary<String, TwitterVertex> oScreenNameDictionary = new Dictionary<String, TwitterVertex>(); // First, add a vertex for each person who has tweeted the search term. AppendVertexXmlNodes(sSearchTerm, eWhatToInclude, iMaximumPeoplePerRequest, oGraphMLXmlDocument, oScreenNameDictionary, oRequestStatistics); if ( WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.FollowedEdges) ) { // Look at the people followed by each author, and if a followed // has also tweeted the search term, add an edge between the author // and the followed. AppendFollowedEdgeXmlNodes(eWhatToInclude, iMaximumPeoplePerRequest, oGraphMLXmlDocument, oScreenNameDictionary, oRequestStatistics); } Boolean bIncludeStatistics = WhatToIncludeFlagIsSet( eWhatToInclude, WhatToInclude.Statistics); AppendMissingGraphMLAttributeValues(oGraphMLXmlDocument, oScreenNameDictionary, bIncludeStatistics, false, oRequestStatistics); AppendRepliesToAndMentionsXmlNodes(oGraphMLXmlDocument, oScreenNameDictionary, WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.RepliesToEdges), WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.MentionsEdges) ); }
//************************************************************************* // Method: AppendAllStatisticGraphMLAttributeValues() // /// <overloads> /// Appends statistic GraphML attribute values to the GraphML document. /// </overloads> /// /// <summary> /// Appends statistic GraphML attribute values to the GraphML document for /// all users in the network. /// </summary> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="oUserNameDictionary"> /// The key is the user name and the value is the corresponding GraphML XML /// node that represents the user. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> //************************************************************************* protected void AppendAllStatisticGraphMLAttributeValues( GraphMLXmlDocument oGraphMLXmlDocument, Dictionary<String, XmlNode> oUserNameDictionary, RequestStatistics oRequestStatistics ) { Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oUserNameDictionary != null); Debug.Assert(oRequestStatistics != null); AssertValid(); foreach (KeyValuePair<String, XmlNode> oKeyValuePair in oUserNameDictionary) { String sUserName = oKeyValuePair.Key; ReportProgress( String.Format( "Getting statistics for \"{0}\"." , sUserName ) ); AppendAllStatisticGraphMLAttributeValues(sUserName, oKeyValuePair.Value, oGraphMLXmlDocument, oRequestStatistics); } }
//************************************************************************* // Method: AppendVertexXmlNode() // /// <summary> /// Appends a vertex XML node to the document for a tag if such a node /// doesn't already exist. /// </summary> /// /// <param name="sTag"> /// Tag to add a vertex XML node for. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="oTagDictionary"> /// The key is the tag name and the value is the corresponding GraphML XML /// node that represents the tag. /// </param> //************************************************************************* protected void AppendVertexXmlNode( String sTag, GraphMLXmlDocument oGraphMLXmlDocument, Dictionary<String, XmlNode> oTagDictionary ) { Debug.Assert( !String.IsNullOrEmpty(sTag) ); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oTagDictionary != null); if ( !oTagDictionary.ContainsKey(sTag) ) { XmlNode oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode( sTag); oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, LabelID, sTag); oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, MenuTextID, "Open Flickr Page for This Tag"); oGraphMLXmlDocument.AppendGraphMLAttributeValue( oVertexXmlNode, MenuActionID, String.Format( "http://www.flickr.com/photos/tags/{0}/" , UrlUtil.EncodeUrlParameter(sTag) ) ); oTagDictionary.Add(sTag, oVertexXmlNode); } }
//************************************************************************* // Method: AppendAllStatisticGraphMLAttributeValues() // /// <summary> /// Appends statistic GraphML attribute values to the GraphML document for /// one user in the network. /// </summary> /// /// <param name="sUserName"> /// The user name. /// </param> /// /// <param name="oVertexXmlNode"> /// The GraphML XML node corresponding to the user. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> //************************************************************************* protected void AppendAllStatisticGraphMLAttributeValues( String sUserName, XmlNode oVertexXmlNode, GraphMLXmlDocument oGraphMLXmlDocument, RequestStatistics oRequestStatistics ) { Debug.Assert( !String.IsNullOrEmpty(sUserName) ); Debug.Assert(oVertexXmlNode != null); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oRequestStatistics != null); AssertValid(); XmlDocument oXmlDocument; XmlNamespaceManager oXmlNamespaceManager; // Some of the statistics are available in the user's profile. String sUrl = "http://gdata.youtube.com/feeds/api/users/" + EncodeUrlParameter(sUserName); if ( TryGetXmlDocument(sUrl, oRequestStatistics, out oXmlDocument, out oXmlNamespaceManager) ) { AppendYouTubeDateGraphMLAttributeValue(oXmlDocument, "a:entry/a:published/text()", oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode, JoinedDateUtcID); AppendStringGraphMLAttributeValue(oXmlDocument, "a:entry/media:thumbnail/@url", oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode, ImageFileID); XmlNode oStatisticsXmlNode; if ( XmlUtil2.TrySelectSingleNode(oXmlDocument, "a:entry/yt:statistics", oXmlNamespaceManager, out oStatisticsXmlNode) ) { AppendInt32GraphMLAttributeValue(oStatisticsXmlNode, "@videoWatchCount", oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode, VideosWatchedID); AppendInt32GraphMLAttributeValue(oStatisticsXmlNode, "@subscriberCount", oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode, SubscribersID); } } // The remaining statistics must be obtained from the totalResults // XML node within documents obtained from other URLs. const String TotalResultsXPath = "a:feed/openSearch:totalResults/text()"; sUrl = GetFriendsOrSubscriptionsUrl(sUserName, true) + "?max-results=1"; AppendInt32GraphMLAttributeValue(sUrl, TotalResultsXPath, oGraphMLXmlDocument, oRequestStatistics, oVertexXmlNode, FriendsID); sUrl = GetFriendsOrSubscriptionsUrl(sUserName, false) + "?max-results=1"; AppendInt32GraphMLAttributeValue(sUrl, TotalResultsXPath, oGraphMLXmlDocument, oRequestStatistics, oVertexXmlNode, SubscriptionsID); sUrl = "http://gdata.youtube.com/feeds/api/users/" + EncodeUrlParameter(sUserName) + "/uploads?max-results=1"; AppendInt32GraphMLAttributeValue(sUrl, TotalResultsXPath, oGraphMLXmlDocument, oRequestStatistics, oVertexXmlNode, VideosUploadedID); }
//************************************************************************* // Method: GetRelatedTagsInternal() // /// <summary> /// Gets the Flickr tags related to a specified tag, given a /// GraphXMLXmlDocument. /// </summary> /// /// <param name="sTag"> /// Tag to get related tags for. /// </param> /// /// <param name="eWhatToInclude"> /// Specifies what should be included in the network. /// </param> /// /// <param name="eNetworkLevel"> /// Network level to include. Must be NetworkLevel.One, OnePointFive, or /// Two. /// </param> /// /// <param name="sApiKey"> /// Flickr API key. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// The GraphMLXmlDocument to populate with the requested network. /// </param> //************************************************************************* protected void GetRelatedTagsInternal( String sTag, WhatToInclude eWhatToInclude, NetworkLevel eNetworkLevel, String sApiKey, RequestStatistics oRequestStatistics, GraphMLXmlDocument oGraphMLXmlDocument ) { Debug.Assert( !String.IsNullOrEmpty(sTag) ); Debug.Assert(eNetworkLevel == NetworkLevel.One || eNetworkLevel == NetworkLevel.OnePointFive || eNetworkLevel == NetworkLevel.Two); Debug.Assert( !String.IsNullOrEmpty(sApiKey) ); Debug.Assert(oRequestStatistics != null); Debug.Assert(oGraphMLXmlDocument != null); AssertValid(); // The key is the tag name and the value is the corresponding GraphML // XML node that represents the tag. This is used to prevent the same // tag from being added to the XmlDocument twice. Dictionary<String, XmlNode> oTagDictionary = new Dictionary<String, XmlNode>(); GetRelatedTagsRecursive(sTag, eWhatToInclude, eNetworkLevel, sApiKey, 1, oGraphMLXmlDocument, oTagDictionary, oRequestStatistics); if ( WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.SampleThumbnails) ) { AppendSampleThumbnails(oTagDictionary, oGraphMLXmlDocument, sApiKey, oRequestStatistics); } }
//************************************************************************* // Method: AppendInt32GraphMLAttributeValue() // /// <summary> /// Appends an Int32 GraphML-Attribute value from an XmlDocument to a /// vertex XML node. /// </summary> /// /// <param name="sUrl"> /// The URL to get the XmlDocument from. /// </param> /// /// <param name="sXPath"> /// Path to the value within the XmlDocument. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> /// /// <param name="oVertexXmlNode"> /// The vertex XML node from <paramref name="oGraphMLXmlDocument" /> to add /// the GraphML attribute value to. /// </param> /// /// <param name="sGraphMLAttributeID"> /// GraphML ID of the attribute. /// </param> /// /// <remarks> /// This method looks for an Int32 value within an XmlDocument returned by /// YouTube and stores the value on a vertex XML node in the GraphML /// document. /// </remarks> //************************************************************************* protected void AppendInt32GraphMLAttributeValue( String sUrl, String sXPath, GraphMLXmlDocument oGraphMLXmlDocument, RequestStatistics oRequestStatistics, XmlNode oVertexXmlNode, String sGraphMLAttributeID ) { Debug.Assert( !String.IsNullOrEmpty(sUrl) ); Debug.Assert( !String.IsNullOrEmpty(sXPath) ); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oRequestStatistics != null); Debug.Assert(oVertexXmlNode != null); Debug.Assert( !String.IsNullOrEmpty(sGraphMLAttributeID) ); AssertValid(); XmlDocument oXmlDocument; XmlNamespaceManager oXmlNamespaceManager; if ( !TryGetXmlDocument(sUrl, oRequestStatistics, out oXmlDocument, out oXmlNamespaceManager) ) { return; } AppendInt32GraphMLAttributeValue(oXmlDocument, sXPath, oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode, sGraphMLAttributeID); }
//************************************************************************* // Method: AppendEdgesFromDictionary() // /// <summary> /// Appends an edge for each pair of videos that share the same dictionary /// key. /// </summary> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="oDictionary"> /// The key is a string and the value is a LinkedList of the video IDs /// corresponding to that key. /// </param> /// /// <param name="sRelationship"> /// The value of the RelationshipID GraphML-attribute to use. /// </param> //************************************************************************* protected void AppendEdgesFromDictionary( Dictionary< String, LinkedList<String> > oDictionary, GraphMLXmlDocument oGraphMLXmlDocument, String sRelationship ) { Debug.Assert(oDictionary != null); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert( !String.IsNullOrEmpty(sRelationship) ); AssertValid(); // For each key... foreach (KeyValuePair< String, LinkedList<String> > oKeyValuePair in oDictionary) { // For each video ID that has the key... LinkedList<String> oVideoIDsWithThisKey = oKeyValuePair.Value; for ( LinkedListNode<String> oVideoIDWithThisKey = oVideoIDsWithThisKey.First; oVideoIDWithThisKey != null; oVideoIDWithThisKey = oVideoIDWithThisKey.Next ) { // For each of the subsequent video IDs in the LinkedList that // have the key... for ( LinkedListNode<String> oSubsequentVideoIDWithThisKey = oVideoIDWithThisKey.Next; oSubsequentVideoIDWithThisKey != null; oSubsequentVideoIDWithThisKey = oSubsequentVideoIDWithThisKey.Next ) { AppendEdgeXmlNode(oGraphMLXmlDocument, oVideoIDWithThisKey.Value, oSubsequentVideoIDWithThisKey.Value, sRelationship); } } } }
//************************************************************************* // Method: CreateGraphMLXmlDocument() // /// <summary> /// Creates a GraphMLXmlDocument representing a network of YouTube users. /// </summary> /// /// <param name="bIncludeAllStatistics"> /// true to include each user's statistics. /// </param> /// /// <returns> /// A GraphMLXmlDocument representing a network of YouTube users. The /// document includes GraphML-attribute definitions but no vertices or /// edges. /// </returns> //************************************************************************* protected GraphMLXmlDocument CreateGraphMLXmlDocument( Boolean bIncludeAllStatistics ) { AssertValid(); GraphMLXmlDocument oGraphMLXmlDocument = new GraphMLXmlDocument(true); DefineCustomMenuGraphMLAttributes(oGraphMLXmlDocument); DefineRelationshipGraphMLAttribute(oGraphMLXmlDocument); if (bIncludeAllStatistics) { oGraphMLXmlDocument.DefineGraphMLAttribute(false, FriendsID, "Friends", "int", null); oGraphMLXmlDocument.DefineGraphMLAttribute(false, SubscriptionsID, "People Subscribed To", "int", null); oGraphMLXmlDocument.DefineGraphMLAttribute(false, SubscribersID, "Subscribers", "int", null); oGraphMLXmlDocument.DefineGraphMLAttribute(false, VideosWatchedID, "Videos Watched", "int", null); oGraphMLXmlDocument.DefineGraphMLAttribute(false, JoinedDateUtcID, "Joined YouTube Date (UTC)", "string", null); oGraphMLXmlDocument.DefineGraphMLAttribute(false, VideosUploadedID, "Videos Uploaded", "int", null); DefineImageFileGraphMLAttribute(oGraphMLXmlDocument); } return (oGraphMLXmlDocument); }
//************************************************************************* // Method: AppendVertexXmlNodes() // /// <summary> /// Appends a vertex XML node for each video that matches a specified /// search term. /// </summary> /// /// <param name="sSearchTerm"> /// The term to search for. /// </param> /// /// <param name="eWhatToInclude"> /// Specifies what should be included in the network. /// </param> /// /// <param name="iMaximumVideos"> /// Maximum number of videos to request, or Int32.MaxValue for no limit. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// The GraphMLXmlDocument being populated. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> /// /// <param name="oVideoIDs"> /// Where the set of unique video IDs gets stored. /// </param> /// /// <param name="oTagDictionary"> /// If an edge should be included for each pair of videos that share the /// same tag, this gets set to a Dictionary for which the key is a /// lower-case tag and the value is a LinkedList of the video IDs that have /// the tag. Otherwise, it gets set to null. /// </param> //************************************************************************* protected void AppendVertexXmlNodes( String sSearchTerm, WhatToInclude eWhatToInclude, Int32 iMaximumVideos, GraphMLXmlDocument oGraphMLXmlDocument, RequestStatistics oRequestStatistics, out HashSet<String> oVideoIDs, out Dictionary< String, LinkedList<String> > oTagDictionary ) { Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) ); Debug.Assert(iMaximumVideos> 0); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oRequestStatistics != null); AssertValid(); ReportProgress("Getting a list of videos."); // This is used to skip duplicate videos in the results returned by // YouTube. (I'm not sure why YouTube sometimes returns duplicates, // but it does.) oVideoIDs = new HashSet<String>(); // If an edge should be included for each pair of videos that share the // same tag, the key is a lower-case tag and the value is a LinkedList // of the video IDs that have the tag. if ( WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.SharedTagEdges) ) { oTagDictionary = new Dictionary< String, LinkedList<String> >(); } else { oTagDictionary = null; } String sUrl = String.Format( "http://gdata.youtube.com/feeds/api/videos?q={0}" , EncodeUrlParameter(sSearchTerm) ); // The document consists of an "entry" XML node for each video. foreach ( XmlNode oEntryXmlNode in EnumerateXmlNodes(sUrl, "a:feed/a:entry", iMaximumVideos, false, oRequestStatistics) ) { XmlNamespaceManager oXmlNamespaceManager = CreateXmlNamespaceManager(oEntryXmlNode.OwnerDocument); // Use the video ID as the GraphML vertex name. The video title // can't be used because it is not unique. String sVideoID; if ( !XmlUtil2.TrySelectSingleNodeAsString(oEntryXmlNode, "media:group/yt:videoid/text()", oXmlNamespaceManager, out sVideoID) || oVideoIDs.Contains(sVideoID) ) { continue; } oVideoIDs.Add(sVideoID); XmlNode oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode( sVideoID); AppendStringGraphMLAttributeValue(oEntryXmlNode, "a:title/text()", oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode, TitleID); AppendDoubleGraphMLAttributeValue(oEntryXmlNode, "gd:rating/@average", oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode, RatingID); AppendInt32GraphMLAttributeValue(oEntryXmlNode, "yt:statistics/@viewCount", oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode, ViewsID); AppendInt32GraphMLAttributeValue(oEntryXmlNode, "yt:statistics/@favoriteCount", oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode, FavoritedID); AppendInt32GraphMLAttributeValue(oEntryXmlNode, "gd:comments/gd:feedLink/@countHint", oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode, CommentsID); AppendYouTubeDateGraphMLAttributeValue(oEntryXmlNode, "a:published/text()", oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode, CreatedDateUtcID); AppendStringGraphMLAttributeValue(oEntryXmlNode, "media:group/media:thumbnail/@url", oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode, ImageFileID); if ( AppendStringGraphMLAttributeValue(oEntryXmlNode, "media:group/media:player/@url", oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode, MenuActionID) ) { oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, MenuTextID, "Play Video in Browser"); } if (oTagDictionary != null) { CollectTags(oEntryXmlNode, sVideoID, oXmlNamespaceManager, oTagDictionary); } } }
//************************************************************************* // Method: GetUserNetworkInternal() // /// <summary> /// Gets a network of YouTube users, given a GraphMLXmlDocument. /// </summary> /// /// <param name="sUserNameToAnalyze"> /// The user name of the YouTube user whose network should be analyzed. /// </param> /// /// <param name="eWhatToInclude"> /// Specifies what should be included in the network. /// </param> /// /// <param name="eNetworkLevel"> /// Network level to include. Must be NetworkLevel.One, OnePointFive, or /// Two. /// </param> /// /// <param name="iMaximumPeoplePerRequest"> /// Maximum number of people to request for each query, or Int32.MaxValue /// for no limit. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// The GraphMLXmlDocument to populate with the requested network. /// </param> //************************************************************************* protected void GetUserNetworkInternal( String sUserNameToAnalyze, WhatToInclude eWhatToInclude, NetworkLevel eNetworkLevel, Int32 iMaximumPeoplePerRequest, RequestStatistics oRequestStatistics, GraphMLXmlDocument oGraphMLXmlDocument ) { Debug.Assert( !String.IsNullOrEmpty(sUserNameToAnalyze) ); Debug.Assert(eNetworkLevel == NetworkLevel.One || eNetworkLevel == NetworkLevel.OnePointFive || eNetworkLevel == NetworkLevel.Two); Debug.Assert(iMaximumPeoplePerRequest > 0); Debug.Assert(oRequestStatistics != null); Debug.Assert(oGraphMLXmlDocument != null); AssertValid(); // The key is the user name and the value is the corresponding GraphML // XML node that represents the user. This is used to prevent the same // user name from being added to the XmlDocument twice. Dictionary<String, XmlNode> oUserNameDictionary = new Dictionary<String, XmlNode>(); // Include friends, subscriptions, both, or neither. Boolean [] abIncludes = new Boolean[] { WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.FriendVertices), WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.SubscriptionVertices), }; for (Int32 i = 0; i < 2; i++) { if ( abIncludes[i] ) { GetUserNetworkRecursive(sUserNameToAnalyze, eWhatToInclude, (i == 0), eNetworkLevel, iMaximumPeoplePerRequest, 1, oGraphMLXmlDocument, oUserNameDictionary, oRequestStatistics); } } if ( WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.AllStatistics) ) { AppendAllStatisticGraphMLAttributeValues(oGraphMLXmlDocument, oUserNameDictionary, oRequestStatistics); } }
//************************************************************************* // Method: GetVideoNetworkInternal() // /// <summary> /// Gets a network of related YouTube videos, given a GraphMLXmlDocument. /// </summary> /// /// <param name="sSearchTerm"> /// The term to search for. /// </param> /// /// <param name="eWhatToInclude"> /// Specifies what should be included in the network. /// </param> /// /// <param name="iMaximumVideos"> /// Maximum number of videos to request, or Int32.MaxValue for no limit. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// The GraphMLXmlDocument to populate with the requested network. /// </param> //************************************************************************* protected void GetVideoNetworkInternal( String sSearchTerm, WhatToInclude eWhatToInclude, Int32 iMaximumVideos, RequestStatistics oRequestStatistics, GraphMLXmlDocument oGraphMLXmlDocument ) { Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) ); Debug.Assert(iMaximumVideos > 0); Debug.Assert(oRequestStatistics != null); Debug.Assert(oGraphMLXmlDocument != null); AssertValid(); // First, add a vertex for each video matching the search term. HashSet<String> oVideoIDs; Dictionary< String, LinkedList<String> > oTagDictionary; AppendVertexXmlNodes(sSearchTerm, eWhatToInclude, iMaximumVideos, oGraphMLXmlDocument, oRequestStatistics, out oVideoIDs, out oTagDictionary); // Now add whatever edges were requested. if ( WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.SharedTagEdges) ) { Debug.Assert(oTagDictionary != null); ReportProgress("Adding edges for shared tags."); AppendEdgesFromDictionary(oTagDictionary, oGraphMLXmlDocument, "Shared tag"); } oTagDictionary = null; if ( WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.SharedCommenterEdges) ) { AppendSharedResponderEdges(oGraphMLXmlDocument, oVideoIDs, MaximumCommentsPerVideo, "http://gdata.youtube.com/feeds/api/videos/{0}/comments", "commenter", oRequestStatistics); } if ( WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.SharedVideoResponderEdges) ) { AppendSharedResponderEdges(oGraphMLXmlDocument, oVideoIDs, iMaximumVideos, "http://gdata.youtube.com/feeds/api/videos/{0}/responses", "video responder", oRequestStatistics); } }
//************************************************************************* // Method: SaveGraphCore() // /// <summary> /// Saves graph data to a stream. /// </summary> /// /// <param name="graph"> /// Graph to save. /// </param> /// /// <param name="stream"> /// Stream to save the graph data to. /// </param> /// /// <remarks> /// This method saves <paramref name="graph" /> to <paramref /// name="stream" />. It does not close <paramref name="stream" />. /// /// <para> /// The arguments have already been checked for validity. /// </para> /// /// </remarks> //************************************************************************* protected override void SaveGraphCore( IGraph graph, Stream stream ) { Debug.Assert(graph != null); Debug.Assert(stream != null); AssertValid(); GraphMLXmlDocument oGraphMLXmlDocument = new GraphMLXmlDocument( graph.Directedness == GraphDirectedness.Directed); String [] asEdgeAttributeNames = ( String[] )graph.GetRequiredValue( ReservedMetadataKeys.AllEdgeMetadataKeys, typeof( String[] ) ); String [] asVertexAttributeNames = ( String[] )graph.GetRequiredValue( ReservedMetadataKeys.AllVertexMetadataKeys, typeof( String[] ) ); // Define the Graph-ML attributes. const String VertexAttributeIDPrefix = "V-"; const String EdgeAttributeIDPrefix = "E-"; foreach (String sVertexAttributeName in asVertexAttributeNames) { oGraphMLXmlDocument.DefineGraphMLAttribute(false, VertexAttributeIDPrefix + sVertexAttributeName, sVertexAttributeName, "string", null); } foreach (String sEdgeAttributeName in asEdgeAttributeNames) { oGraphMLXmlDocument.DefineGraphMLAttribute(true, EdgeAttributeIDPrefix + sEdgeAttributeName, sEdgeAttributeName, "string", null); } // Add the vertices and their Graph-ML attribute values. foreach (IVertex oVertex in graph.Vertices) { XmlNode oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode( oVertex.Name); AppendGraphMLAttributeValues(oVertex, oGraphMLXmlDocument, oVertexXmlNode, asVertexAttributeNames, VertexAttributeIDPrefix); } // Add the edges and their Graph-ML attribute values. foreach (IEdge oEdge in graph.Edges) { IVertex [] oVertices = oEdge.Vertices; XmlNode oEdgeXmlNode = oGraphMLXmlDocument.AppendEdgeXmlNode( oVertices[0].Name, oVertices[1].Name); AppendGraphMLAttributeValues(oEdge, oGraphMLXmlDocument, oEdgeXmlNode, asEdgeAttributeNames, EdgeAttributeIDPrefix); } oGraphMLXmlDocument.Save(stream); }