InternalRelationshipCollection.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Base / MS / Internal / IO / Packaging / InternalRelationshipCollection.cs / 1305600 / InternalRelationshipCollection.cs

                            //------------------------------------------------------------------------------ 
//
// 
//    Copyright (C) Microsoft Corporation.  All rights reserved.
//  
//
// Description: 
//  This is a class for representing a PackageRelationshipCollection. This is an internal 
//  class for manipulating relationships associated with a part
// 
// Details:
//   This class handles serialization to/from relationship parts, creation of those parts
//   and offers methods to create, delete and enumerate relationships. This code was
//   moved from the PackageRelationshipCollection class. 
//
// History: 
//  04/26/2004: SarjanaS: This code was moved from the PackageRelationshipCollection class. 
//
//----------------------------------------------------------------------------- 

using System;
using System.Collections;
using System.Collections.Generic; 
using System.Globalization;
using System.Xml;                           // for XmlReader/Writer 
using System.IO.Packaging; 
using System.Windows;                       // For Exception strings - SRID
using System.IO; 
using System.Diagnostics;
using System.Windows.Markup;                // For XMLCompatibilityReader

using MS.Internal;                          // For Invariant. 
using MS.Internal.WindowsBase;
 
namespace MS.Internal.IO.Packaging 
{
    ///  
    /// Collection of all the relationships corresponding to a given source PackagePart
    /// 
    internal class InternalRelationshipCollection : IEnumerable
    { 
        //-----------------------------------------------------
        // 
        //  Public Methods 
        //
        //----------------------------------------------------- 
        #region IEnumerable
        /// 
        /// Returns an enumertor over all the relationships for a Package or a PackagePart
        ///  
        /// 
        IEnumerator IEnumerable.GetEnumerator() 
        { 
            return _relationships.GetEnumerator();
        } 

        /// 
        /// Returns an enumertor over all the relationships for a Package or a PackagePart
        ///  
        /// 
        IEnumerator IEnumerable.GetEnumerator() 
        { 
            return _relationships.GetEnumerator();
        } 

        /// 
        /// Returns an enumertor over all the relationships for a Package or a PackagePart
        ///  
        /// 
        public List.Enumerator GetEnumerator() 
        { 
            return _relationships.GetEnumerator();
        } 

        #endregion

        //------------------------------------------------------ 
        //
        //  Internal Methods 
        // 
        //-----------------------------------------------------
        #region Internal Methods 
        /// 
        /// Constructor
        /// 
        /// For use by PackagePart 
        internal InternalRelationshipCollection(PackagePart part): this(part.Package, part)
        { 
        } 

        ///  
        /// Constructor
        /// 
        /// For use by Package
        internal InternalRelationshipCollection(Package package): this(package, null) 
        {
        } 
 
        /// 
        /// Add new relationship 
        /// 
        /// target
        /// Enumeration indicating the base uri for the target uri
        /// relationship type that uniquely defines the role of the relationship 
        /// String that conforms to the xsd:ID datatype. Unique across the source's relationships.
        /// Null OK (ID will be generated). 
        internal PackageRelationship Add(Uri targetUri, TargetMode targetMode, string relationshipType, string id) 
        {
            return Add(targetUri, targetMode, relationshipType, id, false /*not parsing*/); 
        }

        /// 
        /// Return the relationship whose id is 'id', and null if not found. 
        /// 
        internal PackageRelationship GetRelationship(string id) 
        { 
            Invariant.Assert(!_package.InStreamingCreation);
            int index = GetRelationshipIndex(id); 
            if (index == -1)
                return null;
            return _relationships[index];
        } 

        ///  
        /// Delete relationship with ID 'id' 
        /// 
        /// ID of the relationship to remove 
        internal void Delete(String id)
        {
            Invariant.Assert(!_package.InStreamingCreation);
            int index = GetRelationshipIndex(id); 
            if (index == -1)
                return; 
 
            _relationships.RemoveAt(index);
            _dirty = true; 
        }

        /// 
        /// Clear all the relationships in this collection 
        /// Today it is only used when the entire relationship part is being deleted
        ///  
        internal void Clear() 
        {
            Invariant.Assert(!_package.InStreamingCreation); 
            _relationships.Clear();
            _dirty = true;
        }
 
        /// 
        /// Flush to stream (destructive) 
        ///  
        /// 
        /// Based on the streaming mode of the package, flush piece or flush part. 
        /// 
        internal void Flush()
        {
            if (!_dirty) 
                return;
 
            if (_package.InStreamingCreation) 
            {
                FlushRelationshipsToPiece(false /* not a terminal piece */); 
            }
            else
            {
                if (_relationships.Count == 0)  // empty? 
                {
                    // delete the part 
                    if (_package.PartExists(_uri)) 
                    {
                        _package.DeletePart(_uri); 
                    }
                    _relationshipPart = null;
                }
                else 
                {
                    EnsureRelationshipPart();   // lazy init 
 
                    // write xml
                    WriteRelationshipPart(_relationshipPart); 
                }
            }
            _dirty = false;
        } 

        ///  
        /// Exclusively used for streaming production. Any relationships remaining to flush 
        /// are flushed to a terminal piece.
        /// If relationships have been created and have all been flushed already, a terminal piece 
        /// is created to complete the Xml document.
        /// 
        internal void CloseInStreamingCreationMode()
        { 
            Debug.Assert(_package.InStreamingCreation, "This method should only be called in streaming creation mode");
            FlushRelationshipsToPiece(true /* last piece */); 
        } 

        internal static void ThrowIfInvalidRelationshipType(string relationshipType) 
        {
            // Look for empty string or string with just spaces
            if (relationshipType.Trim() == String.Empty)
                throw new ArgumentException(SR.Get(SRID.InvalidRelationshipType)); 
        }
 
        // If 'id' is not of the xsd type ID, throw an exception. 
        internal static void ThrowIfInvalidXsdId(string id)
        { 
            Invariant.Assert(id != null, "id should not be null");

            try
            { 
                // An XSD ID is an NCName that is unique.
                XmlConvert.VerifyNCName(id); 
            } 
            catch (XmlException exception)
            { 
                throw new XmlException(SR.Get(SRID.NotAValidXmlIdString, id), exception);
            }
        }
 
        #endregion Internal Methods
 
        //------------------------------------------------------ 
        //
        //  Private Methods 
        //
        //------------------------------------------------------
        #region Private Methods
        ///  
        /// Constructor
        ///  
        /// package 
        /// part will be null if package is the source of the relationships
        /// Shared constructor 
        private InternalRelationshipCollection(Package package, PackagePart part)
        {
            Debug.Assert(package != null, "package parameter passed should never be null");
 
            _package = package;
            _sourcePart = part; 
 
            //_sourcePart may be null representing that the relationships are at the package level
            _uri = GetRelationshipPartUri(_sourcePart); 
            _relationships = new List(4);

            // Load if available (not applicable to write-only mode).
            if (package.FileOpenAccess != FileAccess.Write && package.PartExists(_uri)) 
            {
                _relationshipPart = package.GetPart(_uri); 
                ThrowIfIncorrectContentType(_relationshipPart.ValidatedContentType); 
                ParseRelationshipPart(_relationshipPart);
            } 

            //Any initialization in the constructor should not set the dirty flag to true.
            _dirty = false;
 
        }
 
        ///  
        /// Returns the associated RelationshipPart for this part
        ///  
        /// may be null
        /// name of relationship part for the given part
        private static Uri GetRelationshipPartUri(PackagePart part)
        { 
            Uri sourceUri;
 
            if (part == null) 
                sourceUri = PackUriHelper.PackageRootUri;
            else 
                sourceUri = part.Uri;

            return PackUriHelper.GetRelationshipPartUri(sourceUri);
        } 

