PartBasedPackageProperties.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

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

                            //------------------------------------------------------------------------------ 
//
// 
//    Copyright (C) Microsoft Corporation.  All rights reserved.
//  
//
// Description: 
//  The package properties are a subset of the standard OLE property sets 
//  SummaryInformation and DocumentSummaryInformation, and include such properties
//  as Title and Subject. 
//
// History:
//  06/23/2005: JohnLarc:   Initial implementation.
//  09/23/2005: JohnLarc:   Removed from public surface. 
//
//----------------------------------------------------------------------------- 
 
using System;
using System.Diagnostics; 
using System.IO;
using System.IO.Packaging;
using System.Collections;
using System.Collections.Generic; 
using System.Xml;
using System.Windows;                   // for ExceptionStringTable 
using System.Globalization;             // For CultureInfo 

using MS.Internal;                      // for Invariant.Assert 
using MS.Internal.IO.Packaging;         // for IgnoreFlushAndCloseStream, PackageXmlEnum, and PackageXmlStringTable
using MS.Internal.WindowsBase;

namespace MS.Internal.IO.Packaging 
{
    ///  
    ///  The package properties are a subset of the standard OLE property sets 
    ///  SummaryInformation and DocumentSummaryInformation, and include such properties
    ///  as Title and Subject. 
    /// 
    /// 
    /// No specific API is provided to interleave the properties part in streaming creation.
    /// However, Package.Flush is expected to persist package-wide data, in particular metadata. 
    /// Therefore, in streaming creation, a write-only write-once discipline has to be enforced.
    /// Setting a property to null deletes this property. 'null' is never strictly speaking 
    /// a property value, but an absence indicator. 
    /// 
    internal class PartBasedPackageProperties : PackageProperties 
    {
        //-----------------------------------------------------
        //
        //  Constructors 
        //
        //----------------------------------------------------- 
 
        #region Constructors
 
        internal PartBasedPackageProperties(Package package)
        {
            _package = package;
 
            // Initialize literals as Xml Atomic strings.
            _nameTable = PackageXmlStringTable.NameTable; 
 
            ReadPropertyValuesFromPackage();
 
            // No matter what happens during initialization, the dirty flag should not be set.
            _dirty = false;
        }
 
        #endregion Constructors
 
        //------------------------------------------------------ 
        //
        //  Public Methods 
        //
        //-----------------------------------------------------

        //------------------------------------------------------ 
        //
        //  Public Properties 
        // 
        //------------------------------------------------------
 
        #region Public Properties

        /// 
        /// The primary creator. The identification is environment-specific and 
        /// can consist of a name, email address, employee ID, etc. It is
        /// recommended that this value be only as verbose as necessary to 
        /// identify the individual. 
        /// 
        public override string Creator 
        {
            get
            {
                return (string)GetPropertyValue(PackageXmlEnum.Creator); 
            }
            set 
            { 
                RecordNewBinding(PackageXmlEnum.Creator, value);
            } 
        }

        /// 
        /// The title. 
        /// 
        public override string Title 
        { 
            get
            { 
                return (string)GetPropertyValue(PackageXmlEnum.Title);
            }
            set
            { 
                RecordNewBinding(PackageXmlEnum.Title, value);
            } 
        } 

        ///  
        /// The topic of the contents.
        /// 
        public override string Subject
        { 
            get
            { 
                return (string)GetPropertyValue(PackageXmlEnum.Subject); 
            }
            set 
            {
                RecordNewBinding(PackageXmlEnum.Subject, value);
            }
        } 

        ///  
        /// The category. This value is typically used by UI applications to create navigation 
        /// controls.
        ///  
        public override string Category
        {
            get
            { 
                return (string)GetPropertyValue(PackageXmlEnum.Category);
            } 
            set 
            {
                RecordNewBinding(PackageXmlEnum.Category, value); 
            }
        }

        ///  
        /// A delimited set of keywords to support searching and indexing. This
        /// is typically a list of terms that are not available elsewhere in the 
        /// properties. 
        /// 
        public override string Keywords 
        {
            get
            {
                return (string)GetPropertyValue(PackageXmlEnum.Keywords); 
            }
            set 
            { 
                RecordNewBinding(PackageXmlEnum.Keywords, value);
            } 
        }

        /// 
        /// The description or abstract of the contents. 
        /// 
        public override string Description 
        { 
            get
            { 
                return (string)GetPropertyValue(PackageXmlEnum.Description);
            }
            set
            { 
                RecordNewBinding(PackageXmlEnum.Description, value);
            } 
        } 

