| /******************************************************************************* | 
 |  * You may amend and distribute as you like, but don't remove this header! | 
 |  * | 
 |  * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets. | 
 |  * See http://www.codeplex.com/EPPlus for details. | 
 |  * | 
 |  * Copyright (C) 2011  Jan Källman | 
 |  * | 
 |  * This library is free software; you can redistribute it and/or | 
 |  * modify it under the terms of the GNU Lesser General Public | 
 |  * License as published by the Free Software Foundation; either | 
 |  * version 2.1 of the License, or (at your option) any later version. | 
 |  | 
 |  * This library is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   | 
 |  * See the GNU Lesser General Public License for more details. | 
 |  * | 
 |  * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php | 
 |  * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html | 
 |  * | 
 |  * All code and executables are provided "as is" with no warranty either express or implied.  | 
 |  * The author accepts no liability for any damage or loss of business that this product may cause. | 
 |  * | 
 |  * Code change notes: | 
 |  *  | 
 |  * Author							Change						Date | 
 |  ******************************************************************************* | 
 |  * Jan Källman		Added		01-01-2012 | 
 |  * Jan Källman      Added compression support 27-03-2012 | 
 |  *******************************************************************************/ | 
 | using System; | 
 | using System.Collections.Generic; | 
 | using System.Linq; | 
 | using System.Text; | 
 | using System.Runtime.InteropServices; | 
 | using comTypes = System.Runtime.InteropServices.ComTypes; | 
 | using System.IO; | 
 | using System.Security; | 
 |  | 
 | namespace OfficeOpenXml.Utils | 
 | { | 
 | #if !MONO | 
 |     internal class CompoundDocument | 
 |     {         | 
 |         internal class StoragePart | 
 |         { | 
 |             public StoragePart() | 
 |             { | 
 |  | 
 |             } | 
 |             internal Dictionary<string, StoragePart> SubStorage = new Dictionary<string, StoragePart>(); | 
 |             internal Dictionary<string, byte[]> DataStreams = new Dictionary<string, byte[]>(); | 
 |         } | 
 |         internal StoragePart Storage = null; | 
 |         internal CompoundDocument() | 
 |         { | 
 |             Storage = new CompoundDocument.StoragePart(); | 
 |         } | 
 |         internal CompoundDocument(FileInfo fi) | 
 |         { | 
 |             Read(fi); | 
 |         } | 
 |         internal CompoundDocument(ILockBytes lb) | 
 |         { | 
 |             Read(lb); | 
 |         } | 
 |         internal CompoundDocument(byte[] doc) | 
 |         { | 
 |             Read(doc); | 
 |         } | 
 |         internal void Read(FileInfo fi) | 
 |         { | 
 |             var b = File.ReadAllBytes(fi.FullName); | 
 |             Read(b); | 
 |         } | 
 |         [SecuritySafeCritical] | 
 |         internal void Read(byte[] doc) | 
 |         { | 
 |             ILockBytes lb; | 
 |             var iret = CreateILockBytesOnHGlobal(IntPtr.Zero, true, out lb); | 
 |  | 
 |             IntPtr buffer = Marshal.AllocHGlobal(doc.Length); | 
 |             Marshal.Copy(doc, 0, buffer, doc.Length); | 
 |             UIntPtr readSize; | 
 |             lb.WriteAt(0, buffer, doc.Length, out readSize); | 
 |             Marshal.FreeHGlobal(buffer); | 
 |  | 
 |             Read(lb); | 
 |         } | 
 |  | 
 |         [SecuritySafeCritical] | 
 |         internal void Read(ILockBytes lb) | 
 |         { | 
 |             if (StgIsStorageILockBytes(lb) == 0) | 
 |             { | 
 |                 IStorage storage = null; | 
 |                 if (StgOpenStorageOnILockBytes( | 
 |                     lb, | 
 |                     null, | 
 |                     STGM.DIRECT | STGM.READ | STGM.SHARE_EXCLUSIVE, | 
 |                     IntPtr.Zero, | 
 |                     0, | 
 |                     out storage) == 0) | 
 |                 { | 
 |                     Storage = new StoragePart(); | 
 |                     ReadParts(storage, Storage); | 
 |                     Marshal.ReleaseComObject(storage); | 
 |                 } | 
 |             } | 
 |             else | 
 |             { | 
 |                 throw (new InvalidDataException(string.Format("Part is not a compound document"))); | 
 |             } | 
 |         } | 
 |         #region  Compression | 
 |         /// <summary> | 
 |         /// Compression using a run length encoding algorithm. | 
 |         /// See MS-OVBA Section 2.4 | 
 |         /// </summary> | 
 |         /// <param name="part">Byte array to decompress</param> | 
 |         /// <returns></returns> | 
 |         internal static byte[] CompressPart(byte[] part) | 
 |         { | 
 |             MemoryStream ms = new MemoryStream(4096); | 
 |             BinaryWriter br = new BinaryWriter(ms); | 
 |             br.Write((byte)1); | 
 |  | 
 |             int compStart = 1; | 
 |             int compEnd = 4098; | 
 |             int decompStart = 0; | 
 |             int decompEnd = part.Length < 4096 ? part.Length : 4096; | 
 |  | 
 |             while (decompStart < decompEnd && compStart < compEnd) | 
 |             { | 
 |                 byte[] chunk = CompressChunk(part, ref decompStart); | 
 |                 ushort header; | 
 |                 if (chunk == null || chunk.Length == 0) | 
 |                 { | 
 |                     header = 4096 | 0x600;  //B=011 A=0 | 
 |                 } | 
 |                 else | 
 |                 { | 
 |                     header = (ushort)(((chunk.Length - 1) & 0xFFF)); | 
 |                     header |= 0xB000;   //B=011 A=1 | 
 |                     br.Write(header); | 
 |                     br.Write(chunk);                     | 
 |                 } | 
 |                 decompEnd = part.Length < decompStart + 4096 ? part.Length : decompStart+4096; | 
 |             } | 
 |  | 
 |              | 
 |             br.Flush(); | 
 |             return ms.ToArray();         | 
 |         } | 
 |         private static byte[] CompressChunk(byte[] buffer, ref int startPos) | 
 |         { | 
 |             var comprBuffer=new byte[4096]; | 
 |             int flagPos = 0; | 
 |             int cPos=1; | 
 |             int dPos = startPos; | 
 |             int dEnd=startPos+4096 < buffer.Length? startPos+4096 : buffer.Length; | 
 |             while(dPos<dEnd) | 
 |             { | 
 |                 byte tokenFlags = 0; | 
 |                 for (int i = 0; i < 8; i++) | 
 |                 { | 
 |                     if (dPos - startPos > 0) | 
 |                     { | 
 |                         int bestCandidate = -1; | 
 |                         int bestLength = 0; | 
 |                         int candidate = dPos - 1; | 
 |                         int bitCount = GetLengthBits(dPos-startPos); | 
 |                         int bits = (16 - bitCount); | 
 |                         ushort lengthMask = (ushort)((0xFFFF) >> bits); | 
 |  | 
 |                         while (candidate >= startPos) | 
 |                         { | 
 |                             if (buffer[candidate] == buffer[dPos]) | 
 |                             { | 
 |                                 int length = 1; | 
 |  | 
 |                                 while (buffer.Length > dPos + length && buffer[candidate + length] == buffer[dPos + length] && length < lengthMask && dPos+length < dEnd) | 
 |                                 { | 
 |                                     length++; | 
 |                                 } | 
 |                                 if (length > bestLength) | 
 |                                 { | 
 |                                     bestCandidate = candidate; | 
 |                                     bestLength = length; | 
 |                                     if (bestLength == lengthMask) | 
 |                                     { | 
 |                                         break; | 
 |                                     } | 
 |                                 } | 
 |                             } | 
 |                             candidate--; | 
 |                         } | 
 |                         if (bestLength >= 3)    //Copy token | 
 |                         { | 
 |                             tokenFlags |= (byte)(1 << i); | 
 |  | 
 |                             UInt16 offsetMask = (ushort)~lengthMask; | 
 |                             ushort token = (ushort)(((ushort)(dPos - (bestCandidate+1))) << (bitCount) | (ushort)(bestLength - 3)); | 
 |                             Array.Copy(BitConverter.GetBytes(token), 0, comprBuffer, cPos, 2); | 
 |                             dPos = dPos + bestLength; | 
 |                             cPos += 2; | 
 |                             //SetCopy Token                         | 
 |                         } | 
 |                         else | 
 |                         { | 
 |                             comprBuffer[cPos++] = buffer[dPos++]; | 
 |                         } | 
 |                     } | 
 |                      | 
 |                     else | 
 |                     { | 
 |                         comprBuffer[cPos++] = buffer[dPos++]; | 
 |                     } | 
 |                     if (dPos >= dEnd) break; | 
 |                 } | 
 |                 comprBuffer[flagPos] = tokenFlags; | 
 |                 flagPos = cPos++; | 
 |             } | 
 |             var ret = new byte[cPos - 1]; | 
 |             Array.Copy(comprBuffer, ret, ret.Length); | 
 |             startPos = dEnd; | 
 |             return ret; | 
 |         } | 
 |         internal static byte[] DecompressPart(byte[] part) | 
 |         { | 
 |             return DecompressPart(part, 0); | 
 |         } | 
 |         /// <summary> | 
 |         /// Decompression using a run length encoding algorithm. | 
 |         /// See MS-OVBA Section 2.4 | 
 |         /// </summary> | 
 |         /// <param name="part">Byte array to decompress</param> | 
 |         /// <param name="startPos"></param> | 
 |         /// <returns></returns> | 
 |         internal static byte[] DecompressPart(byte[] part, int startPos) | 
 |         { | 
 |  | 
 |             if (part[startPos] != 1) | 
 |             { | 
 |                 return null; | 
 |             } | 
 |             MemoryStream ms = new MemoryStream(4096); | 
 |             int compressPos = startPos + 1; | 
 |             while(compressPos < part.Length-1) | 
 |             { | 
 |                 DecompressChunk(ms, part, ref compressPos); | 
 |             } | 
 |             return ms.ToArray(); | 
 |         } | 
 |         private static void DecompressChunk(MemoryStream ms, byte[] compBuffer, ref int pos) | 
 |         { | 
 |             ushort header = BitConverter.ToUInt16(compBuffer, pos); | 
 |             int  decomprPos=0; | 
 |             byte[] buffer = new byte[4198]; //Add an extra 100 byte. Some workbooks have overflowing worksheets. | 
 |             int size = (int)(header & 0xFFF)+3; | 
 |             int endPos = pos+size; | 
 |             int a = (int)(header & 0x7000) >> 12; | 
 |             int b = (int)(header & 0x8000) >> 15; | 
 |             pos += 2; | 
 |             if (b == 1) //Compressed chunk | 
 |             { | 
 |                 while (pos < compBuffer.Length && pos < endPos) | 
 |                 { | 
 |                     //Decompress token | 
 |                     byte token = compBuffer[pos++]; | 
 |                     if (pos >= endPos) | 
 |                         break; | 
 |                     for (int i = 0; i < 8; i++) | 
 |                     { | 
 |                         //Literal token | 
 |                         if ((token & (1 << i)) == 0) | 
 |                         { | 
 |                             ms.WriteByte(compBuffer[pos]);   | 
 |                             buffer[decomprPos++] = compBuffer[pos++]; | 
 |                         } | 
 |                         else //copy token | 
 |                         { | 
 |                             var t = BitConverter.ToUInt16(compBuffer, pos); | 
 |                             int bitCount = GetLengthBits(decomprPos); | 
 |                             int bits = (16 - bitCount); | 
 |                             ushort lengthMask = (ushort)((0xFFFF) >> bits); | 
 |                             UInt16 offsetMask = (ushort)~lengthMask; | 
 |                             var length = (lengthMask & t) + 3; | 
 |                             var offset = (offsetMask & t) >> (bitCount); | 
 |                             int source = decomprPos - offset - 1; | 
 |                             if (decomprPos + length >= buffer.Length) | 
 |                             { | 
 |                                 // Be lenient on decompression, so extend our decompression | 
 |                                 // buffer. Excel generated VBA projects do encounter this issue. | 
 |                                 // One would think (not surprisingly that the VBA project spec) | 
 |                                 // over emphasizes the size restrictions of a DecompressionChunk. | 
 |                                 var largerBuffer = new byte[buffer.Length + 4098]; | 
 |                                 Array.Copy(buffer, largerBuffer, decomprPos); | 
 |                                 buffer = largerBuffer; | 
 |                             } | 
 |                              | 
 |                             // Even though we've written to the MemoryStream, | 
 |                             // We still should decompress the token into this buffer | 
 |                             // in case a later token needs to use the bytes we're | 
 |                             // about to decompress. | 
 |                             for (int c = 0; c < length; c++) | 
 |                             { | 
 |                                 ms.WriteByte(buffer[source]); //Must copy byte-wise because copytokens can overlap compressed buffer. | 
 |                                 buffer[decomprPos++] = buffer[source++]; | 
 |                             } | 
 |  | 
 |                             pos += 2; | 
 |  | 
 |                         } | 
 |                         if (pos >= endPos) | 
 |                             break; | 
 |                     } | 
 |                 } | 
 |                 return; | 
 |             } | 
 |             else //Raw chunk | 
 |             { | 
 |                 ms.Write(compBuffer, pos, size); | 
 |                 pos += size; | 
 |                 return; | 
 |             } | 
 |         } | 
 |         private static int GetLengthBits(int decompPos) | 
 |         { | 
 |             if (decompPos <= 16) | 
 |             { | 
 |                 return 12; | 
 |             } | 
 |             else if (decompPos <= 32) | 
 |             { | 
 |                 return 11; | 
 |             } | 
 |             else if (decompPos <= 64) | 
 |             { | 
 |                 return 10; | 
 |             } | 
 |             else if (decompPos <= 128) | 
 |             { | 
 |                 return 9; | 
 |             } | 
 |             else if (decompPos <= 256) | 
 |             { | 
 |                 return 8; | 
 |             } | 
 |             else if (decompPos <= 512) | 
 |             { | 
 |                 return 7; | 
 |             } | 
 |             else if (decompPos <= 1024) | 
 |             { | 
 |                 return 6; | 
 |             } | 
 |             else if (decompPos <= 2048) | 
 |             { | 
 |                 return 5; | 
 |             } | 
 |             else if (decompPos <= 4096) | 
 |             { | 
 |                 return 4; | 
 |             } | 
 |             else | 
 |             { | 
 |                 //We should never end up here, but if so this is the formula to calculate the bits... | 
 |                 return 12 - (int)Math.Truncate(Math.Log(decompPos - 1 >> 4, 2) + 1); | 
 |             } | 
 |         } | 
 |         #endregion | 
 |         #region "API declare" | 
 |         [ComImport] | 
 |         [Guid("0000000d-0000-0000-C000-000000000046")] | 
 |         [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | 
 |         internal interface IEnumSTATSTG | 
 |         { | 
 |             // The user needs to allocate an STATSTG array whose size is celt.  | 
 |             [PreserveSig] | 
 |             uint Next( | 
 |                 uint celt, | 
 |                 [MarshalAs(UnmanagedType.LPArray), Out]  | 
 |             System.Runtime.InteropServices.ComTypes.STATSTG[] rgelt, | 
 |                 out uint pceltFetched | 
 |             ); | 
 |  | 
 |             void Skip(uint celt); | 
 |  | 
 |             void Reset(); | 
 |  | 
 |             [return: MarshalAs(UnmanagedType.Interface)] | 
 |             IEnumSTATSTG Clone(); | 
 |         } | 
 |  | 
 |         [ComImport] | 
 |         [Guid("0000000b-0000-0000-C000-000000000046")] | 
 |         [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | 
 |         interface IStorage | 
 |         { | 
 |             void CreateStream( | 
 |                 /* [string][in] */ string pwcsName, | 
 |                 /* [in] */ uint grfMode, | 
 |                 /* [in] */ uint reserved1, | 
 |                 /* [in] */ uint reserved2, | 
 |                 /* [out] */ out comTypes.IStream ppstm); | 
 |  | 
 |             void OpenStream( | 
 |                 /* [string][in] */ string pwcsName, | 
 |                 /* [unique][in] */ IntPtr reserved1, | 
 |                 /* [in] */ uint grfMode, | 
 |                 /* [in] */ uint reserved2, | 
 |                 /* [out] */ out comTypes.IStream ppstm); | 
 |  | 
 |             void CreateStorage( | 
 |                 /* [string][in] */ string pwcsName, | 
 |                 /* [in] */ uint grfMode, | 
 |                 /* [in] */ uint reserved1, | 
 |                 /* [in] */ uint reserved2, | 
 |                 /* [out] */ out IStorage ppstg); | 
 |  | 
 |             void OpenStorage( | 
 |                 /* [string][unique][in] */ string pwcsName, | 
 |                 /* [unique][in] */ IStorage pstgPriority, | 
 |                 /* [in] */ STGM grfMode, | 
 |                 /* [unique][in] */ IntPtr snbExclude, | 
 |                 /* [in] */ uint reserved, | 
 |                 /* [out] */ out IStorage ppstg); | 
 |  | 
 |             void CopyTo( | 
 |                 [InAttribute] uint ciidExclude, | 
 |                 [InAttribute] Guid[] rgiidExclude, | 
 |                 [InAttribute] IntPtr snbExclude, | 
 |                 [InAttribute] IStorage pstgDest | 
 |             ); | 
 |  | 
 |             void MoveElementTo( | 
 |                 /* [string][in] */ string pwcsName, | 
 |                 /* [unique][in] */ IStorage pstgDest, | 
 |                 /* [string][in] */ string pwcsNewName, | 
 |                 /* [in] */ uint grfFlags); | 
 |  | 
 |             void Commit( | 
 |                 /* [in] */ uint grfCommitFlags); | 
 |  | 
 |             void Revert(); | 
 |  | 
 |             void EnumElements( | 
 |                 /* [in] */ uint reserved1, | 
 |                 /* [size_is][unique][in] */ IntPtr reserved2, | 
 |                 /* [in] */ uint reserved3, | 
 |                 /* [out] */ out IEnumSTATSTG ppenum); | 
 |  | 
 |             void DestroyElement( | 
 |                 /* [string][in] */ string pwcsName); | 
 |  | 
 |             void RenameElement( | 
 |                 /* [string][in] */ string pwcsOldName, | 
 |                 /* [string][in] */ string pwcsNewName); | 
 |  | 
 |             void SetElementTimes( | 
 |                 /* [string][unique][in] */ string pwcsName, | 
 |                 /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME pctime, | 
 |                 /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME patime, | 
 |                 /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME pmtime); | 
 |  | 
 |             void SetClass( | 
 |                 /* [in] */ Guid clsid); | 
 |  | 
 |             void SetStateBits( | 
 |                 /* [in] */ uint grfStateBits, | 
 |                 /* [in] */ uint grfMask); | 
 |  | 
 |             void Stat( | 
 |                 /* [out] */ out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, | 
 |                 /* [in] */ uint grfStatFlag); | 
 |  | 
 |         } | 
 |         [ComVisible(false)] | 
 |         [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("0000000A-0000-0000-C000-000000000046")] | 
 |         internal interface ILockBytes | 
 |         { | 
 |             void ReadAt(long ulOffset, System.IntPtr pv, int cb, out UIntPtr pcbRead); | 
 |             void WriteAt(long ulOffset, System.IntPtr pv, int cb, out UIntPtr pcbWritten); | 
 |             void Flush(); | 
 |             void SetSize(long cb); | 
 |             void LockRegion(long libOffset, long cb, int dwLockType); | 
 |             void UnlockRegion(long libOffset, long cb, int dwLockType); | 
 |             void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag); | 
 |         } | 
 |         [Flags] | 
 |         internal enum STGM : int | 
 |         { | 
 |             DIRECT = 0x00000000, | 
 |             TRANSACTED = 0x00010000, | 
 |             SIMPLE = 0x08000000, | 
 |             READ = 0x00000000, | 
 |             WRITE = 0x00000001, | 
 |             READWRITE = 0x00000002, | 
 |             SHARE_DENY_NONE = 0x00000040, | 
 |             SHARE_DENY_READ = 0x00000030, | 
 |             SHARE_DENY_WRITE = 0x00000020, | 
 |             SHARE_EXCLUSIVE = 0x00000010, | 
 |             PRIORITY = 0x00040000, | 
 |             DELETEONRELEASE = 0x04000000, | 
 |             NOSCRATCH = 0x00100000, | 
 |             CREATE = 0x00001000, | 
 |             CONVERT = 0x00020000, | 
 |             FAILIFTHERE = 0x00000000, | 
 |             NOSNAPSHOT = 0x00200000, | 
 |             DIRECT_SWMR = 0x00400000, | 
 |         } | 
 |  | 
 |         internal enum STATFLAG : uint | 
 |         { | 
 |             STATFLAG_DEFAULT = 0, | 
 |             STATFLAG_NONAME = 1, | 
 |             STATFLAG_NOOPEN = 2 | 
 |         } | 
 |  | 
 |         internal enum STGTY : int | 
 |         { | 
 |             STGTY_STORAGE = 1, | 
 |             STGTY_STREAM = 2, | 
 |             STGTY_LOCKBYTES = 3, | 
 |             STGTY_PROPERTY = 4 | 
 |         } | 
 |  | 
 |         [DllImport("ole32.dll")] | 
 |         private static extern int StgIsStorageFile( | 
 |             [MarshalAs(UnmanagedType.LPWStr)] string pwcsName); | 
 |         [DllImport("ole32.dll")] | 
 |         private static extern int StgIsStorageILockBytes( | 
 |             ILockBytes plkbyt); | 
 |  | 
 |  | 
 |         [DllImport("ole32.dll")] | 
 |         static extern int StgOpenStorage( | 
 |             [MarshalAs(UnmanagedType.LPWStr)] string pwcsName, | 
 |             IStorage pstgPriority, | 
 |             STGM grfMode, | 
 |             IntPtr snbExclude, | 
 |             uint reserved, | 
 |             out IStorage ppstgOpen); | 
 |  | 
 |         [DllImport("ole32.dll")] | 
 |         static extern int StgOpenStorageOnILockBytes( | 
 |             ILockBytes plkbyt, | 
 |             IStorage pStgPriority, | 
 |             STGM grfMode, | 
 |             IntPtr snbEnclude, | 
 |             uint reserved, | 
 |             out IStorage ppstgOpen); | 
 |         [DllImport("ole32.dll")] | 
 |         static extern int CreateILockBytesOnHGlobal( | 
 |             IntPtr hGlobal, | 
 |             bool fDeleteOnRelease, | 
 |             out ILockBytes ppLkbyt); | 
 |  | 
 |         [DllImport("ole32.dll")] | 
 |         static extern int StgCreateDocfileOnILockBytes(ILockBytes plkbyt, STGM grfMode, int reserved, out IStorage ppstgOpen); | 
 |  | 
 |         #endregion | 
 |         [SecuritySafeCritical] | 
 |         internal static int IsStorageFile(string Name) | 
 |         { | 
 |             return StgIsStorageFile(Name); | 
 |         } | 
 |         [SecuritySafeCritical] | 
 |         internal static int IsStorageILockBytes(ILockBytes lb) | 
 |         { | 
 |             return StgIsStorageILockBytes(lb); | 
 |         } | 
 |         [SecuritySafeCritical] | 
 |         internal static ILockBytes GetLockbyte(MemoryStream stream) | 
 |         { | 
 |             ILockBytes lb; | 
 |             var iret = CreateILockBytesOnHGlobal(IntPtr.Zero, true, out lb); | 
 |             byte[] docArray = stream.GetBuffer(); | 
 |  | 
 |             IntPtr buffer = Marshal.AllocHGlobal(docArray.Length); | 
 |             Marshal.Copy(docArray, 0, buffer, docArray.Length); | 
 |             UIntPtr readSize; | 
 |             lb.WriteAt(0, buffer, docArray.Length, out readSize); | 
 |             Marshal.FreeHGlobal(buffer); | 
 |  | 
 |             return lb; | 
 |         } | 
 |         [SecuritySafeCritical] | 
 |         private MemoryStream ReadParts(IStorage storage, StoragePart storagePart) | 
 |         { | 
 |             MemoryStream ret = null; | 
 |             comTypes.STATSTG statstg; | 
 |  | 
 |             storage.Stat(out statstg, (uint)STATFLAG.STATFLAG_DEFAULT); | 
 |  | 
 |             IEnumSTATSTG pIEnumStatStg = null; | 
 |             storage.EnumElements(0, IntPtr.Zero, 0, out pIEnumStatStg); | 
 |  | 
 |             comTypes.STATSTG[] regelt = { statstg }; | 
 |             uint fetched = 0; | 
 |             uint res = pIEnumStatStg.Next(1, regelt, out fetched); | 
 |  | 
 |             //if (regelt[0].pwcsName == "DataSpaces") | 
 |             //{ | 
 |             //    PrintStorage(storage, regelt[0],""); | 
 |             //} | 
 |             while (res != 1) | 
 |             { | 
 |                 foreach (var item in regelt) | 
 |                 { | 
 |                     if (item.type == 1) | 
 |                     { | 
 |                         IStorage subStorage; | 
 |                         storage.OpenStorage(item.pwcsName, null, STGM.DIRECT | STGM.READ | STGM.SHARE_EXCLUSIVE, IntPtr.Zero, 0, out subStorage); | 
 |                         StoragePart subStoragePart=new StoragePart(); | 
 |                         storagePart.SubStorage.Add(item.pwcsName, subStoragePart); | 
 |                         ReadParts(subStorage, subStoragePart); | 
 |                     } | 
 |                     else | 
 |                     { | 
 |                         storagePart.DataStreams.Add(item.pwcsName, GetOleStream(storage, item));                     | 
 |                     } | 
 |                 } | 
 |                 res = pIEnumStatStg.Next(1, regelt, out fetched); | 
 |             } | 
 |             Marshal.ReleaseComObject(pIEnumStatStg); | 
 |             return ret; | 
 |         } | 
 |         // Help method to print a storage part binary to c:\temp | 
 |         //private void PrintStorage(IStorage storage, System.Runtime.InteropServices.ComTypes.STATSTG sTATSTG, string topName) | 
 |         //{ | 
 |         //    IStorage ds; | 
 |         //    if (topName.Length > 0) | 
 |         //    { | 
 |         //        topName = topName[0] < 'A' ? topName.Substring(1, topName.Length - 1) : topName; | 
 |         //    } | 
 |         //    storage.OpenStorage(sTATSTG.pwcsName, | 
 |         //        null, | 
 |         //        (uint)(STGM.DIRECT | STGM.READ | STGM.SHARE_EXCLUSIVE), | 
 |         //        IntPtr.Zero, | 
 |         //        0, | 
 |         //        out ds); | 
 |  | 
 |         //    System.Runtime.InteropServices.ComTypes.STATSTG statstgSub; | 
 |         //    ds.Stat(out statstgSub, (uint)STATFLAG.STATFLAG_DEFAULT); | 
 |  | 
 |         //    IEnumSTATSTG pIEnumStatStgSub = null; | 
 |         //    System.Runtime.InteropServices.ComTypes.STATSTG[] regeltSub = { statstgSub }; | 
 |         //    ds.EnumElements(0, IntPtr.Zero, 0, out pIEnumStatStgSub); | 
 |  | 
 |         //    uint fetched = 0; | 
 |         //    while (pIEnumStatStgSub.Next(1, regeltSub, out fetched) == 0) | 
 |         //    { | 
 |         //        string sName = regeltSub[0].pwcsName[0] < 'A' ? regeltSub[0].pwcsName.Substring(1, regeltSub[0].pwcsName.Length - 1) : regeltSub[0].pwcsName; | 
 |         //        if (regeltSub[0].type == 1) | 
 |         //        { | 
 |         //            PrintStorage(ds, regeltSub[0], topName + sName + "_"); | 
 |         //        } | 
 |         //        else if(regeltSub[0].type==2) | 
 |         //        { | 
 |         //            File.WriteAllBytes(@"c:\temp\" + topName + sName + ".bin", GetOleStream(ds, regeltSub[0])); | 
 |         //        } | 
 |         //    } | 
 |         //}    } | 
 |         /// <summary> | 
 |         /// Read the stream and return it as a byte-array | 
 |         /// </summary> | 
 |         /// <param name="storage"></param> | 
 |         /// <param name="statstg"></param> | 
 |         /// <returns></returns> | 
 |         [SecuritySafeCritical] | 
 |         private byte[] GetOleStream(IStorage storage, comTypes.STATSTG statstg) | 
 |         { | 
 |             comTypes.IStream pIStream; | 
 |             storage.OpenStream(statstg.pwcsName, | 
 |                IntPtr.Zero, | 
 |                (uint)(STGM.READ | STGM.SHARE_EXCLUSIVE), | 
 |                0, | 
 |                out pIStream); | 
 |  | 
 |             byte[] data = new byte[statstg.cbSize]; | 
 |             pIStream.Read(data, (int)statstg.cbSize, IntPtr.Zero); | 
 |             Marshal.ReleaseComObject(pIStream); | 
 |  | 
 |             return data; | 
 |         } | 
 |  | 
 |         [SecuritySafeCritical] | 
 |         internal byte[] Save() | 
 |         { | 
 |             ILockBytes lb; | 
 |             var iret = CreateILockBytesOnHGlobal(IntPtr.Zero, true, out lb); | 
 |  | 
 |             IStorage storage = null; | 
 |             byte[] ret = null; | 
 |  | 
 |             //Create the document in-memory | 
 |             if (StgCreateDocfileOnILockBytes(lb, | 
 |                     STGM.CREATE | STGM.READWRITE | STGM.SHARE_EXCLUSIVE | STGM.TRANSACTED,  | 
 |                     0, | 
 |                     out storage)==0) | 
 |             { | 
 |                 foreach(var store in this.Storage.SubStorage) | 
 |                 { | 
 |                     CreateStore(store.Key, store.Value, storage); | 
 |                 } | 
 |                 CreateStreams(this.Storage, storage);                                 | 
 |                 lb.Flush(); | 
 |                  | 
 |                 //Now copy the unmanaged stream to a byte array --> memory stream | 
 |                 var statstg = new comTypes.STATSTG(); | 
 |                 lb.Stat(out statstg, 0); | 
 |                 int size = (int)statstg.cbSize; | 
 |                 IntPtr buffer = Marshal.AllocHGlobal(size); | 
 |                 UIntPtr readSize; | 
 |                 ret=new byte[size]; | 
 |                 lb.ReadAt(0, buffer, size, out readSize); | 
 |                 Marshal.Copy(buffer, ret, 0, size); | 
 |                 Marshal.FreeHGlobal(buffer); | 
 |             } | 
 |             Marshal.ReleaseComObject(storage); | 
 |             Marshal.ReleaseComObject(lb); | 
 |  | 
 |             return ret; | 
 |         } | 
 |  | 
 |         private void CreateStore(string name, StoragePart subStore, IStorage storage) | 
 |         { | 
 |             IStorage subStorage; | 
 |             storage.CreateStorage(name, (uint)(STGM.CREATE | STGM.WRITE | STGM.DIRECT | STGM.SHARE_EXCLUSIVE), 0, 0, out subStorage); | 
 |             storage.Commit(0); | 
 |             foreach (var store in subStore.SubStorage) | 
 |             { | 
 |                 CreateStore(store.Key, store.Value, subStorage); | 
 |             } | 
 |              | 
 |             CreateStreams(subStore, subStorage); | 
 |         } | 
 |  | 
 |         private void CreateStreams(StoragePart subStore, IStorage subStorage) | 
 |         { | 
 |             foreach (var ds in subStore.DataStreams) | 
 |             { | 
 |                 comTypes.IStream stream; | 
 |                 subStorage.CreateStream(ds.Key, (uint)(STGM.CREATE | STGM.WRITE | STGM.DIRECT | STGM.SHARE_EXCLUSIVE), 0, 0, out stream); | 
 |                 stream.Write(ds.Value, ds.Value.Length, IntPtr.Zero); | 
 |             } | 
 |             subStorage.Commit(0); | 
 |         } | 
 | 			 | 
 |     } | 
 | #endif | 
 | } |