问题
I want to use Openxml to abstract "OLE package" from an ".docx" file. I don't know how to do it,and I didn't find any example about it in offical examples. Please help me.
This is my attempt: 1.I build a Docx file by "MS office 2016" named "Test.docx", and insert an ".zip" file into "Test.docx". I open "Open XML SDK 2.5 Productivity Tool" to Watch "Test.docx", I find this(Figure 1), but I don't get any information about how to extract this zip file through the reflect code.
2.Then I try to use C# and SharpCompress.dll to extract this ".zip" file, next is the code:
class Program
{
static void Main(string[] args)
{
string filepath = @"C:\Users\宇宙无敌帅小伙\Desktop\test.docx";
OleFileTest(filepath);
}
public static void OleFileTest(string filepath)
{
try
{
using (WordprocessingDocument Docx = WordprocessingDocument.Open(filepath, true))
{
Body body = Docx.MainDocumentPart.Document.Body;
IEnumerable<EmbeddedObjectPart> embd1 = Docx.MainDocumentPart.EmbeddedObjectParts;
int cnt = 0;
foreach (EmbeddedObjectPart item in embd1)
{
System.IO.Stream dt = item.GetStream(FileMode.OpenOrCreate);
BinaryWriter writer = new BinaryWriter(dt);
byte[] bt = new byte[dt.Length];
using (FileStream fs = File.Open($"C:\\Users\\宇宙无敌帅小伙\\Desktop\\{cnt}.zip", FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
{
fs.Write(bt, 0, bt.Length);
}
cnt++;
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
But I can't open this ".zip" file which I extract. Can somebody help me? thanks a lot!
回答1:
The challenge is that the binary file you extract from the EmbeddedObjectPart
is not your ZIP file. It is a structured storage file that contains your ZIP file.
The following unit test shows how you can extract a ZIP file (e.g., ZipContents.zip
) that was embedded into a Word document ("Resources\\ZipContainer.docx"
) as an OLE object, using Microsoft Word. Note the usage of the Ole10Native.ExtractFile()
method, which extracts the ZIP file from the structured storage file (e.g., oleObject1.bin
) embedded in your Word document.
using System.IO;
using CodeSnippets.Windows;
using DocumentFormat.OpenXml.Packaging;
using Xunit;
namespace CodeSnippets.Tests.OpenXml.Wordprocessing
{
public class EmbeddedObjectPartTests
{
private static void ExtractFile(EmbeddedObjectPart part, string destinationFolderPath)
{
// Determine the file name and destination path of the binary,
// structured storage file.
string binaryFileName = Path.GetFileName(part.Uri.ToString());
string binaryFilePath = Path.Combine(destinationFolderPath, binaryFileName);
// Ensure the destination directory exists.
Directory.CreateDirectory(destinationFolderPath);
// Copy part contents to structured storage file.
using (Stream partStream = part.GetStream())
using (FileStream fileStream = File.Create(binaryFilePath))
{
partStream.CopyTo(fileStream);
}
// Extract the embedded file from the structured storage file.
Ole10Native.ExtractFile(binaryFilePath, destinationFolderPath);
// Remove the structured storage file.
File.Delete(binaryFilePath);
}
[Fact]
public void CanExtractEmbeddedZipFile()
{
const string documentPath = "Resources\\ZipContainer.docx";
const string destinationFolderPath = "Output";
string destinationFilePath = Path.Combine(destinationFolderPath, "ZipContents.zip");
using WordprocessingDocument wordDocument =
WordprocessingDocument.Open(documentPath, false);
// Extract all embedded objects.
foreach (EmbeddedObjectPart part in wordDocument.MainDocumentPart.EmbeddedObjectParts)
{
ExtractFile(part, destinationFolderPath);
}
Assert.True(File.Exists(destinationFilePath));
}
}
}
Here's the gist of the Ole10Native
class, which was once published by Microsoft but is a bit hard to find nowadays:
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text.RegularExpressions;
namespace CodeSnippets.Windows
{
public class Ole10Native
{
public static void ExtractFile(string sourceFilePath, string destinationFolder)
{
StgOpenStorage(sourceFilePath, null, STGM.READWRITE | STGM.SHARE_EXCLUSIVE, IntPtr.Zero, 0, out IStorage iStorage);
ProcessPackage(iStorage, destinationFolder);
Marshal.ReleaseComObject(iStorage);
}
private static void ProcessPackage(IStorage pStg, string destinationFolder)
{
uint numReturned;
pStg.EnumElements(0, IntPtr.Zero, 0, out IEnumSTATSTG pEnumStatStg);
var ss = new STATSTG[1];
// Loop through the STATSTG structures in the storage.
do
{
// Retrieve the STATSTG structure
pEnumStatStg.Next(1, ss, out numReturned);
if (numReturned != 0)
{
//System.Runtime.InteropServices.ComTypes.STATSTG statstm;
var bytT = new byte[4];
// Check if the pwcsName contains "Ole10Native" stream which contain the actual embedded object
if (ss[0].pwcsName.Contains("Ole10Native"))
{
// Get the stream objectOpen the stream
pStg.OpenStream(ss[0].pwcsName, IntPtr.Zero, (uint) STGM.READ | (uint) STGM.SHARE_EXCLUSIVE, 0,
out IStream pStream);
//pStream.Stat(out statstm, (int) STATFLAG.STATFLAG_DEFAULT);
IntPtr position = IntPtr.Zero;
// File name starts from 7th Byte.
// Position the cursor to the 7th Byte.
pStream.Seek(6, 0, position);
var ulRead = new IntPtr();
var filename = new char[260];
int i;
// Read the File name of the embedded object
for (i = 0; i < 260; i++)
{
pStream.Read(bytT, 1, ulRead);
pStream.Seek(0, 1, position);
filename[i] = (char) bytT[0];
if (bytT[0] == 0) break;
}
var path = new string(filename, 0, i);
// Next part is the source path of the embedded object.
// Length is unknown. Hence, loop through each byte to read the 0 terminated string
// Read the source path.
for (i = 0; i < 260; i++)
{
pStream.Read(bytT, 1, ulRead);
pStream.Seek(0, 1, position);
filename[i] = (char) bytT[0];
if (bytT[0] == 0) break;
}
// Unknown 4 bytes
pStream.Seek(4, 1, position);
// Next 4 byte gives the length of the temporary file path
// (Office uses a temporary location to copy the files before inserting to the document)
// The length is in little endian format. Hence conversion is needed
pStream.Read(bytT, 4, ulRead);
ulong dwSize = 0;
dwSize += (ulong) (bytT[3] << 24);
dwSize += (ulong) (bytT[2] << 16);
dwSize += (ulong) (bytT[1] << 8);
dwSize += bytT[0];
// Skip the temporary file path
pStream.Seek((long) dwSize, 1, position);
// Next four bytes gives the size of the actual data in little endian format.
// Convert the format.
pStream.Read(bytT, 4, ulRead);
dwSize = 0;
dwSize += (ulong) (bytT[3] << 24);
dwSize += (ulong) (bytT[2] << 16);
dwSize += (ulong) (bytT[1] << 8);
dwSize += bytT[0];
// Read the actual file content
var byData = new byte[dwSize];
pStream.Read(byData, (int) dwSize, ulRead);
// Create the file
var bWriter = new BinaryWriter(File.Open(Path.Combine(destinationFolder, GetFileName(path)),
FileMode.Create));
bWriter.Write(byData);
bWriter.Close();
}
}
} while (numReturned > 0);
Marshal.ReleaseComObject(pEnumStatStg);
}
private static string GetFileName(string filePath)
{
return Regex.Replace(filePath, @"^.*[\\]", "");
}
}
}
You can find the full source code (including the Ole10Native
class) in my CodeSnippets GitHub repository.
来源:https://stackoverflow.com/questions/59106776/how-to-extract-ole-file-from-word-docx-by-openxml-through-c-sharp