        ///  
        /// The type of content represented, generally defined by a specific
        /// use and intended audience. Example values include "Whitepaper",
        /// "Security Bulletin", and "Exam". (This property is distinct from
        /// MIME content types as defined in RFC 2616.) 
        /// 
        public override string ContentType 
        { 
            get
            { 
                string contentType = GetPropertyValue(PackageXmlEnum.ContentType) as string;

                return contentType;
            } 
            set
            { 
                RecordNewBinding(PackageXmlEnum.ContentType, value); 
            }
        } 

        /// 
        /// The status of the content. Example values include "Draft",
        /// "Reviewed", and "Final". 
        /// 
        public override string ContentStatus 
        { 
            get
            { 
                return (string)GetPropertyValue(PackageXmlEnum.ContentStatus);
            }
            set
            { 
                RecordNewBinding(PackageXmlEnum.ContentStatus, value);
            } 
        } 

        ///  
        /// The version number. This value is set by the user or by the application.
        /// 
        public override string Version
        { 
            get
            { 
                return (string)GetPropertyValue(PackageXmlEnum.Version); 
            }
            set 
            {
                RecordNewBinding(PackageXmlEnum.Version, value);
            }
        } 

        ///  
        /// The revision number. This value indicates the number of saves or 
        /// revisions. The application is responsible for updating this value
        /// after each revision. 
        /// 
        public override string Revision
        {
            get 
            {
                return (string)GetPropertyValue(PackageXmlEnum.Revision); 
            } 
            set
            { 
                RecordNewBinding(PackageXmlEnum.Revision, value);
            }
        }
 
        /// 
        /// The creation date and time. 
        ///  
        public override Nullable Created
        { 
            get
            {
                return GetDateTimePropertyValue(PackageXmlEnum.Created);
            } 
            set
            { 
                RecordNewBinding(PackageXmlEnum.Created, value); 
            }
        } 

        /// 
        /// The date and time of the last modification.
        ///  
        public override Nullable Modified
        { 
            get 
            {
                return GetDateTimePropertyValue(PackageXmlEnum.Modified); 
            }
            set
            {
                RecordNewBinding(PackageXmlEnum.Modified, value); 
            }
        } 
 
        /// 
        /// The user who performed the last modification. The identification is 
        /// environment-specific and can consist of a name, email address,
        /// employee ID, etc. It is recommended that this value be only as
        /// verbose as necessary to identify the individual.
        ///  
        public override string LastModifiedBy
        { 
            get 
            {
                return (string)GetPropertyValue(PackageXmlEnum.LastModifiedBy); 
            }
            set
            {
                RecordNewBinding(PackageXmlEnum.LastModifiedBy, value); 
            }
        } 
 
        /// 
        /// The date and time of the last printing. 
        /// 
        public override Nullable LastPrinted
        {
            get 
            {
                return GetDateTimePropertyValue(PackageXmlEnum.LastPrinted); 
            } 
            set
            { 
                RecordNewBinding(PackageXmlEnum.LastPrinted, value);
            }
        }
 
        /// 
        /// A language of the intellectual content of the resource 
        ///  
        public override string Language
        { 
            get
            {
                return (string)GetPropertyValue(PackageXmlEnum.Language);
            } 
            set
            { 
                RecordNewBinding(PackageXmlEnum.Language, value); 
            }
        } 

        /// 
        /// A unique identifier.
        ///  
        public override string Identifier
        { 
            get 
            {
                return (string)GetPropertyValue(PackageXmlEnum.Identifier); 
            }
            set
            {
                RecordNewBinding(PackageXmlEnum.Identifier, value); 
            }
        } 
 
        #endregion Public Properties
 
        //-----------------------------------------------------
        //
        //  Internal Methods
        // 
        //------------------------------------------------------
 
        #region Internal Methods 

        // Invoked from Package.Flush. 
        // The expectation is that whatever is currently dirty will get flushed.
        // In streaming production, this requires flushing to a piece.
        // Outside of streaming creation, the whole thing gets rewritten at every flush.
        internal void Flush() 
        {
            if (!_dirty) 
                return; 

            // Make sure there is a part to write to and that it contains 
            // the expected start markup.
            EnsureXmlWriter();

            // Write the property elements and clear _dirty. 
            // The whole property table gets cleared in streaming production.
            SerializeDirtyProperties(); 
 
            // In streaming mode, keep the writer around until we close.
            // Do a simple flush on the writer to make sure the content is written to a piece. 
            if (_package.InStreamingCreation && _xmlWriter != null)
            {
                _xmlWriter.Flush();
            } 
            // Else, add closing markup and close the writer.
            else 
            { 
                CloseXmlWriter();
            } 
        }

