From 43043aea79bfb071d678382819cdeff2edc2c069 Mon Sep 17 00:00:00 2001 From: huababa1 <2037205722@qq.com> Date: Tue, 9 Sep 2025 23:02:57 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E4=BF=AE=E6=94=B9=E3=80=91=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E6=96=87=E5=AD=97=E8=80=83=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/mysql/IMysqlLocalServiceImpl.java | 2 +- .../service/wpspptx/pptx4j/SlideMaster.java | 2 +- .../wpsword/JudgementWpsWordServiceImpl.java | 6 +- .../service/wpsword/docx4j/DocxMaster.java | 41 ++- .../wpsword/docx4j/drawing/Drawing.java | 113 ++++++++- .../wpsword/docx4j/drawing/DrawingInline.java | 203 ++++++++++++++- .../service/wpsword/docx4j/file/isFile.java | 50 ++++ .../wpsword/docx4j/section/SectionPage.java | 236 +++++++++++------- .../wpsword/docx4j/vo/WpsDocxInfoVo.java | 3 + 9 files changed, 531 insertions(+), 125 deletions(-) create mode 100644 src/main/java/com/example/exam/exam/service/wpsword/docx4j/file/isFile.java diff --git a/src/main/java/com/example/exam/exam/service/mysql/IMysqlLocalServiceImpl.java b/src/main/java/com/example/exam/exam/service/mysql/IMysqlLocalServiceImpl.java index 33e67ee..9a04d38 100644 --- a/src/main/java/com/example/exam/exam/service/mysql/IMysqlLocalServiceImpl.java +++ b/src/main/java/com/example/exam/exam/service/mysql/IMysqlLocalServiceImpl.java @@ -81,7 +81,7 @@ public class IMysqlLocalServiceImpl implements IMysqlLocalService { // 文件路径 // String file = "D:\\Desktop\\202504211120_mysql\\71\\MYS_010_122\\结果素材\\结果素材\\Teacher"; -// String file = new File(filepath, "结果").getAbsolutePath(); +// String file = new isFile(filepath, "结果").getAbsolutePath(); // String filePath = "D:\\Desktop\\202504211120_mysql\\71\\MYS_010_122\\结果素材\\结果素材\\Teacher\\answer.txt"; // 答案文件路径 String filePath = new File(stuFilePath).getAbsolutePath(); // 结果/answer.txt diff --git a/src/main/java/com/example/exam/exam/service/wpspptx/pptx4j/SlideMaster.java b/src/main/java/com/example/exam/exam/service/wpspptx/pptx4j/SlideMaster.java index 368553f..53acc2b 100644 --- a/src/main/java/com/example/exam/exam/service/wpspptx/pptx4j/SlideMaster.java +++ b/src/main/java/com/example/exam/exam/service/wpspptx/pptx4j/SlideMaster.java @@ -21,7 +21,7 @@ import java.util.List; public class SlideMaster { public static List slideMaster(List wpsSlideInfoVos, MultipartFile file) throws IOException, Docx4JException { -// File files = new File("E:\\Project\\Exam\\Software\\Temp\\1.pptx"); +// isFile files = new isFile("E:\\Project\\Exam\\Software\\Temp\\1.pptx"); List judgementSlidesVOS = new ArrayList<>(); // 1、获取想要判断的文件地址(文件流) try (InputStream inputStream = file.getInputStream()) { diff --git a/src/main/java/com/example/exam/exam/service/wpsword/JudgementWpsWordServiceImpl.java b/src/main/java/com/example/exam/exam/service/wpsword/JudgementWpsWordServiceImpl.java index 5f96d26..96f04a4 100644 --- a/src/main/java/com/example/exam/exam/service/wpsword/JudgementWpsWordServiceImpl.java +++ b/src/main/java/com/example/exam/exam/service/wpsword/JudgementWpsWordServiceImpl.java @@ -44,13 +44,17 @@ public class JudgementWpsWordServiceImpl implements JudgementWpsWordService { wpsDocxInfoVo.setFunction(docxInfo[2]); wpsDocxInfoVo.setExamName(docxInfo[3]); wpsDocxInfoVo.setExamCode(docxInfo[4]); + // 如果有第 6 个字段,加入 cell + if (docxInfo.length > 5 && docxInfo[5] != null && !docxInfo[5].isEmpty()) { + wpsDocxInfoVo.setFileName(docxInfo[5]); + } wpsDocxInfos.add(wpsDocxInfoVo); } } // 将文件转成 MultipartFile MultipartFile multipartFile = new CustomMultipartFile(new File(path)); // 获取文档检测结果 - List judgementWordsVOS = DocxMaster.docxMaster(wpsDocxInfos, multipartFile); + List judgementWordsVOS = DocxMaster.docxMaster(wpsDocxInfos, multipartFile,path); // 判分 for (ExamQuestionAnswer examQuestionAnswer : answerList) { boolean flag = false; // 每个答案单独判断 diff --git a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/DocxMaster.java b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/DocxMaster.java index 47dfff4..874f4af 100644 --- a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/DocxMaster.java +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/DocxMaster.java @@ -2,6 +2,7 @@ package com.example.exam.exam.service.wpsword.docx4j; import com.example.exam.exam.service.wpsword.docx4j.drawing.DrawingInline; import com.example.exam.exam.service.wpsword.docx4j.endNote.EndNoteing; +import com.example.exam.exam.service.wpsword.docx4j.file.isFile; import com.example.exam.exam.service.wpsword.docx4j.insert.InsertIng; import com.example.exam.exam.service.wpsword.docx4j.paragraph.Convert; import com.example.exam.exam.service.wpsword.docx4j.paragraph.Paragraphs; @@ -14,27 +15,17 @@ import com.example.exam.exam.service.wpsword.docx4j.vo.JudgementWordsVO; import com.example.exam.exam.service.wpsword.docx4j.vo.WpsDocxInfoVo; import jakarta.xml.bind.JAXBElement; import jakarta.xml.bind.JAXBException; -import org.apache.commons.io.IOUtils; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; -import org.apache.poi.openxml4j.opc.OPCPackage; -import org.apache.poi.openxml4j.opc.ZipPackage; import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlObject; import org.docx4j.XmlUtils; -import org.docx4j.com.microsoft.schemas.office.word.x2010.wordprocessingShape.CTWordprocessingShape; -import org.docx4j.dml.CTBlipFillProperties; -import org.docx4j.dml.CTStretchInfoProperties; -import org.docx4j.mce.AlternateContent; import org.docx4j.model.structure.HeaderFooterPolicy; import org.docx4j.model.structure.SectionWrapper; -import org.docx4j.dml.wordprocessingDrawing.Anchor; -import org.docx4j.model.structure.jaxb.Sections; import org.docx4j.openpackaging.exceptions.Docx4JException; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import org.docx4j.openpackaging.parts.WordprocessingML.*; -import org.docx4j.w14.CTWordContentPart; import org.docx4j.wml.*; import org.springframework.web.multipart.MultipartFile; @@ -45,7 +36,6 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; @@ -71,7 +61,7 @@ public class DocxMaster { * @throws InvocationTargetException * @throws IllegalAccessException */ - public static List docxMaster(List wpsDocxInfoVos, MultipartFile file) throws Exception { + public static List docxMaster(List wpsDocxInfoVos, MultipartFile file,String path) throws Exception { // 一共分为 段落, 页面,图形 String firstDuanLuoId = Convert.getStringRandom(); @@ -619,7 +609,34 @@ public class DocxMaster { } + if (firstName.contains("文件")) { + try { + Class clazz = isFile.class; + Method method = clazz.getMethod(function, String.class,String.class); + File filePath = new File(path); + + String parentDir = filePath.getParent(); // 获取父目录 + + + String value = (String) method.invoke(null,parentDir ,wpsDocxInfoVo.getFileName()); + + if (value != null) { + judgementWordsVOS = setJudgementWord( + judgementWordsVOS, + docxFunction + "@" + wpsDocxInfoVo.getFileName() + "@" + value, + firstName + examName + value + ); + } + } catch (NoSuchMethodException e) { + System.err.println("方法不存在: " + function); + } catch (Exception e) { + e.printStackTrace(); + } + + break; // 找到指定表格就退出 + + } diff --git a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/drawing/Drawing.java b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/drawing/Drawing.java index a75b6e7..d9c5042 100644 --- a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/drawing/Drawing.java +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/drawing/Drawing.java @@ -89,17 +89,105 @@ public class Drawing { //布局 环绕方式 public static String getLayoutHuanRao(List judgementWordsVOS, Anchor anchor, int betoLong, WordprocessingMLPackage wordMLPackage) { if (anchor == null) return "未知"; - // 是否衬于文字下方 - boolean behindDoc = anchor.isBehindDoc(); - if (behindDoc) return "衬于文字下方型"; - // 浮于文字上方型 - if (anchor.getWrapNone() != null) return "浮于文字上方型"; - if (anchor.getWrapSquare() != null) return "四周型"; - if (anchor.getWrapTight() != null) return "紧密型"; - if (anchor.getWrapThrough() != null) return "穿越型"; - if (anchor.getWrapTopAndBottom() != null) return "上下型"; - return "无"; + String wrapType = null; + + // 1. 优先判断环绕方式 + if (anchor.getWrapTight() != null) { + wrapType = "紧密型"; + } else if (anchor.getWrapSquare() != null) { + wrapType = "四周型"; + } else if (anchor.getWrapThrough() != null) { + wrapType = "穿越型"; + } else if (anchor.getWrapTopAndBottom() != null) { + wrapType = "上下型"; + } else if (anchor.getWrapNone() != null) { + wrapType = "浮于文字上方型"; + } + + // 2. 如果没有任何环绕方式,则根据 behindDoc 判断 + if (wrapType == null) { + return anchor.isBehindDoc() ? "衬于文字下方型" : "无"; + } + + // 3. 返回最终结果 + return wrapType; + } + + // 位置 → 水平 → 对齐方式 + public static String getLayoutHorizontalAlign(List judgementWordsVOS, Anchor anchor, int betoLong, WordprocessingMLPackage wordMLPackage) { + if (anchor == null || anchor.getPositionH() == null) return "未知"; + if (anchor.getPositionH().getAlign() != null) { + switch (anchor.getPositionH().getAlign()) { + case LEFT: + return "左对齐"; + case CENTER: + return "居中"; + case RIGHT: + return "右对齐"; + default: + return "未知"; + } + } + return "未知"; + } + + // 位置 → 水平 → 相对于 + public static String getLayoutHorizontalRelativeTo(List judgementWordsVOS, Anchor anchor, int betoLong, WordprocessingMLPackage wordMLPackage) { + if (anchor == null || anchor.getPositionH() == null) return "未知"; + if (anchor.getPositionH().getRelativeFrom() != null) { + switch (anchor.getPositionH().getRelativeFrom()) { + case COLUMN: + return "列"; + case MARGIN: + return "页边距"; + case PAGE: + return "页面"; + case CHARACTER: + return "字符"; + default: + return "未知"; + } + } + return "未知"; + } + + // 位置 → 垂直 → 对齐方式 + public static String getLayoutVerticalAlign(List judgementWordsVOS, Anchor anchor, int betoLong, WordprocessingMLPackage wordMLPackage) { + if (anchor == null || anchor.getPositionV() == null) return "未知"; + if (anchor.getPositionV().getAlign() != null) { + switch (anchor.getPositionV().getAlign()) { + case TOP: + return "顶端对齐"; + case CENTER: + return "居中"; + case BOTTOM: + return "底端对齐"; + default: + return "未知"; + } + } + return "未知"; + } + + // 位置 → 垂直 → 相对于 + public static String getLayoutVerticalRelativeTo(List judgementWordsVOS, Anchor anchor, int betoLong, WordprocessingMLPackage wordMLPackage) { + if (anchor == null || anchor.getPositionV() == null) return "未知"; + if (anchor.getPositionV().getRelativeFrom() != null) { + switch (anchor.getPositionV().getRelativeFrom()) { + case PAGE: + return "页面"; + case MARGIN: + return "页边距"; + case PARAGRAPH: + return "段落"; + case LINE: + return "行"; + default: + return "未知"; + } + } + return "未知"; } // 形状和样式 @@ -1395,6 +1483,11 @@ public class Drawing { return "无"; } + + + + + // 获取倒影对象 private static CTReflectionEffect getReflectionEffect(Anchor anchor) { if (anchor == null diff --git a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/drawing/DrawingInline.java b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/drawing/DrawingInline.java index 618cdbb..ea50228 100644 --- a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/drawing/DrawingInline.java +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/drawing/DrawingInline.java @@ -7,6 +7,7 @@ import jakarta.xml.bind.JAXBContext; import jakarta.xml.bind.JAXBElement; import jakarta.xml.bind.JAXBException; import jakarta.xml.bind.Marshaller; +import org.docx4j.XmlUtils; import org.docx4j.dml.*; import org.docx4j.dml.chartDrawing.CTPicture; import org.docx4j.dml.picture.Pic; @@ -14,6 +15,8 @@ import org.docx4j.dml.wordprocessingDrawing.CTWrapSquare; import org.docx4j.dml.wordprocessingDrawing.Inline; import org.docx4j.dml.wordprocessingDrawing.Inline; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; import java.io.StringWriter; import java.lang.reflect.Method; @@ -78,12 +81,206 @@ public class DrawingInline { } //布局 环绕方式 - public static String getLayoutHuanRao(List judgementWordsVOS, Inline anchor, int betoLong, WordprocessingMLPackage wordMLPackage) { - // Inline - return null; + public static String getLayoutHuanRao(List judgementWordsVOS, Inline inline, int betoLong, WordprocessingMLPackage wordMLPackage) { + if (inline == null) return "未知"; + + try { + // 转为 W3C DOM + Element el = XmlUtils.marshaltoW3CDomDocument(inline).getDocumentElement(); + + // 判断环绕方式 + NodeList wrapTight = el.getElementsByTagNameNS("http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", "wrapTight"); + if (wrapTight.getLength() > 0) return "紧密型"; + + NodeList wrapSquare = el.getElementsByTagNameNS("http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", "wrapSquare"); + if (wrapSquare.getLength() > 0) return "四周型"; + + NodeList wrapThrough = el.getElementsByTagNameNS("http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", "wrapThrough"); + if (wrapThrough.getLength() > 0) return "穿越型"; + + NodeList wrapTopAndBottom = el.getElementsByTagNameNS("http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", "wrapTopAndBottom"); + if (wrapTopAndBottom.getLength() > 0) return "上下型"; + + NodeList wrapNone = el.getElementsByTagNameNS("http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", "wrapNone"); + if (wrapNone.getLength() > 0) return "浮于文字上方型"; + + // 判断 behindDoc(默认为 false) + NodeList behindDocNodes = el.getElementsByTagNameNS("http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", "behindDoc"); + if (behindDocNodes.getLength() > 0) { + String val = behindDocNodes.item(0).getTextContent(); + if ("1".equals(val) || "true".equalsIgnoreCase(val)) { + return "衬于文字下方型"; + } + } + + return "无"; + + } catch (Exception e) { + e.printStackTrace(); + return "未知"; + } } + // 位置 → 水平 → 对齐方式 + public static String getLayoutHorizontalAlign(List judgementWordsVOS, Inline anchor, int betoLong, WordprocessingMLPackage wordMLPackage) { + if (anchor == null) return "未知"; + try { + // 转为 W3C DOM + Element el = XmlUtils.marshaltoW3CDomDocument(anchor).getDocumentElement(); + + // 获取 节点 + NodeList posHList = el.getElementsByTagNameNS( + "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "positionH" + ); + + if (posHList.getLength() > 0) { + Element posH = (Element) posHList.item(0); + NodeList alignList = posH.getElementsByTagNameNS( + "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "align" + ); + + if (alignList.getLength() > 0) { + String align = alignList.item(0).getTextContent(); + switch (align.toLowerCase()) { + case "left": + return "左对齐"; + case "center": + return "居中"; + case "right": + return "右对齐"; + default: + return "未知"; + } + } + } + + return "未知"; + + } catch (Exception e) { + e.printStackTrace(); + return "未知"; + } + } + + // 位置 → 水平 → 相对于 + public static String getLayoutHorizontalRelativeTo(List judgementWordsVOS, Inline anchor, int betoLong, WordprocessingMLPackage wordMLPackage) { + if (anchor == null) return "未知"; + + try { + // 转为 W3C DOM + Element el = XmlUtils.marshaltoW3CDomDocument(anchor).getDocumentElement(); + + // 获取 节点 + NodeList posHList = el.getElementsByTagNameNS( + "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "positionH" + ); + + if (posHList.getLength() > 0) { + Element posH = (Element) posHList.item(0); + NodeList relativeList = posH.getElementsByTagNameNS( + "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "relativeFrom" + ); + + if (relativeList.getLength() > 0) { + String relative = relativeList.item(0).getTextContent(); + switch (relative.toLowerCase()) { + case "column": + return "列"; + case "margin": + return "页边距"; + case "page": + return "页面"; + case "character": + return "字符"; + default: + return "未知"; + } + } + } + + return "未知"; + + } catch (Exception e) { + e.printStackTrace(); + return "未知"; + } + } + + // 位置 → 垂直 → 对齐方式 + public static String getLayoutVerticalAlign(List judgementWordsVOS, Inline anchor, int betoLong, WordprocessingMLPackage wordMLPackage) { + if (anchor == null) return "未知"; + + try { + Element el = XmlUtils.marshaltoW3CDomDocument(anchor).getDocumentElement(); + NodeList posVList = el.getElementsByTagNameNS( + "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "positionV" + ); + + if (posVList.getLength() > 0) { + Element posV = (Element) posVList.item(0); + NodeList alignList = posV.getElementsByTagNameNS( + "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "align" + ); + + if (alignList.getLength() > 0) { + String align = alignList.item(0).getTextContent(); + switch (align.toLowerCase()) { + case "top": return "顶端对齐"; + case "center": return "居中"; + case "bottom": return "底端对齐"; + default: return "未知"; + } + } + } + return "未知"; + } catch (Exception e) { + e.printStackTrace(); + return "未知"; + } + } + + // 位置 → 垂直 → 相对于 + public static String getLayoutVerticalRelativeTo(List judgementWordsVOS, Inline anchor, int betoLong, WordprocessingMLPackage wordMLPackage) { + if (anchor == null) return "未知"; + + try { + Element el = XmlUtils.marshaltoW3CDomDocument(anchor).getDocumentElement(); + NodeList posVList = el.getElementsByTagNameNS( + "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "positionV" + ); + + if (posVList.getLength() > 0) { + Element posV = (Element) posVList.item(0); + NodeList relativeList = posV.getElementsByTagNameNS( + "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "relativeFrom" + ); + + if (relativeList.getLength() > 0) { + String relative = relativeList.item(0).getTextContent(); + switch (relative.toLowerCase()) { + case "page": return "页面"; + case "margin": return "页边距"; + case "paragraph": return "段落"; + case "line": return "行"; + default: return "未知"; + } + } + } + return "未知"; + } catch (Exception e) { + e.printStackTrace(); + return "未知"; + } + } // 形状和样式 public static List getStyles(List judgementWordsVOS, Inline anchor, int betoLong, WordprocessingMLPackage wordMLPackage) { diff --git a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/file/isFile.java b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/file/isFile.java new file mode 100644 index 0000000..481758e --- /dev/null +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/file/isFile.java @@ -0,0 +1,50 @@ +package com.example.exam.exam.service.wpsword.docx4j.file; + +import java.io.File; + +public class isFile { + + public static String FileExists(String path, String fileName) { + if (path == null || path.trim().isEmpty()) { + return "路径不能为空"; + } + if (fileName == null || fileName.trim().isEmpty()) { + return "文件名不能为空"; + } + + // 拼接完整文件路径(确保路径分隔符正确) + String fullPath = path.endsWith(File.separator) ? path + fileName : path + File.separator + fileName; + + File file = new File(fullPath); + + // 检查文件是否存在 + if (file.exists() && file.isFile()) { + return "是"; + } else { + return "否"; + } + + } + + public static String FileNotExists(String path, String fileName) { + if (path == null || path.trim().isEmpty()) { + return "路径不能为空"; + } + if (fileName == null || fileName.trim().isEmpty()) { + return "文件名不能为空"; + } + + // 拼接完整文件路径(确保路径分隔符正确) + String fullPath = path.endsWith(File.separator) ? path + fileName : path + File.separator + fileName; + + File file = new File(fullPath); + + // 检查文件是否存在 + if (file.exists() && file.isFile()) { + return "是"; + } else { + return "否"; + } + + } +} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/section/SectionPage.java b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/section/SectionPage.java index 90b1d77..3494d93 100644 --- a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/section/SectionPage.java +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/section/SectionPage.java @@ -1,8 +1,10 @@ -package com.example.exam.exam.service.wpsword.docx4j.section; +package pc.exam.pp.module.judgement.utils.wps_word.docx4j.section; import com.example.exam.exam.service.wpsword.docx4j.section.HeaderInfo; import com.example.exam.exam.service.wpsword.docx4j.utils.ColorUtils; +import jakarta.xml.bind.JAXBContext; import jakarta.xml.bind.JAXBElement; +import jakarta.xml.bind.Marshaller; import org.docx4j.XmlUtils; import org.docx4j.dml.Graphic; import org.docx4j.dml.GraphicData; @@ -11,19 +13,32 @@ import org.docx4j.dml.wordprocessingDrawing.Inline; import org.docx4j.mce.AlternateContent; import org.docx4j.model.structure.DocumentModel; import org.docx4j.model.structure.HeaderFooterPolicy; +import org.docx4j.model.structure.PageDimensions; import org.docx4j.model.structure.SectionWrapper; import org.docx4j.openpackaging.exceptions.Docx4JException; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import org.docx4j.openpackaging.parts.Part; import org.docx4j.openpackaging.parts.WordprocessingML.*; +import org.docx4j.vml.CTFill; import org.docx4j.wml.*; +import org.docx4j.wml.CTBackground; import org.docx4j.wml.CTBorder; import org.docx4j.wml.CTColumns; import org.docx4j.wml.CTDocGrid; +import org.docx4j.wml.CTShd; import org.docx4j.wml.STPageOrientation; import org.openxmlformats.schemas.wordprocessingml.x2006.main.*; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import javax.xml.namespace.QName; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathFactory; +import java.io.StringWriter; +import java.lang.reflect.Field; import java.math.BigInteger; import java.util.*; import java.util.regex.Matcher; @@ -211,76 +226,37 @@ public class SectionPage { } } // 每行字符数 -// 每行字符数 + 每页行数 -// public static String getSectionPageCharNumber(P paragraph, -// HeaderFooterPolicy hfp, -// WordprocessingMLPackage wordMLPackage, -// List sections) { -// try { -// if (sections == null || sections.isEmpty()) { -// return "【没有节信息】"; -// } -// -// // 这里只取第一个节(通常文档只有一个节,如果有多个可以 for 循环) -// SectionWrapper sectionWrapper = sections.get(0); -// SectPr sectPr = sectionWrapper.getSectPr(); -// if (sectPr == null) { -// return "【无 SectPr 信息】"; -// } -// System.out.println("=============="); -// -// -// -// // 页面属性 -// SectPr.PgSz pgSz = sectPr.getPgSz(); -// SectPr.PgMar pgMar = sectPr.getPgMar(); -// if (pgSz == null || pgMar == null) { -// return "【无页面设置】"; -// } -// -// int pageW = pgSz.getW().intValue(); // 页面宽度 (twip) -// int pageH = pgSz.getH().intValue(); // 页面高度 (twip) -// int left = pgMar.getLeft().intValue(); -// int right = pgMar.getRight().intValue(); -// int top = pgMar.getTop().intValue(); -// int bottom = pgMar.getBottom().intValue(); -// -// // 可用区域 -// int availableWidth = pageW - left - right; -// int availableHeight = pageH - top - bottom; -// -// // 网格设置 -// CTDocGrid grid = sectPr.getDocGrid(); -// if (grid == null) { -// return "【未设置文档网格】"; -// } -// -// StringBuilder sb = new StringBuilder("【页面设置(文档网格)】"); -// -// // 每行字符数 -// if (grid.getCharSpace() != null) { -// int charSpace = grid.getCharSpace().intValue(); -// if (charSpace > 0) { -// int charsPerLine = Math.round((float) availableWidth / charSpace); -// sb.append("【每行字符数】").append(charsPerLine).append(" 字/行"); -// } -// } -// -// // 每页行数 -// if (grid.getLinePitch() != null) { -// int linePitch = grid.getLinePitch().intValue(); -// if (linePitch > 0) { -// int linesPerPage = Math.round((float) availableHeight / linePitch); -// sb.append("【每页行数】").append(linesPerPage).append(" 行/页"); -// } -// } -// -// return sb.toString(); -// -// } catch (Exception e) { -// return "【计算异常】" + e.getMessage(); -// } -// } + public static String getSectionPageCharNumber(P paragraph, + HeaderFooterPolicy hfp, + WordprocessingMLPackage wordMLPackage, + List sections) { + if (paragraph == null || paragraph.getPPr() == null) return "未知"; + + SectPr sectPr = paragraph.getPPr().getSectPr(); + if (sectPr == null) return "无"; + + CTDocGrid docGrid = sectPr.getDocGrid(); + if (docGrid == null || docGrid.getCharSpace() == null) { + return "未设置"; + } + + // 取 charSpace (每个字的宽度, twips) + int charSpace = docGrid.getCharSpace().intValue(); + + // 计算可写宽度(页面宽度 - 左右边距) + PageDimensions pageDim = new PageDimensions(sectPr); + int availableWidth = pageDim.getWritableWidthTwips(); + + // 反推字数 (四舍五入) + int charsPerLine = Math.round((float) availableWidth / (float) charSpace); + System.out.println("charSpace = " + charSpace); + System.out.println("可写宽度 = " + availableWidth); + System.out.println("反推字数 = " + charsPerLine); + + return "【每行字符数】" + charsPerLine + " 字/行"; + } + + @@ -1364,25 +1340,6 @@ public class SectionPage { // } // } - - - - - - - - - - - - - - - - - - - // ----- 公共方法:获取单个属性(示例) ----- //旋转角度 public static String getRotationDegree(P paragraph, HeaderFooterPolicy hfp, WordprocessingMLPackage wordMLPackage, List sections) throws Exception { @@ -1412,17 +1369,102 @@ public class SectionPage { public static String getOpacityPercent(P paragraph, HeaderFooterPolicy hfp, WordprocessingMLPackage wordMLPackage, List sections) throws Exception { String vmlXml = getVMLWatermarkXml(paragraph, hfp, wordMLPackage, sections); if (vmlXml == null) return null; - Pattern pattern = Pattern.compile("opacity=\"([0-9a-fA-F]+)f\""); - Pattern opacityPattern = Pattern.compile("]*opacity=\"([0-9\\.]+)\""); - Matcher matcher = opacityPattern.matcher(vmlXml); - if (matcher.find()) { - String opacityStr = matcher.group(1); - float opacityPercent = Float.parseFloat(opacityStr) * 100; - return String.format("%.2f%%", opacityPercent); + + // 1. 小数形式,例如 opacity="0.4" + Pattern decimalPattern = Pattern.compile("]*opacity=\"([0-9\\.]+)\""); + Matcher decimalMatcher = decimalPattern.matcher(vmlXml); + if (decimalMatcher.find()) { + try { + float opacity = Float.parseFloat(decimalMatcher.group(1)) * 100; + // 反转:Word显示的透明度 = 100 - 实际值 + opacity = 100 - opacity; + // 限制在 0~100% + opacity = Math.min(Math.max(opacity, 0), 100); + return String.format("%d%%", Math.round(opacity)); + } catch (NumberFormatException e) { + // 忽略,尝试解析十六进制 + } + } + + // 2. 匹配十六进制或整数值 + Pattern hexPattern = Pattern.compile("opacity\\s*=\\s*\"([0-9a-fA-F]+)\""); + Matcher hexMatcher = hexPattern.matcher(vmlXml); + if (hexMatcher.find()) { + String hexStr = hexMatcher.group(1).replaceFirst("(?i)^0x", ""); + + // 如果最后一个字符是 'f' 或 'F',去掉 + if (hexStr.endsWith("f") || hexStr.endsWith("F")) { + hexStr = hexStr.substring(0, hexStr.length() - 1); + } + + try { + int value; + + // 如果是纯数字字符串,直接十进制解析 + if (hexStr.matches("\\d+")) { + value = Integer.parseInt(hexStr); + } else { + // 否则按16进制解析 + value = Integer.parseInt(hexStr, 16); + } + + // Word 的公式: 透明度 = (value / 65535.0) * 100 + float percent =100- (value / 65535.0f) * 100; + + // 限制在 0-100 + percent = Math.min(Math.max(percent, 0), 100); + + return String.format("%d%%", Math.round(percent)); + + } catch (NumberFormatException ignored) {} } return null; } + + + + //页面背景→ 填充方式 + public static String getPageFillType(P paragraph, HeaderFooterPolicy hfp, WordprocessingMLPackage wordMLPackage, List sections) throws Exception { + SectPr sectPr = wordMLPackage.getMainDocumentPart().getJaxbElement().getBody().getSectPr(); + if (sectPr == null) return "无填充"; + + // 转为 XML + String xml = org.docx4j.XmlUtils.marshaltoString(sectPr, true); + + if (xml == null || xml.isEmpty()) return "无填充"; + + XPathFactory xPathfactory = XPathFactory.newInstance(); + XPath xpath = xPathfactory.newXPath(); + + // 检查是否有背景颜色或填充 + XPathExpression colorExpr = xpath.compile("//*[local-name()='bgColor' or local-name()='shd']"); + Node colorNode = (Node) colorExpr.evaluate(org.docx4j.XmlUtils.getNewDocumentBuilder().parse( + new java.io.ByteArrayInputStream(xml.getBytes())), XPathConstants.NODE); + if (colorNode != null) { + return "纯色填充"; + } + + // 检查是否有纹理或图案填充 + XPathExpression patternExpr = xpath.compile("//*[local-name()='fill' and @*]"); // 一般纹理/图案用 fill 属性 + Node patternNode = (Node) patternExpr.evaluate(org.docx4j.XmlUtils.getNewDocumentBuilder().parse( + new java.io.ByteArrayInputStream(xml.getBytes())), XPathConstants.NODE); + if (patternNode != null) { + return "纹理/图案填充"; + } + + // 检查是否有图片背景 + XPathExpression picExpr = xpath.compile("//*[local-name()='blip']"); // 图片一般是 + Node picNode = (Node) picExpr.evaluate(org.docx4j.XmlUtils.getNewDocumentBuilder().parse( + new java.io.ByteArrayInputStream(xml.getBytes())), XPathConstants.NODE); + if (picNode != null) { + return "图片填充"; + } + + return "无填充"; + + } + //水印文字 public static String getWatermarkText(P paragraph, HeaderFooterPolicy hfp, WordprocessingMLPackage wordMLPackage, List sections) throws Exception { String vmlXml = getVMLWatermarkXml(paragraph, hfp, wordMLPackage, sections); diff --git a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/vo/WpsDocxInfoVo.java b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/vo/WpsDocxInfoVo.java index 6d3e774..3822034 100644 --- a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/vo/WpsDocxInfoVo.java +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/vo/WpsDocxInfoVo.java @@ -22,4 +22,7 @@ public class WpsDocxInfoVo { // 方式方法 private String method; + + //文件名称 + private String fileName; }