package com.trafficaudit.holiday.service; import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class HolidayTrendWordServiceTest { @Test void rebuildsTemplateWithSectionsChartsAndAppendix() throws Exception { Path template = resolveTemplate(); byte[] templateBytes = Files.readAllBytes(template); HolidayTrendWordService service = new HolidayTrendWordService(); List blocks = new ArrayList<>(); blocks.add(new HolidayTrendWordService.SectionBlock("一、总体情况")); blocks.add(new HolidayTrendWordService.ChartBlock("客流总量", png(1), new HolidayTrendWordService.ImageSize(5000000L, 3000000L))); blocks.add(new HolidayTrendWordService.ChartBlock("客流占比", png(2), true, new HolidayTrendWordService.ImageSize(4000000L, 2500000L))); blocks.add(new HolidayTrendWordService.PageBreakBlock()); blocks.add(new HolidayTrendWordService.SectionBlock("附录一 每日客流构成变化")); blocks.add(new HolidayTrendWordService.ChartBlock("每日构成", png(3), true)); byte[] result = service.generate(templateBytes, 2026, blocks); List entries = entries(result); assertTrue(entries.contains("word/media/image1.png")); assertTrue(entries.contains("word/media/image2.png")); assertTrue(entries.contains("word/media/image3.png")); assertEquals(3, entries.stream().filter(name -> name.endsWith(".png")).count()); String documentXml = readEntry(result, "word/document.xml"); assertTrue(documentXml.contains("一、总体情况")); assertTrue(documentXml.contains("附录一 每日客流构成变化")); assertTrue(documentXml.contains("客流总量")); assertTrue(documentXml.contains("每日构成")); assertTrue(!paragraphHasNumbering(documentXml, "一、总体情况")); assertTrue(!paragraphHasNumbering(documentXml, "附录一 每日客流构成变化")); assertTrue(documentXml.contains("cx=\"5000000\" cy=\"3000000\"")); assertTrue(documentXml.contains("cx=\"4000000\" cy=\"2500000\"")); assertEquals(2, pageBreakCount(documentXml)); String rels = readEntry(result, "word/_rels/document.xml.rels"); assertTrue(rels.contains("rId100")); assertTrue(rels.contains("rId102")); } private Path resolveTemplate() { Path direct = Paths.get("docs/专项统计/重要节假日/2026年十一假期全省综合运输客流量走势图.docx"); if (Files.exists(direct)) return direct; return Paths.get("..", "docs", "专项统计", "重要节假日", "2026年十一假期全省综合运输客流量走势图.docx"); } private boolean paragraphHasNumbering(String documentXml, String text) throws Exception { javax.xml.parsers.DocumentBuilderFactory factory = javax.xml.parsers.DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); org.w3c.dom.Document document = factory.newDocumentBuilder().parse( new java.io.ByteArrayInputStream(documentXml.getBytes("UTF-8"))); org.w3c.dom.NodeList paragraphs = document.getElementsByTagNameNS( "http://schemas.openxmlformats.org/wordprocessingml/2006/main", "p"); for (int i = 0; i < paragraphs.getLength(); i++) { org.w3c.dom.Element paragraph = (org.w3c.dom.Element) paragraphs.item(i); StringBuilder builder = new StringBuilder(); org.w3c.dom.NodeList texts = paragraph.getElementsByTagNameNS( "http://schemas.openxmlformats.org/wordprocessingml/2006/main", "t"); for (int j = 0; j < texts.getLength(); j++) { builder.append(texts.item(j).getTextContent()); } if (text.equals(builder.toString())) { return paragraph.getElementsByTagNameNS( "http://schemas.openxmlformats.org/wordprocessingml/2006/main", "numPr") .getLength() > 0; } } throw new AssertionError("Paragraph not found: " + text); } private int pageBreakCount(String documentXml) throws Exception { javax.xml.parsers.DocumentBuilderFactory factory = javax.xml.parsers.DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); org.w3c.dom.Document document = factory.newDocumentBuilder().parse( new java.io.ByteArrayInputStream(documentXml.getBytes("UTF-8"))); org.w3c.dom.NodeList breaks = document.getElementsByTagNameNS( "http://schemas.openxmlformats.org/wordprocessingml/2006/main", "br"); int count = 0; for (int i = 0; i < breaks.getLength(); i++) { org.w3c.dom.Element br = (org.w3c.dom.Element) breaks.item(i); if ("page".equals(br.getAttributeNS( "http://schemas.openxmlformats.org/wordprocessingml/2006/main", "type"))) { count++; } } return count; } private byte[] png(int seed) { byte[] bytes = new byte[256]; Arrays.fill(bytes, (byte) seed); bytes[0] = (byte) 0x89; bytes[1] = 0x50; bytes[2] = 0x4e; bytes[3] = 0x47; return bytes; } private List entries(byte[] zipBytes) throws Exception { List result = new ArrayList<>(); try (ZipInputStream input = new ZipInputStream(new ByteArrayInputStream(zipBytes))) { ZipEntry entry; while ((entry = input.getNextEntry()) != null) result.add(entry.getName()); } return result; } private String readEntry(byte[] zipBytes, String name) throws Exception { try (ZipInputStream input = new ZipInputStream(new ByteArrayInputStream(zipBytes))) { ZipEntry entry; while ((entry = input.getNextEntry()) != null) { if (name.equals(entry.getName())) { java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int length; while ((length = input.read(buffer)) > 0) out.write(buffer, 0, length); return out.toString("UTF-8"); } } } return ""; } }