|
|
|
@ -4,11 +4,20 @@ import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
|
|
|
|
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
|
|
|
|
|
import org.apache.poi.xwpf.usermodel.XWPFRun;
|
|
|
|
|
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
|
|
|
|
|
import org.apache.poi.xwpf.usermodel.XWPFTable;
|
|
|
|
|
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
|
|
|
|
|
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
|
|
|
|
|
import org.commonmark.node.*;
|
|
|
|
|
import org.commonmark.parser.Parser;
|
|
|
|
|
import org.commonmark.ext.gfm.tables.TablesExtension;
|
|
|
|
|
import org.commonmark.ext.heading.anchor.HeadingAnchorExtension;
|
|
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
|
import java.text.SimpleDateFormat;
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.util.Date;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Word文档生成器 - 负责生成分析报告的Word文档
|
|
|
|
@ -105,52 +114,280 @@ public class WordGenerator {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 添加分析内容
|
|
|
|
|
* 添加分析内容 - 使用CommonMark解析Markdown
|
|
|
|
|
*/
|
|
|
|
|
private static void addAnalysisContent(XWPFDocument document, String analysisResult) {
|
|
|
|
|
String[] lines = analysisResult.split("\n");
|
|
|
|
|
XWPFParagraph currentParagraph = null;
|
|
|
|
|
XWPFRun currentRun = null;
|
|
|
|
|
|
|
|
|
|
for (String line : lines) {
|
|
|
|
|
if (line.trim().isEmpty()) {
|
|
|
|
|
// 空行,创建新段落
|
|
|
|
|
document.createParagraph();
|
|
|
|
|
currentParagraph = null;
|
|
|
|
|
currentRun = null;
|
|
|
|
|
} else {
|
|
|
|
|
// 检查是否是标题行
|
|
|
|
|
boolean isTitle = isTitle(line);
|
|
|
|
|
|
|
|
|
|
if (currentParagraph == null) {
|
|
|
|
|
currentParagraph = document.createParagraph();
|
|
|
|
|
currentRun = currentParagraph.createRun();
|
|
|
|
|
currentRun.setFontFamily("宋体");
|
|
|
|
|
currentRun.setFontSize(12);
|
|
|
|
|
// 创建CommonMark解析器,支持表格和标题锚点扩展
|
|
|
|
|
List<org.commonmark.Extension> extensions = Arrays.asList(
|
|
|
|
|
TablesExtension.create(),
|
|
|
|
|
HeadingAnchorExtension.create()
|
|
|
|
|
);
|
|
|
|
|
Parser parser = Parser.builder().extensions(extensions).build();
|
|
|
|
|
|
|
|
|
|
// 解析Markdown文档
|
|
|
|
|
Node markdownDoc = parser.parse(analysisResult);
|
|
|
|
|
|
|
|
|
|
// 遍历并处理所有节点
|
|
|
|
|
processMarkdownNode(document, markdownDoc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理Markdown节点并转换为Word格式
|
|
|
|
|
*/
|
|
|
|
|
private static void processMarkdownNode(XWPFDocument document, Node node) {
|
|
|
|
|
Node child = node.getFirstChild();
|
|
|
|
|
while (child != null) {
|
|
|
|
|
if (child instanceof Heading) {
|
|
|
|
|
processHeading(document, (Heading) child);
|
|
|
|
|
} else if (child instanceof Paragraph) {
|
|
|
|
|
processParagraph(document, (Paragraph) child);
|
|
|
|
|
} else if (child instanceof BulletList) {
|
|
|
|
|
processBulletList(document, (BulletList) child);
|
|
|
|
|
} else if (child instanceof OrderedList) {
|
|
|
|
|
processOrderedList(document, (OrderedList) child);
|
|
|
|
|
} else if (child instanceof org.commonmark.ext.gfm.tables.TableBlock) {
|
|
|
|
|
processTable(document, (org.commonmark.ext.gfm.tables.TableBlock) child);
|
|
|
|
|
} else if (child instanceof BlockQuote) {
|
|
|
|
|
processBlockQuote(document, (BlockQuote) child);
|
|
|
|
|
} else if (child instanceof FencedCodeBlock || child instanceof IndentedCodeBlock) {
|
|
|
|
|
processCodeBlock(document, child);
|
|
|
|
|
}
|
|
|
|
|
child = child.getNext();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理标题
|
|
|
|
|
*/
|
|
|
|
|
private static void processHeading(XWPFDocument document, Heading heading) {
|
|
|
|
|
XWPFParagraph paragraph = document.createParagraph();
|
|
|
|
|
XWPFRun run = paragraph.createRun();
|
|
|
|
|
|
|
|
|
|
String text = getTextContent(heading);
|
|
|
|
|
run.setText(text);
|
|
|
|
|
run.setBold(true);
|
|
|
|
|
run.setFontFamily("宋体");
|
|
|
|
|
|
|
|
|
|
// 根据标题级别设置字体大小
|
|
|
|
|
switch (heading.getLevel()) {
|
|
|
|
|
case 1:
|
|
|
|
|
run.setFontSize(18);
|
|
|
|
|
paragraph.setAlignment(ParagraphAlignment.CENTER);
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
run.setFontSize(16);
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
run.setFontSize(14);
|
|
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
run.setFontSize(13);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
run.setFontSize(12);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理段落
|
|
|
|
|
*/
|
|
|
|
|
private static void processParagraph(XWPFDocument document, Paragraph para) {
|
|
|
|
|
XWPFParagraph paragraph = document.createParagraph();
|
|
|
|
|
processInlineNodes(paragraph, para.getFirstChild());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理无序列表
|
|
|
|
|
*/
|
|
|
|
|
private static void processBulletList(XWPFDocument document, BulletList bulletList) {
|
|
|
|
|
Node listItem = bulletList.getFirstChild();
|
|
|
|
|
while (listItem != null) {
|
|
|
|
|
if (listItem instanceof ListItem) {
|
|
|
|
|
XWPFParagraph paragraph = document.createParagraph();
|
|
|
|
|
XWPFRun run = paragraph.createRun();
|
|
|
|
|
run.setText("• " + getTextContent(listItem));
|
|
|
|
|
run.setFontSize(12);
|
|
|
|
|
run.setFontFamily("宋体");
|
|
|
|
|
}
|
|
|
|
|
listItem = listItem.getNext();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理有序列表
|
|
|
|
|
*/
|
|
|
|
|
private static void processOrderedList(XWPFDocument document, OrderedList orderedList) {
|
|
|
|
|
Node listItem = orderedList.getFirstChild();
|
|
|
|
|
int counter = orderedList.getStartNumber();
|
|
|
|
|
while (listItem != null) {
|
|
|
|
|
if (listItem instanceof ListItem) {
|
|
|
|
|
XWPFParagraph paragraph = document.createParagraph();
|
|
|
|
|
XWPFRun run = paragraph.createRun();
|
|
|
|
|
run.setText(counter + ". " + getTextContent(listItem));
|
|
|
|
|
run.setFontSize(12);
|
|
|
|
|
run.setFontFamily("宋体");
|
|
|
|
|
counter++;
|
|
|
|
|
}
|
|
|
|
|
listItem = listItem.getNext();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理表格
|
|
|
|
|
*/
|
|
|
|
|
private static void processTable(XWPFDocument document, org.commonmark.ext.gfm.tables.TableBlock table) {
|
|
|
|
|
// 计算表格列数
|
|
|
|
|
int cols = 0;
|
|
|
|
|
Node firstRow = table.getFirstChild();
|
|
|
|
|
if (firstRow instanceof org.commonmark.ext.gfm.tables.TableHead) {
|
|
|
|
|
Node headerRow = firstRow.getFirstChild();
|
|
|
|
|
if (headerRow instanceof org.commonmark.ext.gfm.tables.TableRow) {
|
|
|
|
|
Node cell = headerRow.getFirstChild();
|
|
|
|
|
while (cell != null) {
|
|
|
|
|
cols++;
|
|
|
|
|
cell = cell.getNext();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isTitle) {
|
|
|
|
|
// 如果当前段落已有内容,创建新段落
|
|
|
|
|
String currentText = currentRun.getText(0);
|
|
|
|
|
if (currentText != null && !currentText.isEmpty()) {
|
|
|
|
|
currentParagraph = document.createParagraph();
|
|
|
|
|
currentRun = currentParagraph.createRun();
|
|
|
|
|
currentRun.setFontFamily("宋体");
|
|
|
|
|
}
|
|
|
|
|
currentRun.setText(line);
|
|
|
|
|
currentRun.setBold(true);
|
|
|
|
|
currentRun.setFontSize(14);
|
|
|
|
|
currentParagraph = null; // 强制下一行创建新段落
|
|
|
|
|
currentRun = null;
|
|
|
|
|
} else {
|
|
|
|
|
// 普通内容
|
|
|
|
|
String currentText = currentRun.getText(0);
|
|
|
|
|
if (currentText != null && !currentText.isEmpty()) {
|
|
|
|
|
currentRun.addBreak();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cols > 0) {
|
|
|
|
|
XWPFTable wordTable = document.createTable();
|
|
|
|
|
|
|
|
|
|
Node tableChild = table.getFirstChild();
|
|
|
|
|
boolean isFirstRow = true;
|
|
|
|
|
while (tableChild != null) {
|
|
|
|
|
if (tableChild instanceof org.commonmark.ext.gfm.tables.TableHead ||
|
|
|
|
|
tableChild instanceof org.commonmark.ext.gfm.tables.TableBody) {
|
|
|
|
|
|
|
|
|
|
Node rowNode = tableChild.getFirstChild();
|
|
|
|
|
while (rowNode != null) {
|
|
|
|
|
if (rowNode instanceof org.commonmark.ext.gfm.tables.TableRow) {
|
|
|
|
|
XWPFTableRow row;
|
|
|
|
|
if (isFirstRow) {
|
|
|
|
|
row = wordTable.getRow(0);
|
|
|
|
|
isFirstRow = false;
|
|
|
|
|
} else {
|
|
|
|
|
row = wordTable.createRow();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Node cellNode = rowNode.getFirstChild();
|
|
|
|
|
int cellIndex = 0;
|
|
|
|
|
while (cellNode != null && cellIndex < cols) {
|
|
|
|
|
if (cellNode instanceof org.commonmark.ext.gfm.tables.TableCell) {
|
|
|
|
|
XWPFTableCell cell = row.getCell(cellIndex);
|
|
|
|
|
if (cell == null) {
|
|
|
|
|
cell = row.addNewTableCell();
|
|
|
|
|
}
|
|
|
|
|
cell.setText(getTextContent(cellNode));
|
|
|
|
|
cellIndex++;
|
|
|
|
|
}
|
|
|
|
|
cellNode = cellNode.getNext();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
rowNode = rowNode.getNext();
|
|
|
|
|
}
|
|
|
|
|
currentRun.setText(line);
|
|
|
|
|
currentRun.setFontSize(12);
|
|
|
|
|
}
|
|
|
|
|
tableChild = tableChild.getNext();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理引用块
|
|
|
|
|
*/
|
|
|
|
|
private static void processBlockQuote(XWPFDocument document, BlockQuote blockQuote) {
|
|
|
|
|
XWPFParagraph paragraph = document.createParagraph();
|
|
|
|
|
XWPFRun run = paragraph.createRun();
|
|
|
|
|
run.setText("" + getTextContent(blockQuote));
|
|
|
|
|
run.setFontSize(12);
|
|
|
|
|
run.setFontFamily("宋体");
|
|
|
|
|
run.setItalic(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理代码块
|
|
|
|
|
*/
|
|
|
|
|
private static void processCodeBlock(XWPFDocument document, Node codeBlock) {
|
|
|
|
|
XWPFParagraph paragraph = document.createParagraph();
|
|
|
|
|
XWPFRun run = paragraph.createRun();
|
|
|
|
|
|
|
|
|
|
String code;
|
|
|
|
|
if (codeBlock instanceof FencedCodeBlock) {
|
|
|
|
|
code = ((FencedCodeBlock) codeBlock).getLiteral();
|
|
|
|
|
} else {
|
|
|
|
|
code = ((IndentedCodeBlock) codeBlock).getLiteral();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
run.setText(code);
|
|
|
|
|
run.setFontSize(10);
|
|
|
|
|
run.setFontFamily("Courier New");
|
|
|
|
|
// 设置背景色(如果支持)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理内联节点(粗体、斜体、链接等)
|
|
|
|
|
*/
|
|
|
|
|
private static void processInlineNodes(XWPFParagraph paragraph, Node node) {
|
|
|
|
|
while (node != null) {
|
|
|
|
|
if (node instanceof Text) {
|
|
|
|
|
XWPFRun run = paragraph.createRun();
|
|
|
|
|
run.setText(((Text) node).getLiteral());
|
|
|
|
|
run.setFontSize(12);
|
|
|
|
|
run.setFontFamily("宋体");
|
|
|
|
|
} else if (node instanceof StrongEmphasis) {
|
|
|
|
|
XWPFRun run = paragraph.createRun();
|
|
|
|
|
run.setText(getTextContent(node));
|
|
|
|
|
run.setBold(true);
|
|
|
|
|
run.setFontSize(12);
|
|
|
|
|
run.setFontFamily("宋体");
|
|
|
|
|
} else if (node instanceof Emphasis) {
|
|
|
|
|
XWPFRun run = paragraph.createRun();
|
|
|
|
|
run.setText(getTextContent(node));
|
|
|
|
|
run.setItalic(true);
|
|
|
|
|
run.setFontSize(12);
|
|
|
|
|
run.setFontFamily("宋体");
|
|
|
|
|
} else if (node instanceof Code) {
|
|
|
|
|
XWPFRun run = paragraph.createRun();
|
|
|
|
|
run.setText(((Code) node).getLiteral());
|
|
|
|
|
run.setFontSize(10);
|
|
|
|
|
run.setFontFamily("Courier New");
|
|
|
|
|
} else if (node instanceof Link) {
|
|
|
|
|
XWPFRun run = paragraph.createRun();
|
|
|
|
|
run.setText(getTextContent(node) + " (" + ((Link) node).getDestination() + ")");
|
|
|
|
|
run.setFontSize(12);
|
|
|
|
|
run.setFontFamily("宋体");
|
|
|
|
|
run.setColor("0000FF");
|
|
|
|
|
} else {
|
|
|
|
|
// 递归处理子节点
|
|
|
|
|
processInlineNodes(paragraph, node.getFirstChild());
|
|
|
|
|
}
|
|
|
|
|
node = node.getNext();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取节点的文本内容
|
|
|
|
|
*/
|
|
|
|
|
private static String getTextContent(Node node) {
|
|
|
|
|
StringBuilder text = new StringBuilder();
|
|
|
|
|
extractText(node, text);
|
|
|
|
|
return text.toString().trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 递归提取文本内容
|
|
|
|
|
*/
|
|
|
|
|
private static void extractText(Node node, StringBuilder text) {
|
|
|
|
|
if (node instanceof Text) {
|
|
|
|
|
text.append(((Text) node).getLiteral());
|
|
|
|
|
} else if (node instanceof Code) {
|
|
|
|
|
text.append(((Code) node).getLiteral());
|
|
|
|
|
} else {
|
|
|
|
|
Node child = node.getFirstChild();
|
|
|
|
|
while (child != null) {
|
|
|
|
|
extractText(child, text);
|
|
|
|
|
child = child.getNext();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|