GetNetworkAsync
        (
            String userNameToAnalyze,
            WhatToInclude whatToInclude,
            NetworkLevel networkLevel,
            Int32 maximumPeoplePerRequest
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(userNameToAnalyze));

            Debug.Assert(networkLevel == NetworkLevel.One ||
                         networkLevel == NetworkLevel.OnePointFive ||
                         networkLevel == NetworkLevel.Two);

            Debug.Assert(maximumPeoplePerRequest > 0);

            AssertValid();

            const String MethodName = "GetNetworkAsync";

            CheckIsBusy(MethodName);

            // Wrap the arguments in an object that can be passed to
            // BackgroundWorker.RunWorkerAsync().

            GetNetworkAsyncArgs oGetNetworkAsyncArgs = new GetNetworkAsyncArgs();

            oGetNetworkAsyncArgs.UserNameToAnalyze       = userNameToAnalyze;
            oGetNetworkAsyncArgs.WhatToInclude           = whatToInclude;
            oGetNetworkAsyncArgs.NetworkLevel            = networkLevel;
            oGetNetworkAsyncArgs.MaximumPeoplePerRequest = maximumPeoplePerRequest;

            m_oBackgroundWorker.RunWorkerAsync(oGetNetworkAsyncArgs);
        }
        GetNetworkAsync
        (
            String searchTerm,
            WhatToInclude whatToInclude,
            Int32 maximumVideos
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(searchTerm));
            Debug.Assert(maximumVideos > 0);
            AssertValid();

            const String MethodName = "GetNetworkAsync";

            CheckIsBusy(MethodName);

            // Wrap the arguments in an object that can be passed to
            // BackgroundWorker.RunWorkerAsync().

            GetNetworkAsyncArgs oGetNetworkAsyncArgs = new GetNetworkAsyncArgs();

            oGetNetworkAsyncArgs.SearchTerm    = searchTerm;
            oGetNetworkAsyncArgs.WhatToInclude = whatToInclude;
            oGetNetworkAsyncArgs.MaximumVideos = maximumVideos;

            m_oBackgroundWorker.RunWorkerAsync(oGetNetworkAsyncArgs);
        }
Пример #3
0
        GetVideoNetworkInternal
        (
            String sSearchTerm,
            WhatToInclude eWhatToInclude,
            Int32 iMaximumVideos
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(sSearchTerm));
            Debug.Assert(iMaximumVideos > 0);
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument = CreateGraphMLXmlDocument();
            RequestStatistics  oRequestStatistics  = new RequestStatistics();

            try
            {
                GetVideoNetworkInternal(sSearchTerm, eWhatToInclude,
                                        iMaximumVideos, oRequestStatistics, oGraphMLXmlDocument);
            }
            catch (Exception oException)
            {
                OnTerminatingException(oException);
            }

            OnNetworkObtainedWithoutTerminatingException(oGraphMLXmlDocument,
                                                         oRequestStatistics);

            return(oGraphMLXmlDocument);
        }
        GetNetworkAsync
        (
            String tag,
            WhatToInclude whatToInclude,
            NetworkLevel networkLevel,
            String apiKey
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(tag));

            Debug.Assert(networkLevel == NetworkLevel.One ||
                         networkLevel == NetworkLevel.OnePointFive ||
                         networkLevel == NetworkLevel.Two);

            Debug.Assert(!String.IsNullOrEmpty(apiKey));
            AssertValid();

            const String MethodName = "GetNetworkAsync";

            CheckIsBusy(MethodName);

            // Wrap the arguments in an object that can be passed to
            // BackgroundWorker.RunWorkerAsync().

            GetNetworkAsyncArgs oGetNetworkAsyncArgs = new GetNetworkAsyncArgs();

            oGetNetworkAsyncArgs.Tag           = tag;
            oGetNetworkAsyncArgs.NetworkLevel  = networkLevel;
            oGetNetworkAsyncArgs.WhatToInclude = whatToInclude;
            oGetNetworkAsyncArgs.ApiKey        = apiKey;

            m_oBackgroundWorker.RunWorkerAsync(oGetNetworkAsyncArgs);
        }
        GetNetworkDescription
        (
            String sTag,
            WhatToInclude eWhatToInclude,
            NetworkLevel eNetworkLevel
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(sTag));
            AssertValid();

            NetworkDescriber oNetworkDescriber = new NetworkDescriber();

            oNetworkDescriber.AddSentence(

                "The graph represents the {0} network of Flickr tags related to"
                + " the tag \"{1}\"."
                ,
                NetworkLevelToString(eNetworkLevel),
                sTag
                );

            oNetworkDescriber.AddNetworkTime(NetworkSource);

            return(oNetworkDescriber.ConcatenateSentences());
        }
 WhatToIncludeFlagIsSet
 (
     WhatToInclude eORedEnumFlags,
     WhatToInclude eORedEnumFlagsToCheck
 )
 {
     return((eORedEnumFlags & eORedEnumFlagsToCheck) != 0);
 }
        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> > oCategoryDictionary;

            AppendVertexXmlNodes(sSearchTerm, eWhatToInclude, iMaximumVideos,
                                 oGraphMLXmlDocument, oRequestStatistics, out oVideoIDs,
                                 out oCategoryDictionary);

            // Now add whatever edges were requested.

            if (WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.SharedCategoryEdges))
            {
                Debug.Assert(oCategoryDictionary != null);

                ReportProgress("Adding edges for shared categories.");

                AppendEdgesFromDictionary(oCategoryDictionary, oGraphMLXmlDocument,
                                          "Shared category", SharedCategoryID);
            }

            oCategoryDictionary = null;

            if (WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.SharedCommenterEdges))
            {
                AppendSharedResponderEdges(oGraphMLXmlDocument, oVideoIDs,
                                           MaximumCommentsPerVideo,
                                           "http://gdata.youtube.com/feeds/api/videos/{0}/comments",
                                           "commenter", SharedCommenterID, oRequestStatistics);
            }

            if (WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.SharedVideoResponderEdges))
            {
                AppendSharedResponderEdges(oGraphMLXmlDocument, oVideoIDs,
                                           iMaximumVideos,
                                           "http://gdata.youtube.com/feeds/api/videos/{0}/responses",
                                           "video responder", SharedVideoResponderID, oRequestStatistics);
            }
        }
        CreateGraphMLXmlDocument
        (
            WhatToInclude eWhatToInclude
        )
        {
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument = new GraphMLXmlDocument(false);

            NodeXLGraphMLUtil.DefineEdgeRelationshipGraphMLAttribute(
                oGraphMLXmlDocument);

            if (WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.SharedCategoryEdges))
            {
                oGraphMLXmlDocument.DefineEdgeStringGraphMLAttributes(
                    SharedCategoryID, "Shared Category");
            }

            if (WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.SharedCommenterEdges))
            {
                oGraphMLXmlDocument.DefineEdgeStringGraphMLAttributes(
                    SharedCommenterID, "Shared Commenter");
            }

            if (WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.SharedVideoResponderEdges))
            {
                oGraphMLXmlDocument.DefineEdgeStringGraphMLAttributes(
                    SharedVideoResponderID, "Shared Video Responder");
            }

            oGraphMLXmlDocument.DefineVertexStringGraphMLAttributes(
                TitleID, "Title",
                AuthorID, "Author",
                CreatedDateUtcID, "Created Date (UTC)"
                );

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, RatingID,
                                                       "Rating", "double", null);

            oGraphMLXmlDocument.DefineGraphMLAttributes(false, "int",
                                                        ViewsID, "Views",
                                                        FavoritedID, "Favorited",
                                                        CommentsID, "Comments"
                                                        );

            NodeXLGraphMLUtil.DefineVertexImageFileGraphMLAttribute(
                oGraphMLXmlDocument);

            NodeXLGraphMLUtil.DefineVertexCustomMenuGraphMLAttributes(
                oGraphMLXmlDocument);

            return(oGraphMLXmlDocument);
        }
