用Node生成office文档(doc/docx)不完美方案

198 阅读2分钟

用到的生成库

officegen github.com/Ziv-Barber/…

demo下载地址

image.png

image.png

image.png
当然也有下载了更高,版本迭代更多的,文档全面的docx 。对比文档来看officegen的api更简单方便。

原理

当把.docx 文件更改后缀名为.zip 。解压之后可以看到这样的文件结构

image.png
其中 document.xml 就是内容区域,styles.xml存储了一些样式
这个库就是主要把内容生成 document.xml的标签,然后通过jszip打包。最后将生成好的文档呈现出来
除了docx,ppt,xls等文档也是可以通过这个库生成。

服务端

生成Docx部分,使用了 createByJson这个API ,这个API可以使用一个JSON描述的文档结构直接生成成为Docx文档,所以在浏览器端只需要关注怎么样生成这个结构即可。

const generateDocx = async (
  HTTPStream,
  data,
  options = {
    orientation: "portrait" || "landscape",
    author: "",
    creator: "",
    description: "",
    keywords: "",
    pageMargins: { top: 1800, right: 1440, bottom: 1800, left: 1440 },
    pageSize: "A4",
  }
) => {
  return new Promise((resolve, reject) => {
    let docx = officegen({ type: "docx", ...options });
    docx.createByJson(data);
    docx.on("finalize", function (written) {
      console.log(
        "Finish to create a Word file.\nTotal bytes created: " + written + "\n"
      );
      resolve();
    });

    docx.on("error", function (err) {
      reject();
      console.log("// Error handing...");
    });
    docx.generate(HTTPStream);
  });
};

使用 koa搭建HTTP服务

const Koa = require("koa");
const router = require("koa-router")();
const static_ = require("koa-static");
const bodyparser = require("koa-bodyparser");
const { PassThrough } = require("stream");
var app = new Koa();
var port = 3001;
app.use(static_(path.join(__dirname, "./public/")));
/**
 * 使用koa-router 来定义一个post请求  
 * 接收 nodedata 和 options 两个参数。
 * https://github.com/Ziv-Barber/officegen/blob/master/manual/docx/README.md#the-document-objects-settings
 */
router.post("/api/docx.gen", async (ctx, next) => {
  const { nodedata = [], options = {} } = ctx.request.body;
  ctx.set(
    "Content-Type",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
  );
  ctx.set("Content-disposition", "attachment; filename=demo.docx");

  const stream = new PassThrough();
  ctx.body = stream;
  ctx.status = 200;
  generateDocx(stream, nodedata, options);
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(port);

http服务就初步搭建成功,可以跑起来之后访问 localhost:3001/api/docx.gen 这个接口了

客户端

1,获取到要生成的html节点

2,将节点转换成 createByJson这个API需要的格式

// 将HTML字符串转成DOM对象
const officeGen = (html) => {
  const Parser = new DOMParser();
  const doc = Parser.parseFromString(html, "text/html");
  const nodes = doc.body.childNodes;
  return nodes2array(nodes);
};
// 将所有的dom节点转成需要的格式 
function nodes2array(nodes) {
  var Root = [];
  for (var i = 0; i < nodes.length; i++) {
    possiblyAddNodeToResult(Root, nodes[i], {});
  }
  return Root;
}
// 递归处理节点 生成数组 
function possiblyAddNodeToResult(result = [], node, attr = {}) {
    //...
}
//... 篇幅太长了可以下载demo查看


3,调用接口 获取文件流

    const getDocx = () => {
      const html = document.querySelector("#report").innerHTML;
      const nodedata = officeGen(html);
      fetch("/api/docx.gen", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          nodedata,
          options: {
            orientation: "landscape",
          },
        }),
      })
        .then((res) => res.blob())
        .then((blob) => {
          const url = URL.createObjectURL(blob);
          window.open(url);
        });
    };

4,保存文件到本地
在第三步是直接用的URL.createObjectURL 生成url再新窗口打开,但是这样生成的文档名称是随机的。所以完成请求之后可以使用saveAs这些第三方库保存。

不是很完美的生成方案

像基本的字体样式 表格都能够支持了。图片canvas,需要单独处理,转成图片上传到服务端或者用base64方式,但是都会增加服务器压力。后面就尝试一下另外个库 docx的生成效果。