        // Invoked from Package.Close.
        // Carries out the same operations as Flush, except that, in streaming mode, 
        // the writer has to be closed.
        internal void Close() 
        { 
            Flush();
 
            // The following has to be done even if all properties have been saved (_dirty == false).
            if (_package.InStreamingCreation && _xmlWriter != null)
                CloseXmlWriter();
        } 

        #endregion Internal Methods 
 
        //-----------------------------------------------------
        // 
        //  Internal Properties
        //
        //-----------------------------------------------------
 
        //-----------------------------------------------------
        // 
        //  Private Methods 
        //
        //------------------------------------------------------ 

        #region Private Methods

        // The property store is implemented as a hash table of objects. 
        // Keys are taken from the set of string constants defined in this
        // class and compared by their references rather than their values. 
 
        private object GetPropertyValue(PackageXmlEnum propertyName)
        { 
            _package.ThrowIfWriteOnly();

            if (!_propertyDictionary.ContainsKey(propertyName))
                return null; 
            return _propertyDictionary[propertyName];
        } 
 
        // Shim function to adequately cast the result of GetPropertyValue.
        private Nullable GetDateTimePropertyValue(PackageXmlEnum propertyName) 
        {
            object valueObject = GetPropertyValue(propertyName);
            if (valueObject == null)
                return null; 
            // If an object is there, it will be a DateTime (not a Nullable).
            return (Nullable) valueObject; 
        } 

        // Set new property value. 
        // Override that sets the initializing flag to false to reflect the default
        // situation: recording a binding to implement a value assignment.
        private void RecordNewBinding(PackageXmlEnum propertyenum, object value)
        { 
            RecordNewBinding(propertyenum, value, false /* not invoked at construction */, null);
        } 
 
        // Set new property value.
        // Null value is passed for deleting a property. 
        // While initializing, we are not assigning new values, and so the dirty flag should
        // stay untouched.
        private void RecordNewBinding(PackageXmlEnum propertyenum, object value, bool initializing, XmlTextReader reader)
        { 
            // If we are reading values from the package, reader cannot be null
            Debug.Assert(!initializing || reader != null); 
 
            if (!initializing)
                _package.ThrowIfReadOnly(); 

            // Case of an existing property.
            if (_propertyDictionary.ContainsKey(propertyenum))
            { 
                // Parsing should detect redundant entries.
                if (initializing) 
                { 
                    throw new XmlException(SR.Get(SRID.DuplicateCorePropertyName, reader.Name),
                        null, reader.LineNumber, reader.LinePosition); 
                }

                // No edit of existing properties in streaming production.
                if (_package.InStreamingCreation) 
                    throw new InvalidOperationException(SR.Get(SRID.OperationViolatesWriteOnceSemantics));
 
                // Nullable values can be checked against null 
                if (value == null) // a deletion
                { 
                    _propertyDictionary.Remove(propertyenum);
                }
                else // an update
                { 
                    _propertyDictionary[propertyenum] = value;
                } 
                // If the binding is an assignment rather than an initialization, set the dirty flag. 
                _dirty = !initializing;
            } 
            // Case of a null value in the absence of an existing property.
            else if (value == null)
            {
                // Even thinking about setting a property to null in streaming production is a no-no. 
                if (_package.InStreamingCreation)
                    throw new InvalidOperationException(SR.Get(SRID.OperationViolatesWriteOnceSemantics)); 
 
                // Outside of streaming production, deleting a non-existing property is a no-op.
            } 
            // Case of an initial value being set for a property.
            else
            {
                _propertyDictionary.Add(propertyenum, value); 
                // If the binding is an assignment rather than an initialization, set the dirty flag.
                _dirty = !initializing; 
            } 
        }
 
        // Initialize object from property values found in package.
        // All values will remain null if the package is not enabled for reading.
        private void ReadPropertyValuesFromPackage()
        { 
            Invariant.Assert(_propertyPart == null); // This gets called exclusively from constructor.
 
            // Don't try to read properties from the package it does not have read access 
            if (_package.FileOpenAccess == FileAccess.Write)
                return; 

            _propertyPart = GetPropertyPart();
            if (_propertyPart == null)
                return; 

            ParseCorePropertyPart(_propertyPart); 
        } 