Пример #9
0
        GetNetworkInternal
        (
            String sSearchTerm,
            WhatToInclude eWhatToInclude,
            Int32 iMaximumStatuses,
            RequestStatistics oRequestStatistics,
            GraphMLXmlDocument oGraphMLXmlDocument
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(sSearchTerm));
            Debug.Assert(iMaximumStatuses > 0);
            Debug.Assert(iMaximumStatuses != Int32.MaxValue);
            Debug.Assert(oRequestStatistics != null);
            Debug.Assert(oGraphMLXmlDocument != null);
            AssertValid();

            // The key is the Twitter user ID and the value is the corresponding
            // TwitterUser.

            Dictionary <String, TwitterUser> oUserIDDictionary =
                new Dictionary <String, TwitterUser>();

            // First, append a vertex for each person who has tweeted the search
            // term.

            AppendVertexXmlNodesForSearchTerm(sSearchTerm, eWhatToInclude,
                                              iMaximumStatuses, oGraphMLXmlDocument, oUserIDDictionary,
                                              oRequestStatistics);

            // Now append a vertex for each person who was mentioned or replied to
            // by the first set of people, but who didn't tweet the search term
            // himself.

            AppendVertexXmlNodesForMentionsAndRepliesTo(eWhatToInclude,
                                                        oGraphMLXmlDocument, oUserIDDictionary, oRequestStatistics);

            TwitterSearchNetworkGraphMLUtil.AppendVertexTooltipXmlNodes(
                oGraphMLXmlDocument, oUserIDDictionary);

            if (WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.FollowedEdges))
            {
                // Look at each author's friends, and if a friend has also tweeted
                // the search term, add an edge between the author and the friend.

                AppendFriendEdgeXmlNodes(oUserIDDictionary, MaximumFriends,
                                         oGraphMLXmlDocument, oRequestStatistics);
            }

            AppendRepliesToAndMentionsEdgeXmlNodes(oGraphMLXmlDocument,
                                                   oUserIDDictionary.Values,

                                                   TwitterGraphMLUtil.TwitterUsersToUniqueScreenNames(
                                                       oUserIDDictionary.Values)
                                                   );
        }
        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);
            }
        }
Пример #11
0
        GetNetwork
        (
            String searchTerm,
            WhatToInclude whatToInclude,
            Int32 maximumPeoplePerRequest
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(searchTerm));
            Debug.Assert(maximumPeoplePerRequest > 0);
            AssertValid();

            return(GetSearchNetworkInternal(searchTerm, whatToInclude,
                                            maximumPeoplePerRequest));
        }
Пример #12
0
        GetNetwork
        (
            String searchTerm,
            WhatToInclude whatToInclude,
            Int32 maximumStatuses
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(searchTerm));
            Debug.Assert(maximumStatuses > 0);
            Debug.Assert(maximumStatuses != Int32.MaxValue);
            AssertValid();

            return(GetNetworkInternal(searchTerm, whatToInclude,
                                      maximumStatuses));
        }
        GetNetworkDescription
        (
            String sUserNameToAnalyze,
            WhatToInclude eWhatToInclude,
            NetworkLevel eNetworkLevel,
            Int32 iMaximumPeoplePerRequest
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(sUserNameToAnalyze));
            Debug.Assert(iMaximumPeoplePerRequest > 0);
            AssertValid();

            NetworkDescriber oNetworkDescriber = new NetworkDescriber();

            oNetworkDescriber.AddSentence(

                "The graph represents the {0} YouTube network of the user with the"
                + " username \"{1}\"."
                ,
                NetworkLevelToString(eNetworkLevel),
                sUserNameToAnalyze
                );

            oNetworkDescriber.AddNetworkTime(NetworkSource);

            oNetworkDescriber.StartNewParagraph();

            if (WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.FriendVertices))
            {
                oNetworkDescriber.AddSentence(
                    "There is a vertex for each friend of the user."
                    );
            }

            if (WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.SubscriptionVertices))
            {
                oNetworkDescriber.AddSentence(
                    "There is a vertex for each person or channel subscribed to by"
                    + " the user."
                    );
            }

            oNetworkDescriber.AddNetworkLimit(iMaximumPeoplePerRequest, "people");

            return(oNetworkDescriber.ConcatenateSentences());
        }
Пример #14
0
        GetUserNetworkInternal
        (
            String sScreenName,
            WhatToInclude eWhatToInclude,
            NetworkLevel eNetworkLevel,
            Int32 iMaximumPerRequest,
            String sApiKey
        )
        {
            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));

            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument = CreateGraphMLXmlDocument(

                WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.CommenterVertices),

                WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.UserInformation)
                );

            RequestStatistics oRequestStatistics = new RequestStatistics();

            try
            {
                GetUserNetworkInternal(sScreenName, eWhatToInclude, eNetworkLevel,
                                       iMaximumPerRequest, sApiKey, oRequestStatistics,
                                       oGraphMLXmlDocument);
            }
            catch (Exception oException)
            {
                OnTerminatingException(oException);
            }

            OnNetworkObtainedWithoutTerminatingException(oGraphMLXmlDocument,
                                                         oRequestStatistics);

            return(oGraphMLXmlDocument);
        }
        GetUserNetworkInternal
        (
            String sUserNameToAnalyze,
            WhatToInclude eWhatToInclude,
            NetworkLevel eNetworkLevel,
            Int32 iMaximumPeoplePerRequest
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(sUserNameToAnalyze));

            Debug.Assert(eNetworkLevel == NetworkLevel.One ||
                         eNetworkLevel == NetworkLevel.OnePointFive ||
                         eNetworkLevel == NetworkLevel.Two);

            Debug.Assert(iMaximumPeoplePerRequest > 0);
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument =
                CreateGraphMLXmlDocument(WhatToIncludeFlagIsSet(
                                             eWhatToInclude, WhatToInclude.AllStatistics));

            RequestStatistics oRequestStatistics = new RequestStatistics();

            try
            {
                GetUserNetworkInternal(sUserNameToAnalyze, eWhatToInclude,
                                       eNetworkLevel, iMaximumPeoplePerRequest, oRequestStatistics,
                                       oGraphMLXmlDocument);
            }
            catch (Exception oException)
            {
                OnUnexpectedException(oException, oGraphMLXmlDocument,
                                      oRequestStatistics);
            }

            String sNetworkTitle = "YouTube User " + sUserNameToAnalyze;

            OnNetworkObtained(oGraphMLXmlDocument, oRequestStatistics,

                              GetNetworkDescription(sUserNameToAnalyze, eWhatToInclude,
                                                    eNetworkLevel, iMaximumPeoplePerRequest),

                              sNetworkTitle, sNetworkTitle
                              );

            return(oGraphMLXmlDocument);
        }
