无法使用 Blob 对象在客户端打开“docx”文件 - vanilla JavaScript

Posted

技术标签:

【中文标题】无法使用 Blob 对象在客户端打开“docx”文件 - vanilla JavaScript【英文标题】:Unable to open `docx` files client-side using a Blob object - vanilla JavaScript 【发布时间】:2020-03-30 17:54:55 【问题描述】:

这是客户端代码,它是最小的、完整的和可验证的 sn-p,它允许其他开发人员自行测试。

// requires: a string that contains html tags
// returns: a word document that can be downloaded with extension .doc or docx
// @ param cvAsHTML is a string that contains html tags

const preHtml = "<html xmlns:v='urn:schemas-microsoft-com:vml' xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:w='urn:schemas-microsoft-com:office:word' xmlns='http://www.w3.org/TR/html4/loose.dtd\'><head><meta charset='utf-8'></head><body>";
const postHtml = "</body></html>";
const html = preHtml + cvAsHTML + postHtml;

let filename = "filename";
const blob = new Blob(["\ufeff", html],  type: "application/msword");

上面的 sn-p 就像一个魅力。请注意,XML 模式是多余的,实际上是不必要的。 doc 文件可以在没有它们的情况下工作,但必须存在 head 和 body 标签。

对于docx 文件,我无法下载该文件。该文件似乎已损坏,经过几次试验,我真的不知道该怎么办。这是 docx 文件的代码:

const preHtml = "<?xml version='1.0' encoding='UTF-8?><html xmlns:v='urn:schemas-microsoft-com:vml' xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:w='urn:schemas-microsoft-com:office:word' xmlns='http://www.w3.org/TR/html4/loose.dtd\'><head><meta charset='utf-8'></head><body>";
const postHtml = "</body></html>";
const html = preHtml + cvAsHTML + postHtml;

