//*************************************************************************
        //  Method: GetUserNetworkInternal()
        //
        /// <summary>
        /// Gets a network of Twitter users, given a GraphMLXmlDocument.
        /// </summary>
        ///
        /// <param name="sScreenNameToAnalyze">
        /// The screen name of the Twitter user whose network should be analyzed.
        /// </param>
        ///
        /// <param name="eWhatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="eNetworkLevel">
        /// Network level to include.  Must be NetworkLevel.One, OnePointFive, or
        /// Two.
        /// </param>
        ///
        /// <param name="iMaximumPeoplePerRequest">
        /// Maximum number of people to request for each query, or Int32.MaxValue
        /// for no limit.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// The GraphMLXmlDocument to populate with the requested network.
        /// </param>
        //*************************************************************************
        protected void GetUserNetworkInternal(
            String sScreenNameToAnalyze,
            WhatToInclude eWhatToInclude,
            NetworkLevel eNetworkLevel,
            Int32 iMaximumPeoplePerRequest,
            RequestStatistics oRequestStatistics,
            GraphMLXmlDocument oGraphMLXmlDocument
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sScreenNameToAnalyze) );

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

            Debug.Assert(iMaximumPeoplePerRequest > 0);
            Debug.Assert(oRequestStatistics != null);
            Debug.Assert(oGraphMLXmlDocument != null);
            AssertValid();

            // The key is the screen name and the value is the corresponding
            // TwitterVertex.  This is used to prevent the same screen name from
            // being added to the XmlDocument twice.

            Dictionary<String, TwitterVertex> oScreenNameDictionary =
            new Dictionary<String, TwitterVertex>();

            // Include followed, followers, both, or neither.

            Boolean [] abIncludes = new Boolean[] {

            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.FollowedVertices),

            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.FollowerVertices),
            };

            for (Int32 i = 0; i < 2; i++)
            {
            if ( abIncludes[i] )
            {
                GetUserNetworkRecursive(sScreenNameToAnalyze, eWhatToInclude,
                    (i == 0), eNetworkLevel, iMaximumPeoplePerRequest, 1,
                    oGraphMLXmlDocument, oScreenNameDictionary,
                    oRequestStatistics);
            }
            }

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

            AppendMissingGraphMLAttributeValues(oGraphMLXmlDocument,
            oScreenNameDictionary, true, bIncludeLatestStatus,
            oRequestStatistics);

            AppendRepliesToAndMentionsXmlNodes(oGraphMLXmlDocument,
            oScreenNameDictionary,

            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.RepliesToEdges),

            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.MentionsEdges)
            );
        }
        //*************************************************************************
        //  Method: AppendSharedResponderEdges()
        //
        /// <summary>
        /// Appends an edge XML node for each pair of videos for which the same
        /// person responded.
        /// </summary>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// The GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oVideoIDs">
        /// The set of unique video IDs.
        /// </param>
        ///
        /// <param name="iMaximumResponses">
        /// Maximum number of responses to request, or Int32.MaxValue for no limit.
        /// </param>
        ///
        /// <param name="sUrlPattern">
        /// URL to get the responses for one video, with "{0}" where the video ID
        /// goes.
        /// </param>
        ///
        /// <param name="sResponderTitle">
        /// Title describing the person who has responded.  Samples: "commenter",
        /// "video responder".
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        //*************************************************************************
        protected void AppendSharedResponderEdges(
            GraphMLXmlDocument oGraphMLXmlDocument,
            HashSet<String> oVideoIDs,
            Int32 iMaximumResponses,
            String sUrlPattern,
            String sResponderTitle,
            RequestStatistics oRequestStatistics
            )
        {
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oVideoIDs != null);
            Debug.Assert(iMaximumResponses > 0);
            Debug.Assert( !String.IsNullOrEmpty(sUrlPattern) );
            Debug.Assert(sUrlPattern.IndexOf("{0}") >= 0);
            Debug.Assert( !String.IsNullOrEmpty(sResponderTitle) );
            Debug.Assert(oRequestStatistics != null);
            AssertValid();

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

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

            foreach (String sVideoID in oVideoIDs)
            {
            ReportProgress(String.Format(

                "Getting {0}s for the video with the ID \"{1}\"."
                ,
                sResponderTitle,
                sVideoID
                ) );

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

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

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

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

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

                String sAuthorUserName;

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

                    oAuthorUserNames.Add(sAuthorUserName);
                }
            }
            }

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

            AppendEdgesFromDictionary(oAuthorUserNameDictionary,
            oGraphMLXmlDocument, "Shared " + sResponderTitle);
        }
        //*************************************************************************
        //  Method: CreateGraphMLXmlDocument()
        //
        /// <summary>
        /// Creates a GraphMLXmlDocument representing a network of YouTube videos.
        /// </summary>
        ///
        /// <returns>
        /// A GraphMLXmlDocument representing a network of YouTube videos.  The
        /// document includes GraphML-attribute definitions but no vertices or
        /// edges.
        /// </returns>
        //*************************************************************************
        protected GraphMLXmlDocument CreateGraphMLXmlDocument()
        {
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument = new GraphMLXmlDocument(false);

            DefineRelationshipGraphMLAttribute(oGraphMLXmlDocument);

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, TitleID,
            "Title", "string", null);

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

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, ViewsID,
            "Views", "int", null);

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, FavoritedID,
            "Favorited", "int", null);

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, CommentsID,
            "Comments", "int", null);

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, CreatedDateUtcID,
            "Created Date (UTC)", "string", null);

            DefineImageFileGraphMLAttribute(oGraphMLXmlDocument);
            DefineCustomMenuGraphMLAttributes(oGraphMLXmlDocument);

            return (oGraphMLXmlDocument);
        }
        //*************************************************************************
        //  Method: CreateGraphMLXmlDocument()
        //
        /// <summary>
        /// Creates a GraphMLXmlDocument representing a network of Flickr related
        /// tags.
        /// </summary>
        ///
        /// <param name="bIncludeSampleThumbnails">
        /// true to include a sample thumbnail for each tag.
        /// photos.
        /// </param>
        ///
        /// <returns>
        /// A GraphMLXmlDocument representing a network of Flickr related tags.
        /// The document includes GraphML-attribute definitions but no vertices or
        /// edges.
        /// </returns>
        //*************************************************************************
        protected GraphMLXmlDocument CreateGraphMLXmlDocument(
            Boolean bIncludeSampleThumbnails
            )
        {
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument = new GraphMLXmlDocument(true);

            DefineLabelGraphMLAttribute(oGraphMLXmlDocument);
            DefineCustomMenuGraphMLAttributes(oGraphMLXmlDocument);

            if (bIncludeSampleThumbnails)
            {
            DefineImageFileGraphMLAttribute(oGraphMLXmlDocument);
            }

            return (oGraphMLXmlDocument);
        }
        //*************************************************************************
        //  Method: GetRelatedTagsRecursive()
        //
        /// <summary>
        /// Recursively gets a tag's related tags.
        /// </summary>
        ///
        /// <param name="sTag">
        /// Tag to get related tags for.
        /// </param>
        ///
        /// <param name="eWhatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="eNetworkLevel">
        /// Network level to include.  Must be NetworkLevel.One, OnePointFive, or
        /// Two.
        /// </param>
        ///
        /// <param name="sApiKey">
        /// Flickr API key.
        /// </param>
        ///
        /// <param name="iRecursionLevel">
        /// Recursion level for this call.  Must be 1 or 2.  Gets incremented when
        /// recursing.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oTagDictionary">
        /// The key is the tag name and the value is the corresponding GraphML XML
        /// node that represents the tag.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        //*************************************************************************
        protected void GetRelatedTagsRecursive(
            String sTag,
            WhatToInclude eWhatToInclude,
            NetworkLevel eNetworkLevel,
            String sApiKey,
            Int32 iRecursionLevel,
            GraphMLXmlDocument oGraphMLXmlDocument,
            Dictionary<String, XmlNode> oTagDictionary,
            RequestStatistics oRequestStatistics
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sTag) );

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

            Debug.Assert( !String.IsNullOrEmpty(sApiKey) );
            Debug.Assert(iRecursionLevel == 1 || iRecursionLevel == 2);
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oTagDictionary != null);
            Debug.Assert(oRequestStatistics != null);
            AssertValid();

            /*
            Here is what this method should do, based on the eNetworkLevel and
            iRecursionLevel parameters.

                eNetworkLevel

               |One               | OnePointFive      | Two
            ---|------------------| ------------------| -----------------
            i   1  |Add all vertices. | Add all vertices. | Add all vertices.
            R      |                  |                   |
            e      |Add all edges.    | Add all edges.    | Add all edges.
            c      |                  |                   |
            u      |Do not recurse.   | Recurse.          | Recurse.
            r      |                  |                   |
            s   ---|------------------|-------------------|------------------
            i   2  |Impossible.       | Do not add        | Add all vertices.
            o      |                  | vertices.         |
            n      |                  |                   |
            L      |                  | Add edges only if | Add all edges.
            e      |                  | vertices are      |
            v      |                  | already included. |
            e      |                  |                   |
            l      |                  | Do not recurse.   | Do not recurse.
               |                  |                   |
            ---|------------------|-------------------|------------------
            */

            Boolean bNeedToRecurse = GetNeedToRecurse(eNetworkLevel,
            iRecursionLevel);

            Boolean bNeedToAppendVertices = GetNeedToAppendVertices(
            eNetworkLevel, iRecursionLevel);

            ReportProgress("Getting tags related to \"" + sTag + "\".");

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

            XmlDocument oXmlDocument;

            try
            {
            oXmlDocument = GetXmlDocument(sUrl, oRequestStatistics);
            }
            catch (Exception oException)
            {
            // If the exception is not a WebException or XmlException, or if
            // none of the network has been obtained yet, throw the exception.

            if (!ExceptionIsWebOrXml(oException) ||
                !oGraphMLXmlDocument.HasVertexXmlNode)
            {
                throw oException;
            }

            return;
            }

            // The document consists of a single "tags" node with zero or more
            // "tag" child nodes.

            String sOtherTag = null;

            XmlNodeList oTagNodes = oXmlDocument.DocumentElement.SelectNodes(
            "tags/tag");

            if (oTagNodes.Count > 0)
            {
            AppendVertexXmlNode(sTag, oGraphMLXmlDocument, oTagDictionary);
            }

            foreach (XmlNode oTagNode in oTagNodes)
            {
            sOtherTag = XmlUtil2.SelectRequiredSingleNodeAsString(oTagNode,
                "text()", null);

            if (bNeedToAppendVertices)
            {
                AppendVertexXmlNode(sOtherTag, oGraphMLXmlDocument,
                    oTagDictionary);
            }

            if ( bNeedToAppendVertices ||
                oTagDictionary.ContainsKey(sOtherTag) )
            {
                oGraphMLXmlDocument.AppendEdgeXmlNode(sTag, sOtherTag);
            }
            }

            if (bNeedToRecurse)
            {
            foreach (XmlNode oTagNode in oTagNodes)
            {
                sOtherTag = XmlUtil2.SelectRequiredSingleNodeAsString(oTagNode,
                    "text()", null);

                GetRelatedTagsRecursive(sOtherTag, eWhatToInclude,
                    eNetworkLevel, sApiKey, 2, oGraphMLXmlDocument,
                    oTagDictionary, oRequestStatistics);
            }
            }
        }
        //*************************************************************************
        //  Method: AppendVertexXmlNodes()
        //
        /// <summary>
        /// Appends a vertex XML node for each person who has tweeted a specified
        /// search term.
        /// </summary>
        ///
        /// <param name="sSearchTerm">
        /// The term to search for.
        /// </param>
        ///
        /// <param name="eWhatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="iMaximumPeoplePerRequest">
        /// Maximum number of people to request for each query, or Int32.MaxValue
        /// for no limit.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// The GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oScreenNameDictionary">
        /// The key is the screen name in lower case and the value is the
        /// corresponding TwitterVertex.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        //*************************************************************************
        protected void AppendVertexXmlNodes(
            String sSearchTerm,
            WhatToInclude eWhatToInclude,
            Int32 iMaximumPeoplePerRequest,
            GraphMLXmlDocument oGraphMLXmlDocument,
            Dictionary<String, TwitterVertex> oScreenNameDictionary,
            RequestStatistics oRequestStatistics
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
            Debug.Assert(iMaximumPeoplePerRequest > 0);
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oScreenNameDictionary != null);
            Debug.Assert(oRequestStatistics != null);
            AssertValid();

            // Convert spaces in the search term to a plus sign.
            //
            // (Note: Don't try to use Twitter's "show_user" URL parameter in an
            // attempt to get a "user" XML node for each author.  That's not what
            // the URL parameter does.)

            String sUrl = String.Format(

            "http://search.twitter.com/search.atom?q={0}&rpp=100"
            ,
            UrlUtil.EncodeUrlParameter(sSearchTerm).Replace("%20", "+")
            );

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

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

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

            // The document consists of an "entry" XML node for each tweet that
            // contains the search term.  Multiple tweets may have the same author,
            // so we have to enumerate until the requested maximum number of unique
            // authors is reached.

            foreach ( XmlNode oAuthorNameXmlNode in EnumerateXmlNodes(
            sUrl, "a:feed/a:entry/a:author/a:name", "a", AtomNamespaceUri,
            15, Int32.MaxValue, true, false, oRequestStatistics) )
            {
            if (oScreenNameDictionary.Count == iMaximumPeoplePerRequest)
            {
                break;
            }

            // The author name is in this format:
            //
            // james_laker (James Laker)

            String sAuthorName;

            if ( !XmlUtil2.TrySelectSingleNodeAsString(oAuthorNameXmlNode,
                "text()", null, out sAuthorName) )
            {
                continue;
            }

            Int32 iIndexOfSpace = sAuthorName.IndexOf(' ');
            String sScreenName = sAuthorName;

            if (iIndexOfSpace != -1)
            {
                sScreenName = sAuthorName.Substring(0, iIndexOfSpace);
            }

            sScreenName = sScreenName.ToLower();

            TwitterVertex oTwitterVertex;

            if ( !TryAppendVertexXmlNode(sScreenName, null,
                oGraphMLXmlDocument, oScreenNameDictionary, bIncludeStatistics,
                false, out oTwitterVertex) )
            {
                // A tweet for the author has already been found.

                continue;
            }

            XmlNode oVertexXmlNode = oTwitterVertex.VertexXmlNode;
            XmlNode oEntryXmlNode = oAuthorNameXmlNode.ParentNode.ParentNode;

            XmlNamespaceManager oXmlNamespaceManager = new XmlNamespaceManager(
                oEntryXmlNode.OwnerDocument.NameTable);

            oXmlNamespaceManager.AddNamespace("a", AtomNamespaceUri);

            // Get the image URL and status for the tweet's author.

            AppendStringGraphMLAttributeValue(oEntryXmlNode,
                "a:link[@rel='image']/@href", oXmlNamespaceManager,
                oGraphMLXmlDocument, oVertexXmlNode, ImageFileID);

            String sStatus;

            if ( XmlUtil2.TrySelectSingleNodeAsString(oEntryXmlNode,
                "a:title/text()", oXmlNamespaceManager, out sStatus) )
            {
                String sStatusDateUtc;

                if ( XmlUtil2.TrySelectSingleNodeAsString(oEntryXmlNode,
                    "a:published/text()", oXmlNamespaceManager,
                    out sStatusDateUtc) )
                {
                    sStatusDateUtc = TwitterDateParser.ParseTwitterDate(
                        sStatusDateUtc);
                }

                oTwitterVertex.StatusForAnalysis = sStatus;
                oTwitterVertex.StatusForAnalysisDateUtc = sStatusDateUtc;

                if (bIncludeStatuses)
                {
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                        oVertexXmlNode, StatusID, sStatus);

                    if ( !String.IsNullOrEmpty(sStatusDateUtc) )
                    {
                        oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                            oVertexXmlNode, StatusDateUtcID, sStatusDateUtc);
                    }
                }
            }
            }
        }
        //*************************************************************************
        //  Method: AppendSampleThumbnails()
        //
        /// <summary>
        /// Appends sample thumbnails to the GraphMLXmlDocument being populated.
        /// </summary>
        ///
        /// <param name="oTagDictionary">
        /// The key is the tag name and the value is the corresponding GraphML XML
        /// node that represents the tag.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="sApiKey">
        /// Flickr API key.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        //*************************************************************************
        protected void AppendSampleThumbnails(
            Dictionary<String, XmlNode> oTagDictionary,
            GraphMLXmlDocument oGraphMLXmlDocument,
            String sApiKey,
            RequestStatistics oRequestStatistics
            )
        {
            Debug.Assert(oTagDictionary != null);
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert( !String.IsNullOrEmpty(sApiKey) );
            Debug.Assert(oRequestStatistics != null);
            AssertValid();

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

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

            String sSampleImageUrl;

            if ( TryGetSampleImageUrl(sTag, sApiKey, oRequestStatistics,
                out sSampleImageUrl) )
            {
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                    oKeyValuePair.Value, ImageFileID, sSampleImageUrl);
            }
            }
        }
        //*************************************************************************
        //  Method: DefineImageFileGraphMLAttribute()
        //
        /// <summary>
        /// Defines a GraphML-Attribute for vertex image files.
        /// </summary>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        //*************************************************************************
        protected void DefineImageFileGraphMLAttribute(
            GraphMLXmlDocument oGraphMLXmlDocument
            )
        {
            Debug.Assert(oGraphMLXmlDocument != null);
            AssertValid();

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, ImageFileID,
            ImageColumnName, "string", null);
        }
        //*************************************************************************
        //  Method: DefineLabelGraphMLAttribute()
        //
        /// <summary>
        /// Defines a GraphML-Attribute for vertex labels.
        /// </summary>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        //*************************************************************************
        protected void DefineLabelGraphMLAttribute(
            GraphMLXmlDocument oGraphMLXmlDocument
            )
        {
            Debug.Assert(oGraphMLXmlDocument != null);
            AssertValid();

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, LabelID,
            LabelColumnName, "string", null);
        }