Пример #16
0
        GetUserNetworkInternal
        (
            String sScreenNameToAnalyze,
            WhatToInclude eWhatToInclude,
            NetworkLevel eNetworkLevel,
            Int32 iMaximumPeoplePerRequest
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(sScreenNameToAnalyze));

            Debug.Assert(eNetworkLevel == NetworkLevel.One ||
                         eNetworkLevel == NetworkLevel.OnePointFive ||
                         eNetworkLevel == NetworkLevel.Two);

            Debug.Assert(iMaximumPeoplePerRequest > 0);
            AssertValid();

            BeforeGetNetwork();

            Boolean bIncludeLatestStatus = WhatToIncludeFlagIsSet(eWhatToInclude,
                                                                  WhatToInclude.LatestStatuses);

            GraphMLXmlDocument oGraphMLXmlDocument =
                CreateGraphMLXmlDocument(true, bIncludeLatestStatus);

            RequestStatistics oRequestStatistics = new RequestStatistics();

            try
            {
                GetUserNetworkInternal(sScreenNameToAnalyze, eWhatToInclude,
                                       eNetworkLevel, iMaximumPeoplePerRequest, oRequestStatistics,
                                       oGraphMLXmlDocument);
            }
            catch (Exception oException)
            {
                OnTerminatingException(oException);
            }

            OnNetworkObtainedWithoutTerminatingException(oGraphMLXmlDocument,
                                                         oRequestStatistics);

            return(oGraphMLXmlDocument);
        }
        GetRelatedTagsInternal
        (
            String sTag,
            WhatToInclude eWhatToInclude,
            NetworkLevel eNetworkLevel,
            String sApiKey
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(sTag));

            Debug.Assert(eNetworkLevel == NetworkLevel.One ||
                         eNetworkLevel == NetworkLevel.OnePointFive ||
                         eNetworkLevel == NetworkLevel.Two);

            Debug.Assert(!String.IsNullOrEmpty(sApiKey));
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument = CreateGraphMLXmlDocument(
                WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.SampleThumbnails));

            RequestStatistics oRequestStatistics = new RequestStatistics();

            try
            {
                GetRelatedTagsInternal(sTag, eWhatToInclude, eNetworkLevel,
                                       sApiKey, oRequestStatistics, oGraphMLXmlDocument);
            }
            catch (Exception oException)
            {
                OnUnexpectedException(oException, oGraphMLXmlDocument,
                                      oRequestStatistics);
            }

            String sNetworkTitle = "Flickr Tag " + sTag;

            OnNetworkObtained(oGraphMLXmlDocument, oRequestStatistics,
                              GetNetworkDescription(sTag, eWhatToInclude, eNetworkLevel),
                              sNetworkTitle, sNetworkTitle
                              );

            return(oGraphMLXmlDocument);
        }
Пример #18
0
        GetNetwork
        (
            String screenNameToAnalyze,
            WhatToInclude whatToInclude,
            NetworkLevel networkLevel,
            Int32 maximumPeoplePerRequest
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(screenNameToAnalyze));

            Debug.Assert(networkLevel == NetworkLevel.One ||
                         networkLevel == NetworkLevel.OnePointFive ||
                         networkLevel == NetworkLevel.Two);

            Debug.Assert(maximumPeoplePerRequest > 0);
            AssertValid();

            return(GetUserNetworkInternal(screenNameToAnalyze, whatToInclude,
                                          networkLevel, maximumPeoplePerRequest));
        }
Пример #19
0
        GetNetworkInternal
        (
            String sSearchTerm,
            WhatToInclude eWhatToInclude,
            Int32 iMaximumStatuses
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(sSearchTerm));
            Debug.Assert(iMaximumStatuses > 0);
            Debug.Assert(iMaximumStatuses != Int32.MaxValue);
            AssertValid();

            BeforeGetNetwork();

            GraphMLXmlDocument oGraphMLXmlDocument =
                TwitterSearchNetworkGraphMLUtil.CreateGraphMLXmlDocument();

            RequestStatistics oRequestStatistics = new RequestStatistics();

            try
            {
                GetNetworkInternal(sSearchTerm, eWhatToInclude, iMaximumStatuses,
                                   oRequestStatistics, oGraphMLXmlDocument);
            }
            catch (Exception oException)
            {
                OnUnexpectedException(oException, oGraphMLXmlDocument,
                                      oRequestStatistics);
            }

            OnNetworkObtained(oGraphMLXmlDocument, oRequestStatistics,

                              GetNetworkDescription(sSearchTerm, eWhatToInclude,
                                                    iMaximumStatuses, oGraphMLXmlDocument),

                              SnaTitleCreator.CreateSnaTitle(sSearchTerm, oRequestStatistics),
                              "Twitter Search " + sSearchTerm
                              );

            return(oGraphMLXmlDocument);
        }
Пример #20
0
        GetRelatedTagsInternal
        (
            String sTag,
            WhatToInclude eWhatToInclude,
            NetworkLevel eNetworkLevel,
            String sApiKey
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(sTag));

            Debug.Assert(eNetworkLevel == NetworkLevel.One ||
                         eNetworkLevel == NetworkLevel.OnePointFive ||
                         eNetworkLevel == NetworkLevel.Two);

            Debug.Assert(!String.IsNullOrEmpty(sApiKey));
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument = CreateGraphMLXmlDocument(
                WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.SampleThumbnails));

            RequestStatistics oRequestStatistics = new RequestStatistics();

            try
            {
                GetRelatedTagsInternal(sTag, eWhatToInclude, eNetworkLevel,
                                       sApiKey, oRequestStatistics, oGraphMLXmlDocument);
            }
            catch (Exception oException)
            {
                OnTerminatingException(oException);
            }

            OnNetworkObtainedWithoutTerminatingException(oGraphMLXmlDocument,
                                                         oRequestStatistics);

            return(oGraphMLXmlDocument);
        }
        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);
            }
        }
Пример #22
0
        GetSearchNetworkInternal
        (
            String sSearchTerm,
            WhatToInclude eWhatToInclude,
            Int32 iMaximumPeoplePerRequest
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(sSearchTerm));
            Debug.Assert(iMaximumPeoplePerRequest > 0);
            AssertValid();

            BeforeGetNetwork();

            Boolean bIncludeStatistics = WhatToIncludeFlagIsSet(
                eWhatToInclude, WhatToInclude.Statistics);

            GraphMLXmlDocument oGraphMLXmlDocument = CreateGraphMLXmlDocument(
                bIncludeStatistics, false);

            RequestStatistics oRequestStatistics = new RequestStatistics();

            try
            {
                GetSearchNetworkInternal(sSearchTerm, eWhatToInclude,
                                         iMaximumPeoplePerRequest, oRequestStatistics,
                                         oGraphMLXmlDocument);
            }
            catch (Exception oException)
            {
                OnTerminatingException(oException);
            }

            OnNetworkObtainedWithoutTerminatingException(oGraphMLXmlDocument,
                                                         oRequestStatistics);

            return(oGraphMLXmlDocument);
        }