        // Locate core properties part using the package relationship that points to it. 
        private PackagePart GetPropertyPart()
        {
            // Find a package-wide relationship of type CoreDocumentPropertiesRelationshipType.
            PackageRelationship corePropertiesRelationship = GetCorePropertiesRelationship(); 
            if (corePropertiesRelationship == null)
                return null; 
 
            // Retrieve the part referenced by its target URI.
            if (corePropertiesRelationship.TargetMode != TargetMode.Internal) 
                throw new FileFormatException(SR.Get(SRID.NoExternalTargetForMetadataRelationship));

            PackagePart propertiesPart = null;
            Uri propertiesPartUri = PackUriHelper.ResolvePartUri( 
                PackUriHelper.PackageRootUri,
                corePropertiesRelationship.TargetUri); 
 
            if (!_package.PartExists(propertiesPartUri))
                throw new FileFormatException(SR.Get(SRID.DanglingMetadataRelationship)); 

            propertiesPart = _package.GetPart(propertiesPartUri);
            if (!propertiesPart.ValidatedContentType.AreTypeAndSubTypeEqual(_coreDocumentPropertiesContentType))
            { 
                throw new FileFormatException(SR.Get(SRID.WrongContentTypeForPropertyPart));
            } 
 
            return propertiesPart;
        } 

        // Find a package-wide relationship of type CoreDocumentPropertiesRelationshipType.
        private PackageRelationship GetCorePropertiesRelationship()
        { 
            PackageRelationship propertiesPartRelationship = null;
            foreach (PackageRelationship rel 
                in _package.GetRelationshipsByType(_coreDocumentPropertiesRelationshipType)) 
            {
                if (propertiesPartRelationship != null) 
                {
                    throw new FileFormatException(SR.Get(SRID.MoreThanOneMetadataRelationships));
                }
                propertiesPartRelationship = rel; 
            }
            return propertiesPartRelationship; 
        } 