        ///  
        /// Parse PackageRelationship Stream 
        /// 
        /// relationship part 
        /// Thrown if XML is malformed
        private void ParseRelationshipPart(PackagePart part)
        {
            //We can safely open the stream as FileAccess.Read, as this code 
            //should only be invoked if the Package has been opened in Read or ReadWrite mode.
            Debug.Assert(_package.FileOpenAccess == FileAccess.Read || _package.FileOpenAccess == FileAccess.ReadWrite, 
                "This method should only be called when FileAccess is Read or ReadWrite"); 

            using (Stream s = part.GetStream(FileMode.Open, FileAccess.Read)) 
            {
                // load from the relationship part associated with the given part
                using (XmlTextReader baseReader = new XmlTextReader(s))
                { 
                    baseReader.WhitespaceHandling = WhitespaceHandling.None;
 
                    //Prohibit DTD from the markup as per the OPC spec 
                    baseReader.ProhibitDtd = true;
 
                    using (XmlCompatibilityReader reader = new XmlCompatibilityReader(baseReader, RelationshipKnownNamespaces))
                    {
                        //This method expects the reader to be in ReadState.Initial.
                        //It will make the first read call. 
                        PackagingUtilities.PerformInitailReadAndVerifyEncoding(baseReader);
 
                        //Note: After the previous method call the reader should be at the first tag in the markup. 
                        //MoveToContent - Skips over the following - ProcessingInstruction, DocumentType, Comment, Whitespace, or SignificantWhitespace
                        //If the reader is currently at a content node then this function call is a no-op 
                        reader.MoveToContent();

                        // look for our tag and namespace pair - throw if other elements are encountered
                        // Make sure that the current node read is an Element 
                        if (reader.NodeType == XmlNodeType.Element
                            && (reader.Depth == 0) 
                            && (String.CompareOrdinal(RelationshipsTagName, reader.LocalName) == 0) 
                            && (String.CompareOrdinal(PackagingUtilities.RelationshipNamespaceUri, reader.NamespaceURI) == 0))
                        { 
                            ThrowIfXmlBaseAttributeIsPresent(reader);

                            //There should be a namespace Attribute present at this level.
                            //Also any other attribute on the  tag is an error including xml: and xsi: attributes 
                            if (PackagingUtilities.GetNonXmlnsAttributeCount(reader) > 0)
                                throw new XmlException(SR.Get(SRID.RelationshipsTagHasExtraAttributes), null, reader.LineNumber, reader.LinePosition); 
 
                            // start tag encountered for Relationships
                            // now parse individual Relationship tags 
                            while (reader.Read())
                            {
                                //Skips over the following - ProcessingInstruction, DocumentType, Comment, Whitespace, or SignificantWhitespace
                                //If the reader is currently at a content node then this function call is a no-op 
                                reader.MoveToContent();
 
                                //If MoveToContent() takes us to the end of the content 
                                if (reader.NodeType == XmlNodeType.None)
                                    continue; 

                                if (reader.NodeType == XmlNodeType.Element
                                    && (reader.Depth == 1)
                                    && (String.CompareOrdinal(RelationshipTagName, reader.LocalName) == 0) 
                                    && (String.CompareOrdinal(PackagingUtilities.RelationshipNamespaceUri, reader.NamespaceURI) == 0))
                                { 
                                    ThrowIfXmlBaseAttributeIsPresent(reader); 

                                    int expectedAttributesCount = 3; 

                                    string targetModeAttributeValue = reader.GetAttribute(TargetModeAttributeName);
                                    if (targetModeAttributeValue != null)
                                        expectedAttributesCount++; 

                                    //check if there are expected number of attributes. 
                                    //Also any other attribute on the  tag is an error including xml: and xsi: attributes 
                                    if (PackagingUtilities.GetNonXmlnsAttributeCount(reader) == expectedAttributesCount)
                                    { 
                                        ProcessRelationshipAttributes(reader);

                                        //Skip the EndElement for Relationship
                                        if (!reader.IsEmptyElement) 
                                            ProcessEndElementForRelationshipTag(reader);
                                    } 
                                    else throw new XmlException(SR.Get(SRID.RelationshipTagDoesntMatchSchema), null, reader.LineNumber, reader.LinePosition); 
                                }
                                else 
                                    if (!(String.CompareOrdinal(RelationshipsTagName,reader.LocalName) == 0 && (reader.NodeType == XmlNodeType.EndElement)))
                                        throw new XmlException(SR.Get(SRID.UnknownTagEncountered), null, reader.LineNumber, reader.LinePosition);
                            }
                        } 
                        else throw new XmlException(SR.Get(SRID.ExpectedRelationshipsElementTag), null, reader.LineNumber, reader.LinePosition);
                    } 
                } 
            }
        } 


        //This method processes the attributes that are present on the Relationship element
        private void ProcessRelationshipAttributes(XmlCompatibilityReader reader) 
        {
            // Attribute : TargetMode 
 
            string targetModeAttributeValue = reader.GetAttribute(TargetModeAttributeName);
 
            //If the TargetMode attribute is missing in the underlying markup then we assume it to be internal
            TargetMode relationshipTargetMode = TargetMode.Internal;

            if (targetModeAttributeValue != null) 
            {
                try 
                { 
                    relationshipTargetMode = (TargetMode)(Enum.Parse(typeof(TargetMode), targetModeAttributeValue, false /* ignore case */));
                } 
                catch (ArgumentNullException argNullEx)
                {
                    ThrowForInvalidAttributeValue(reader, TargetModeAttributeName, argNullEx);
                } 
                catch (ArgumentException argEx)
                { 
                    //if the targetModeAttributeValue is not Internal|External then Argument Exception will be thrown. 
                    ThrowForInvalidAttributeValue(reader, TargetModeAttributeName, argEx);
                } 
            }

            // Attribute : Target
            // create a new PackageRelationship 
            string targetAttributeValue = reader.GetAttribute(TargetAttributeName);
            if (targetAttributeValue == null || targetAttributeValue == String.Empty) 
                throw new XmlException(SR.Get(SRID.RequiredRelationshipAttributeMissing, TargetAttributeName), null, reader.LineNumber, reader.LinePosition); 

            Uri targetUri = new Uri(targetAttributeValue, UriKind.RelativeOrAbsolute); 

            // Attribute : Type
            string typeAttributeValue = reader.GetAttribute(TypeAttributeName);
            if (typeAttributeValue == null || typeAttributeValue == String.Empty) 
                throw new XmlException(SR.Get(SRID.RequiredRelationshipAttributeMissing, TypeAttributeName), null, reader.LineNumber, reader.LinePosition);
 
            // Attribute : Id 
            // Get the Id attribute (required attribute).
            string idAttributeValue = reader.GetAttribute(IdAttributeName); 
            if (idAttributeValue == null || idAttributeValue == String.Empty)
                throw new XmlException(SR.Get(SRID.RequiredRelationshipAttributeMissing, IdAttributeName), null, reader.LineNumber, reader.LinePosition);

            // Add the relationship to the collection 
            Add(targetUri, relationshipTargetMode, typeAttributeValue, idAttributeValue, true /*parsing*/);
 
        } 

