|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
/*++
Abstract:
This file contains the definition for IXpsFixedDocumentSequenceReader
and IXpsFixedDocumentSequenceWriter interfaces as well as definition
and implementation of XpsFixedDocumentSequenceReaderWriter. These
interfaces and class are used for writing document sequence
parts to a Xps package.
--*/
using System.IO;
using System.Collections.ObjectModel;
using System.IO.Packaging;
using System.Xml;
using System.Printing;
using MS.Internal.IO.Packaging.Extensions;
using PackUriHelper = System.IO.Packaging.PackUriHelper;
namespace System.Windows.Xps.Packaging
{
#region IXpsFixedDocumentSequenceReader interface definition
/// <summary>
/// Interface for reading document sequence parts from a Xps package.
/// </summary>
/// <remarks>Class will be internal until reading/de-serialization is implemented.</remarks>
public interface IXpsFixedDocumentSequenceReader
{
#region Public methods
/// <summary>
/// This method retrieves a fixed document part from a Xps
/// package using the given URI.
/// </summary>
/// <param name="documentSource">
/// The URI of the fixed document to retrieve.
/// </param>
/// <returns>
/// Returns an interface to an IXpsFixedDocumentReader to read
/// the requested fixed document.
/// </returns>
IXpsFixedDocumentReader
GetFixedDocument(
Uri documentSource
);
#endregion Public methods
#region Public properties
/// <summary>
/// Gets the PrintTicket associated with this document sequence.
/// </summary>
/// <value>Value can be a PrintTicket or null.</value>
/// <exception cref="SR.ReachPackaging_PrintTicketAlreadyCommitted">PrintTicket has already been committed.</exception>
/// <exception cref="SR.ReachPackaging_NotAPrintTicket">Property is not a valid PrintTicket instance.</exception>
PrintTicket PrintTicket { get; }
/// <summary>
/// Gets the URI assigned to this document sequence part.
/// </summary>
/// <value>Value is a URI for the Metro part.</value>
Uri Uri { get; }
/// <summary>
/// Gets a collection of all fixed documents that are contained within
/// this document sequence.
/// </summary>
/// <value>
/// Value is a Collection containing IXpsFixedDocumentReader interfaces.
/// </value>
ReadOnlyCollection<IXpsFixedDocumentReader> FixedDocuments { get; }
/// <summary>
/// thumbnail image associated with this sequence
/// </summary>
XpsThumbnail
Thumbnail{ get; }
#endregion Public properties
}
#endregion IXpsFixedDocumentSequenceReader interface definition
#region IXpsFixedDocumentSequenceWriter interface definition
/// <summary>
/// Interface for writing a document sequence to the Xps package.
/// </summary>
public interface IXpsFixedDocumentSequenceWriter
{
#region Public methods
/// <summary>
/// This method adds a fixed document part to the Xps package
/// and associates it with the current document sequence.
/// </summary>
/// <returns>
/// Returns an interface to the newly created fixed document.
/// </returns>
/// <exception cref="SR.ReachPackaging_PanelOrSequenceAlreadyOpen">Current DocumentSequence not completed.</exception>
IXpsFixedDocumentWriter
AddFixedDocument(
);
/// <summary>
/// This method adds a thumbnail to the current DocumentSequence.
///
/// There can only be one thumbnail attached to the DocumentSequence.
/// Calling this method when there
/// is already a starting part causes InvalidOperationException to be
/// thrown.
/// </summary>
XpsThumbnail
AddThumbnail(
XpsImageType imageType
);
/// <summary>
/// This method commits any changes not already committed for this
/// document sequence.
///
/// </summary>
void
Commit(
);
#endregion Public methods
#region Public properties
/// <summary>
/// Sets the PrintTicket associated with this document sequence.
/// </summary>
/// <value>
/// The value must be a valid PrintTicket instance.
///
/// Note: The PrintTicket can only be assigned to prior to if being
/// committed to the package. The commit happens when a valid PrintTicket
/// is set and a subsequent flush on the document occurs.
/// </value>
/// <exception cref="SR.ReachPackaging_PrintTicketAlreadyCommitted">PrintTicket has already been committed.</exception>
/// <exception cref="SR.ReachPackaging_NotAPrintTicket">Property is not a valid PrintTicket instance.</exception>
PrintTicket PrintTicket { set; }
/// <summary>
/// Gets the URI assigned to this document sequence part.
/// </summary>
/// <value>Value is a URI for the Metro part.</value>
Uri Uri { get; }
#endregion Public properties
}
#endregion IXpsFixedDocumentSequenceWriter interface definition
/// <summary>
/// This class implements the reading and writing functionality for
/// a document sequence within a Xps package.
/// </summary>
internal sealed class XpsFixedDocumentSequenceReaderWriter : XpsPartBase,
IXpsFixedDocumentSequenceReader,
IXpsFixedDocumentSequenceWriter,
INode,
IDisposable
{
#region Constructors
/// <summary>
/// Internal constructor for the XpsFixedDocumentSequenceReaderWriter class.
/// This class is created internally so we can keep the Xps hierarchy
/// completely under control when using these APIs.
/// </summary>
/// <param name="xpsManager">
/// The XpsManager for the current Xps package.
/// </param>
/// <param name="parent">
/// The parent node of this document.
/// </param>
/// <param name="part">
/// The internal Metro part that represents this document sequence.
/// </param>
/// <exception cref="ArgumentNullException">part is null.</exception>
internal
XpsFixedDocumentSequenceReaderWriter(
XpsManager xpsManager,
INode parent,
PackagePart part
)
: base(xpsManager)
{
ArgumentNullException.ThrowIfNull(part);
this.Uri = part.Uri;
_metroPart = part;
_partEditor = new XmlPartEditor(_metroPart);
_documentCache = new List<IXpsFixedDocumentReader>();
_documentsWritten = 0;
_parentNode = parent;
_hasParsedDocuments = false;
}
#endregion Constructors
#region Public properties
/// <summary>
/// Gets or sets the PrintTicket to be stored with this document
/// sequence.
///
/// NOTE: The PrintTicket can only be assigned to prior to if being
/// committed to the package. The commit happens when a valid PrintTicket
/// is set and a subsequent flush on the document occurs.
/// </summary>
/// <value>Value can be a PrintTicket or null.</value>
/// <exception cref="SR.ReachPackaging_PrintTicketAlreadyCommitted">PrintTicket has already been committed.</exception>
/// <exception cref="SR.ReachPackaging_NotAPrintTicket">Property is not a valid PrintTicket instance.</exception>
public PrintTicket PrintTicket
{
get
{
if( _printTicket == null )
{
_printTicket = CurrentXpsManager.EnsurePrintTicket(Uri);
}
return _printTicket;
}
set
{
if(value != null)
{
if (_isPrintTicketCommitted)
{
throw new XpsPackagingException(SR.ReachPackaging_PrintTicketAlreadyCommitted);
}
if (!value.GetType().Equals(typeof(PrintTicket)))
{
throw new XpsPackagingException(SR.ReachPackaging_NotAPrintTicket);
}
_printTicket = value.Clone();
}
else
{
_printTicket = null;
}
}
}
/// <summary>
/// Gets a collection of all fixed documents that are contained within
/// this document sequence.
/// </summary>
/// <value>
/// Value is a Collection containing IXpsFixedDocumentReader interfaces.
/// </value>
/// <remarks>
/// This is only internal until it is implemented.
/// </remarks>
public ReadOnlyCollection<IXpsFixedDocumentReader> FixedDocuments
{
get
{
UpdateDocumentCache();
return new ReadOnlyCollection<IXpsFixedDocumentReader>(_documentCache);
}
}
/// <summary>
/// thumbnail image associated with this sequence
/// </summary>
public
XpsThumbnail
Thumbnail
{
get
{
EnsureThumbnail();
return _thumbnail;
}
}
#endregion Public properties
#region Public methods
/// <summary>
/// This method adds a fixed document part to the Xps package
/// and associates it with the current document sequence.
/// </summary>
/// <returns>
/// Returns an interface to the newly created fixed document.
/// </returns>
/// <exception cref="SR.ReachPackaging_PanelOrSequenceAlreadyOpen">Current DocumentSequence not completed.</exception>
public
IXpsFixedDocumentWriter
AddFixedDocument(
)
{
//
// Create the part and coresponding writer
//
PackagePart metroPart = CurrentXpsManager.GenerateUniquePart(XpsS0Markup.FixedDocumentContentType);
XpsFixedDocumentReaderWriter fixedDocument = new XpsFixedDocumentReaderWriter(CurrentXpsManager, this, metroPart,_documentsWritten + 1);
//Here we used to add the fixed document to _documentCache, but _documentCache is never accessed if this object was created as an IXpsFixedDocumentSequenceWriter.
//So instead keep a separate documentsWritten count and forget about the cache when using this method.
_documentsWritten++;
//
// Write out FixedDocument markup
//
AddDocumentToSequence(fixedDocument.Uri);
return fixedDocument;
}
/// <summary>
/// This method adds a thumbnail to the current DocumentSequence.
///
/// There can only be one thumbnail attached to the DocumentSequence.
/// Calling this method when there
/// is already a starting part causes InvalidOperationException to be
/// thrown.
/// </summary>
/// <returns>Returns a XpsThumbnail instance.</returns>
public
XpsThumbnail
AddThumbnail(
XpsImageType imageType
)
{
_thumbnail = CurrentXpsManager.AddThumbnail( imageType, this, Thumbnail );
_metroPart.CreateRelationship( _thumbnail.Uri,
TargetMode.Internal,
XpsS0Markup.ThumbnailRelationshipName
);
return _thumbnail;
}
/// <summary>
/// This method retrieves a fixed document part from a Xps
/// package using the given URI.
/// </summary>
/// <param name="documentUri">
/// The URI of the fixed document to retrieve.
/// </param>
/// <returns>
/// Returns an interface to an IXpsFixedDocumentReader to read
/// the requested fixed document.
/// </returns>
public
IXpsFixedDocumentReader
GetFixedDocument(
Uri documentUri
)
{
UpdateDocumentCache();
IXpsFixedDocumentReader fixedDocument = null;
//
// Check the cache for the requested document
//
foreach (IXpsFixedDocumentReader reader in _documentCache)
{
if( documentUri == reader.Uri )
{
fixedDocument = reader; }
}
return fixedDocument;
}
/// <summary>
/// This method commits any changes not already committed for this
/// document sequence.
///
/// NOTE: This commits changes on all child objects underneath this
/// branch. No further changes will be allowed.
/// </summary>
public
void
Commit(
)
{
CommitInternal();
}
/// <summary>
/// This method closes streams and frees memory for this
/// document sequence.
/// </summary>
internal
override
void
CommitInternal(
)
{
CommitPrintTicket();
if (null != _partEditor)
{
((INode)this).Flush();
_partEditor.Close();
_partEditor = null;
_metroPart = null;
_documentCache = null;
_documentsWritten = 0;
_thumbnail = null;
_parentNode = null;
_hasParsedDocuments = false;
}
}
/// <summary>
/// Adds itself and and its reationship if it exists
/// Adds dependent part Uris to the passed list following
/// the passed restrictions dependents
/// include pages, annotaions, properties, and signatures
/// </summary>
internal
void
CollectSelfAndDependents(
Dictionary<Uri,Uri> dependentList,
List<PackageRelationshipSelector> selectorList,
XpsDigSigPartAlteringRestrictions restrictions
)
{
//
// Add my self
//
dependentList[Uri] = Uri;
//
// Add my dependents
//
CollectDependents( dependentList, selectorList, restrictions);
}
internal
void
CollectXmlPartsAndDepenedents(
List<PackagePart> xmlPartList
)
{
//
// Add my self to be tested for V&E Markup
//
xmlPartList.Add(_metroPart);
UpdateDocumentCache();
// Add all documents
foreach (IXpsFixedDocumentReader reader in _documentCache)
{
(reader as XpsFixedDocumentReaderWriter).CollectXmlPartsAndDepenedents(xmlPartList);
}
}
#endregion Public methods
#region Private methods
private
void
AddDocumentToSequence(
Uri partUri
)
{
_partEditor.PrepareXmlWriter(XpsS0Markup.FixedDocumentSequence, XpsS0Markup.DocumentSequenceNamespace);
XmlTextWriter xmlWriter = _partEditor.XmlWriter;
//
// Write <Item Target="partUri"/>
//
String relativePath = XpsManager.MakeRelativePath(Uri, partUri);
xmlWriter.WriteStartElement(XpsS0Markup.DocumentReference);
xmlWriter.WriteAttributeString(XmlTags.Source, relativePath);
xmlWriter.WriteEndElement();
}
/// <summary>
/// This method writes the PrintTicket associated with
/// this document sequence to the Metro package.
/// </summary>
private
void
CommitPrintTicket(
)
{
//
// Flush the PrintTicket if needed
//
if (!_isPrintTicketCommitted )
{
if(null != _printTicket)
{
CurrentXpsManager.WritePrintTicket(this, _metroPart, _printTicket);
}
else
{
CurrentXpsManager.WriteEmptyPrintTicket(this, _metroPart);
}
_isPrintTicketCommitted = true;
}
}
/// <summary>
/// Test if the document cache has initialized and
/// Updates if necessary
/// </summary>
private
void
UpdateDocumentCache()
{
if( !_hasParsedDocuments )
{
ParseDocuments();
_hasParsedDocuments = true;
}
}
/// <summary>
/// This method parses the part pulling out the Document Referneces
/// and populates the _documentCache
/// </summary>
private
void
ParseDocuments()
{
using (Stream stream = _metroPart.GetStream(FileMode.Open))
{
//
// If the stream is empty there are no documents to parse
//
if (stream.Length > 0)
{
XmlTextReader reader = new XmlTextReader(stream);
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == XpsS0Markup.DocumentReference)
{
string attribute = reader.GetAttribute(XmlTags.Source);
if (attribute != null)
{
Uri relativeUri = new Uri(attribute, UriKind.Relative);
//This routine properly adds DocumentReaderWriter to the _documentCache
AddDocumentToCache(PackUriHelper.ResolvePartUri(Uri, relativeUri));
}
}
}
}
}
}
/// <summary>
/// Adds dependent part Uris to the passed list
/// </summary>
private
void
CollectDependents(
Dictionary<Uri,Uri> dependents,
List<PackageRelationshipSelector> selectorList,
XpsDigSigPartAlteringRestrictions restrictions
)
{
UpdateDocumentCache();
// Add all documents
foreach( IXpsFixedDocumentReader reader in _documentCache)
{
(reader as XpsFixedDocumentReaderWriter).
CollectSelfAndDependents(
dependents,
selectorList,
restrictions);
}
}
private
void
EnsureThumbnail()
{
if( _thumbnail == null )
{
_thumbnail = CurrentXpsManager.EnsureThumbnail( this, _metroPart );
}
}
private
IXpsFixedDocumentReader
AddDocumentToCache(Uri documentUri)
{
//
// Retrieve the requested part from the package
//
PackagePart documentPart = CurrentXpsManager.GetPart(documentUri);
if (documentPart == null)
{
throw new XpsPackagingException(SR.ReachPackaging_PartNotFound);
}
//
// If the part is not a fixed document then throw an exception
//
if (!documentPart.ValidatedContentType().AreTypeAndSubTypeEqual(XpsS0Markup.FixedDocumentContentType))
{
throw new XpsPackagingException(SR.ReachPackaging_NotAFixedDocument);
}
//
// Create the reader/writer for the part
//
IXpsFixedDocumentReader fixedDocument = new XpsFixedDocumentReaderWriter(CurrentXpsManager, null, documentPart, _documentCache.Count+1);
//
// Cache the new reader/writer for later
//
_documentCache.Add( fixedDocument );
return fixedDocument;
}
#endregion Private methods
#region Private data
private PackagePart _metroPart;
private PrintTicket _printTicket;
private XmlPartEditor _partEditor;
private List<IXpsFixedDocumentReader>_documentCache;
//
// This variable is used to keep a count of pages written via AddFixedDocument
//
private int _documentsWritten = 0;
//
// This variable flags whether the PrintTicket property is
// committed. A writer can only commit this property once.
//
private bool _isPrintTicketCommitted;
//
// These variables are used to keep track of the parent
// and current child of this node for walking up and
// down the current tree. This is used be the flushing
// policy to do interleave flushing of parts correctly.
//
private INode _parentNode;
//
// This variable flags wehter the documentCashe
// has been populated by parsing the part for dependent documents
private bool _hasParsedDocuments;
private XpsThumbnail _thumbnail;
#endregion Private data
#region Internal properties
/// <summary>
/// Gets a reference to the XmlWriter for the internal Metro
/// part that represents this document sequence.
/// </summary>
internal System.Xml.XmlWriter XmlWriter
{
get
{
_partEditor.DoesWriteStartEndTags = false;
return _partEditor.XmlWriter;
}
}
#endregion Internal properties
#region INode implementation
void
INode.Flush(
)
{
//
// Commit the PrintTicket (if necessary)
//
CommitPrintTicket();
//
// Create the relationship between the package and the tumbnail
//
if( _thumbnail != null )
{
_thumbnail = null;
}
}
void
INode.CommitInternal(
)
{
CommitInternal();
}
PackagePart
INode.GetPart(
)
{
return _metroPart;
}
#endregion INode implementation
#region IDisposable implementation
void
IDisposable.Dispose(
)
{
CommitInternal();
}
#endregion IDisposable implementation
}
}
|