예제 #10
0
        //*************************************************************************
        //  Method: AppendStringGraphMLAttributeValue()
        //
        /// <summary>
        /// Appends a String GraphML-Attribute value to an edge or vertex XML node. 
        /// </summary>
        ///
        /// <param name="oXmlNodeToSelectFrom">
        /// Node to select from.
        /// </param>
        /// 
        /// <param name="sXPath">
        /// XPath expression to a String descendant of <paramref
        /// name="oXmlNodeToSelectFrom" />.
        /// </param>
        ///
        /// <param name="oXmlNamespaceManager">
        /// NamespaceManager to use, or null to not use one.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oEdgeOrVertexXmlNode">
        /// The edge or vertex XML node from <paramref
        /// name="oGraphMLXmlDocument" /> to add the GraphML attribute value to.
        /// </param>
        ///
        /// <param name="sGraphMLAttributeID">
        /// GraphML ID of the attribute.
        /// </param>
        ///
        /// <returns>
        /// true if the GraphML-Attribute was appended.
        /// </returns>
        ///
        /// <remarks>
        /// This method selects from <paramref name="oXmlNodeToSelectFrom" /> using
        /// the <paramref name="sXPath" /> expression.  If the selection is
        /// successful, the specified String value gets stored on <paramref
        /// name="oEdgeOrVertexXmlNode" /> as a Graph-ML Attribute.
        /// </remarks>
        //*************************************************************************
        protected Boolean AppendStringGraphMLAttributeValue(
            XmlNode oXmlNodeToSelectFrom,
            String sXPath,
            XmlNamespaceManager oXmlNamespaceManager,
            GraphMLXmlDocument oGraphMLXmlDocument,
            XmlNode oEdgeOrVertexXmlNode,
            String sGraphMLAttributeID
            )
        {
            Debug.Assert(oXmlNodeToSelectFrom != null);
            Debug.Assert( !String.IsNullOrEmpty(sXPath) );
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oEdgeOrVertexXmlNode != null);
            Debug.Assert( !String.IsNullOrEmpty(sGraphMLAttributeID) );
            AssertValid();

            String sAttributeValue;

            if ( XmlUtil2.TrySelectSingleNodeAsString(oXmlNodeToSelectFrom, sXPath,
            oXmlNamespaceManager, out sAttributeValue) )
            {
            oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                oEdgeOrVertexXmlNode, sGraphMLAttributeID, sAttributeValue);

            return (true);
            }

            return (false);
        }