        //If End element is present for Relationship then we process it 
        private void ProcessEndElementForRelationshipTag(XmlCompatibilityReader reader)
        {
            Debug.Assert(!reader.IsEmptyElement, "This method should only be called it the Relationship Element is not empty");
 
            reader.Read();
 
            //Skips over the following - ProcessingInstruction, DocumentType, Comment, Whitespace, or SignificantWhitespace 
            reader.MoveToContent();
 
            if (reader.NodeType == XmlNodeType.EndElement && String.CompareOrdinal(RelationshipTagName, reader.LocalName) == 0)
                return;
            else
                throw new XmlException(SR.Get(SRID.ElementIsNotEmptyElement, RelationshipTagName), null, reader.LineNumber, reader.LinePosition); 
        }
 
 
        /// 
        /// Add new relationship to the Collection 
        /// 
        /// target
        /// Enumeration indicating the base uri for the target uri
        /// relationship type that uniquely defines the role of the relationship 
        /// String that conforms to the xsd:ID datatype. Unique across the source's relationships.
        /// Null OK (ID will be generated). 
        /// Indicates whether the add call is made while parsing existing relationships 
        /// from a relationship part, or we are adding a new relationship
        private PackageRelationship Add(Uri targetUri, TargetMode targetMode, string relationshipType, string id, bool parsing) 
        {
            if (targetUri == null)
                throw new ArgumentNullException("targetUri");
 
            if (relationshipType == null)
                throw new ArgumentNullException("relationshipType"); 
 
            ThrowIfInvalidRelationshipType(relationshipType);
 
            //Verify if the Enum value is valid
            if (targetMode < TargetMode.Internal || targetMode > TargetMode.External)
                throw new ArgumentOutOfRangeException("targetMode");
 
            // don't accept absolute Uri's if targetMode is Internal.
            if (targetMode == TargetMode.Internal && targetUri.IsAbsoluteUri) 
                throw new ArgumentException(SR.Get(SRID.RelationshipTargetMustBeRelative), "targetUri"); 

            // don't allow relationships to relationships 
            //  This check should be made for following cases
            //      1. Uri is absolute and it is pack Uri
            //      2. Uri is NOT absolute and its target mode is internal (or NOT external)
            //      Note: if the target is absolute uri and its not a pack scheme then we cannot determine if it is a rels part 
            //      Note: if the target is relative uri and target mode is external, we cannot determine if it is a rels part
            if ((!targetUri.IsAbsoluteUri && targetMode != TargetMode.External) 
                    || (targetUri.IsAbsoluteUri && targetUri.Scheme == PackUriHelper.UriSchemePack)) 
            {
                Uri resolvedUri = GetResolvedTargetUri(targetUri, targetMode); 
                //GetResolvedTargetUri returns a null if the target mode is external and the
                //target Uri is a packUri with no "part" component, so in that case we know that
                //its not a relationship part.
                if (resolvedUri != null) 
                {
                    if (PackUriHelper.IsRelationshipPartUri(resolvedUri)) 
                        throw new ArgumentException(SR.Get(SRID.RelationshipToRelationshipIllegal), "targetUri"); 
                }
            } 

            // Generate an ID if id is null. Throw exception if neither null nor a valid unique xsd:ID.
            if (id == null)
                id = GenerateUniqueRelationshipId(); 
            else
                ValidateUniqueRelationshipId(id); 
 
            //Ensure the relationship part
            EnsureRelationshipPart(); 

            // create and add
            PackageRelationship relationship = new PackageRelationship(_package, _sourcePart, targetUri, targetMode, relationshipType, id);
            _relationships.Add(relationship); 

            //If we are adding relationships as a part of Parsing the underlying relationship part, we should not set 
            //the dirty flag to false. 
            _dirty = !parsing;
 
            return relationship;
        }

        ///  
        /// Write PackageRelationship Stream
        ///  
        /// part to persist to 
        private void WriteRelationshipPart(PackagePart part)
        { 
            using (IgnoreFlushAndCloseStream s = new IgnoreFlushAndCloseStream(part.GetStream()))
            {
                s.SetLength(0);    // truncate to resolve PS 954048
 
                // use UTF-8 encoding by default
                using (XmlTextWriter writer = new XmlTextWriter(s, System.Text.Encoding.UTF8)) 
                { 
#if DEBUG
                    writer.Formatting = Formatting.Indented; 
#endif
                    writer.WriteStartDocument();

                    // start outer Relationships tag 
                    writer.WriteStartElement(RelationshipsTagName, PackagingUtilities.RelationshipNamespaceUri);
 
                    // Write Relationship elements. 
                    WriteRelationshipsAsXml(
                        writer, 
                        _relationships,
                        false, /* do not systematically write target mode */
                        false  /* not in streaming production */
                        ); 

                    // end of Relationships tag 
                    writer.WriteEndElement(); 

                    // close the document 
                    writer.WriteEndDocument();
                }
            }
 
        }
 
        ///  
        /// Write one Relationship element for each member of relationships.
        /// This method is used by XmlDigitalSignatureProcessor code as well 
        /// 
        internal static void WriteRelationshipsAsXml(XmlWriter writer, IEnumerable relationships, bool alwaysWriteTargetModeAttribute, bool inStreamingProduction)
        {
            foreach (PackageRelationship relationship in relationships) 
            {
                if (inStreamingProduction && relationship.Saved) 
                    continue; 

                writer.WriteStartElement(RelationshipTagName); 

                // Write RelationshipType attribute.
                writer.WriteAttributeString(TypeAttributeName, relationship.RelationshipType);
 
                // Write Target attribute.
                // We would like to persist the uri as passed in by the user and so we use the 
                // OriginalString property. This makes the persisting behavior consistent 
                // for relative and absolute Uris.
                // Since we accpeted the Uri as a string, we are at the minimum guaranteed that 
                // the string can be converted to a valid Uri.
                // Also, we are just using it here to persist the information and we are not
                // resolving or fetching a resource based on this Uri.
                writer.WriteAttributeString(TargetAttributeName, relationship.TargetUri.OriginalString); 

                // TargetMode is optional attribute in the markup and its default value is TargetMode="Internal" 
                if (alwaysWriteTargetModeAttribute || relationship.TargetMode == TargetMode.External) 
                    writer.WriteAttributeString(TargetModeAttributeName, relationship.TargetMode.ToString());
 
                // Write Id attribute.
                writer.WriteAttributeString(IdAttributeName, relationship.Id);

                writer.WriteEndElement(); 

                // The following flag is useful only in a write-once context, namely 
                // in streaming production. In other contexts, it is simply ignored. 
                if (inStreamingProduction)
                    relationship.Saved = true; 
            }

        }
 
        /// 
        /// Ensures that the PackageRelationship PackagePart has been created - lazy init 
        ///  
        /// 
        /// Streaming production is a special case because the storage layer 
        /// can't and needn't be accessed to retrieve the relationship part
        /// once it has been created.
        /// 
        private void EnsureRelationshipPart() 
        {
            if (_relationshipPart == null || _relationshipPart.IsDeleted) 
            { 
                if (!_package.InStreamingCreation && _package.PartExists(_uri))
                { 
                    _relationshipPart = _package.GetPart(_uri);
                    ThrowIfIncorrectContentType(_relationshipPart.ValidatedContentType);
                }
                else 
                {
                    CompressionOption compressionOption = _sourcePart == null ? CompressionOption.NotCompressed : _sourcePart.CompressionOption; 
                    _relationshipPart = _package.CreatePart(_uri, PackagingUtilities.RelationshipPartContentType.ToString(), compressionOption); 
                }
            } 
        }