        // Deserialize properties part. 
        private void ParseCorePropertyPart(PackagePart part)
        {
            Stream stream = part.GetStream(FileMode.Open, FileAccess.Read);
 
            // Create a reader that uses _nameTable so as to use the set of tag literals
            // in effect as a set of atomic identifiers. 
            XmlTextReader reader = new XmlTextReader(stream, _nameTable); 

            //Prohibit DTD from the markup as per the OPC spec 
            reader.ProhibitDtd = true;

            //This method expects the reader to be in ReadState.Initial.
            //It will make the first read call. 
            PackagingUtilities.PerformInitailReadAndVerifyEncoding(reader);
 
            //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 
            if ( reader.MoveToContent() != XmlNodeType.Element
                || (object)reader.NamespaceURI != PackageXmlStringTable.GetXmlStringAsObject(PackageXmlEnum.PackageCorePropertiesNamespace)
                || (object)reader.LocalName != PackageXmlStringTable.GetXmlStringAsObject(PackageXmlEnum.CoreProperties))
            { 
                throw new XmlException(SR.Get(SRID.CorePropertiesElementExpected),
                    null, reader.LineNumber, reader.LinePosition); 
            } 

            // The schema is closed and defines no attributes on the root element. 
            if (PackagingUtilities.GetNonXmlnsAttributeCount(reader) != 0)
            {
                throw new XmlException(SR.Get(SRID.PropertyWrongNumbOfAttribsDefinedOn, reader.Name),
                    null, reader.LineNumber, reader.LinePosition); 
            }
 
            // Iterate through property elements until EOF. Note the proper closing of all 
            // open tags is checked by the reader itself.
            // This loop deals only with depth-1 start tags. Handling of element content 
            // is delegated to dedicated functions.
            int attributesCount;

            while (reader.Read() && reader.MoveToContent() != XmlNodeType.None) 
            {
                // Ignore end-tags. We check element errors on opening tags. 
                if (reader.NodeType == XmlNodeType.EndElement) 
                    continue;
 
                // Any content markup that is not an element here is unexpected.
                if (reader.NodeType != XmlNodeType.Element)
                    throw new XmlException(SR.Get(SRID.PropertyStartTagExpected),
                        null, reader.LineNumber, reader.LinePosition); 

                // Any element below the root should open at level 1 exclusively. 
                if (reader.Depth != 1) 
                    throw new XmlException(SR.Get(SRID.NoStructuredContentInsideProperties),
                        null, reader.LineNumber, reader.LinePosition); 

                attributesCount = PackagingUtilities.GetNonXmlnsAttributeCount(reader);

                // Property elements can occur in any order (xsd:all). 
                object localName = reader.LocalName;
                PackageXmlEnum xmlStringIndex = PackageXmlStringTable.GetEnumOf(localName); 
                String valueType = PackageXmlStringTable.GetValueType(xmlStringIndex); 

                if (Array.IndexOf(_validProperties, xmlStringIndex) == -1)  // An unexpected element is an error. 
                {
                    throw new XmlException(
                        SR.Get(SRID.InvalidPropertyNameInCorePropertiesPart, reader.LocalName),
                        null, reader.LineNumber, reader.LinePosition); 
                }
 
                // Any element not in the valid core properties namespace is unexpected. 
                // The following is an object comparison, not a string comparison.
                if ((object)reader.NamespaceURI != PackageXmlStringTable.GetXmlStringAsObject(PackageXmlStringTable.GetXmlNamespace(xmlStringIndex))) 
                    throw new XmlException(SR.Get(SRID.UnknownNamespaceInCorePropertiesPart),
                        null, reader.LineNumber, reader.LinePosition);

                if (String.CompareOrdinal(valueType, "String") == 0) 
                {
                    // The schema is closed and defines no attributes on this type of element. 
                    if (attributesCount != 0) 
                    {
                        throw new XmlException(SR.Get(SRID.PropertyWrongNumbOfAttribsDefinedOn, reader.Name), 
                            null, reader.LineNumber, reader.LinePosition);
                    }

                    RecordNewBinding(xmlStringIndex, GetStringData(reader), true /*initializing*/, reader); 
                }
                else if (String.CompareOrdinal(valueType, "DateTime") == 0) 
                { 
                    int allowedAttributeCount = (object) reader.NamespaceURI ==
                                                        PackageXmlStringTable.GetXmlStringAsObject(PackageXmlEnum.DublinCoreTermsNamespace) 
                                                    ? 1 : 0;

                    // The schema is closed and defines no attributes on this type of element.
                    if (attributesCount != allowedAttributeCount) 
                    {
                        throw new XmlException(SR.Get(SRID.PropertyWrongNumbOfAttribsDefinedOn, reader.Name), 
                            null, reader.LineNumber, reader.LinePosition); 
                    }
 
                    if (allowedAttributeCount != 0)
                    {
                        ValidateXsiType(reader,
                            PackageXmlStringTable.GetXmlStringAsObject(PackageXmlEnum.DublinCoreTermsNamespace), 
                            _w3cdtf);
                    } 
 
                    RecordNewBinding(xmlStringIndex, GetDateData(reader), true /*initializing*/, reader);
                } 
                else  // An unexpected element is an error.
                {
                    Invariant.Assert(false, "Unknown value type for properties");
                } 
            }
        } 
 
        // This method validates xsi:type="dcterms:W3CDTF"
        // The valude of xsi:type is a qualified name. It should have a prefix that matches 
        //  the xml namespace (ns) within the scope and the name that matches name
        // The comparisons should be case-sensitive comparisons
        internal static void ValidateXsiType(XmlTextReader reader, Object ns, string name)
        { 
            // Get the value of xsi;type
            String typeValue = reader.GetAttribute(PackageXmlStringTable.GetXmlString(PackageXmlEnum.Type), 
                                PackageXmlStringTable.GetXmlString(PackageXmlEnum.XmlSchemaInstanceNamespace)); 

            // Missing xsi:type 
            if (typeValue == null)
            {
                throw new XmlException(SR.Get(SRID.UnknownDCDateTimeXsiType, reader.Name),
                    null, reader.LineNumber, reader.LinePosition); 
            }
 
            int index = typeValue.IndexOf(':'); 

            // The valude of xsi:type is not a qualified name 
            if (index == -1)
            {
                throw new XmlException(SR.Get(SRID.UnknownDCDateTimeXsiType, reader.Name),
                    null, reader.LineNumber, reader.LinePosition); 
            }
 
            // Check the following conditions 
            //  The namespace of the prefix (string before ":") matches "ns"
            //  The name (string after ":") matches "name" 
            if (!Object.ReferenceEquals(ns, reader.LookupNamespace(typeValue.Substring(0, index)))
                    || String.CompareOrdinal(name, typeValue.Substring(index + 1, typeValue.Length - index - 1)) != 0)
            {
                throw new XmlException(SR.Get(SRID.UnknownDCDateTimeXsiType, reader.Name), 
                    null, reader.LineNumber, reader.LinePosition);
            } 
 

 
        }

