// from http://news.oreilly.com/XmlZipResolver.cs - no copyright notices or use restrictions of any kind with the original file. using System; using System.Net; using System.Xml; using System.IO; using System.Text.RegularExpressions; using ICSharpCode.SharpZipLib.Zip; namespace net.windward.utils.xml { /// /// A subclass of the XmlUrlResolver class which allows you to resolve references inside zip files. /// public class XmlZipResolver : XmlUrlResolver { private const string zipRegex = "^(jar|zip):"; private const string uriRegex = "^([^!]+)!(/[^!]+)$"; /// ///Maps a URI to an object containing the actual resource. /// 1) Tests whether the URL starts with "jar:" or "zip:" /// 2) If not, pass the URL to XmlUrlResolver (to the superclass) /// 3) If it does, then interprets it like the apache version: /// a) Strip out leading "zip:" or "jar:" /// b) Split into two strings, before and after the "!" /// c) use the first string as a ZIP file, use the second as an ZIP path to a file /// d) return a stream from that file /// ///A System.IO.Stream object or null if a type other than stream is specified. ///The current implementation does not use this parameter when resolving URIs. This is provided for future extensibility purposes. For example, this can be mapped to the xlink:role and used as an implementation specific argument in other scenarios. ///The type of object to return. The current implementation only returns System.IO.Stream objects. ///The URI returned from ///absoluteUri is null. ///The specified URI is not an absolute URI. ///There is a runtime error (for example, an interrupted server connection). ///ofObjectToReturn is neither null nor a Stream type. // There is a runtime error (for example, an interrupted server connection). public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn) { // Test whether the URL starts with "jar:" or "zip:" String uriString = absoluteUri.ToString(); Match zipMatch = Regex.Match(uriString, zipRegex); // If not a zip uri, pass the URL to XmlUrlResolver if (!zipMatch.Success) return base.GetEntity(absoluteUri, role, ofObjectToReturn); // Strip out leading "zip:" or "jar:" String stripedUriString = Regex.Replace(uriString, zipRegex, ""); // Split into two strings, before and after the "!" Match uriMatch = Regex.Match(stripedUriString, uriRegex); if (!uriMatch.Success) throw new UriFormatException("Zip URI does not have a '!' between the zip file path and " + "the path to the xml within the zip file, or path to xml does not " + "start with a '/'. Zip URI found was '" + uriString + "'"); String zipFilePath = uriMatch.Groups[1].ToString(); String xmlInZipPath = uriMatch.Groups[2].ToString(); if (xmlInZipPath.StartsWith("/") || xmlInZipPath.StartsWith("\\")) xmlInZipPath = xmlInZipPath.Substring(1); // Use the first string as a ZIP file, use the second as a ZIP path to a file using (WebClient reader = new WebClient()) { // need to do this for the self: case in particular Stream zipFileStream = null; ZipFile zipFile = null; try { if (zipFilePath.ToLower().StartsWith("file:")) zipFileStream = new FileStream(zipFilePath.Substring(5), FileMode.Open, FileAccess.Read, FileShare.ReadWrite); else zipFileStream = reader.OpenRead(zipFilePath); zipFile = new ZipFile(zipFileStream); ZipEntry entry = zipFile.GetEntry(xmlInZipPath); if (entry == null) throw new FileNotFoundException( String.Format("could not find the xml file {0} in the zip file {1}", xmlInZipPath, zipFilePath), xmlInZipPath); // return the stream to that entry. return new StreamPair(zipFile, zipFileStream, zipFile.GetInputStream(entry)); } catch (Exception) { if (zipFileStream != null) zipFileStream.Close(); if (zipFile != null) zipFile.Close(); throw; } } } } /// /// Holds both the zip file and entry stream so both can be closed/disposed when done. /// internal class StreamPair : Stream { private readonly ZipFile zipFile; private readonly Stream zipFileStream; private readonly Stream zipEntryStream; internal StreamPair(ZipFile zipFile, Stream zipFileStream, Stream zipEntryStream) { this.zipFile = zipFile; this.zipFileStream = zipFileStream; this.zipEntryStream = zipEntryStream; } /// ///Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. /// ///1 public override void Close() { zipFile.Close(); zipEntryStream.Close(); zipFileStream.Close(); } /// ///Releases the unmanaged resources used by the and optionally releases the managed resources. /// /// ///true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { if (disposing) Close(); base.Dispose(disposing); } /// ///When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device. /// /// ///An I/O error occurs. 2 public override void Flush() { zipEntryStream.Flush(); } /// ///Begins an asynchronous read operation. /// /// /// ///An that represents the asynchronous read, which could still be pending. /// /// ///The byte offset in buffer at which to begin writing data read from the stream. ///The maximum number of bytes to read. ///The buffer to read the data into. ///An optional asynchronous callback, to be called when the read is complete. ///A user-provided object that distinguishes this particular asynchronous read request from other requests. ///Attempted an asynchronous read past the end of the stream, or a disk error occurs. ///The current Stream implementation does not support the read operation. ///One or more of the arguments is invalid. ///Methods were called after the stream was closed. 2 public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { return zipEntryStream.BeginRead(buffer, offset, count, callback, state); } /// ///Waits for the pending asynchronous read to complete. /// /// /// ///The number of bytes read from the stream, between zero (0) and the number of bytes you requested. Streams return zero (0) only at the end of the stream, otherwise, they should block until at least one byte is available. /// /// ///The reference to the pending asynchronous request to finish. ///asyncResult did not originate from a method on the current stream. ///asyncResult is null. 2 public override int EndRead(IAsyncResult asyncResult) { return zipEntryStream.EndRead(asyncResult); } /// ///Begins an asynchronous write operation. /// /// /// ///An IAsyncResult that represents the asynchronous write, which could still be pending. /// /// ///The byte offset in buffer from which to begin writing. ///The maximum number of bytes to write. ///The buffer to write data from. ///An optional asynchronous callback, to be called when the write is complete. ///A user-provided object that distinguishes this particular asynchronous write request from other requests. ///The current Stream implementation does not support the write operation. ///Attempted an asynchronous write past the end of the stream, or a disk error occurs. ///One or more of the arguments is invalid. ///Methods were called after the stream was closed. 2 public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { return zipEntryStream.BeginWrite(buffer, offset, count, callback, state); } /// ///Ends an asynchronous write operation. /// /// ///A reference to the outstanding asynchronous I/O request. ///asyncResult is null. ///asyncResult did not originate from a method on the current stream. 2 public override void EndWrite(IAsyncResult asyncResult) { zipEntryStream.EndWrite(asyncResult); } /// ///When overridden in a derived class, sets the position within the current stream. /// /// /// ///The new position within the current stream. /// /// ///A byte offset relative to the origin parameter. ///A value of type indicating the reference point used to obtain the new position. ///An I/O error occurs. ///The stream does not support seeking, such as if the stream is constructed from a pipe or console output. ///Methods were called after the stream was closed. 1 public override long Seek(long offset, SeekOrigin origin) { return zipEntryStream.Seek(offset, origin); } /// ///When overridden in a derived class, sets the length of the current stream. /// /// ///The desired length of the current stream in bytes. ///The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. ///An I/O error occurs. ///Methods were called after the stream was closed. 2 public override void SetLength(long value) { zipEntryStream.SetLength(value); } /// ///When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. /// /// /// ///The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. /// /// ///The zero-based byte offset in buffer at which to begin storing the data read from the current stream. ///The maximum number of bytes to be read from the current stream. ///An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source. ///The sum of offset and count is larger than the buffer length. ///Methods were called after the stream was closed. ///The stream does not support reading. ///buffer is null. ///An I/O error occurs. ///offset or count is negative. 1 public override int Read(byte[] buffer, int offset, int count) { return zipEntryStream.Read(buffer, offset, count); } /// ///Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream. /// /// /// ///The unsigned byte cast to an Int32, or -1 if at the end of the stream. /// /// ///The stream does not support reading. ///Methods were called after the stream was closed. 2 public override int ReadByte() { return zipEntryStream.ReadByte(); } /// ///When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. /// /// ///The zero-based byte offset in buffer at which to begin copying bytes to the current stream. ///The number of bytes to be written to the current stream. ///An array of bytes. This method copies count bytes from buffer to the current stream. ///An I/O error occurs. ///The stream does not support writing. ///Methods were called after the stream was closed. ///buffer is null. ///The sum of offset and count is greater than the buffer length. ///offset or count is negative. 1 public override void Write(byte[] buffer, int offset, int count) { zipEntryStream.Write(buffer, offset, count); } /// ///Writes a byte to the current position in the stream and advances the position within the stream by one byte. /// /// ///The byte to write to the stream. ///An I/O error occurs. ///Methods were called after the stream was closed. ///The stream does not support writing, or the stream is already closed. 2 public override void WriteByte(byte value) { zipEntryStream.WriteByte(value); } /// ///When overridden in a derived class, gets a value indicating whether the current stream supports reading. /// /// /// ///true if the stream supports reading; otherwise, false. /// ///1 public override bool CanRead { get { return zipEntryStream.CanRead; } } /// ///When overridden in a derived class, gets a value indicating whether the current stream supports seeking. /// /// /// ///true if the stream supports seeking; otherwise, false. /// ///1 public override bool CanSeek { get { return zipEntryStream.CanSeek; } } /// ///Gets a value that determines whether the current stream can time out. /// /// /// ///A value that determines whether the current stream can time out. /// ///2 public override bool CanTimeout { get { return zipEntryStream.CanTimeout; } } /// ///When overridden in a derived class, gets a value indicating whether the current stream supports writing. /// /// /// ///true if the stream supports writing; otherwise, false. /// ///1 public override bool CanWrite { get { return zipEntryStream.CanWrite; } } /// ///When overridden in a derived class, gets the length in bytes of the stream. /// /// /// ///A long value representing the length of the stream in bytes. /// /// ///A class derived from Stream does not support seeking. ///Methods were called after the stream was closed. 1 public override long Length { get { return zipEntryStream.Length; } } /// ///When overridden in a derived class, gets or sets the position within the current stream. /// /// /// ///The current position within the stream. /// /// ///An I/O error occurs. ///The stream does not support seeking. ///Methods were called after the stream was closed. 1 public override long Position { get { return zipEntryStream.Position; } set { zipEntryStream.Position = value; } } /// ///Gets or sets a value that determines how long the stream will attempt to read before timing out. /// /// /// ///A value that determines how long the stream will attempt to read before timing out. /// /// ///The method always throws an . 2 public override int ReadTimeout { get { return zipEntryStream.ReadTimeout; } set { zipEntryStream.ReadTimeout = value; } } /// ///Gets or sets a value that determines how long the stream will attempt to write before timing out. /// /// /// ///A value that determines how long the stream will attempt to write before timing out. /// /// ///The method always throws an . 2 public override int WriteTimeout { get { return zipEntryStream.WriteTimeout; } set { zipEntryStream.WriteTimeout = value; } } } }