        /// 
        /// Resolves the target uri in the relationship against the source part or the 
        /// package root. This resolved Uri is then used by the Add method to figure
        /// out if a relationship is being created to another relationship part. 
        ///  
        /// PackageRelationship target uri
        ///  Enum value specifying the interpretation of the base uri 
        /// for the relationship target uri
        /// Resolved Uri
        private Uri GetResolvedTargetUri(Uri target, TargetMode targetMode)
        { 
            if (targetMode == TargetMode.Internal)
            { 
                Debug.Assert(!target.IsAbsoluteUri, "Uri should be relative at this stage"); 

                if (_sourcePart == null) //indicates that the source is the package root 
                    return PackUriHelper.ResolvePartUri(PackUriHelper.PackageRootUri, target);
                else
                    return PackUriHelper.ResolvePartUri(_sourcePart.Uri, target);
            } 
            else
            { 
                if (target.IsAbsoluteUri) 
                {
                    if (String.CompareOrdinal(target.Scheme, PackUriHelper.UriSchemePack) == 0) 
                        return PackUriHelper.GetPartUri(target);
                }
                else
                    Debug.Assert(false, "Uri should not be relative at this stage"); 
            }
            // relative to the location of the package. 
            return target; 
        }
 
        //Throws an exception if the relationship part does not have the correct content type
        private void ThrowIfIncorrectContentType(ContentType contentType)
        {
            if (!contentType.AreTypeAndSubTypeEqual(PackagingUtilities.RelationshipPartContentType)) 
                throw new FileFormatException(SR.Get(SRID.RelationshipPartIncorrectContentType));
        } 
 
        //Throws an exception if the xml:base attribute is present in the Relationships XML
        private void ThrowIfXmlBaseAttributeIsPresent(XmlCompatibilityReader reader) 
        {
            string xmlBaseAttributeValue = reader.GetAttribute(XmlBaseAttributeName);

            if (xmlBaseAttributeValue != null) 
                throw new XmlException(SR.Get(SRID.InvalidXmlBaseAttributePresent, XmlBaseAttributeName), null, reader.LineNumber, reader.LinePosition);
        } 
 
        //Throws an XML exception if the attribute value is invalid
        private void ThrowForInvalidAttributeValue(XmlCompatibilityReader reader, String attributeName, Exception ex) 
        {
            throw new XmlException(SR.Get(SRID.InvalidValueForTheAttribute, attributeName), ex, reader.LineNumber, reader.LinePosition);
        }
 
        // Generate a unique relation ID.
        // In streaming production, we rely on the fact that the time stamp is supposedly 
        // unique on a given machine. So no duplication test is carried out. 
        private string GenerateUniqueRelationshipId()
        { 
            string id;
            do
            {
                id = GenerateRelationshipId(); 
            } while (!_package.InStreamingCreation && GetRelationship(id) != null);
            return id; 
        } 

        // Build an ID string consisting of the letter 'R' followed by an 8-byte GUID timestamp. 
        // Guid.ToString() outputs the bytes in the big-endian order (higher order byte first)
        private string GenerateRelationshipId()
        {
            // The timestamp consists of the first 8 hex octets of the GUID. 
            return String.Concat("R", Guid.NewGuid().ToString("N").Substring(0, _timestampLength));
        } 
 
        // If 'id' is not of the xsd type ID or is not unique for this collection, throw an exception.
        private void ValidateUniqueRelationshipId(string id) 
        {
            // An XSD ID is an NCName that is unique.
            ThrowIfInvalidXsdId(id);
 
            // Check for uniqueness.
            if (GetRelationshipIndex(id) >= 0) 
                throw new XmlException(SR.Get(SRID.NotAUniqueRelationshipId, id)); 
        }
 

        // Retrieve a relationship's index in _relationships given its id.
        // Return a negative value if not found.
        private int GetRelationshipIndex(string id) 
        {
            for (int index = 0; index < _relationships.Count; ++index) 
                if (string.Equals(_relationships[index].Id, id, StringComparison.Ordinal)) 
                    return index;
 
            return -1;
        }

 
        /// 
        /// If any relationships have to be flushed, will lazily create a StreamingZipPartStream 
        /// to flush them to a piece. 
        /// When isLastPiece is true and a StreamingZipPartStream has been created or there are
        /// more relationships to be flushed, the Xml document is completed and the 
        /// StreamingZipPartStream is closed.
        /// 
        private void FlushRelationshipsToPiece(bool isLastPiece)
        { 
            Debug.Assert(_package.InStreamingCreation, "This method should only be called in streaming creation mode");
 
            if (_dirty) 
            {
                // No deletion in streaming production. 
                Invariant.Assert(_relationships.Count > 0);

                // Dump the contents of _relationships to the stream and mark as saved.
                WriteRelationshipsAsXml( 
                    StreamingXmlWriter,
                    _relationships, 
                    false, /* do not systematically write target mode */ 
                    true   /* in streaming production */
                    ); 

                if (!isLastPiece)
                {
                    // Create a piece with the Xml just written. 
                    StreamingXmlWriter.Flush();
                } 
 
                _dirty = false;
            } 

            if (isLastPiece && StreamingXmlWriter.WriteState != WriteState.Closed)
            {
                // Close Relationships tag. 
                StreamingXmlWriter.WriteEndElement();
 
                // Close the document. 
                StreamingXmlWriter.WriteEndDocument();
 
                // Create a terminal piece. This will set StreamingXmlWriter.WriteState to Closed.
                StreamingXmlWriter.Close();
            }
        } 

        #endregion 
 
        #region Private Properties
 
        /// 
        /// Invoked strictly in streaming production to return and, if needed,
        /// lazily initialize _streamingXmlWriter.
        ///  
        private XmlWriter StreamingXmlWriter
        { 
            get 
            {
                if (_streamingXmlWriter == null) 
                {
                    // Implement the writer on top of a streaming stream, so each Flush
                    // will create a piece.
                    EnsureRelationshipPart(); 
                    StreamingZipPartStream s = (StreamingZipPartStream) _relationshipPart.GetStream(
                        FileMode.CreateNew, FileAccess.Write); 
                    _streamingXmlWriter = new XmlTextWriter(s, System.Text.Encoding.UTF8); 

                    // Write the top of the Xml document. 
                    StreamingXmlWriter.WriteStartDocument();
                    StreamingXmlWriter.WriteStartElement(
                        RelationshipsTagName, PackagingUtilities.RelationshipNamespaceUri);
                } 
                return _streamingXmlWriter;
            } 
        } 

        #endregion Private Properties 

         //-----------------------------------------------------
        //
        //  Private Members 
        //
        //------------------------------------------------------ 
        #region Private Members 
        private List  _relationships;
        private bool                       _dirty;    // true if we have uncommitted changes to _relationships 
        private Package                    _package;     // our package - in case _sourcePart is null
        private PackagePart                _sourcePart;      // owning part - null if package is the owner
        private PackagePart                _relationshipPart;  // where our relationships are persisted
        private Uri                        _uri;           // the URI of our relationship part 
        private XmlWriter                  _streamingXmlWriter;
 
        //----------------------------------------------------- 
        //
        //  Private Fields 
        //
        //-----------------------------------------------------
        // segment that indicates a relationship part
 
        private static readonly int _timestampLength = 16;
 
        private static readonly string RelationshipsTagName     = "Relationships"; 
        private static readonly string RelationshipTagName      = "Relationship";
        private static readonly string TargetAttributeName      = "Target"; 
        private static readonly string TypeAttributeName        = "Type";
        private static readonly string IdAttributeName          = "Id";
        private static readonly string XmlBaseAttributeName     = "xml:base";
        private static readonly string TargetModeAttributeName  = "TargetMode"; 

        private static readonly string[] RelationshipKnownNamespaces 
            = new string[] { PackagingUtilities.RelationshipNamespaceUri }; 

