//*************************************************************************
        //  Method: AppendFromUserXmlNodeCalled()
        //
        /// <summary>
        /// Checks whether <see cref="AppendFromUserXmlNode" /> has been called for
        /// a vertex XML node.
        /// </summary>
        ///
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oVertexXmlNode">
        /// The vertex XML node to check.
        /// </param>
        ///
        /// <remarks>
        /// true if <see cref="AppendFromUserXmlNode" /> has been called for the
        /// vertex XML node.
        /// </remarks>
        //*************************************************************************
        protected Boolean AppendFromUserXmlNodeCalled(
            GraphMLXmlDocument oGraphMLXmlDocument,
            XmlNode oVertexXmlNode
            )
        {
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oVertexXmlNode != null);

            XmlNamespaceManager oXmlNamespaceManager =
            oGraphMLXmlDocument.CreateXmlNamespaceManager("a");

            // The FollowedID and LatestStatusID GraphML-attributes are appended
            // only by AppendFromUserXmlNode(), so if either is present,
            // AppendFromUserXmlNode() has been called.

            return (
            oVertexXmlNode.SelectSingleNode(
                "a:data[@key='" + FollowedID + "']", oXmlNamespaceManager)
                != null
            ||
            oVertexXmlNode.SelectSingleNode(
                "a:data[@key='" + LatestStatusID + "']", oXmlNamespaceManager)
                != null
            );
        }
        //*************************************************************************
        //  Method: AppendRepliesToAndMentionsXmlNodes()
        //
        /// <summary>
        /// Appends edge XML nodes for replies-to and mentions relationships.
        /// </summary>
        ///
        /// <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="bIncludeRepliesToEdges">
        /// true to append edges for replies-to relationships.
        /// </param>
        ///
        /// <param name="bIncludeMentionsEdges">
        /// true to append edges for mentions relationships.
        /// </param>
        //*************************************************************************
        protected void AppendRepliesToAndMentionsXmlNodes(
            GraphMLXmlDocument oGraphMLXmlDocument,
            Dictionary<String, TwitterVertex> oScreenNameDictionary,
            Boolean bIncludeRepliesToEdges,
            Boolean bIncludeMentionsEdges
            )
        {
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oScreenNameDictionary != null);
            AssertValid();

            if (!bIncludeRepliesToEdges && !bIncludeMentionsEdges)
            {
            return;
            }

            XmlNamespaceManager oGraphMLXmlNamespaceManager =
            oGraphMLXmlDocument.CreateXmlNamespaceManager("g");

            ReportProgress("Examining relationships.");

            // "Starts with a screen name," which means it's a "reply-to".

            Regex oReplyToRegex = new Regex(@"^@(?<ScreenName>\w+)");

            // "Contains a screen name," which means it's a "mentions".
            //
            // Note that a "reply-to" is also a "mentions."

            Regex oMentionsRegex = new Regex(@"(^|\s)@(?<ScreenName>\w+)");

            foreach (KeyValuePair<String, TwitterVertex> oKeyValuePair in
            oScreenNameDictionary)
            {
            String sScreenName = oKeyValuePair.Key;
            TwitterVertex oTwitterVertex = oKeyValuePair.Value;
            String sStatusForAnalysis = oTwitterVertex.StatusForAnalysis;

            String sStatusForAnalysisDateUtc =
                oTwitterVertex.StatusForAnalysisDateUtc;

            if ( String.IsNullOrEmpty(sStatusForAnalysis) )
            {
                continue;
            }

            if (bIncludeRepliesToEdges)
            {
                Match oReplyToMatch = oReplyToRegex.Match(sStatusForAnalysis);

                if (oReplyToMatch.Success)
                {
                    String sReplyToScreenName =
                        oReplyToMatch.Groups["ScreenName"].Value.ToLower();

                    if (
                        sReplyToScreenName != sScreenName
                        &&
                        oScreenNameDictionary.ContainsKey(sReplyToScreenName)
                        )
                    {
                        XmlNode oEdgeXmlNode = AppendEdgeXmlNode(
                            oGraphMLXmlDocument, sScreenName,
                            sReplyToScreenName, "Replies to");

                        if ( !String.IsNullOrEmpty(sStatusForAnalysisDateUtc) )
                        {
                            oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                                oEdgeXmlNode, RelationshipDateUtcID,
                                sStatusForAnalysisDateUtc);
                        }
                    }
                }
            }

            if (bIncludeMentionsEdges)
            {
                Match oMentionsMatch =
                    oMentionsRegex.Match(sStatusForAnalysis);

                while (oMentionsMatch.Success)
                {
                    String sMentionsScreenName =
                        oMentionsMatch.Groups["ScreenName"].Value.ToLower();

                    if (
                        sMentionsScreenName != sScreenName
                        &&
                        oScreenNameDictionary.ContainsKey(sMentionsScreenName)
                        )
                    {
                        XmlNode oEdgeXmlNode = AppendEdgeXmlNode(
                            oGraphMLXmlDocument, sScreenName,
                            sMentionsScreenName, "Mentions");

                        if ( !String.IsNullOrEmpty(sStatusForAnalysisDateUtc) )
                        {
                            oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                                oEdgeXmlNode, RelationshipDateUtcID,
                                sStatusForAnalysisDateUtc);
                        }
                    }

                    oMentionsMatch = oMentionsMatch.NextMatch();
                }
            }
            }
        }
        //*************************************************************************
        //  Method: AppendFromUserXmlNode()
        //
        /// <summary>
        /// Appends GraphML-Attribute values from a "user" XML node returned by
        /// Twitter to a vertex XML node.
        /// </summary>
        ///
        /// <param name="oUserXmlNode">
        /// The "user" XML node returned by Twitter.  Can't be null.
        /// </param>
        /// 
        /// <param name="oGraphMLXmlDocument">
        /// GraphMLXmlDocument being populated.
        /// </param>
        ///
        /// <param name="oTwitterVertex">
        /// Contains the vertex XML node from <paramref
        /// name="oGraphMLXmlDocument" /> to add the GraphML attribute values to.
        /// </param>
        ///
        /// <param name="bIncludeStatistics">
        /// true to include the user's statistics.
        /// </param>
        ///
        /// <param name="bIncludeLatestStatus">
        /// true to include a latest status attribute value.
        /// </param>
        ///
        /// <remarks>
        /// This method reads information from a "user" XML node returned by
        /// Twitter and appends the information to a vertex XML node in the GraphML
        /// document.
        /// </remarks>
        //*************************************************************************
        protected void AppendFromUserXmlNode(
            XmlNode oUserXmlNode,
            GraphMLXmlDocument oGraphMLXmlDocument,
            TwitterVertex oTwitterVertex,
            Boolean bIncludeStatistics,
            Boolean bIncludeLatestStatus
            )
        {
            Debug.Assert(oUserXmlNode != null);
            Debug.Assert(oGraphMLXmlDocument != null);
            Debug.Assert(oTwitterVertex != null);
            AssertValid();

            XmlNode oVertexXmlNode = oTwitterVertex.VertexXmlNode;

            if (bIncludeStatistics)
            {
            AppendInt32GraphMLAttributeValue(oUserXmlNode,
                "friends_count/text()", null, oGraphMLXmlDocument,
                oVertexXmlNode, FollowedID);

            AppendInt32GraphMLAttributeValue(oUserXmlNode,
                "followers_count/text()", null, oGraphMLXmlDocument,
                oVertexXmlNode, FollowersID);

            AppendInt32GraphMLAttributeValue(oUserXmlNode,
                "statuses_count/text()", null, oGraphMLXmlDocument,
                oVertexXmlNode, StatusesID);

            AppendInt32GraphMLAttributeValue(oUserXmlNode,
                "favourites_count/text()", null, oGraphMLXmlDocument,
                oVertexXmlNode, FavoritesID);

            AppendStringGraphMLAttributeValue(oUserXmlNode,
                "description/text()", null, oGraphMLXmlDocument,
                oVertexXmlNode, DescriptionID);

            AppendStringGraphMLAttributeValue(oUserXmlNode,
                "time_zone/text()", null, oGraphMLXmlDocument, oVertexXmlNode,
                TimeZoneID);

            AppendStringGraphMLAttributeValue(oUserXmlNode,
                "utc_offset/text()", null, oGraphMLXmlDocument, oVertexXmlNode,
                UtcOffsetID);

            String sJoinedDateUtc;

            if ( XmlUtil2.TrySelectSingleNodeAsString(oUserXmlNode,
                "created_at/text()", null, out sJoinedDateUtc) )
            {
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                    oVertexXmlNode, JoinedDateUtcID,
                    TwitterDateParser.ParseTwitterDate(sJoinedDateUtc) );
            }
            }

            String sLatestStatus;

            if ( XmlUtil2.TrySelectSingleNodeAsString(oUserXmlNode,
            "status/text/text()", null, out sLatestStatus) )
            {
            String sLatestStatusDateUtc;

            if ( XmlUtil2.TrySelectSingleNodeAsString(oUserXmlNode,
                "status/created_at/text()", null, out sLatestStatusDateUtc) )
            {
                sLatestStatusDateUtc = TwitterDateParser.ParseTwitterDate(
                    sLatestStatusDateUtc);
            }

            // Don't overwrite any status the derived class may have already
            // stored on the TwitterVertex object.

            if (oTwitterVertex.StatusForAnalysis == null)
            {
                oTwitterVertex.StatusForAnalysis = sLatestStatus;
                oTwitterVertex.StatusForAnalysisDateUtc = sLatestStatusDateUtc;
            }

            if (bIncludeLatestStatus)
            {
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode,
                    LatestStatusID, sLatestStatus);

                if ( !String.IsNullOrEmpty(sLatestStatusDateUtc) )
                {
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                        oVertexXmlNode, LatestStatusDateUtcID,
                        sLatestStatusDateUtc);
                }
            }
            }

            // Add an image URL GraphML-attribute if it hasn't already been added.
            // (It might have been added from an "entry" node by
            // TwitterSearchNetworkAnalyzer.)

            if (oVertexXmlNode.SelectSingleNode(
            "a:data[@key='" + ImageFileID + "']",
            oGraphMLXmlDocument.CreateXmlNamespaceManager("a") ) == null)
            {
            AppendStringGraphMLAttributeValue(oUserXmlNode,
                "profile_image_url/text()", null, oGraphMLXmlDocument,
                oVertexXmlNode, ImageFileID);
            }
        }