diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/AutoWps/AutoWpsController.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/AutoWps/AutoWpsController.java index a4ad3692..d51d7b88 100644 --- a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/AutoWps/AutoWpsController.java +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/AutoWps/AutoWpsController.java @@ -36,7 +36,7 @@ import java.util.List; @RequestMapping("/auto/wps") @Tag(name = "测试判分2 - wps相关操作") @Validated -public class AutoWpsController { +public class AutoWpsController { @Resource JudgementWpsWordService judgementWpsWordService; @@ -82,7 +82,7 @@ public class AutoWpsController { * @throws Exception */ @PostMapping(value = "/docxMaster", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public CommonResult> docxMaster(@RequestPart("data") String jsonData, @RequestPart("file") MultipartFile file) throws Exception { + public CommonResult> docxMaster(@RequestPart("data") String jsonData, @RequestPart("file") MultipartFile file, @RequestParam(value = "name", required = false) String name) throws Exception { // 手动解析JSON数组 ObjectMapper objectMapper = new ObjectMapper(); List wpsDocxInfoVos = objectMapper.readValue( @@ -90,7 +90,7 @@ public class AutoWpsController { new TypeReference>() { } ); - return CommonResult.success(judgementWpsWordService.docxMaster(wpsDocxInfoVos, file)); + return CommonResult.success(judgementWpsWordService.docxMaster(wpsDocxInfoVos, file,name)); } /** diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/service/wps_word/JudgementWpsWordService.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/service/wps_word/JudgementWpsWordService.java index b755ec16..3aac606c 100644 --- a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/service/wps_word/JudgementWpsWordService.java +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/service/wps_word/JudgementWpsWordService.java @@ -15,7 +15,7 @@ import java.util.List; */ public interface JudgementWpsWordService { - List docxMaster(List wpsDocxInfoVos, MultipartFile file) throws Exception; + List docxMaster(List wpsDocxInfoVos, MultipartFile file,String name) throws Exception; List docxDataInfo(MultipartFile file) throws Exception; } diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/service/wps_word/JudgementWpsWordServiceImpl.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/service/wps_word/JudgementWpsWordServiceImpl.java index 29142958..55450757 100644 --- a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/service/wps_word/JudgementWpsWordServiceImpl.java +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/service/wps_word/JudgementWpsWordServiceImpl.java @@ -30,8 +30,8 @@ public class JudgementWpsWordServiceImpl implements JudgementWpsWordService { * @throws Exception 异常 */ @Override - public List docxMaster(List wpsDocxInfoVos, MultipartFile file) throws Exception { - return DocxMaster.docxMaster(wpsDocxInfoVos, file); + public List docxMaster(List wpsDocxInfoVos, MultipartFile file,String name) throws Exception { + return DocxMaster.docxMaster(wpsDocxInfoVos, file,name); } /** diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/DocxConversion.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/DocxConversion.java index c786014d..902d0402 100644 --- a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/DocxConversion.java +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/DocxConversion.java @@ -23,62 +23,72 @@ public class DocxConversion { public static List DocxDataInfos(MultipartFile file) throws Exception { List dataInfoVOS = new ArrayList<>(); try (InputStream inputStream = file.getInputStream()) { - // 1. 直接使用 InputStream 构造 XWPFDocument(无需本地文件) XWPFDocument document = new XWPFDocument(inputStream); - // 2. 读取文档内容(示例:提取所有段落文本) - StringBuilder content = new StringBuilder(); - document.getParagraphs().forEach(paragraph -> { - content.append(paragraph.getText()).append("\n"); - }); - - String xmlString = XmlUtil.getDocumentXml(document); String namespace = WpsWordNameSpaces.getNameSpace(xmlString); XmlObject docXml = document.getDocument(); - // 1、获取文档段落W:P标签得数量,判断出一个有多少段 + + // 段落根节点 XmlCursor wpCursor = docXml.newCursor(); String wpPath = namespace + "/w:document/w:body/w:p"; wpCursor.selectPath(wpPath); + int wpIndex = 0; - // 段落 String firstIdWp = getStringRandom(); setWordDataInfo(firstIdWp, "", "段落", "w:p", "", false, dataInfoVOS); - while (wpCursor.toNextSelection()) { - wpIndex ++; - // 段落属性 - if (!wpCursor.xmlText().contains("w:sectPr")) { - // 获取文本 - XmlCursor wpTextXml = wpCursor.newCursor(); - wpTextXml.selectPath(namespace + ".//w:r/w:t"); - StringBuilder fullTextBuilder = new StringBuilder(); - while (wpTextXml.toNextSelection()) { - String part = wpTextXml.getTextValue(); - if (part != null) { - fullTextBuilder.append(part); - } - } - String fullText = fullTextBuilder.toString().trim(); - if (!fullText.isEmpty()) { - String secondIdWp = getStringRandom(); - String shortText = shorten(fullText); // 截断展示 - setWordDataInfo(secondIdWp, firstIdWp, "段落" + wpIndex + ":" + shortText, "w:p", String.valueOf(wpIndex), true, dataInfoVOS); + while (wpCursor.toNextSelection()) { + wpIndex++; + if (!wpCursor.xmlText().contains("w:sectPr")) { + // 1. 提取普通段落文本(排除文本框) + String fullText = extractParagraphText(wpCursor, namespace); + + // 2. 提取文本框文本 + List textBoxSentences = processTextBoxParagraphs(wpCursor, namespace); + + String secondIdWp = null; + if (!fullText.isEmpty()) { + // 过滤掉文本框句子 + StringBuilder filteredTextBuilder = new StringBuilder(); String[] sentences = fullText.split("[。!?.!?]"); + for (String sentence : sentences) { + sentence = sentence.trim(); + if (!sentence.isEmpty() && (textBoxSentences == null || !textBoxSentences.contains(sentence))) { + if (filteredTextBuilder.length() > 0) { + filteredTextBuilder.append("。"); // 可以用原文分隔符 + } + filteredTextBuilder.append(sentence); + } + } + String filteredFullText = filteredTextBuilder.toString(); + + // 生成段落节点,使用过滤后的文本 + secondIdWp = getStringRandom(); + setWordDataInfo(secondIdWp, firstIdWp, "段落" + wpIndex + ":" + shorten(filteredFullText), "w:p", String.valueOf(wpIndex), true, dataInfoVOS); + + // 拆分句子 int sentenceIndex = 0; for (String sentence : sentences) { sentence = sentence.trim(); if (!sentence.isEmpty()) { + // 跳过文本框中已经存在的句子 + if (textBoxSentences != null && textBoxSentences.contains(sentence)) { + continue; + } sentenceIndex++; String thirdIdWp = getStringRandom(); setWordDataInfo(thirdIdWp, secondIdWp, "句子" + sentenceIndex + ":" + shorten(sentence), "w:t", String.valueOf(wpIndex), true, dataInfoVOS); } } } - } } + + + + // 2、页面 XmlCursor wSectPrCursor = docXml.newCursor(); String wSectPrPath = namespace + "//w:sectPr"; // 查找所有节 @@ -243,7 +253,18 @@ public class DocxConversion { dataInfoVOS ); } - + String firstIdFTbl = getStringRandom(); + setWordDataInfo(firstIdFTbl, "", "杂项", "file", "1", false, dataInfoVOS); + String secondIdTbl = getStringRandom(); + setWordDataInfo( + secondIdTbl, + firstIdFTbl, + "文件" , + "file", + "1", + true, + dataInfoVOS + ); // // 5、域 @@ -319,5 +340,52 @@ public class DocxConversion { return text; // 如果没匹配到,就返回原字符串 } + /** + * 提取段落文本,包括 w:t + */ + private static String extractParagraphText(XmlCursor wpCursor, String namespace) { + XmlCursor wpTextXml = wpCursor.newCursor(); + wpTextXml.selectPath(namespace + ".//w:r/w:t"); + + StringBuilder fullTextBuilder = new StringBuilder(); + while (wpTextXml.toNextSelection()) { + String part = wpTextXml.getTextValue(); + if (part != null) { + fullTextBuilder.append(part); + } + } + return fullTextBuilder.toString().trim(); + } + + /** + * 递归处理文本框段落 + */ + + + + private static List processTextBoxParagraphs(XmlCursor wpCursor, String namespace) throws Exception { + List textBoxSentences = new ArrayList<>(); + XmlCursor tbCursor = wpCursor.newCursor(); + // 注意命名空间声明方式,避免 XPath 异常 + tbCursor.selectPath(namespace + ".//w:txbxContent/w:p"); + while (tbCursor.toNextSelection()) { + String tbText = extractParagraphText(tbCursor, namespace); + if (!tbText.isEmpty()) { + // 按句子拆分 + String[] sentences = tbText.split("[。!?.!?]"); + for (String sentence : sentences) { + sentence = sentence.trim(); + if (!sentence.isEmpty()) { + textBoxSentences.add(sentence); + } + } + } + } + tbCursor.dispose(); + return textBoxSentences; + } + + + } diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/DocxMaster.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/DocxMaster.java index 0beceb58..e2e45cd4 100644 --- a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/DocxMaster.java +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/DocxMaster.java @@ -28,6 +28,7 @@ import org.springframework.web.multipart.MultipartFile; import org.w3c.dom.Node; import pc.exam.pp.module.judgement.controller.admin.AutoWps.vo.WpsDocxInfoVo; import pc.exam.pp.module.judgement.utils.wps_word.docx4j.endNote.EndNoteing; +import pc.exam.pp.module.judgement.utils.wps_word.docx4j.file.isFile; import pc.exam.pp.module.judgement.utils.wps_word.docx4j.insert.InsertIng; import pc.exam.pp.module.judgement.utils.wps_word.docx4j.paragraph.Convert; import pc.exam.pp.module.judgement.utils.wps_word.docx4j.paragraph.Paragraphs; @@ -72,7 +73,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 name) throws Exception { // 一共分为 段落, 页面,图形 String firstDuanLuoId = Convert.getStringRandom(); @@ -626,6 +627,28 @@ public class DocxMaster { + if (firstName.contains("文件")) { + try { + + Class clazz = isFile.class; + Method method = clazz.getMethod(function); + String value = (String) method.invoke(null ); + if (value != null) { + judgementWordsVOS = setJudgementWord( + judgementWordsVOS, + docxFunction + "@" +name+"@"+ value, + firstName + examName + name + ); + } + } catch (NoSuchMethodException e) { + System.err.println("方法不存在: " + function); + } catch (Exception e) { + e.printStackTrace(); + } + + break; // 找到指定表格就退出 + + } diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/drawing/Drawing.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/drawing/Drawing.java index 2fc35bb3..873c8dd9 100644 --- a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/drawing/Drawing.java +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/drawing/Drawing.java @@ -90,19 +90,108 @@ 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 "未知"; + } + + // 形状和样式 public static List getStyles(List judgementWordsVOS, Anchor anchor, int betoLong, WordprocessingMLPackage wordMLPackage) { String name = "【第" + betoLong + "个图形】【图片】"; @@ -1396,6 +1485,11 @@ public static String getSolidFillColor(List judgementWordsVOS, return "无"; } + + + + + // 获取倒影对象 private static CTReflectionEffect getReflectionEffect(Anchor anchor) { if (anchor == null diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/drawing/DrawingInline.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/drawing/DrawingInline.java index ef0e97b9..359a39b2 100644 --- a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/drawing/DrawingInline.java +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/drawing/DrawingInline.java @@ -4,6 +4,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; @@ -11,6 +12,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 pc.exam.pp.module.judgement.utils.wps_word.docx4j.DocxSetInfo; import pc.exam.pp.module.judgement.utils.wps_word.docx4j.paragraph.Convert; import pc.exam.pp.module.judgement.utils.wps_word.docx4j.vo.JudgementWordsVO; @@ -79,12 +82,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/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/file/isFile.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/file/isFile.java new file mode 100644 index 00000000..c74627be --- /dev/null +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/file/isFile.java @@ -0,0 +1,15 @@ +package pc.exam.pp.module.judgement.utils.wps_word.docx4j.file; + +import java.io.File; + +public class isFile { + + public static String FileExists() { + return "是"; + + } + + public static String FileNotExists() { + return "否"; + } +} diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/section/SectionPage.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/section/SectionPage.java index 617619ea..f1534224 100644 --- a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/section/SectionPage.java +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/utils/wps_word/docx4j/section/SectionPage.java @@ -1,6 +1,8 @@ package pc.exam.pp.module.judgement.utils.wps_word.docx4j.section; +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; @@ -15,14 +17,26 @@ 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; @@ -1324,25 +1338,6 @@ public class SectionPage { // } // } - - - - - - - - - - - - - - - - - - - // ----- 公共方法:获取单个属性(示例) ----- //旋转角度 public static String getRotationDegree(P paragraph, HeaderFooterPolicy hfp, WordprocessingMLPackage wordMLPackage, List sections) throws Exception { @@ -1372,18 +1367,103 @@ 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); if (vmlXml == null) return null;