        #endregion 
    }
}


// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------------------------ 
//
// 
//    Copyright (C) Microsoft Corporation.  All rights reserved.
//  
//
// Description: 
//  This is a class for representing a PackageRelationshipCollection. This is an internal 
//  class for manipulating relationships associated with a part
// 
// Details:
//   This class handles serialization to/from relationship parts, creation of those parts
//   and offers methods to create, delete and enumerate relationships. This code was
//   moved from the PackageRelationshipCollection class. 
//
// History: 
//  04/26/2004: SarjanaS: This code was moved from the PackageRelationshipCollection class. 
//
//----------------------------------------------------------------------------- 

using System;
using System.Collections;
using System.Collections.Generic; 
using System.Globalization;
using System.Xml;                           // for XmlReader/Writer 
using System.IO.Packaging; 
using System.Windows;                       // For Exception strings - SRID
using System.IO; 
using System.Diagnostics;
using System.Windows.Markup;                // For XMLCompatibilityReader

using MS.Internal;                          // For Invariant. 
using MS.Internal.WindowsBase;
 
namespace MS.Internal.IO.Packaging 
{
    ///  
    /// Collection of all the relationships corresponding to a given source PackagePart
    /// 
    internal class InternalRelationshipCollection : IEnumerable
    { 
        //-----------------------------------------------------
        // 
        //  Public Methods 
        //
        //----------------------------------------------------- 
        #region IEnumerable
        /// 
        /// Returns an enumertor over all the relationships for a Package or a PackagePart
        ///  
        /// 
        IEnumerator IEnumerable.GetEnumerator() 
        { 
            return _relationships.GetEnumerator();
        } 

        /// 
        /// Returns an enumertor over all the relationships for a Package or a PackagePart
        ///  
        /// 
        IEnumerator IEnumerable.GetEnumerator() 
        { 
            return _relationships.GetEnumerator();
        } 

        /// 
        /// Returns an enumertor over all the relationships for a Package or a PackagePart
        ///  
        /// 
        public List.Enumerator GetEnumerator() 
        { 
            return _relationships.GetEnumerator();
        } 

        #endregion

        //------------------------------------------------------ 
        //
        //  Internal Methods 
        // 
        //-----------------------------------------------------
        #region Internal Methods 
        /// 
        /// Constructor
        /// 
        /// For use by PackagePart 
        internal InternalRelationshipCollection(PackagePart part): this(part.Package, part)
        { 
        } 

        ///  
        /// Constructor
        /// 
        /// For use by Package
        internal InternalRelationshipCollection(Package package): this(package, null) 
        {
        } 
 
        /// 
        /// Add new relationship 
        /// 
        /// target
        /// Enumeration indicating the base uri for the target uri
        /// relationship type that uniquely defines the role of the relationship 
        /// String that conforms to the xsd:ID datatype. Unique across the source's relationships.
        /// Null OK (ID will be generated). 
        internal PackageRelationship Add(Uri targetUri, TargetMode targetMode, string relationshipType, string id) 
        {
            return Add(targetUri, targetMode, relationshipType, id, false /*not parsing*/); 
        }

        /// 
        /// Return the relationship whose id is 'id', and null if not found. 
        /// 
        internal PackageRelationship GetRelationship(string id) 
        { 
            Invariant.Assert(!_package.InStreamingCreation);
            int index = GetRelationshipIndex(id); 
            if (index == -1)
                return null;
            return _relationships[index];
        } 

        ///  
        /// Delete relationship with ID 'id' 
        /// 
        /// ID of the relationship to remove 
        internal void Delete(String id)
        {
            Invariant.Assert(!_package.InStreamingCreation);
            int index = GetRelationshipIndex(id); 
            if (index == -1)
                return; 
 
            _relationships.RemoveAt(index);
            _dirty = true; 
        }

        /// 
        /// Clear all the relationships in this collection 
        /// Today it is only used when the entire relationship part is being deleted
        ///  
        internal void Clear() 
        {
            Invariant.Assert(!_package.InStreamingCreation); 
            _relationships.Clear();
            _dirty = true;
        }
 
        /// 
        /// Flush to stream (destructive) 
        ///  
        /// 
        /// Based on the streaming mode of the package, flush piece or flush part. 
        /// 
        internal void Flush()
        {
            if (!_dirty) 
                return;
 
            if (_package.InStreamingCreation) 
            {
                FlushRelationshipsToPiece(false /* not a terminal piece */); 
            }
            else
            {
                if (_relationships.Count == 0)  // empty? 
                {
                    // delete the part 
                    if (_package.PartExists(_uri)) 
                    {
                        _package.DeletePart(_uri); 
                    }
                    _relationshipPart = null;
                }
                else 
                {
                    EnsureRelationshipPart();   // lazy init 
 
                    // write xml
                    WriteRelationshipPart(_relationshipPart); 
                }
            }
            _dirty = false;
        } 

        ///  
        /// Exclusively used for streaming production. Any relationships remaining to flush 
        /// are flushed to a terminal piece.
        /// If relationships have been created and have all been flushed already, a terminal piece 
        /// is created to complete the Xml document.
        /// 
        internal void CloseInStreamingCreationMode()
        { 
            Debug.Assert(_package.InStreamingCreation, "This method should only be called in streaming creation mode");
            FlushRelationshipsToPiece(true /* last piece */); 
        } 

        internal static void ThrowIfInvalidRelationshipType(string relationshipType) 
        {
            // Look for empty string or string with just spaces
            if (relationshipType.Trim() == String.Empty)
                throw new ArgumentException(SR.Get(SRID.InvalidRelationshipType)); 
        }
 
        // If 'id' is not of the xsd type ID, throw an exception. 
        internal static void ThrowIfInvalidXsdId(string id)
        { 
            Invariant.Assert(id != null, "id should not be null");

            try
            { 
                // An XSD ID is an NCName that is unique.
                XmlConvert.VerifyNCName(id); 
            } 
            catch (XmlException exception)
            { 
                throw new XmlException(SR.Get(SRID.NotAValidXmlIdString, id), exception);
            }
        }
 
        #endregion Internal Methods
 
        //------------------------------------------------------ 
        //
        //  Private Methods 
        //
        //------------------------------------------------------
        #region Private Methods
        ///  
        /// Constructor
        ///  
        /// package 
        /// part will be null if package is the source of the relationships
        /// Shared constructor 
        private InternalRelationshipCollection(Package package, PackagePart part)
        {
            Debug.Assert(package != null, "package parameter passed should never be null");
 
            _package = package;
            _sourcePart = part; 
 
            //_sourcePart may be null representing that the relationships are at the package level
            _uri = GetRelationshipPartUri(_sourcePart); 
            _relationships = new List(4);

            // Load if available (not applicable to write-only mode).
            if (package.FileOpenAccess != FileAccess.Write && package.PartExists(_uri)) 
            {
                _relationshipPart = package.GetPart(_uri); 
                ThrowIfIncorrectContentType(_relationshipPart.ValidatedContentType); 
                ParseRelationshipPart(_relationshipPart);
            } 

            //Any initialization in the constructor should not set the dirty flag to true.
            _dirty = false;
 
        }
 
        ///  
        /// Returns the associated RelationshipPart for this part
        ///  
        /// may be null
        /// name of relationship part for the given part
        private static Uri GetRelationshipPartUri(PackagePart part)
        { 
            Uri sourceUri;
 
            if (part == null) 
                sourceUri = PackUriHelper.PackageRootUri;
            else 
                sourceUri = part.Uri;

            return PackUriHelper.GetRelationshipPartUri(sourceUri);
        } 

