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

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

            oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode,
                LabelID, sTag);

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

            oGraphMLXmlDocument.AppendGraphMLAttributeValue( oVertexXmlNode,
                MenuActionID,

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

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

            XmlNode oVertexXmlNode;

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

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

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

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

            return (true);
        }
        //*************************************************************************
        //  Method: 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);
        }
        //*************************************************************************
        //  Method: TryAppendVertexXmlNode()
        //
        /// <overloads>
        /// Appends a vertex XML node to the GraphML document for a person if such
        /// a node doesn't already exist.
        /// </overloads>
        ///
        /// <summary>
        /// Appends a vertex XML node to the GraphML document for a person if such
        /// a node doesn't already exist and provides the TwitterVertex for the
        /// node.
        /// </summary>
        ///
        /// <param name="sScreenName">
        /// Screen name to add a vertex XML node for.
        /// </param>
        ///
        /// <param name="oUserXmlNode">
        /// The "user" XML node returned by Twitter, or null if a user node isn't
        /// available.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oScreenNameDictionary">
        /// The key is the screen name in lower case and the value is the
        /// corresponding TwitterVertex.
        /// </param>
        ///
        /// <param name="bIncludeStatistics">
        /// true to include the user's statistics if <paramref
        /// name="oUserXmlNode" /> is not null.
        /// </param>
        ///
        /// <param name="bIncludeLatestStatus">
        /// true to include a latest status attribute value if <paramref
        /// name="oUserXmlNode" /> is not null.
        /// </param>
        ///
        /// <param name="oTwitterVertex">
        /// Where the TwitterVertex that wraps the vertex XML node gets stored.
        /// This gets set regardless of whether the node already existed.
        /// </param>
        ///
        /// <returns>
        /// true if a vertex XML node was added, false if a vertex XML node already
        /// existed.
        /// </returns>
        //*************************************************************************
        protected Boolean TryAppendVertexXmlNode(
            String sScreenName,
            XmlNode oUserXmlNode,
            GraphMLXmlDocument oGraphMLXmlDocument,
            Dictionary<String, TwitterVertex> oScreenNameDictionary,
            Boolean bIncludeStatistics,
            Boolean bIncludeLatestStatus,
            out TwitterVertex oTwitterVertex
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sScreenName) );
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oScreenNameDictionary != null);

            oTwitterVertex = null;

            if ( oScreenNameDictionary.TryGetValue(sScreenName,
            out oTwitterVertex) )
            {
            return (false);
            }

            XmlNode oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode(
            sScreenName);

            oTwitterVertex = new TwitterVertex(oVertexXmlNode);
            oScreenNameDictionary.Add(sScreenName, oTwitterVertex);

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

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

            if (oUserXmlNode != null)
            {
            AppendFromUserXmlNode(oUserXmlNode, oGraphMLXmlDocument,
                oTwitterVertex, bIncludeStatistics, bIncludeLatestStatus);
            }

            return (true);
        }
        //*************************************************************************
        //  Method: 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="sUserID">
        /// User ID to add a vertex XML node for.
        /// </param>
        ///
        /// <param name="sScreenName">
        /// Screen name to add a vertex XML node for.
        /// </param>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oUserIDDictionary">
        /// The key is the user ID and the value is the corresponding GraphML XML
        /// node that represents the user.
        /// </param>
        ///
        /// <returns>
        /// true if a vertex XML node was added, false if a vertex XML node already
        /// exists.
        /// </returns>
        //*************************************************************************
        protected Boolean TryAppendVertexXmlNode(
            String sUserID,
            String sScreenName,
            GraphMLXmlDocument oGraphMLXmlDocument,
            Dictionary<String, XmlNode> oUserIDDictionary
            )
        {
            Debug.Assert( !String.IsNullOrEmpty(sUserID) );
            Debug.Assert( !String.IsNullOrEmpty(sScreenName) );
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oUserIDDictionary != null);

            XmlNode oVertexXmlNode;

            if ( oUserIDDictionary.TryGetValue(sUserID, out oVertexXmlNode) )
            {
            return (false);
            }

            oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode(sScreenName);
            oUserIDDictionary.Add(sUserID, oVertexXmlNode);

            return (true);
        }