예제 #11
0
        //*************************************************************************
        //  Method: DefineCustomMenuGraphMLAttributes()
        //
        /// <summary>
        /// Defines the GraphML-Attributes for custom menu items.
        /// </summary>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        //*************************************************************************
        protected void DefineCustomMenuGraphMLAttributes(
            GraphMLXmlDocument oGraphMLXmlDocument
            )
        {
            Debug.Assert(oGraphMLXmlDocument != null);
            AssertValid();

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, MenuTextID,
            MenuTextColumnName, "string", null);

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, MenuActionID,
            MenuActionColumnName, "string", null);
        }
예제 #12
0
        //*************************************************************************
        //  Method: AppendEdgeXmlNode()
        //
        /// <summary>
        /// Appends an edge XML node to a GraphML document.
        /// </summary>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="sVertex1ID">
        /// ID of the edge's first vertex.
        /// </param>
        ///
        /// <param name="sVertex2ID">
        /// ID of the edge's second vertex.
        /// </param>
        ///
        /// <param name="sRelationship">
        /// The value of the edge's RelationshipID GraphML-attribute.
        /// </param>
        ///
        /// <returns>
        /// The new edge XML node.
        /// </returns>
        //*************************************************************************
        protected XmlNode AppendEdgeXmlNode(
            GraphMLXmlDocument oGraphMLXmlDocument,
            String sVertex1ID,
            String sVertex2ID,
            String sRelationship
            )
        {
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert( !String.IsNullOrEmpty(sVertex1ID) );
            Debug.Assert( !String.IsNullOrEmpty(sVertex2ID) );
            Debug.Assert( !String.IsNullOrEmpty(sRelationship) );
            AssertValid();

            XmlNode oEdgeXmlNode = oGraphMLXmlDocument.AppendEdgeXmlNode(
            sVertex1ID, sVertex2ID);

            oGraphMLXmlDocument.AppendGraphMLAttributeValue(oEdgeXmlNode,
            RelationshipID, sRelationship);

            return (oEdgeXmlNode);
        }
