AppendGraphMLAttributeValue() public method

public AppendGraphMLAttributeValue ( XmlNode edgeOrVertexXmlNode, String attributeID, Double attributeValue ) : void
edgeOrVertexXmlNode System.Xml.XmlNode
attributeID String
attributeValue Double
return void
        AddStatusUpdates
        (
            ref GraphMLXmlDocument oGraphMLXmlDocument,
            ref XmlNode oVertexXmlNode,
            string userID,
            Dictionary<string, List<Dictionary<string, object>>> statusUpdates
        )
        {
            List<Dictionary<string, object>>.Enumerator en;
            string currentStatusUpdate = "";
            string currentStatusTags = "";           


            //Add status updates
            if (statusUpdates.ContainsKey(userID))
            {
                en = statusUpdates[userID].GetEnumerator();
                currentStatusUpdate = "";
                currentStatusTags = "";
                while (en.MoveNext() && currentStatusUpdate.Length < 8000)
                {
                    if (((JSONObject)en.Current["message"]).String.Equals("")) continue;
                    currentStatusUpdate += ((JSONObject)en.Current["message"]).String + "\n\n";
                    //Add tags
                    if (en.Current["message_tags"] is List<JSONObject>)
                    {
                        foreach (JSONObject ob in (List<JSONObject>)en.Current["message_tags"])
                        {
                            currentStatusTags += ob.String + ",";
                        }
                        currentStatusTags = currentStatusTags.Remove(currentStatusTags.Length - 1);
                        currentStatusTags += "\n\n";
                    }
                }

                oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "statuses", currentStatusUpdate);
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "statuses_tags", currentStatusTags);
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "statuses_urls", GetURLs(currentStatusUpdate, ' '));
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "statuses_hashtags", GetHashtags(currentStatusUpdate, ' '));
            }
        }
    AppendVertexXmlNodes
    (
        String sSearchTerm,
        WhatToInclude eWhatToInclude,
        Int32 iMaximumVideos,
        GraphMLXmlDocument oGraphMLXmlDocument,
        RequestStatistics oRequestStatistics,
        out HashSet<String> oVideoIDs,
        out Dictionary< String, LinkedList<String> > oCategoryDictionary
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sSearchTerm) );
        Debug.Assert(iMaximumVideos> 0);
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

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

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

        oVideoIDs = new HashSet<String>();

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

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

        String sUrl = String.Format(

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

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

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

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

            String sVideoID;

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

            oVideoIDs.Add(sVideoID);

            XmlNode oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode(
                sVideoID);

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

            AppendStringGraphMLAttributeValue(oEntryXmlNode,
                "a:author/a:name/text()", oXmlNamespaceManager,
                oGraphMLXmlDocument, oVertexXmlNode, AuthorID);

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

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

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

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

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

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

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

            if (oCategoryDictionary != null)
            {
                CollectCategories(oEntryXmlNode, sVideoID,
                    oXmlNamespaceManager, oCategoryDictionary);
            }
        }
    }
    AppendEdgeXmlNode
    (
        GraphMLXmlDocument graphMLXmlDocument,
        String vertex1ID,
        String vertex2ID,
        String relationship
    )
    {
        Debug.Assert(graphMLXmlDocument != null);
        Debug.Assert( !String.IsNullOrEmpty(vertex1ID) );
        Debug.Assert( !String.IsNullOrEmpty(vertex2ID) );
        Debug.Assert( !String.IsNullOrEmpty(relationship) );

        XmlNode edgeXmlNode = graphMLXmlDocument.AppendEdgeXmlNode(
            vertex1ID, vertex2ID);

        graphMLXmlDocument.AppendGraphMLAttributeValue(edgeXmlNode,
            EdgeRelationshipID, relationship);

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

        oGraphMLXmlDocument.AppendGraphMLAttributeValue(oEdgeXmlNode,

            TwitterGraphMLUtil.EdgeRelationshipDateUtcID, 

            DateTimeUtil2.ToCultureInvariantString(
                oRequestStatistics.StartTimeUtc)
            );
    }
    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);
    }
    AppendSampleThumbnails
    (
        Dictionary<String, XmlNode> oTagDictionary,
        GraphMLXmlDocument oGraphMLXmlDocument,
        String sApiKey,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert(oTagDictionary != null);
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert( !String.IsNullOrEmpty(sApiKey) );
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

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

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

            String sSampleImageUrl;

            if ( TryGetSampleImageUrl(sTag, sApiKey, oRequestStatistics,
                out sSampleImageUrl) )
            {
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                    oKeyValuePair.Value, NodeXLGraphMLUtil.VertexImageFileID,
                    sSampleImageUrl);
            }
        }
    }