Пример #23
0
        AppendVertexXmlNodesForMentionsAndRepliesTo
        (
            WhatToInclude eWhatToInclude,
            GraphMLXmlDocument oGraphMLXmlDocument,
            Dictionary <String, TwitterUser> oUserIDDictionary,
            RequestStatistics oRequestStatistics
        )
        {
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oUserIDDictionary != null);
            Debug.Assert(oRequestStatistics != null);
            AssertValid();

            ReportProgress("Getting replied to and mentioned users.");

            // Get the screen names that were mentioned or replied to by the people
            // who tweeted the search term.

            String[] asUniqueMentionsAndRepliesToScreenNames =
                TwitterSearchNetworkGraphMLUtil.GetMentionsAndRepliesToScreenNames(
                    oUserIDDictionary);

            // Get information about each of those screen names and append vertex
            // XML nodes for them if necessary.

            foreach (Dictionary <String, Object> oUserValueDictionary in
                     EnumerateUserValueDictionaries(
                         asUniqueMentionsAndRepliesToScreenNames, false,
                         oRequestStatistics))
            {
                TwitterUser oTwitterUser;

                TwitterSearchNetworkGraphMLUtil.TryAppendVertexXmlNode(
                    oUserValueDictionary, false, oGraphMLXmlDocument,
                    oUserIDDictionary, out oTwitterUser);
            }
        }
        GetVideoNetworkInternal
        (
            String sSearchTerm,
            WhatToInclude eWhatToInclude,
            Int32 iMaximumVideos
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(sSearchTerm));
            Debug.Assert(iMaximumVideos > 0);
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument =
                CreateGraphMLXmlDocument(eWhatToInclude);

            RequestStatistics oRequestStatistics = new RequestStatistics();

            try
            {
                GetVideoNetworkInternal(sSearchTerm, eWhatToInclude,
                                        iMaximumVideos, oRequestStatistics, oGraphMLXmlDocument);
            }
            catch (Exception oException)
            {
                OnUnexpectedException(oException, oGraphMLXmlDocument,
                                      oRequestStatistics);
            }

            String sNetworkTitle = "YouTube Video " + sSearchTerm;

            OnNetworkObtained(oGraphMLXmlDocument, oRequestStatistics,
                              GetNetworkDescription(sSearchTerm, eWhatToInclude, iMaximumVideos),
                              sNetworkTitle, sNetworkTitle
                              );

            return(oGraphMLXmlDocument);
        }
        //*************************************************************************
        //  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: 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: 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: 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);
            }
            }
        }
    GetNetwork
    (
        String searchTerm,
        WhatToInclude whatToInclude,
        Int32 maximumStatuses
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(searchTerm) );
        Debug.Assert(maximumStatuses > 0);
        Debug.Assert(maximumStatuses != Int32.MaxValue);
        AssertValid();

        return ( GetNetworkInternal(searchTerm, whatToInclude,
            maximumStatuses) );
    }
        //*************************************************************************
        //  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);
            }
        }
    GetNetworkInternal
    (
        String sSearchTerm,
        WhatToInclude eWhatToInclude,
        Int32 iMaximumStatuses,
        RequestStatistics oRequestStatistics,
        GraphMLXmlDocument oGraphMLXmlDocument
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
        Debug.Assert(iMaximumStatuses > 0);
        Debug.Assert(iMaximumStatuses != Int32.MaxValue);
        Debug.Assert(oRequestStatistics != null);
        Debug.Assert(oGraphMLXmlDocument != null);
        AssertValid();

        // The key is the Twitter user ID and the value is the corresponding
        // TwitterUser.

        Dictionary<String, TwitterUser> oUserIDDictionary =
            new Dictionary<String, TwitterUser>();

        // First, append a vertex for each person who has tweeted the search
        // term.

        AppendVertexXmlNodesForSearchTerm(sSearchTerm, eWhatToInclude,
            iMaximumStatuses, oGraphMLXmlDocument, oUserIDDictionary,
            oRequestStatistics);

        // Now append a vertex for each person who was mentioned or replied to
        // by the first set of people, but who didn't tweet the search term
        // himself.

        AppendVertexXmlNodesForMentionsAndRepliesTo(eWhatToInclude,
            oGraphMLXmlDocument, oUserIDDictionary, oRequestStatistics);

        TwitterSearchNetworkGraphMLUtil.AppendVertexTooltipXmlNodes(
            oGraphMLXmlDocument, oUserIDDictionary);

        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.

            AppendFollowedOrFollowingEdgeXmlNodes(oUserIDDictionary, true,
                MaximumFollowers, oGraphMLXmlDocument, oRequestStatistics);
        }

        AppendRepliesToAndMentionsEdgeXmlNodes(oGraphMLXmlDocument,
            oUserIDDictionary.Values,

            TwitterGraphMLUtil.TwitterUsersToUniqueScreenNames(
                oUserIDDictionary.Values),

            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.RepliesToEdges),

            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.MentionsEdges),

            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.NonRepliesToNonMentionsEdges),

            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.Statuses)
            );
    }
    AppendVertexXmlNodesForMentionsAndRepliesTo
    (
        WhatToInclude eWhatToInclude,
        GraphMLXmlDocument oGraphMLXmlDocument,
        Dictionary<String, TwitterUser> oUserIDDictionary,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert(oUserIDDictionary != null);
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        ReportProgress("Getting replied to and mentioned users.");

        // Get the screen names that were mentioned or replied to by the people
        // who tweeted the search term.

        String[] asUniqueMentionsAndRepliesToScreenNames =
            TwitterSearchNetworkGraphMLUtil.GetMentionsAndRepliesToScreenNames(
                oUserIDDictionary);

        Boolean bIncludeStatistics = WhatToIncludeFlagIsSet(
            eWhatToInclude, WhatToInclude.Statistics);

        // Get information about each of those screen names and append vertex
        // XML nodes for them if necessary.

        foreach ( Dictionary<String, Object> oUserValueDictionary in
            EnumerateUserValueDictionaries(
                asUniqueMentionsAndRepliesToScreenNames, false,
                oRequestStatistics) )
        {
            TwitterUser oTwitterUser;

            TwitterSearchNetworkGraphMLUtil.TryAppendVertexXmlNode(
                oUserValueDictionary, bIncludeStatistics, false,
                oGraphMLXmlDocument, oUserIDDictionary, out oTwitterUser);
        }
    }
    AppendVertexXmlNodesForSearchTerm
    (
        String sSearchTerm,
        WhatToInclude eWhatToInclude,
        Int32 iMaximumStatuses,
        GraphMLXmlDocument oGraphMLXmlDocument,
        Dictionary<String, TwitterUser> oUserIDDictionary,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
        Debug.Assert(iMaximumStatuses > 0);
        Debug.Assert(iMaximumStatuses != Int32.MaxValue);
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert(oUserIDDictionary != null);
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        Boolean bExpandStatusUrls = WhatToIncludeFlagIsSet(
            eWhatToInclude, WhatToInclude.ExpandedStatusUrls);

        Boolean bIncludeStatistics = WhatToIncludeFlagIsSet(
            eWhatToInclude, WhatToInclude.Statistics);

        ReportProgress("Getting a list of tweets.");

        // Get the tweets that contain the search term.  Note that multiple
        // tweets may have the same author.

        Debug.Assert(m_oTwitterUtil != null);

        foreach ( Dictionary<String, Object> oStatusValueDictionary in

            m_oTwitterUtil.EnumerateSearchStatuses(
                sSearchTerm, iMaximumStatuses, oRequestStatistics,
                new ReportProgressHandler(this.ReportProgress),

                new CheckCancellationPendingHandler(
                    this.CheckCancellationPending)
                ) )
        {
            const String UserKeyName = "user";

            if ( !oStatusValueDictionary.ContainsKey(UserKeyName) )
            {
                // This has actually happened--Twitter occasionally sends a
                // status without user information.

                continue;
            }

            Dictionary<String, Object> oUserValueDictionary =
                ( Dictionary<String, Object> )
                oStatusValueDictionary[UserKeyName];

            TwitterUser oTwitterUser;

            if ( !TwitterSearchNetworkGraphMLUtil.
                TryAppendVertexXmlNode(oUserValueDictionary,
                    bIncludeStatistics, true, oGraphMLXmlDocument,
                    oUserIDDictionary, out oTwitterUser) )
            {
                continue;
            }

            // Parse the status and add it to the user's status collection.

            if ( !TwitterSearchNetworkGraphMLUtil.TryAddStatusToUser(
                oStatusValueDictionary, oTwitterUser, bExpandStatusUrls) )
            {
                continue;
            }
        }
    }
    GetVideoNetworkInternal
    (
        String sSearchTerm,
        WhatToInclude eWhatToInclude,
        Int32 iMaximumVideos
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
        Debug.Assert(iMaximumVideos > 0);
        AssertValid();

        GraphMLXmlDocument oGraphMLXmlDocument =
            CreateGraphMLXmlDocument(eWhatToInclude);

        RequestStatistics oRequestStatistics = new RequestStatistics();

        try
        {
            GetVideoNetworkInternal(sSearchTerm, eWhatToInclude,
                iMaximumVideos, oRequestStatistics, oGraphMLXmlDocument);
        }
        catch (Exception oException)
        {
            OnUnexpectedException(oException, oGraphMLXmlDocument,
                oRequestStatistics);
        }

        OnNetworkObtained(oGraphMLXmlDocument, oRequestStatistics, 
            GetNetworkDescription(sSearchTerm, eWhatToInclude, iMaximumVideos),
            "YouTube Video " + sSearchTerm
            );

        return (oGraphMLXmlDocument);
    }
    GetNetworkDescription
    (
        String sSearchTerm,
        WhatToInclude eWhatToInclude,
        Int32 iMaximumStatuses,
        GraphMLXmlDocument oGraphMLXmlDocument
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
        Debug.Assert(iMaximumStatuses > 0);
        Debug.Assert(iMaximumStatuses != Int32.MaxValue);
        Debug.Assert(oGraphMLXmlDocument != null);
        AssertValid();

        const String Int32FormatString = "N0";

        NetworkDescriber oNetworkDescriber = new NetworkDescriber();
        Int32 iVertexXmlNodes = oGraphMLXmlDocument.VertexXmlNodes;

        oNetworkDescriber.AddSentence(

            // WARNING:
            //
            // If you change this first sentence, you may also have to change
            // the code in
            // TwitterSearchNetworkTopItemsCalculator2.GetSearchTerm(), which
            // attempts to extract the search term from the graph description.

            "The graph represents a network of {0} Twitter {1} whose recent"
            + " tweets contained \"{2}\", or who {3} replied to or mentioned"
            + " in those tweets, taken from a data set limited to a maximum of"
            + " {4} tweets."
            ,
            iVertexXmlNodes.ToString(Int32FormatString),
            StringUtil.MakePlural("user", iVertexXmlNodes),
            sSearchTerm,
            iVertexXmlNodes > 1 ? "were" : "was",
            iMaximumStatuses.ToString(Int32FormatString)
            );

        oNetworkDescriber.AddNetworkTime(NetworkSource);

        Boolean bIncludeFollowedEdges = WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.FollowedEdges);

        Boolean bIncludeRepliesToEdges = WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.RepliesToEdges);

        Boolean bIncludeMentionsEdges = WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.MentionsEdges);

        Boolean bIncludeNonRepliesToNonMentionsEdges = WhatToIncludeFlagIsSet(
            eWhatToInclude, WhatToInclude.NonRepliesToNonMentionsEdges);

        if (bIncludeRepliesToEdges && bIncludeMentionsEdges &&
            bIncludeNonRepliesToNonMentionsEdges)
        {
            // Every collected tweet has an edge that contains the date of the
            // tweet, so the range of tweet dates can be determined.

            oNetworkDescriber.StartNewParagraph();

            TweetDateRangeAnalyzer.AddTweetDateRangeToNetworkDescription(
                oGraphMLXmlDocument, oNetworkDescriber);
        }

        if (bIncludeFollowedEdges || bIncludeRepliesToEdges ||
            bIncludeMentionsEdges || bIncludeNonRepliesToNonMentionsEdges)
        {
            oNetworkDescriber.StartNewParagraph();
        }

        if (bIncludeFollowedEdges)
        {
            oNetworkDescriber.AddSentence(
                "There is an edge for each follows relationship."
                );
        }

        if (bIncludeRepliesToEdges)
        {
            oNetworkDescriber.AddSentence(
                "There is an edge for each \"replies-to\" relationship in a"
                + " tweet."
                );
        }

        if (bIncludeMentionsEdges)
        {
            oNetworkDescriber.AddSentence(
                "There is an edge for each \"mentions\" relationship in a"
                + " tweet."
                );
        }

        if (bIncludeNonRepliesToNonMentionsEdges)
        {
            oNetworkDescriber.AddSentence(
                "There is a self-loop edge for each tweet that is not a"
                + " \"replies-to\" or \"mentions\"."
                );
        }

        return ( oNetworkDescriber.ConcatenateSentences() );
    }
        //*************************************************************************
        //  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: 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: GetUserNetworkInternal()
        //
        /// <overloads>
        /// Gets a network of YouTube users.
        /// </overloads>
        ///
        /// <summary>
        /// Gets a network of YouTube users.
        /// </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>
        ///
        /// <returns>
        /// An XmlDocument containing the network as GraphML.
        /// </returns>
        //*************************************************************************
        protected XmlDocument GetUserNetworkInternal(
            String sUserNameToAnalyze,
            WhatToInclude eWhatToInclude,
            NetworkLevel eNetworkLevel,
            Int32 iMaximumPeoplePerRequest
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sUserNameToAnalyze) );

            Debug.Assert(eNetworkLevel == NetworkLevel.One ||
            eNetworkLevel == NetworkLevel.OnePointFive ||
            eNetworkLevel == NetworkLevel.Two);

            Debug.Assert(iMaximumPeoplePerRequest > 0);
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument =
            CreateGraphMLXmlDocument( WhatToIncludeFlagIsSet(
                eWhatToInclude, WhatToInclude.AllStatistics) );

            RequestStatistics oRequestStatistics = new RequestStatistics();

            try
            {
            GetUserNetworkInternal(sUserNameToAnalyze, eWhatToInclude,
                eNetworkLevel, iMaximumPeoplePerRequest, oRequestStatistics,
                oGraphMLXmlDocument);
            }
            catch (Exception oException)
            {
            OnTerminatingException(oException);
            }

            OnNetworkObtainedWithoutTerminatingException(oGraphMLXmlDocument,
            oRequestStatistics);

            return (oGraphMLXmlDocument);
        }
        //*************************************************************************
        //  Method: GetNetworkAsync()
        //
        /// <summary>
        /// Asynchronously gets a directed network of YouTube users.
        /// </summary>
        ///
        /// <param name="userNameToAnalyze">
        /// The user name of the YouTube user whose network should be analyzed.
        /// </param>
        ///
        /// <param name="whatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="networkLevel">
        /// Network level to include.
        /// </param>
        ///
        /// <param name="maximumPeoplePerRequest">
        /// Maximum number of people to request for each query, or Int32.MaxValue
        /// for no limit.
        /// </param>
        ///
        /// <remarks>
        /// When the analysis completes, the <see
        /// cref="HttpNetworkAnalyzerBase.AnalysisCompleted" /> event fires.  The
        /// <see cref="RunWorkerCompletedEventArgs.Result" /> property will return
        /// an XmlDocument containing the network as GraphML.
        ///
        /// <para>
        /// To cancel the analysis, call <see
        /// cref="HttpNetworkAnalyzerBase.CancelAsync" />.
        /// </para>
        ///
        /// </remarks>
        //*************************************************************************
        public void GetNetworkAsync(
            String userNameToAnalyze,
            WhatToInclude whatToInclude,
            NetworkLevel networkLevel,
            Int32 maximumPeoplePerRequest
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(userNameToAnalyze) );

            Debug.Assert(networkLevel == NetworkLevel.One ||
            networkLevel == NetworkLevel.OnePointFive ||
            networkLevel == NetworkLevel.Two);

            Debug.Assert(maximumPeoplePerRequest > 0);

            AssertValid();

            const String MethodName = "GetNetworkAsync";
            CheckIsBusy(MethodName);

            // Wrap the arguments in an object that can be passed to
            // BackgroundWorker.RunWorkerAsync().

            GetNetworkAsyncArgs oGetNetworkAsyncArgs = new GetNetworkAsyncArgs();

            oGetNetworkAsyncArgs.UserNameToAnalyze = userNameToAnalyze;
            oGetNetworkAsyncArgs.WhatToInclude = whatToInclude;
            oGetNetworkAsyncArgs.NetworkLevel = networkLevel;
            oGetNetworkAsyncArgs.MaximumPeoplePerRequest = maximumPeoplePerRequest;

            m_oBackgroundWorker.RunWorkerAsync(oGetNetworkAsyncArgs);
        }
        AppendVertexXmlNodes
        (
            String sSearchTerm,
            WhatToInclude eWhatToInclude,
            Int32 iMaximumVideos,
            GraphMLXmlDocument oGraphMLXmlDocument,
            RequestStatistics oRequestStatistics,
            out HashSet <String> oVideoIDs,
            out Dictionary <String, LinkedList <String> > oCategoryDictionary
        )
        {
            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 category, the key is a lower-case category and the value is a
            // LinkedList of the video IDs that have the category.

            if (WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.SharedCategoryEdges))
            {
                oCategoryDictionary =
                    new Dictionary <String, LinkedList <String> >();
            }
            else
            {
                oCategoryDictionary = 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);

                AppendStringGraphMLAttributeValue(oEntryXmlNode,
                                                  "a:author/a:name/text()", oXmlNamespaceManager,
                                                  oGraphMLXmlDocument, oVertexXmlNode, AuthorID);

                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,
                                                  NodeXLGraphMLUtil.VertexImageFileID);

                if (AppendStringGraphMLAttributeValue(oEntryXmlNode,
                                                      "media:group/media:player/@url", oXmlNamespaceManager,
                                                      oGraphMLXmlDocument, oVertexXmlNode,
                                                      NodeXLGraphMLUtil.VertexMenuActionID))
                {
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode,
                                                                    NodeXLGraphMLUtil.VertexMenuTextID,
                                                                    "Play Video in Browser");
                }

                if (oCategoryDictionary != null)
                {
                    CollectCategories(oEntryXmlNode, sVideoID,
                                      oXmlNamespaceManager, oCategoryDictionary);
                }
            }
        }
        //*************************************************************************
        //  Method: GetRelatedTagsInternal()
        //
        /// <overloads>
        /// Gets the Flickr tags related to a specified tag.
        /// </overloads>
        ///
        /// <summary>
        /// Gets the Flickr tags related to a specified tag.
        /// </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>
        ///
        /// <returns>
        /// An XmlDocument containing the network as GraphML.
        /// </returns>
        //*************************************************************************
        protected XmlDocument GetRelatedTagsInternal(
            String sTag,
            WhatToInclude eWhatToInclude,
            NetworkLevel eNetworkLevel,
            String sApiKey
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sTag) );

            Debug.Assert(eNetworkLevel == NetworkLevel.One ||
            eNetworkLevel == NetworkLevel.OnePointFive ||
            eNetworkLevel == NetworkLevel.Two);

            Debug.Assert( !String.IsNullOrEmpty(sApiKey) );
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument = CreateGraphMLXmlDocument(
            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.SampleThumbnails) );

            RequestStatistics oRequestStatistics = new RequestStatistics();

            try
            {
            GetRelatedTagsInternal(sTag, eWhatToInclude, eNetworkLevel,
                sApiKey, oRequestStatistics, oGraphMLXmlDocument);
            }
            catch (Exception oException)
            {
            OnTerminatingException(oException);
            }

            OnNetworkObtainedWithoutTerminatingException(oGraphMLXmlDocument,
            oRequestStatistics);

            return (oGraphMLXmlDocument);
        }
        GetNetworkDescription
        (
            String sSearchTerm,
            WhatToInclude eWhatToInclude,
            Int32 iMaximumVideos
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(sSearchTerm));
            Debug.Assert(iMaximumVideos > 0);
            AssertValid();

            NetworkDescriber oNetworkDescriber = new NetworkDescriber();

            oNetworkDescriber.AddSentence(

                "The graph represents the network of YouTube videos whose title,"
                + " keywords, description, categories, or author\'s username"
                + " contain \"{0}\"."
                ,
                sSearchTerm
                );

            oNetworkDescriber.AddNetworkTime(NetworkSource);

            oNetworkDescriber.StartNewParagraph();
            oNetworkDescriber.AddNetworkLimit(iMaximumVideos, "videos");

            if (WhatToIncludeFlagIsSet(eWhatToInclude,

                                       WhatToInclude.SharedCategoryEdges
                                       |
                                       WhatToInclude.SharedCommenterEdges
                                       |
                                       WhatToInclude.SharedVideoResponderEdges
                                       ))
            {
                oNetworkDescriber.StartNewParagraph();
            }

            if (WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.SharedCategoryEdges))
            {
                oNetworkDescriber.AddSentence(
                    "There is an edge for each pair of videos that have the same"
                    + " category."
                    );
            }

            if (WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.SharedCommenterEdges))
            {
                oNetworkDescriber.AddSentence(
                    "There is an edge for each pair of videos commented on by the"
                    + " same user."
                    );
            }

            if (WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.SharedVideoResponderEdges))
            {
                oNetworkDescriber.AddSentence(
                    "There is an edge for each pair of videos responded to with"
                    + " another video by the same user."
                    );
            }

            return(oNetworkDescriber.ConcatenateSentences());
        }
        //*************************************************************************
        //  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: GetNetwork()
        //
        /// <summary>
        /// Synchronously gets a directed network of people who have tweeted a
        /// specified search term.
        /// </summary>
        ///
        /// <param name="searchTerm">
        /// The term to search for.
        /// </param>
        ///
        /// <param name="whatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="maximumPeoplePerRequest">
        /// Maximum number of people to request for each query, or Int32.MaxValue
        /// for no limit.
        /// </param>
        ///
        /// <returns>
        /// An XmlDocument containing the network as GraphML.
        /// </returns>
        //*************************************************************************
        public XmlDocument GetNetwork(
            String searchTerm,
            WhatToInclude whatToInclude,
            Int32 maximumPeoplePerRequest
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(searchTerm) );
            Debug.Assert(maximumPeoplePerRequest > 0);
            AssertValid();

            return ( GetSearchNetworkInternal(searchTerm, whatToInclude,
            maximumPeoplePerRequest) );
        }
    GetNetworkInternal
    (
        String sSearchTerm,
        WhatToInclude eWhatToInclude,
        Int32 iMaximumStatuses
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
        Debug.Assert(iMaximumStatuses > 0);
        Debug.Assert(iMaximumStatuses != Int32.MaxValue);
        AssertValid();

        BeforeGetNetwork();

        Boolean bIncludeStatistics = WhatToIncludeFlagIsSet(
            eWhatToInclude, WhatToInclude.Statistics);

        Boolean bIncludeStatuses = WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.Statuses);

        GraphMLXmlDocument oGraphMLXmlDocument =
            TwitterSearchNetworkGraphMLUtil.CreateGraphMLXmlDocument(
                bIncludeStatistics, bIncludeStatuses);

        RequestStatistics oRequestStatistics = new RequestStatistics();

        try
        {
            GetNetworkInternal(sSearchTerm, eWhatToInclude, iMaximumStatuses,
                oRequestStatistics, oGraphMLXmlDocument);
        }
        catch (Exception oException)
        {
            OnUnexpectedException(oException, oGraphMLXmlDocument,
                oRequestStatistics);
        }

        OnNetworkObtained(oGraphMLXmlDocument, oRequestStatistics, 

            GetNetworkDescription(sSearchTerm, eWhatToInclude,
                iMaximumStatuses, oGraphMLXmlDocument),

            "Twitter Search " + sSearchTerm
            );

        return (oGraphMLXmlDocument);
    }
        //*************************************************************************
        //  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);
                    }
                }
            }
            }
        }
        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 (!HttpSocialNetworkUtil.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);
                }
            }
        }