예제 #13
0
        //*************************************************************************
        //  Method: OnNetworkObtainedWithoutTerminatingException()
        //
        /// <summary>
        /// Call this when part or all of the network has been obtained without a
        /// terminating exception occurring.
        /// </summary>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        ///
        /// <remarks>
        /// If the entire network has been obtained, this method does nothing.
        /// Otherwise, a PartialNetworkException is thrown.
        /// </remarks>
        //*************************************************************************
        protected void OnNetworkObtainedWithoutTerminatingException(
            GraphMLXmlDocument oGraphMLXmlDocument,
            RequestStatistics oRequestStatistics
            )
        {
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oRequestStatistics != null);
            AssertValid();

            if (oRequestStatistics.UnexpectedExceptions > 0)
            {
            // The network is partial.

            throw new PartialNetworkException(oGraphMLXmlDocument,
                oRequestStatistics);
            }
        }
        //*************************************************************************
        //  Method: GetUserNetworkRecursive()
        //
        /// <summary>
        /// Recursively gets a network of Twitter users.
        /// </summary>
        ///
        /// <param name="sScreenName">
        /// The screen name to use in this call.
        /// </param>
        ///
        /// <param name="eWhatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="bIncludeFollowedThisCall">
        /// true to include the people followed by the user, false to include the
        /// people following the user.
        /// </param>
        ///
        /// <param name="eNetworkLevel">
        /// Network level to include.  Must be NetworkLevel.One, OnePointFive, or
        /// Two.
        /// </param>
        ///
        /// <param name="iMaximumPeoplePerRequest">
        /// Maximum number of people to request for each query, or Int32.MaxValue
        /// for no limit.
        /// </param>
        ///
        /// <param name="iRecursionLevel">
        /// Recursion level for this call.  Must be 1 or 2.  Gets incremented when
        /// recursing.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oScreenNameDictionary">
        /// The key is the screen name in lower case and the value is the
        /// corresponding TwitterVertex.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        //*************************************************************************
        protected void GetUserNetworkRecursive(
            String sScreenName,
            WhatToInclude eWhatToInclude,
            Boolean bIncludeFollowedThisCall,
            NetworkLevel eNetworkLevel,
            Int32 iMaximumPeoplePerRequest,
            Int32 iRecursionLevel,
            GraphMLXmlDocument oGraphMLXmlDocument,
            Dictionary<String, TwitterVertex> oScreenNameDictionary,
            RequestStatistics oRequestStatistics
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sScreenName) );

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

            Debug.Assert(iMaximumPeoplePerRequest > 0);
            Debug.Assert(iRecursionLevel == 1 || iRecursionLevel == 2);
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oScreenNameDictionary != null);
            Debug.Assert(oRequestStatistics != null);
            AssertValid();

            /*
            Here is what this method should do, based on the eNetworkLevel and
            iRecursionLevel parameters.  It's assumed that
            eWhatToInclude.FollowedFollowerEdge is set.

                eNetworkLevel

               |One               | OnePointFive      | Two
            ---|------------------| ------------------| -----------------
            i   1  |Add all vertices. | Add all vertices. | Add all vertices.
            R      |                  |                   |
            e      |Add all edges.    | Add all edges.    | Add all edges.
            c      |                  |                   |
            u      |Do not recurse.   | Recurse.          | Recurse.
            r      |                  |                   |
            s   ---|------------------|-------------------|------------------
            i   2  |Impossible.       | Do not add        | Add all vertices.
            o      |                  | vertices.         |
            n      |                  |                   |
            L      |                  | Add edges only if | Add all edges.
            e      |                  | vertices are      |
            v      |                  | already included. |
            e      |                  |                   |
            l      |                  | Do not recurse.   | Do not recurse.
               |                  |                   |
            ---|------------------|-------------------|------------------
            */

            Boolean bNeedToRecurse = GetNeedToRecurse(eNetworkLevel,
            iRecursionLevel);

            List<String> oScreenNamesToRecurse = new List<String>();

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

            ReportProgressForFollowedOrFollowing(sScreenName,
            bIncludeFollowedThisCall);

            Boolean bThisUserAppended = false;

            // If the GraphMLXmlDocument already contains at least one vertex node,
            // then this is the second time that this method has been called, a
            // partial network has already been obtained, and most errors should
            // now be skipped.  However, if none of the network has been obtained
            // yet, errors on page 1 should throw an immediate exception.

            Boolean bSkipMostPage1Errors = oGraphMLXmlDocument.HasVertexXmlNode;

            // The document consists of a single "users" node with zero or more
            // "user" child nodes.

            foreach ( XmlNode oUserXmlNode in EnumerateXmlNodes(
            GetFollowedOrFollowingUrl(sScreenName, bIncludeFollowedThisCall),
            "users_list/users/user", null, null, Int32.MaxValue,
            iMaximumPeoplePerRequest, false, bSkipMostPage1Errors,
            oRequestStatistics) )
            {
            String sOtherScreenName;

            if ( !TryGetScreenName(oUserXmlNode, out sOtherScreenName) )
            {
                // Nothing can be done without a screen name.

                continue;
            }

            if (!bThisUserAppended)
            {
                // Append a vertex node for this request's user.
                //
                // This used to be done after the foreach loop, which avoided
                // the need for a "bThisUserAppended" flag.  That caused the
                // following bug: If a Twitter error occurred within
                // EnumerateXmlNodes() after some edges had been added, and the
                // user decided to import the resulting partial network, the
                // GraphML might contain edges that referenced "this user"
                // without containing a vertex for "this user."  That is an
                // illegal state for GraphML, which the ExcelTemplate project
                // caught and reported as an error.

                TryAppendVertexXmlNode(sScreenName, null, oGraphMLXmlDocument,
                    oScreenNameDictionary, true, bIncludeLatestStatuses);

                bThisUserAppended = true;
            }

            Boolean bNeedToAppendVertices = GetNeedToAppendVertices(
                eNetworkLevel, iRecursionLevel);

            if (bNeedToAppendVertices)
            {
                if (
                    TryAppendVertexXmlNode(sOtherScreenName, oUserXmlNode,
                        oGraphMLXmlDocument, oScreenNameDictionary, true,
                        bIncludeLatestStatuses)
                    &&
                    bNeedToRecurse
                    )
                {
                    oScreenNamesToRecurse.Add(sOtherScreenName);
                }
            }

            if ( WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.FollowedFollowerEdges) )
            {
                if ( bNeedToAppendVertices ||
                    oScreenNameDictionary.ContainsKey(sOtherScreenName) )
                {
                    XmlNode oEdgeXmlNode;

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

                    AppendRelationshipDateUtcGraphMLAttributeValue(
                        oGraphMLXmlDocument, oEdgeXmlNode, oRequestStatistics);
                }
            }
            }

            if (bNeedToRecurse)
            {
            foreach (String sScreenNameToRecurse in oScreenNamesToRecurse)
            {
                GetUserNetworkRecursive(sScreenNameToRecurse, eWhatToInclude,
                    bIncludeFollowedThisCall, eNetworkLevel,
                    iMaximumPeoplePerRequest, 2, oGraphMLXmlDocument,
                    oScreenNameDictionary, oRequestStatistics);
            }
            }
        }
        //*************************************************************************
        //  Method: GetUserNetworkRecursive()
        //
        /// <summary>
        /// Recursively gets a network of YouTube users.
        /// </summary>
        ///
        /// <param name="sUserName">
        /// The user name to use in this call.
        /// </param>
        ///
        /// <param name="eWhatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="bIncludeFriendsThisCall">
        /// true to include the user's friends, false to include the people
        /// subscribed to by the user.
        /// </param>
        ///
        /// <param name="eNetworkLevel">
        /// Network level to include.  Must be NetworkLevel.One, OnePointFive, or
        /// Two.
        /// </param>
        ///
        /// <param name="iMaximumPeoplePerRequest">
        /// Maximum number of people to request for each query, or Int32.MaxValue
        /// for no limit.
        /// </param>
        ///
        /// <param name="iRecursionLevel">
        /// Recursion level for this call.  Must be 1 or 2.  Gets incremented when
        /// recursing.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oUserNameDictionary">
        /// The key is the user name and the value is the corresponding GraphML XML
        /// node that represents the user.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        //*************************************************************************
        protected void GetUserNetworkRecursive(
            String sUserName,
            WhatToInclude eWhatToInclude,
            Boolean bIncludeFriendsThisCall,
            NetworkLevel eNetworkLevel,
            Int32 iMaximumPeoplePerRequest,
            Int32 iRecursionLevel,
            GraphMLXmlDocument oGraphMLXmlDocument,
            Dictionary<String, XmlNode> oUserNameDictionary,
            RequestStatistics oRequestStatistics
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sUserName) );

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

            Debug.Assert(iMaximumPeoplePerRequest > 0);
            Debug.Assert(iRecursionLevel == 1 || iRecursionLevel == 2);
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oUserNameDictionary != null);
            Debug.Assert(oRequestStatistics != null);
            AssertValid();

            /*
            Here is what this method should do, based on the eNetworkLevel and
            iRecursionLevel parameters.

                eNetworkLevel

               |One               | OnePointFive      | Two
            ---|------------------| ------------------| -----------------
            i   1  |Add all vertices. | Add all vertices. | Add all vertices.
            R      |                  |                   |
            e      |Add all edges.    | Add all edges.    | Add all edges.
            c      |                  |                   |
            u      |Do not recurse.   | Recurse.          | Recurse.
            r      |                  |                   |
            s   ---|------------------|-------------------|------------------
            i   2  |Impossible.       | Do not add        | Add all vertices.
            o      |                  | vertices.         |
            n      |                  |                   |
            L      |                  | Add edges only if | Add all edges.
            e      |                  | vertices are      |
            v      |                  | already included. |
            e      |                  |                   |
            l      |                  | Do not recurse.   | Do not recurse.
               |                  |                   |
            ---|------------------|-------------------|------------------
            */

            Boolean bNeedToRecurse = GetNeedToRecurse(eNetworkLevel,
            iRecursionLevel);

            List<String> oUserNamesToRecurse = new List<String>();

            ReportProgressForFriendsOrSubscriptions(sUserName,
            bIncludeFriendsThisCall);

            Boolean bThisUserAppended = false;

            // If the GraphMLXmlDocument already contains at least one vertex node,
            // then this is the second time that this method has been called, a
            // partial network has already been obtained, and most errors should
            // now be skipped.  However, if none of the network has been obtained
            // yet, errors on page 1 should throw an immediate exception.

            Boolean bSkipMostPage1Errors = oGraphMLXmlDocument.HasVertexXmlNode;

            // The document consists of a single "feed" node with zero or more
            // "entry" child nodes.

            foreach ( XmlNode oEntryXmlNode in EnumerateXmlNodes(
            GetFriendsOrSubscriptionsUrl(sUserName, bIncludeFriendsThisCall),
            "a:feed/a:entry", iMaximumPeoplePerRequest, bSkipMostPage1Errors,
            oRequestStatistics) )
            {
            XmlNamespaceManager oXmlNamespaceManager =
                CreateXmlNamespaceManager(oEntryXmlNode.OwnerDocument);

            String sOtherUserName;

            if ( !XmlUtil2.TrySelectSingleNodeAsString(oEntryXmlNode,
                "yt:username/text()", oXmlNamespaceManager,
                out sOtherUserName) )
            {
                continue;
            }

            if (!bThisUserAppended)
            {
                // Append a vertex node for this request's user.
                //
                // This used to be done after the foreach loop, which avoided
                // the need for a "bThisUserAppended" flag.  That caused the
                // following bug: If a YouTube error occurred within
                // EnumerateXmlNodes() after some edges had been added, and the
                // user decided to import the resulting partial network, the
                // GraphML might contain edges that referenced "this user"
                // without containing a vertex for "this user."  That is an
                // illegal state for GraphML, which the ExcelTemplate project
                // caught and reported as an error.

                TryAppendVertexXmlNode(sUserName, null, oGraphMLXmlDocument,
                    oUserNameDictionary);

                bThisUserAppended = true;
            }

            Boolean bNeedToAppendVertices = GetNeedToAppendVertices(
                eNetworkLevel, iRecursionLevel);

            if (bNeedToAppendVertices)
            {
                if (
                    TryAppendVertexXmlNode(sOtherUserName, oEntryXmlNode,
                        oGraphMLXmlDocument, oUserNameDictionary)
                    &&
                    bNeedToRecurse
                    )
                {
                    oUserNamesToRecurse.Add(sOtherUserName);
                }
            }

            if ( bNeedToAppendVertices ||
                oUserNameDictionary.ContainsKey(sOtherUserName) )
            {
                String sRelationship;

                if (bIncludeFriendsThisCall)
                {
                    sRelationship = "Friend of";
                }
                else
                {
                    sRelationship = "Subscribes to";
                    String sSubscriptionType = null;

                    if ( XmlUtil2.TrySelectSingleNodeAsString(oEntryXmlNode,

                        "a:category[@scheme='http://gdata.youtube.com/schemas/"
                        + "2007/subscriptiontypes.cat']/@term",

                        oXmlNamespaceManager, out sSubscriptionType) )
                    {
                        sRelationship += " " + sSubscriptionType;
                    }
                }

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

            if (bNeedToRecurse)
            {
            foreach (String sUserNameToRecurse in oUserNamesToRecurse)
            {
                GetUserNetworkRecursive(sUserNameToRecurse,
                    eWhatToInclude, bIncludeFriendsThisCall, eNetworkLevel,
                    iMaximumPeoplePerRequest, 2, oGraphMLXmlDocument,
                    oUserNameDictionary, oRequestStatistics);
            }
            }
        }
예제 #16
0
        //*************************************************************************
        //  Method: DefineRelationshipGraphMLAttribute()
        //
        /// <summary>
        /// Defines a GraphML-Attribute for edge relationships.
        /// </summary>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        //*************************************************************************
        protected void DefineRelationshipGraphMLAttribute(
            GraphMLXmlDocument oGraphMLXmlDocument
            )
        {
            Debug.Assert(oGraphMLXmlDocument != null);
            AssertValid();

            oGraphMLXmlDocument.DefineGraphMLAttribute(true, RelationshipID,
            "Relationship", "string", null);
        }
        //*************************************************************************
        //  Method: AppendFollowedEdgeXmlNodes()
        //
        /// <summary>
        /// Appends an edge XML node for each pair of people who have tweeted a
        /// specified search term and one follows the other.
        /// </summary>
        ///
        /// <param name="eWhatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="iMaximumPeoplePerRequest">
        /// Maximum number of people to request for each query, or Int32.MaxValue
        /// for no limit.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oScreenNameDictionary">
        /// The key is the screen name in lower case and the value is the
        /// corresponding TwitterVertex.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        //*************************************************************************
        protected void AppendFollowedEdgeXmlNodes(
            WhatToInclude eWhatToInclude,
            Int32 iMaximumPeoplePerRequest,
            GraphMLXmlDocument oGraphMLXmlDocument,
            Dictionary<String, TwitterVertex> oScreenNameDictionary,
            RequestStatistics oRequestStatistics
            )
        {
            Debug.Assert(iMaximumPeoplePerRequest > 0);
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oScreenNameDictionary != null);
            Debug.Assert(oRequestStatistics != null);
            AssertValid();

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

            // Look at the people followed by each author, and if a followed has
            // also tweeted the search term, add an edge between the author and the
            // followed.

            foreach (String sScreenName in oScreenNameDictionary.Keys)
            {
            ReportProgressForFollowedOrFollowing(sScreenName, true);

            foreach ( XmlNode oOtherUserXmlNode in EnumerateXmlNodes(
                GetFollowedOrFollowingUrl(sScreenName, true),
                "users_list/users/user", null, null, Int32.MaxValue,
                iMaximumPeoplePerRequest, false, true, oRequestStatistics) )
            {
                String sOtherScreenName;

                if ( !TryGetScreenName(oOtherUserXmlNode,
                    out sOtherScreenName) )
                {
                    continue;
                }

                TwitterVertex oOtherTwitterVertex;

                if ( oScreenNameDictionary.TryGetValue(sOtherScreenName,
                    out oOtherTwitterVertex) )
                {
                    XmlNode oOtherVertexXmlNode =
                        oOtherTwitterVertex.VertexXmlNode;

                    XmlNode oEdgeXmlNode = AppendEdgeXmlNode(
                        oGraphMLXmlDocument, sScreenName, sOtherScreenName,
                        "Followed");

                    AppendRelationshipDateUtcGraphMLAttributeValue(
                        oGraphMLXmlDocument, oEdgeXmlNode, oRequestStatistics);

                    if (bIncludeStatistics &&
                        !AppendFromUserXmlNodeCalled(oGraphMLXmlDocument,
                            oOtherVertexXmlNode) )
                    {
                        // The other vertex node has no statistics.  Add them.

                        AppendFromUserXmlNode(oOtherUserXmlNode,
                            oGraphMLXmlDocument, oOtherTwitterVertex,
                            bIncludeStatistics, false);
                    }
                }
            }
            }
        }
        //*************************************************************************
        //  Method: TryAppendVertexXmlNode()
        //
        /// <summary>
        /// Appends a vertex XML node to the GraphML document for a user if such a
        /// node doesn't already exist.
        /// </summary>
        ///
        /// <param name="sUserName">
        /// User name to add a vertex XML node for.
        /// </param>
        ///
        /// <param name="oEntryXmlNode">
        /// The "entry" XML node returned by YouTube, or null if an entry node
        /// isn't available.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oUserNameDictionary">
        /// The key is the user name and the value is the corresponding GraphML XML
        /// node that represents the user.
        /// </param>
        ///
        /// <returns>
        /// true if a vertex XML node was added, false if a vertex XML node already
        /// exists.
        /// </returns>
        //*************************************************************************
        protected Boolean TryAppendVertexXmlNode(
            String sUserName,
            XmlNode oEntryXmlNode,
            GraphMLXmlDocument oGraphMLXmlDocument,
            Dictionary<String, XmlNode> oUserNameDictionary
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sUserName) );
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oUserNameDictionary != null);

            XmlNode oVertexXmlNode;

            if ( oUserNameDictionary.TryGetValue(sUserName, out oVertexXmlNode) )
            {
            return (false);
            }

            oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode(sUserName);
            oUserNameDictionary.Add(sUserName, oVertexXmlNode);

            oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode,
            MenuTextID, "Open YouTube Page for This Person");

            oGraphMLXmlDocument.AppendGraphMLAttributeValue( oVertexXmlNode,
            MenuActionID, String.Format(WebPageUrlPattern, sUserName) );

            return (true);
        }
        //*************************************************************************
        //  Method: GetSearchNetworkInternal()
        //
        /// <summary>
        /// Gets the network of people who have tweeted a specified search term,
        /// given a GraphMLXmlDocument.
        /// </summary>
        ///
        /// <param name="sSearchTerm">
        /// The term to search for.
        /// </param>
        ///
        /// <param name="eWhatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="iMaximumPeoplePerRequest">
        /// Maximum number of people to request for each query, or Int32.MaxValue
        /// for no limit.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// The GraphMLXmlDocument to populate with the requested network.
        /// </param>
        //*************************************************************************
        protected void GetSearchNetworkInternal(
            String sSearchTerm,
            WhatToInclude eWhatToInclude,
            Int32 iMaximumPeoplePerRequest,
            RequestStatistics oRequestStatistics,
            GraphMLXmlDocument oGraphMLXmlDocument
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
            Debug.Assert(iMaximumPeoplePerRequest > 0);
            Debug.Assert(oRequestStatistics != null);
            Debug.Assert(oGraphMLXmlDocument != null);
            AssertValid();

            if ( WhatToIncludeFlagIsSet(eWhatToInclude, WhatToInclude.Statuses) )
            {
            oGraphMLXmlDocument.DefineGraphMLAttribute(false, StatusID,
                "Tweet", "string", null);

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, StatusDateUtcID,
                "Tweet Date (UTC)", "string", null);
            }

            // The key is the screen name and the value is the corresponding
            // TwitterVertex.  This is used to prevent the same screen name from
            // being added to the XmlDocument twice.

            Dictionary<String, TwitterVertex> oScreenNameDictionary =
            new Dictionary<String, TwitterVertex>();

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

            AppendVertexXmlNodes(sSearchTerm, eWhatToInclude,
            iMaximumPeoplePerRequest, oGraphMLXmlDocument,
            oScreenNameDictionary, oRequestStatistics);

            if ( WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.FollowedEdges) )
            {
            // Look at the people followed by each author, and if a followed
            // has also tweeted the search term, add an edge between the author
            // and the followed.

            AppendFollowedEdgeXmlNodes(eWhatToInclude,
                iMaximumPeoplePerRequest, oGraphMLXmlDocument,
                oScreenNameDictionary, oRequestStatistics);
            }

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

            AppendMissingGraphMLAttributeValues(oGraphMLXmlDocument,
            oScreenNameDictionary, bIncludeStatistics, false,
            oRequestStatistics);

            AppendRepliesToAndMentionsXmlNodes(oGraphMLXmlDocument,
            oScreenNameDictionary,

            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.RepliesToEdges),

            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.MentionsEdges)
            );
        }
        //*************************************************************************
        //  Method: AppendAllStatisticGraphMLAttributeValues()
        //
        /// <overloads>
        /// Appends statistic GraphML attribute values to the GraphML document.
        /// </overloads>
        ///
        /// <summary>
        /// Appends statistic GraphML attribute values to the GraphML document for
        /// all users in the network.
        /// </summary>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oUserNameDictionary">
        /// The key is the user name and the value is the corresponding GraphML XML
        /// node that represents the user.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        //*************************************************************************
        protected void AppendAllStatisticGraphMLAttributeValues(
            GraphMLXmlDocument oGraphMLXmlDocument,
            Dictionary<String, XmlNode> oUserNameDictionary,
            RequestStatistics oRequestStatistics
            )
        {
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oUserNameDictionary != null);
            Debug.Assert(oRequestStatistics != null);
            AssertValid();

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

            ReportProgress( String.Format(

                "Getting statistics for \"{0}\"."
                ,
                sUserName
                ) );

            AppendAllStatisticGraphMLAttributeValues(sUserName,
                oKeyValuePair.Value, oGraphMLXmlDocument, oRequestStatistics);
            }
        }
        //*************************************************************************
        //  Method: AppendVertexXmlNode()
        //
        /// <summary>
        /// Appends a vertex XML node to the document for a tag if such a node
        /// doesn't already exist.
        /// </summary>
        ///
        /// <param name="sTag">
        /// Tag to add a vertex XML node for.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oTagDictionary">
        /// The key is the tag name and the value is the corresponding GraphML XML
        /// node that represents the tag.
        /// </param>
        //*************************************************************************
        protected void AppendVertexXmlNode(
            String sTag,
            GraphMLXmlDocument oGraphMLXmlDocument,
            Dictionary<String, XmlNode> oTagDictionary
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sTag) );
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oTagDictionary != null);

            if ( !oTagDictionary.ContainsKey(sTag) )
            {
            XmlNode oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode(
                sTag);

            oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode,
                LabelID, sTag);

            oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode,
                MenuTextID, "Open Flickr Page for This Tag");

            oGraphMLXmlDocument.AppendGraphMLAttributeValue( oVertexXmlNode,
                MenuActionID,

                String.Format(
                    "http://www.flickr.com/photos/tags/{0}/"
                    ,
                    UrlUtil.EncodeUrlParameter(sTag)
                ) );

            oTagDictionary.Add(sTag, oVertexXmlNode);
            }
        }
        //*************************************************************************
        //  Method: AppendAllStatisticGraphMLAttributeValues()
        //
        /// <summary>
        /// Appends statistic GraphML attribute values to the GraphML document for
        /// one user in the network.
        /// </summary>
        ///
        /// <param name="sUserName">
        /// The user name.
        /// </param>
        ///
        /// <param name="oVertexXmlNode">
        /// The GraphML XML node corresponding to the user.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        //*************************************************************************
        protected void AppendAllStatisticGraphMLAttributeValues(
            String sUserName,
            XmlNode oVertexXmlNode,
            GraphMLXmlDocument oGraphMLXmlDocument,
            RequestStatistics oRequestStatistics
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sUserName) );
            Debug.Assert(oVertexXmlNode != null);
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oRequestStatistics != null);
            AssertValid();

            XmlDocument oXmlDocument;
            XmlNamespaceManager oXmlNamespaceManager;

            // Some of the statistics are available in the user's profile.

            String sUrl = "http://gdata.youtube.com/feeds/api/users/"
            + EncodeUrlParameter(sUserName);

            if ( TryGetXmlDocument(sUrl, oRequestStatistics, out oXmlDocument,
            out oXmlNamespaceManager) )
            {
            AppendYouTubeDateGraphMLAttributeValue(oXmlDocument,
                "a:entry/a:published/text()", oXmlNamespaceManager,
                oGraphMLXmlDocument, oVertexXmlNode, JoinedDateUtcID);

            AppendStringGraphMLAttributeValue(oXmlDocument,
                "a:entry/media:thumbnail/@url", oXmlNamespaceManager,
                oGraphMLXmlDocument, oVertexXmlNode, ImageFileID);

            XmlNode oStatisticsXmlNode;

            if ( XmlUtil2.TrySelectSingleNode(oXmlDocument,
                "a:entry/yt:statistics", oXmlNamespaceManager,
                out oStatisticsXmlNode) )
            {
                AppendInt32GraphMLAttributeValue(oStatisticsXmlNode,
                    "@videoWatchCount", oXmlNamespaceManager,
                    oGraphMLXmlDocument, oVertexXmlNode, VideosWatchedID);

                AppendInt32GraphMLAttributeValue(oStatisticsXmlNode,
                    "@subscriberCount", oXmlNamespaceManager,
                    oGraphMLXmlDocument, oVertexXmlNode, SubscribersID);
            }
            }

            // The remaining statistics must be obtained from the totalResults
            // XML node within documents obtained from other URLs.

            const String TotalResultsXPath =
            "a:feed/openSearch:totalResults/text()";

            sUrl = GetFriendsOrSubscriptionsUrl(sUserName, true)
            + "?max-results=1";

            AppendInt32GraphMLAttributeValue(sUrl, TotalResultsXPath,
            oGraphMLXmlDocument, oRequestStatistics, oVertexXmlNode,
            FriendsID);

            sUrl = GetFriendsOrSubscriptionsUrl(sUserName, false)
            + "?max-results=1";

            AppendInt32GraphMLAttributeValue(sUrl, TotalResultsXPath,
            oGraphMLXmlDocument, oRequestStatistics, oVertexXmlNode,
            SubscriptionsID);

            sUrl = "http://gdata.youtube.com/feeds/api/users/"
            + EncodeUrlParameter(sUserName) + "/uploads?max-results=1";

            AppendInt32GraphMLAttributeValue(sUrl, TotalResultsXPath,
            oGraphMLXmlDocument, oRequestStatistics, oVertexXmlNode,
            VideosUploadedID);
        }
        //*************************************************************************
        //  Method: GetRelatedTagsInternal()
        //
        /// <summary>
        /// Gets the Flickr tags related to a specified tag, given a
        /// GraphXMLXmlDocument.
        /// </summary>
        ///
        /// <param name="sTag">
        /// Tag to get related tags for.
        /// </param>
        ///
        /// <param name="eWhatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="eNetworkLevel">
        /// Network level to include.  Must be NetworkLevel.One, OnePointFive, or
        /// Two.
        /// </param>
        ///
        /// <param name="sApiKey">
        /// Flickr API key.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// The GraphMLXmlDocument to populate with the requested network.
        /// </param>
        //*************************************************************************
        protected void GetRelatedTagsInternal(
            String sTag,
            WhatToInclude eWhatToInclude,
            NetworkLevel eNetworkLevel,
            String sApiKey,
            RequestStatistics oRequestStatistics,
            GraphMLXmlDocument oGraphMLXmlDocument
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sTag) );

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

            Debug.Assert( !String.IsNullOrEmpty(sApiKey) );
            Debug.Assert(oRequestStatistics != null);
            Debug.Assert(oGraphMLXmlDocument != null);
            AssertValid();

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

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

            GetRelatedTagsRecursive(sTag, eWhatToInclude, eNetworkLevel, sApiKey,
            1, oGraphMLXmlDocument, oTagDictionary, oRequestStatistics);

            if ( WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.SampleThumbnails) )
            {
            AppendSampleThumbnails(oTagDictionary, oGraphMLXmlDocument,
                sApiKey, oRequestStatistics);
            }
        }
        //*************************************************************************
        //  Method: AppendInt32GraphMLAttributeValue()
        //
        /// <summary>
        /// Appends an Int32 GraphML-Attribute value from an XmlDocument to a
        /// vertex XML node. 
        /// </summary>
        ///
        /// <param name="sUrl">
        /// The URL to get the XmlDocument from.
        /// </param>
        /// 
        /// <param name="sXPath">
        /// Path to the value within the XmlDocument.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        ///
        /// <param name="oVertexXmlNode">
        /// The vertex XML node from <paramref name="oGraphMLXmlDocument" /> to add
        /// the GraphML attribute value to.
        /// </param>
        ///
        /// <param name="sGraphMLAttributeID">
        /// GraphML ID of the attribute.
        /// </param>
        ///
        /// <remarks>
        /// This method looks for an Int32 value within an XmlDocument returned by
        /// YouTube and stores the value on a vertex XML node in the GraphML
        /// document.
        /// </remarks>
        //*************************************************************************
        protected void AppendInt32GraphMLAttributeValue(
            String sUrl,
            String sXPath,
            GraphMLXmlDocument oGraphMLXmlDocument,
            RequestStatistics oRequestStatistics,
            XmlNode oVertexXmlNode,
            String sGraphMLAttributeID
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sUrl) );
            Debug.Assert( !String.IsNullOrEmpty(sXPath) );
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oRequestStatistics != null);
            Debug.Assert(oVertexXmlNode != null);
            Debug.Assert( !String.IsNullOrEmpty(sGraphMLAttributeID) );
            AssertValid();

            XmlDocument oXmlDocument;
            XmlNamespaceManager oXmlNamespaceManager;

            if ( !TryGetXmlDocument(sUrl, oRequestStatistics, out oXmlDocument,
            out oXmlNamespaceManager) )
            {
            return;
            }

            AppendInt32GraphMLAttributeValue(oXmlDocument, sXPath,
            oXmlNamespaceManager, oGraphMLXmlDocument, oVertexXmlNode,
            sGraphMLAttributeID);
        }
        //*************************************************************************
        //  Method: AppendEdgesFromDictionary()
        //
        /// <summary>
        /// Appends an edge for each pair of videos that share the same dictionary
        /// key.
        /// </summary>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oDictionary">
        /// The key is a string and the value is a LinkedList of the video IDs
        /// corresponding to that key.
        /// </param>
        ///
        /// <param name="sRelationship">
        /// The value of the RelationshipID GraphML-attribute to use.
        /// </param>
        //*************************************************************************
        protected void AppendEdgesFromDictionary(
            Dictionary< String, LinkedList<String> > oDictionary,
            GraphMLXmlDocument oGraphMLXmlDocument,
            String sRelationship
            )
        {
            Debug.Assert(oDictionary != null);
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert( !String.IsNullOrEmpty(sRelationship) );
            AssertValid();

            // For each key...

            foreach (KeyValuePair< String, LinkedList<String> > oKeyValuePair in
            oDictionary)
            {
            // For each video ID that has the key...

            LinkedList<String> oVideoIDsWithThisKey = oKeyValuePair.Value;

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

                oVideoIDWithThisKey != null;

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

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

                    oSubsequentVideoIDWithThisKey != null;

                    oSubsequentVideoIDWithThisKey =
                        oSubsequentVideoIDWithThisKey.Next
                    )
                {
                    AppendEdgeXmlNode(oGraphMLXmlDocument,
                        oVideoIDWithThisKey.Value,
                        oSubsequentVideoIDWithThisKey.Value, sRelationship);
                }
            }
            }
        }
        //*************************************************************************
        //  Method: CreateGraphMLXmlDocument()
        //
        /// <summary>
        /// Creates a GraphMLXmlDocument representing a network of YouTube users.
        /// </summary>
        ///
        /// <param name="bIncludeAllStatistics">
        /// true to include each user's statistics.
        /// </param>
        ///
        /// <returns>
        /// A GraphMLXmlDocument representing a network of YouTube users.  The
        /// document includes GraphML-attribute definitions but no vertices or
        /// edges.
        /// </returns>
        //*************************************************************************
        protected GraphMLXmlDocument CreateGraphMLXmlDocument(
            Boolean bIncludeAllStatistics
            )
        {
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument = new GraphMLXmlDocument(true);

            DefineCustomMenuGraphMLAttributes(oGraphMLXmlDocument);
            DefineRelationshipGraphMLAttribute(oGraphMLXmlDocument);

            if (bIncludeAllStatistics)
            {
            oGraphMLXmlDocument.DefineGraphMLAttribute(false, FriendsID,
                "Friends", "int", null);

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, SubscriptionsID,
                "People Subscribed To", "int", null);

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, SubscribersID,
                "Subscribers", "int", null);

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, VideosWatchedID,
                "Videos Watched", "int", null);

            oGraphMLXmlDocument.DefineGraphMLAttribute(false,
                JoinedDateUtcID, "Joined YouTube Date (UTC)", "string", null);

            oGraphMLXmlDocument.DefineGraphMLAttribute(false, VideosUploadedID,
                "Videos Uploaded", "int", null);

            DefineImageFileGraphMLAttribute(oGraphMLXmlDocument);
            }

            return (oGraphMLXmlDocument);
        }
        //*************************************************************************
        //  Method: AppendVertexXmlNodes()
        //
        /// <summary>
        /// Appends a vertex XML node for each video that matches a specified
        /// search term.
        /// </summary>
        ///
        /// <param name="sSearchTerm">
        /// The term to search for.
        /// </param>
        ///
        /// <param name="eWhatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="iMaximumVideos">
        /// Maximum number of videos to request, or Int32.MaxValue for no limit.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// The GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        ///
        /// <param name="oVideoIDs">
        /// Where the set of unique video IDs gets stored.
        /// </param>
        ///
        /// <param name="oTagDictionary">
        /// If an edge should be included for each pair of videos that share the
        /// same tag, this gets set to a Dictionary for which the key is a
        /// lower-case tag and the value is a LinkedList of the video IDs that have
        /// the tag.  Otherwise, it gets set to null.
        /// </param>
        //*************************************************************************
        protected void AppendVertexXmlNodes(
            String sSearchTerm,
            WhatToInclude eWhatToInclude,
            Int32 iMaximumVideos,
            GraphMLXmlDocument oGraphMLXmlDocument,
            RequestStatistics oRequestStatistics,
            out HashSet<String> oVideoIDs,
            out Dictionary< String, LinkedList<String> > oTagDictionary
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
            Debug.Assert(iMaximumVideos> 0);
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oRequestStatistics != null);
            AssertValid();

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

            // This is used to skip duplicate videos in the results returned by
            // YouTube.  (I'm not sure why YouTube sometimes returns duplicates,
            // but it does.)

            oVideoIDs = new HashSet<String>();

            // If an edge should be included for each pair of videos that share the
            // same tag, the key is a lower-case tag and the value is a LinkedList
            // of the video IDs that have the tag.

            if ( WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.SharedTagEdges) )
            {
            oTagDictionary = new Dictionary< String, LinkedList<String> >();
            }
            else
            {
            oTagDictionary = null;
            }

            String sUrl = String.Format(

            "http://gdata.youtube.com/feeds/api/videos?q={0}"
            ,
            EncodeUrlParameter(sSearchTerm)
            );

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

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

            // Use the video ID as the GraphML vertex name.  The video title
            // can't be used because it is not unique.

            String sVideoID;

            if (
                !XmlUtil2.TrySelectSingleNodeAsString(oEntryXmlNode,
                    "media:group/yt:videoid/text()", oXmlNamespaceManager,
                    out sVideoID)
                ||
                oVideoIDs.Contains(sVideoID)
                )
            {
                continue;
            }

            oVideoIDs.Add(sVideoID);

            XmlNode oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode(
                sVideoID);

            AppendStringGraphMLAttributeValue(oEntryXmlNode,
                "a:title/text()", oXmlNamespaceManager, oGraphMLXmlDocument,
                oVertexXmlNode, TitleID);

            AppendDoubleGraphMLAttributeValue(oEntryXmlNode,
                "gd:rating/@average", oXmlNamespaceManager,
                oGraphMLXmlDocument, oVertexXmlNode, RatingID);

            AppendInt32GraphMLAttributeValue(oEntryXmlNode,
                "yt:statistics/@viewCount", oXmlNamespaceManager,
                oGraphMLXmlDocument, oVertexXmlNode, ViewsID);

            AppendInt32GraphMLAttributeValue(oEntryXmlNode,
                "yt:statistics/@favoriteCount", oXmlNamespaceManager,
                oGraphMLXmlDocument, oVertexXmlNode, FavoritedID);

            AppendInt32GraphMLAttributeValue(oEntryXmlNode,
                "gd:comments/gd:feedLink/@countHint", oXmlNamespaceManager,
                oGraphMLXmlDocument, oVertexXmlNode, CommentsID);

            AppendYouTubeDateGraphMLAttributeValue(oEntryXmlNode,
                "a:published/text()", oXmlNamespaceManager,
                oGraphMLXmlDocument, oVertexXmlNode, CreatedDateUtcID);

            AppendStringGraphMLAttributeValue(oEntryXmlNode,
                "media:group/media:thumbnail/@url", oXmlNamespaceManager,
                oGraphMLXmlDocument, oVertexXmlNode, ImageFileID);

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

            if (oTagDictionary != null)
            {
                CollectTags(oEntryXmlNode, sVideoID, oXmlNamespaceManager,
                    oTagDictionary);
            }
            }
        }
        //*************************************************************************
        //  Method: GetUserNetworkInternal()
        //
        /// <summary>
        /// Gets a network of YouTube users, given a GraphMLXmlDocument.
        /// </summary>
        ///
        /// <param name="sUserNameToAnalyze">
        /// The user name of the YouTube user whose network should be analyzed.
        /// </param>
        ///
        /// <param name="eWhatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="eNetworkLevel">
        /// Network level to include.  Must be NetworkLevel.One, OnePointFive, or
        /// Two.
        /// </param>
        ///
        /// <param name="iMaximumPeoplePerRequest">
        /// Maximum number of people to request for each query, or Int32.MaxValue
        /// for no limit.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// The GraphMLXmlDocument to populate with the requested network.
        /// </param>
        //*************************************************************************
        protected void GetUserNetworkInternal(
            String sUserNameToAnalyze,
            WhatToInclude eWhatToInclude,
            NetworkLevel eNetworkLevel,
            Int32 iMaximumPeoplePerRequest,
            RequestStatistics oRequestStatistics,
            GraphMLXmlDocument oGraphMLXmlDocument
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sUserNameToAnalyze) );

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

            Debug.Assert(iMaximumPeoplePerRequest > 0);
            Debug.Assert(oRequestStatistics != null);
            Debug.Assert(oGraphMLXmlDocument != null);
            AssertValid();

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

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

            // Include friends, subscriptions, both, or neither.

            Boolean [] abIncludes = new Boolean[] {

            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.FriendVertices),

            WhatToIncludeFlagIsSet(eWhatToInclude,
                WhatToInclude.SubscriptionVertices),
            };

            for (Int32 i = 0; i < 2; i++)
            {
            if ( abIncludes[i] )
            {
                GetUserNetworkRecursive(sUserNameToAnalyze, eWhatToInclude,
                    (i == 0), eNetworkLevel, iMaximumPeoplePerRequest, 1,
                    oGraphMLXmlDocument, oUserNameDictionary,
                    oRequestStatistics);
            }
            }

            if ( WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.AllStatistics) )
            {
            AppendAllStatisticGraphMLAttributeValues(oGraphMLXmlDocument,
                oUserNameDictionary, oRequestStatistics);
            }
        }
        //*************************************************************************
        //  Method: GetVideoNetworkInternal()
        //
        /// <summary>
        /// Gets a network of related YouTube videos, given a GraphMLXmlDocument.
        /// </summary>
        ///
        /// <param name="sSearchTerm">
        /// The term to search for.
        /// </param>
        ///
        /// <param name="eWhatToInclude">
        /// Specifies what should be included in the network.
        /// </param>
        ///
        /// <param name="iMaximumVideos">
        /// Maximum number of videos to request, or Int32.MaxValue for no limit.
        /// </param>
        ///
        /// <param name="oRequestStatistics">
        /// A <see cref="RequestStatistics" /> object that is keeping track of
        /// requests made while getting the network.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// The GraphMLXmlDocument to populate with the requested network.
        /// </param>
        //*************************************************************************
        protected void GetVideoNetworkInternal(
            String sSearchTerm,
            WhatToInclude eWhatToInclude,
            Int32 iMaximumVideos,
            RequestStatistics oRequestStatistics,
            GraphMLXmlDocument oGraphMLXmlDocument
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
            Debug.Assert(iMaximumVideos > 0);
            Debug.Assert(oRequestStatistics != null);
            Debug.Assert(oGraphMLXmlDocument != null);
            AssertValid();

            // First, add a vertex for each video matching the search term.

            HashSet<String> oVideoIDs;
            Dictionary< String, LinkedList<String> > oTagDictionary;

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

            // Now add whatever edges were requested.

            if ( WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.SharedTagEdges) )
            {
            Debug.Assert(oTagDictionary != null);

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

            AppendEdgesFromDictionary(oTagDictionary, oGraphMLXmlDocument,
                "Shared tag");
            }

            oTagDictionary = null;

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

            if ( WhatToIncludeFlagIsSet(eWhatToInclude,
            WhatToInclude.SharedVideoResponderEdges) )
            {
            AppendSharedResponderEdges(oGraphMLXmlDocument, oVideoIDs,
                iMaximumVideos,
                "http://gdata.youtube.com/feeds/api/videos/{0}/responses",
                "video responder", oRequestStatistics);
            }
        }
