实现思路:
一、制作模板
1、在word中无非三种格式,文字、图片、表格,目前我所涉及到的业务中只有这三个,其他情况没涉及到没研究,首先需要准备一个模板文件,即xml格式的模板,通过打开一个空白的word文档,另存为xml文件,即可得到一个空白的word模板(可使用notepad++工具打开)
2、获取到空白文档的模板后,再新建一个word,填充文字,再另存为xml,打开并对比之前空白的模板之间的差异,可以找出添加文字后加了哪些代码。如果需要表格,则在空白文档加入加入表格,然后保存为xml文件打开查看代码,对比差异,可以知道具体的代码代表的意思。
3、图片方面的处理,同样是在空白文档中添加图片,然后查看xml代码,其中图片是以base64形式出现的,在替换图片的过程中,是需要将图片的base64码进行替换,所有的需要动态改变的数据,都使用freemarker标签的形式替换,如果不了解的,自行去了解一下freemarker标签的使用,具体的在xml中的代码的代表意思,可以到百度查找。
二、填充模板
1、后台使用jsoup解析HTML代码后,获取html代码中的值,保存到map中。文字段落,图片,表格,可分开处理。解析的HTML代码是通过百度UEditor生成的代码,如果是需要解析完整的正规的html代码,需要另外写解析标签的代码。
2、解析HTML时,可对应保存HTML中标签存在的属性,对应xml中的属性,通过实体来进行设置,遍历标签时,获取实体属性即可
3、填充模板保存后的文件仍然是xml格式的文件,需要另外处理,将文件另存为docx格式的word文档
实现代码:
注意:代码仅可以作为参考,即使完全复制,也不一定适合你,仅仅提供一个思路和参考,是否值得借鉴,需要自己评估,或者有更好的方法,欢迎留言评论一起交流
模板代码:(xml模板的代码,只贴出body中的代码)
<w:body>
<wx:sect>
<!-- 判断第一部分是文字段落,并遍历第一部分的段落 -->
<#if part1 == "paragraph">
<#list paragraphList1 as paragraph>
<w:p wsp:rsidR="0050577F" wsp:rsidRPr="00177379" wsp:rsidRDefault="00177379">
<w:pPr>
<w:jc w:val="${paragraph.align}"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/>
<wx:font wx:val="宋体"/>
<w:color w:val="${paragraph.color}"/>
<#if paragraph_index==0>
<w:sz w:val="30"/>
<w:sz-cs w:val="30"/>
<#else>
<w:sz w:val="${paragraph.size}"/>
<w:sz-cs w:val="${paragraph.size}"/>
</#if>
</w:rPr>
<w:spacing w:line="${paragraph.lineHeight}" w:line-rule="auto"/>
</w:pPr>
<!-- 遍历段落中的wr,每个wr可定义对应的文字属性,如果内容不为空,则执行文字遍历循环 -->
<#list paragraph.wrList as wr>
<#if wr.content??>
<w:r>
<w:rPr>
<w:rFonts w:ascii="${wr.font}" w:fareast="${wr.font}" w:h-ansi="${wr.font}" w:hint="fareast"/>
<wx:font wx:val="${wr.font}"/>
<w:color w:val="${wr.color}"/>
<#if paragraph_index==0>
<w:sz w:val="30"/>
<w:sz-cs w:val="30"/>
<#else>
<w:sz w:val="${paragraph.size}"/>
<w:sz-cs w:val="${paragraph.size}"/>
</#if>
${wr.bold}
${wr.tilt}
${wr.underLine}
</w:rPr>
<w:t>${wr.content}</w:t>
</w:r>
</#if>
<!-- 如果是图片,则在wr上添加图片 -->
<#if wr.hasImg == 1>
<#list wr.imgList as image>
<w:r wsp:rsidRPr="006E22D7">
<w:rPr>
<w:noProof/>
</w:rPr>
<w:pict>
<v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f">
<v:stroke joinstyle="miter"/>
<v:formulas>
<v:f eqn="if lineDrawn pixelLineWidth 0"/>
<v:f eqn="sum @0 1 0"/>
<v:f eqn="sum 0 0 @1"/>
<v:f eqn="prod @2 1 2"/>
<v:f eqn="prod @3 21600 pixelWidth"/>
<v:f eqn="prod @3 21600 pixelHeight"/>
<v:f eqn="sum @0 0 1"/>
<v:f eqn="prod @6 1 2"/>
<v:f eqn="prod @7 21600 pixelWidth"/>
<v:f eqn="sum @8 21600 0"/>
<v:f eqn="prod @7 21600 pixelHeight"/>
<v:f eqn="sum @10 21600 0"/>
</v:formulas>
<v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"/>
<o:lock v:ext="edit" aspectratio="t"/>
</v:shapetype>
<w:binData w:name="wordml://${image.imgName}.png" xml:space="preserve">${image.imgBase64}</w:binData>
<v:shape id="${image.imgId}" o:spid="_x0000_i1025" type="#_x0000_t75" style="width:${image.width}pt;height:${image.height}pt;visibility:visible;mso-wrap-style:square">
<v:imagedata src="wordml://${image.imgName}.png" o:title="${image.imgName}"/>
</v:shape>
</w:pict>
</w:r>
</#list>
</#if>
</#list>
</w:p>
</#list>
</#if>
<!-- 如果第一部分是表格,则填充表格 -->
<#if part1 == "table">
<w:tbl>
<w:tblPr>
<w:tblW w:w="8522" w:type="dxa"/>
<w:tblBorders>
<w:top w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:left w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:bottom w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:right w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:insideH w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:insideV w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
</w:tblBorders>
<!-- 设置表格的对齐方式 -->
<w:tblLook w:val="${table.tblLook}"/>
</w:tblPr>
<w:tblGrid>
<!-- 表格列,定义的表格有多少列,则遍历显示多少个网格 -->
<#list table.gridColList as gridCol>
${gridCol}
</#list>
</w:tblGrid>
<!-- 遍历表格中的行,tr -->
<#list table.trList as tr>
<w:tr wsp:rsidR="00616293" wsp:rsidRPr="00616293" wsp:rsidTr="00616293">
<w:trPr>
<!-- 设置行高属性 -->
<w:trHeight w:val="${tr.height}"/>
</w:trPr>
<!-- 遍历行中的列 -->
<#list tr.tdList as td>
<w:tc>
<w:tcPr>
<!-- 列宽属性 -->
<w:tcW w:w="${td.width}" w:type="dxa"/>
<w:shd w:val="clear" w:color="auto" w:fill="auto"/>
<w:tcBorders>
${td.top}
${td.right}
${td.bottom}
${td.left}
</w:tcBorders>
${td.gridSpan}
${td.merge}
</w:tcPr>
<!-- 遍历单元格中的段落文字 -->
<#list td.paragraphList as paragraph>
<w:p wsp:rsidR="0050577F" wsp:rsidRPr="00177379" wsp:rsidRDefault="00177379">
<w:pPr>
<!-- 设置单元格中段落的属性 -->
<w:jc w:val="${paragraph.align}"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/>
<wx:font wx:val="宋体"/>
<w:color w:val="${paragraph.color}"/>
<#if tr_index==0>
<w:sz w:val="30"/>
<w:sz-cs w:val="30"/>
<#else>
<w:sz w:val="${paragraph.size}"/>
<w:sz-cs w:val="${paragraph.size}"/>
</#if>
</w:rPr>
<w:spacing w:line="${paragraph.lineHeight}" w:line-rule="auto"/>
</w:pPr>
<!-- 遍历段落中的wr -->
<#list paragraph.wrList as wr>
<!-- 如果内容不为空,则执行文字遍历循环 -->
<#if wr.content??>
<w:r>
<w:rPr>
<w:rFonts w:ascii="${wr.font}" w:fareast="${wr.font}" w:h-ansi="${wr.font}" w:hint="fareast"/>
<wx:font wx:val="${wr.font}"/>
<w:color w:val="${wr.color}"/>
<#if tr_index==0>
<w:sz w:val="30"/>
<w:sz-cs w:val="30"/>
<#else>
<w:sz w:val="${paragraph.size}"/>
<w:sz-cs w:val="${paragraph.size}"/>
</#if>
${wr.bold}
${wr.tilt}
${wr.underLine}
</w:rPr>
<w:t>${wr.content}</w:t>
</w:r>
</#if>
<!-- 如果是图片,则在wr上添加图片 -->
<#if wr.hasImg == 1>
<!-- 遍历图片 -->
<#list wr.imgList as image>
<w:r wsp:rsidRPr="006E22D7">
<w:rPr>
<w:noProof/>
</w:rPr>
<w:pict>
<v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f">
<v:stroke joinstyle="miter"/>
<v:formulas>
<v:f eqn="if lineDrawn pixelLineWidth 0"/>
<v:f eqn="sum @0 1 0"/>
<v:f eqn="sum 0 0 @1"/>
<v:f eqn="prod @2 1 2"/>
<v:f eqn="prod @3 21600 pixelWidth"/>
<v:f eqn="prod @3 21600 pixelHeight"/>
<v:f eqn="sum @0 0 1"/>
<v:f eqn="prod @6 1 2"/>
<v:f eqn="prod @7 21600 pixelWidth"/>
<v:f eqn="sum @8 21600 0"/>
<v:f eqn="prod @7 21600 pixelHeight"/>
<v:f eqn="sum @10 21600 0"/>
</v:formulas>
<v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"/>
<o:lock v:ext="edit" aspectratio="t"/>
</v:shapetype>
<!-- 设置图片base64字符编码 -->
<w:binData w:name="wordml://${image.imgName}.png" xml:space="preserve">${image.imgBase64}</w:binData>
<!-- 设置图片属性 -->
<v:shape id="${image.imgId}" o:spid="_x0000_i1025" type="#_x0000_t75" style="width:${image.width}pt;height:${image.height}pt;visibility:visible;mso-wrap-style:square">
<v:imagedata src="wordml://${image.imgName}.png" o:title="${image.imgName}"/>
</v:shape>
</w:pict>
</w:r>
</#list>
</#if>
</#list>
</w:p>
</#list>
</w:tc>
</#list>
</w:tr>
</#list>
</w:tbl>
</#if>
<!-- 判断第二部分是文字段落,并遍历第一部分的段落 -->
<#if part2 == "paragraph">
<#list paragraphList2 as paragraph>
<w:p wsp:rsidR="0050577F" wsp:rsidRPr="00177379" wsp:rsidRDefault="00177379">
<w:pPr>
<w:jc w:val="${paragraph.align}"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/>
<wx:font wx:val="宋体"/>
<w:color w:val="${paragraph.color}"/>
<#if paragraph_index==0>
<w:sz w:val="30"/>
<w:sz-cs w:val="30"/>
<#else>
<w:sz w:val="${paragraph.size}"/>
<w:sz-cs w:val="${paragraph.size}"/>
</#if>
</w:rPr>
<w:spacing w:line="${paragraph.lineHeight}" w:line-rule="auto"/>
</w:pPr>
<!-- 遍历段落中的wr,每个wr可定义对应的文字属性,如果内容不为空,则执行文字遍历循环 -->
<#list paragraph.wrList as wr>
<#if wr.content?? >
<w:r>
<w:rPr>
<w:rFonts w:ascii="${wr.font}" w:fareast="${wr.font}" w:h-ansi="${wr.font}" w:hint="fareast"/>
<wx:font wx:val="${wr.font}"/>
<w:color w:val="${wr.color}"/>
<#if paragraph_index==0>
<w:sz w:val="30"/>
<w:sz-cs w:val="30"/>
<#else>
<w:sz w:val="${paragraph.size}"/>
<w:sz-cs w:val="${paragraph.size}"/>
</#if>
${wr.bold}
${wr.tilt}
${wr.underLine}
</w:rPr>
<w:t>${wr.content}</w:t>
</w:r>
</#if>
<!-- 如果是图片,则在wr上添加图片 -->
<#if wr.hasImg == 1>
<#list wr.imgList as image>
<w:r wsp:rsidRPr="006E22D7">
<w:rPr>
<w:noProof/>
</w:rPr>
<w:pict>
<v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f">
<v:stroke joinstyle="miter"/>
<v:formulas>
<v:f eqn="if lineDrawn pixelLineWidth 0"/>
<v:f eqn="sum @0 1 0"/>
<v:f eqn="sum 0 0 @1"/>
<v:f eqn="prod @2 1 2"/>
<v:f eqn="prod @3 21600 pixelWidth"/>
<v:f eqn="prod @3 21600 pixelHeight"/>
<v:f eqn="sum @0 0 1"/>
<v:f eqn="prod @6 1 2"/>
<v:f eqn="prod @7 21600 pixelWidth"/>
<v:f eqn="sum @8 21600 0"/>
<v:f eqn="prod @7 21600 pixelHeight"/>
<v:f eqn="sum @10 21600 0"/>
</v:formulas>
<v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"/>
<o:lock v:ext="edit" aspectratio="t"/>
</v:shapetype>
<w:binData w:name="wordml://${image.imgName}.png" xml:space="preserve">${image.imgBase64}</w:binData>
<v:shape id="${image.imgId}" o:spid="_x0000_i1025" type="#_x0000_t75" style="width:${image.width}pt;height:${image.height}pt;visibility:visible;mso-wrap-style:square">
<v:imagedata src="wordml://${image.imgName}.png" o:title="${image.imgName}"/>
</v:shape>
</w:pict>
</w:r>
</#list>
</#if>
</#list>
</w:p>
</#list>
</#if>
<!-- 如果第二部分是表格,则填充表格 -->
<#if part2 == "table">
<w:tbl>
<w:tblPr>
<w:tblW w:w="8522" w:type="dxa"/>
<w:tblBorders>
<w:top w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:left w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:bottom w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:right w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:insideH w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:insideV w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
</w:tblBorders>
<!-- 设置表格的对齐方式 -->
<w:tblLook w:val="${table.tblLook}"/>
</w:tblPr>
<w:tblGrid>
<!-- 表格列,定义的表格有多少列,则遍历显示多少个网格 -->
<#list table.gridColList as gridCol>
${gridCol}
</#list>
</w:tblGrid>
<!-- 遍历表格中的行,tr -->
<#list table.trList as tr>
<w:tr wsp:rsidR="00616293" wsp:rsidRPr="00616293" wsp:rsidTr="00616293">
<w:trPr>
<!-- 设置行高属性 -->
<w:trHeight w:val="${tr.height}"/>
</w:trPr>
<!-- 遍历行中的列 -->
<#list tr.tdList as td>
<w:tc>
<w:tcPr>
<!-- 列宽属性 -->
<w:tcW w:w="${td.width}" w:type="dxa"/>
<w:shd w:val="clear" w:color="auto" w:fill="auto"/>
<w:tcBorders>
${td.top}
${td.right}
${td.bottom}
${td.left}
</w:tcBorders>
${td.gridSpan}
${td.merge}
</w:tcPr>
<!-- 遍历单元格中的段落文字 -->
<#list td.paragraphList as paragraph>
<w:p wsp:rsidR="0050577F" wsp:rsidRPr="00177379" wsp:rsidRDefault="00177379">
<w:pPr>
<!-- 设置单元格中段落的属性 -->
<w:jc w:val="${paragraph.align}"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/>
<wx:font wx:val="宋体"/>
<w:color w:val="${paragraph.color}"/>
<w:sz w:val="${paragraph.size}"/>
<w:sz-cs w:val="${paragraph.size}"/>
</w:rPr>
<!-- 设置行间距 -->
<w:spacing w:line="${paragraph.lineHeight}" w:line-rule="auto"/>
</w:pPr>
<!-- 遍历段落中的wr -->
<#list paragraph.wrList as wr>
<!-- 如果内容不为空,则执行文字遍历循环 -->
<#if wr.content??>
<w:r>
<w:rPr>
<w:rFonts w:ascii="${wr.font}" w:fareast="${wr.font}" w:h-ansi="${wr.font}" w:hint="fareast"/>
<wx:font wx:val="${wr.font}"/>
<w:color w:val="${wr.color}"/>
<w:sz w:val="${wr.size}"/>
<w:sz-cs w:val="${wr.size}"/>
${wr.bold}
${wr.tilt}
${wr.underLine}
</w:rPr>
<w:t>${wr.content}</w:t>
</w:r>
</#if>
<!-- 如果是图片,则在wr上添加图片 -->
<#if wr.hasImg == 1>
<!-- 遍历图片 -->
<#list wr.imgList as image>
<w:r wsp:rsidRPr="006E22D7">
<w:rPr>
<w:noProof/>
</w:rPr>
<w:pict>
<v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f">
<v:stroke joinstyle="miter"/>
<v:formulas>
<v:f eqn="if lineDrawn pixelLineWidth 0"/>
<v:f eqn="sum @0 1 0"/>
<v:f eqn="sum 0 0 @1"/>
<v:f eqn="prod @2 1 2"/>
<v:f eqn="prod @3 21600 pixelWidth"/>
<v:f eqn="prod @3 21600 pixelHeight"/>
<v:f eqn="sum @0 0 1"/>
<v:f eqn="prod @6 1 2"/>
<v:f eqn="prod @7 21600 pixelWidth"/>
<v:f eqn="sum @8 21600 0"/>
<v:f eqn="prod @7 21600 pixelHeight"/>
<v:f eqn="sum @10 21600 0"/>
</v:formulas>
<v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"/>
<o:lock v:ext="edit" aspectratio="t"/>
</v:shapetype>
<!-- 设置图片base64字符编码 -->
<w:binData w:name="wordml://${image.imgName}.png" xml:space="preserve">${image.imgBase64}</w:binData>
<!-- 设置图片属性 -->
<v:shape id="${image.imgId}" o:spid="_x0000_i1025" type="#_x0000_t75" style="width:${image.width}pt;height:${image.height}pt;visibility:visible;mso-wrap-style:square">
<v:imagedata src="wordml://${image.imgName}.png" o:title="${image.imgName}"/>
</v:shape>
</w:pict>
</w:r>
</#list>
</#if>
</#list>
</w:p>
</#list>
</w:tc>
</#list>
</w:tr>
</#list>
</w:tbl>
</#if>
<!-- 判断第三部分是文字段落,并遍历第一部分的段落 -->
<#if part3?? && part3 == "paragraph">
<#list paragraphList3 as paragraph>
<w:p wsp:rsidR="0050577F" wsp:rsidRPr="00177379" wsp:rsidRDefault="00177379">
<w:pPr>
<w:jc w:val="${paragraph.align}"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/>
<wx:font wx:val="宋体"/>
<w:color w:val="${paragraph.color}"/>
<#if paragraph_index==0>
<w:sz w:val="30"/>
<w:sz-cs w:val="30"/>
<#else>
<w:sz w:val="${paragraph.size}"/>
<w:sz-cs w:val="${paragraph.size}"/>
</#if>
</w:rPr>
<w:spacing w:line="${paragraph.lineHeight}" w:line-rule="auto"/>
</w:pPr>
<!-- 遍历段落中的wr,每个wr可定义对应的文字属性,如果内容不为空,则执行文字遍历循环 -->
<#list paragraph.wrList as wr>
<#if wr.content??>
<w:r>
<w:rPr>
<w:rFonts w:ascii="${wr.font}" w:fareast="${wr.font}" w:h-ansi="${wr.font}" w:hint="fareast"/>
<wx:font wx:val="${wr.font}"/>
<w:color w:val="${wr.color}"/>
<#if paragraph_index==0>
<w:sz w:val="30"/>
<w:sz-cs w:val="30"/>
<#else>
<w:sz w:val="${paragraph.size}"/>
<w:sz-cs w:val="${paragraph.size}"/>
</#if>
${wr.bold}
${wr.tilt}
${wr.underLine}
</w:rPr>
<w:t>${wr.content}</w:t>
</w:r>
</#if>
<!-- 如果是图片,则在wr上添加图片 -->
<#if wr.hasImg == 1>
<#list wr.imgList as image>
<w:r wsp:rsidRPr="006E22D7">
<w:rPr>
<w:noProof/>
</w:rPr>
<w:pict>
<v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f">
<v:stroke joinstyle="miter"/>
<v:formulas>
<v:f eqn="if lineDrawn pixelLineWidth 0"/>
<v:f eqn="sum @0 1 0"/>
<v:f eqn="sum 0 0 @1"/>
<v:f eqn="prod @2 1 2"/>
<v:f eqn="prod @3 21600 pixelWidth"/>
<v:f eqn="prod @3 21600 pixelHeight"/>
<v:f eqn="sum @0 0 1"/>
<v:f eqn="prod @6 1 2"/>
<v:f eqn="prod @7 21600 pixelWidth"/>
<v:f eqn="sum @8 21600 0"/>
<v:f eqn="prod @7 21600 pixelHeight"/>
<v:f eqn="sum @10 21600 0"/>
</v:formulas>
<v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"/>
<o:lock v:ext="edit" aspectratio="t"/>
</v:shapetype>
<w:binData w:name="wordml://${image.imgName}.png" xml:space="preserve">${image.imgBase64}</w:binData>
<v:shape id="${image.imgId}" o:spid="_x0000_i1025" type="#_x0000_t75" style="width:${image.width}pt;height:${image.height}pt;visibility:visible;mso-wrap-style:square">
<v:imagedata src="wordml://${image.imgName}.png" o:title="${image.imgName}"/>
</v:shape>
</w:pict>
</w:r>
</#list>
</#if>
</#list>
</w:p>
</#list>
</#if>
<w:sectPr wsp:rsidR="0050577F" wsp:rsidRPr="00177379">
<w:pgSz w:w="11906" w:h="16838"/>
<w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851" w:footer="992" w:gutter="0"/>
<w:cols w:space="425"/>
<w:docGrid w:type="lines" w:line-pitch="312"/>
</w:sectPr>
</wx:sect>
</w:body>
解析html代码:
/**
* 解析word
* @Description:
*
* @Date 2018年7月31日
* @author lxs
* @version 2.1.4
*
*/
public class ParseWordUtil {
public static Map<String, Object> getDataMap(Map<String, String> partMap,
Map<String, List<Map<String, String>>> sealMap, List<String> imgVariable)
throws IOException, BadElementException{
// 填充word的数据
Map<String,Object> dataMap = new HashMap<String, Object>();
// 根据标签的不连续,区分部分
for(int i = 0; i < partMap.size(); i++){
String partContent = partMap.get("part"+(i+1));
List<WParagraph> paragraphList = null;
Table table = new Table();
// 获取p标签
if(partContent.startsWith("<p")){
// 传入部分HTML代码,将内容解析为word文档的段落文本
paragraphList = ParseWordUtil.getParagraphList(partContent, sealMap, imgVariable);
}
// 获取table标签
else if(partContent.startsWith("<table")){
Document subDoc = Jsoup.parseBodyFragment(partContent);
Elements e = subDoc.select("tr");
// 获取td列表的列数
List<String> gridColList = ParseWordUtil.getGridColList(e);
int cellSize = 0;
if(gridColList != null && gridColList.size() > 0){
cellSize = gridColList.size();
}
// 获取tr列表
List<Tr> trList = ParseWordUtil.getTrList(e, cellSize, sealMap, imgVariable);
// 设置table属性
table.setGridColList(gridColList);
table.setTrList(trList);
}
if(partContent.startsWith("<p")){
dataMap.put("part"+(i+1), "paragraph");
dataMap.put("paragraphList"+(i+1), paragraphList);
} else if(partContent.startsWith("<table")){
dataMap.put("part"+(i+1), "table");
dataMap.put("table", table);
}
}
return dataMap;
}
/**
* 获取html部分内容,根据段落和表格区分部分
* @Description:
* @param childNodes
* @return
* @author lxs
* @date 2018年7月31日
*/
public static Map<String, String> getPartMap(List<Node> childNodes){
Map<String, String> partMap = new HashMap<String,String>();
List<String> tarFlag = new ArrayList<String>();
int changeFlag = 1;
// 遍历子标签
for (Node node : childNodes) {
if(node.nodeName().trim().equals("#text")){
continue;
}
tarFlag.add(node.nodeName());
if(tarFlag.size() > 1){
//判断,最后一个是否和上一个标签一致,不一致时,改变标志+1
if(!(tarFlag.get(tarFlag.size()-1).equals(tarFlag.get(tarFlag.size()-2)))){
++ changeFlag;
}
}
// 如果是p标签
if(node.toString().startsWith("<p")){
if(partMap.get("part"+changeFlag) == null){
partMap.put("part"+changeFlag, "");
}
partMap.put("part"+changeFlag, partMap.get("part"+changeFlag) + node.toString());
}
// 如果是table标签
else if(node.toString().startsWith("<table")){
if(partMap.get("part"+changeFlag) == null){
partMap.put("part"+changeFlag, "");
}
partMap.put("part"+changeFlag, partMap.get("part"+changeFlag) + node.toString());
}
}
return partMap;
}
/**
* 传入部分HTML标签的代码,返回段落列表文本
* @Description:
* @param subHtml
* @return
* @author lxs
* @throws IOException
* @throws BadElementException
* @date 2018年7月30日
*/
public static List<WParagraph> getParagraphList(String subHtml,
Map<String, List<Map<String, String>>> sealMap, List<String> imgVariable)
throws IOException, BadElementException{
List<WParagraph> paragraphList = new ArrayList<WParagraph>();
// 解析文档,获取所有p标签
Document subDoc = Jsoup.parseBodyFragment(subHtml);
List<Node> nodeP = subDoc.body().childNodes();
// 遍历所有p标签
for (Node node : nodeP) {
if(node.nodeName().equals("#text")){
continue;
}
WParagraph paragraph = new WParagraph();
// 获取p标签的style属性
String pStyle = node.attr("style");
if(pStyle != null && pStyle != ""){
// 找到字体对齐方式,
int alignStart = pStyle.indexOf("text-align:");
int alignEnd = pStyle.indexOf(";", alignStart);
String align = "";
// 设置word段落的对齐方式
if(alignStart > -1 && alignEnd > -1){
align = pStyle.substring(alignStart+"text-align:".length(), alignEnd);
paragraph.setAlign(align.trim());
} else if(alignStart > -1 && alignEnd == -1){
align = pStyle.substring(alignStart+"text-align:".length());
paragraph.setAlign(align.trim());
}
}
// 解析单个p标签
List<Wr> wrList = getWrList(node.toString(), sealMap, imgVariable);
if(wrList == null || wrList.size() == 0){
continue;
}
// word段落设置 w:r列表
paragraph.setWrList(wrList);
// 设置段落列表
paragraphList.add(paragraph);
}
return paragraphList;
}
/**
* 获取列数量
* @Description:
* @param tdNum
* @return
* @author lxs
* @date 2018年7月30日
*/
public static List<String> getGridColList(Elements e) {
// 获取表格中的列数
int tdNum = 0;
for (Element element : e) {
int cellNums = 0;
for (Node tdNode : element.childNodes()) {
if(tdNode.nodeName().equals("#text")){
continue;
}
cellNums ++;
}
if(cellNums >= tdNum){
tdNum = cellNums;
cellNums = 0;
}
}
if(tdNum == 0){
return null;
}
final int tableWidth = 9800;
int colWidth = tableWidth/tdNum;
List<String> gridColList = new ArrayList<String>();
for (int i = 0; i < tdNum; i++) {
gridColList.add("<w:gridCol w:w=\""+colWidth+"\"/>");
}
return gridColList;
}
/**
* 获取tr列表
* @Description:
* @param e
* @param tdWidth
* @return
* @author lxs
* @throws IOException
* @throws BadElementException
* @date 2018年7月30日
*/
public static List<Tr> getTrList(Elements e, int cellSize, Map<String,
List<Map<String, String>>> sealMap, List<String> imgVariable)
throws IOException, BadElementException {
List<Tr> trList = new ArrayList<Tr>();
// 遍历表格tr
for (Element element : e) {
// 如果tr没有子节点则不处理
if(element.children().size() == 0){
continue;
}
Tr tr = new Tr();
// 获取tr的td列表,并设置td列表
List<Td> tdList = getTdList(element, cellSize, sealMap, imgVariable);
tr.setTdList(tdList);
// 设置tr列表
trList.add(tr);
}
return trList;
}
/**
* 根据tr标签,获取一行的td列表
* @Description:
* @param element
* @return
* @author lxs
* @throws IOException
* @throws BadElementException
* @date 2018年7月30日
*/
public static List<Td> getTdList(Element element, int cellSize,
Map<String, List<Map<String, String>>> sealMap, List<String> imgVariable)
throws IOException, BadElementException {
List<Node> childTd = element.childNodes();
List<Td> tdList = new ArrayList<Td>();
int tdWidth = 9800/cellSize;
// 遍历tr中的td
for (Node tdNode : childTd) {
if(tdNode.nodeName().equals("#text")){
continue;
}
Td td = new Td();
// 设置单元格宽度
td.setWidth(tdWidth+"");
// 设置单元格合并属性
if(tdNode.hasAttr("colspan")){
String colSpan = tdNode.attr("colspan");
td.setGridSpan("<w:gridSpan w:val=\""+ colSpan +"\"/>");
}
String tdStyle = tdNode.attr("style");
if(tdStyle != null && tdStyle != ""){
String[] styleArr = tdStyle.split(";");
for (String str : styleArr) {
if(str.contains("border-top")){
int index = str.indexOf(":");
if(str.substring(index+1).trim().equals("none")){
td.setTop("<w:top w:val=\"nil\"/>");
}
}
if(str.contains("border-right")){
int index = str.indexOf(":");
if(str.substring(index+1).trim().equals("none")){
td.setRight("<w:right w:val=\"nil\"/>");
}
}
if(str.contains("border-bottom")){
int index = str.indexOf(":");
if(str.substring(index+1).trim().equals("none")){
td.setBottom("<w:bottom w:val=\"nil\"/>");
}
}
if(str.contains("border-left")){
int index = str.indexOf(":");
if(str.substring(index+1).trim().equals("none")){
td.setLeft("<w:left w:val=\"nil\"/>");
}
}
}
}
// 获取td中的所有标签
List<Node> childP = tdNode.childNodes();
// 如果td没有p标签
Document spanDoc = Jsoup.parseBodyFragment(tdNode.toString());
// 查找td中的所有p标签,不存在p标签,则另外处理
Elements pElement = spanDoc.select("p");
if(pElement.size() == 0){
List<WParagraph> subParagraphList = getSubParagraphList(childP, sealMap, imgVariable);
td.setParagraphList(subParagraphList);
tdList.add(td);
continue;
}
List<WParagraph> paragraphList = getParagraphList(childP.toString(), sealMap, imgVariable);
// td 段落列表
td.setParagraphList(paragraphList);
tdList.add(td);
}
// 行设置多个单元格列表
return tdList;
}
/**
* 处理td中不存在p标签的数据
* @Description:
* @param tdChilds td下的子标签
* @param sealMap 图片列表
* @param imgVariable 图片变量
* @return
* @author lxs
* @throws IOException
* @throws BadElementException
* @date 2018年8月2日
*/
public static List<WParagraph> getSubParagraphList(List<Node> tdChilds,
Map<String, List<Map<String, String>>> sealMap,
List<String> imgVariable) throws BadElementException, IOException {
List<Wr> wrList = getWrList(tdChilds.toString(), sealMap, imgVariable);
List<WParagraph> paragraphList = new ArrayList<WParagraph>();
// 当获取未获取到其他标签内容时,尝试获取td中的文本内容
if(wrList.size() != 0){
WParagraph paragraph = new WParagraph();
paragraph.setWrList(wrList);
paragraphList.add(paragraph);
}else{
for (Node nodeStr : tdChilds) {
if(!nodeStr.toString().equals("<br>")){
Wr wr = new Wr();
wr.setContent(nodeStr.toString());
wrList.add(wr);
}
}
WParagraph paragraph = new WParagraph();
paragraph.setWrList(wrList);
paragraphList.add(paragraph);
}
return paragraphList;
}
/**
* 解析单个p标签
* @Description:
* @param pText p标签内容
* @param sealMap 图片路径列表
* @param imgVariable 图片变量
* @return
* @author lxs
* @throws IOException
* @throws BadElementException
* @date 2018年7月30日
*/
public static List<Wr> getWrList(String pText,
Map<String, List<Map<String, String>>> sealMap,
List<String> imgVariable)
throws IOException, BadElementException{
List<Wr> wrList = new ArrayList<Wr>();
// 处理p标签中,自身文本内容和标签内容的顺序问题
String handleText = handleContextOfP(pText);
Document spanDoc = Jsoup.parseBodyFragment(handleText);
// p标签自身存在内容
Elements p = spanDoc.select("p");
if(p.size() > 0){
String pOwnText = p.get(0).ownText();
// 清除转义空格,制表符,换行,
pOwnText = pOwnText.replaceAll("\\s*|\t|\r|\n", "");
// 清除一般的空格
pOwnText = pOwnText.replaceAll(" ", "");
// 清除HTML中 形式的空格
pOwnText = pOwnText.replaceAll("[\\s\\u00A0]+", "");
if(pOwnText.length() > 0){
Wr wr = new Wr();
wr.setContent(pOwnText);
wrList.add(wr);
}
}
// 查找该标签下的所有span标签
Elements spanElement = spanDoc.select("span");
int spanElementSize = spanElement.size();
// 存放span列表
List<Element> spanList = new ArrayList<Element>();
/** 遍历获取有所span标签列表 **/
for(int i = 0; i < spanElementSize; i++){
int spanChilds = spanElement.get(i).childNodes().size();
Element e = spanElement.get(i);
// 如果标签的内容为空,则跳过,不解析
if(e.text().isEmpty() || e.ownText().length() == 0){
continue;
}
// 如果没有子标签,则添加进span列表中
if(spanChilds <= 1){
spanList.add(e);
}
// 如果存在子标签
else if(spanChilds > 1){
// 先将自身内容取出
String selfText = e.ownText();
// 将所有子节点的内容清空,重新设置内容
e.empty();
e.text(selfText);
spanList.add(e);
}
}
/** 遍历span列表,将span中的内容封装到word文档的w:r中 **/
for (Element span : spanList) {
// 过滤span元素中,< 开头和 > 结尾的所有内容
// span元素的父级标签
String spanParentTag = span.parent().tagName();
// 存在子标签strong
if(span.text().contains("<strong>")){
List<Wr> subWrList = getSubSpan(span);
wrList.addAll(subWrList);
continue;
}
// w:r为图片变量, 即span的内容为图片的变量
if(imgVariable.contains(span.text())){
List<Wr> subWrList = getHasImgSpan(span, sealMap);
wrList.addAll(subWrList);
continue;
}
// 新建一个w:r对象
Wr wr = new Wr();
// 如果父标签为strong,w:r设置加粗
if(spanParentTag.equals("strong")){
wr.setBold("<w:b/>");
}
//String str = handleStr(span.text());
wr.setContent(span.text().replace("`", " "));
wrList.add(wr);
}
// 过滤重复标签
for(int i = 0; i < wrList.size(); i++){
if(i >= 1){
if( wrList.get(i).getContent() != null && wrList.get(i).getContent().equals(wrList.get(i-1).getContent())){
wrList.remove(i);
}
}
}
return wrList;
}
/**
* 处理存在html空格的字符串
* @Description:
* @param text
* @return
* @author lxs
* @date 2018年9月11日
*/
/*private static String handleStr(String text) {
int index = text.indexOf(" ");
Pattern p = Pattern.compile(" ", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(text);
int count = 0;
while (m.find()) {
count++;
}
StringBuffer sb = new StringBuffer();
if(count > 6){
sb.append(text.replace("`", ""));
sb.insert(0, " ");
}else{
sb.append(text.replace("`", ""));
sb.insert(0, " ");
}
return sb.toString();
}*/
/**
* 处理在p标签中存在自身内容和标签内容时,存在的内容顺序混乱问题
* @Description:
* @param pText
* @return
* @author lxs
* @date 2018年9月11日
*/
private static String handleContextOfP(String pText) {
Document spanDoc = Jsoup.parseBodyFragment(pText);
List<Node> childNodes = spanDoc.body().childNodes();
List<Node> handleNodes = new ArrayList<Node>();
for (Node node : childNodes) {
// 若子节点以p标签为开头的,则处理,否则不处理
if(node.toString().trim().startsWith("<p")){
for (int i = 0; i< node.childNodeSize(); i++ ) {
// 如果p的子节点是文本内容,不存在标签,将文本内容用span包含
if("#text".equals(node.childNode(i).nodeName())){
if(node.childNode(i).toString().length() <= 0 ){
continue;
}
Element e = new Element("span");
String nodeText = node.childNode(i).toString().replaceAll("[\\s\\u00A0]+", " ");
String[] strArr = nodeText.split(";");
int count = 0;
for (String str : strArr) {
if(str.equals("nbsp")){
count ++;
}
}
if(count <= 6){
nodeText = nodeText.replaceAll(" ", "`");
}else{
/*for(int j = 0; j < 5; j++){
nodeText = nodeText.replace(" ", "`");
}*/
nodeText = nodeText.replaceAll(" ", "`");
}
e.appendText(nodeText);
node.childNode(i).replaceWith(e);
handleNodes.add(node);
// 去除重复的数据
for (int j = 0; j < handleNodes.size(); j++) {
if(j > 0 && handleNodes.get(j).toString().equals(handleNodes.get(j - 1).toString())){
handleNodes.remove(j);
continue;
}
}
}
}
} else{
handleNodes.add(node);
}
}
if(handleNodes.size() > 0){
return handleNodes.toString();
} else{
return pText;
}
}
/**
* 处理span元素中存在strong标签的元素
* @Description:
* @param subSpanText
* @return
* @author lxs
* @date 2018年8月1日
*/
public static List<Wr> getSubSpan(Element subSpanText){
List<Wr> wrList = new ArrayList<Wr>();
// 将文本内容,和strong标签的内容分开存储
String spanText = subSpanText.text();
List<String> spanTextList = new ArrayList<String>();
int start = -1;
int end = -1;
while(true){
start = spanText.indexOf("<strong>");
end = spanText.indexOf("</strong>");
if(start > -1 && end > -1){
spanTextList.add(spanText.substring(0, start));
spanTextList.add(spanText.substring(start, "</strong>".length()+end));
spanText = spanText.substring("</strong>".length()+end);
} else{
spanTextList.add(spanText);
break;
}
}
// 遍历span,生成wr
for (String spanStr : spanTextList) {
Wr wr = new Wr();
// 如果存在strong标签,则删除标签,保留内容
if(spanStr.contains("<strong>")){
wr.setBold("<w:b/>");
int startIndex = -1;
int endIndex = -1;
while(true){
String newEleText = "";
startIndex = spanStr.indexOf("<");
endIndex = spanStr.indexOf(">");
if(startIndex > -1 && endIndex > -1){
newEleText += spanStr.substring(0,startIndex);
newEleText += spanStr.substring(endIndex+1);
} else {
break;
}
spanStr = newEleText;
}
}
wr.setContent(spanStr);
wrList.add(wr);
}
return wrList;
}
/**
* 处理span中的图片变量
* @Description:
* @param element span标签内容
* @param sealMap 图片列表
* @return
* @throws BadElementException
* @throws IOException
* @author lxs
* @date 2018年7月31日
*/
public static List<Wr> getHasImgSpan(Element element, Map<String, List<Map<String, String>>> sealMap) throws BadElementException, IOException{
List<Wr> wrList = new ArrayList<Wr>();
Wr wr = new Wr();
// 如果span的内容包含印章标签则,设置图片
if(element.text().equals("${agentSeal}")){
if(StringUtil.isEmpty(sealMap.get("agentSeal")) || sealMap.get("agentSeal").size() <= 0){
wr.setContent(" ");
wrList.add(wr);
} else{
wr.setHasImg(1);
List<WImage> imgList = getImgList(sealMap.get("agentSeal"), "agentSeal");
wr.setImgList(imgList);
wrList.add(wr);
}
}
if(element.text().equals("${reviewSeal}")){
if(StringUtil.isEmpty(sealMap.get("reviewSeal")) || sealMap.get("reviewSeal").size() <= 0){
wr.setContent(" ");
wrList.add(wr);
} else{
wr.setHasImg(1);
List<WImage> imgList = getImgList(sealMap.get("reviewSeal"), "reviewSeal");
wr.setImgList(imgList);
wrList.add(wr);
}
}
if(element.text().equals("${approveSeal}")){
if(StringUtil.isEmpty(sealMap.get("approveSeal")) || sealMap.get("approveSeal").size() <= 0){
wr.setContent(" ");
wrList.add(wr);
}else{
wr.setHasImg(1);
List<WImage> imgList = getImgList(sealMap.get("approveSeal"), "approveSeal");
wr.setImgList(imgList);
wrList.add(wr);
}
}
if(element.text().equals("${deptSeal}")){
if(StringUtil.isEmpty(sealMap.get("deptSeal")) || sealMap.get("deptSeal").size() <= 0){
wr.setContent(" ");
wrList.add(wr);
}else{
wr.setHasImg(1);
List<WImage> imgList = getImgList(sealMap.get("deptSeal"), "deptSeal", "120", "80");
wr.setImgList(imgList);
wrList.add(wr);
}
}
if(element.text().equals("${companySeal}")){
if(StringUtil.isEmpty(sealMap.get("company")) || sealMap.get("company").size() <= 0){
wr.setContent(" ");
wrList.add(wr);
}else{
wr.setHasImg(1);
List<WImage> imgList = getImgList(sealMap.get("company"), "company", "120", "80");
wr.setImgList(imgList);
wrList.add(wr);
}
}
return wrList;
}
/**
* 获取图片列表
* @Description:
* @param sealList
* @param imgName
* @return
* @author lxs
* @throws IOException
* @throws BadElementException
* @date 2018年7月31日
*/
public static List<WImage> getImgList(List<Map<String,String>> sealList, String imgName) throws IOException, BadElementException{
int sealLen = sealList.size();
List<WImage> imgList = new ArrayList<WImage>();
for(int i = 0; i< sealLen; i++){
WImage img = new WImage();
//Image image = Image.getInstance(sealList.get(i).get("sealPath"));
// 个人章大小
//img.setHeight(image.getHeight()+"");
//img.setWidth(image.getHeight()+"");
img.setHeight("30");
img.setWidth("65");
img.setImgId(imgName + "_" + i);
img.setImgName(imgName + "_" + i);
img.setImgBase64(ImageHandleUtil.getImgStr(sealList.get(i).get("sealPath")));
imgList.add(img);
}
return imgList;
}
/**
* 获取图片列表
* @Description:
* @param sealList
* @param imgName
* @return
* @author lxs
* @throws IOException
* @throws BadElementException
* @date 2018年7月31日
*/
public static List<WImage> getImgList(List<Map<String,String>> sealList, String imgName, String width, String height) throws IOException, BadElementException{
int sealLen = sealList.size();
List<WImage> imgList = new ArrayList<WImage>();
for(int i = 0; i< sealLen; i++){
WImage img = new WImage();
Image image = Image.getInstance(sealList.get(i).get("sealPath"));
// 部门章
//img.setHeight(image.getHeight()+"");
//img.setWidth(image.getHeight()+"");
float imgHeight= image.getHeight();
float imgWidth = image.getWidth();
float resNumber = (imgHeight - imgWidth) > 0 ? (imgHeight - imgWidth) : -(imgHeight - imgWidth);
if(resNumber > 20){
img.setHeight(height);
img.setWidth(width);
} else{
img.setHeight("120");
img.setWidth("120");
}
img.setImgId(imgName + "_" + i);
img.setImgName(imgName + "_" + i);
img.setImgBase64(ImageHandleUtil.getImgStr(sealList.get(i).get("sealPath")));
imgList.add(img);
}
return imgList;
}
}
其他的一些实体类以及属性,根据自己的需求创建:
使用到的工具类:
1、图片转base64
2、StringUtil的字符串非空判断:
使用到的包:
1、jsoup
2、itextpdf
该功能主要是通过前台使用百度UEditor富文本编辑器,编辑模板并添加变量,然后保存,后台通过解析保存的模板源代码,替换变量。填充模板来实现的,如果有发现问题,或者有建议的,欢迎指出,感激不尽,有问题的也可以留言评论。