本文介绍了一个用Java编写的自动化文档生成工具,通过读取开发清单文本自动生成格式规范的Word文档。该工具的主要特点包括:
- 采用Apache POI库处理Word文档,支持多级标题和段落自动生成
- 实现中文数字转换功能,将编号转换为"一、二、三"等样式
- 预设标准的文档结构(输入/流程/输出/财务/异常/源代码等章节)
- 通过Maven打包成可执行JAR,只需将清单文本与JAR同目录即可运行
使用该工具可将原本繁琐的手动文档编写工作自动化,显著提高开发文档的编写效率。
单位要写开发文档,文档大致结构是上面有一个表格,填入开发内容清单,比如是这样:
|---|------|
| 1 | 新增用户 |
| 2 | 删除用户 |
| 3 | 查询用户 |
下面需要有对应的二级标题和三级标题,我就需要每次都去复制粘贴,改编号,很是麻烦。干脆用java写个脚本来处理吧。
核心思路是,把开发清单复制到txt文本,然后去读取,生成一个新的word,带上这些格式,再拷贝回去即可,省了不少时间。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com</groupId>
<artifactId>doc-server</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 其他插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>main.WordGenerator</mainClass> <!-- 替换为你的主类 -->
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
WordGenerator.java
package main;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import java.io.*;
import java.math.BigInteger;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class WordGenerator {
/**
* 使用BufferedReader按行读取文件
*/
public static List<String> readLinesWithBufferedReader(String filePath) {
List<String> lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
} catch (IOException e) {
System.err.println("读取文件失败: " + e.getMessage());
}
return lines;
}
static class Transaction {
private String num;
private String content;
public Transaction(String num, String content) {
this.num = num;
this.content = content;
}
public String getNum() {
return num;
}
public String getContent() {
return content;
}
@Override
public String toString() {
return "Transaction{" +
"num='" + num + '\'' +
", content='" + content + '\'' +
'}';
}
}
public static String getPath()
{
String path = WordGenerator.class.getProtectionDomain().getCodeSource().getLocation().getPath();
System.out.println("原始路径:" + path);
if(System.getProperty("os.name").contains("dows"))
{
//path -> file:/D:/Haier/hlmcheckup-admin.jar!/BOOT-INF/lib/hlmcheckup-common-1.0.0.jar!/
//如果是windows系统,前面有可能有"file:"要替换掉。
path = path.replace("file:", "");
path = path.substring(1,path.length());
System.out.println("原始路径:" + path);
}
if(path.contains("jar"))
{
//这里根据你jar包名称分割
//D:/Haier/hlmcheckup-admin.jar!/BOOT-INF/lib/hlmcheckup-common-1.0.0.jar!/
// -> D:/Haier/
String[] split = path.split("doc-server-1.0-SNAPSHOT-jar-with-dependencies.jar");
System.out.println("jar包路径:" + split[0]);
return split[0];
}
//"hlmcheckup-common/target/classes/" 为当前类路径 linux与macos需要替换掉
return path.replace("hlmcheckup-common/target/classes/", "");
}
public static String getParentDirectory(String jarFilePath) {
if (jarFilePath == null || jarFilePath.isEmpty()) {
return null;
}
File jarFile = new File(jarFilePath);
File parentDir = jarFile.getParentFile();
if (parentDir != null && parentDir.exists() && parentDir.isDirectory()) {
return parentDir.getAbsolutePath();
}
return null;
}
public static void main(String[] args) throws Exception {
File jarFile = new File( getPath() + "\\1.txt" );
System.out.println(jarFile.getAbsoluteFile());
List<String> lines = readLinesWithBufferedReader(jarFile.getAbsolutePath());
List<Transaction> transactionList = new ArrayList<>();
System.out.println("读取的行数: " + lines.size());
for (int i = 0; i < lines.size(); i++) {
transactionList.add(new Transaction(lines.get(i).split("\\t")[0],lines.get(i).split("\\t")[2]));
}
System.out.println(transactionList);
// 创建新的Word文档
try (XWPFDocument document = new XWPFDocument()) {
ensureHeadingStylesExist(document);
for (Transaction transaction: transactionList) {
int index = 1;
String chinese = NumberToChinese.toChinese(Long.parseLong(transaction.getNum()));
// 创建一级标题
createHeading(document, " 交易" + chinese + ":" + transaction.getContent(), 2);
createHeading(document, " 输入" , 3 );
createParagraph(document, " " + transaction.getContent() + " 输入参数" );
createHeading(document, " 流程" , 3 );
createParagraph(document, " 开始 -> " + transaction.getContent() + " -> 结束");
createHeading(document, " 输出" , 3 );
createParagraph(document, " " + transaction.getContent() + " 输出参数");
createHeading(document, " 财务" , 3 );
createParagraph(document, " 无");
createHeading(document, " 异常" , 3 );
createParagraph(document, " 无");
createHeading(document, " 源代码" , 3 );
createParagraph(document, " 无");
}
// 保存文档
try (FileOutputStream out = new FileOutputStream(new File("out.docx"))) {
document.write(out);
System.out.println("Word文档生成成功!");
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 创建标题段落
* @param document Word文档对象
* @param text 标题文本
* @param level 标题级别(1-9)
*/
private static void createHeading(XWPFDocument document, String text, int level) {
XWPFParagraph paragraph = document.createParagraph();
paragraph.setStyle("Heading" + level); // 设置标题样式
XWPFRun run = paragraph.createRun();
run.setText(text);
run.setFontSize(16 - (level - 1) * 2); // 标题1:16pt, 标题2:14pt, 依此类推
run.setBold(true);
}
/**
* 创建正文段落
* @param document Word文档对象
* @param text 段落文本
*/
private static void createParagraph(XWPFDocument document, String text) {
XWPFParagraph paragraph = document.createParagraph();
XWPFRun run = paragraph.createRun();
run.setText(text);
run.setFontSize(12); // 正文默认12pt
}
// 确保文档包含必要的标题样式
private static void ensureHeadingStylesExist(XWPFDocument document) {
try {
XWPFStyles styles = document.getStyles();
if (styles == null) {
styles = document.createStyles();
}
// 检查Heading 1样式是否存在,不存在则创建
if (!hasStyle(styles, "Heading1")) {
createHeadingStyle(document, "Heading1", "Heading 1", 1, 24);
}
if (!hasStyle(styles, "Heading2")) {
createHeadingStyle(document, "Heading2", "Heading 2", 2, 18);
}
if (!hasStyle(styles, "Heading3")) {
createHeadingStyle(document, "Heading3", "Heading 3", 3, 14);
}
} catch (Exception e) {
System.err.println("创建标题样式失败: " + e.getMessage());
}
}
// 检查样式是否存在
private static boolean hasStyle(XWPFStyles styles, String styleId) {
return styles.getStyle(styleId) != null;
}
// 创建标题样式(兼容所有POI版本)
private static void createHeadingStyle(XWPFDocument document, String styleId, String styleName, int level, int fontSize) {
XWPFStyles styles = document.getStyles();
// 创建新样式
CTStyle ctStyle = CTStyle.Factory.newInstance();
ctStyle.setStyleId(styleId);
// 设置样式名称
CTString name = CTString.Factory.newInstance();
name.setVal(styleName);
ctStyle.setName(name);
// 设置样式类型为段落
ctStyle.setType(STStyleType.PARAGRAPH);
// 设置为标题样式
CTDecimalNumber indentNumber = CTDecimalNumber.Factory.newInstance();
indentNumber.setVal(BigInteger.valueOf(level));
ctStyle.setUiPriority(indentNumber);
// 设置基于Normal样式
ctStyle.setBasedOn(CTString.Factory.newInstance());
ctStyle.getBasedOn().setVal("Normal");
// 设置段落属性
CTPPr ppr = ctStyle.addNewPPr();
ppr.addNewSpacing().setAfter(BigInteger.valueOf(100)); // 段后间距
// 设置字体和大小(兼容旧版POI)
CTRPr rpr = ctStyle.addNewRPr();
CTFonts fonts = rpr.addNewRFonts();
fonts.setAscii("宋体");
fonts.setEastAsia("宋体");
rpr.addNewSz().setVal(BigInteger.valueOf(fontSize * 2)); // 字体大小(Twips单位)
rpr.addNewB().setVal(STOnOff.TRUE); // 加粗
// 添加样式到文档
XWPFStyle newStyle = new XWPFStyle(ctStyle);
styles.addStyle(newStyle);
}
}
NumberToChinese.java 工具类
package main;
import java.math.BigDecimal;
public class NumberToChinese {
// 中文数字字符映射
private static final String[] CN_NUMBERS = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九"};
private static final String[] CN_INTEGER_UNITS = {"", "十", "百", "千", "万", "十", "百", "千", "亿", "十", "百", "千", "兆"};
private static final String[] CN_DECIMAL_UNITS = {"角", "分", "厘", "毫"};
// 中文金额单位映射(用于财务场景)
private static final String[] CN_FINANCIAL_UNITS = {"", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾", "佰", "仟", "兆"};
private static final String CN_ZERO = "零";
private static final String CN_INTEGER = "整";
private static final String CN_NEGATIVE = "负";
/**
* 将数字转换为中文大写(普通模式)
* 例如:1234 → 一千二百三十四
*/
public static String toChinese(long number) {
if (number == 0) {
return CN_NUMBERS[0];
}
StringBuilder result = new StringBuilder();
boolean isNegative = number < 0;
if (isNegative) {
number = -number;
result.append(CN_NEGATIVE);
}
String numStr = String.valueOf(number);
int length = numStr.length();
for (int i = 0; i < length; i++) {
int digit = numStr.charAt(i) - '0';
int unitIndex = length - i - 1;
// 处理零的情况
if (digit == 0) {
// 避免多个连续的零
if (i > 0 && numStr.charAt(i - 1) != '0') {
result.append(CN_NUMBERS[0]);
}
// 处理万亿等单位
if (unitIndex % 4 == 0 && i < length - 1 && numStr.charAt(i + 1) != '0') {
result.append(CN_INTEGER_UNITS[unitIndex]);
}
continue;
}
result.append(CN_NUMBERS[digit]);
result.append(CN_INTEGER_UNITS[unitIndex]);
}
return result.toString();
}
/**
* 将数字转换为中文大写(财务模式)
* 例如:1234.56 → 壹仟贰佰叁拾肆元伍角陆分
*/
public static String toFinancialChinese(double number) {
// 使用BigDecimal避免浮点数精度问题
BigDecimal bd = new BigDecimal(String.valueOf(number));
boolean isNegative = bd.signum() < 0;
if (isNegative) {
bd = bd.negate();
}
// 分离整数和小数部分
BigDecimal integerPart = bd.setScale(0, BigDecimal.ROUND_DOWN);
BigDecimal decimalPart = bd.subtract(integerPart).multiply(new BigDecimal(100)); // 转换为分
StringBuilder result = new StringBuilder();
if (isNegative) {
result.append(CN_NEGATIVE);
}
// 处理整数部分
String integerStr = integerPart.toPlainString();
if (integerStr.equals("0")) {
result.append(CN_NUMBERS[0]);
} else {
int length = integerStr.length();
for (int i = 0; i < length; i++) {
int digit = integerStr.charAt(i) - '0';
int unitIndex = length - i - 1;
// 处理零的情况
if (digit == 0) {
if (i > 0 && integerStr.charAt(i - 1) != '0') {
result.append(CN_ZERO);
}
if (unitIndex % 4 == 0 && i < length - 1 && integerStr.charAt(i + 1) != '0') {
result.append(CN_FINANCIAL_UNITS[unitIndex]);
}
continue;
}
result.append(CN_NUMBERS[digit]);
result.append(CN_FINANCIAL_UNITS[unitIndex]);
}
}
result.append("元");
// 处理小数部分
int decimalInt = decimalPart.intValue();
if (decimalInt == 0) {
result.append(CN_INTEGER);
} else {
int jiao = decimalInt / 10;
int fen = decimalInt % 10;
if (jiao > 0) {
result.append(CN_NUMBERS[jiao]).append(CN_DECIMAL_UNITS[0]);
} else if (decimalInt > 9) {
result.append(CN_ZERO);
}
if (fen > 0) {
result.append(CN_NUMBERS[fen]).append(CN_DECIMAL_UNITS[1]);
}
}
return result.toString();
}
// 测试示例
public static void main(String[] args) {
System.out.println("普通模式:");
System.out.println("1234 → " + toChinese(1234));
System.out.println("10001 → " + toChinese(10001));
System.out.println("10030 → " + toChinese(10030));
System.out.println("100000001 → " + toChinese(100000001));
System.out.println("-123 → " + toChinese(-123));
System.out.println("\n财务模式:");
System.out.println("1234.56 → " + toFinancialChinese(1234.56));
System.out.println("1000.01 → " + toFinancialChinese(1000.01));
System.out.println("0.50 → " + toFinancialChinese(0.50));
System.out.println("1000000 → " + toFinancialChinese(1000000));
System.out.println("-123.45 → " + toFinancialChinese(-123.45));
}
}
用maven打包后,将1.txt和jar放在同一个目录,双击jar,就会生成目标word文件。
1.txt
1 无 新增用户
2 无 删除用户
3 无 查询用户
生成out

目录大纲也是正常的