        ///  
        /// Parse PackageRelationship Stream 
        /// 
        /// relationship part 
        /// Thrown if XML is malformed
        private void ParseRelationshipPart(PackagePart part)
        {
            //We can safely open the stream as FileAccess.Read, as this code 
            //should only be invoked if the Package has been opened in Read or ReadWrite mode.
            Debug.Assert(_package.FileOpenAccess == FileAccess.Read || _package.FileOpenAccess == FileAccess.ReadWrite, 
                "This method should only be called when FileAccess is Read or ReadWrite"); 

            using (Stream s = part.GetStream(FileMode.Open, FileAccess.Read)) 
            {
                // load from the relationship part associated with the given part
                using (XmlTextReader baseReader = new XmlTextReader(s))
                { 
                    baseReader.WhitespaceHandling = WhitespaceHandling.None;
 
                    //Prohibit DTD from the markup as per the OPC spec 
                    baseReader.ProhibitDtd = true;
 
                    using (XmlCompatibilityReader reader = new XmlCompatibilityReader(baseReader, RelationshipKnownNamespaces))
                    {
                        //This method expects the reader to be in ReadState.Initial.
                        //It will make the first read call. 
                        PackagingUtilities.PerformInitailReadAndVerifyEncoding(baseReader);
 
                        //Note: After the previous method call the reader should be at the first tag in the markup. 
                        //MoveToContent - Skips over the following - ProcessingInstruction, DocumentType, Comment, Whitespace, or SignificantWhitespace
                        //If the reader is currently at a content node then this function call is a no-op 
                        reader.MoveToContent();

                        // look for our tag and namespace pair - throw if other elements are encountered
                        // Make sure that the current node read is an Element 
                        if (reader.NodeType == XmlNodeType.Element
                            && (reader.Depth == 0) 
                            && (String.CompareOrdinal(RelationshipsTagName, reader.LocalName) == 0) 
                            && (String.CompareOrdinal(PackagingUtilities.RelationshipNamespaceUri, reader.NamespaceURI) == 0))
                        { 
                            ThrowIfXmlBaseAttributeIsPresent(reader);

                            //There should be a namespace Attribute present at this level.
                            //Also any other attribute on the  tag is an error including xml: and xsi: attributes 
                            if (PackagingUtilities.GetNonXmlnsAttributeCount(reader) > 0)
                                throw new XmlException(SR.Get(SRID.RelationshipsTagHasExtraAttributes), null, reader.LineNumber, reader.LinePosition); 
 
                            // start tag encountered for Relationships
                            // now parse individual Relationship tags 
                            while (reader.Read())
                            {
                                //Skips over the following - ProcessingInstruction, DocumentType, Comment, Whitespace, or SignificantWhitespace
                                //If the reader is currently at a content node then this function call is a no-op 
                                reader.MoveToContent();
 
                                //If MoveToContent() takes us to the end of the content 
                                if (reader.NodeType == XmlNodeType.None)
                                    continue; 

                                if (reader.NodeType == XmlNodeType.Element
                                    && (reader.Depth == 1)
                                    && (String.CompareOrdinal(RelationshipTagName, reader.LocalName) == 0) 
                                    && (String.CompareOrdinal(PackagingUtilities.RelationshipNamespaceUri, reader.NamespaceURI) == 0))
                                { 
                                    ThrowIfXmlBaseAttributeIsPresent(reader); 

                                    int expectedAttributesCount = 3; 

                                    string targetModeAttributeValue = reader.GetAttribute(TargetModeAttributeName);
                                    if (targetModeAttributeValue != null)
                                        expectedAttributesCount++; 

                                    //check if there are expected number of attributes. 
                                    //Also any other attribute on the  tag is an error including xml: and xsi: attributes 
                                    if (PackagingUtilities.GetNonXmlnsAttributeCount(reader) == expectedAttributesCount)
                                    { 
                                        ProcessRelationshipAttributes(reader);

                                        //Skip the EndElement for Relationship
                                        if (!reader.IsEmptyElement) 
                                            ProcessEndElementForRelationshipTag(reader);
                                    } 
                                    else throw new XmlException(SR.Get(SRID.RelationshipTagDoesntMatchSchema), null, reader.LineNumber, reader.LinePosition); 
                                }
                                else 
                                    if (!(String.CompareOrdinal(RelationshipsTagName,reader.LocalName) == 0 && (reader.NodeType == XmlNodeType.EndElement)))
                                        throw new XmlException(SR.Get(SRID.UnknownTagEncountered), null, reader.LineNumber, reader.LinePosition);
                            }
                        } 
                        else throw new XmlException(SR.Get(SRID.ExpectedRelationshipsElementTag), null, reader.LineNumber, reader.LinePosition);
                    } 
                } 
            }
        } 


        //This method processes the attributes that are present on the Relationship element
        private void ProcessRelationshipAttributes(XmlCompatibilityReader reader) 
        {
            // Attribute : TargetMode 
 
            string targetModeAttributeValue = reader.GetAttribute(TargetModeAttributeName);
 
            //If the TargetMode attribute is missing in the underlying markup then we assume it to be internal
            TargetMode relationshipTargetMode = TargetMode.Internal;

            if (targetModeAttributeValue != null) 
            {
                try 
                { 
                    relationshipTargetMode = (TargetMode)(Enum.Parse(typeof(TargetMode), targetModeAttributeValue, false /* ignore case */));
                } 
                catch (ArgumentNullException argNullEx)
                {
                    ThrowForInvalidAttributeValue(reader, TargetModeAttributeName, argNullEx);
                } 
                catch (ArgumentException argEx)
                { 
                    //if the targetModeAttributeValue is not Internal|External then Argument Exception will be thrown. 
                    ThrowForInvalidAttributeValue(reader, TargetModeAttributeName, argEx);
                } 
            }

            // Attribute : Target
            // create a new PackageRelationship 
            string targetAttributeValue = reader.GetAttribute(TargetAttributeName);
            if (targetAttributeValue == null || targetAttributeValue == String.Empty) 
                throw new XmlException(SR.Get(SRID.RequiredRelationshipAttributeMissing, TargetAttributeName), null, reader.LineNumber, reader.LinePosition); 

            Uri targetUri = new Uri(targetAttributeValue, UriKind.RelativeOrAbsolute); 

            // Attribute : Type
            string typeAttributeValue = reader.GetAttribute(TypeAttributeName);
            if (typeAttributeValue == null || typeAttributeValue == String.Empty) 
                throw new XmlException(SR.Get(SRID.RequiredRelationshipAttributeMissing, TypeAttributeName), null, reader.LineNumber, reader.LinePosition);
 
            // Attribute : Id 
            // Get the Id attribute (required attribute).
            string idAttributeValue = reader.GetAttribute(IdAttributeName); 
            if (idAttributeValue == null || idAttributeValue == String.Empty)
                throw new XmlException(SR.Get(SRID.RequiredRelationshipAttributeMissing, IdAttributeName), null, reader.LineNumber, reader.LinePosition);

            // Add the relationship to the collection 
            Add(targetUri, relationshipTargetMode, typeAttributeValue, idAttributeValue, true /*parsing*/);
 
        } 