Пример #48
0
        GetUserNetworkInternal
        (
            String sScreenName,
            WhatToInclude eWhatToInclude,
            NetworkLevel eNetworkLevel,
            Int32 iMaximumPerRequest,
            String sApiKey,
            RequestStatistics oRequestStatistics,
            GraphMLXmlDocument oGraphMLXmlDocument
        )
        {
            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(oRequestStatistics != null);
            Debug.Assert(oGraphMLXmlDocument != null);
            AssertValid();

            String sUserID;

            // Get the user's ID and the correct case of the screen name.

            FlickrScreenNameToUserID(sScreenName, sApiKey, oRequestStatistics,
                                     out sUserID, out sScreenName);

            // The key is the user ID name and the value is the corresponding
            // GraphML XML node that represents the user.  This is used to prevent
            // the same user ID from being added to the XmlDocument twice.

            Dictionary <String, XmlNode> oUserIDDictionary =
                new Dictionary <String, XmlNode>();

            // Include contacts, commenters, both, or neither.

            Boolean [] abIncludes = new Boolean[] {
                WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.ContactVertices),

                WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.CommenterVertices),
            };

            for (Int32 i = 0; i < 2; i++)
            {
                if (abIncludes[i])
                {
                    GetUserNetworkRecursive(sUserID, sScreenName, eWhatToInclude,
                                            (i == 0), eNetworkLevel, iMaximumPerRequest, sApiKey, 1,
                                            oGraphMLXmlDocument, oUserIDDictionary,
                                            oRequestStatistics);
                }
            }

            if (WhatToIncludeFlagIsSet(eWhatToInclude,
                                       WhatToInclude.UserInformation))
            {
                AppendUserInformationGraphMLAttributeValues(oGraphMLXmlDocument,
                                                            oUserIDDictionary, sApiKey, oRequestStatistics);
            }
        }
        //*************************************************************************
        //  Method: GetNetworkAsync()
        //
        /// <summary>
        /// Asynchronously gets an undirected network of related YouTube videos.
        /// </summary>
        ///
        /// <param name="searchTerm">
        /// The term to search for.
        /// </param>
        ///
        /// <param name="whatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="maximumVideos">
        /// Maximum number of videos to request, or Int32.MaxValue for no limit.
        /// </param>
        ///
        /// <remarks>
        /// When the analysis completes, the <see
        /// cref="HttpNetworkAnalyzerBase.AnalysisCompleted" /> event fires.  The
        /// <see cref="RunWorkerCompletedEventArgs.Result" /> property will return
        /// an XmlDocument containing the network as GraphML.
        ///
        /// <para>
        /// To cancel the analysis, call <see
        /// cref="HttpNetworkAnalyzerBase.CancelAsync" />.
        /// </para>
        ///
        /// </remarks>
        //*************************************************************************
        public void GetNetworkAsync(
            String searchTerm,
            WhatToInclude whatToInclude,
            Int32 maximumVideos
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(searchTerm) );
            Debug.Assert(maximumVideos > 0);
            AssertValid();

            const String MethodName = "GetNetworkAsync";
            CheckIsBusy(MethodName);

            // Wrap the arguments in an object that can be passed to
            // BackgroundWorker.RunWorkerAsync().

            GetNetworkAsyncArgs oGetNetworkAsyncArgs = new GetNetworkAsyncArgs();

            oGetNetworkAsyncArgs.SearchTerm = searchTerm;
            oGetNetworkAsyncArgs.WhatToInclude = whatToInclude;
            oGetNetworkAsyncArgs.MaximumVideos = maximumVideos;

            m_oBackgroundWorker.RunWorkerAsync(oGetNetworkAsyncArgs);
        }
    CreateGraphMLXmlDocument
    (
        WhatToInclude eWhatToInclude
    )
    {
        AssertValid();

        GraphMLXmlDocument oGraphMLXmlDocument = new GraphMLXmlDocument(false);

        NodeXLGraphMLUtil.DefineEdgeRelationshipGraphMLAttribute(
            oGraphMLXmlDocument);

        if ( WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.SharedCategoryEdges) )
        {
            oGraphMLXmlDocument.DefineEdgeStringGraphMLAttributes(
                SharedCategoryID, "Shared Category");
        }

        if ( WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.SharedCommenterEdges) )
        {
            oGraphMLXmlDocument.DefineEdgeStringGraphMLAttributes(
                SharedCommenterID, "Shared Commenter");
        }

        if ( WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.SharedVideoResponderEdges) )
        {
            oGraphMLXmlDocument.DefineEdgeStringGraphMLAttributes(
                SharedVideoResponderID, "Shared Video Responder");
        }

        oGraphMLXmlDocument.DefineVertexStringGraphMLAttributes(
            TitleID, "Title",
            AuthorID, "Author",
            CreatedDateUtcID, "Created Date (UTC)"
            );

        oGraphMLXmlDocument.DefineGraphMLAttribute(false, RatingID,
            "Rating", "double", null);

        oGraphMLXmlDocument.DefineGraphMLAttributes(false, "int",
            ViewsID, "Views",
            FavoritedID, "Favorited",
            CommentsID, "Comments"
            );

        NodeXLGraphMLUtil.DefineVertexImageFileGraphMLAttribute(
            oGraphMLXmlDocument);

        NodeXLGraphMLUtil.DefineVertexCustomMenuGraphMLAttributes(
            oGraphMLXmlDocument);

        return (oGraphMLXmlDocument);
    }
        //*************************************************************************
        //  Method: GetVideoNetworkInternal()
        //
        /// <overloads>
        /// Gets a network of related YouTube videos.
        /// </overloads>
        ///
        /// <summary>
        /// Gets a network of related YouTube videos.
        /// </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>
        ///
        /// <returns>
        /// An XmlDocument containing the network as GraphML.
        /// </returns>
        //*************************************************************************
        protected XmlDocument GetVideoNetworkInternal(
            String sSearchTerm,
            WhatToInclude eWhatToInclude,
            Int32 iMaximumVideos
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
            Debug.Assert(iMaximumVideos > 0);
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument = CreateGraphMLXmlDocument();
            RequestStatistics oRequestStatistics = new RequestStatistics();

            try
            {
            GetVideoNetworkInternal(sSearchTerm, eWhatToInclude,
                iMaximumVideos, oRequestStatistics, oGraphMLXmlDocument);
            }
            catch (Exception oException)
            {
            OnTerminatingException(oException);
            }

            OnNetworkObtainedWithoutTerminatingException(oGraphMLXmlDocument,
            oRequestStatistics);

            return (oGraphMLXmlDocument);
        }
        //*************************************************************************
        //  Method: GetNetwork()
        //
        /// <summary>
        /// Synchronously gets a directed network of Twitter users.
        /// </summary>
        ///
        /// <param name="screenNameToAnalyze">
        /// The screen name of the Twitter user whose network should be analyzed.
        /// </param>
        ///
        /// <param name="whatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="networkLevel">
        /// Network level to include.
        /// </param>
        ///
        /// <param name="maximumPeoplePerRequest">
        /// Maximum number of people to request for each query, or Int32.MaxValue
        /// for no limit.
        /// </param>
        ///
        /// <returns>
        /// An XmlDocument containing the network as GraphML.
        /// </returns>
        //*************************************************************************
        public XmlDocument GetNetwork(
            String screenNameToAnalyze,
            WhatToInclude whatToInclude,
            NetworkLevel networkLevel,
            Int32 maximumPeoplePerRequest
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(screenNameToAnalyze) );

            Debug.Assert(networkLevel == NetworkLevel.One ||
            networkLevel == NetworkLevel.OnePointFive ||
            networkLevel == NetworkLevel.Two);

            Debug.Assert(maximumPeoplePerRequest > 0);
            AssertValid();

            return ( GetUserNetworkInternal(screenNameToAnalyze, whatToInclude,
            networkLevel, maximumPeoplePerRequest) );
        }
 //*************************************************************************
 //  Method: WhatToIncludeFlagIsSet()
 //
 /// <summary>
 /// Checks whether a flag is set in an ORed combination of WhatToInclude
 /// flags.
 /// </summary>
 ///
 /// <param name="eORedEnumFlags">
 /// Zero or more ORed Enum flags.
 /// </param>
 ///
 /// <param name="eORedEnumFlagsToCheck">
 /// One or more Enum flags to check.
 /// </param>
 ///
 /// <returns>
 /// true if any of the <paramref name="eORedEnumFlagsToCheck" /> flags are
 /// set in <paramref name="eORedEnumFlags" />.
 /// </returns>
 //*************************************************************************
 protected Boolean WhatToIncludeFlagIsSet(
     WhatToInclude eORedEnumFlags,
     WhatToInclude eORedEnumFlagsToCheck
     )
 {
     return ( (eORedEnumFlags & eORedEnumFlagsToCheck) != 0 );
 }
        //*************************************************************************
        //  Method: GetNetworkAsync()
        //
        /// <summary>
        /// Asynchronously gets an undirected network of Flickr tags related to a
        /// specified tag.
        /// </summary>
        ///
        /// <param name="tag">
        /// Tag to get related tags for.
        /// </param>
        ///
        /// <param name="whatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="networkLevel">
        /// Network level to include.
        /// </param>
        ///
        /// <param name="apiKey">
        /// Flickr API key.
        /// </param>
        ///
        /// <remarks>
        /// When the analysis completes, the <see
        /// cref="HttpNetworkAnalyzerBase.AnalysisCompleted" /> event fires.  The
        /// <see cref="RunWorkerCompletedEventArgs.Result" /> property will return
        /// an XmlDocument containing the network as GraphML.
        ///
        /// <para>
        /// To cancel the analysis, call <see
        /// cref="HttpNetworkAnalyzerBase.CancelAsync" />.
        /// </para>
        ///
        /// </remarks>
        //*************************************************************************
        public void GetNetworkAsync(
            String tag,
            WhatToInclude whatToInclude,
            NetworkLevel networkLevel,
            String apiKey
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(tag) );

            Debug.Assert(networkLevel == NetworkLevel.One ||
            networkLevel == NetworkLevel.OnePointFive ||
            networkLevel == NetworkLevel.Two);

            Debug.Assert( !String.IsNullOrEmpty(apiKey) );
            AssertValid();

            const String MethodName = "GetNetworkAsync";
            CheckIsBusy(MethodName);

            // Wrap the arguments in an object that can be passed to
            // BackgroundWorker.RunWorkerAsync().

            GetNetworkAsyncArgs oGetNetworkAsyncArgs = new GetNetworkAsyncArgs();

            oGetNetworkAsyncArgs.Tag = tag;
            oGetNetworkAsyncArgs.NetworkLevel = networkLevel;
            oGetNetworkAsyncArgs.WhatToInclude = whatToInclude;
            oGetNetworkAsyncArgs.ApiKey = apiKey;

            m_oBackgroundWorker.RunWorkerAsync(oGetNetworkAsyncArgs);
        }
        //*************************************************************************
        //  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);
                    }
                }
            }
            }
        }