        // Expect to find text data and return its value.
        private string GetStringData(XmlTextReader reader) 
        {
            if (reader.IsEmptyElement) 
                return string.Empty; 

            reader.Read(); 
            if (reader.MoveToContent() == XmlNodeType.EndElement)
                return string.Empty;

            // If there is any content in the element, it should be text content and nothing else. 
            if (reader.NodeType != XmlNodeType.Text)
                throw new XmlException(SR.Get(SRID.NoStructuredContentInsideProperties), 
                    null, reader.LineNumber, reader.LinePosition); 

            return reader.Value; 
        }

        // Expect to find text data and return its value as DateTime.
        private Nullable GetDateData(XmlTextReader reader) 
        {
            string data = GetStringData(reader); 
            DateTime dateTime; 

            try 
            {
                // Note: No more than 7 second decimals are accepted by the
                // list of formats given. There currently is no method that
                // would perform XSD-compliant parsing. 
                dateTime = XmlConvert.ToDateTime(data, _dateTimeFormats);
            } 
            catch (FormatException exc) 
            {
                throw new XmlException(SR.Get(SRID.XsdDateTimeExpected), 
                    exc, reader.LineNumber, reader.LinePosition);
            }
            return dateTime;
        } 

        // Make sure there is a part to write to and that it contains 
        // the expected start markup. 
        private void EnsureXmlWriter()
        { 
            // It does not make sense to reuse a writer outside of streaming creation.
            if (!_package.InStreamingCreation)
                Invariant.Assert(_xmlWriter == null);
 
            if (_xmlWriter != null)
                return; 
 
            EnsurePropertyPart(); // Should succeed or throw an exception.
 
            Stream writerStream;

            if(_package.InStreamingCreation)
                writerStream = _propertyPart.GetStream(FileMode.Create, FileAccess.Write); 
            else
                writerStream = new IgnoreFlushAndCloseStream(_propertyPart.GetStream(FileMode.Create, FileAccess.Write)); 
 
            _xmlWriter = new XmlTextWriter(writerStream, System.Text.Encoding.UTF8);
 
            WriteXmlStartTagsForPackageProperties();
        }

        // Create a property part if none exists yet. 
        private void EnsurePropertyPart()
        { 
            if (_propertyPart != null) 
                return;
 
            // If _propertyPart is null, no property part existed when this object was created,
            // and this function is being called for the first time.
            // However, when read access is available, we can afford the luxury of checking whether
            // a property part and its referring relationship got correctly created in the meantime 
            // outside of this class.
            // In write-only mode, it is impossible to perform this check, and the external creation 
            // scenario will result in an exception being thrown. 
            if (_package.FileOpenAccess == FileAccess.Read || _package.FileOpenAccess == FileAccess.ReadWrite)
            { 
                _propertyPart = GetPropertyPart();
                if (_propertyPart != null)
                    return;
            } 

            CreatePropertyPart(); 
        } 

        // Create a new property relationship pointing to a new property part. 
        // If only this class is used for manipulating property relationships, there cannot be a
        // pre-existing dangling property relationship.
        // No check is performed here for other classes getting misused insofar as this function
        // has to work in write-only mode. 
        private void CreatePropertyPart()
        { 
            _propertyPart = _package.CreatePart(GeneratePropertyPartUri(), _coreDocumentPropertiesContentType.ToString()); 
            _package.CreateRelationship(_propertyPart.Uri, TargetMode.Internal,
                _coreDocumentPropertiesRelationshipType); 
        }

        private Uri GeneratePropertyPartUri()
        { 
            string propertyPartName = _defaultPropertyPartNamePrefix
                + Guid.NewGuid().ToString(_guidStorageFormatString, (IFormatProvider)null) 
                + _defaultPropertyPartNameExtension; 

            return PackUriHelper.CreatePartUri(new Uri(propertyPartName, UriKind.Relative)); 
        }

