//*************************************************************************
        //  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);
        }
        //*************************************************************************
        //  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: 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);
        }