        //If End element is present for Relationship then we process it 
        private void ProcessEndElementForRelationshipTag(XmlCompatibilityReader reader)
        {
            Debug.Assert(!reader.IsEmptyElement, "This method should only be called it the Relationship Element is not empty");
 
            reader.Read();
 
            //Skips over the following - ProcessingInstruction, DocumentType, Comment, Whitespace, or SignificantWhitespace 
            reader.MoveToContent();
 
            if (reader.NodeType == XmlNodeType.EndElement && String.CompareOrdinal(RelationshipTagName, reader.LocalName) == 0)
                return;
            else
                throw new XmlException(SR.Get(SRID.ElementIsNotEmptyElement, RelationshipTagName), null, reader.LineNumber, reader.LinePosition); 
        }
 
 
        /// 
        /// Add new relationship to the Collection 
        /// 
        /// target
        /// Enumeration indicating the base uri for the target uri
        /// relationship type that uniquely defines the role of the relationship 
        /// String that conforms to the xsd:ID datatype. Unique across the source's relationships.
        /// Null OK (ID will be generated). 
        /// Indicates whether the add call is made while parsing existing relationships 
        /// from a relationship part, or we are adding a new relationship
        private PackageRelationship Add(Uri targetUri, TargetMode targetMode, string relationshipType, string id, bool parsing) 
        {
            if (targetUri == null)
                throw new ArgumentNullException("targetUri");
 
            if (relationshipType == null)
                throw new ArgumentNullException("relationshipType"); 
 
            ThrowIfInvalidRelationshipType(relationshipType);
 
            //Verify if the Enum value is valid
            if (targetMode < TargetMode.Internal || targetMode > TargetMode.External)
                throw new ArgumentOutOfRangeException("targetMode");
 
            // don't accept absolute Uri's if targetMode is Internal.
            if (targetMode == TargetMode.Internal && targetUri.IsAbsoluteUri) 
                throw new ArgumentException(SR.Get(SRID.RelationshipTargetMustBeRelative), "targetUri"); 

            // don't allow relationships to relationships 
            //  This check should be made for following cases
            //      1. Uri is absolute and it is pack Uri
            //      2. Uri is NOT absolute and its target mode is internal (or NOT external)
            //      Note: if the target is absolute uri and its not a pack scheme then we cannot determine if it is a rels part 
            //      Note: if the target is relative uri and target mode is external, we cannot determine if it is a rels part
            if ((!targetUri.IsAbsoluteUri && targetMode != TargetMode.External) 
                    || (targetUri.IsAbsoluteUri && targetUri.Scheme == PackUriHelper.UriSchemePack)) 
            {
                Uri resolvedUri = GetResolvedTargetUri(targetUri, targetMode); 
                //GetResolvedTargetUri returns a null if the target mode is external and the
                //target Uri is a packUri with no "part" component, so in that case we know that
                //its not a relationship part.
                if (resolvedUri != null) 
                {
                    if (PackUriHelper.IsRelationshipPartUri(resolvedUri)) 
                        throw new ArgumentException(SR.Get(SRID.RelationshipToRelationshipIllegal), "targetUri"); 
                }
            } 

            // Generate an ID if id is null. Throw exception if neither null nor a valid unique xsd:ID.
            if (id == null)
                id = GenerateUniqueRelationshipId(); 
            else
                ValidateUniqueRelationshipId(id); 
 
            //Ensure the relationship part
            EnsureRelationshipPart(); 

            // create and add
            PackageRelationship relationship = new PackageRelationship(_package, _sourcePart, targetUri, targetMode, relationshipType, id);
            _relationships.Add(relationship); 

            //If we are adding relationships as a part of Parsing the underlying relationship part, we should not set 
            //the dirty flag to false. 
            _dirty = !parsing;
 
            return relationship;
        }

        ///  
        /// Write PackageRelationship Stream
        ///  
        /// part to persist to 
        private void WriteRelationshipPart(PackagePart part)
        { 
            using (IgnoreFlushAndCloseStream s = new IgnoreFlushAndCloseStream(part.GetStream()))
            {
                s.SetLength(0);    // truncate to resolve PS 954048
 
                // use UTF-8 encoding by default
                using (XmlTextWriter writer = new XmlTextWriter(s, System.Text.Encoding.UTF8)) 
                { 
#if DEBUG
                    writer.Formatting = Formatting.Indented; 
#endif
                    writer.WriteStartDocument();

                    // start outer Relationships tag 
                    writer.WriteStartElement(RelationshipsTagName, PackagingUtilities.RelationshipNamespaceUri);
 
                    // Write Relationship elements. 
                    WriteRelationshipsAsXml(
                        writer, 
                        _relationships,
                        false, /* do not systematically write target mode */
                        false  /* not in streaming production */
                        ); 

                    // end of Relationships tag 
                    writer.WriteEndElement(); 

                    // close the document 
                    writer.WriteEndDocument();
                }
            }
 
        }
 
        ///  
        /// Write one Relationship element for each member of relationships.
        /// This method is used by XmlDigitalSignatureProcessor code as well 
        /// 
        internal static void WriteRelationshipsAsXml(XmlWriter writer, IEnumerable relationships, bool alwaysWriteTargetModeAttribute, bool inStreamingProduction)
        {
            foreach (PackageRelationship relationship in relationships) 
            {
                if (inStreamingProduction && relationship.Saved) 
                    continue; 

                writer.WriteStartElement(RelationshipTagName); 

                // Write RelationshipType attribute.
                writer.WriteAttributeString(TypeAttributeName, relationship.RelationshipType);
 
                // Write Target attribute.
                // We would like to persist the uri as passed in by the user and so we use the 
                // OriginalString property. This makes the persisting behavior consistent 
                // for relative and absolute Uris.
                // Since we accpeted the Uri as a string, we are at the minimum guaranteed that 
                // the string can be converted to a valid Uri.
                // Also, we are just using it here to persist the information and we are not
                // resolving or fetching a resource based on this Uri.
                writer.WriteAttributeString(TargetAttributeName, relationship.TargetUri.OriginalString); 

                // TargetMode is optional attribute in the markup and its default value is TargetMode="Internal" 
                if (alwaysWriteTargetModeAttribute || relationship.TargetMode == TargetMode.External) 
                    writer.WriteAttributeString(TargetModeAttributeName, relationship.TargetMode.ToString());
 
                // Write Id attribute.
                writer.WriteAttributeString(IdAttributeName, relationship.Id);

                writer.WriteEndElement(); 

                // The following flag is useful only in a write-once context, namely 
                // in streaming production. In other contexts, it is simply ignored. 
                if (inStreamingProduction)
                    relationship.Saved = true; 
            }

        }
 
        /// 
        /// Ensures that the PackageRelationship PackagePart has been created - lazy init 
        ///  
        /// 
        /// Streaming production is a special case because the storage layer 
        /// can't and needn't be accessed to retrieve the relationship part
        /// once it has been created.
        /// 
        private void EnsureRelationshipPart() 
        {
            if (_relationshipPart == null || _relationshipPart.IsDeleted) 
            { 
                if (!_package.InStreamingCreation && _package.PartExists(_uri))
                { 
                    _relationshipPart = _package.GetPart(_uri);
                    ThrowIfIncorrectContentType(_relationshipPart.ValidatedContentType);
                }
                else 
                {
                    CompressionOption compressionOption = _sourcePart == null ? CompressionOption.NotCompressed : _sourcePart.CompressionOption; 
                    _relationshipPart = _package.CreatePart(_uri, PackagingUtilities.RelationshipPartContentType.ToString(), compressionOption); 
                }
            } 
        }