        private void WriteXmlStartTagsForPackageProperties()
        { 
            _xmlWriter.WriteStartDocument();
 
            // [] entriesToNullify = null;
            int numEntriesToNullify = 0; 
            if (_package.InStreamingCreation)
                entriesToNullify = new KeyValuePair[_propertyDictionary.Count];

            // Create a property element for each non-null entry. 
            foreach (KeyValuePair entry in _propertyDictionary)
            { 
                // If we are NOT in streaming mode, the property value should NOT be null 
                Debug.Assert(entry.Value != null || _package.InStreamingCreation);
 
                if (_package.InStreamingCreation
                    && entry.Value == null) // already saved
                {
                    continue; 
                }
 
 
                PackageXmlEnum propertyNamespace = PackageXmlStringTable.GetXmlNamespace(entry.Key);
 
                _xmlWriter.WriteStartElement(PackageXmlStringTable.GetXmlString(entry.Key),
                                                PackageXmlStringTable.GetXmlString(propertyNamespace));

                if (entry.Value is Nullable) 
                {
                    if (propertyNamespace == PackageXmlEnum.DublinCoreTermsNamespace) 
                    { 
                        // xsi:type=
                        _xmlWriter.WriteStartAttribute(PackageXmlStringTable.GetXmlString(PackageXmlEnum.Type), 
                                                        PackageXmlStringTable.GetXmlString(PackageXmlEnum.XmlSchemaInstanceNamespace));

                        // "dcterms:W3CDTF"
                        _xmlWriter.WriteQualifiedName(_w3cdtf, 
                                                        PackageXmlStringTable.GetXmlString(PackageXmlEnum.DublinCoreTermsNamespace));
 
                        _xmlWriter.WriteEndAttribute(); 
                    }
 
                    // Use sortable ISO 8601 date/time pattern. Include second fractions down to the 100-nanosecond interval,
                    // which is the definition of a "tick" for the DateTime type.
                    _xmlWriter.WriteString(XmlConvert.ToString(((Nullable)entry.Value).Value.ToUniversalTime(), "yyyy-MM-ddTHH:mm:ss.fffffffZ"));
                } 
                else
                { 
                    // The following uses the fact that ToString is virtual. 
                    _xmlWriter.WriteString(entry.Value.ToString());
                } 

                _xmlWriter.WriteEndElement();

                if (_package.InStreamingCreation) 
                    entriesToNullify[numEntriesToNullify++] = entry;
            } 
 
            // Mark properties as saved.
            _dirty = false; 

            // Detailed marking of saved properties for the streaming mode.
            if (_package.InStreamingCreation)
            { 
                for (int i = 0; i < numEntriesToNullify; ++i)
                { 
                    _propertyDictionary[entriesToNullify[i].Key] = null; 
                }
            } 
        }

        // Add end markup and close the writer.
        private void CloseXmlWriter() 
        {
            // Close the root element. 
            _xmlWriter.WriteEndElement(); 

            // Close the writer itself. 
            _xmlWriter.Close();

            // Make sure we know it's closed.
            _xmlWriter = null; 
        }
 
        #endregion Private Methods 

        //----------------------------------------------------- 
        //
        //   Private fields
        //
        //------------------------------------------------------ 

        #region Private Fields 
 
        private Package     _package;
        private PackagePart _propertyPart; 
        private XmlTextWriter _xmlWriter;

        // Table of objects from the closed set of literals defined below.
        // (Uses object comparison rather than string comparison.) 
        private const int   _numCoreProperties = 16;
        private Dictionary _propertyDictionary = new Dictionary(_numCoreProperties); 
        private bool        _dirty = false; 

        // This System.Xml.NameTable makes sure that we use the same references to strings 
        // throughout (including when parsing Xml) and so can perform reference comparisons
        // rather than value comparisons.
        private NameTable   _nameTable;
 
        // Literals.
        private static readonly ContentType _coreDocumentPropertiesContentType 
            = new ContentType("application/vnd.openxmlformats-package.core-properties+xml"); 
        private const string _coreDocumentPropertiesRelationshipType
            = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"; 

        private const string _defaultPropertyPartNamePrefix =
            "/package/services/metadata/core-properties/";
        private const string _w3cdtf = "W3CDTF"; 
        private const string _defaultPropertyPartNameExtension = ".psmdcp";
        private const string _guidStorageFormatString = @"N";     // N - simple format without adornments 
 
        private static PackageXmlEnum[] _validProperties = new PackageXmlEnum[] {
                                                                                PackageXmlEnum.Creator, 
                                                                                PackageXmlEnum.Identifier,
                                                                                PackageXmlEnum.Title,
                                                                                PackageXmlEnum.Subject,
                                                                                PackageXmlEnum.Description, 
                                                                                PackageXmlEnum.Language,
                                                                                PackageXmlEnum.Created, 
                                                                                PackageXmlEnum.Modified, 
                                                                                PackageXmlEnum.ContentType,
                                                                                PackageXmlEnum.Keywords, 
                                                                                PackageXmlEnum.Category,
                                                                                PackageXmlEnum.Version,
                                                                                PackageXmlEnum.LastModifiedBy,
                                                                                PackageXmlEnum.ContentStatus, 
                                                                                PackageXmlEnum.Revision,
                                                                                PackageXmlEnum.LastPrinted 
                                                                                }; 