예제 #30
0
        //*************************************************************************
        //  Method: SaveGraphCore()
        //
        /// <summary>
        /// Saves graph data to a stream.
        /// </summary>
        ///
        /// <param name="graph">
        /// Graph to save.
        /// </param>
        ///
        /// <param name="stream">
        /// Stream to save the graph data to.
        /// </param>
        ///
        /// <remarks>
        /// This method saves <paramref name="graph" /> to <paramref
        /// name="stream" />.  It does not close <paramref name="stream" />.
        ///
        /// <para>
        /// The arguments have already been checked for validity.
        /// </para>
        ///
        /// </remarks>
        //*************************************************************************
        protected override void SaveGraphCore(
            IGraph graph,
            Stream stream
            )
        {
            Debug.Assert(graph != null);
            Debug.Assert(stream != null);
            AssertValid();

            GraphMLXmlDocument oGraphMLXmlDocument = new GraphMLXmlDocument(
            graph.Directedness == GraphDirectedness.Directed);

            String [] asEdgeAttributeNames = ( String[] )graph.GetRequiredValue(
            ReservedMetadataKeys.AllEdgeMetadataKeys, typeof( String[] ) );

            String [] asVertexAttributeNames = ( String[] )graph.GetRequiredValue(
            ReservedMetadataKeys.AllVertexMetadataKeys, typeof( String[] ) );

            // Define the Graph-ML attributes.

            const String VertexAttributeIDPrefix = "V-";
            const String EdgeAttributeIDPrefix = "E-";

            foreach (String sVertexAttributeName in asVertexAttributeNames)
            {
            oGraphMLXmlDocument.DefineGraphMLAttribute(false,
                VertexAttributeIDPrefix + sVertexAttributeName,
                sVertexAttributeName, "string", null);
            }

            foreach (String sEdgeAttributeName in asEdgeAttributeNames)
            {
            oGraphMLXmlDocument.DefineGraphMLAttribute(true,
                EdgeAttributeIDPrefix + sEdgeAttributeName,
                sEdgeAttributeName, "string", null);
            }

            // Add the vertices and their Graph-ML attribute values.

            foreach (IVertex oVertex in graph.Vertices)
            {
            XmlNode oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode(
                oVertex.Name);

            AppendGraphMLAttributeValues(oVertex, oGraphMLXmlDocument,
                oVertexXmlNode, asVertexAttributeNames,
                VertexAttributeIDPrefix);
            }

            // Add the edges and their Graph-ML attribute values.

            foreach (IEdge oEdge in graph.Edges)
            {
            IVertex [] oVertices = oEdge.Vertices;

            XmlNode oEdgeXmlNode = oGraphMLXmlDocument.AppendEdgeXmlNode(
                oVertices[0].Name, oVertices[1].Name);

            AppendGraphMLAttributeValues(oEdge, oGraphMLXmlDocument,
                oEdgeXmlNode, asEdgeAttributeNames, EdgeAttributeIDPrefix);
            }

            oGraphMLXmlDocument.Save(stream);
        }