如何使用字节流生成Word文档

5

我有一串字节流,如果正确排列,将形成一个有效的Word文件。我需要将这个流转换为Word文件,而不必写入磁盘。我从SQL Server数据库表中获取原始流:

ID   Name    FileData
----------------------------------------
1    Word1   292jf2jf2ofm29fj29fj29fj29f2jf29efj29fj2f9 (actual file data)

文件数据字段(FileData field)携带着数据。
Microsoft.Office.Interop.Word.Application word = new Microsoft.Office.Interop.Word.Application();
Microsoft.Office.Interop.Word.Document doc = new Microsoft.Office.Interop.Word.Document(); 
doc = word.Documents.Open(@"C:\SampleText.doc");
doc.Activate();

上面的代码从文件系统打开并填充了一个Word文件,但我不想这样做。我想定义一个新的Microsoft.Office.Interop.Word.Document,但我想手动从字节流中填充它的内容。
获取内存中的Word文档后,我想对关键字进行一些解析。
有什么想法吗?
4个回答

0
只有两种编程方式可以打开 Word 文档——作为物理文件或流。有“包装”,但实际上并不适用。
这里介绍了流方法:https://learn.microsoft.com/en-us/office/open-xml/how-to-open-a-word-processing-document-from-a-stream 但是即使是它也依赖于存在物理文件才能形成流:
string strDoc = @"C:\Users\Public\Public Documents\Word13.docx";
Stream stream = File.Open(strDoc, FileMode.Open);

我能提供的最佳解决方案是将文件写入临时位置,该位置应该是应用程序服务帐户有写入权限的地方:
string newDocument = @"C:\temp\test.docx";
WriteFile(byteArray, newDocument);

如果在我的示例中,它没有对“temp”文件夹的权限,您只需添加应用程序的服务帐户(如果是网站,则为应用程序池)以完全控制文件夹即可。
您可以使用此WriteFile()函数:
/// <summary>
/// Write a byte[] to a new file at the location where you choose
/// </summary>
/// <param name="byteArray">byte[] that consists of file data</param>
/// <param name="newDocument">Path to where the new document will be written</param>
public static void WriteFile(byte[] byteArray, string newDocument)
{
    using (MemoryStream stream = new MemoryStream())
    {
        stream.Write(byteArray, 0, (int)byteArray.Length);

        // Save the file with the new name
        File.WriteAllBytes(newDocument, stream.ToArray());
    }
}

从那里,您可以使用OpenXML打开并编辑文件。没有办法直接将Word文档以byte[]形式打开到Word的实例中 - Interop、OpenXML或其他方式 - 因为您需要一个documentPath,或者之前提到的依赖于存在物理文件的流方法。您可以通过将字节读入字符串和XML后进行编辑,或直接编辑字符串:

string docText = null;
byte[] byteArray = null;
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(documentPath, true))
{
    using (StreamReader sr = new StreamReader(wordDoc.MainDocumentPart.GetStream()))
    {
        docText = sr.ReadToEnd();  // <-- converts byte[] stream to string
    }

    // Play with the XML
    XmlDocument xml = new XmlDocument();
    xml.LoadXml(docText);  // the string contains the XML of the Word document

    XmlNodeList nodes = xml.GetElementsByTagName("w:body");
    XmlNode chiefBodyNode = nodes[0];
    // add paragraphs with AppendChild... 
    // remove a node by getting a ChildNode and removing it, like this...
    XmlNode firstParagraph = chiefBodyNode.ChildNodes[2];
    chiefBodyNode.RemoveChild(firstParagraph);

    // Or play with the string form
    docText = docText.Replace("John","Joe");

    // If you manipulated the XML, write it back to the string
    //docText = xml.OuterXml;  // comment out the line above if XML edits are all you want to do, and uncomment out this line

     // Save the file - yes, back to the file system - required
     using (StreamWriter sw = new StreamWriter(wordDoc.MainDocumentPart.GetStream(FileMode.Create)))
     {                    
        sw.Write(docText);
     }
 }

 // Read it back in as bytes
 byteArray = File.ReadAllBytes(documentPath); // new bytes, ready for DB saving

参考:

https://learn.microsoft.com/en-us/office/open-xml/how-to-search-and-replace-text-in-a-document-part