Пример #56
0
        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);
                }
            }
        }
        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;
                        }
                    }

                    NodeXLGraphMLUtil.AppendEdgeXmlNode(oGraphMLXmlDocument,
                                                        sUserName, sOtherUserName, sRelationship);
                }
            }

            if (bNeedToRecurse)
            {
                foreach (String sUserNameToRecurse in oUserNamesToRecurse)
                {
                    GetUserNetworkRecursive(sUserNameToRecurse,
                                            eWhatToInclude, bIncludeFriendsThisCall, eNetworkLevel,
                                            iMaximumPeoplePerRequest, 2, oGraphMLXmlDocument,
                                            oUserNameDictionary, oRequestStatistics);
                }
            }
        }
        //*************************************************************************
        //  Method: GetSearchNetworkInternal()
        //
        /// <overloads>
        /// Gets the network of people who have tweeted a specified search term.
        /// </overloads>
        ///
        /// <summary>
        /// Gets the network of people who have 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>
        ///
        /// <returns>
        /// An XmlDocument containing the network as GraphML.
        /// </returns>
        //*************************************************************************
        protected XmlDocument GetSearchNetworkInternal(
            String sSearchTerm,
            WhatToInclude eWhatToInclude,
            Int32 iMaximumPeoplePerRequest
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
            Debug.Assert(iMaximumPeoplePerRequest > 0);
            AssertValid();

            BeforeGetNetwork();

            Boolean bIncludeStatistics = WhatToIncludeFlagIsSet(
            eWhatToInclude, WhatToInclude.Statistics);

            GraphMLXmlDocument oGraphMLXmlDocument = CreateGraphMLXmlDocument(
            bIncludeStatistics, false);

            RequestStatistics oRequestStatistics = new RequestStatistics();

            try
            {
            GetSearchNetworkInternal(sSearchTerm, eWhatToInclude,
                iMaximumPeoplePerRequest, oRequestStatistics,
                oGraphMLXmlDocument);
            }
            catch (Exception oException)
            {
            OnTerminatingException(oException);
            }

            OnNetworkObtainedWithoutTerminatingException(oGraphMLXmlDocument,
            oRequestStatistics);

            return (oGraphMLXmlDocument);
        }
        //*************************************************************************
        //  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)
            );
        }
    GetNetworkDescription
    (
        String sSearchTerm,
        WhatToInclude eWhatToInclude,
        Int32 iMaximumVideos
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
        Debug.Assert(iMaximumVideos > 0);
        AssertValid();

        NetworkDescriber oNetworkDescriber = new NetworkDescriber();

        oNetworkDescriber.AddSentence(

            "The graph represents the network of YouTube videos whose title,"
            + " keywords, description, categories, or author\'s username"
            + " contain \"{0}\"."
            ,
            sSearchTerm
            );

        oNetworkDescriber.AddNetworkTime(NetworkSource);

        oNetworkDescriber.StartNewParagraph();
        oNetworkDescriber.AddNetworkLimit(iMaximumVideos, "videos");

        if ( WhatToIncludeFlagIsSet(eWhatToInclude,

            WhatToInclude.SharedCategoryEdges
            |
            WhatToInclude.SharedCommenterEdges
            |
            WhatToInclude.SharedVideoResponderEdges
            ) )
        {
            oNetworkDescriber.StartNewParagraph();
        }

        if ( WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.SharedCategoryEdges) )
        {
            oNetworkDescriber.AddSentence(
                "There is an edge for each pair of videos that have the same"
                + " category."
                );
        }

        if ( WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.SharedCommenterEdges) )
        {
            oNetworkDescriber.AddSentence(
                "There is an edge for each pair of videos commented on by the"
                + " same user."
                );
        }

        if ( WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.SharedVideoResponderEdges) )
        {
            oNetworkDescriber.AddSentence(
                "There is an edge for each pair of videos responded to with"
                + " another video by the same user."
                );
        }

        return ( oNetworkDescriber.ConcatenateSentences() );
    }