Keeps track of requests that have been made while getting a network.
Call OnSuccessfulRequest each time a network request succeeds. Call OnUnexpectedException each time a network request fails (after retries) with an unexpected exception. After all requests have been made, read the SuccessfulRequests, , and LastUnexpectedException properties to create a request summary.
Inheritance: Object
        GetHttpWebResponseStreamWithRetries
        (
            String url,
            HttpStatusCode [] httpStatusCodesToFailImmediately,
            RequestStatistics requestStatistics,
            String userAgent,
            Int32 timeoutMs,
            ReportProgressHandler reportProgressHandler,
            CheckCancellationPendingHandler checkCancellationPendingHandler
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(url));
            Debug.Assert(requestStatistics != null);
            Debug.Assert(!String.IsNullOrEmpty(userAgent));
            Debug.Assert(timeoutMs > 0);

            Int32 iMaximumRetries = HttpRetryDelaysSec.Length;
            Int32 iRetriesSoFar   = 0;

            while (true)
            {
                if (reportProgressHandler != null && iRetriesSoFar > 0)
                {
                    reportProgressHandler("Retrying request.");
                }

                // Important Note: You cannot use the same HttpWebRequest object
                // for the retries.  The object must be recreated each time.

                HttpWebRequest oHttpWebRequest = CreateHttpWebRequest(
                    url, userAgent, timeoutMs);

                Stream oStream = null;

                try
                {
                    if (checkCancellationPendingHandler != null)
                    {
                        checkCancellationPendingHandler();
                    }

                    oStream = GetHttpWebResponseStreamNoRetries(oHttpWebRequest);

                    if (reportProgressHandler != null && iRetriesSoFar > 0)
                    {
                        reportProgressHandler("Retry succeeded, continuing...");
                    }

                    requestStatistics.OnSuccessfulRequest();

                    return(oStream);
                }
                catch (Exception oException)
                {
                #if WriteRequestsToDebug
                    Debug.WriteLine("Exception: " + oException.Message);
                #endif

                    if (oStream != null)
                    {
                        oStream.Close();
                    }

                    // The IOException "Unable to read data from the transport"
                    // connection: The connection was closed." can occur when
                    // requesting data from Twitter, for example.

                    if (!(
                            oException is WebException
                            ||
                            oException is IOException
                            ))
                    {
                        throw oException;
                    }

                    if (iRetriesSoFar == iMaximumRetries)
                    {
                        requestStatistics.OnUnexpectedException(oException);

                        throw (oException);
                    }

                    // If the status code is one of the ones specified in
                    // httpStatusCodesToFailImmediately, rethrow the exception
                    // without retrying the request.

                    if (httpStatusCodesToFailImmediately != null &&
                        oException is WebException)
                    {
                        if (WebExceptionHasHttpStatusCode(
                                (WebException)oException,
                                httpStatusCodesToFailImmediately))
                        {
                            throw (oException);
                        }
                    }

                    Int32 iSeconds = HttpRetryDelaysSec[iRetriesSoFar];

                    if (reportProgressHandler != null)
                    {
                        reportProgressHandler(String.Format(

                                                  "Request failed, pausing {0} {1} before retrying..."
                                                  ,
                                                  iSeconds,
                                                  StringUtil.MakePlural("second", iSeconds)
                                                  ));
                    }

                    System.Threading.Thread.Sleep(1000 * iSeconds);
                    iRetriesSoFar++;
                }
            }
        }
    TryGetXmlDocument
    (
        String sUrl,
        RequestStatistics oRequestStatistics,
        out XmlDocument oXmlDocument,
        out XmlNamespaceManager oXmlNamespaceManager
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sUrl) );
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        oXmlDocument = null;
        oXmlNamespaceManager = null;

        try
        {
            oXmlDocument = GetXmlDocument(sUrl, oRequestStatistics,
                out oXmlNamespaceManager);

            return (true);
        }
        catch (Exception oException)
        {
            if ( !HttpSocialNetworkUtil.ExceptionIsWebOrXml(oException))
            {
                throw oException;
            }

            return (false);
        }
    }
    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);
    }
    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);
            }
        }
    }
    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;
            }
        }
    }
    GetNetworkInternal
    (
        String sSearchTerm,
        DateTime oMinimumStatusDateUtc,
        DateTime oMaximumStatusDateUtc,
        Boolean bExpandStatusUrls,
        String sGraphServerUserName,
        String sGraphServerPassword
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
        Debug.Assert(oMaximumStatusDateUtc >= oMinimumStatusDateUtc);
        Debug.Assert( !String.IsNullOrEmpty(sGraphServerUserName) );
        Debug.Assert( !String.IsNullOrEmpty(sGraphServerPassword) );
        AssertValid();

        XmlDocument oGraphMLXmlDocument = null;
        RequestStatistics oRequestStatistics = new RequestStatistics();

        try
        {
            oGraphMLXmlDocument = GetNetworkInternal(
                sSearchTerm, oMinimumStatusDateUtc, oMaximumStatusDateUtc,
                bExpandStatusUrls, sGraphServerUserName, sGraphServerPassword,
                oRequestStatistics);
        }
        catch (Exception oException)
        {
            OnUnexpectedException(oException, new XmlDocument(),
                oRequestStatistics);
        }

        OnNetworkObtained(oGraphMLXmlDocument, oRequestStatistics, 

            GetNetworkDescription(sSearchTerm, oMinimumStatusDateUtc,
                oMaximumStatusDateUtc, sGraphServerUserName,
                sGraphServerPassword, oGraphMLXmlDocument),

            "Graph Server " + sSearchTerm
            );

        return (oGraphMLXmlDocument);
    }
    TryGetUserValueDictionary
    (
        String sScreenName,
        RequestStatistics oRequestStatistics,
        Boolean bIgnoreWebAndJsonExceptions,
        out Dictionary<String, Object> oUserValueDictionary
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sScreenName) );
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        oUserValueDictionary = null;

        String sUrl = String.Format(

            "{0}users/show.json?screen_name={1}&{2}"
            ,
            TwitterApiUrls.Rest,
            TwitterUtil.EncodeUrlParameter(sScreenName),
            TwitterApiUrlParameters.IncludeEntities
            );

        ReportProgress( String.Format(

            "Getting information about \"{0}\"."
            ,
            sScreenName
            ) );
            
        try
        {
            oUserValueDictionary = ( Dictionary<String, Object> )
                ( new JavaScriptSerializer() ).DeserializeObject(
                    GetTwitterResponseAsString(sUrl, oRequestStatistics) );

            return (true);
        }
        catch (Exception oException)
        {
            if (!HttpSocialNetworkUtil.ExceptionIsWebOrJson(oException) ||
                !bIgnoreWebAndJsonExceptions)
            {
                throw oException;
            }

            return (false);
        }
    }
    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);
    }
    AppendFollowedOrFollowingEdgeXmlNodes
    (
        ICollection<String> oScreenNames,
        Dictionary<String, TwitterUser> oUserIDDictionary,
        Boolean bFollowed,
        Int32 iMaximumPeoplePerRequest,
        GraphMLXmlDocument oGraphMLXmlDocument,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert(oScreenNames != null);
        Debug.Assert(oUserIDDictionary != null);
        Debug.Assert(iMaximumPeoplePerRequest > 0);
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        foreach (String sScreenName in oScreenNames)
        {
            ReportProgressForFollowedOrFollowing(sScreenName, bFollowed);

            // We need to find out who are the followed (or followers) of
            // sScreenName, and see if any of them are in oUserIDDictionary,
            // which means they are in the network.

            foreach ( String sOtherUserID in EnumerateFriendOrFollowerIDs(
                sScreenName, bFollowed, iMaximumPeoplePerRequest,
                oRequestStatistics) )
            {
                TwitterUser oOtherTwitterUser;

                if ( oUserIDDictionary.TryGetValue(sOtherUserID,
                    out oOtherTwitterUser) )
                {
                    // sScreenName is a followed (or follower) of sOtherUserID.

                    AppendFollowedOrFollowingEdgeXmlNode(sScreenName,
                        oOtherTwitterUser.ScreenName, bFollowed,
                        oGraphMLXmlDocument, oRequestStatistics);
                }
            }
        }
    }
    AppendFollowedOrFollowingEdgeXmlNode
    (
        String sScreenName,
        String sOtherScreenName,
        Boolean bFollowed,
        GraphMLXmlDocument oGraphMLXmlDocument,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sScreenName) );
        Debug.Assert( !String.IsNullOrEmpty(sOtherScreenName) );
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        XmlNode oEdgeXmlNode;

        if (bFollowed)
        {
            oEdgeXmlNode = NodeXLGraphMLUtil.AppendEdgeXmlNode(
                oGraphMLXmlDocument, sScreenName, sOtherScreenName,
                "Followed");
        }
        else
        {
            oEdgeXmlNode = NodeXLGraphMLUtil.AppendEdgeXmlNode(
                oGraphMLXmlDocument, sOtherScreenName, sScreenName,
                "Follower");
        }

        AppendStartTimeRelationshipDateUtcGraphMLAttributeValue(
            oGraphMLXmlDocument, oEdgeXmlNode, oRequestStatistics);
    }
    TryGetSampleImageUrl
    (
        String sTag,
        String sApiKey,
        RequestStatistics oRequestStatistics,
        out String sSampleImageUrl
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sTag) );
        Debug.Assert( !String.IsNullOrEmpty(sApiKey) );
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        sSampleImageUrl = null;

        String sUrl = GetFlickrMethodUrl( "flickr.tags.getClusterPhotos",
            sApiKey, "&tag=" + UrlUtil.EncodeUrlParameter(sTag) );

        XmlDocument oXmlDocument;
        String sPhotoID;
        
        if (
            !TryGetXmlDocument(sUrl, oRequestStatistics, out oXmlDocument)
            ||
            ! XmlUtil2.TrySelectSingleNodeAsString(oXmlDocument,
                "rsp/photos/photo/@id", null, out sPhotoID)
            )
        {
            return (false);
        }

        sUrl = GetFlickrMethodUrl( "flickr.photos.getSizes", sApiKey,
            "&photo_id=" + UrlUtil.EncodeUrlParameter(sPhotoID) );

        if (
            !TryGetXmlDocument(sUrl, oRequestStatistics, out oXmlDocument)
            ||
            ! XmlUtil2.TrySelectSingleNodeAsString(oXmlDocument,
                "rsp/sizes/size[@label='Thumbnail']/@source", null,
                out sSampleImageUrl)
            )
        {
            return (false);
        }

        return (true);
    }
    AppendSampleThumbnails
    (
        Dictionary<String, XmlNode> oTagDictionary,
        GraphMLXmlDocument oGraphMLXmlDocument,
        String sApiKey,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert(oTagDictionary != null);
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert( !String.IsNullOrEmpty(sApiKey) );
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        foreach (KeyValuePair<String, XmlNode> oKeyValuePair in oTagDictionary)
        {
            String sTag = oKeyValuePair.Key;

            ReportProgress("Getting sample image file for \"" + sTag + "\".");

            String sSampleImageUrl;

            if ( TryGetSampleImageUrl(sTag, sApiKey, oRequestStatistics,
                out sSampleImageUrl) )
            {
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                    oKeyValuePair.Value, NodeXLGraphMLUtil.VertexImageFileID,
                    sSampleImageUrl);
            }
        }
    }
    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);
            }
        }
    }
    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);
        }
    }
    GetTwitterResponseAsString
    (
        String sUrl,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sUrl) );
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        Debug.Assert(m_oTwitterUtil != null);

        return ( m_oTwitterUtil.GetTwitterResponseAsString(sUrl,
            oRequestStatistics,
            new ReportProgressHandler(this.ReportProgress),
            new CheckCancellationPendingHandler(this.CheckCancellationPending)
            ) );
    }
    AppendVertexXmlNodes
    (
        Boolean bUseListName,
        String sListName,
        ICollection<String> oScreenNames,
        WhatToInclude eWhatToInclude,
        GraphMLXmlDocument oGraphMLXmlDocument,
        Dictionary<String, TwitterUser> oUserIDDictionary,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert( !bUseListName || !String.IsNullOrEmpty(sListName) );
        Debug.Assert(bUseListName || oScreenNames != null);
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert(oUserIDDictionary != null);
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

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

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

        Boolean bExpandLatestStatusUrls = WhatToIncludeFlagIsSet(
            eWhatToInclude, WhatToInclude.ExpandedLatestStatusUrls);

        String sUserID;

        if (bUseListName)
        {
            String sSlug, sOwnerScreenName;

            if ( !TryParseListName(sListName, out sSlug,
                out sOwnerScreenName) )
            {
                return;
            }

            String sUrl = String.Format(

                "{0}lists/members.json?slug={1}&owner_screen_name={2}&{3}"
                ,
                TwitterApiUrls.Rest,
                TwitterUtil.EncodeUrlParameter(sSlug),
                TwitterUtil.EncodeUrlParameter(sOwnerScreenName),
                TwitterApiUrlParameters.IncludeEntities
                );

            // The JSON contains a "users" array for the users in the Twitter
            // list.

            foreach ( Object oResult in EnumerateJsonValues(sUrl, "users",
                Int32.MaxValue, false, oRequestStatistics) )
            {
                String sScreenName;

                Dictionary<String, Object> oUserValueDictionary =
                    ( Dictionary<String, Object> )oResult;

                if (
                    TryGetScreenNameFromDictionary(oUserValueDictionary,
                        out sScreenName)
                    &&
                    TryGetUserIDFromDictionary(oUserValueDictionary,
                        out sUserID)
                    )
                {
                    AppendVertexXmlNode(sScreenName, sUserID,
                        oGraphMLXmlDocument, oUserIDDictionary,
                        oUserValueDictionary, bIncludeStatistics,
                        bIncludeLatestStatuses, bExpandLatestStatusUrls);
                }
            }
        }
        else
        {
            // Eliminate duplicate names.

            HashSet<String> oUniqueScreenNames = new HashSet<String>();

            foreach (String sScreenName in oScreenNames)
            {
                oUniqueScreenNames.Add( sScreenName.ToLower() );
            }

            foreach (String sScreenName in oUniqueScreenNames)
            {
                Dictionary<String, Object> oUserValueDictionary;

                if (
                    TryGetUserValueDictionary(sScreenName, oRequestStatistics,
                        true, out oUserValueDictionary)
                    &&
                    TryGetUserIDFromDictionary(oUserValueDictionary,
                        out sUserID)
                    )
                {
                    AppendVertexXmlNode(sScreenName, sUserID,
                        oGraphMLXmlDocument, oUserIDDictionary,
                        oUserValueDictionary, bIncludeStatistics,
                        bIncludeLatestStatuses, bExpandLatestStatusUrls);
                }
            }
        }
    }
    EnumerateJsonValues
    (
        String sUrl,
        String sJsonName,
        Int32 iMaximumValues,
        Boolean bSkipMostPage1Errors,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sUrl) );
        Debug.Assert(iMaximumValues > 0);
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        return ( m_oTwitterUtil.EnumerateJsonValues(sUrl, sJsonName,
            iMaximumValues, bSkipMostPage1Errors, oRequestStatistics,
            new ReportProgressHandler(this.ReportProgress),
            new CheckCancellationPendingHandler(this.CheckCancellationPending)
            ) );
    }
    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)
            );
    }
    EnumerateFriendOrFollowerIDs
    (
        String sScreenName,
        Boolean bFollowed,
        Int32 iMaximumPeoplePerRequest,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sScreenName) );
        Debug.Assert(iMaximumPeoplePerRequest > 0);
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        String sUrl = String.Format(

            "{0}{1}.json?screen_name={2}"
            ,
            TwitterApiUrls.Rest,
            bFollowed ? "friends/ids" : "followers/ids",
            TwitterUtil.EncodeUrlParameter(sScreenName)
            );

        // The JSON looped through here has an "ids" name whose value is an
        // array of integer IDs.

        foreach ( Object oUserIDAsObject in EnumerateJsonValues(
            sUrl, "ids", iMaximumPeoplePerRequest, true, oRequestStatistics) )
        {
            String sUserID;

            if ( TwitterJsonUtil.TryConvertJsonValueToString(
                oUserIDAsObject, out sUserID) )
            {
                yield return (sUserID);
            }
        }
    }
    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);
        }
    }
    EnumerateUserValueDictionaries
    (
        String [] asUserIDsOrScreenNames,
        Boolean bUserIDsSpecified,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert(asUserIDsOrScreenNames != null);
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        return ( m_oTwitterUtil.EnumerateUserValueDictionaries(
            asUserIDsOrScreenNames, bUserIDsSpecified, oRequestStatistics,
            new ReportProgressHandler(this.ReportProgress),
            new CheckCancellationPendingHandler(this.CheckCancellationPending)
            ) );
    }
    GetNetworkInternal
    (
        String sSearchTerm,
        DateTime oMinimumStatusDateUtc,
        DateTime oMaximumStatusDateUtc,
        Boolean bExpandStatusUrls,
        String sGraphServerUserName,
        String sGraphServerPassword,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
        Debug.Assert(oMaximumStatusDateUtc >= oMinimumStatusDateUtc);
        Debug.Assert( !String.IsNullOrEmpty(sGraphServerUserName) );
        Debug.Assert( !String.IsNullOrEmpty(sGraphServerPassword) );
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        GraphServiceClient oClient = new GraphServiceClient(
            GetWcfServiceBinding(), new EndpointAddress(GraphServiceUrl) );

        Byte [] abtZippedGraphML =
            oClient.GetTwitterSearchNetworkAsZippedGraphML(
                sSearchTerm, oMinimumStatusDateUtc, oMaximumStatusDateUtc,
                bExpandStatusUrls, sGraphServerUserName, sGraphServerPassword);

        String sGraphML = ZipUtil.UnzipOneTextFile(abtZippedGraphML);
        abtZippedGraphML = null;

        XmlDocument oXmlDocument = new XmlDocument();

        // Note: When the DotNetZip library used by ZipUtil unzips the GraphML,
        // it includes a BOM as the first character.  Remove that character.

        oXmlDocument.LoadXml( sGraphML.Substring(1) );

        return (oXmlDocument);
    }
    AppendStartTimeRelationshipDateUtcGraphMLAttributeValue
    (
        GraphMLXmlDocument oGraphMLXmlDocument,
        XmlNode oEdgeXmlNode,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert(oEdgeXmlNode != null);
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        oGraphMLXmlDocument.AppendGraphMLAttributeValue(oEdgeXmlNode,

            TwitterGraphMLUtil.EdgeRelationshipDateUtcID, 

            DateTimeUtil2.ToCultureInvariantString(
                oRequestStatistics.StartTimeUtc)
            );
    }
    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);
        }
    }
    AppendFollowedOrFollowingEdgeXmlNodes
    (
        Dictionary<String, TwitterUser> oUserIDDictionary,
        Boolean bFollowed,
        Int32 iMaximumPeoplePerRequest,
        GraphMLXmlDocument oGraphMLXmlDocument,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert(oUserIDDictionary != null);
        Debug.Assert(iMaximumPeoplePerRequest > 0);
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        AppendFollowedOrFollowingEdgeXmlNodes(

            TwitterGraphMLUtil.TwitterUsersToUniqueScreenNames(
                oUserIDDictionary.Values),

            oUserIDDictionary, bFollowed, iMaximumPeoplePerRequest,
            oGraphMLXmlDocument, oRequestStatistics);
    }
    AppendSharedResponderEdges
    (
        GraphMLXmlDocument oGraphMLXmlDocument,
        HashSet<String> oVideoIDs,
        Int32 iMaximumResponses,
        String sUrlPattern,
        String sResponderTitle,
        String sKeyAttributeID,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert(oVideoIDs != null);
        Debug.Assert(iMaximumResponses > 0);
        Debug.Assert( !String.IsNullOrEmpty(sUrlPattern) );
        Debug.Assert(sUrlPattern.IndexOf("{0}") >= 0);
        Debug.Assert( !String.IsNullOrEmpty(sResponderTitle) );
        Debug.Assert( !String.IsNullOrEmpty(sKeyAttributeID) );
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        // The key is the name of an author and the value is a LinkedList of
        // the video IDs to which the author has responded.

        Dictionary< String, LinkedList<String> > oAuthorUserNameDictionary =
            new Dictionary< String, LinkedList<String> >();

        foreach (String sVideoID in oVideoIDs)
        {
            ReportProgress(String.Format(
            
                "Getting {0}s for the video with the ID \"{1}\"."
                ,
                sResponderTitle,
                sVideoID
                ) );

            // This is to prevent self-loop edges that would result when the
            // same author responds to the same video more than once.

            HashSet<String> oAuthorUserNames = new HashSet<String>();

            String sUrl = String.Format(sUrlPattern, sVideoID);

            // The document consists of an "entry" XML node for each response.

            foreach ( XmlNode oEntryXmlNode in EnumerateXmlNodes(sUrl,
                "a:feed/a:entry", iMaximumResponses, true,
                oRequestStatistics) )
            {
                XmlNamespaceManager oXmlNamespaceManager =
                    CreateXmlNamespaceManager(oEntryXmlNode.OwnerDocument);

                String sAuthorUserName;

                if (
                    XmlUtil2.TrySelectSingleNodeAsString(oEntryXmlNode,
                        "a:author/a:name/text()", oXmlNamespaceManager,
                        out sAuthorUserName)
                    &&
                    !oAuthorUserNames.Contains(sAuthorUserName)
                    )
                {
                    AddVideoIDToDictionary(sAuthorUserName, sVideoID,
                        oAuthorUserNameDictionary);

                    oAuthorUserNames.Add(sAuthorUserName);
                }
            }
        }

        ReportProgress("Adding edges for shared " + sResponderTitle + "s.");

        AppendEdgesFromDictionary(oAuthorUserNameDictionary,
            oGraphMLXmlDocument, "Shared " + sResponderTitle, sKeyAttributeID);
    }
    GetNetworkInternal
    (
        Boolean bUseListName,
        String sListName,
        ICollection<String> oScreenNames,
        WhatToInclude eWhatToInclude
    )
    {
        Debug.Assert( !bUseListName || !String.IsNullOrEmpty(sListName) );
        Debug.Assert(bUseListName || oScreenNames != null);
        AssertValid();

        BeforeGetNetwork();

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

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

        GraphMLXmlDocument oGraphMLXmlDocument = CreateGraphMLXmlDocument(
            bIncludeStatistics, bIncludeLatestStatuses);

        RequestStatistics oRequestStatistics = new RequestStatistics();

        try
        {
            GetNetworkInternal(bUseListName, sListName, oScreenNames,
                eWhatToInclude, oRequestStatistics, oGraphMLXmlDocument);
        }
        catch (Exception oException)
        {
            OnUnexpectedException(oException, oGraphMLXmlDocument,
                oRequestStatistics);
        }

        OnNetworkObtained(oGraphMLXmlDocument, oRequestStatistics, 

            GetNetworkDescription(bUseListName, sListName, oScreenNames,
                eWhatToInclude),

            "Twitter List " + (bUseListName ? sListName : "Usernames")
            );

        return (oGraphMLXmlDocument);
    }
    EnumerateXmlNodes
    (
        String sUrl,
        String sXPath,
        Int32 iMaximumXmlNodes,
        Boolean bSkipMostPage1Errors,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sUrl) );
        Debug.Assert( !String.IsNullOrEmpty(sXPath) );
        Debug.Assert(iMaximumXmlNodes > 0);
        Debug.Assert(oRequestStatistics != null);

        AssertValid();

        Int32 iStartIndex = 1;
        Int32 iMaxResults = 50;
        Int32 iXmlNodesEnumerated = 0;

        while (true)
        {
            if (iStartIndex > 1)
            {
                ReportProgress("Getting page starting with item "
                    + iStartIndex + ".");
            }

            String sUrlWithPagination = String.Format(
            
                "{0}{1}start-index={2}&max-results={3}"
                ,
                sUrl,
                sUrl.IndexOf('?') == -1 ? '?' : '&',
                iStartIndex,
                iMaxResults
                );

            XmlDocument oXmlDocument;
            XmlNamespaceManager oXmlNamespaceManager;

            try
            {
                oXmlDocument = GetXmlDocument(sUrlWithPagination,
                    oRequestStatistics, out oXmlNamespaceManager);
            }
            catch (Exception oException)
            {
                if (!HttpSocialNetworkUtil.ExceptionIsWebOrXml(oException))
                {
                    throw oException;
                }

                if (iStartIndex > 1 || bSkipMostPage1Errors)
                {
                    // Always skip errors on page 2 and above.

                    yield break;
                }

                throw (oException);
            }

            XmlNodeList oXmlNodesThisPage = oXmlDocument.SelectNodes(sXPath,
                oXmlNamespaceManager);

            Int32 iXmlNodesThisPage = oXmlNodesThisPage.Count;

            if (iXmlNodesThisPage == 0)
            {
                yield break;
            }

            for (Int32 i = 0; i < iXmlNodesThisPage; i++)
            {
                yield return ( oXmlNodesThisPage[i] );

                iXmlNodesEnumerated++;

                if (iXmlNodesEnumerated == iMaximumXmlNodes)
                {
                    yield break;
                }
            }

            // The next page, if there is one, is obtained from a link tag that
            // looks something like this:
            //
            // <link rel="next" ... href="http://gdata.youtube.com/feeds/...?
            // start-index=26&max-results=25"/>

            String sHRef;

            if ( !XmlUtil2.TrySelectSingleNodeAsString(oXmlDocument,
                "a:feed/a:link[@rel='next']/@href", oXmlNamespaceManager,
                out sHRef) )
            {
                yield break;
            }

            const String Pattern = @"start-index=(?<NextStartIndex>\d+)";
            Regex oRegex = new Regex(Pattern);
            Match oMatch = oRegex.Match(sHRef);

            if (!oMatch.Success
                ||
                !MathUtil.TryParseCultureInvariantInt32(
                    oMatch.Groups["NextStartIndex"].Value, out iStartIndex)
                )
            {
                yield break;
            }

            // Get the next page...
        }
    }
    GetNetworkInternal
    (
        Boolean bUseListName,
        String sListName,
        ICollection<String> oScreenNames,
        WhatToInclude eWhatToInclude,
        RequestStatistics oRequestStatistics,
        GraphMLXmlDocument oGraphMLXmlDocument
    )
    {
        Debug.Assert( !bUseListName || !String.IsNullOrEmpty(sListName) );
        Debug.Assert(bUseListName || oScreenNames != null);
        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>();

        AppendVertexXmlNodes(bUseListName, sListName, oScreenNames,
            eWhatToInclude, oGraphMLXmlDocument, oUserIDDictionary,
            oRequestStatistics);

        if ( WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.FollowedEdges) )
        {
            AppendFollowedOrFollowingEdgeXmlNodes(oUserIDDictionary, true,
                Int32.MaxValue, oGraphMLXmlDocument, oRequestStatistics);
        }

        // Add edges nodes for replies-to and mentions relationships.

        AppendRepliesToAndMentionsEdgeXmlNodes(oGraphMLXmlDocument,
            oUserIDDictionary.Values,

            TwitterGraphMLUtil.TwitterUsersToUniqueScreenNames(
                oUserIDDictionary.Values),

            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.RepliesToEdges),

            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.MentionsEdges),

            false, false
            );
    }
    GetXmlDocument
    (
        String sUrl,
        RequestStatistics oRequestStatistics,
        out XmlNamespaceManager oXmlNamespaceManager
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sUrl) );
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        // Always request API version 2.

        String sUrlWithVersion = String.Format(
            
            "{0}{1}v=2"
            ,
            sUrl,
            sUrl.IndexOf('?') == -1 ? '?' : '&'
            );

        XmlDocument oXmlDocument = GetXmlDocumentWithRetries(sUrlWithVersion,
            HttpStatusCodesToFailImmediately, oRequestStatistics);

        oXmlNamespaceManager = CreateXmlNamespaceManager(oXmlDocument);

        return (oXmlDocument);
    }
    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);
        }

        OnNetworkObtained(oGraphMLXmlDocument, oRequestStatistics,
            GetNetworkDescription(sTag, eWhatToInclude, eNetworkLevel),
            "Flickr Tag " + sTag
            );

        return (oGraphMLXmlDocument);
    }