我知道这不是理想的方法,但我已经搜索过了,没有找到直接编辑byte[]的方法,而不需要进行涉及写出文件、在Word中打开进行编辑,然后重新上传以恢复新字节的转换。在重新读取文件之前执行byte[] byteArray = Encoding.UTF8.GetBytes(docText);会破坏它们,任何其他我尝试过的EncodingUTF7DefaultUnicodeASCII)也是如此,当我尝试使用上面的WriteFile()函数将它们写回时,在最后一行。当未编码并仅使用File.ReadAllBytes()收集,然后使用WriteFile()将字节写回时,它可以正常工作。

更新:

可能可以像这样操作字节:

//byte[] byteArray = File.ReadAllBytes("Test.docx"); // you might be able to assign your bytes here, instead of from a file?
byte[] byteArray = GetByteArrayFromDatabase(fileId); // function you have for getting the document from the database
using (MemoryStream mem = new MemoryStream())
{
    mem.Write(byteArray, 0, (int)byteArray.Length);
    using (WordprocessingDocument wordDoc =
            WordprocessingDocument.Open(mem, true))
    {
        // do your updates -- see string or XML edits, above

        // Once done, you may need to save the changes....
        //wordDoc.MainDocumentPart.Document.Save();
    }

    // But you will still need to save it to the file system here....
    // You would update "documentPath" to a new name first...
    string documentPath = @"C:\temp\newDoc.docx";
    using (FileStream fileStream = new FileStream(documentPath,
            System.IO.FileMode.CreateNew))
    {
        mem.WriteTo(fileStream);
    }
}

// And then read the bytes back in, to save it to the database
byteArray = File.ReadAllBytes(documentPath); // new bytes, ready for DB saving

参考:

https://learn.microsoft.com/en-us/previous-versions/office/office-12//ee945362(v=office.12)

但请注意,即使使用此方法也需要保存文档,然后重新读取它,以便将其保存为数据库的字节。如果文档在打开文档的那一行中是.doc格式而不是.docx,则此方法也会失败。

除了最后一个部分将文件保存到文件系统之外,您还可以将内存流直接保存回字节,一旦您退出WordprocessingDocument.Open()块,但仍在using (MemoryStream mem = new MemoryStream() { ... }语句内:

// Convert
byteArray = mem.ToArray();

这将会返回你的Word文档byte[]


0
  1. 创建一个内存文件系统,有相应的驱动程序。
  2. 为Word提供FTP服务器路径(或其他路径),然后使用该路径推送数据。

需要注意的一件重要事情是:通常将文件存储在数据库中不是一个好的设计。


在这个项目中,将文件存储在数据库中是必须的。第二个选项涉及文件系统,所以我不能接受这个答案,谢谢。 - Ken D
3
在数据库中存储文件通常不是一个好的设计,为什么?我认为这是一个复杂的决定,需要考虑每种情况的利弊。我认为一般而言不能简单地说它是或不是一个好的设计。你可以解释一下吗?回答:通常而言,将文件存储在数据库中会导致性能下降并增加数据库的负担。此外,数据库适合存储结构化数据,而文件通常是非结构化的数据,因此应该使用适当的文件系统来管理和存储文件。这并不意味着在某些情况下在数据库中存储文件是不可行的,但是应该仔细考虑其影响并选择适当的解决方案。 - CesarGon
我的第二个选择与GvS的答案类似,不涉及文件系统。将文件从内存传输到套接字上,然后逐字读取。无需访问文件系统。 - nulvinge
1
CesarGon: 确实你需要评估你的选项,但在一般情况下(大多数人面临的情况),这是糟糕的设计。数据库不是为容纳大块数据而设计的,它会将其表示为指向一个块的指针,而可以将其表示为文件路径。当文件本身就是文件时,使用文件会更加简单明了。这个问题提供了一个很好的例子。数据库不是为文件而设计的,文件系统才是。 - nulvinge
请参考以下内容:https://dev59.com/xHVC5IYBdhLWcg3wsTVi将文档存储为数据库中的BLOB是否存在任何缺点? - nulvinge
显示剩余2条评论

0

0
你可以看看Sharepoint是如何解决这个问题的。他们为数据库中存储的文档创建了一个Web界面。
在你的应用程序中创建或嵌入一个Web服务器来提供页面给Word并不难,你甚至不必使用标准端口。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接