CreateGraphMLXmlDocument
        (
            Boolean bIncludeAllStatistics
        )
        {
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument = new GraphMLXmlDocument(true);

            NodeXLGraphMLUtil.DefineVertexCustomMenuGraphMLAttributes(
                oGraphMLXmlDocument);

            NodeXLGraphMLUtil.DefineEdgeRelationshipGraphMLAttribute(
                oGraphMLXmlDocument);

            if (bIncludeAllStatistics)
            {
                oGraphMLXmlDocument.DefineGraphMLAttributes(false, "int",
                                                            FriendsID, "Friends",
                                                            SubscriptionsID, "People Subscribed To",
                                                            SubscribersID, "Subscribers",
                                                            VideosWatchedID, "Videos Watched",
                                                            VideosUploadedID, "Videos Uploaded"
                                                            );

                oGraphMLXmlDocument.DefineVertexStringGraphMLAttributes(
                    JoinedDateUtcID, "Joined YouTube Date (UTC)");

                NodeXLGraphMLUtil.DefineVertexImageFileGraphMLAttribute(
                    oGraphMLXmlDocument);
            }

            return(oGraphMLXmlDocument);
        }
        AppendFriendOrFollowerEdgeXmlNode
        (
            String sScreenName1,
            String sScreenName2,
            Boolean bAppendFriendEdgeXmlNode,
            GraphMLXmlDocument oGraphMLXmlDocument,
            RequestStatistics oRequestStatistics
        )
        {
            Debug.Assert(!String.IsNullOrEmpty(sScreenName1));
            Debug.Assert(!String.IsNullOrEmpty(sScreenName2));
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oRequestStatistics != null);
            AssertValid();

            // Don't use "friend" or "follower" terminology here, which can be
            // confusing.  Instead, simply indicate who follows whom.

            XmlNode oEdgeXmlNode = NodeXLGraphMLUtil.AppendEdgeXmlNode(
                oGraphMLXmlDocument,
                bAppendFriendEdgeXmlNode ? sScreenName1 : sScreenName2,
                bAppendFriendEdgeXmlNode ? sScreenName2 : sScreenName1,
                "Follows"
                );

            AppendStartTimeRelationshipDateUtcGraphMLAttributeValue(
                oGraphMLXmlDocument, oEdgeXmlNode, oRequestStatistics);
        }
        AppendEdgesFromDictionary
        (
            Dictionary <String, LinkedList <String> > oDictionary,
            GraphMLXmlDocument oGraphMLXmlDocument,
            String sRelationship,
            String sKeyAttributeID
        )
        {
            Debug.Assert(oDictionary != null);
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(!String.IsNullOrEmpty(sRelationship));
            Debug.Assert(!String.IsNullOrEmpty(sKeyAttributeID));
            AssertValid();

            // For each key...

            foreach (KeyValuePair <String, LinkedList <String> > oKeyValuePair in
                     oDictionary)
            {
                String sKey = oKeyValuePair.Key;

                // For each video ID that has the key...

                LinkedList <String> oVideoIDsWithThisKey = oKeyValuePair.Value;

                for (
                    LinkedListNode <String> oVideoIDWithThisKey =
                        oVideoIDsWithThisKey.First;

                    oVideoIDWithThisKey != null;

                    oVideoIDWithThisKey = oVideoIDWithThisKey.Next
                    )
                {
                    // For each of the subsequent video IDs in the LinkedList that
                    // have the key...

                    for (
                        LinkedListNode <String> oSubsequentVideoIDWithThisKey =
                            oVideoIDWithThisKey.Next;

                        oSubsequentVideoIDWithThisKey != null;

                        oSubsequentVideoIDWithThisKey =
                            oSubsequentVideoIDWithThisKey.Next
                        )
                    {
                        XmlNode oEdgeXmlNode = NodeXLGraphMLUtil.AppendEdgeXmlNode(
                            oGraphMLXmlDocument, oVideoIDWithThisKey.Value,
                            oSubsequentVideoIDWithThisKey.Value, sRelationship);

                        oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                            oEdgeXmlNode, sKeyAttributeID, sKey);
                    }
                }
            }
        }
        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);
        }
        CreateGraphMLXmlDocument
        (
            Boolean bIncludeSampleThumbnails
        )
        {
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument = new GraphMLXmlDocument(true);

            NodeXLGraphMLUtil.DefineVertexLabelGraphMLAttribute(
                oGraphMLXmlDocument);

            NodeXLGraphMLUtil.DefineVertexCustomMenuGraphMLAttributes(
                oGraphMLXmlDocument);

            if (bIncludeSampleThumbnails)
            {
                NodeXLGraphMLUtil.DefineVertexImageFileGraphMLAttribute(
                    oGraphMLXmlDocument);
            }

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