文章目录
- 背景
- 仓库介绍
- 获取代码
- 本地体验
- 项目效果展示
- 本地test.docx
- 线上解析效果
- 线上体验
- codesandbox
- 在inscode中体验(1.1.0版本正在审核)
- go代码主逻辑展示
- html使用wasm展示
- 开发进度
- 最终吐槽
背景
之前的一个项目, 涉及到前端解析docx文件流为html来做编辑,但是,说实话,前端常用的mammoth库除了文字和图片能提取, 很多样式都会丢,效果看着非常不理想。
最终docx相关功能:通过转pdf进行预览,然后编辑直接不做。
于是,兴趣使然,我尝试使用go语言读取docx文件,使用encoding/xml模块解析docx中的文字和图片,最终输出为html。
仓库介绍
我将此库命名为 docx-to-html-wasm ,当然,只支持简单的段落文字、表格文字以及图片解析。
获取代码
- npm仓库地址:
https://www.npmjs.com/package/docx-to-html-wasm?activeTab=code
- 执行
npm view docx-to-html-wasm@1.1.0 dist.tarball
可获取
本地体验
## 新建目录 test ,进入
yarn add docx-to-html-wasm@latest
serve -s node_modules/docx-to-html-wasm
## 访问浏览器 127.0.0.1:3000
因为wasm文件的加载是通过fetch请求,所以你需要通过serve启动一个简单的静态网站服务,然后访问127.0.0.1:3000. 如图中的红框所示
项目效果展示
本地test.docx
线上解析效果
线上体验
codesandbox
codesandbox访问
在inscode中体验(1.1.0版本正在审核)
go代码主逻辑展示
go代码写的太烂,只展示main函数。转化逻辑让chatgpt帮你写就行。
- !wasm时,读取docx并转换为html到本地进行预览。
- wasm时,接收前端input传来的文件流(base64编码二进制流,防止文件损坏),解析为html并返回。
//go:build !wasm
// +build !wasm
package main
import (
"encoding/base64"
"fmt"
"go-wasm/src/parse"
"go-wasm/src/utils"
"io/ioutil"
)
func main() {
// 读取docx文件的内容
content, err := ioutil.ReadFile("example.docx")
if err != nil {
fmt.Println("Error reading docx file:", err)
return
}
// 将内容转换为UTF-8编码
utf8Content := string(content)
// 将UTF-8编码的内容进行base64编码
base64Content := base64.StdEncoding.EncodeToString([]byte(utf8Content))
// fmt.Print(base64Content)
// 解码
zipReader := utils.ConvertBase64ToZipReader(base64Content)
html, _ := parse.DocxToHTML(zipReader)
// fmt.Print(html)
ioutil.WriteFile("dist/index.html", []byte(html), 0777)
}
//go:build wasm
// +build wasm
package main
import (
"go-wasm/src/parse"
"go-wasm/src/utils"
"syscall/js"
)
// wasm构建使用以下函数
func DocxToHtml(this js.Value, inputs []js.Value) interface{} {
base64Content := inputs[0].String()
zipReader := utils.ConvertBase64ToZipReader(base64Content)
html, _ := parse.DocxToHTML(zipReader)
return js.ValueOf(html)
}
func registerCallbacks() {
js.Global().Set("DocxToHtml", js.FuncOf(DocxToHtml))
}
func main() {
c := make(chan struct{}, 0)
registerCallbacks()
<-c
}
html使用wasm展示
所有文件获取方式都在 本文的仓库介绍 中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Docx to HTML</title>
</head>
<style>
#html-output img {
width: 100%;
}
</style>
<body>
<div style="display: flex;width: 100%;justify-content: space-around">
<input type="file" id="fileInput" accept=".docx">
<div style="width: 48%" id="html-output"></div>
</div>
<script src="wasm_exec.js"></script>
<script>
// 加载go-wasm模块
const go = new Go();
WebAssembly.instantiateStreaming(fetch('output.wasm'), go.importObject).then((result) => {
const mod = result.module;
const inst = result.instance;
go.run(inst);
});
// 调用wasm,转化docx为html
async function convertDocx(base64Data) {
const DocxToHtml = globalThis.DocxToHtml;
const htmlOutput = DocxToHtml(base64Data);
document.getElementById('html-output').innerHTML = htmlOutput;
}
// 文件转base64
async function convertFileToBase64(file) {
var reader = new FileReader();
reader.onloadend = function () {
console.log('reader.result', reader.result)
convertDocx(reader.result.split(',')[1])
}
file ? reader.readAsDataURL(file) : alert('请选择一个文件')
}
// 文件上传监听
document.getElementById('fileInput').addEventListener('change', async function (event) {
const files = event.target.files;
convertFileToBase64(files[0])
});
</script>
</body>
</html>
开发进度
-
- 支持文字解析:
-
- 3种图片解析:
- 通过本地插入的图片;
- 通过直接粘贴作为base64嵌入的图片;
- 2的位置及层级会有变种。(我推测至少有4种图片需要解析,可能仅仅是标签位置和层级改变就得做一堆兼容)
- 3种图片解析:
-
- 支持表格中的纯文字解析,因为encoding/xml通过静态映射xml标签,难以解决段落和表格的顺序问题,可以结合etree库来解析段落和表格的顺序。
-
- 不支持横向布局。
-
- 不支持页码分割。
-
-
性能测试:(纯文字最快,图片越多越慢, g5400测试结果如下:)
文件大小 加载时间(秒) 60M 24s 20M 9s 10M <4s
-
最终吐槽
这docx里的xml真复杂,希望有大佬整一个吧。
- 光图片解析就得兼容至少4种情况,
- 使用etree性能下降很多,20M的文件解析从4s上升到9秒,但是可以支持表格解析。
- 甚至还有横向布局这种玩意儿。
- 把xml布局转化为html+css布局可真是蛋疼。
- docx文件没有保存页码信息,全靠渲染生成,这也是不同软件下样式有差异的原因。
- docx在线预览的话建议让后端转pdf回来。 在线编辑的话建议买onlyoffice