        // Array of formats to supply to XmlConvert.ToDateTime or DateTime.ParseExact. 
        // xsd:DateTime requires full date time in sortable (ISO 8601) format.
        // It can be expressed in local time, universal time (Z), or relative to universal time (zzz).
        // Negative years are accepted.
        // IMPORTANT: Second fractions are recognized only down to 1 tenth of a microsecond because this is the resolution 
        // of the DateTime type. The Xml standard, however, allows any number of decimals; but XmlConvert only offers
        // this very awkward API with an explicit pattern enumeration. 
        private static readonly string[] _dateTimeFormats = new string[] { 
            "yyyy-MM-ddTHH:mm:ss",
            "yyyy-MM-ddTHH:mm:ssZ", 
            "yyyy-MM-ddTHH:mm:sszzz",
            @"\-yyyy-MM-ddTHH:mm:ss",
            @"\-yyyy-MM-ddTHH:mm:ssZ",
            @"\-yyyy-MM-ddTHH:mm:sszzz", 

            "yyyy-MM-ddTHH:mm:ss.ff", 
            "yyyy-MM-ddTHH:mm:ss.fZ", 
            "yyyy-MM-ddTHH:mm:ss.fzzz",
            @"\-yyyy-MM-ddTHH:mm:ss.f", 
            @"\-yyyy-MM-ddTHH:mm:ss.fZ",
            @"\-yyyy-MM-ddTHH:mm:ss.fzzz",

            "yyyy-MM-ddTHH:mm:ss.ff", 
            "yyyy-MM-ddTHH:mm:ss.ffZ",
            "yyyy-MM-ddTHH:mm:ss.ffzzz", 
            @"\-yyyy-MM-ddTHH:mm:ss.ff", 
            @"\-yyyy-MM-ddTHH:mm:ss.ffZ",
            @"\-yyyy-MM-ddTHH:mm:ss.ffzzz", 

            "yyyy-MM-ddTHH:mm:ss.fff",
            "yyyy-MM-ddTHH:mm:ss.fffZ",
            "yyyy-MM-ddTHH:mm:ss.fffzzz", 
            @"\-yyyy-MM-ddTHH:mm:ss.fff",
            @"\-yyyy-MM-ddTHH:mm:ss.fffZ", 
            @"\-yyyy-MM-ddTHH:mm:ss.fffzzz", 

            "yyyy-MM-ddTHH:mm:ss.ffff", 
            "yyyy-MM-ddTHH:mm:ss.ffffZ",
            "yyyy-MM-ddTHH:mm:ss.ffffzzz",
            @"\-yyyy-MM-ddTHH:mm:ss.ffff",
            @"\-yyyy-MM-ddTHH:mm:ss.ffffZ", 
            @"\-yyyy-MM-ddTHH:mm:ss.ffffzzz",
 
            "yyyy-MM-ddTHH:mm:ss.fffff", 
            "yyyy-MM-ddTHH:mm:ss.fffffZ",
            "yyyy-MM-ddTHH:mm:ss.fffffzzz", 
            @"\-yyyy-MM-ddTHH:mm:ss.fffff",
            @"\-yyyy-MM-ddTHH:mm:ss.fffffZ",
            @"\-yyyy-MM-ddTHH:mm:ss.fffffzzz",
 
            "yyyy-MM-ddTHH:mm:ss.ffffff",
            "yyyy-MM-ddTHH:mm:ss.ffffffZ", 
            "yyyy-MM-ddTHH:mm:ss.ffffffzzz", 
            @"\-yyyy-MM-ddTHH:mm:ss.ffffff",
            @"\-yyyy-MM-ddTHH:mm:ss.ffffffZ", 
            @"\-yyyy-MM-ddTHH:mm:ss.ffffffzzz",

            "yyyy-MM-ddTHH:mm:ss.fffffff",
            "yyyy-MM-ddTHH:mm:ss.fffffffZ", 
            "yyyy-MM-ddTHH:mm:ss.fffffffzzz",
            @"\-yyyy-MM-ddTHH:mm:ss.fffffff", 
            @"\-yyyy-MM-ddTHH:mm:ss.fffffffZ", 
            @"\-yyyy-MM-ddTHH:mm:ss.fffffffzzz",
        }; 

        #endregion Private Fields
     }
} 


// 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