示例#7
0
 protected override void AddVertexImageAttribute(XmlNode oVertexXmlNode, Vertex<String> oVertex, GraphMLXmlDocument oGraphMLXmlDocument)
 {
     // add picture
     if (oVertex.Attributes.ContainsKey("photo_50") &&
         oVertex.Attributes["photo_50"] != null)
     {
         oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, ImageFileID, oVertex.Attributes["photo_50"].ToString());
     }
 }
    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,
            NodeXLGraphMLUtil.VertexMenuTextID,
            "Open YouTube Page for This Person");

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

        return (true);
    }
    AppendVertexTooltipXmlNodes
    (
        GraphMLXmlDocument graphMLXmlDocument,
        Dictionary<String, TwitterUser> userIDDictionary
    )
    {
        Debug.Assert(graphMLXmlDocument != null);
        Debug.Assert(userIDDictionary != null);

        foreach (TwitterUser twitterUser in userIDDictionary.Values)
        {
            // The tooltip is the user's screen name followed by the first
            // tweet returned by Twitter.

            ICollection<TwitterStatus> statuses = twitterUser.Statuses;

            String firstStatus = statuses.Count > 0 ?
                statuses.First().Text : String.Empty;

            // The Excel template doesn't wrap long tooltip text.  Break the
            // status into lines so the entire tooltip will show in the graph
            // pane.

            firstStatus = StringUtil.BreakIntoLines(firstStatus, 30);

            String toolTip = String.Format(
                "{0}\n\n{1}"
                ,
                twitterUser.ScreenName,
                firstStatus
                );

            graphMLXmlDocument.AppendGraphMLAttributeValue(
                twitterUser.VertexXmlNode, VertexToolTipID, toolTip);
        }
    }
        AddUserUserCommentsEdges
        (
            ref GraphMLXmlDocument oGraphMLXmlDocument,
            Dictionary<string, Dictionary<string, List<string>>> commentersComments,
            JSONObject streamPosts,
            Dictionary<string, Dictionary<string, JSONObject>> attributeValues
        )
        {
            Dictionary<string, List<string>> postCommenters = new Dictionary<string,List<string>>();
            XmlNode oEdgeXmlNode = null;
            List<string> postsIntersection = new List<string>();
            string posts = "";

            //Create the Post-Commenter relationship from commenterComments
            foreach(JSONObject item in streamPosts.Array)
            {
                foreach (KeyValuePair<string, Dictionary<string, List<string>>> kvp in commentersComments)
                {
                    if (kvp.Value.ContainsKey(item.Dictionary["post_id"].String))
                    {
                        try
                        {
                            postCommenters[item.Dictionary["post_id"].String].Add(kvp.Key);
                            
                        }
                        catch (KeyNotFoundException e)
                        {
                            List<string> tmp = new List<string>();
                            tmp.Add(kvp.Key);
                            postCommenters.Add(item.Dictionary["post_id"].String, tmp);
                        }                        
                    }
                }
            }

            //Add Edges
            foreach (KeyValuePair<string, List<string>> kvp in postCommenters)
            {
                for (int i = 0; i < kvp.Value.Count - 1; i++)
                {
                    for (int j = i + 1; j < kvp.Value.Count; j++)
                    {
                        //Sometimes a commenter or liker can be another fan page and this is not a user
                        if (!usersDisplayName.ContainsKey(kvp.Value[i]) ||
                            !usersDisplayName.ContainsKey(kvp.Value[j])) continue;
                        
                        oEdgeXmlNode = oGraphMLXmlDocument.AppendEdgeXmlNode(usersDisplayName[kvp.Value[i]],
                                                                            usersDisplayName[kvp.Value[j]]);
                       
                        postsIntersection = commentersComments[kvp.Value[i]].Keys.Intersect(commentersComments[kvp.Value[j]].Keys).ToList();
                        oGraphMLXmlDocument.AppendGraphMLAttributeValue(oEdgeXmlNode, "e_weight", postsIntersection.Count);
                        string[] postsToJoin = (from p in postsIntersection
                                                        select new { Post = streamPosts.Array.Single(x => x.Dictionary["post_id"].String.Equals(p)).Dictionary["message"].String }).Select(x => x.Post).ToArray();
                        posts = String.Join("\n\n", postsToJoin);
                        oGraphMLXmlDocument.AppendGraphMLAttributeValue(oEdgeXmlNode, "post", posts);
                        oGraphMLXmlDocument.AppendGraphMLAttributeValue(oEdgeXmlNode, "Relationship", "Co-Commenter");                        
                    }
                }
            }                                                           
        }
        AppendVertexTooltipXmlNodes
        (
            GraphMLXmlDocument oGraphMLXmlDocument,
            XmlNode oVertexXmlNode,
            String sVertex,
            String sDisplayString
        )
        {
            // The NodeXL template doesn't wrap long tooltip text.  Break the
            // status into lines so the entire tooltip will show in the graph
            // pane.

            sDisplayString = StringUtil.BreakIntoLines(sDisplayString, 30);

            String sTooltip = String.Format(
                "{0}\n\n{1}"
                ,
                sVertex,
                sDisplayString
                );

            oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                oVertexXmlNode, TooltipID, sTooltip);           

        }              
        AddPostVertices
        (
            ref GraphMLXmlDocument oGraphMLXmlDocument,
            JSONObject streamPosts
        )
        {
            XmlNode oVertexXmlNode;

            //Add nodes (posts)
            foreach (JSONObject ob in streamPosts.Array)
            {
                oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode(ob.Dictionary["post_id"].String);

                //Add tooltip
                AppendVertexTooltipXmlNodes(oGraphMLXmlDocument, oVertexXmlNode,
                    ob.Dictionary["post_id"].String, ob.Dictionary["message"].String);
                //Add attributes
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "type", "2");
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, LabelColumnName, ob.Dictionary["message"].String);
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "content", ob.Dictionary["message"].String);
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, MenuTextID,
                    "Open Facebook Page for This Post");
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, MenuActionID,
                    ob.Dictionary["permalink"].String);
                //If the post contains a link to a video, photo, document etc
                //and it has a picture, use it as the post image
                if(ob.Dictionary["attachment"].Dictionary.ContainsKey("media") &&
                    ob.Dictionary["attachment"].Dictionary["media"].Array.Length>0)
                {
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "Image",
                        ob.Dictionary["attachment"].Dictionary["media"].Array[0].Dictionary["src"].String);
                }
                
            }
        }
        AddLikerVertices
        (
            ref GraphMLXmlDocument oGraphMLXmlDocument,
            Dictionary<string, List<string>> likersPost,
            Dictionary<string, Dictionary<string, JSONObject>> attributeValues,            
            Dictionary<string, List<Dictionary<string, object>>> statusUpdates,
            Dictionary<string, List<Dictionary<string, object>>> wallPosts
        )
        {
            XmlNode oVertexXmlNode;
            likersWithNoAttributes = new Dictionary<string, string>();

            //Add nodes
            foreach (var item in likersPost)
            {
                if (!attributeValues.ContainsKey(item.Key))
                {
                    //This means that the liker is not a user but a page
                    //FacebookAPI fb = new FacebookAPI();
                    JSONObject pageObject = CallGraphAPIWithRelogin("/" + item.Key);              
                    if (!pageObject.IsDictionary) continue;
                    string vertexName = ManageDisplayNames(pageObject.Dictionary["name"].String, item.Key);
                    if (String.IsNullOrEmpty(vertexName)) continue;
                    oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode(vertexName);

                    //Add tooltip
                    AppendVertexTooltipXmlNodes(oGraphMLXmlDocument, oVertexXmlNode,
                        pageObject.Dictionary["name"].String, "");                    
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "type", "1");

                    if (pageObject.Dictionary.ContainsKey("picture"))
                    {
                        oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "Image", pageObject.Dictionary["picture"].String);
                    }

                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "v_weight", item.Value.Count);
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, MenuTextID,
                        "Open Facebook Page for This User");
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, MenuActionID,
                        FacebookURL + item.Key);

                    //Add this user to the appropriate "no attributes" dictionary
                    //likersWithNoAttributes.Add(item.Key, pageObject.Dictionary["name"].String);
                }
                else
                {
                    oVertexXmlNode = oGraphMLXmlDocument.AppendVertexXmlNode(usersDisplayName[item.Key]);
                    //Add tooltip
                    AppendVertexTooltipXmlNodes(oGraphMLXmlDocument, oVertexXmlNode,
                        usersDisplayName[item.Key], "");
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "type", "1");

                    //Add attributes for the actual user
                    AddAttributes(ref oGraphMLXmlDocument,
                                    ref oVertexXmlNode,
                                    attributeValues[item.Key]
                                    );

                    if (attributeValues[item.Key].ContainsKey("pic_small") && attributeValues[item.Key]["pic_small"] != null)
                    {
                        oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "Image", attributeValues[item.Key]["pic_small"].String);
                    }

                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "v_weight", item.Value.Count);
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, MenuTextID,
                        "Open Facebook Page for This User");
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, MenuActionID,
                        FacebookURL + item.Key);
                }

                if (bGetStatusUpdates)
                {
                    AddStatusUpdates(ref oGraphMLXmlDocument,
                                    ref oVertexXmlNode,
                                    item.Key,
                                    statusUpdates
                                    );
                }

                if (bGetWallPosts)
                {
                    AddWallPosts(ref oGraphMLXmlDocument,
                                ref oVertexXmlNode,
                                item.Key,
                                wallPosts
                                );
                }                
            }
        }
        AddWallPosts
        (
            ref GraphMLXmlDocument oGraphMLXmlDocument,
            ref XmlNode oVertexXmlNode,
            string userID,
            Dictionary<string, List<Dictionary<string, object>>> wallPosts
        )
        {
            List<Dictionary<string, object>>.Enumerator en;            
            string currentWallPost = "";
            string currentWallTags = "";

            //Add wall posts
            if (wallPosts.ContainsKey(userID))
            {
                en = wallPosts[userID].GetEnumerator();
                currentWallPost = "";
                currentWallTags = "";
                while (en.MoveNext() && currentWallPost.Length < 8000)
                {                    
                    if (((JSONObject)en.Current["message"]).String.Equals("")) continue;
                    currentWallPost += (en.Current.ContainsKey("name")?((JSONObject)en.Current["name"]).String:"[No Name]") + ": " + ((JSONObject)en.Current["message"]).String + "\n\n";
                    //Add tags
                    if (en.Current["message_tags"] is List<JSONObject>)
                    {
                        foreach (JSONObject ob in (List<JSONObject>)en.Current["message_tags"])
                        {
                            currentWallTags += ob.String + ",";
                        }
                        currentWallTags = currentWallTags.Remove(currentWallTags.Length - 1);
                        currentWallTags += "\n\n";
                    }
                }

                oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "wall_posts", currentWallPost);
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "wall_tags", currentWallTags);
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "wall_urls", GetURLs(currentWallPost, ' '));
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "wall_hashtags", GetHashtags(currentWallPost, ' '));
            }
        }
    AppendLatitudeAndLongitudeGraphMLAttributeValues
    (
        GraphMLXmlDocument graphMLXmlDocument,
        XmlNode edgeOrVertexXmlNode,
        String latitude,
        String longitude
    )
    {
        Debug.Assert(graphMLXmlDocument != null);
        Debug.Assert(edgeOrVertexXmlNode != null);

        if ( !String.IsNullOrEmpty(latitude) )
        {
            graphMLXmlDocument.AppendGraphMLAttributeValue(
                edgeOrVertexXmlNode, LatitudeID, latitude);
        }

        if ( !String.IsNullOrEmpty(longitude) )
        {
            graphMLXmlDocument.AppendGraphMLAttributeValue(
                edgeOrVertexXmlNode, LongitudeID, longitude);
        }
    }
    AppendTweetedSearchTermGraphMLAttributeValue
    (
        GraphMLXmlDocument graphMLXmlDocument,
        TwitterUser twitterUser,
        Boolean userTweetedSearchTerm
    )
    {
        Debug.Assert(graphMLXmlDocument != null);
        Debug.Assert(twitterUser != null);

        graphMLXmlDocument.AppendGraphMLAttributeValue(
            twitterUser.VertexXmlNode,
            VertexTweetedSearchTermID,
            userTweetedSearchTerm ? "Yes" : "No");
    }
    AppendInReplyToStatusIDGraphMLAttributeValue
    (
        GraphMLXmlDocument graphMLXmlDocument,
        XmlNode edgeOrVertexXmlNode,
        String inReplyToStatusID
    )
    {
        Debug.Assert(graphMLXmlDocument != null);
        Debug.Assert(edgeOrVertexXmlNode != null);

        if ( !String.IsNullOrEmpty(inReplyToStatusID) )
        {
            // Precede the ID with a single quote to force Excel to treat the
            // ID as text.  Otherwise, it formats the ID, which is a large
            // number, in scientific notation.

            graphMLXmlDocument.AppendGraphMLAttributeValue(
                edgeOrVertexXmlNode, InReplyToStatusIDID,
                "'" + inReplyToStatusID);
        }
    }
    AppendUserInformationGraphMLAttributeValues
    (
        String sUserID,
        XmlNode oVertexXmlNode,
        GraphMLXmlDocument oGraphMLXmlDocument,
        String sApiKey,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sUserID) );
        Debug.Assert(oVertexXmlNode != null);
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert( !String.IsNullOrEmpty(sApiKey) );
        Debug.Assert(oRequestStatistics != null);
        AssertValid();

        String sUrl = GetFlickrMethodUrl( "flickr.people.getInfo",
            sApiKey, GetUserIDUrlParameter(sUserID) );

        XmlDocument oXmlDocument;

        if ( !TryGetXmlDocument(sUrl, oRequestStatistics, out oXmlDocument) )
        {
            // Ignore errors.  The user information isn't critical.

            return;
        }

        const String XPathRoot = "rsp/person/";

        AppendStringGraphMLAttributeValue(oXmlDocument,
            XPathRoot + "realname/text()", null, oGraphMLXmlDocument,
            oVertexXmlNode, RealNameID);

        AppendInt32GraphMLAttributeValue(oXmlDocument,
            XPathRoot + "photos/count/text()", null, oGraphMLXmlDocument,
            oVertexXmlNode, TotalPhotosID);

        String sIsProfessional;

        if ( XmlUtil2.TrySelectSingleNodeAsString(oXmlDocument,
            XPathRoot + "@ispro", null, out sIsProfessional) )
        {
            oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode,
                IsProfessionalID,
                (sIsProfessional == "0") ? "No" : "Yes"
                );
        }

        // Get the URL to the user's buddy icon, which is explained here:
        //
        // http://www.flickr.com/services/api/misc.buddyicons.html

        Int32 iIconServer, iIconFarm;
        String sBuddyIconUrl;

        if (
            XmlUtil2.TrySelectSingleNodeAsInt32(oXmlDocument,
                XPathRoot + "@iconserver", null, out iIconServer)
            &&
            XmlUtil2.TrySelectSingleNodeAsInt32(oXmlDocument,
                XPathRoot + "@iconfarm", null, out iIconFarm)
            &&
            iIconServer > 0
            )
        {
            sBuddyIconUrl = String.Format(

                "http://farm{0}.static.flickr.com/{1}/buddyicons/{2}.jpg"
                ,
                iIconFarm,
                iIconServer,
                sUserID
                );
        }
        else
        {
            sBuddyIconUrl = "http://www.flickr.com/images/buddyicon.jpg";
        }

        oGraphMLXmlDocument.AppendGraphMLAttributeValue(
            oVertexXmlNode, NodeXLGraphMLUtil.VertexImageFileID,
            sBuddyIconUrl);

        if ( AppendStringGraphMLAttributeValue(oXmlDocument,
            XPathRoot + "photosurl/text()", null, oGraphMLXmlDocument,
            oVertexXmlNode, NodeXLGraphMLUtil.VertexMenuActionID))
        {
            oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode,
                NodeXLGraphMLUtil.VertexMenuTextID,
                "Open Flickr Page for This Person");
        }
    }
    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,
                NodeXLGraphMLUtil.VertexLabelID, sTag);

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

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

            oTagDictionary.Add(sTag, oVertexXmlNode);
        }
    }
    GetUserNetworkRecursive
    (
        String sUserID,
        String sScreenName,
        WhatToInclude eWhatToInclude,
        Boolean bIncludeContactsThisCall,
        NetworkLevel eNetworkLevel,
        Int32 iMaximumPerRequest,
        String sApiKey,
        Int32 iRecursionLevel,
        GraphMLXmlDocument oGraphMLXmlDocument,
        Dictionary<String, XmlNode> oUserIDDictionary,
        RequestStatistics oRequestStatistics
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(sUserID) );
        Debug.Assert( !String.IsNullOrEmpty(sScreenName) );

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

        Debug.Assert(iMaximumPerRequest > 0);
        Debug.Assert( !String.IsNullOrEmpty(sApiKey) );
        Debug.Assert(iRecursionLevel == 1 || iRecursionLevel == 2);
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert(oUserIDDictionary != 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);

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

        ReportProgressForContactsOrCommenters(sScreenName,
            bIncludeContactsThisCall);

        Boolean bThisUserAppended = false;

        foreach ( XmlNode oChildXmlNode in GetContactsOrCommentersEnumerator(
            sUserID, bIncludeContactsThisCall, iMaximumPerRequest,
            oGraphMLXmlDocument, sApiKey, oRequestStatistics) )
        {
            String sOtherScreenName, sOtherUserID;

            if (
                !XmlUtil2.TrySelectSingleNodeAsString(oChildXmlNode,
                    bIncludeContactsThisCall ? "@username" : "@authorname",
                    null, out sOtherScreenName)
                ||
                !XmlUtil2.TrySelectSingleNodeAsString(oChildXmlNode,
                    bIncludeContactsThisCall ? "@nsid" : "@author",
                    null, out sOtherUserID)
                )
            {
                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(sUserID, sScreenName,
                    oGraphMLXmlDocument, oUserIDDictionary);

                bThisUserAppended = true;
            }

            if (bNeedToAppendVertices)
            {
                if (
                    TryAppendVertexXmlNode(sOtherUserID, sOtherScreenName,
                        oGraphMLXmlDocument, oUserIDDictionary)
                    &&
                    bNeedToRecurse
                    )
                {
                    oUserIDsToRecurse.Add(sOtherUserID);
                }
            }

            if ( bNeedToAppendVertices ||
                oUserIDDictionary.ContainsKey(sOtherUserID) )
            {
                // Append an edge node and optional attributes.

                XmlNode oEdgeXmlNode;

                if (bIncludeContactsThisCall)
                {
                    oEdgeXmlNode = NodeXLGraphMLUtil.AppendEdgeXmlNode(
                        oGraphMLXmlDocument, sScreenName, sOtherScreenName,
                        "Contact");
                }
                else
                {
                    // (Note the swapping of screen names in the commenter
                    // case.)

                    oEdgeXmlNode = NodeXLGraphMLUtil.AppendEdgeXmlNode(
                        oGraphMLXmlDocument, sOtherScreenName, sScreenName,
                        "Commenter");

                    UInt32 uCommentDateUtc;

                    if ( XmlUtil2.TrySelectSingleNodeAsUInt32(oChildXmlNode,
                            "@datecreate", null, out uCommentDateUtc) )
                    {
                        DateTime oCommentDateUtc =
                            DateTimeUtil2.UnixTimestampToDateTimeUtc(
                                uCommentDateUtc);

                        oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                            oEdgeXmlNode, CommentDateUtcID,

                            ExcelDateTimeUtil.DateTimeToStringLocale1033(
                                oCommentDateUtc, ExcelColumnFormat.DateAndTime)
                            );
                    }

                    AppendStringGraphMLAttributeValue(oChildXmlNode,
                        "@permalink", null, oGraphMLXmlDocument, oEdgeXmlNode,
                        CommentUrlID);
                }
            }
        }

        if (bNeedToRecurse)
        {
            foreach (String sUserIDToRecurse in oUserIDsToRecurse)
            {
                XmlNode oVertexXmlNode = oUserIDDictionary[sUserIDToRecurse];

                String sScreenNameToRecurse = GetScreenNameFromVertexXmlNode(
                    oVertexXmlNode);

                GetUserNetworkRecursive(sUserIDToRecurse, sScreenNameToRecurse,
                    eWhatToInclude, bIncludeContactsThisCall, eNetworkLevel,
                    iMaximumPerRequest, sApiKey, 2, oGraphMLXmlDocument,
                    oUserIDDictionary, oRequestStatistics);
            }
        }
    }
    AppendLatestStatusInformationFromValueDictionary
    (
        Dictionary<String, Object> oStatusValueDictionary,
        GraphMLXmlDocument oGraphMLXmlDocument,
        TwitterUser oTwitterUser,
        Boolean bIncludeLatestStatus,
        Boolean bExpandLatestStatusUrls
    )
    {
        Debug.Assert(oStatusValueDictionary != null);
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert(oTwitterUser != null);
        AssertValid();

        TwitterStatus oTwitterStatus;

        if ( !TwitterStatus.TryFromStatusValueDictionary(
            oStatusValueDictionary, bExpandLatestStatusUrls,
            out oTwitterStatus) )
        {
            return;
        }

        XmlNode oVertexXmlNode = oTwitterUser.VertexXmlNode;

        if (bIncludeLatestStatus)
        {
            // Add the status to the vertex XML node.

            oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                oVertexXmlNode, TwitterGraphMLUtil.VertexLatestStatusID,
                oTwitterStatus.Text);

            String sLatestStatusUrls = oTwitterStatus.Urls;

            if ( !String.IsNullOrEmpty(sLatestStatusUrls) )
            {
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                    oVertexXmlNode,
                    TwitterGraphMLUtil.VertexLatestStatusUrlsID,
                    sLatestStatusUrls);

                oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                    oVertexXmlNode,
                    TwitterGraphMLUtil.VertexLatestStatusDomainsID,
                    TwitterGraphMLUtil.UrlsToDomains(sLatestStatusUrls) ); 
            }

            String sLatestStatusHashtags = oTwitterStatus.Hashtags;

            if ( !String.IsNullOrEmpty(sLatestStatusHashtags) )
            {
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                    oVertexXmlNode,
                    TwitterGraphMLUtil.VertexLatestStatusHashtagsID,
                    sLatestStatusHashtags);
            }

            if ( !String.IsNullOrEmpty(oTwitterStatus.ParsedDateUtc) )
            {
                oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                    oVertexXmlNode,
                    TwitterGraphMLUtil.VertexLatestStatusDateUtcID,
                    oTwitterStatus.ParsedDateUtc);
            }

            TwitterGraphMLUtil.
                AppendLatitudeAndLongitudeGraphMLAttributeValues(
                    oGraphMLXmlDocument, oVertexXmlNode,
                    oTwitterStatus.Latitude, oTwitterStatus.Longitude);

            TwitterGraphMLUtil.AppendInReplyToStatusIDGraphMLAttributeValue(
                oGraphMLXmlDocument, oVertexXmlNode,
                oTwitterStatus.InReplyToStatusID);
        }

        oTwitterUser.Statuses.Add(oTwitterStatus);
    }
    AppendRepliesToAndMentionsEdgeXmlNode
    (
        GraphMLXmlDocument graphMLXmlDocument,
        String screenName1,
        String screenName2,
        String relationship,
        TwitterStatus twitterStatus,
        Boolean includeStatus
    )
    {
        Debug.Assert(graphMLXmlDocument != null);
        Debug.Assert( !String.IsNullOrEmpty(screenName1) );
        Debug.Assert( !String.IsNullOrEmpty(screenName2) );
        Debug.Assert( !String.IsNullOrEmpty(relationship) );
        Debug.Assert(twitterStatus != null);

        XmlNode edgeXmlNode = NodeXLGraphMLUtil.AppendEdgeXmlNode(
            graphMLXmlDocument, screenName1, screenName2, relationship);

        String statusDateUtc = twitterStatus.ParsedDateUtc;
        Boolean hasStatusDateUtc = !String.IsNullOrEmpty(statusDateUtc);

        if (hasStatusDateUtc)
        {
            // The status's date is the relationship date.

            graphMLXmlDocument.AppendGraphMLAttributeValue(edgeXmlNode,
                EdgeRelationshipDateUtcID, statusDateUtc);
        }

        if (includeStatus)
        {
            String statusText = twitterStatus.Text;

            graphMLXmlDocument.AppendGraphMLAttributeValue(edgeXmlNode,
                EdgeStatusID, statusText);

            String urls = twitterStatus.Urls;

            if ( !String.IsNullOrEmpty(urls) )
            {
                graphMLXmlDocument.AppendGraphMLAttributeValue(edgeXmlNode,
                    EdgeStatusUrlsID, urls);

                graphMLXmlDocument.AppendGraphMLAttributeValue( edgeXmlNode,
                    EdgeStatusDomainsID, UrlsToDomains(urls) );
            }

            if ( !String.IsNullOrEmpty(twitterStatus.Hashtags) )
            {
                graphMLXmlDocument.AppendGraphMLAttributeValue(edgeXmlNode,
                    EdgeStatusHashtagsID, twitterStatus.Hashtags);
            }

            if (hasStatusDateUtc)
            {
                graphMLXmlDocument.AppendGraphMLAttributeValue(edgeXmlNode,
                    EdgeStatusDateUtcID, statusDateUtc);
            }

            graphMLXmlDocument.AppendGraphMLAttributeValue(edgeXmlNode,
                EdgeStatusWebPageUrlID, 

                FormatStatusWebPageUrl(screenName1, twitterStatus)
                );

            AppendLatitudeAndLongitudeGraphMLAttributeValues(
                graphMLXmlDocument, edgeXmlNode, twitterStatus.Latitude,
                twitterStatus.Longitude);

            // Precede the ID with a single quote to force Excel to treat the
            // ID as text.  Otherwise, it formats the ID, which is a large
            // number, in scientific notation.

            graphMLXmlDocument.AppendGraphMLAttributeValue(edgeXmlNode,
                NodeXLGraphMLUtil.ImportedIDID, "'" + twitterStatus.ID);

            AppendInReplyToStatusIDGraphMLAttributeValue(graphMLXmlDocument,
                edgeXmlNode, twitterStatus.InReplyToStatusID);
        }
    }
    AppendDoubleGraphMLAttributeValue
    (
        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();

        Double dAttributeValue;

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

            return (true);
        }

        return (false);
    }
    TryAppendVertexXmlNode
    (
        String screenName,
        String userID,
        GraphMLXmlDocument graphMLXmlDocument,
        Dictionary<String, TwitterUser> userIDDictionary,
        out TwitterUser twitterUser
    )
    {
        Debug.Assert( !String.IsNullOrEmpty(screenName) );
        Debug.Assert( !String.IsNullOrEmpty(userID) );
        Debug.Assert(graphMLXmlDocument != null);
        Debug.Assert(userIDDictionary != null);

        twitterUser = null;

        if ( userIDDictionary.TryGetValue(userID, out twitterUser) )
        {
            // A vertex XML node already exists.

            return (false);
        }

        XmlNode vertexXmlNode = graphMLXmlDocument.AppendVertexXmlNode(
            screenName);

        twitterUser = new TwitterUser(screenName, vertexXmlNode);
        userIDDictionary.Add(userID, twitterUser);

        graphMLXmlDocument.AppendGraphMLAttributeValue(vertexXmlNode,
            NodeXLGraphMLUtil.VertexMenuTextID,
            "Open Twitter Page for This Person");

        graphMLXmlDocument.AppendGraphMLAttributeValue(
            vertexXmlNode,
            NodeXLGraphMLUtil.VertexMenuActionID,
            String.Format(TwitterApiUrls.UserWebPageUrlPattern, screenName)
            );

        return (true);
    }
    AppendGraphMLAttributeValues
    (
        IMetadataProvider oEdgeOrVertex,
        GraphMLXmlDocument oGraphMLXmlDocument,
        XmlNode oEdgeOrVertexXmlNode,
        String [] asAttributeNames,
        String AttributeIDPrefix
    )
    {
        Debug.Assert(oEdgeOrVertex != null);
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert(oEdgeOrVertexXmlNode != null);
        Debug.Assert(asAttributeNames != null);
        Debug.Assert( !String.IsNullOrEmpty(AttributeIDPrefix) );
        AssertValid();

        foreach (String sAttributeName in asAttributeNames)
        {
            Object oAttributeValue;

            // Note that the value type isn't checked.  Whatever type it is,
            // it gets converted to a string.

            if (oEdgeOrVertex.TryGetValue(sAttributeName,
                out oAttributeValue) && oAttributeValue != null)
            {
                oGraphMLXmlDocument.AppendGraphMLAttributeValue( 
                    oEdgeOrVertexXmlNode, AttributeIDPrefix + sAttributeName,
                    oAttributeValue.ToString() );
            }
        }
    }
    AppendUserStatisticsFromValueDictionary
    (
        Dictionary<String, Object> userValueDictionary,
        GraphMLXmlDocument graphMLXmlDocument,
        TwitterUser twitterUser
    )
    {
        Debug.Assert(userValueDictionary != null);
        Debug.Assert(graphMLXmlDocument != null);
        Debug.Assert(twitterUser != null);

        XmlNode vertexXmlNode = twitterUser.VertexXmlNode;

        AppendValueFromValueDictionary(userValueDictionary,
            "friends_count", graphMLXmlDocument, vertexXmlNode,
            VertexFollowedID);

        AppendValueFromValueDictionary(userValueDictionary,
            "followers_count", graphMLXmlDocument, vertexXmlNode,
            VertexFollowersID);

        AppendValueFromValueDictionary(userValueDictionary,
            "statuses_count", graphMLXmlDocument, vertexXmlNode,
            VertexStatusesID);

        AppendValueFromValueDictionary(userValueDictionary,
            "favourites_count", graphMLXmlDocument, vertexXmlNode,
            VertexFavoritesID);

        AppendValueFromValueDictionary(userValueDictionary,
            "description", graphMLXmlDocument, vertexXmlNode,
            VertexDescriptionID);

        AppendValueFromValueDictionary(userValueDictionary,
            "location", graphMLXmlDocument, vertexXmlNode,
            VertexLocationID);

        AppendValueFromValueDictionary(userValueDictionary,
            "url", graphMLXmlDocument, vertexXmlNode,
            VertexUrlID);

        AppendValueFromValueDictionary(userValueDictionary,
            "time_zone", graphMLXmlDocument, vertexXmlNode,
            VertexTimeZoneID);

        AppendValueFromValueDictionary(userValueDictionary,
            "utc_offset", graphMLXmlDocument, vertexXmlNode,
            VertexUtcOffsetID);

        String joinedDateUtc;

        if ( TwitterJsonUtil.TryGetJsonValueFromDictionary(userValueDictionary,
            "created_at", out joinedDateUtc) )
        {
            graphMLXmlDocument.AppendGraphMLAttributeValue(
                vertexXmlNode, VertexJoinedDateUtcID,
                TwitterDateParser.ParseTwitterDate(joinedDateUtc) );
        }
    }
    AppendEdgesFromDictionary
    (
        Dictionary< String, LinkedList<String> > oDictionary,
        GraphMLXmlDocument oGraphMLXmlDocument,
        String sRelationship,
        String sKeyAttributeID
    )
    {
        Debug.Assert(oDictionary != null);
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert( !String.IsNullOrEmpty(sRelationship) );
        Debug.Assert( !String.IsNullOrEmpty(sKeyAttributeID) );
        AssertValid();

        // For each key...

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

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

            LinkedList<String> oVideoIDsWithThisKey = oKeyValuePair.Value;

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

                oVideoIDWithThisKey != null;

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

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

                    oSubsequentVideoIDWithThisKey != null;

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

                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(
                        oEdgeXmlNode, sKeyAttributeID, sKey);
                }
            }
        }
    }
    AppendValueFromValueDictionary
    (
        Dictionary<String, Object> valueDictionary,
        String name,
        GraphMLXmlDocument graphMLXmlDocument,
        XmlNode edgeOrVertexXmlNode,
        String graphMLAttributeID
    )
    {
        Debug.Assert(valueDictionary != null);
        Debug.Assert( !String.IsNullOrEmpty(name) );
        Debug.Assert(graphMLXmlDocument != null);
        Debug.Assert(edgeOrVertexXmlNode != null);
        Debug.Assert( !String.IsNullOrEmpty(graphMLAttributeID) );

        String value;

        if ( TwitterJsonUtil.TryGetJsonValueFromDictionary(
            valueDictionary, name, out value) )
        {
            graphMLXmlDocument.AppendGraphMLAttributeValue(
                edgeOrVertexXmlNode, graphMLAttributeID, value);

            return (true);
        }

        return (false);
    }
    AppendYouTubeDateGraphMLAttributeValue
    (
        XmlNode oXmlNodeToSelectFrom,
        String sXPath,
        XmlNamespaceManager oXmlNamespaceManager,
        GraphMLXmlDocument oGraphMLXmlDocument,
        XmlNode oVertexXmlNode,
        String sGraphMLAttributeID
    )
    {
        Debug.Assert(oXmlNodeToSelectFrom != null);
        Debug.Assert( !String.IsNullOrEmpty(sXPath) );
        Debug.Assert(oGraphMLXmlDocument != null);
        Debug.Assert(oVertexXmlNode != null);
        Debug.Assert( !String.IsNullOrEmpty(sGraphMLAttributeID) );
        AssertValid();

        String sYouTubeDate;

        if ( XmlUtil2.TrySelectSingleNodeAsString(oXmlNodeToSelectFrom,
            sXPath, oXmlNamespaceManager, out sYouTubeDate) )
        {
            oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode,
                sGraphMLAttributeID, FormatYouTubeDate(sYouTubeDate) );

            return (true);
        }

        return (false);
    }
        AddAttributes
        (
            ref GraphMLXmlDocument oGraphMLXmlDocument,
            ref XmlNode oVertexXmlNode,
            Dictionary<string, JSONObject> attributeValues
        )
        {
            string attributeValue = "";

            foreach (KeyValuePair<string, JSONObject> kvp in attributeValues)
            {
                //Facebook's TOS doesn't allow the redistribution of UIDs
                //so they are not included in the network. 
                //They are used as keys for the dictionaries.
                if (kvp.Key.Equals("uid") || kvp.Key.Equals("display_name"))
                {
                    continue;
                }

                if (kvp.Value == null || (kvp.Value.String == null && !kvp.Value.IsDictionary))
                {
                    attributeValue = "";
                }
                else if (kvp.Key.Equals("hometown_location"))
                {

                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "hometown", kvp.Value.Dictionary.ContainsKey("name") ? kvp.Value.Dictionary["name"].String : "");
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "hometown_city", kvp.Value.Dictionary.ContainsKey("city") ? kvp.Value.Dictionary["city"].String : "");
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "hometown_state", kvp.Value.Dictionary.ContainsKey("state") ? kvp.Value.Dictionary["state"].String : "");
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "hometown_country", kvp.Value.Dictionary.ContainsKey("country") ? kvp.Value.Dictionary["country"].String : "");
                }
                else if (kvp.Key.Equals("current_location"))
                {
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "location", kvp.Value.Dictionary.ContainsKey("name") ? kvp.Value.Dictionary["name"].String : "");
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "location_city", kvp.Value.Dictionary.ContainsKey("city") ? kvp.Value.Dictionary["city"].String : "");
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "location_state", kvp.Value.Dictionary.ContainsKey("state") ? kvp.Value.Dictionary["state"].String : "");
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, "location_country", kvp.Value.Dictionary.ContainsKey("country") ? kvp.Value.Dictionary["country"].String : "");
                }                
                else
                {
                    if (kvp.Key.Equals("profile_update_time"))
                    {
                        attributeValue = DateUtil.ConvertToDateTime(kvp.Value.Integer).ToString();
                    }
                    else if (kvp.Value.String.Length > 8000)
                    {
                        attributeValue = kvp.Value.String.Remove(8000);
                    }
                    else
                    {
                        attributeValue = kvp.Value.String;
                    }
                    oGraphMLXmlDocument.AppendGraphMLAttributeValue(oVertexXmlNode, kvp.Key, attributeValue);
                }

            }
        }