        /// 
        /// Resolves the target uri in the relationship against the source part or the 
        /// package root. This resolved Uri is then used by the Add method to figure
        /// out if a relationship is being created to another relationship part. 
        ///  
        /// PackageRelationship target uri
        ///  Enum value specifying the interpretation of the base uri 
        /// for the relationship target uri
        /// Resolved Uri
        private Uri GetResolvedTargetUri(Uri target, TargetMode targetMode)
        { 
            if (targetMode == TargetMode.Internal)
            { 
                Debug.Assert(!target.IsAbsoluteUri, "Uri should be relative at this stage"); 

                if (_sourcePart == null) //indicates that the source is the package root 
                    return PackUriHelper.ResolvePartUri(PackUriHelper.PackageRootUri, target);
                else
                    return PackUriHelper.ResolvePartUri(_sourcePart.Uri, target);
            } 
            else
            { 
                if (target.IsAbsoluteUri) 
                {
                    if (String.CompareOrdinal(target.Scheme, PackUriHelper.UriSchemePack) == 0) 
                        return PackUriHelper.GetPartUri(target);
                }
                else
                    Debug.Assert(false, "Uri should not be relative at this stage"); 
            }
            // relative to the location of the package. 
            return target; 
        }
 
        //Throws an exception if the relationship part does not have the correct content type
        private void ThrowIfIncorrectContentType(ContentType contentType)
        {
            if (!contentType.AreTypeAndSubTypeEqual(PackagingUtilities.RelationshipPartContentType)) 
                throw new FileFormatException(SR.Get(SRID.RelationshipPartIncorrectContentType));
        } 
 
        //Throws an exception if the xml:base attribute is present in the Relationships XML
        private void ThrowIfXmlBaseAttributeIsPresent(XmlCompatibilityReader reader) 
        {
            string xmlBaseAttributeValue = reader.GetAttribute(XmlBaseAttributeName);

            if (xmlBaseAttributeValue != null) 
                throw new XmlException(SR.Get(SRID.InvalidXmlBaseAttributePresent, XmlBaseAttributeName), null, reader.LineNumber, reader.LinePosition);
        } 
 
        //Throws an XML exception if the attribute value is invalid
        private void ThrowForInvalidAttributeValue(XmlCompatibilityReader reader, String attributeName, Exception ex) 
        {
            throw new XmlException(SR.Get(SRID.InvalidValueForTheAttribute, attributeName), ex, reader.LineNumber, reader.LinePosition);
        }
 
        // Generate a unique relation ID.
        // In streaming production, we rely on the fact that the time stamp is supposedly 
        // unique on a given machine. So no duplication test is carried out. 
        private string GenerateUniqueRelationshipId()
        { 
            string id;
            do
            {
                id = GenerateRelationshipId(); 
            } while (!_package.InStreamingCreation && GetRelationship(id) != null);
            return id; 
        } 

        // Build an ID string consisting of the letter 'R' followed by an 8-byte GUID timestamp. 
        // Guid.ToString() outputs the bytes in the big-endian order (higher order byte first)
        private string GenerateRelationshipId()
        {
            // The timestamp consists of the first 8 hex octets of the GUID. 
            return String.Concat("R", Guid.NewGuid().ToString("N").Substring(0, _timestampLength));
        } 
 
        // If 'id' is not of the xsd type ID or is not unique for this collection, throw an exception.
        private void ValidateUniqueRelationshipId(string id) 
        {
            // An XSD ID is an NCName that is unique.
            ThrowIfInvalidXsdId(id);
 
            // Check for uniqueness.
            if (GetRelationshipIndex(id) >= 0) 
                throw new XmlException(SR.Get(SRID.NotAUniqueRelationshipId, id)); 
        }
 

        // Retrieve a relationship's index in _relationships given its id.
        // Return a negative value if not found.
        private int GetRelationshipIndex(string id) 
        {
            for (int index = 0; index < _relationships.Count; ++index) 
                if (string.Equals(_relationships[index].Id, id, StringComparison.Ordinal)) 
                    return index;
 
            return -1;
        }

 
        /// 
        /// If any relationships have to be flushed, will lazily create a StreamingZipPartStream 
        /// to flush them to a piece. 
        /// When isLastPiece is true and a StreamingZipPartStream has been created or there are
        /// more relationships to be flushed, the Xml document is completed and the 
        /// StreamingZipPartStream is closed.
        /// 
        private void FlushRelationshipsToPiece(bool isLastPiece)
        { 
            Debug.Assert(_package.InStreamingCreation, "This method should only be called in streaming creation mode");
 
            if (_dirty) 
            {
                // No deletion in streaming production. 
                Invariant.Assert(_relationships.Count > 0);

                // Dump the contents of _relationships to the stream and mark as saved.
                WriteRelationshipsAsXml( 
                    StreamingXmlWriter,
                    _relationships, 
                    false, /* do not systematically write target mode */ 
                    true   /* in streaming production */
                    ); 

                if (!isLastPiece)
                {
                    // Create a piece with the Xml just written. 
                    StreamingXmlWriter.Flush();
                } 
 
                _dirty = false;
            } 

            if (isLastPiece && StreamingXmlWriter.WriteState != WriteState.Closed)
            {
                // Close Relationships tag. 
                StreamingXmlWriter.WriteEndElement();
 
                // Close the document. 
                StreamingXmlWriter.WriteEndDocument();
 
                // Create a terminal piece. This will set StreamingXmlWriter.WriteState to Closed.
                StreamingXmlWriter.Close();
            }
        } 

        #endregion 
 
        #region Private Properties
 
        /// 
        /// Invoked strictly in streaming production to return and, if needed,
        /// lazily initialize _streamingXmlWriter.
        ///  
        private XmlWriter StreamingXmlWriter
        { 
            get 
            {
                if (_streamingXmlWriter == null) 
                {
                    // Implement the writer on top of a streaming stream, so each Flush
                    // will create a piece.
                    EnsureRelationshipPart(); 
                    StreamingZipPartStream s = (StreamingZipPartStream) _relationshipPart.GetStream(
                        FileMode.CreateNew, FileAccess.Write); 
                    _streamingXmlWriter = new XmlTextWriter(s, System.Text.Encoding.UTF8); 

                    // Write the top of the Xml document. 
                    StreamingXmlWriter.WriteStartDocument();
                    StreamingXmlWriter.WriteStartElement(
                        RelationshipsTagName, PackagingUtilities.RelationshipNamespaceUri);
                } 
                return _streamingXmlWriter;
            } 
        } 

        #endregion Private Properties 

         //-----------------------------------------------------
        //
        //  Private Members 
        //
        //------------------------------------------------------ 
        #region Private Members 
        private List  _relationships;
        private bool                       _dirty;    // true if we have uncommitted changes to _relationships 
        private Package                    _package;     // our package - in case _sourcePart is null
        private PackagePart                _sourcePart;      // owning part - null if package is the owner
        private PackagePart                _relationshipPart;  // where our relationships are persisted
        private Uri                        _uri;           // the URI of our relationship part 
        private XmlWriter                  _streamingXmlWriter;
 
        //----------------------------------------------------- 
        //
        //  Private Fields 
        //
        //-----------------------------------------------------
        // segment that indicates a relationship part
 
        private static readonly int _timestampLength = 16;
 
        private static readonly string RelationshipsTagName     = "Relationships"; 
        private static readonly string RelationshipTagName      = "Relationship";
        private static readonly string TargetAttributeName      = "Target"; 
        private static readonly string TypeAttributeName        = "Type";
        private static readonly string IdAttributeName          = "Id";
        private static readonly string XmlBaseAttributeName     = "xml:base";
        private static readonly string TargetModeAttributeName  = "TargetMode"; 

        private static readonly string[] RelationshipKnownNamespaces 
            = new string[] { PackagingUtilities.RelationshipNamespaceUri }; 

        #endregion 
    }
}


// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK