likes
comments
collection
share

使用officegen 生成word,为word插入可导航的标题

作者站长头像
站长
· 阅读数 60

结论

完整代码在github.com/it-mei/offi…

先说结论,写一个styles.xml内容如下,后续为查找过程

<w:styles xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
    xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
    xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
    xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"
    xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" mc:Ignorable="w14 w15 w16se">
    <w:style w:type="paragraph" w:styleId="myHeading1">
        <w:name w:val="heading 1"/>
    </w:style>
    <w:style w:type="paragraph" w:styleId="myHeading2">
        <w:name w:val="heading 2"/>
    </w:style>
    <w:style w:type="paragraph" w:styleId="myHeading3">
        <w:name w:val="heading 3"/>
    </w:style>
    <w:style w:type="paragraph" w:styleId="myHeading4">
        <w:name w:val="heading 4"/>
    </w:style>
    <w:style w:type="paragraph" w:styleId="myHeading5">
        <w:name w:val="heading 5"/>
    </w:style>
</w:styles>

生成程序这样写

let officegen = require("officegen");
let fs = require("fs");
let styleXML = fs.readFileSync("./utils/styles.xml", "utf-8");
let docx = officegen({
    type: "docx",
    title:"文档一",
    styleXML,
});
let style = { bold: true, font_face: "楷体", font_size: 20,align:'left' };


let pObj1 = docx.createP()
pObj1.options.force_style = "myHeading1";
pObj1.addText(`一级标题`,style)

docx.putPageBreak();

let pObj2 = docx.createP()
pObj2.options.force_style = "myHeading2";
pObj2.addText(`二级标题`,style)
docx.putPageBreak();

let pObj3 = docx.createP()
pObj3.options.force_style = "myHeading3";
pObj3.addText(`三级标题`,style)
docx.putPageBreak();

//  将 docx 数据写入到文件
let out = fs.createWriteStream("./out/data.docx");
out.on("error", function (err) {
    console.log(err);
});
docx.generate(out);

可以生成带导航的标题

需求

最近接收到一个需求,需要根据Java端传送的JSON数据生成一份Docx格式的报告文档。

技术选择

经过调研,可以使用docxtemplaterofficegenadm-zip来进行Word文档处理。

鉴于文档中包含大量表格且行数、样式等变化较多,docxtemplater的免费版本不支持动态插入表格,所以排除了此选项。 adm-zip通过修改Docx的XML来实现,主要操作word/document.xml,但作为非专业研究人员,大量处理XML的操作还是选择尽量避免。最后选择了officegen来进行开发。

问题

开发过程中遇到了一个问题,需要对对应文字进行一级标题和二级标题的添加,便于用户通过导航快速跳转

根据officegen文档描述,新增相关代码如下,插入

let officegen = require("officegen");
let fs = require("fs");
//  使用 officegen 创建一个 docx 文件
let styleXML = fs.readFileSync("./utils/styles.xml", "utf-8");
let docx = officegen({
    type: "docx",
    title:"文档一",
    styleXML,
});
let style = { bold: true, font_face: "楷体", font_size: 20,align:'left' };
docx.createP().addText(`一级标题`,style)
docx.putPageBreak();

docx.createP().addText(`二级标题`,style)
docx.putPageBreak();

docx.createP().addText(`三级标题`,style)

//  将 docx 数据写入到文件
let out = fs.createWriteStream("./out/data.docx");
out.on("error", function (err) {
    console.log(err);
});
docx.generate(out);

得到了结果如下

使用officegen 生成word,为word插入可导航的标题

发现导航的地方并没有识别标题,并不能快速引导用户跳转到指定章节

寻找解决方案

为了解决这个问题,查询了officegen的在线文档及github的issue,并没有找到对应的解决方案。

仔细想想,既然在word上设置后会生效,docx本质上又是office openxml,那一定有什么配置会让标题的设置生效,那就在word上点一个标题,然后对比一下xml有什么区别,不就能找到了吗。

那就用生成的docx,设置个标题,再设置上标题,一起解压缩下,对比对比,肯定能发现端倪

使用officegen 生成word,为word插入可导航的标题

然后对docx包进行了拆解,将其后缀改为.zip 然后解压缩,解压缩后得到的文件结构如下:

使用officegen 生成word,为word插入可导航的标题

使用排除法,进行范围的文件替换,逐步缩小影响范围,最终发现word/document.xmlword/styles.xml这两个文件中被替换后,神奇的导航部分出现了标题。

那就好整了,继续缩小范围,

发现,word/document.xml有标题版本多这样一个pStyle的设置

                    <w:pPr>
                        <w:pStyle w:val="a3"/>
                    </w:pPr>

        而无标题版本是这样

                    <w:pPr>
                        <w:ind/>
                    </w:pPr>

其中`w:val`对应的值`a3`,在`word/styles.xml`中有对应的配置

            <w:style w:type="paragraph" w:styleId="a3">
                    <w:name w:val="heading 1"/>
                    <w:basedOn w:val="a"/>
                    <w:next w:val="a"/>
                    <w:link w:val="a4"/>
                    <w:uiPriority w:val="10"/>
                    <w:qFormat/>
                    <w:rsid w:val="00C31982"/>
                    <w:pPr>
                        <w:spacing w:before="240" w:after="60"/>
                        <w:jc w:val="center"/>
                        <w:outlineLvl w:val="0"/>
                    </w:pPr>
                    <w:rPr>
                        <w:rFonts w:asciiTheme="majorHAnsi" w:eastAsiaTheme="majorEastAsia" w:hAnsiTheme="majorHAnsi" w:cstheme="majorBidi"/>
                        <w:b/>
                        <w:bCs/>
                        <w:sz w:val="32"/>
                        <w:szCs w:val="32"/>
                    </w:rPr>
                </w:style>
                <w:style w:type="character" w:customStyle="1" w:styleId="a4">
                    <w:name w:val="标题 字符"/>
                    <w:basedOn w:val="a0"/>
                    <w:link w:val="a3"/>
                    <w:uiPriority w:val="10"/>
                    <w:rsid w:val="00C31982"/>
                    <w:rPr>
                        <w:rFonts w:asciiTheme="majorHAnsi" w:eastAsiaTheme="majorEastAsia" w:hAnsiTheme="majorHAnsi" w:cstheme="majorBidi"/>
                        <w:b/>
                        <w:bCs/>
                        <w:sz w:val="32"/>
                        <w:szCs w:val="32"/>
                    </w:rPr>
                </w:style>

替换之后,奇迹发生了!

使用officegen 生成word,为word插入可导航的标题

对内容进行极限删减,并放置在无标题版本的代码中,发现生效的内容是

        <w:style w:type="paragraph" w:styleId="a3">
            <w:name w:val="heading 1"/>
        </w:style>

并且,a3相当于是一个id,可以进行修改,只要匹配的上,就可以是任意值,所以可以命名为myHeading1

那么就简单了,只要在程序里,给word/styles.xml上加入上述代码,理论上就可以完成导航标题的设置

查阅文档,没有发现怎么能够直接插入这样的配置,于是翻阅了github 中 officegen的 issue

在issue中进行检索时候,发现了这样一条:Paragraph styles · Issue #257 · Ziv-Barber/officegen (github.com)

说是添加heading无效,但是有一条评论引起了注意

使用officegen 生成word,为word插入可导航的标题

然后观察了259号issue

Allow custom document styles to be set by colinjeanne · Pull Request #259 · Ziv-Barber/officegen (github.com)

可以通过声明docx时候,添加styleXml 的方式添加样式

使用officegen 生成word,为word插入可导航的标题

既然如此,那就尝试一下此方法

先写一个styles.xml的样式表,命名五个myHeading (注意:styles.xml里,第一行是<?xml version="1.0" encoding="UTF-8" standalone="yes"?>,这一行代码officegen会为我们添加上,所以样式表里不写这一行,写了会报错)

<w:styles xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
    xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
    xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
    xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"
    xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" mc:Ignorable="w14 w15 w16se">
    <w:style w:type="paragraph" w:styleId="myHeading1">
        <w:name w:val="heading 1"/>
    </w:style>
    <w:style w:type="paragraph" w:styleId="myHeading2">
        <w:name w:val="heading 2"/>
    </w:style>
    <w:style w:type="paragraph" w:styleId="myHeading3">
        <w:name w:val="heading 3"/>
    </w:style>
    <w:style w:type="paragraph" w:styleId="myHeading4">
        <w:name w:val="heading 4"/>
    </w:style>
    <w:style w:type="paragraph" w:styleId="myHeading5">
        <w:name w:val="heading 5"/>
    </w:style>
</w:styles>

然后修改生成代码

let officegen = require("officegen");
let fs = require("fs");
let styleXML = fs.readFileSync("./utils/styles.xml", "utf-8");
let docx = officegen({
    type: "docx",
    title:"文档一",
    styleXML,
});
let style = { bold: true, font_face: "楷体", font_size: 20,align:'left' };


docx.createP({
    pStypeDef: 'myHeading1'
}).addText(`一级标题`,style)
docx.putPageBreak();

docx.createP({
    pStypeDef: 'myHeading1'
}).addText(`二级标题`,style)
docx.putPageBreak();

docx.createP({
    pStypeDef: 'myHeading1'
}).addText(`三级标题`,style)
docx.putPageBreak();

//  将 docx 数据写入到文件
let out = fs.createWriteStream("./out/data.docx");
out.on("error", function (err) {
    console.log(err);
});
docx.generate(out);

然后执行,发现并未生效

使用officegen 生成word,为word插入可导航的标题

可能有哪里的配置还是不正常,解包后发现,word/styles.xml被正确放置了,再看word/document.xml,标题对应的位置,并没有一下代码

<w:style w:type="paragraph" w:styleId="a3">
    <w:name w:val="heading 1"/>
</w:style

那可能有别的设置方式,经过各种测试,发现如下写法有效

    let pObj1 = docx.createP()
    pObj1.options.force_style = "myHeading1";
    pObj1.addText(`一级标题`,{ bold: true, font_face: "楷体", font_size: 20,align:'left' })

name修改生成代码如下

let officegen = require("officegen");
let fs = require("fs");
let styleXML = fs.readFileSync("./utils/styles.xml", "utf-8");
let docx = officegen({
    type: "docx",
    title:"文档一",
    styleXML,
});
let style = { bold: true, font_face: "楷体", font_size: 20,align:'left' };


let pObj1 = docx.createP()
pObj1.options.force_style = "myHeading1";
pObj1.addText(`一级标题`,style)

docx.putPageBreak();

let pObj2 = docx.createP()
pObj2.options.force_style = "myHeading2";
pObj2.addText(`二级标题`,style)
docx.putPageBreak();

let pObj3 = docx.createP()
pObj3.options.force_style = "myHeading3";
pObj3.addText(`三级标题`,style)
docx.putPageBreak();

//  将 docx 数据写入到文件
let out = fs.createWriteStream("./out/data.docx");
out.on("error", function (err) {
    console.log(err);
});
docx.generate(out);

再进行生成

使用officegen 生成word,为word插入可导航的标题

圆满完成!

转载自:https://juejin.cn/post/7239876138544562233
评论
请登录