|
|
@@ -9,6 +9,7 @@ import java.io.ByteArrayOutputStream;
|
|
|
import java.io.File;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
|
|
|
/**
|
|
|
* 卷宗生成测试类(使用Aspose实现多格式合并)
|
|
|
@@ -17,18 +18,18 @@ import java.util.List;
|
|
|
* 页码规则:
|
|
|
* - 封面:无页码
|
|
|
* - 目录:无页码
|
|
|
- * - 正文:有页码,页眉全局连续(001、002...),页脚按资料类别重置(第 1 页 / 共 N 页)
|
|
|
+ * - 正文:有页码,页眉全局连续(001、002...),页脚按文件重置(第 1 页 / 共 N 页)
|
|
|
* - 封底:无页码
|
|
|
*/
|
|
|
public class ArchiveTest {
|
|
|
|
|
|
/**
|
|
|
- * 文件边界信息(用于追踪每个资料类别对应的节范围)
|
|
|
+ * 文件边界信息(用于追踪每个文件对应的节范围)
|
|
|
*/
|
|
|
private static class FileRange {
|
|
|
- int startSectionIndex; // 资料类别开始的节索引
|
|
|
- int endSectionIndex; // 资料类别结束的节索引
|
|
|
- String fileName; // 资料类别名称
|
|
|
+ int startSectionIndex; // 文件开始的节索引
|
|
|
+ int endSectionIndex; // 文件结束的节索引
|
|
|
+ String fileName; // 文件名称
|
|
|
|
|
|
FileRange(int startSectionIndex, String fileName) {
|
|
|
this.startSectionIndex = startSectionIndex;
|
|
|
@@ -51,22 +52,32 @@ public class ArchiveTest {
|
|
|
catalogFiles.add(basePath + "卷内目录20251225133017203.docx");
|
|
|
|
|
|
// 正文文件(支持Word、Excel、PDF混合)
|
|
|
- // 注意:使用 ##SUMMARY_BOUNDARY## 分隔不同的资料类别
|
|
|
+ // 注意:使用 ##FILE_BOUNDARY## 分隔不同的文件
|
|
|
List<String> contentFiles = new ArrayList<>();
|
|
|
|
|
|
- // 资料类别1:成本监审表系列
|
|
|
+ // 文件1:政府定价成本监审结论报告
|
|
|
+ contentFiles.add(basePath + "政府定价成本监审结论报告20260202114449119.docx");
|
|
|
+ contentFiles.add("##FILE_BOUNDARY##"); // 文件分隔符
|
|
|
+
|
|
|
+ // 文件2:成本监审表(封面)
|
|
|
contentFiles.add(basePath + "小店区-幼儿教育成本监审表(封面)_176664431285620251225143152602.xlsx");
|
|
|
+ contentFiles.add("##FILE_BOUNDARY##"); // 文件分隔符
|
|
|
+
|
|
|
+ // 文件3:成本监审表(固定资产)
|
|
|
contentFiles.add(basePath + "小店区-幼儿教育成本监审表(固定资产) _176664457050920251225143610719.xlsx");
|
|
|
+ contentFiles.add("##FILE_BOUNDARY##"); // 文件分隔符
|
|
|
+
|
|
|
+ // 文件4:报送资料文件
|
|
|
contentFiles.add(basePath + "报送资料文件20251225143055127.docx");
|
|
|
- contentFiles.add("##SUMMARY_BOUNDARY##"); // 类别分隔符
|
|
|
+ contentFiles.add("##FILE_BOUNDARY##"); // 文件分隔符
|
|
|
|
|
|
- // 资料类别2:提取资料登记表
|
|
|
+ // 文件5:提取资料登记表
|
|
|
contentFiles.add(basePath + "成本监审提取资料登记表20251225154211987.docx");
|
|
|
- contentFiles.add("##SUMMARY_BOUNDARY##"); // 类别分隔符
|
|
|
+ contentFiles.add("##FILE_BOUNDARY##"); // 文件分隔符
|
|
|
|
|
|
- // 资料类别3:报送资料
|
|
|
+ // 文件6:报送资料文件
|
|
|
contentFiles.add(basePath + "报送资料文件20251225143050824.docx");
|
|
|
- // 最后一个类别不需要分隔符
|
|
|
+ // 最后一个文件不需要分隔符
|
|
|
|
|
|
// 封底文件
|
|
|
List<String> backCoverFiles = new ArrayList<>();
|
|
|
@@ -127,30 +138,30 @@ public class ArchiveTest {
|
|
|
// 记录正文开始的节索引
|
|
|
int contentStartSectionIndex = mainDoc.getSections().getCount();
|
|
|
|
|
|
- // 3. 合并正文(有页码节)- 追踪每个资料类别的节范围
|
|
|
+ // 3. 合并正文(有页码节)- 追踪每个文件的节范围
|
|
|
List<FileRange> contentFileRanges = new ArrayList<>();
|
|
|
if (contentFiles != null && !contentFiles.isEmpty()) {
|
|
|
System.out.println("\n3. 合并正文(有页码)...");
|
|
|
|
|
|
- int summaryStartSection = -1;
|
|
|
- int summaryIndex = 1;
|
|
|
- String currentSummaryName = "资料类别" + summaryIndex;
|
|
|
+ int fileStartSection = -1;
|
|
|
+ int fileIndex = 1;
|
|
|
+ String currentFileName = "文件" + fileIndex;
|
|
|
|
|
|
for (String filePath : contentFiles) {
|
|
|
// 检查是否是分隔符
|
|
|
- if ("##SUMMARY_BOUNDARY##".equals(filePath)) {
|
|
|
- // 遇到分隔符,保存当前 summary 的范围
|
|
|
- if (summaryStartSection >= 0) {
|
|
|
- int summaryEndSection = mainDoc.getSections().getCount() - 1;
|
|
|
- FileRange fileRange = new FileRange(summaryStartSection, currentSummaryName);
|
|
|
- fileRange.endSectionIndex = summaryEndSection;
|
|
|
+ if ("##FILE_BOUNDARY##".equals(filePath)) {
|
|
|
+ // 遇到分隔符,保存当前文件的范围
|
|
|
+ if (fileStartSection >= 0) {
|
|
|
+ int fileEndSection = mainDoc.getSections().getCount() - 1;
|
|
|
+ FileRange fileRange = new FileRange(fileStartSection, currentFileName);
|
|
|
+ fileRange.endSectionIndex = fileEndSection;
|
|
|
contentFileRanges.add(fileRange);
|
|
|
- System.out.println(" ✓ 资料类别 " + summaryIndex + " 完成,节范围:" + summaryStartSection + "-" + summaryEndSection);
|
|
|
+ System.out.println(" ✓ 文件 " + fileIndex + " 完成,节范围:" + fileStartSection + "-" + fileEndSection);
|
|
|
}
|
|
|
- // 重置,准备下一个 summary
|
|
|
- summaryStartSection = -1;
|
|
|
- summaryIndex++;
|
|
|
- currentSummaryName = "资料类别" + summaryIndex;
|
|
|
+ // 重置,准备下一个文件
|
|
|
+ fileStartSection = -1;
|
|
|
+ fileIndex++;
|
|
|
+ currentFileName = "文件" + fileIndex;
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
@@ -158,22 +169,22 @@ public class ArchiveTest {
|
|
|
if (filePath != null && new File(filePath).exists()) {
|
|
|
System.out.println(" ✓ " + extractFileName(filePath));
|
|
|
|
|
|
- // 如果是当前 summary 的第一个文件,记录起始节索引
|
|
|
- if (summaryStartSection < 0) {
|
|
|
- summaryStartSection = mainDoc.getSections().getCount();
|
|
|
+ // 如果是当前文件的第一个节,记录起始节索引
|
|
|
+ if (fileStartSection < 0) {
|
|
|
+ fileStartSection = mainDoc.getSections().getCount();
|
|
|
}
|
|
|
|
|
|
isFirst = appendDocument(mainDoc, filePath, isFirst, SectionStart.NEW_PAGE);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 处理最后一个 summary(如果没有遇到分隔符)
|
|
|
- if (summaryStartSection >= 0) {
|
|
|
- int summaryEndSection = mainDoc.getSections().getCount() - 1;
|
|
|
- FileRange fileRange = new FileRange(summaryStartSection, currentSummaryName);
|
|
|
- fileRange.endSectionIndex = summaryEndSection;
|
|
|
+ // 处理最后一个文件(如果没有遇到分隔符)
|
|
|
+ if (fileStartSection >= 0) {
|
|
|
+ int fileEndSection = mainDoc.getSections().getCount() - 1;
|
|
|
+ FileRange fileRange = new FileRange(fileStartSection, currentFileName);
|
|
|
+ fileRange.endSectionIndex = fileEndSection;
|
|
|
contentFileRanges.add(fileRange);
|
|
|
- System.out.println(" ✓ 资料类别 " + summaryIndex + " 完成,节范围:" + summaryStartSection + "-" + summaryEndSection);
|
|
|
+ System.out.println(" ✓ 文件 " + fileIndex + " 完成,节范围:" + fileStartSection + "-" + fileEndSection);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -191,10 +202,14 @@ public class ArchiveTest {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 5. 设置页码(页眉全局连续,页脚按资料类别重置)
|
|
|
- System.out.println("\n5. 设置页码(每个资料类别独立编页码)...");
|
|
|
+ // 5. 设置页码(每个文件独立编页码)
|
|
|
+ System.out.println("\n5. 设置页码(每个文件独立编页码)...");
|
|
|
setupPageNumbersPerFile(mainDoc, contentFileRanges);
|
|
|
|
|
|
+ // 6. 在每页右上角添加全局页码(不使用页眉)
|
|
|
+ System.out.println("\n6. 在每页右上角添加全局页码...");
|
|
|
+ addPageNumbersToTopRight(mainDoc, contentFileRanges);
|
|
|
+
|
|
|
// 保存文档
|
|
|
File outputFile = new File(outputPath);
|
|
|
if (outputFile.getParentFile() != null && !outputFile.getParentFile().exists()) {
|
|
|
@@ -537,10 +552,10 @@ public class ArchiveTest {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 设置页码(页眉全局连续,页脚按资料类别重置)
|
|
|
+ * 设置页码(页眉全局连续,页脚按文件重置)
|
|
|
*
|
|
|
* @param doc 文档
|
|
|
- * @param contentFileRanges 正文资料类别的节范围列表
|
|
|
+ * @param contentFileRanges 正文文件的节范围列表
|
|
|
*/
|
|
|
private static void setupPageNumbersPerFile(Document doc, List<FileRange> contentFileRanges) throws Exception {
|
|
|
if (contentFileRanges == null || contentFileRanges.isEmpty()) {
|
|
|
@@ -585,12 +600,12 @@ public class ArchiveTest {
|
|
|
}
|
|
|
System.out.println(" 正文全局起始页码:" + contentGlobalStartPage);
|
|
|
|
|
|
- // 计算每个资料类别的页数和起始页码(全局)
|
|
|
+ // 计算每个文件的页数和起始页码(全局)
|
|
|
for (FileRange fileRange : contentFileRanges) {
|
|
|
- int summaryPageCount = calculateFilePages(doc, collector, fileRange.startSectionIndex, fileRange.endSectionIndex);
|
|
|
- System.out.println(" 资料类别 " + fileRange.fileName + " 页数: " + summaryPageCount);
|
|
|
+ int filePageCount = calculateFilePages(doc, collector, fileRange.startSectionIndex, fileRange.endSectionIndex);
|
|
|
+ System.out.println(" 文件 " + fileRange.fileName + " 页数: " + filePageCount);
|
|
|
|
|
|
- // 获取该资料类别的全局起始页码
|
|
|
+ // 获取该文件的全局起始页码
|
|
|
Section firstSection = sections.get(fileRange.startSectionIndex);
|
|
|
Node firstNode = firstSection.getBody().getFirstChild();
|
|
|
int globalStartPage = 1;
|
|
|
@@ -598,7 +613,7 @@ public class ArchiveTest {
|
|
|
globalStartPage = collector.getStartPageIndex(firstNode);
|
|
|
}
|
|
|
|
|
|
- // 为这个资料类别的所有节设置页码
|
|
|
+ // 为这个文件的所有节设置页码
|
|
|
for (int i = fileRange.startSectionIndex; i <= fileRange.endSectionIndex && i < sections.getCount(); i++) {
|
|
|
Section section = sections.get(i);
|
|
|
HeaderFooterCollection headerFooters = section.getHeadersFooters();
|
|
|
@@ -625,11 +640,15 @@ public class ArchiveTest {
|
|
|
currentGlobalPage = collector.getStartPageIndex(currentSectionFirstNode);
|
|
|
}
|
|
|
|
|
|
+ // 调试日志
|
|
|
+ System.out.println(String.format(" 节 %d: 全局页码=%d, 文件起始页码=%d",
|
|
|
+ i, currentGlobalPage, globalStartPage));
|
|
|
+
|
|
|
// 计算正文内的相对页码(从1开始)
|
|
|
int globalPageNum = currentGlobalPage - contentGlobalStartPage + 1;
|
|
|
|
|
|
- // 关键:只在每个文件的第一个节重置页码为1
|
|
|
- // 同一文件的其他节不重置,继续递增
|
|
|
+ // 关键:在每个文件的第一个节重置页码为1
|
|
|
+ // 这样 PAGE 字段会在每个文件内从1开始递增
|
|
|
if (i == fileRange.startSectionIndex) {
|
|
|
section.getPageSetup().setRestartPageNumbering(true);
|
|
|
section.getPageSetup().setPageStartingNumber(1);
|
|
|
@@ -637,35 +656,8 @@ public class ArchiveTest {
|
|
|
section.getPageSetup().setRestartPageNumbering(false);
|
|
|
}
|
|
|
|
|
|
- // ========== 创建页眉(右上角编号 001、002、003...全局连续)==========
|
|
|
- HeaderFooter header = new HeaderFooter(doc, HeaderFooterType.HEADER_PRIMARY);
|
|
|
- headerFooters.add(header);
|
|
|
-
|
|
|
- // 创建页眉段落(右对齐)
|
|
|
- Paragraph headerPara = new Paragraph(doc);
|
|
|
- headerPara.getParagraphFormat().setAlignment(ParagraphAlignment.RIGHT);
|
|
|
- headerPara.getParagraphFormat().setRightIndent(20);
|
|
|
- headerPara.getParagraphFormat().getBorders().getBottom().setLineStyle(LineStyle.NONE);
|
|
|
-
|
|
|
- header.appendChild(headerPara);
|
|
|
-
|
|
|
- // 使用DocumentBuilder插入带格式的页码字段
|
|
|
- DocumentBuilder builder = new DocumentBuilder(doc);
|
|
|
- builder.moveTo(headerPara);
|
|
|
-
|
|
|
- // 设置字体(加粗)
|
|
|
- builder.getFont().setName("宋体");
|
|
|
- builder.getFont().setSize(12);
|
|
|
- builder.getFont().setBold(true);
|
|
|
-
|
|
|
- // 插入全局页码(使用静态文本)
|
|
|
- // 因为PAGE字段现在是每文件独立的,无法用于全局连续编号
|
|
|
- String pageNumStr = String.format("%03d", globalPageNum);
|
|
|
- builder.write(pageNumStr);
|
|
|
-
|
|
|
- section.getPageSetup().setHeaderDistance(20);
|
|
|
-
|
|
|
// ========== 创建页脚(第 X 页 / 共 Y 页)==========
|
|
|
+ DocumentBuilder builder = new DocumentBuilder(doc);
|
|
|
HeaderFooter footer = new HeaderFooter(doc, HeaderFooterType.FOOTER_PRIMARY);
|
|
|
headerFooters.add(footer);
|
|
|
|
|
|
@@ -687,15 +679,14 @@ public class ArchiveTest {
|
|
|
// 添加"第 "
|
|
|
builder.write("第 ");
|
|
|
|
|
|
- // 页脚使用 PAGE 字段(动态递增)
|
|
|
- // 因为每个文件第一个节重置为1,所以PAGE字段会自动显示正确的相对页码
|
|
|
+ // 页脚使用 PAGE 字段(因为每个文件第一个节重置为1,所以会自动显示文件内页码)
|
|
|
builder.insertField("PAGE", "1");
|
|
|
|
|
|
// 添加" 页 / 共 "
|
|
|
builder.write(" 页 / 共 ");
|
|
|
|
|
|
// 插入总页数(固定值)
|
|
|
- builder.write(String.valueOf(summaryPageCount));
|
|
|
+ builder.write(String.valueOf(filePageCount));
|
|
|
|
|
|
// 添加" 页"
|
|
|
builder.write(" 页");
|
|
|
@@ -707,7 +698,7 @@ public class ArchiveTest {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 计算指定节范围的页数(用于单个资料类别)
|
|
|
+ * 计算指定节范围的页数(用于单个文件)
|
|
|
*/
|
|
|
private static int calculateFilePages(Document doc, LayoutCollector collector, int startSectionIndex, int endSectionIndex) throws Exception {
|
|
|
SectionCollection sections = doc.getSections();
|
|
|
@@ -752,4 +743,114 @@ public class ArchiveTest {
|
|
|
int lastSlash = Math.max(url.lastIndexOf('/'), url.lastIndexOf('\\'));
|
|
|
return lastSlash >= 0 ? url.substring(lastSlash + 1) : url;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 在每页右上角添加全局页码(不使用页眉)
|
|
|
+ *
|
|
|
+ * @param doc 文档
|
|
|
+ * @param contentFileRanges 正文文件的节范围列表
|
|
|
+ */
|
|
|
+ private static void addPageNumbersToTopRight(Document doc, List<FileRange> contentFileRanges) throws Exception {
|
|
|
+ if (contentFileRanges == null || contentFileRanges.isEmpty()) {
|
|
|
+ System.out.println(" [警告] 没有正文文件,跳过右上角页码设置");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新文档布局
|
|
|
+ doc.updatePageLayout();
|
|
|
+ LayoutCollector collector = new LayoutCollector(doc);
|
|
|
+
|
|
|
+ // 计算正文起始页码(全局)
|
|
|
+ int contentGlobalStartPage = Integer.MAX_VALUE;
|
|
|
+ FileRange firstFileRange = contentFileRanges.get(0);
|
|
|
+ Section firstSection = doc.getSections().get(firstFileRange.startSectionIndex);
|
|
|
+ Node firstNode = firstSection.getBody().getFirstChild();
|
|
|
+ if (firstNode != null) {
|
|
|
+ contentGlobalStartPage = collector.getStartPageIndex(firstNode);
|
|
|
+ }
|
|
|
+
|
|
|
+ System.out.println(" 正文全局起始页码:" + contentGlobalStartPage);
|
|
|
+
|
|
|
+ // 遍历每个文件的每个节
|
|
|
+ for (FileRange fileRange : contentFileRanges) {
|
|
|
+ System.out.println(" 处理文件:" + fileRange.fileName);
|
|
|
+
|
|
|
+ for (int sectionIdx = fileRange.startSectionIndex; sectionIdx <= fileRange.endSectionIndex && sectionIdx < doc.getSections().getCount(); sectionIdx++) {
|
|
|
+ Section section = doc.getSections().get(sectionIdx);
|
|
|
+ Body body = section.getBody();
|
|
|
+
|
|
|
+ // 获取该节的所有段落
|
|
|
+ NodeCollection paragraphs = body.getChildNodes(NodeType.PARAGRAPH, true);
|
|
|
+ if (paragraphs.getCount() == 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按页分组段落
|
|
|
+ Map<Integer, List<Paragraph>> pageGroups = new java.util.HashMap<>();
|
|
|
+ for (int i = 0; i < paragraphs.getCount(); i++) {
|
|
|
+ Paragraph para = (Paragraph) paragraphs.get(i);
|
|
|
+ int pageNum = collector.getStartPageIndex(para);
|
|
|
+ pageGroups.computeIfAbsent(pageNum, k -> new java.util.ArrayList<>()).add(para);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 为每一页添加右上角页码
|
|
|
+ for (Map.Entry<Integer, List<Paragraph>> entry : pageGroups.entrySet()) {
|
|
|
+ int globalPage = entry.getKey();
|
|
|
+ List<Paragraph> pageParagraphs = entry.getValue();
|
|
|
+
|
|
|
+ if (pageParagraphs.isEmpty()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算正文内的相对页码
|
|
|
+ int globalPageNum = globalPage - contentGlobalStartPage + 1;
|
|
|
+ String pageNumStr = String.format("%03d", globalPageNum);
|
|
|
+
|
|
|
+ // 在该页的第一个段落中插入浮动文本框
|
|
|
+ Paragraph firstPara = pageParagraphs.get(0);
|
|
|
+
|
|
|
+ // 创建文本框(浮动定位)
|
|
|
+ Shape textBox = new Shape(doc, ShapeType.TEXT_BOX);
|
|
|
+ textBox.setWidth(60);
|
|
|
+ textBox.setHeight(20);
|
|
|
+
|
|
|
+ // 设置为浮动定位(相对于页边距,即版心)
|
|
|
+ textBox.setWrapType(WrapType.NONE);
|
|
|
+ textBox.setRelativeHorizontalPosition(RelativeHorizontalPosition.MARGIN); // 相对于页边距
|
|
|
+ textBox.setRelativeVerticalPosition(RelativeVerticalPosition.MARGIN); // 相对于页边距
|
|
|
+ textBox.setHorizontalAlignment(HorizontalAlignment.RIGHT); // 右对齐
|
|
|
+ textBox.setTop(0); // 距离版心顶部0磅
|
|
|
+ textBox.setLeft(0); // 由于右对齐,这个值会被忽略
|
|
|
+
|
|
|
+ // 设置文本框样式
|
|
|
+ textBox.getStroke().setVisible(false); // 无边框
|
|
|
+ textBox.getFill().setColor(java.awt.Color.WHITE); // 白色背景
|
|
|
+
|
|
|
+ // 在文本框中添加段落和文本
|
|
|
+ Paragraph textBoxPara = new Paragraph(doc);
|
|
|
+ textBoxPara.getParagraphFormat().setAlignment(ParagraphAlignment.RIGHT);
|
|
|
+
|
|
|
+ Run run = new Run(doc, pageNumStr);
|
|
|
+ run.getFont().setName("宋体");
|
|
|
+ run.getFont().setSize(12);
|
|
|
+ run.getFont().setBold(true);
|
|
|
+ run.getFont().setColor(java.awt.Color.BLACK);
|
|
|
+
|
|
|
+ textBoxPara.appendChild(run);
|
|
|
+ textBox.appendChild(textBoxPara);
|
|
|
+
|
|
|
+ // 将文本框插入到段落的开头(作为段落的第一个子节点)
|
|
|
+ if (firstPara.getChildNodes().getCount() > 0) {
|
|
|
+ firstPara.insertBefore(textBox, firstPara.getFirstChild());
|
|
|
+ } else {
|
|
|
+ firstPara.appendChild(textBox);
|
|
|
+ }
|
|
|
+
|
|
|
+ System.out.println(String.format(" 页 %d: 添加页码 %s", globalPage, pageNumStr));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ System.out.println(" 右上角页码添加完成");
|
|
|
+ }
|
|
|
}
|