let filename = "filename.docx";
const blob = new Blob(["\ufeff", html],  type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main");

注意:我已更改 Blob 对象内的 MIME 类型,并尝试了不同的其他选项,例如 application/zipapplication/octet-stream 等,但均无济于事。我还更改了 prehtml 变量以包括:

<?xml version='1.0' encoding='UTF-8?>

鉴于我了解 docx 文件本质上是包含 xml 段的压缩文件...

非常感谢您提供的任何帮助。

编辑:2019 年 12 月 16 日

这是我在@dw_建议的实现后截取的截图:

使用JSZip 的实现无法按预期工作,因为:

    浏览器本身不允许用户在 microsoft word 中打开文件,就像使用 doc 文件一样; 用户必须先保存文件,但即便如此,文件也无法打开,因为它已损坏。

【问题讨论】:

【参考方案1】:

我觉得没那么简单。带有 docx 扩展名的文档确实是压缩的,但没有单个压缩文件,但需要特定的文件夹结构和文件名,请参阅https://en.wikipedia.org/wiki/Office_Open_XML_file_formats。为了能够动态生成文档,您必须生成“最小结构和文件”。您可以通过保存空的 docx 文件并解压缩它来了解我的意思。在 MS Word 或 LibreOffice 或其他任何工具中尝试,结构将是“相同的”。

使用拉链 - 也许这可以帮助https://stuk.github.io/jszip/ 可以提供帮助。

对于文档本身 - 我可以建议我们使用的方法。我们在office应用程序中准备了“模板文档”,并在其中放置了占位符,例如$HEADER$、$BODY$等。然后在程序中我们解压缩它,将占位符替换为真实字符串,然后将其压缩到输出。它非常有效和实用——我们可以完全控制最终文档,并且更改设计、颜色、静态文本非常容易——只需编辑模板然后上传即可。

【讨论】:

感谢您的回答以及您花时间写这篇文章。这并不能回答问题,因为给出的所有信息/参考都是通用的。这一切我都已经知道了。该问题为我要完成的工作提供了代码 sn-ps;我能够在客户端下载doc 文件,但是当我对docx 文件进行类似尝试时,该文件被证明已损坏。这是为什么?我的代码中缺少什么?我构造prehtmlposthtml 字符串的方式是什么?它是 Blob obj 中的类型吗(请参阅问题的标题)?不只是在寻找外部参考【参考方案2】:

.docx 是压缩文件的集合,使用simplified, minimal DOCX document as a guideline,我创建了一个".zip" 文件,其中包含主要的word/document.xml 文件和3 个额外的必需文件。

更多关于.docx文件的信息可以在这里找到:An Informal Introduction to DOCX

// Other needed files
const REQUIRED_FILES = 
  content_types_xml: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml"
          ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
</Types>`,
  rels: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
              Target="word/document.xml"/>
</Relationships>`,
  document_xml_rels: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">

</Relationships>`
;
/// --
const preHtml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:o="urn:schemas-microsoft-com:office:office"
            xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
            xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml"
            xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
            xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
            xmlns:w10="urn:schemas-microsoft-com:office:word"
            xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
            xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
            xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
            xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk"
            xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
            xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 wp14">
    <w:body><w:p w:rsidR="005F670F" w:rsidRDefault="005F79F5">`;
const postHtml = `<w:bookmarkStart w:id="0" w:name="_GoBack"/>
            <w:bookmarkEnd w:id="0"/>
        </w:p>
        <w:sectPr w:rsidR="005F670F">
            <w:pgSz w:w="12240" w:h="15840"/>
            <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" w:footer="720"
                     w:gutter="0"/>
            <w:cols w:space="720"/>
            <w:docGrid w:linePitch="360"/>
        </w:sectPr>
    </w:body>
</w:document>`;
const cvAsHTML = `<w:r><w:t>Sample content inside .docx</w:t></w:r>`;
const html = preHtml + cvAsHTML + postHtml;

function generateDocx(fname) 
  let zip = new JSZip();
  // prerequisites: 
    zip.file("_rels/.rels", REQUIRED_FILES.rels);
    zip.file("[Content_Types].xml", REQUIRED_FILES.content_types_xml);
    zip.file("word/_rels/document.xml.rels", REQUIRED_FILES.document_xml_rels);
  //
  zip.file("word/document.xml", html);
  zip.generateAsync(type:"blob").then(function(content) 
      saveAs(content, fname + ".docx");
  );
<script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.2/dist/FileSaver.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.2.2/jszip.min.js"></script>
<button onclick="generateDocx('test_1')">Download .docx</button>

使用的库

JSZip FileSaver.js

External Demo(因为内联可能不起作用)

【讨论】:

感谢您的详细解答,确实很有用。但是,这并没有按预期工作。为了清楚起见,我重复一遍,我用 JS 原生 blob 创建的函数就像魅力一样。当用户以doc 格式保存文件时,浏览器本机支持“使用 microsoft word 保存文件”,我可以直接打开文件而无需选择保存选项。尝试运行自己的 sn-p,您会看到浏览器不允许您将文件保存在 docx 中,也不允许您像使用 doc 文件那样打开(读取)文件。 @rags2riches 您确定要在文件名(1) 中添加.docx 扩展名吗?正如我在 2 台 Windows 机器和 Mac 上测试过的那样。两者都在打开word文档。 imgur.com/a/z2zpzIP 是的,我有。我正在运行一些额外的测试,因为我最初的假设是 cvAsHTML 变量包含要处理的无效语法,但即使完全删除变量,文件也不会打开。在 MS OS 中的 Chrome anf FF 中测试。

以上是关于无法使用 Blob 对象在客户端打开“docx”文件 - vanilla JavaScript的主要内容,如果未能解决你的问题,请参考以下文章

无法从 BLOB 存储打开备份设备 - 访问被拒绝

RN从IOS中的URL获取blob下载docx或pdf

如何在 Azure 逻辑应用中将 blob 文件内容转换为 .docx

在 iOS Chrome 和 Safari 上使用 FileSaver.js 保存或打开 blob

在移动设备上从 Angular 下载 Word .docx 作为 Blob 文件

如何通过api将文本文档(ODT或DOCX)中的表格导出为EMF格式