//************************************************************************* // 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: 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: 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: 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: 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: 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: 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: AppendRepliesToAndMentionsXmlNodes() // /// <summary> /// Appends edge XML nodes for replies-to and mentions relationships. /// </summary> /// /// <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="bIncludeRepliesToEdges"> /// true to append edges for replies-to relationships. /// </param> /// /// <param name="bIncludeMentionsEdges"> /// true to append edges for mentions relationships. /// </param> //************************************************************************* protected void AppendRepliesToAndMentionsXmlNodes( GraphMLXmlDocument oGraphMLXmlDocument, Dictionary<String, TwitterVertex> oScreenNameDictionary, Boolean bIncludeRepliesToEdges, Boolean bIncludeMentionsEdges ) { Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oScreenNameDictionary != null); AssertValid(); if (!bIncludeRepliesToEdges && !bIncludeMentionsEdges) { return; } XmlNamespaceManager oGraphMLXmlNamespaceManager = oGraphMLXmlDocument.CreateXmlNamespaceManager("g"); ReportProgress("Examining relationships."); // "Starts with a screen name," which means it's a "reply-to". Regex oReplyToRegex = new Regex(@"^@(?<ScreenName>\w+)"); // "Contains a screen name," which means it's a "mentions". // // Note that a "reply-to" is also a "mentions." Regex oMentionsRegex = new Regex(@"(^|\s)@(?<ScreenName>\w+)"); foreach (KeyValuePair<String, TwitterVertex> oKeyValuePair in oScreenNameDictionary) { String sScreenName = oKeyValuePair.Key; TwitterVertex oTwitterVertex = oKeyValuePair.Value; String sStatusForAnalysis = oTwitterVertex.StatusForAnalysis; String sStatusForAnalysisDateUtc = oTwitterVertex.StatusForAnalysisDateUtc; if ( String.IsNullOrEmpty(sStatusForAnalysis) ) { continue; } if (bIncludeRepliesToEdges) { Match oReplyToMatch = oReplyToRegex.Match(sStatusForAnalysis); if (oReplyToMatch.Success) { String sReplyToScreenName = oReplyToMatch.Groups["ScreenName"].Value.ToLower(); if ( sReplyToScreenName != sScreenName && oScreenNameDictionary.ContainsKey(sReplyToScreenName) ) { XmlNode oEdgeXmlNode = AppendEdgeXmlNode( oGraphMLXmlDocument, sScreenName, sReplyToScreenName, "Replies to"); if ( !String.IsNullOrEmpty(sStatusForAnalysisDateUtc) ) { oGraphMLXmlDocument.AppendGraphMLAttributeValue( oEdgeXmlNode, RelationshipDateUtcID, sStatusForAnalysisDateUtc); } } } } if (bIncludeMentionsEdges) { Match oMentionsMatch = oMentionsRegex.Match(sStatusForAnalysis); while (oMentionsMatch.Success) { String sMentionsScreenName = oMentionsMatch.Groups["ScreenName"].Value.ToLower(); if ( sMentionsScreenName != sScreenName && oScreenNameDictionary.ContainsKey(sMentionsScreenName) ) { XmlNode oEdgeXmlNode = AppendEdgeXmlNode( oGraphMLXmlDocument, sScreenName, sMentionsScreenName, "Mentions"); if ( !String.IsNullOrEmpty(sStatusForAnalysisDateUtc) ) { oGraphMLXmlDocument.AppendGraphMLAttributeValue( oEdgeXmlNode, RelationshipDateUtcID, sStatusForAnalysisDateUtc); } } oMentionsMatch = oMentionsMatch.NextMatch(); } } } }
//************************************************************************* // Method: AppendGraphMLAttributeValues() // /// <summary> /// Appends Graph-ML attribute values to a vertex or edge XML node while /// saving a graph. /// </summary> /// /// <param name="oEdgeOrVertex"> /// The edge or vertex to read metadata from. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// The GraphML document being populated. /// </param> /// /// <param name="oEdgeOrVertexXmlNode"> /// The edge or vertex XML node in the GraphML document that corresponds to /// <paramref name="oEdgeOrVertex" />. /// </param> /// /// <param name="asAttributeNames"> /// Array of all possible Graph-ML attribute names for the edge or vertex. /// </param> /// /// <param name="AttributeIDPrefix"> /// The prefix to use for each Graph-ML attribute ID. /// </param> //************************************************************************* protected void AppendGraphMLAttributeValues( IMetadataProvider oEdgeOrVertex, GraphMLXmlDocument oGraphMLXmlDocument, XmlNode oEdgeOrVertexXmlNode, String [] asAttributeNames, String AttributeIDPrefix ) { Debug.Assert(oEdgeOrVertex != null); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oEdgeOrVertexXmlNode != null); Debug.Assert(asAttributeNames != null); Debug.Assert( !String.IsNullOrEmpty(AttributeIDPrefix) ); AssertValid(); foreach (String sAttributeName in asAttributeNames) { Object oAttributeValue; // Note that the value type isn't checked. Whatever type it is, // it gets converted to a string. if (oEdgeOrVertex.TryGetValue(sAttributeName, out oAttributeValue) && oAttributeValue != null) { oGraphMLXmlDocument.AppendGraphMLAttributeValue( oEdgeOrVertexXmlNode, AttributeIDPrefix + sAttributeName, oAttributeValue.ToString() ); } } }
//************************************************************************* // Method: AppendRelationshipDateUtcGraphMLAttributeValue() // /// <summary> /// Appends a GraphML attribute value for the relationship date to an edge /// XML node. /// </summary> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="oEdgeXmlNode"> /// The edge XML node to add the Graph-ML attribute value to. /// </param> /// /// <param name="oRequestStatistics"> /// A <see cref="RequestStatistics" /> object that is keeping track of /// requests made while getting the network. /// </param> /// /// <remarks> /// The Graph-ML attribute value is set to the start time of the network /// request. /// </remarks> //************************************************************************* protected void AppendRelationshipDateUtcGraphMLAttributeValue( GraphMLXmlDocument oGraphMLXmlDocument, XmlNode oEdgeXmlNode, RequestStatistics oRequestStatistics ) { Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oEdgeXmlNode != null); Debug.Assert(oRequestStatistics != null); AssertValid(); oGraphMLXmlDocument.AppendGraphMLAttributeValue(oEdgeXmlNode, RelationshipDateUtcID, TwitterDateParser.ToCultureInvariantString( oRequestStatistics.StartTimeUtc) ); }
//************************************************************************* // Method: AppendFromUserXmlNode() // /// <summary> /// Appends GraphML-Attribute values from a "user" XML node returned by /// Twitter to a vertex XML node. /// </summary> /// /// <param name="oUserXmlNode"> /// The "user" XML node returned by Twitter. Can't be null. /// </param> /// /// <param name="oGraphMLXmlDocument"> /// GraphMLXmlDocument being populated. /// </param> /// /// <param name="oTwitterVertex"> /// Contains the vertex XML node from <paramref /// name="oGraphMLXmlDocument" /> to add the GraphML attribute values to. /// </param> /// /// <param name="bIncludeStatistics"> /// true to include the user's statistics. /// </param> /// /// <param name="bIncludeLatestStatus"> /// true to include a latest status attribute value. /// </param> /// /// <remarks> /// This method reads information from a "user" XML node returned by /// Twitter and appends the information to a vertex XML node in the GraphML /// document. /// </remarks> //************************************************************************* protected void AppendFromUserXmlNode( XmlNode oUserXmlNode, GraphMLXmlDocument oGraphMLXmlDocument, TwitterVertex oTwitterVertex, Boolean bIncludeStatistics, Boolean bIncludeLatestStatus ) { Debug.Assert(oUserXmlNode != null); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oTwitterVertex != null); AssertValid(); XmlNode oVertexXmlNode = oTwitterVertex.VertexXmlNode; if (bIncludeStatistics) { AppendInt32GraphMLAttributeValue(oUserXmlNode, "friends_count/text()", null, oGraphMLXmlDocument, oVertexXmlNode, FollowedID); AppendInt32GraphMLAttributeValue(oUserXmlNode, "followers_count/text()", null, oGraphMLXmlDocument, oVertexXmlNode, FollowersID); AppendInt32GraphMLAttributeValue(oUserXmlNode, "statuses_count/text()", null, oGraphMLXmlDocument, oVertexXmlNode, StatusesID); AppendInt32GraphMLAttributeValue(oUserXmlNode, "favourites_count/text()", null, oGraphMLXmlDocument, oVertexXmlNode, FavoritesID); AppendStringGraphMLAttributeValue(oUserXmlNode, "description/text()", null, oGraphMLXmlDocument, oVertexXmlNode, DescriptionID); AppendStringGraphMLAttributeValue(oUserXmlNode, "time_zone/text()", null, oGraphMLXmlDocument, oVertexXmlNode, TimeZoneID); AppendStringGraphMLAttributeValue(oUserXmlNode, "utc_offset/text()", null, oGraphMLXmlDocument, oVertexXmlNode, UtcOffsetID); String sJoinedDateUtc; if ( XmlUtil2.TrySelectSingleNodeAsString(oUserXmlNode, "created_at/text()", null, out sJoinedDateUtc) ) { oGraphMLXmlDocument.AppendGraphMLAttributeValue( oVertexXmlNode, JoinedDateUtcID, TwitterDateParser.ParseTwitterDate(sJoinedDateUtc) ); } } String sLatestStatus; if ( XmlUtil2.TrySelectSingleNodeAsString(oUserXmlNode, "status/text/text()", null, out sLatestStatus) ) { String sLatestStatusDateUtc; if ( XmlUtil2.TrySelectSingleNodeAsString(oUserXmlNode, "status/created_at/text()", null, out sLatestStatusDateUtc) ) { sLatestStatusDateUtc = TwitterDateParser.ParseTwitterDate( sLatestStatusDateUtc); } // Don't overwrite any status the derived class may have already // stored on the TwitterVertex object. if (oTwitterVertex.StatusForAnalysis == null) { oTwitterVertex.StatusForAnalysis = sLatestStatus; oTwitterVertex.StatusForAnalysisDateUtc = sLatestStatusDateUtc; } if (bIncludeLatestStatus) { oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, LatestStatusID, sLatestStatus); if ( !String.IsNullOrEmpty(sLatestStatusDateUtc) ) { oGraphMLXmlDocument.AppendGraphMLAttributeValue( oVertexXmlNode, LatestStatusDateUtcID, sLatestStatusDateUtc); } } } // Add an image URL GraphML-attribute if it hasn't already been added. // (It might have been added from an "entry" node by // TwitterSearchNetworkAnalyzer.) if (oVertexXmlNode.SelectSingleNode( "a:data[@key='" + ImageFileID + "']", oGraphMLXmlDocument.CreateXmlNamespaceManager("a") ) == null) { AppendStringGraphMLAttributeValue(oUserXmlNode, "profile_image_url/text()", null, oGraphMLXmlDocument, oVertexXmlNode, ImageFileID); } }
//************************************************************************* // Method: TryAppendVertexXmlNode() // /// <overloads> /// Appends a vertex XML node to the GraphML document for a person if such /// a node doesn't already exist. /// </overloads> /// /// <summary> /// Appends a vertex XML node to the GraphML document for a person if such /// a node doesn't already exist and provides the TwitterVertex for the /// node. /// </summary> /// /// <param name="sScreenName"> /// Screen name to add a vertex XML node for. /// </param> /// /// <param name="oUserXmlNode"> /// The "user" XML node returned by Twitter, or null if a user node isn't /// available. /// </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="bIncludeStatistics"> /// true to include the user's statistics if <paramref /// name="oUserXmlNode" /> is not null. /// </param> /// /// <param name="bIncludeLatestStatus"> /// true to include a latest status attribute value if <paramref /// name="oUserXmlNode" /> is not null. /// </param> /// /// <param name="oTwitterVertex"> /// Where the TwitterVertex that wraps the vertex XML node gets stored. /// This gets set regardless of whether the node already existed. /// </param> /// /// <returns> /// true if a vertex XML node was added, false if a vertex XML node already /// existed. /// </returns> //************************************************************************* protected Boolean TryAppendVertexXmlNode( String sScreenName, XmlNode oUserXmlNode, GraphMLXmlDocument oGraphMLXmlDocument, Dictionary<String, TwitterVertex> oScreenNameDictionary, Boolean bIncludeStatistics, Boolean bIncludeLatestStatus, out TwitterVertex oTwitterVertex ) { Debug.Assert( !String.IsNullOrEmpty(sScreenName) ); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oScreenNameDictionary != null); oTwitterVertex = null; if ( oScreenNameDictionary.TryGetValue(sScreenName, out oTwitterVertex) ) { return (false); } XmlNode oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode( sScreenName); oTwitterVertex = new TwitterVertex(oVertexXmlNode); oScreenNameDictionary.Add(sScreenName, oTwitterVertex); oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, MenuTextID, "Open Twitter Page for This Person"); oGraphMLXmlDocument.AppendGraphMLAttributeValue( oVertexXmlNode, MenuActionID, String.Format(WebPageUrlPattern, sScreenName) ); if (oUserXmlNode != null) { AppendFromUserXmlNode(oUserXmlNode, oGraphMLXmlDocument, oTwitterVertex, bIncludeStatistics, bIncludeLatestStatus); } return (true); }
//************************************************************************* // Method: GetUserNetworkRecursive() // /// <summary> /// Recursively gets a network of Flickr users. /// </summary> /// /// <param name="sUserID"> /// Flickr user ID. /// </param> /// /// <param name="sScreenName"> /// Flickr screen name. /// </param> /// /// <param name="eWhatToInclude"> /// Specifies what should be included in the network. /// </param> /// /// <param name="bIncludeContactsThisCall"> /// true to include the user's contacts, false to include the people who /// have commented on the user's photos. /// </param> /// /// <param name="eNetworkLevel"> /// Network level to include. Must be NetworkLevel.One, OnePointFive, or /// Two. /// </param> /// /// <param name="iMaximumPerRequest"> /// Maximum number of people or photos to request for each query, or /// Int32.MaxValue for no limit. /// </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="oUserIDDictionary"> /// The key is the user ID 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 sUserID, String sScreenName, WhatToInclude eWhatToInclude, Boolean bIncludeContactsThisCall, NetworkLevel eNetworkLevel, Int32 iMaximumPerRequest, String sApiKey, Int32 iRecursionLevel, GraphMLXmlDocument oGraphMLXmlDocument, Dictionary<String, XmlNode> oUserIDDictionary, RequestStatistics oRequestStatistics ) { Debug.Assert( !String.IsNullOrEmpty(sUserID) ); Debug.Assert( !String.IsNullOrEmpty(sScreenName) ); Debug.Assert(eNetworkLevel == NetworkLevel.One || eNetworkLevel == NetworkLevel.OnePointFive || eNetworkLevel == NetworkLevel.Two); Debug.Assert(iMaximumPerRequest > 0); Debug.Assert( !String.IsNullOrEmpty(sApiKey) ); Debug.Assert(iRecursionLevel == 1 || iRecursionLevel == 2); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert(oUserIDDictionary != 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); List<String> oUserIDsToRecurse = new List<String>(); ReportProgressForContactsOrCommenters(sScreenName, bIncludeContactsThisCall); Boolean bThisUserAppended = false; foreach ( XmlNode oChildXmlNode in GetContactsOrCommentersEnumerator( sUserID, bIncludeContactsThisCall, iMaximumPerRequest, oGraphMLXmlDocument, sApiKey, oRequestStatistics) ) { String sOtherScreenName, sOtherUserID; if ( !XmlUtil2.TrySelectSingleNodeAsString(oChildXmlNode, bIncludeContactsThisCall ? "@username" : "@authorname", null, out sOtherScreenName) || !XmlUtil2.TrySelectSingleNodeAsString(oChildXmlNode, bIncludeContactsThisCall ? "@nsid" : "@author", null, out sOtherUserID) ) { 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(sUserID, sScreenName, oGraphMLXmlDocument, oUserIDDictionary); bThisUserAppended = true; } if (bNeedToAppendVertices) { if ( TryAppendVertexXmlNode(sOtherUserID, sOtherScreenName, oGraphMLXmlDocument, oUserIDDictionary) && bNeedToRecurse ) { oUserIDsToRecurse.Add(sOtherUserID); } } if ( bNeedToAppendVertices || oUserIDDictionary.ContainsKey(sOtherUserID) ) { // Append an edge node and optional attributes. XmlNode oEdgeXmlNode; if (bIncludeContactsThisCall) { oEdgeXmlNode = AppendEdgeXmlNode(oGraphMLXmlDocument, sScreenName, sOtherScreenName, "Contact"); } else { // (Note the swapping of screen names in the commenter // case.) oEdgeXmlNode = AppendEdgeXmlNode(oGraphMLXmlDocument, sOtherScreenName, sScreenName, "Commenter"); UInt32 uCommentDateUtc; if ( XmlUtil2.TrySelectSingleNodeAsUInt32(oChildXmlNode, "@datecreate", null, out uCommentDateUtc) ) { DateTime oCommentDateUtc = DateTimeUtil2.UnixTimestampToDateTimeUtc( uCommentDateUtc); oGraphMLXmlDocument.AppendGraphMLAttributeValue( oEdgeXmlNode, CommentDateUtcID, ExcelDateTimeUtil.DateTimeToStringLocale1033( oCommentDateUtc, ExcelColumnFormat.DateAndTime) ); } AppendStringGraphMLAttributeValue(oChildXmlNode, "@permalink", null, oGraphMLXmlDocument, oEdgeXmlNode, CommentUrlID); } } } if (bNeedToRecurse) { foreach (String sUserIDToRecurse in oUserIDsToRecurse) { XmlNode oVertexXmlNode = oUserIDDictionary[sUserIDToRecurse]; String sScreenNameToRecurse = GetScreenNameFromVertexXmlNode( oVertexXmlNode); GetUserNetworkRecursive(sUserIDToRecurse, sScreenNameToRecurse, eWhatToInclude, bIncludeContactsThisCall, eNetworkLevel, iMaximumPerRequest, sApiKey, 2, oGraphMLXmlDocument, oUserIDDictionary, oRequestStatistics); } } }
//************************************************************************* // Method: AppendUserInformationGraphMLAttributeValues() // /// <summary> /// Appends user information GraphML attribute values to the GraphML /// document for one user in the network. /// </summary> /// /// <param name="sUserID"> /// The user ID. /// </param> /// /// <param name="oVertexXmlNode"> /// The GraphML XML node corresponding to the user. /// </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 AppendUserInformationGraphMLAttributeValues( String sUserID, XmlNode oVertexXmlNode, GraphMLXmlDocument oGraphMLXmlDocument, String sApiKey, RequestStatistics oRequestStatistics ) { Debug.Assert( !String.IsNullOrEmpty(sUserID) ); Debug.Assert(oVertexXmlNode != null); Debug.Assert(oGraphMLXmlDocument != null); Debug.Assert( !String.IsNullOrEmpty(sApiKey) ); Debug.Assert(oRequestStatistics != null); AssertValid(); String sUrl = GetFlickrMethodUrl( "flickr.people.getInfo", sApiKey, GetUserIDUrlParameter(sUserID) ); XmlDocument oXmlDocument; if ( !TryGetXmlDocument(sUrl, oRequestStatistics, out oXmlDocument) ) { // Ignore errors. The user information isn't critical. return; } const String XPathRoot = "rsp/person/"; AppendStringGraphMLAttributeValue(oXmlDocument, XPathRoot + "realname/text()", null, oGraphMLXmlDocument, oVertexXmlNode, RealNameID); AppendInt32GraphMLAttributeValue(oXmlDocument, XPathRoot + "photos/count/text()", null, oGraphMLXmlDocument, oVertexXmlNode, TotalPhotosID); String sIsProfessional; if ( XmlUtil2.TrySelectSingleNodeAsString(oXmlDocument, XPathRoot + "@ispro", null, out sIsProfessional) ) { oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, IsProfessionalID, (sIsProfessional == "0") ? "No" : "Yes" ); } // Get the URL to the user's buddy icon, which is explained here: // // http://www.flickr.com/services/api/misc.buddyicons.html Int32 iIconServer, iIconFarm; String sBuddyIconUrl; if ( XmlUtil2.TrySelectSingleNodeAsInt32(oXmlDocument, XPathRoot + "@iconserver", null, out iIconServer) && XmlUtil2.TrySelectSingleNodeAsInt32(oXmlDocument, XPathRoot + "@iconfarm", null, out iIconFarm) && iIconServer > 0 ) { sBuddyIconUrl = String.Format( "http://farm{0}.static.flickr.com/{1}/buddyicons/{2}.jpg" , iIconFarm, iIconServer, sUserID ); } else { sBuddyIconUrl = "http://www.flickr.com/images/buddyicon.jpg"; } oGraphMLXmlDocument.AppendGraphMLAttributeValue( oVertexXmlNode, ImageFileID, sBuddyIconUrl); if ( AppendStringGraphMLAttributeValue(oXmlDocument, XPathRoot + "photosurl/text()", null, oGraphMLXmlDocument, oVertexXmlNode, MenuActionID) ) { oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, MenuTextID, "Open Flickr Page for This Person"); } }