From 30e5eaa71ac3ac25e6d5fa1316f5bd93316ba097 Mon Sep 17 00:00:00 2001 From: dlaren Date: Sun, 10 Aug 2025 02:23:58 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E4=BF=AE=E6=94=B9=E3=80=91=20word?= =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E5=88=A4=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 16 + .../exam/service/wpsword/ColorNameFinder.java | 65 -- .../wpsword/JudgementWpsWordServiceImpl.java | 81 +- .../service/wpsword/PageSizeDetector.java | 97 --- .../service/wpsword/ShadowDirectionUtils.java | 77 -- .../exam/service/wpsword/TwipConverter.java | 42 - .../service/wpsword/WordHeaderFooterVo.java | 11 - .../service/wpsword/WpsWordJudgementDto.java | 14 - .../service/wpsword/WpsWordNameSpaces.java | 30 - .../exam/service/wpsword/WpsWordReqDto.java | 36 - .../exam/service/wpsword/WpsWordUtils.java | 761 ----------------- .../service/wpsword/docx4j/DocxMaster.java | 139 ++++ .../service/wpsword/docx4j/DocxSetInfo.java | 20 + .../wpsword/docx4j/drawing/Drawing.java | 224 +++++ .../wpsword/docx4j/paragraph/Convert.java | 84 ++ .../wpsword/docx4j/paragraph/Paragraphs.java | 771 ++++++++++++++++++ .../wpsword/docx4j/paragraph/RunText.java | 172 ++++ .../wpsword/docx4j/section/SectionPage.java | 308 +++++++ .../service/wpsword/docx4j/text/TextInfo.java | 133 +++ .../wpsword/docx4j/vo/DocxDataInfoVO.java | 19 + .../wpsword/docx4j/vo/JudgementWordsVO.java | 17 + .../wpsword/docx4j/vo/WpsDocxInfoVo.java | 25 + .../exam/exam/utils/CustomMultipartFile.java | 57 ++ 23 files changed, 2021 insertions(+), 1178 deletions(-) delete mode 100644 src/main/java/com/example/exam/exam/service/wpsword/ColorNameFinder.java delete mode 100644 src/main/java/com/example/exam/exam/service/wpsword/PageSizeDetector.java delete mode 100644 src/main/java/com/example/exam/exam/service/wpsword/ShadowDirectionUtils.java delete mode 100644 src/main/java/com/example/exam/exam/service/wpsword/TwipConverter.java delete mode 100644 src/main/java/com/example/exam/exam/service/wpsword/WordHeaderFooterVo.java delete mode 100644 src/main/java/com/example/exam/exam/service/wpsword/WpsWordJudgementDto.java delete mode 100644 src/main/java/com/example/exam/exam/service/wpsword/WpsWordNameSpaces.java delete mode 100644 src/main/java/com/example/exam/exam/service/wpsword/WpsWordReqDto.java delete mode 100644 src/main/java/com/example/exam/exam/service/wpsword/WpsWordUtils.java create mode 100644 src/main/java/com/example/exam/exam/service/wpsword/docx4j/DocxMaster.java create mode 100644 src/main/java/com/example/exam/exam/service/wpsword/docx4j/DocxSetInfo.java create mode 100644 src/main/java/com/example/exam/exam/service/wpsword/docx4j/drawing/Drawing.java create mode 100644 src/main/java/com/example/exam/exam/service/wpsword/docx4j/paragraph/Convert.java create mode 100644 src/main/java/com/example/exam/exam/service/wpsword/docx4j/paragraph/Paragraphs.java create mode 100644 src/main/java/com/example/exam/exam/service/wpsword/docx4j/paragraph/RunText.java create mode 100644 src/main/java/com/example/exam/exam/service/wpsword/docx4j/section/SectionPage.java create mode 100644 src/main/java/com/example/exam/exam/service/wpsword/docx4j/text/TextInfo.java create mode 100644 src/main/java/com/example/exam/exam/service/wpsword/docx4j/vo/DocxDataInfoVO.java create mode 100644 src/main/java/com/example/exam/exam/service/wpsword/docx4j/vo/JudgementWordsVO.java create mode 100644 src/main/java/com/example/exam/exam/service/wpsword/docx4j/vo/WpsDocxInfoVo.java create mode 100644 src/main/java/com/example/exam/exam/utils/CustomMultipartFile.java diff --git a/pom.xml b/pom.xml index 590d003..3eeef4e 100644 --- a/pom.xml +++ b/pom.xml @@ -160,6 +160,22 @@ + + org.docx4j + docx4j-core + 11.5.4 + + + org.docx4j + docx4j-JAXB-MOXy + 11.5.4 + + + + org.docx4j + docx4j-JAXB-Internal + 8.3.9 + diff --git a/src/main/java/com/example/exam/exam/service/wpsword/ColorNameFinder.java b/src/main/java/com/example/exam/exam/service/wpsword/ColorNameFinder.java deleted file mode 100644 index 0882a63..0000000 --- a/src/main/java/com/example/exam/exam/service/wpsword/ColorNameFinder.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.example.exam.exam.service.wpsword; - -import java.util.HashMap; -import java.util.Map; - -public class ColorNameFinder { - // 颜色代码到名称的映射 - private static final Map COLOR_MAP = new HashMap<>(); - - static { - COLOR_MAP.put("FF0000", "红色"); - COLOR_MAP.put("00FF00", "绿色"); - COLOR_MAP.put("0000FF", "蓝色"); - COLOR_MAP.put("FFFF00", "黄色"); - COLOR_MAP.put("FFA500", "橙色"); - COLOR_MAP.put("800080", "紫色"); - COLOR_MAP.put("000000", "黑色"); - COLOR_MAP.put("FFFFFF", "白色"); - COLOR_MAP.put("BB6433", "棕橙色"); - COLOR_MAP.put("FFC000", "淡黄色"); - COLOR_MAP.put("0070C0", "蓝色"); - - // 你可以继续扩展 - } - - public static String getColorName(String input) { - String hex = null; - - input = input.trim(); - - if (input.startsWith("#")) { - hex = input.substring(1).toUpperCase(); - } else if (input.matches("[0-9a-fA-F]{6}")) { - hex = input.toUpperCase(); - } else if (input.toLowerCase().startsWith("rgb")) { - // 解析 rgb(255,0,0) 格式 - hex = rgbToHex(input); - } - - if (hex != null) { - return COLOR_MAP.getOrDefault(hex, "未知颜色"); - } - - return "格式错误"; - } - - private static String rgbToHex(String rgb) { - // rgb(255,0,0) -> "FF0000" - try { - String inner = rgb.substring(rgb.indexOf('(') + 1, rgb.indexOf(')')); - String[] parts = inner.split(","); - if (parts.length != 3) return null; - - int r = Integer.parseInt(parts[0].trim()); - int g = Integer.parseInt(parts[1].trim()); - int b = Integer.parseInt(parts[2].trim()); - - if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) return null; - - return String.format("%02X%02X%02X", r, g, b); - } catch (Exception e) { - return null; - } - } -} 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 3889f28..d24cebd 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 @@ -4,11 +4,17 @@ package com.example.exam.exam.service.wpsword; import com.example.exam.exam.dal.ExamQuestion; import com.example.exam.exam.dal.ExamQuestionAnswer; import com.example.exam.exam.dal.SourceAndText; +import com.example.exam.exam.service.wpsword.docx4j.DocxMaster; +import com.example.exam.exam.service.wpsword.docx4j.vo.JudgementWordsVO; +import com.example.exam.exam.service.wpsword.docx4j.vo.WpsDocxInfoVo; +import com.example.exam.exam.utils.CustomMultipartFile; import com.example.exam.exam.utils.HtmlAppender; import com.example.exam.exam.utils.c.LogFileUtils; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import java.io.File; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; @@ -23,73 +29,58 @@ public class JudgementWpsWordServiceImpl implements JudgementWpsWordService { @Override public SourceAndText judgementWpsWord(double sorce, String pathC, String path, ExamQuestion examQuestion, String judgementStr) throws Exception { SourceAndText sourceAndText = new SourceAndText(); + List wpsDocxInfos = new ArrayList<>(); // 创建log文件txt,用于记录 -// LogFileUtils.createFile(pathC + "/WPS_Word判分过程.txt"); -// LogFileUtils.writeLine("✅ 开始WPS_Word判分"); judgementStr = HtmlAppender.appendHtmlLine(judgementStr, "✅ 开始WPS_Word判分"); double wps_word_sorce = 0; // 2、docx文件读取并返回考点及说明信息 -// List margins = WpsWordUtils.wps_word(path); // 3、获取答案得组成 List answerList = examQuestion.getAnswerList(); // 4、进行关联判断 for (ExamQuestionAnswer examQuestionAnswer : answerList) { // 拆分数据、 - String[] wordInfos = examQuestionAnswer.getContent().split("-/"); - String[] chineseName = examQuestionAnswer.getContentIn().split("-"); - String[] typeList = examQuestionAnswer.getImage().split("-"); - // 创建拼接数据 - // 只取三层结构 - List wordReqDto = new ArrayList<>(); - WpsWordReqDto wpsWordReqDto = new WpsWordReqDto(); - wpsWordReqDto.setName(examQuestionAnswer.getContentIn()); - wpsWordReqDto.setEnglishName(wordInfos[0].split("]")[0] + "]"); - wpsWordReqDto.setFilePath(path); - // 存放类型 - wpsWordReqDto.setType(typeList[0]); - wpsWordReqDto.setBelongTo(typeList[1]); - wpsWordReqDto.setIsboo(typeList[2]); - wpsWordReqDto.setUnit(typeList[3]); - wpsWordReqDto.setFunction(wordInfos[0]); - wpsWordReqDto.setIsExam("1"); - wordReqDto.add(wpsWordReqDto); - System.out.println(examQuestionAnswer.getContentIn()); - System.out.println(examQuestionAnswer.getContent()); - List judgementDtos = WpsWordUtils.getWordInfo(wordReqDto); - boolean flag = false; - double one_sorce = 0; - for (WpsWordJudgementDto wordJudgementDto : judgementDtos) { + String[] docxInfo = examQuestionAnswer.getContent().split("@"); + WpsDocxInfoVo wpsDocxInfoVo = new WpsDocxInfoVo(); + wpsDocxInfoVo.setFirstName(docxInfo[0]); + wpsDocxInfoVo.setIndex(docxInfo[1]); + wpsDocxInfoVo.setFunction(docxInfo[2]); + wpsDocxInfoVo.setExamName(docxInfo[3]); + wpsDocxInfoVo.setExamCode(docxInfo[4]); + // 组成判分的结果 + wpsDocxInfos.add(wpsDocxInfoVo); + } + // 4.1、将文件转成文件流的形式 + MultipartFile multipartFile = new CustomMultipartFile(new File(path)); + // 5、进行判分 + List judgementWordsVOS = DocxMaster.docxMaster(wpsDocxInfos, multipartFile); + boolean flag = false; + double one_sorce = 0; + // 6、进行得分计算 + for (ExamQuestionAnswer examQuestionAnswer : answerList) { + for (JudgementWordsVO wordJudgementDto : judgementWordsVOS) { if (wordJudgementDto.getContent() != null) { -// for (String str : wordJudgementDto.getFunction()) { - int index = 0; - if (wordJudgementDto.getContent().equals(examQuestionAnswer.getContent())) { - flag = true; - // 得分 根据权重进行得分 每个选项分值 = 总分 / 总权重 - if (examQuestionAnswer.getScoreRate().equals("1")) { - // 说明权重相等,直接平分分数 - one_sorce = sorce / answerList.size(); - } else { - one_sorce = sorce * Double.parseDouble(examQuestionAnswer.getScoreRate()); - } - break; + if (wordJudgementDto.getContent().equals(examQuestionAnswer.getContent())) { + flag = true; + // 得分 根据权重进行得分 每个选项分值 = 总分 / 总权重 + if (examQuestionAnswer.getScoreRate().equals("1")) { + // 说明权重相等,直接平分分数 + one_sorce = sorce / answerList.size(); + } else { + one_sorce = sorce * Double.parseDouble(examQuestionAnswer.getScoreRate()); } -// } + break; + } } } wps_word_sorce += one_sorce; if (flag) { -// LogFileUtils.writeLine("✅ " + examQuestionAnswer.getContentIn() + " 得分成功,得分:" + one_sorce); judgementStr = HtmlAppender.appendHtmlLine(judgementStr, "✅ " + examQuestionAnswer.getContentIn() + " 得分成功,得分:" + new BigDecimal(one_sorce).setScale(1, RoundingMode.HALF_UP)); } else { -// LogFileUtils.writeLine("❌ " + examQuestionAnswer.getContentIn() + " 得分失败"); judgementStr = HtmlAppender.appendHtmlLine(judgementStr, "❌ " + examQuestionAnswer.getContentIn() + " 得分失败"); } } -// LogFileUtils.writeLine("✅ 结束WPS_Word判分,试题得分:" + wps_word_sorce); - // 关闭已经打开得文件 -// LogFileUtils.close(); sourceAndText.setScore(wps_word_sorce); sourceAndText.setText(judgementStr); return sourceAndText; diff --git a/src/main/java/com/example/exam/exam/service/wpsword/PageSizeDetector.java b/src/main/java/com/example/exam/exam/service/wpsword/PageSizeDetector.java deleted file mode 100644 index 7ffefc4..0000000 --- a/src/main/java/com/example/exam/exam/service/wpsword/PageSizeDetector.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.example.exam.exam.service.wpsword; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class PageSizeDetector { - public static String detectPaperSize(String pgSzXml) { - Pattern pattern = Pattern.compile("w:w=\"(\\d+)\".*?"); - Matcher matcher = pattern.matcher(pgSzXml); - int widthTwip = 0; - if (matcher.find()) { - widthTwip = Integer.parseInt(matcher.group(1)); - } - Pattern patterns = Pattern.compile("w:h=\"(\\d+)\".*?"); - Matcher matchers = patterns.matcher(pgSzXml); - int heightTwip = 0; - if (matchers.find()) { - heightTwip = Integer.parseInt(matchers.group(1)); - } - Pattern patternss = Pattern.compile("w:orient\\s*=\\s*\"(.*?)\""); - Matcher matcherss = patternss.matcher(pgSzXml); - String orient = ""; - if (matcherss.find()) { - orient = matcherss.group(1); - } - // 如果方向是 landscape,宽高调换(视觉上) - if ("landscape".equalsIgnoreCase(orient)) { - int temp = widthTwip; - widthTwip = heightTwip; - heightTwip = temp; - } - // 转换为 cm:1 twip = 1/1440 英寸 = 2.54 / 1440 cm - double widthCm = widthTwip * 2.54 / 1440; - double heightCm = heightTwip * 2.54 / 1440; - - // 四舍五入保留一位小数 - widthCm = Math.round(widthCm * 10) / 10.0; - heightCm = Math.round(heightCm * 10) / 10.0; - - // 判断标准纸型(以 cm 为单位进行匹配) - String paperType = "Unknown"; - if (approx(widthCm, 21.0) && approx(heightCm, 29.7)) { - paperType = "A4"; - } else if (approx(widthCm, 29.7) && approx(heightCm, 42.0)) { - paperType = "A3"; - } else if (approx(widthCm, 14.8) && approx(heightCm, 21.0)) { - paperType = "A5"; - } - - return String.format("纸张类型:%s(%.1fcm × %.1fcm)", paperType, widthCm, heightCm); - } - - public static String detectPaperPPTSize(String pgSzXml) { - Pattern pattern = Pattern.compile("cx=\"(\\d+)\".*?"); - Matcher matcher = pattern.matcher(pgSzXml); - int widthTwip = 0; - if (matcher.find()) { - widthTwip = Integer.parseInt(matcher.group(1)); - } - Pattern patterns = Pattern.compile("cy=\"(\\d+)\".*?"); - Matcher matchers = patterns.matcher(pgSzXml); - int heightTwip = 0; - if (matchers.find()) { - heightTwip = Integer.parseInt(matchers.group(1)); - } - // 转换为 cm:1 twip = 1/1440 英寸 = 2.54 / 1440 cm - double widthCm = widthTwip / 360000; - double heightCm = heightTwip / 360000; - - // 四舍五入保留一位小数 - widthCm = Math.round(widthCm * 10) / 10.0; - heightCm = Math.round(heightCm * 10) / 10.0; - - // 判断标准纸型(以 cm 为单位进行匹配) - String paperType = "自定义"; - if (approx(widthCm, 21.0) && approx(heightCm, 29.7)) { - paperType = "A4"; - } else if (approx(widthCm, 29.7) && approx(heightCm, 42.0)) { - paperType = "A3"; - } else if (approx(widthCm, 14.8) && approx(heightCm, 21.0)) { - paperType = "A5"; - } - - return String.format("纸张类型:%s(%.1fcm × %.1fcm)", paperType, widthCm, heightCm); - } - - // 判断两个 double 值是否接近 - private static boolean approx(double a, double b) { - return Math.abs(a - b) < 0.5; // 误差范围 0.5cm 内 - } - - // 示例用法 - public static void main(String[] args) { - String xml = ""; - System.out.println(detectPaperSize(xml)); - } -} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/ShadowDirectionUtils.java b/src/main/java/com/example/exam/exam/service/wpsword/ShadowDirectionUtils.java deleted file mode 100644 index 2ff5784..0000000 --- a/src/main/java/com/example/exam/exam/service/wpsword/ShadowDirectionUtils.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.example.exam.exam.service.wpsword; - -public class ShadowDirectionUtils { - - // 中文阴影方向枚举 - public enum ShadowDirection { - 上("上"), - 右上("右上"), - 右("右"), - 右下("右下"), - 下("下"), - 左下("左下"), - 左("左"), - 左上("左上"), - 中心("中心"), - 未知("未知"); - - private final String label; - - ShadowDirection(String label) { - this.label = label; - } - - public String getLabel() { - return label; - } - } - - /** - * 根据 dir 值判断阴影方向类型(返回中文方向) - * @param dir 阴影方向(单位为 1/60000 度) - * @return 中文方向名 - */ - public static ShadowDirection getShadowDirection(long dir) { - // 转换为角度 - double angle = dir / 60000.0; - angle = ((angle % 360) + 360) % 360; // 归一化到 0~360° - - if (angle >= 337.5 || angle < 22.5) { - return ShadowDirection.右; - } else if (angle >= 22.5 && angle < 67.5) { - return ShadowDirection.右下; - } else if (angle >= 67.5 && angle < 112.5) { - return ShadowDirection.下; - } else if (angle >= 112.5 && angle < 157.5) { - return ShadowDirection.左下; - } else if (angle >= 157.5 && angle < 202.5) { - return ShadowDirection.左; - } else if (angle >= 202.5 && angle < 247.5) { - return ShadowDirection.左上; - } else if (angle >= 247.5 && angle < 292.5) { - return ShadowDirection.上; - } else if (angle >= 292.5 && angle < 337.5) { - return ShadowDirection.右上; - } else { - return ShadowDirection.未知; - } - } - - // 示例用法 - public static void main(String[] args) { - long[] testDirs = { - 0, // 右 - 1350000, // 右下 - 5400000, // 下 - 13500000, // 左 - 18900000, // 右上 - 16200000, // 上 - 21600000 // 右 - }; - - for (long dir : testDirs) { - ShadowDirection direction = getShadowDirection(dir); - System.out.printf("dir=%d (%.1f°) => 阴影方向:%s%n", dir, dir / 60000.0, direction.getLabel()); - } - } -} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/TwipConverter.java b/src/main/java/com/example/exam/exam/service/wpsword/TwipConverter.java deleted file mode 100644 index bd00907..0000000 --- a/src/main/java/com/example/exam/exam/service/wpsword/TwipConverter.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.example.exam.exam.service.wpsword; - -public class TwipConverter { - - // 转换常量 - private static final double TWIPS_PER_INCH = 1440.0; - private static final double TWIPS_PER_CM = 567.0; - private static final double TWIPS_PER_POINT = 20.0; - private static final double EMUS_PER_POINT = 12700.0; - - // Twip 转厘米 - public static double toCentimeters(int twip) { - return twip * 2.54 / TWIPS_PER_INCH; - } - - // Twip 转英寸 - public static double toInches(int twip) { - return twip / TWIPS_PER_INCH; - } - - // Twip 转磅(point) - public static double toPoints(int twip) { - return twip / TWIPS_PER_POINT; - } - public static double toEmus(int twip) { - return twip / EMUS_PER_POINT; - } - // 保留两位小数格式化(通用) - public static String formatDouble(double value) { - return String.format("%.2f", value); - } - - // 示例主方法(可删除) - public static void main(String[] args) { - int twipValue = 1134; - - System.out.println("Twip: " + twipValue); - System.out.println("厘米: " + formatDouble(toCentimeters(twipValue)) + " cm"); - System.out.println("英寸: " + formatDouble(toInches(twipValue)) + " in"); - System.out.println("磅数: " + formatDouble(toPoints(twipValue)) + " pt"); - } -} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/WordHeaderFooterVo.java b/src/main/java/com/example/exam/exam/service/wpsword/WordHeaderFooterVo.java deleted file mode 100644 index ecfbf76..0000000 --- a/src/main/java/com/example/exam/exam/service/wpsword/WordHeaderFooterVo.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.example.exam.exam.service.wpsword; - -import lombok.Data; - -@Data -public class WordHeaderFooterVo { - - private String rid; - private String type; - private String xmlType; -} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/WpsWordJudgementDto.java b/src/main/java/com/example/exam/exam/service/wpsword/WpsWordJudgementDto.java deleted file mode 100644 index 06a1215..0000000 --- a/src/main/java/com/example/exam/exam/service/wpsword/WpsWordJudgementDto.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.exam.exam.service.wpsword; - -import lombok.Data; - -/** - * @author REN - */ -@Data -public class WpsWordJudgementDto { - private String content; - private String contentIn; - private String image; - private String scoreRate; -} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/WpsWordNameSpaces.java b/src/main/java/com/example/exam/exam/service/wpsword/WpsWordNameSpaces.java deleted file mode 100644 index a0b7bbf..0000000 --- a/src/main/java/com/example/exam/exam/service/wpsword/WpsWordNameSpaces.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.example.exam.exam.service.wpsword; - -@SuppressWarnings("all") -public class WpsWordNameSpaces { - - public static String getNameSpace(String xmlString) { - // 2、创建最全的命名空间 -// Pattern pattern = Pattern.compile("xmlns:(\\w+)='([^']+)'"); -// Matcher matcher = pattern.matcher(xmlString); -// Map namespaces = new HashMap<>(); -// while (matcher.find()) { -// // 如 w, wp, a -// String prefix = matcher.group(1); -// // 如 http://schemas.openxmlformats.org/... -// String uri = matcher.group(2); -// namespaces.put(prefix, uri); -// } -// StringBuilder xpathBuilder = new StringBuilder(); -// namespaces.forEach((prefix, uri) -> -// xpathBuilder.append("declare namespace ") -// .append(prefix) -// .append("='") -// .append(uri) -// .append("' ") -// ); -// // 2-1、获取出来最全的命名空间 -// String allPathx = xpathBuilder.toString(); - return "declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' declare namespace r='http://schemas.openxmlformats.org/officeDocument/2006/relationships' declare namespace a='http://schemas.openxmlformats.org/drawingml/2006/main' declare namespace pic='http://schemas.openxmlformats.org/drawingml/2006/picture' declare namespace wp='http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing' declare namespace wp14='http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing' declare namespace w14='http://schemas.microsoft.com/office/word/2010/wordml' declare namespace w15='http://schemas.microsoft.com/office/word/2012/wordml' declare namespace w16='http://schemas.microsoft.com/office/word/2018/wordml' declare namespace w16cid='http://schemas.microsoft.com/office/word/2016/wordml/cid' declare namespace v='urn:schemas-microsoft-com:vml' declare namespace o='urn:schemas-microsoft-com:office:office' declare namespace m='http://schemas.openxmlformats.org/officeDocument/2006/math' declare namespace mc='http://schemas.openxmlformats.org/markup-compatibility/2006' declare namespace wpg='http://schemas.microsoft.com/office/word/2010/wordprocessingGroup' declare namespace wpi='http://schemas.microsoft.com/office/word/2010/wordprocessingInk' declare namespace wps='http://schemas.microsoft.com/office/word/2010/wordprocessingShape' declare namespace c='http://schemas.openxmlformats.org/drawingml/2006/chart' declare namespace cx='http://schemas.microsoft.com/office/drawing/2014/chart' declare namespace dgm='http://schemas.openxmlformats.org/drawingml/2006/diagram' declare namespace lc='http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas' declare namespace svg='http://schemas.microsoft.com/office/drawing/2016/SVG/main' declare namespace aink='http://schemas.microsoft.com/office/drawing/2016/ink' declare namespace am3d='http://schemas.microsoft.com/office/drawing/2017/model3D' declare namespace wne='http://schemas.microsoft.com/office/word/2006/wordml' "; - } -} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/WpsWordReqDto.java b/src/main/java/com/example/exam/exam/service/wpsword/WpsWordReqDto.java deleted file mode 100644 index 1d4fcd6..0000000 --- a/src/main/java/com/example/exam/exam/service/wpsword/WpsWordReqDto.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.example.exam.exam.service.wpsword; - -import lombok.Data; - -/** - * @author REN - */ -@Data -public class WpsWordReqDto { - -// @Schema(description = "主中文名称") - private String name; - -// @Schema(description = "主英文名称") - private String englishName; - -// @Schema(description = "文件路径") - private String filePath; - -// @Schema(description = "内外参数(0:内参数;1:外参数)") - private String type; - -// @Schema(description = "归属(0:段落;1:页眉页脚;2:图形)") - private String belongTo; - -// @Schema(description = "参数类型(0:值;1:判断)") - private String isboo; - -// @Schema(description = "子标签") - private String function; - - private String unit; - - private String isExam; - -} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/WpsWordUtils.java b/src/main/java/com/example/exam/exam/service/wpsword/WpsWordUtils.java deleted file mode 100644 index 2145cf2..0000000 --- a/src/main/java/com/example/exam/exam/service/wpsword/WpsWordUtils.java +++ /dev/null @@ -1,761 +0,0 @@ -package com.example.exam.exam.service.wpsword; -import org.apache.commons.io.IOUtils; -import org.apache.poi.openxml4j.opc.OPCPackage; -import org.apache.poi.openxml4j.opc.PackagePart; -import org.apache.poi.xwpf.usermodel.XWPFDocument; -import org.apache.poi.xwpf.usermodel.XWPFFooter; -import org.apache.poi.xwpf.usermodel.XWPFHeader; -import org.apache.poi.xwpf.usermodel.XWPFParagraph; -import org.apache.xmlbeans.XmlCursor; -import org.apache.xmlbeans.XmlObject; - -import javax.xml.namespace.QName; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * @author REN - */ -public class WpsWordUtils { - public static String getStringRandom() { - String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - Random random = new Random(); - StringBuilder sb = new StringBuilder(); - - // 生成指定长度的随机字符字符串 - for (int i = 0; i < 10; i++) { - int randomIndex = random.nextInt(characters.length()); - // 随机字符 - sb.append(characters.charAt(randomIndex)); - } - return sb.toString(); - } - - public static List getWordInfo(List wpsWordReqDtos) throws Exception { - String filePath = wpsWordReqDtos.get(0).getFilePath(); - // 创建返回数组 - List judgementList = new ArrayList<>(); - // 创建文件路径数组 - List> filePathList = new ArrayList<>(); - for (WpsWordReqDto wpsWordReqDto : wpsWordReqDtos) { - try (OPCPackage pkg = OPCPackage.open(wpsWordReqDto.getFilePath()); - XWPFDocument document = new XWPFDocument(pkg)) { - for (PackagePart part : pkg.getParts()) { - String entryName = part.getPartName().getName(); - if (entryName.contains("word/_rels") && entryName.contains(".xml.rels")) { - try (InputStream is = part.getInputStream()) { - String xmlContent = IOUtils.toString(is, StandardCharsets.UTF_8); - // 解析 xmlContent - XmlObject xmlObject = XmlObject.Factory.parse(xmlContent); - XmlCursor cursor = xmlObject.newCursor(); - cursor.selectPath("declare namespace r='http://schemas.openxmlformats.org/package/2006/relationships' .//r:Relationships/r:Relationship"); - // 存放数据 - while (cursor.toNextSelection()) { - Map map = new HashMap<>(); - String rId = cursor.getAttributeText(new QName("Id")); - String target = cursor.getAttributeText(new QName("Target")); - map.put(rId, target); - filePathList.add(map); - } - } - } - } - XmlObject docXml = document.getDocument(); - XmlCursor cursor = docXml.newCursor(); - String nameSpace = WpsWordNameSpaces.getNameSpace(cursor.xmlText()); - // 需要联合查询数据,查询位置xml来获取准确的文件 - // 开始查询标签 - // 1、段落 - if ("0".equals(wpsWordReqDto.getBelongTo())) { - if (wpsWordReqDto.getFunction().contains("w:cols")) { - XmlCursor wColsCursor = cursor.newCursor(); - XmlCursor wColsCursors = null; - // 如果是分栏需要单独进行查询 - // 需要查找查询第几个段落 - String number = wpsWordReqDto.getFunction().split("]")[0]; - number = number.split("\\[")[1]; - boolean flag = true; - int numbers = Integer.parseInt(number); - while (flag) { - numbers += 1; - // 向下查询是否存在样式段落 - String nameSpaces = nameSpace + "(//w:p)["+numbers+"]"; - wColsCursor.selectPath(nameSpaces); - if (wColsCursor.toNextSelection()) { - if (wColsCursor.xmlText().contains("w:cols")) { - System.out.println(wColsCursor.xmlText()); - wColsCursors = wColsCursor.newCursor(); - flag = false; - } - } - if (numbers > 20) { - flag = false; - } - } - if (wColsCursors != null) { - String nameSpaceCols = nameSpace + "." + wpsWordReqDto.getFunction().split("]")[1]; - System.out.println(wColsCursors.xmlText()); - wColsCursors.selectPath(nameSpaceCols); - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - if (wColsCursors.toNextSelection()) { - String value = wColsCursors.getTextValue(); - judgement.setContentIn(wpsWordReqDto.getName() + value); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + value); - judgement.setScoreRate("1"); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - // 查询指定值,返回固定的文本 - judgementList.add(judgement); - } - } - } else { - String function = wpsWordReqDto.getFunction(); - String chineseName = wpsWordReqDto.getName(); - - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - // 1-1、创建新的数据组 - XmlCursor wpCursor = cursor.newCursor(); - wpCursor.selectPath(nameSpace + function); - if (wpCursor.toNextSelection()) { - if ("1".equals(wpsWordReqDto.getIsboo())) { - judgement.setContentIn(chineseName + "是"); - judgement.setContent(function + "-/true"); - } else { - String value = wpCursor.getTextValue(); - if (wpsWordReqDto.getName().contains("倾斜")) { - value = wpCursor.getAttributeText(new QName("w:val")); - value = value == null ? "是" : value == "0" ? "否" :value == "1" ? "是" : "否"; - } - value = getValueType(wpsWordReqDto, value); - judgement.setContentIn(chineseName + value); - judgement.setContent(function + "-/" + value); - } - judgement.setScoreRate("1"); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - // 查询指定值,返回固定的文本 - judgementList.add(judgement); - } else { - // 获取是否在对应的styles.xml里面 - wpCursor.selectPath(nameSpace + wpsWordReqDto.getEnglishName() + "/w:pPr/w:pStyle/@w:val"); - if (wpCursor.toNextSelection()) { - String value = wpCursor.getTextValue(); - for (PackagePart part : pkg.getParts()) { - String entryName = part.getPartName().getName(); - if (entryName.contains("word/styles") && entryName.contains(".xml")) { - try (InputStream is = part.getInputStream()) { - String xmlContent = IOUtils.toString(is, StandardCharsets.UTF_8); - XmlObject xmlObject = XmlObject.Factory.parse(xmlContent); - XmlCursor cursorStyle = xmlObject.newCursor(); - String nameSpaceStyle = WpsWordNameSpaces.getNameSpace(cursorStyle.xmlText()); - cursorStyle.selectPath(nameSpaceStyle + ".//w:style[@w:styleId='"+value+"']"); - if (cursorStyle.toNextSelection()) { - String functions = wpsWordReqDto.getFunction().replace("/w:r/", ""); - if (functions.charAt(0) == '/') { - functions = ".//" + functions.split("]")[1]; - } else { - functions = "./" + functions.split("]")[1]; - } - - cursorStyle.selectPath(nameSpaceStyle + functions); - if (cursorStyle.toNextSelection()) { - - if ("1".equals(wpsWordReqDto.getIsboo())) { - judgement.setContentIn(chineseName + "是"); - judgement.setContent(function + "-/true"); - } else { - String valueStyle = cursorStyle.getTextValue(); - valueStyle = getValueType(wpsWordReqDto, valueStyle); - judgement.setContentIn(chineseName + valueStyle); - judgement.setContent(function + "-/" + valueStyle); - } - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - } - } - } - } else if ("1".equals(wpsWordReqDto.getIsboo())) { - judgement.setContentIn(chineseName + "否"); - judgement.setContent(function + "-/false"); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - - } - } - } - List headers = document.getHeaderList(); - List footers = document.getFooterList(); - // 2、节 - if ("1".equals(wpsWordReqDto.getBelongTo())) { - // 先要获取ID - XmlCursor sectPrCursor = docXml.newCursor(); - // 内外参数(0:内参数;1:外参数) - if ("1".equals(wpsWordReqDto.getType())) { - sectPrCursor.selectPath(nameSpace + wpsWordReqDto.getFunction().split("#")[0]); - String rId = ""; - String type = ""; - List headerFooterVos = new ArrayList<>(); - while (sectPrCursor.toNextSelection()) { - rId = sectPrCursor.getAttributeText(new QName("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "id")); - type = sectPrCursor.getAttributeText(new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "type")); - WordHeaderFooterVo headerFooterVo = new WordHeaderFooterVo(); - headerFooterVo.setRid(rId); - headerFooterVo.setType(type); - headerFooterVo.setXmlType(wpsWordReqDto.getFunction()); - headerFooterVos.add(headerFooterVo); - } - String fileName = ""; - for (Map str : filePathList) { - // 页眉页脚 - for (WordHeaderFooterVo headerVo : headerFooterVos) { - if (str.containsKey(headerVo.getRid())) { - fileName = str.get(headerVo.getRid()); - if (headerVo.getXmlType().contains("headerReference")) { - for (XWPFHeader header : headers) { - if (header.getPackagePart().getPartName().getName().contains(fileName)) { - // 2-1、针对不同的进行排查 - String chineseType = Objects.equals(headerVo.getType(), "default") ? "奇数页" : Objects.equals(headerVo.getType(), "even") ? "偶数页" : ""; - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - List headerpar = header.getParagraphs(); - XmlCursor headerXml = headerpar.get(0).getCTP().newCursor(); - if (wpsWordReqDto.getFunction().contains("w:page")) { - if (headerXml.xmlText().contains("PAGE")) { - judgement.setContentIn(wpsWordReqDto.getName() + chineseType + " 是"); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + "false" + "-"+ headerVo.getType()); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } else { - judgement.setContentIn(wpsWordReqDto.getName() + chineseType + " 否"); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + "false" + "-"+ headerVo.getType()); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } else if (wpsWordReqDto.getName().contains("水印")) { - if (wpsWordReqDto.getName().contains("水印类型")) { - if (headerXml.xmlText().contains("v:shape") && headerXml.xmlText().contains("v:textpath")) { - judgement.setContentIn(wpsWordReqDto.getName() + chineseType + " 文字水印"); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + "文字水印" + "-"+ headerVo.getType()); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - if (wpsWordReqDto.getName().contains("文本")) { - headerXml.selectPath(nameSpace + "//v:shape/v:textpath/@string"); - if (headerXml.toNextSelection()) { - String value = headerXml.getTextValue(); - judgement.setContentIn(wpsWordReqDto.getName() + chineseType + "-" + value); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + value + "-"+ headerVo.getType()); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - if (wpsWordReqDto.getName().contains("字体")) { - headerXml.selectPath(nameSpace + "//v:shape/v:textpath/@style"); - if (headerXml.toNextSelection()) { - String value = headerXml.getTextValue(); - Pattern pattern = Pattern.compile("font-family:([^;]+)"); - Matcher matcher = pattern.matcher(value); - if (matcher.find()) { - String fontFamily = matcher.group(1).trim(); - judgement.setContentIn(wpsWordReqDto.getName() + chineseType + "-" +fontFamily); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + fontFamily + "-"+ headerVo.getType()); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - } - if (wpsWordReqDto.getName().contains("字号")) { - headerXml.selectPath(nameSpace + "//v:shape/v:textpath/@style"); - if (headerXml.toNextSelection()) { - String value = headerXml.getTextValue(); - Pattern pattern = Pattern.compile("font-size:([^;]+)"); - Matcher matcher = pattern.matcher(value); - if (matcher.find()) { - String fontFamily = matcher.group(1).trim(); - judgement.setContentIn(wpsWordReqDto.getName() + chineseType + "-" +fontFamily); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + fontFamily + "-"+ headerVo.getType()); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - } - if (wpsWordReqDto.getName().contains("颜色")) { - headerXml.selectPath(nameSpace + "//v:shape/@fillcolor"); - if (headerXml.toNextSelection()) { - String value = headerXml.getTextValue(); - value = getValueType(wpsWordReqDto, value); - judgement.setContentIn(wpsWordReqDto.getName() + chineseType + "-" +value); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + value + "-"+ headerVo.getType()); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - if (wpsWordReqDto.getName().contains("透明度")) { - headerXml.selectPath(nameSpace + "//v:shape/v:fill/@opacity"); - if (headerXml.toNextSelection()) { - String value = headerXml.getTextValue(); - while (value.length() < 8) { - value = "0" + value; - } - long bits = Long.parseLong(value, 16); - float opacityFloat = Float.intBitsToFloat((int) bits); - // 转为百分比(例如 0.3 -> 30%) - float opacityPercent = opacityFloat * 100; - judgement.setContentIn(wpsWordReqDto.getName() + chineseType + "-" +opacityPercent); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + opacityPercent + "-"+ headerVo.getType()); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - if (wpsWordReqDto.getName().contains("版式")) { - headerXml.selectPath(nameSpace + "//v:shape/@style"); - if (headerXml.toNextSelection()) { - String value = headerXml.getTextValue(); - value = detectWatermarkLayout(value); - judgement.setContentIn(wpsWordReqDto.getName() + chineseType + "-" +value); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + value + "-"+ headerVo.getType()); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - } else { - headerXml.selectPath(nameSpace + "./" + wpsWordReqDto.getFunction().split("#")[1]); - if (headerXml.toNextSelection()) { - String value = headerXml.getTextValue(); - value = getValueType(wpsWordReqDto, value); - judgement.setContentIn(wpsWordReqDto.getName() + chineseType + "-" +value); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + value + "-"+ headerVo.getType()); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - - } - - } - } else if (headerVo.getXmlType().contains("footerReference")) { - for (XWPFFooter footer : footers) { - if (footer.getPackagePart().getPartName().getName().contains(fileName)) { - // 2-1、针对不同的进行排查 - String chineseType = Objects.equals(headerVo.getType(), "default") ? "奇数页" : Objects.equals(headerVo.getType(), "even") ? "偶数页" : ""; - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - List footerpar = footer.getParagraphs(); - XmlCursor footerXml = footerpar.get(0).getCTP().newCursor(); - if (wpsWordReqDto.getFunction().contains("w:page")) { - if (footerXml.xmlText().contains("PAGE")) { - judgement.setContentIn(wpsWordReqDto.getName() + chineseType + " 是"); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + "true" + "-"+ headerVo.getType()); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } else { - judgement.setContentIn(wpsWordReqDto.getName() + chineseType + " 否"); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + "false" + "-"+ headerVo.getType()); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } else { - footerXml.selectPath(nameSpace + "./" + wpsWordReqDto.getFunction().split("#")[1]); - if (footerXml.toNextSelection()) { - String value = footerXml.getTextValue(); - value = getValueType(wpsWordReqDto, value); - judgement.setContentIn(wpsWordReqDto.getName() + chineseType + "-"+value); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + value + "-"+ headerVo.getType()); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - - } - } - } - } - } - } - } else { - // 需要查询主页面的方法 - XmlCursor sectPrXsml = docXml.newCursor(); - if (wpsWordReqDto.getUnit().contains("twipstolines")) { - String[] docGrid = wpsWordReqDto.getFunction().split("#"); - XmlCursor docGridXsml = sectPrXsml; - docGridXsml.selectPath(nameSpace + docGrid[0]); - String docGridValues = ""; - String topValues = ""; - String bottomValues = ""; - String allValues = ""; - if (docGridXsml.toNextSelection()) { - docGridValues = docGridXsml.getTextValue(); - } - XmlCursor pgMgXsml = sectPrXsml; - pgMgXsml.selectPath(nameSpace + wpsWordReqDto.getEnglishName() + docGrid[1]); - if (pgMgXsml.toNextSelection()) { - topValues = pgMgXsml.getAttributeText(new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "top")); - bottomValues = pgMgXsml.getAttributeText(new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "bottom")); - } - XmlCursor allXsml = sectPrXsml; - pgMgXsml.selectPath(nameSpace + wpsWordReqDto.getEnglishName() + docGrid[2]); - if (allXsml.toNextSelection()) { - allValues = allXsml.getAttributeText(new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "h")); - } - Integer values = Integer.parseInt(allValues) - Integer.parseInt(bottomValues) - - Integer.parseInt(topValues); - Integer docGridValuesInt = values / Integer.parseInt(docGridValues); - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - judgement.setContentIn(wpsWordReqDto.getName() + docGridValuesInt); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + docGridValuesInt); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } else if (wpsWordReqDto.getUnit().contains("paper_shape")) { - sectPrXsml.selectPath(nameSpace + wpsWordReqDto.getFunction()); - // 纸型 - if (sectPrXsml.toNextSelection()) { - String text = sectPrXsml.xmlText(); - String value = PageSizeDetector.detectPaperSize(text); - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - judgement.setContentIn(wpsWordReqDto.getName() + value); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + value); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } else{ - sectPrXsml.selectPath(nameSpace + wpsWordReqDto.getFunction()); - if (sectPrXsml.toNextSelection()) { - String value = sectPrXsml.getTextValue(); - value = getValueType(wpsWordReqDto, value); - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - judgement.setContentIn(wpsWordReqDto.getName() + value); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + value); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - } - } - // 3、图形 - if ("2".equals(wpsWordReqDto.getBelongTo())) { - if (wpsWordReqDto.getName().contains("环绕方式")) { - XmlCursor wpCursor = cursor.newCursor(); - wpCursor.selectPath(nameSpace + wpsWordReqDto.getFunction()); { - if (wpCursor.toNextSelection()) { - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - String text = wpCursor.xmlText(); - String value = getDrawingType(text); - value = getValueType(wpsWordReqDto, value); - judgement.setContentIn(wpsWordReqDto.getName() + value.split("#")[1]); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + value.split("#")[0]); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - // 查询指定值,返回固定的文本 - judgementList.add(judgement); - } - } - } else if (wpsWordReqDto.getName().contains("形状填充") && wpsWordReqDto.getName().contains("填充方式")) { - XmlCursor wpCursor = cursor.newCursor(); - wpCursor.selectPath(nameSpace + wpsWordReqDto.getFunction()); - if (wpCursor.toNextSelection()) { - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - String text = wpCursor.xmlText(); - String value = getValueType(wpsWordReqDto, text); - value = getValueType(wpsWordReqDto, value); - judgement.setContentIn(wpsWordReqDto.getName() + value); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + value); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } else { - String function = ""; - if (wpsWordReqDto.getIsExam().equals("0")) { - String[] functionList = wpsWordReqDto.getFunction().split("#"); - if (wpsWordReqDto.getName().contains("图片")) { - function = functionList[0]; - } else { - function = wpsWordReqDto.getEnglishName() + functionList[1]; - } - } else { - function = wpsWordReqDto.getFunction(); - } - String chineseName = wpsWordReqDto.getName(); - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - // 1-1、创建新的数据组 - XmlCursor wpCursor = cursor.newCursor(); - System.out.println(nameSpace + function); - wpCursor.selectPath(nameSpace + function); - if (wpCursor.toNextSelection()) { - if ("1".equals(wpsWordReqDto.getIsboo())) { - judgement.setContentIn(wpsWordReqDto.getName() + "是"); - judgement.setContent(function + "-/" + "true"); - - } else { - String value = wpCursor.getTextValue(); - value = getValueType(wpsWordReqDto, value); - judgement.setContentIn(wpsWordReqDto.getName() + value); - judgement.setContent(function + "-/" + value); - } - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - // 查询指定值,返回固定的文本 - judgementList.add(judgement); - } else { - // 参数类型(0:值;1:判断) - if ("1".equals(wpsWordReqDto.getIsboo())) { - judgement.setContentIn(wpsWordReqDto.getName() + "false"); - judgement.setContent(function + "-/" + "false"); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - } - - } - // 4、尾注 - if ("3".equals(wpsWordReqDto.getBelongTo())) { - if ("0".equals(wpsWordReqDto.getType())) { - if (wpsWordReqDto.getName().contains("插入尾注")) { - // 判断是否存在 - XmlCursor wpCursor = cursor.newCursor(); - wpCursor.selectPath(nameSpace + "//w:endnoteReference/@w:id"); - if (wpCursor.toNextSelection()) { - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - judgement.setContentIn(wpsWordReqDto.getName() + " 是"); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + "true"); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - } else { - for (PackagePart part : pkg.getParts()) { - String entryName = part.getPartName().getName(); - if (entryName.contains("word/endnotes") && entryName.contains(".xml")) { - try (InputStream is = part.getInputStream()) { - String xmlContent = IOUtils.toString(is, StandardCharsets.UTF_8); - XmlObject xmlObject = XmlObject.Factory.parse(xmlContent); - XmlCursor cursorEnd = xmlObject.newCursor(); - String namespace = WpsWordNameSpaces.getNameSpace(cursorEnd.xmlText()); - if (wpsWordReqDto.getName().contains("内容")) { - System.out.println(cursorEnd.xmlText()); - String value = cursorEnd.getTextValue(); - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - judgement.setContentIn(wpsWordReqDto.getName() + value); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + value); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - } - } - } - } - // 表格属性 - if ("4".equals(wpsWordReqDto.getBelongTo())) { - XmlCursor tblCursor = cursor.newCursor(); - tblCursor.selectPath(nameSpace + wpsWordReqDto.getFunction()); - if (tblCursor.toNextSelection()) { - String value = tblCursor.getTextValue(); - value = getValueType(wpsWordReqDto, value); - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - judgement.setContentIn(wpsWordReqDto.getName() + value); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + value); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - // 5、域 - if ("5".equals(wpsWordReqDto.getBelongTo())) { - XmlCursor yuCursor = cursor.newCursor(); - yuCursor.selectPath(nameSpace + wpsWordReqDto.getFunction().replace("/chao","")); - if (yuCursor.toNextSelection()) { - if (wpsWordReqDto.getName().contains("域代码")) { - String value = yuCursor.getTextValue(); - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - judgement.setContentIn(wpsWordReqDto.getName() + value); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + value); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - if (wpsWordReqDto.getName().contains("域类型")) { - String value = yuCursor.getTextValue(); - if (value.contains("HYPERLINK")) { - value = "连接和引用->打开并跳到指定文件"; - } - WpsWordJudgementDto judgement = new WpsWordJudgementDto(); - judgement.setContentIn(wpsWordReqDto.getName() + value); - judgement.setContent(wpsWordReqDto.getFunction() + "-/" + value); - judgement.setImage(wpsWordReqDto.getType()+"-"+wpsWordReqDto.getBelongTo()+"-"+wpsWordReqDto.getIsboo()+"-"+wpsWordReqDto.getUnit()); - judgement.setScoreRate("1"); - judgementList.add(judgement); - } - } - } - } catch (IOException e) { - e.printStackTrace(); - } - } - return judgementList; - } - public static String getValueType(WpsWordReqDto wpsWordReqDto, String value) { - String values = value; - if (wpsWordReqDto.getUnit().contains("Filling_method")) { - if (value.contains("solidFill")) { - values = "纯色填充"; - } else if (value.contains("gradFill")) { - values = "渐变填充"; - } else if (value.contains("pattFill")) { - values = "图案填充"; - } else if (value.contains("blipFill")) { - values = "图片填充"; - } - } - if (wpsWordReqDto.getUnit().contains("twiptopt")) { - values = TwipConverter.formatDouble(TwipConverter.toPoints(Integer.parseInt(value))) + "磅"; - } - if (wpsWordReqDto.getUnit().contains("emustopt")) { - values = TwipConverter.formatDouble(TwipConverter.toEmus(Integer.parseInt(value))) + "磅"; - } - if (wpsWordReqDto.getUnit().contains("color")) { - values = ColorNameFinder.getColorName(value); - } - if (wpsWordReqDto.getUnit().contains("underline")) { - if ("double".equals(value)) { - values = "双下划线"; - } else if ("single".equals(value)) { - values = "单下划线"; - } else if ("none".equals(value)) { - values = "无下划线"; - } else if ("dotted".equals(value)) { - values = "点划线"; - } else if ("dottedHeavy".equals(value)) { - values = "粗点划线"; - } else if ("dash".equals(value)) { - values = "虚线"; - } else if ("dashedHeavy".equals(value)) { - values = "粗虚线"; - } else if ("dashLong".equals(value)) { - values = "长虚线"; - } - } - if (wpsWordReqDto.getUnit().contains("direction")) { - // 方向英文转换 - if ("landscape".equals(value)) { - values = "横向"; - } else if ("portrait".equals(value)) { - values = "纵向"; - } else if ("up".equals(value)) { - values = "上对齐"; - } else if ("down".equals(value)) { - values = "下对齐"; - } - } - if (value.contains("center")) { - values = "居中对齐"; - } - if (wpsWordReqDto.getUnit().contains("textdirection")) { - // 方向英文转换 - if ("left".equals(value)) { - values = "左对齐"; - } else if ("center".equals(value)) { - values = "居中对齐"; - } else if ("right".equals(value)) { - values = "右对齐"; - } else if ("both".equals(value)) { - values = "两端对齐"; - } - } - if (wpsWordReqDto.getUnit().contains("halfpt")) { - values = String.valueOf (Integer.parseInt(value) / 2); - } - if (wpsWordReqDto.getUnit().contains("shadowdirection")) { - ShadowDirectionUtils.ShadowDirection direction = ShadowDirectionUtils.getShadowDirection(Long.valueOf(value)); - values = direction.getLabel(); - } - if (wpsWordReqDto.getUnit().contains("ershifenzhiyi")) { - values = String.valueOf (Integer.parseInt(value) / 20); - } - if (wpsWordReqDto.getUnit().contains("duanluojianju")) { - if (Integer.parseInt(value) == 240) { - values = "1.5 倍行距"; - } else if (Integer.parseInt(value) == 360) { - values = "2 倍行距"; - } else if (Integer.parseInt(value) == 480) { - values = "3 倍行距"; - } - } - - return values; - } - - public static String detectWatermarkLayout(String style) { - if (style == null || !style.contains("rotation:")) { - return "水平"; - } - - try { - // 提取 rotation 的值 - String[] parts = style.split(";"); - for (String part : parts) { - part = part.trim(); - if (part.startsWith("rotation:")) { - String rotationStr = part.replace("rotation:", "").replace("f", "").trim(); - int rotationValue = Integer.parseInt(rotationStr); - - // 转换为角度(1度 = 65536) - int degrees = rotationValue / 65536; - - // 判断角度 - if (Math.abs(degrees) == 45) { - return "倾斜"; - } else { - return "水平"; - } - } - } - } catch (Exception e) { - return "解析出错:" + e.getMessage(); - } - - return "未知格式"; - } - - public static String getDrawingType(String text) { - String value =""; - if (text.contains("wp:wrapSquare")) { - value = "wp:wrapSquare" + "#" + "四周型环绕"; - } else if (text.contains("wp:wrapTight")) { - value = "wp:wrapTight" + "#" + "紧密型环绕"; - } else if (text.contains("wp:wrapThrough")) { - value = "wp:wrapThrough" + "#" + "穿越型环绕"; - } else if (text.contains("wp:wrapTopAndBottom")) { - value = "wp:wrapTopAndBottom" + "#" + "上下型环绕"; - } else if (text.contains("wp:wrapNone")) { - value = "wp:wrapNone" + "#" + "无环绕"; - } - return value; - } -} 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 new file mode 100644 index 0000000..a39687f --- /dev/null +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/DocxMaster.java @@ -0,0 +1,139 @@ +package com.example.exam.exam.service.wpsword.docx4j; + +import com.example.exam.exam.service.wpsword.docx4j.paragraph.Convert; +import com.example.exam.exam.service.wpsword.docx4j.paragraph.Paragraphs; +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.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.xmlbeans.XmlException; +import org.docx4j.XmlUtils; +import org.docx4j.model.structure.SectionWrapper; +import org.docx4j.openpackaging.exceptions.Docx4JException; +import org.docx4j.openpackaging.packages.WordprocessingMLPackage; +import org.docx4j.openpackaging.parts.WordprocessingML.DocumentSettingsPart; +import org.docx4j.openpackaging.parts.WordprocessingML.NumberingDefinitionsPart; +import org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart; +import org.docx4j.openpackaging.parts.WordprocessingML.WebSettingsPart; +import org.docx4j.wml.P; +import org.docx4j.wml.PPr; +import org.docx4j.wml.R; +import org.docx4j.wml.Text; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.mock.web.MockMultipartFile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * @author REN + */ +public class DocxMaster { + + /** + * + * @param wpsDocxInfoVos 考点参数 + * @param file 文件流 + * @return + * @throws Docx4JException + * @throws InvalidFormatException + * @throws IOException + * @throws JAXBException + * @throws XmlException + * @throws NoSuchMethodException + * @throws InvocationTargetException + * @throws IllegalAccessException + */ + public static List docxMaster(List wpsDocxInfoVos, MultipartFile file) throws Docx4JException, InvalidFormatException, IOException, JAXBException, XmlException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { + List judgementWordsVOS = new ArrayList<>(); + // 1、获取想要判断的文件地址(文件流) + InputStream inputStream = file.getInputStream(); + // 2、Docx转换对象 + WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(inputStream); + List paragraphs = wordMLPackage.getMainDocumentPart().getContent(); + // 2-1、样式对象 + StyleDefinitionsPart stylePart = wordMLPackage.getMainDocumentPart().getStyleDefinitionsPart(); + // 2-2、编号对象 + NumberingDefinitionsPart ndp = wordMLPackage.getMainDocumentPart().getNumberingDefinitionsPart(); + // 2-3、节对象 + List sections = wordMLPackage.getDocumentModel().getSections(); + // 2-3、Web 设置 + WebSettingsPart webSettingsPart = wordMLPackage.getMainDocumentPart().getWebSettingsPart(); + // 2-4、文档设置 + DocumentSettingsPart settingsPart = wordMLPackage.getMainDocumentPart().getDocumentSettingsPart(); + wordMLPackage.getHeaderFooterPolicy().getDefaultFooter(); + for (WpsDocxInfoVo wpsDocxInfoVo : wpsDocxInfoVos) { + // 参数实例化 + // 大类名称 + String firstName = wpsDocxInfoVo.getFirstName(); + // 序号 + String indexParm = wpsDocxInfoVo.getIndex(); + // 方法名称 + String function = wpsDocxInfoVo.getFunction(); + // 考点名称 + String examName = wpsDocxInfoVo.getExamName(); + // 考点代码 + String examCode = wpsDocxInfoVo.getExamCode(); + String docxFunction = firstName + "@" + indexParm + "@" + function + "@" + examName + "@" + examCode; + // 创建一个索引ID,跟参数中index进行匹配 + int index = 0; + for (Object obj : paragraphs) { + index ++; + Object realObj = XmlUtils.unwrap(obj); + if (realObj instanceof P) { + P paragraph = (P) realObj; + PPr pPr = paragraph.getPPr(); + if (pPr == null || pPr.getSectPr() == null) { + // 开始查询段落的属性,非句子的属性 + if (index == Integer.parseInt(indexParm)) { + // 查询出具体想要查询哪个段落的数据 + // 目标对象 + Paragraphs paragraphsFunction = new Paragraphs(); + // 获取参数中类型的定义 + Class[] paramTypes = {P.class, StyleDefinitionsPart.class, WordprocessingMLPackage.class, NumberingDefinitionsPart.class}; + // 带参数的方法调用示例 + Method methodWithArgs = paragraphsFunction.getClass().getMethod(function, paramTypes); + // 实际参数值 + Object[] arguments = {paragraph, stylePart, wordMLPackage, ndp}; + String value = (String) methodWithArgs.invoke(obj, arguments); + judgementWordsVOS = setJudgementWord(judgementWordsVOS, docxFunction + "@" + value, firstName + examName + value); + } + } + } + } + } + return judgementWordsVOS; + } + + private static List setJudgementWord(List judgementWordsVOList, String content, String contentIn) { + JudgementWordsVO judgementWordsVO = new JudgementWordsVO(); + judgementWordsVO.setContent(content); + judgementWordsVO.setContentIn(contentIn); + judgementWordsVOList.add(judgementWordsVO); + return judgementWordsVOList; + } + + + // 提取 run 中的文本内容 + private static String getRunText(R run) { + StringBuilder sb = new StringBuilder(); + for (Object content : run.getContent()) { + if (content instanceof Text) { + sb.append(((Text) content).getValue()); + } else if (content instanceof JAXBElement) { + Object val = ((JAXBElement) content).getValue(); + if (val instanceof Text) { + sb.append(((Text) val).getValue()); + } + } + } + return sb.toString(); + } +} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/DocxSetInfo.java b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/DocxSetInfo.java new file mode 100644 index 0000000..d81aef5 --- /dev/null +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/DocxSetInfo.java @@ -0,0 +1,20 @@ +package com.example.exam.exam.service.wpsword.docx4j; + + +import com.example.exam.exam.service.wpsword.docx4j.vo.JudgementWordsVO; + +import java.util.List; + +/** + * @author REN + */ +public class DocxSetInfo { + public static List setInfo(List judgementWordsVOS, String id, String parentId, String contentIn, String content, String name) { + JudgementWordsVO judgementWordsVO = new JudgementWordsVO(); + judgementWordsVO.setContentIn(contentIn); + judgementWordsVO.setContent(content); + judgementWordsVOS.add(judgementWordsVO); + return judgementWordsVOS; + } + +} 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 new file mode 100644 index 0000000..471d4c9 --- /dev/null +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/drawing/Drawing.java @@ -0,0 +1,224 @@ +package com.example.exam.exam.service.wpsword.docx4j.drawing; + +import com.example.exam.exam.service.wpsword.docx4j.DocxSetInfo; +import com.example.exam.exam.service.wpsword.docx4j.paragraph.Convert; +import com.example.exam.exam.service.wpsword.docx4j.vo.JudgementWordsVO; +import org.docx4j.dml.CTLineProperties; +import org.docx4j.dml.CTOuterShadowEffect; +import org.docx4j.dml.CTShapeProperties; +import org.docx4j.dml.chartDrawing.CTPicture; +import org.docx4j.dml.wordprocessingDrawing.Anchor; + +import java.lang.reflect.Method; +import java.util.List; + +public class Drawing { + + // 布局 + public static List getLayout(List judgementWordsVOS, Anchor anchor, int betoLong, String firstId){ + String name = "【第" + betoLong + "个图形】【图片】"; + String parentId = Convert.getStringRandom(); + String parName = "【布局】【锁定横纵比】"; + String layoutHuanRaoName = "【布局】【环绕方式】"; + // 环绕方式 + String layoutHuanRaoValue = anchor.getWrapSquare().getWrapText().value(); + layoutHuanRaoValue = getLayoutHuanRaoName(layoutHuanRaoValue); + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + layoutHuanRaoName + layoutHuanRaoValue, name + layoutHuanRaoName + layoutHuanRaoValue, name); + + // 锁定横纵比 + boolean lock = anchor.getCNvGraphicFramePr().getGraphicFrameLocks().isNoChangeAspect(); + + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + parName + lock, name + parName + lock, name); + + // 高度 + String heightName = "【布局】【高度】"; + String height = anchor.getExtent().getCy() / 360000 + "cm"; + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + heightName + height, name + heightName + height, name); + + // 宽度 + String weightName = "【布局】【宽度】"; + String weight = anchor.getExtent().getCx() / 360000 + "cm"; + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + weightName + weight, name + weightName + weight, name); + + return judgementWordsVOS; + } + + // 形状和样式 + public static List getStyles(List judgementWordsVOS, Anchor anchor, int betoLong, String firstId){ + String name = "【第" + betoLong + "个图形】【图片】"; + String parName = "【形状和样式】【自选图形->形状】"; + CTPicture picture = (CTPicture) anchor.getGraphic().getGraphicData().getAny().get(0); + // 获取形状属性 + CTShapeProperties shapeProps = picture.getSpPr(); + String parentId = Convert.getStringRandom(); + if (shapeProps != null) { + // 获取形状几何属性 + if (shapeProps.getPrstGeom() != null) { + String prst = shapeProps.getPrstGeom().getPrst().value(); + prst = getShapeName(prst); + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + parName + prst, name + parName + prst, name); + } + } + return judgementWordsVOS; + } + + // 形状轮廓 + public static List getShapeProfile(List judgementWordsVOS, Anchor anchor, int betoLong, String firstId){ + String name = "【第" + betoLong + "个图形】【图片】"; + String firstName = "【形状轮廓】"; + String huiZhiName = firstName + "【绘制】"; + String yanSeName = firstName + "【颜色】"; + String kuanDuName = firstName + "【宽度(粗细)】"; + String xianXingName = firstName + "【线型(复合类型)】"; + String xuXianName = firstName + "【虚线(短划线类型)】"; + String touMingName = firstName + "【透明度】"; + String parentId = Convert.getStringRandom(); + CTPicture picture = (CTPicture) anchor.getGraphic().getGraphicData().getAny().get(0); + + // 绘制 + if (picture.getSpPr() == null || picture.getSpPr().getLn() == null) { + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + huiZhiName + "是", name + huiZhiName + "是", name); + + // 宽度 + CTLineProperties outline = picture.getSpPr().getLn(); + // 轮廓宽度(以EMU为单位,1厘米=360000 EMU) + long widthEMU = outline.getW(); + String widthValue =widthEMU / 360000.0 +" 厘米"; + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + kuanDuName + widthValue, name + kuanDuName + widthValue, name); + // 颜色 + if (outline.getSolidFill() != null) { + if (outline.getSolidFill().getSrgbClr() != null) { + // RGB颜色 + String colorValue = outline.getSolidFill().getSrgbClr().getVal(); + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + yanSeName + colorValue, name + yanSeName + colorValue, name); + } else if (outline.getSolidFill().getSchemeClr() != null) { + // 主题颜色 + String colorValue = outline.getSolidFill().getSchemeClr().getVal().value(); + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + yanSeName + colorValue, name + yanSeName + colorValue, name); + } + } + // 轮廓样式 + if (outline.getPrstDash() != null) { + String val = outline.getPrstDash().getVal().value(); + val = getDashStyleName(val); + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + xianXingName + val, name + xianXingName + val, name); + } + } else { + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + huiZhiName + "否", name + huiZhiName + "否", name); + } + return judgementWordsVOS; + } + + // 形状效果(阴影) + public static List getShapeYinYing(List judgementWordsVOS, Anchor anchor, int betoLong, String firstId){ + String name = "【第" + betoLong + "个图形】【图片】"; + String firstName = "【形状效果(阴影)】"; + String huiZhiName = firstName + "【绘制】"; + String yanSeName = firstName + "【颜色】"; + String touMingDuName = firstName + "【透明度】"; + String daXiaoName = firstName + "【大小】"; + String moHuName = firstName + "【模糊】"; + String parentId = Convert.getStringRandom(); + CTPicture picture = (CTPicture) anchor.getGraphic().getGraphicData().getAny().get(0); + if (picture.getSpPr() == null || picture.getSpPr().getEffectLst() == null) { + // 没有设置效果 + CTOuterShadowEffect shadow = picture.getSpPr().getEffectLst().getOuterShdw(); + if (shadow == null) { + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + huiZhiName + "否", name + huiZhiName + "否", name); + } + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + huiZhiName + "是", name + huiZhiName + "是", name); + if (shadow.getPrstClr() != null) { + String colorVal = shadow.getPrstClr().getVal().value(); + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + yanSeName + colorVal, name + yanSeName + colorVal, name); + + // 尝试通过颜色变换获取透明度 + try { + Object egColorTransform = shadow.getPrstClr().getEGColorTransform(); + if (egColorTransform != null) { + // 使用反射检查是否存在alpha属性 + Method getAlphaMethod = egColorTransform.getClass().getMethod("getAlpha"); + if (getAlphaMethod != null) { + Object alpha = getAlphaMethod.invoke(egColorTransform); + if (alpha != null) { + String value = alpha.toString(); + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + touMingDuName + value, name + touMingDuName + value, name); + } + } + } + } catch (Exception e) { + System.out.println("无法获取透明度信息"); + } + } else if (shadow.getSrgbClr() != null) { + String colorVal = shadow.getSrgbClr().getVal(); + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + yanSeName + colorVal, name + yanSeName + colorVal, name); + // 尝试通过颜色变换获取透明度 + try { + Object egColorTransform = shadow.getPrstClr().getEGColorTransform(); + if (egColorTransform != null) { + // 使用反射检查是否存在alpha属性 + Method getAlphaMethod = egColorTransform.getClass().getMethod("getAlpha"); + if (getAlphaMethod != null) { + Object alpha = getAlphaMethod.invoke(egColorTransform); + if (alpha != null) { + String value = alpha.toString(); + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + touMingDuName + value, name + touMingDuName + value, name); + } + } + } + } catch (Exception e) { + System.out.println("无法获取透明度信息"); + } + } + System.out.printf("模糊半径: %.2f cm\n", shadow.getBlurRad()/360000.0); + System.out.printf("阴影距离: %.2f cm\n", shadow.getDist()/360000.0); + System.out.printf("阴影方向: %.1f°\n", shadow.getDir()/60000.0); + System.out.printf("水平缩放: %.1f%%\n", shadow.getSx()/1000.0); + System.out.printf("垂直缩放: %.1f%%\n", shadow.getSy()/1000.0); + System.out.printf("水平倾斜: %.1f°\n", shadow.getKx()/60000.0); + System.out.printf("垂直倾斜: %.1f°\n", shadow.getKy()/60000.0); + } + return judgementWordsVOS; + } + + + private static String getShapeName(String shapeType) { + switch (shapeType) { + case "rect": return "矩形"; + case "roundRect": return "圆角矩形"; + case "ellipse": return "椭圆/圆形"; + case "triangle": return "三角形"; + case "diamond": return "菱形"; + case "hexagon": return "六边形"; + case "cloud": return "云形"; + case "star": return "星形"; + default: return shapeType; + } + } + + private static String getDashStyleName(String val) { + switch(val) { + case "solid": return "实线"; + case "dot": return "点线"; + case "dash": return "虚线"; + case "lgDash": return "长虚线"; + case "dashDot": return "点划线"; + case "lgDashDot": return "长点划线"; + case "sysDash": return "系统虚线"; + case "sysDot": return "系统点线"; + case "sysDashDot": return "系统点划线"; + default: return val; + } + } + + private static String getLayoutHuanRaoName(String wrapText) { + if (wrapText == null) return "两侧"; + + switch (wrapText) { + case "bothSides": return "两侧"; + case "left": return "只在左侧"; + case "right": return "只在右侧"; + case "largest": return "最大边"; + default: return "无"; + } + } +} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/paragraph/Convert.java b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/paragraph/Convert.java new file mode 100644 index 0000000..1caa317 --- /dev/null +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/paragraph/Convert.java @@ -0,0 +1,84 @@ +package com.example.exam.exam.service.wpsword.docx4j.paragraph; + +import org.docx4j.openpackaging.parts.WordprocessingML.NumberingDefinitionsPart; +import org.docx4j.wml.*; + +import java.math.BigInteger; +import java.util.List; +import java.util.Random; + +public class Convert { + public static String getStringRandom() { + String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + Random random = new Random(); + StringBuilder sb = new StringBuilder(); + + // 生成指定长度的随机字符字符串 + for (int i = 0; i < 10; i++) { + int randomIndex = random.nextInt(characters.length()); + // 随机字符 + sb.append(characters.charAt(randomIndex)); + } + return sb.toString(); + } + public static String convertJc(String val) { + switch (val) { + case "left": return "左对齐"; + case "center": return "居中对齐"; + case "right": return "右对齐"; + case "both": return "两端对齐"; + case "distribute": return "分散对齐"; + default: return "未知对齐方式(" + val + ")"; + } + } + + public static String convertOutlineLvl(String val) { + switch (val) { + case "0": + return "标题1"; + case "1": + return "标题2"; + default: + return null; + } + } + + public static Numbering.AbstractNum getAbstractNumById(NumberingDefinitionsPart ndp, BigInteger abstractNumId) { + List abstractNums = ndp.getJaxbElement().getAbstractNum(); + for (Numbering.AbstractNum abs : abstractNums) { + if (abs.getAbstractNumId().equals(abstractNumId)) { + return abs; + } + } + return null; + } + + public static void printBorder(String name, CTBorder ctBorder) { + if (ctBorder != null) { + System.out.println(name + " 样式: " + ctBorder.getVal()); // eg: single, double, dashed + System.out.println(name + " 颜色: #" + ctBorder.getColor()); // hex color + System.out.println(name + " 宽度: " + ctBorder.getSz() + " 八分之一磅"); // 单位是1/8 pt + System.out.println(name + " 间距: " + ctBorder.getSpace()); // twips之间距(可选) + } + } + + public static String getFirstRunFont(P paragraph) { + List runs = paragraph.getContent(); + for (Object runObj : runs) { + if (runObj instanceof R) { + R run = (R) runObj; + RPr rPr = run.getRPr(); + if (rPr != null && rPr.getRFonts() != null) { + RFonts fonts = rPr.getRFonts(); + if (fonts.getAscii() != null) { + return fonts.getAscii(); + } else if (fonts.getHAnsi() != null) { + return fonts.getHAnsi(); + } + } + break; // 只取第一个文字 Run + } + } + return null; + } +} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/paragraph/Paragraphs.java b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/paragraph/Paragraphs.java new file mode 100644 index 0000000..0d3a914 --- /dev/null +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/paragraph/Paragraphs.java @@ -0,0 +1,771 @@ +package com.example.exam.exam.service.wpsword.docx4j.paragraph; + +import jakarta.xml.bind.JAXBElement; +import org.docx4j.openpackaging.packages.WordprocessingMLPackage; +import org.docx4j.openpackaging.parts.WordprocessingML.NumberingDefinitionsPart; +import org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart; +import org.docx4j.wml.*; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTextAlignment; + +import java.math.BigInteger; + +/** + * @author REN + */ +public class Paragraphs { + /** + * 段落-对齐方式 + * @param paragraph + * @param stylePart + * @return + */ + public static String getParagraphAlignment(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + // 先查段落自身 + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getJc() != null && pPr.getJc().getVal() != null) { + return Convert.convertJc(pPr.getJc().getVal().value()); + } + // 若段落未定义对齐方式,则尝试从样式中继承 + if (pPr != null && pPr.getPStyle() != null && stylePart != null) { + String styleId = pPr.getPStyle().getVal(); + Style style = stylePart.getStyleById(styleId); + if (style != null && style.getPPr() != null && style.getPPr().getJc() != null) { + return Convert.convertJc(style.getPPr().getJc().getVal().value()); + } + } + // 都没有设置,返回默认 + return ""; + } + + // 段落-大纲级别 + public static String getParagraphOutlineLvl(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + // 先查询自身段落数据 + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getOutlineLvl() != null && pPr.getOutlineLvl().getVal() != null) { + return Convert.convertOutlineLvl(pPr.getOutlineLvl().getVal().toString()); + } + // 没有设置,从公共方法中获取参数 + if (pPr != null && pPr.getPStyle() != null && stylePart != null) { + String styleId = pPr.getPStyle().getVal(); + Style style = stylePart.getStyleById(styleId); + if (style != null && style.getPPr() != null && style.getPPr().getJc() != null) { + return Convert.convertJc(style.getPPr().getOutlineLvl().getVal().toString()); + } + } + return "正文"; + } + /// 段落格式(缩进) 左缩进 磅 + public static String getParagraphIndLeft(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + PPrBase.Ind ind = pPr.getInd(); + if (ind != null) { + if (ind.getLeft() != null) { + return ind.getLeft().intValue() + " 磅"; + } + } + if (pPr != null && pPr.getPStyle() != null && stylePart != null) { + String styleId = pPr.getPStyle().getVal(); + Style style = stylePart.getStyleById(styleId); + if (style != null && style.getPPr() != null && style.getPPr().getInd() != null) { + PPrBase.Ind indStyle = style.getPPr().getInd(); + if (indStyle.getLeft() != null) { + return indStyle.getLeft().intValue() + " 磅"; + } + } + } + return ""; + } + /// 段落格式(缩进) 右缩进 磅 + public static String getParagraphIndRight(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + PPrBase.Ind ind = pPr.getInd(); + if (ind != null) { + if (ind.getRight() != null) { + return ind.getRight().intValue() + " 磅"; + } + } + if (pPr != null && pPr.getPStyle() != null && stylePart != null) { + String styleId = pPr.getPStyle().getVal(); + Style style = stylePart.getStyleById(styleId); + if (style != null && style.getPPr() != null && style.getPPr().getInd() != null) { + PPrBase.Ind indStyle = style.getPPr().getInd(); + if (indStyle.getRight() != null) { + return indStyle.getRight().intValue() + " 磅"; + } + } + } + return ""; + } + /// 段落格式(缩进) 首行缩进 磅 + public static String getParagraphIndFirstLine(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + PPrBase.Ind ind = pPr.getInd(); + if (ind != null) { + if (ind.getFirstLine() != null) { + return ind.getFirstLine().intValue() + " 磅"; + } + } + if (pPr != null && pPr.getPStyle() != null && stylePart != null) { + String styleId = pPr.getPStyle().getVal(); + Style style = stylePart.getStyleById(styleId); + if (style != null && style.getPPr() != null && style.getPPr().getInd() != null) { + PPrBase.Ind indStyle = style.getPPr().getInd(); + if (indStyle.getFirstLine() != null) { + return indStyle.getFirstLine().intValue() + " 磅"; + } + } + } + return ""; + } + /// 段落格式(缩进) 悬挂缩进 磅 + public static String getParagraphIndHanging(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + PPrBase.Ind ind = pPr.getInd(); + if (ind != null) { + if (ind.getHanging() != null) { + return ind.getHanging().intValue() + " 磅"; + } + } + if (pPr != null && pPr.getPStyle() != null && stylePart != null) { + String styleId = pPr.getPStyle().getVal(); + Style style = stylePart.getStyleById(styleId); + if (style != null && style.getPPr() != null && style.getPPr().getInd() != null) { + PPrBase.Ind indStyle = style.getPPr().getInd(); + if (indStyle.getHanging() != null) { + return indStyle.getHanging().intValue() + " 磅"; + } + } + } + return ""; + } + + /// 段落格式(间距) 段前行 + public static String getParagraphSpacingBeforeLines(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getSpacing() != null) { + return pPr.getSpacing().getBeforeLines().toString(); + } + if (pPr != null && pPr.getPStyle() != null && stylePart != null) { + String styleId = pPr.getPStyle().getVal(); + Style style = stylePart.getStyleById(styleId); + if (style != null && style.getPPr() != null && style.getPPr().getSpacing() != null) { + PPrBase.Spacing spacing = style.getPPr().getSpacing(); + return spacing.getBeforeLines().toString(); + } + } + return ""; + } + /// 段落格式(间距) 段前磅 + public static String getParagraphSpacingBefore(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getSpacing() != null) { + return pPr.getSpacing().getBefore().toString(); + } + if (pPr != null && pPr.getPStyle() != null && stylePart != null) { + String styleId = pPr.getPStyle().getVal(); + Style style = stylePart.getStyleById(styleId); + if (style != null && style.getPPr() != null && style.getPPr().getSpacing() != null) { + PPrBase.Spacing spacing = style.getPPr().getSpacing(); + return spacing.getBefore().toString(); + } + } + return ""; + } + /// 段落格式(间距) 段后磅 + public static String getParagraphSpacingAfter(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getSpacing() != null) { + return pPr.getSpacing().getAfter().toString(); + } + if (pPr != null && pPr.getPStyle() != null && stylePart != null) { + String styleId = pPr.getPStyle().getVal(); + Style style = stylePart.getStyleById(styleId); + if (style != null && style.getPPr() != null && style.getPPr().getSpacing() != null) { + PPrBase.Spacing spacing = style.getPPr().getSpacing(); + return spacing.getAfter().toString(); + } + } + return ""; + } + + /// 段落格式(间距) 段后行 + public static String getParagraphSpacingAfterLines(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getSpacing() != null) { + return pPr.getSpacing().getAfterLines().toString(); + } + if (pPr != null && pPr.getPStyle() != null && stylePart != null) { + String styleId = pPr.getPStyle().getVal(); + Style style = stylePart.getStyleById(styleId); + if (style != null && style.getPPr() != null && style.getPPr().getSpacing() != null) { + PPrBase.Spacing spacing = style.getPPr().getSpacing(); + return spacing.getAfterLines().toString(); + } + } + return ""; + } + + /// 段落格式(间距) 间距 + public static String getParagraphSpacingLine(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getSpacing() != null) { + return pPr.getSpacing().getLine().toString(); + } + if (pPr != null && pPr.getPStyle() != null && stylePart != null) { + String styleId = pPr.getPStyle().getVal(); + Style style = stylePart.getStyleById(styleId); + if (style != null && style.getPPr() != null && style.getPPr().getSpacing() != null) { + PPrBase.Spacing spacing = style.getPPr().getSpacing(); + return spacing.getLine().toString(); + } + } + return ""; + } + + /// 段落格式(间距) 间距值 + public static String getParagraphSpacingLines(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getSpacing() != null) { + return pPr.getSpacing().getLine().toString(); + } + if (pPr != null && pPr.getPStyle() != null && stylePart != null) { + String styleId = pPr.getPStyle().getVal(); + Style style = stylePart.getStyleById(styleId); + if (style != null && style.getPPr() != null && style.getPPr().getSpacing() != null) { + PPrBase.Spacing spacing = style.getPPr().getSpacing(); + return spacing.getLine().toString(); + } + } + return ""; + } + + /// 段落 编号列表 存在 + public static String getParagraphListIsTrue(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getNumPr() != null && pPr.getNumPr().getNumId() != null) { + return "存在"; + } else { + return "不存在"; + } + } + /// 段落 编号列表 列表级别 + public static String getParagraphListIlvl(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getNumPr() != null && pPr.getNumPr().getNumId() != null) { + PPrBase.NumPr numPr = pPr.getNumPr(); + BigInteger ilvl = numPr.getIlvl() != null ? numPr.getIlvl().getVal() : null; + return (ilvl != null ? String.valueOf(ilvl.intValue()) : "未知"); + } else { + return "未知"; + } + } + /// 段落 编号列表 编号样式 + public static String getParagraphListAbstractNumId(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getNumPr() != null && pPr.getNumPr().getNumId() != null) { + PPrBase.NumPr numPr = pPr.getNumPr(); + BigInteger numId = numPr.getNumId() != null ? numPr.getNumId().getVal() : null; + if (numId != null && ndp != null) { + Numbering.Num num = ndp.getJaxbElement().getNum().stream() + .filter(n -> n.getNumId().equals(numId)) + .findFirst().orElse(null); + + if (num != null) { + BigInteger abstractNumId = num.getAbstractNumId().getVal(); + Numbering.AbstractNum absNum = Convert.getAbstractNumById(ndp, abstractNumId); + BigInteger ilvl = numPr.getIlvl() != null ? numPr.getIlvl().getVal() : null; + if (absNum != null && ilvl != null) { + Lvl lvl = absNum.getLvl().stream() + .filter(l -> l.getIlvl().equals(ilvl)) + .findFirst().orElse(null); + + if (lvl != null) { + return lvl.getNumFmt().getVal().toString(); + + } + } + } + } + } + return "未知"; + } + /// 段落 编号列表 编号格式 + public static String getParagraphListLvlText(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getNumPr() != null && pPr.getNumPr().getNumId() != null) { + PPrBase.NumPr numPr = pPr.getNumPr(); + BigInteger numId = numPr.getNumId() != null ? numPr.getNumId().getVal() : null; + BigInteger ilvl = numPr.getIlvl() != null ? numPr.getIlvl().getVal() : null; + if (numId != null && ndp != null) { + Numbering.Num num = ndp.getJaxbElement().getNum().stream() + .filter(n -> n.getNumId().equals(numId)) + .findFirst().orElse(null); + + if (num != null) { + BigInteger abstractNumId = num.getAbstractNumId().getVal(); + Numbering.AbstractNum absNum = Convert.getAbstractNumById(ndp, abstractNumId); + if (absNum != null && ilvl != null) { + Lvl lvl = absNum.getLvl().stream() + .filter(l -> l.getIlvl().equals(ilvl)) + .findFirst().orElse(null); + + if (lvl != null) { + // 获取对应的编号格式 + return lvl.getLvlText().getVal(); + } + } + } + } + } + return "未知"; + } + /// 段落 编号列表 项目符号 + public static String getParagraphListLvlFuHao(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getNumPr() != null && pPr.getNumPr().getNumId() != null) { + PPrBase.NumPr numPr = pPr.getNumPr(); + BigInteger numId = numPr.getNumId() != null ? numPr.getNumId().getVal() : null; + BigInteger ilvl = numPr.getIlvl() != null ? numPr.getIlvl().getVal() : null; + if (numId != null && ndp != null) { + Numbering.Num num = ndp.getJaxbElement().getNum().stream() + .filter(n -> n.getNumId().equals(numId)) + .findFirst().orElse(null); + if (num != null) { + BigInteger abstractNumId = num.getAbstractNumId().getVal(); + Numbering.AbstractNum absNum = Convert.getAbstractNumById(ndp, abstractNumId); + if (absNum != null && ilvl != null) { + Lvl lvl = absNum.getLvl().stream() + .filter(l -> l.getIlvl().equals(ilvl)) + .findFirst().orElse(null); + if (lvl != null) { + if ("bullet".equals(lvl.getNumFmt().getVal().value())) { + return lvl.getLvlText().getVal(); + } + } + } + } + } + } + return "未知"; + } + /// 段落 编号列表 列表类型 + public static String getParagraphListLvlType(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getNumPr() != null && pPr.getNumPr().getNumId() != null) { + PPrBase.NumPr numPr = pPr.getNumPr(); + BigInteger numId = numPr.getNumId() != null ? numPr.getNumId().getVal() : null; + BigInteger ilvl = numPr.getIlvl() != null ? numPr.getIlvl().getVal() : null; + if (numId != null && ndp != null) { + Numbering.Num num = ndp.getJaxbElement().getNum().stream() + .filter(n -> n.getNumId().equals(numId)) + .findFirst().orElse(null); + if (num != null) { + BigInteger abstractNumId = num.getAbstractNumId().getVal(); + Numbering.AbstractNum absNum = Convert.getAbstractNumById(ndp, abstractNumId); + + if (absNum != null && ilvl != null) { + Lvl lvl = absNum.getLvl().stream() + .filter(l -> l.getIlvl().equals(ilvl)) + .findFirst().orElse(null); + if (lvl != null) { + if ("bullet".equals(lvl.getNumFmt().getVal().value())) { + return "无序列表"; + } else { + return "有序列表"; + + } + } + } + } + } + } + return "未知"; + } + + // 段落边框 样式 + public static String getParagraphBorderStyle(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + // 先查询自身段落数据 + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getPBdr() != null) { + PPrBase.PBdr border = pPr.getPBdr(); + // 上边框 + CTBorder top = border.getTop(); + // 下边框 + CTBorder bottom = border.getBottom(); + // 左边框 + CTBorder left = border.getLeft(); + // 右边框 + CTBorder right = border.getRight(); + // 判断上下左右边框是否一致 + if (top.getVal().value().equals(bottom.getVal().value()) && top.getVal().value().equals(left.getVal().value()) && top.getVal().value().equals(right.getVal().value())) { + // 说明样式一样 + return top.getVal().value(); + } else { + // 说明样式不一样 + return top.getVal().value() + "-" + bottom.getVal().value() + "-" + left.getVal().value() + "-" + right.getVal().value(); + } + } + return "未知"; + } + + // 段落边框 - 颜色 + public static String getParagraphBorderColor(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + // 先查询自身段落数据 + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getPBdr() != null) { + PPrBase.PBdr border = pPr.getPBdr(); + // 上边框 + CTBorder top = border.getTop(); + // 下边框 + CTBorder bottom = border.getBottom(); + // 左边框 + CTBorder left = border.getLeft(); + // 右边框 + CTBorder right = border.getRight(); + // 判断上下左右边框是否一致 + if (top.getColor().equals(bottom.getColor()) && top.getColor().equals(left.getColor()) && top.getColor().equals(right.getColor())) { + // 说明样式一样 + return top.getColor(); + } else { + // 说明样式不一样 + return top.getColor() + "-" + bottom.getColor() + "-" + left.getColor() + "-" + right.getColor(); + } + } + return "未知"; + } + + // 段落边框 - 宽度 + public static String getParagraphBorderSize(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + // 先查询自身段落数据 + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getPBdr() != null) { + PPrBase.PBdr border = pPr.getPBdr(); + // 上边框 + CTBorder top = border.getTop(); + // 下边框 + CTBorder bottom = border.getBottom(); + // 左边框 + CTBorder left = border.getLeft(); + // 右边框 + CTBorder right = border.getRight(); + // 判断上下左右边框是否一致 + if (top.getSz().toString().equals(bottom.getSz().toString()) && top.getSz().toString().equals(left.getSz().toString()) && top.getSz().toString().equals(right.getSz().toString())) { + // 说明样式一样 + return top.getSz().toString(); + } else { + // 说明样式不一样 + return top.getSz().toString() + "-" + bottom.getSz().toString() + "-" + left.getSz().toString() + "-" + right.getSz().toString(); + } + } + return "未知"; + } + + // 段落底纹 - 填充颜色 + public static String getParagraphShdFillColor(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + // 先查询自身段落数据 + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getShd() != null) { + CTShd shd = pPr.getShd(); + return shd.getFill(); + } + return "未知"; + } + + // 段落底纹 - 图案样式 + public static String getParagraphShdStyle(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + // 先查询自身段落数据 + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getShd() != null) { + CTShd shd = pPr.getShd(); + return shd.getVal().value(); + } + return "未知"; + } + + // 段落底纹 - 图案颜色 + public static String getParagraphShdStyleColor(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + // 先查询自身段落数据 + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getShd() != null) { + CTShd shd = pPr.getShd(); + return shd.getColor(); + } + return "未知"; + } + + // 首字下沉 - 是否存在 + public static String getParagraphDropCapIsTrue(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getFramePr() != null) { + return "是"; + } else { + return "否"; + } + } + + + // 首字下沉 - 首字位置 + public static String getParagraphDropCapLocation(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getFramePr() != null) { + CTFramePr framePr = pPr.getFramePr(); + if (framePr.getDropCap() != null) { + return framePr.getDropCap().value(); // drop | none | margin + } + } + return "未知"; + } + + // 首字下沉 - 行数 + public static String getParagraphDropCapLine(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getFramePr() != null) { + CTFramePr framePr = pPr.getFramePr(); + if (framePr.getLines() != null) { + return String.valueOf(framePr.getLines().intValue()); // twips + } + } + return "未知"; + } + + // 首字下沉 - 距正文 + public static String getParagraphDropCapHSpace(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getFramePr() != null) { + CTFramePr framePr = pPr.getFramePr(); + if (framePr.getHSpace() != null) { + return String.valueOf(framePr.getHSpace().intValue()); // twips + } + } + return "未知"; + } + + // 首字下沉 - 字体 + public static String getParagraphDropCapFont(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getFramePr() != null) { + // 获取首字字体 + String fontName = Convert.getFirstRunFont(paragraph); + if (fontName != null) { + return fontName; + } + } + return "未知"; + } + + // 分栏 - 栏数 +// public static String getParagraphCols(P paragraph, StyleDefinitionsPart stylePart) { +// PPr pPr = paragraph.getPPr(); +// SectPr sectPr = null; +// sectPr = pPr.getSectPr(); +// // 优先取段落自身的节属性 +// if (pPr != null && pPr.getSectPr() != null) { +// sectPr = pPr.getSectPr(); +// } +// // 或最后一个段落中会有一个节结束的SectPr +// if (sectPr == null && paragraph.getContent().size() > 0) { +// Object lastObj = paragraph.getContent().get(paragraph.getContent().size() - 1); +// if (lastObj instanceof JAXBElement && ((JAXBElement) lastObj).getValue() instanceof SectPr) { +// sectPr = (SectPr) ((JAXBElement) lastObj).getValue(); +// } +// } +// if (sectPr != null && sectPr.getCols() != null) { +// CTColumns cols = sectPr.getCols(); +// return String.valueOf(cols.getNum().intValue()); +// } +// +// return "未知"; +// } + + public static String getParagraphCols(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + // 1. 首先检查段落自身的节属性 + if (paragraph.getPPr() != null && paragraph.getPPr().getSectPr() != null) { + CTColumns cols = paragraph.getPPr().getSectPr().getCols(); + if (cols != null && cols.getNum() != null) { + return String.valueOf(cols.getNum().intValue()); + } + } + + // 2. 检查段落内容中是否有节属性 + for (Object content : paragraph.getContent()) { + if (content instanceof JAXBElement) { + Object value = ((JAXBElement) content).getValue(); + if (value instanceof SectPr) { + CTColumns cols = ((SectPr) value).getCols(); + if (cols != null && cols.getNum() != null) { + return String.valueOf(cols.getNum().intValue()); + } + } + } + } + + // 3. 向后查找后续段落中的节属性 + Body body = wordMLPackage.getMainDocumentPart().getJaxbElement().getBody(); + boolean foundCurrent = false; + + for (Object obj : body.getContent()) { + if (obj instanceof P) { + P p = (P) obj; + + // 找到当前段落后才开始查找 + if (p == paragraph) { + foundCurrent = true; + continue; + } + + if (foundCurrent) { + // 检查后续段落的节属性 + if (p.getPPr() != null && p.getPPr().getSectPr() != null) { + CTColumns cols = p.getPPr().getSectPr().getCols(); + if (cols != null && cols.getNum() != null) { + return String.valueOf(cols.getNum().intValue()); + } + } + + // 检查后续段落内容中的节属性 + for (Object content : p.getContent()) { + if (content instanceof JAXBElement) { + Object value = ((JAXBElement) content).getValue(); + if (value instanceof SectPr) { + CTColumns cols = ((SectPr) value).getCols(); + if (cols != null && cols.getNum() != null) { + return String.valueOf(cols.getNum().intValue()); + } + } + } + } + } + } + } + + // 4. 如果都没找到,返回默认值(如"1"表示单栏) + return "1"; + } + + // 分栏 - 分隔线 + public static String getParagraphColIsSep(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + SectPr sectPr = null; + sectPr = pPr.getSectPr(); + // 优先取段落自身的节属性 + if (pPr != null && pPr.getSectPr() != null) { + sectPr = pPr.getSectPr(); + } + + // 或最后一个段落中会有一个节结束的SectPr + if (sectPr == null && paragraph.getContent().size() > 0) { + Object lastObj = paragraph.getContent().get(paragraph.getContent().size() - 1); + if (lastObj instanceof JAXBElement && ((JAXBElement) lastObj).getValue() instanceof SectPr) { + sectPr = (SectPr) ((JAXBElement) lastObj).getValue(); + } + } + + if (sectPr != null && sectPr.getCols() != null) { + CTColumns cols = sectPr.getCols(); + return cols.isSep() ? "是" : "否"; + } + return "未知"; + } + + // 分栏 - 各栏间距 + public static String getParagraphColSpace(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + SectPr sectPr = null; + sectPr = pPr.getSectPr(); + // 优先取段落自身的节属性 + if (pPr != null && pPr.getSectPr() != null) { + sectPr = pPr.getSectPr(); + } + + // 或最后一个段落中会有一个节结束的SectPr + if (sectPr == null && paragraph.getContent().size() > 0) { + Object lastObj = paragraph.getContent().get(paragraph.getContent().size() - 1); + if (lastObj instanceof JAXBElement && ((JAXBElement) lastObj).getValue() instanceof SectPr) { + sectPr = (SectPr) ((JAXBElement) lastObj).getValue(); + } + } + + if (sectPr != null && sectPr.getCols() != null) { + CTColumns cols = sectPr.getCols(); + if (cols.getCol() != null && !cols.getCol().isEmpty()) { + int index = 1; + String value = ""; + for (CTColumn col : cols.getCol()) { + value += "第 " + index + " 栏间距 (twips): " + col.getSpace() + " "; + index++; + } + return value; + } + } + return "未知"; + } + // 分栏 - 各栏宽度 + public static String getParagraphColW(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + SectPr sectPr = null; + sectPr = pPr.getSectPr(); + // 优先取段落自身的节属性 + if (pPr != null && pPr.getSectPr() != null) { + sectPr = pPr.getSectPr(); + } + + // 或最后一个段落中会有一个节结束的SectPr + if (sectPr == null && paragraph.getContent().size() > 0) { + Object lastObj = paragraph.getContent().get(paragraph.getContent().size() - 1); + if (lastObj instanceof JAXBElement && ((JAXBElement) lastObj).getValue() instanceof SectPr) { + sectPr = (SectPr) ((JAXBElement) lastObj).getValue(); + } + } + + if (sectPr != null && sectPr.getCols() != null) { + CTColumns cols = sectPr.getCols(); + if (cols.getCol() != null && !cols.getCol().isEmpty()) { + int index = 1; + String value = ""; + for (CTColumn col : cols.getCol()) { + value += "第 " + index + " 栏宽度 (twips): " + col.getW() + " "; + index++; + } + return value; + } + } + return "未知"; + } + + // 分栏 - 栏宽相等 + public static String getParagraphColEqualWidth(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + SectPr sectPr = null; + sectPr = pPr.getSectPr(); + // 优先取段落自身的节属性 + if (pPr != null && pPr.getSectPr() != null) { + sectPr = pPr.getSectPr(); + } + // 或最后一个段落中会有一个节结束的SectPr + if (sectPr == null && paragraph.getContent().size() > 0) { + Object lastObj = paragraph.getContent().get(paragraph.getContent().size() - 1); + if (lastObj instanceof JAXBElement && ((JAXBElement) lastObj).getValue() instanceof SectPr) { + sectPr = (SectPr) ((JAXBElement) lastObj).getValue(); + } + } + if (sectPr != null && sectPr.getCols() != null) { + CTColumns cols = sectPr.getCols(); + return cols.isEqualWidth() ? "是" : "否"; + } + return "未知"; + } + + // 中文版式 - 双行合一 + public static String getParagraphCNBidi(P paragraph, StyleDefinitionsPart stylePart, WordprocessingMLPackage wordMLPackage, NumberingDefinitionsPart ndp) { + PPr pPr = paragraph.getPPr(); + if (pPr.getTextAlignment() != null) { + PPrBase.TextAlignment textAlignment = pPr.getTextAlignment(); + if (textAlignment.getVal() != null && + textAlignment.getVal().equals(STTextAlignment.CENTER)) { + return "是"; + } + } + return "否"; + } + +} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/paragraph/RunText.java b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/paragraph/RunText.java new file mode 100644 index 0000000..8923c6f --- /dev/null +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/paragraph/RunText.java @@ -0,0 +1,172 @@ +package com.example.exam.exam.service.wpsword.docx4j.paragraph; + +import org.docx4j.XmlUtils; +import org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart; +import org.docx4j.wml.R; +import org.docx4j.wml.RFonts; +import org.docx4j.wml.RPr; + +import java.math.BigInteger; + +public class RunText { + + // 句子-字体 + public static void getParagraphAlignment(R run, String text) { + if (run == null) { + return ; + } + RPr rPr = run.getRPr(); + String eastAsiaFont = null, asciiFont = null, hAnsiFont = null; + String fontSize = null; + + if (rPr != null) { + // 字体 + if (rPr.getRFonts() != null) { + RFonts fonts = rPr.getRFonts(); + eastAsiaFont = fonts.getEastAsia(); + asciiFont = fonts.getAscii(); + hAnsiFont = fonts.getHAnsi(); + } + // 字号(单位是半磅;20 = 10.0pt) + if (rPr.getSz() != null) { + BigInteger sz = rPr.getSz().getVal(); + fontSize = sz != null ? (sz.intValue() / 2.0) + " pt" : null; + } + } + + System.out.println("文本内容: " + text); + System.out.println("中文字体: " + (eastAsiaFont != null ? eastAsiaFont : "未设置")); + System.out.println("西文字体(ASCII): " + (asciiFont != null ? asciiFont : "未设置")); + System.out.println("西文字体(HAnsi): " + (hAnsiFont != null ? hAnsiFont : "未设置")); + System.out.println("字号: " + (fontSize != null ? fontSize : "未设置")); + System.out.println("--------"); + } + + // 字体-字形 + public static void getParagraphFontStyle(R run, String text) { + RPr rPr = run.getRPr(); + boolean isBold = false; + boolean isItalic = false; + + if (rPr != null) { + isBold = rPr.getB() != null && rPr.getB().isVal(); + isItalic = rPr.getI() != null && rPr.getI().isVal(); + } + + System.out.println("文本: " + text); + if (isBold && isItalic) { + System.out.println("样式: 加粗 + 倾斜"); + } else if (isBold) { + System.out.println("样式: 加粗"); + } else if (isItalic) { + System.out.println("样式: 倾斜"); + } else { + System.out.println("样式: 常规"); + } + System.out.println("--------"); + } + // 字体-所有文字 + public static void getParagraphFontColor(R run, StyleDefinitionsPart stylePart, String text) { + RPr rPr = run.getRPr(); + + String color = "默认"; + String underlineVal = "无"; + String underlineColor = "默认"; + + if (rPr != null) { + if (rPr.getColor() != null && rPr.getColor().getVal() != null) { + color = rPr.getColor().getVal(); // 如 "FF0000" + } + + if (rPr.getU() != null) { + if (rPr.getU().getVal() != null) { + underlineVal = rPr.getU().getVal().value(); // single, double, none 等 + } + if (rPr.getU().getColor() != null) { + underlineColor = rPr.getU().getColor(); // 例如 "0000FF" + } + } + } + + System.out.println("文本: " + text); + System.out.println("字体颜色: " + color); + System.out.println("下划线样式: " + underlineVal); + System.out.println("下划线颜色: " + underlineColor); + System.out.println("--------"); + } + // 字体-效果-删除线 + public static void getParagraphStrike(R run, String text) { + RPr rPr = run.getRPr(); + + boolean isStrike = false; + boolean isDoubleStrike = false; + String verticalAlign = "正常"; + + if (rPr != null) { + isStrike = rPr.getStrike() != null && rPr.getStrike().isVal(); + isDoubleStrike = rPr.getDstrike() != null && rPr.getDstrike().isVal(); + + if (rPr.getVertAlign() != null && rPr.getVertAlign().getVal() != null) { + verticalAlign = rPr.getVertAlign().getVal().value(); // superscript or subscript + } + } + + System.out.println("文本: " + text); + System.out.println("删除线: " + (isStrike ? "有" : "无")); + System.out.println("双删除线: " + (isDoubleStrike ? "有" : "无")); + System.out.println("上下标: " + verticalAlign); // superscript/subscript/normal + System.out.println("--------"); + } + + // 字体-字符间距 + public static void getParagraphFontSpacing(R run, String text) { + RPr rPr = run.getRPr(); + + String scale = "100%"; + String spacing = "0"; + String position = "0"; + String kern = "无"; + String snapToGrid = "否"; + + if (rPr != null) { + if (rPr.getW() != null && rPr.getW().getVal() != null) { + scale = rPr.getW().getVal().toString() + "%"; + } + if (rPr.getSpacing() != null && rPr.getSpacing().getVal() != null) { + spacing = rPr.getSpacing().getVal().toString() + " twips"; + } + if (rPr.getPosition() != null && rPr.getPosition().getVal() != null) { + position = rPr.getPosition().getVal().toString() + " pt(1/2)"; + } + if (rPr.getKern() != null && rPr.getKern().getVal() != null) { + kern = rPr.getKern().getVal().toString() + " (1/100 pt)"; + } + if (rPr.getSnapToGrid() != null) { + snapToGrid = rPr.getSnapToGrid().isVal() ? "是" : "否"; + } + } + + System.out.println("文本: " + text); + System.out.println("缩放比例: " + scale); + System.out.println("字符间距: " + spacing); + System.out.println("字符位置: " + position); + System.out.println("最小间距 (Kern): " + kern); + System.out.println("对齐到网格: " + snapToGrid); + System.out.println("--------"); + } + // 字体-文本效果 + public static void getParagraphTextEffect(R run, String text) { + + // 转换为 XML 文本 + String runXml = XmlUtils.marshaltoString(run, true); + System.out.println("文本: " + text); + + // 检测特效 + System.out.println("文本填充 (textFill): " + (runXml.contains("w14:textFill") ? "是" : "否")); + System.out.println("文本轮廓 (textOutline): " + (runXml.contains("w14:textOutline") ? "是" : "否")); + System.out.println("阴影 (shadow): " + (runXml.contains("w14:shadow") ? "是" : "否")); + System.out.println("倒影 (reflection): " + (runXml.contains("w14:reflection") ? "是" : "否")); + System.out.println("发光 (glow): " + (runXml.contains("w14:glow") ? "是" : "否")); + System.out.println("--------"); + } +} 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 new file mode 100644 index 0000000..4d8c380 --- /dev/null +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/section/SectionPage.java @@ -0,0 +1,308 @@ +package com.example.exam.exam.service.wpsword.docx4j.section; + +import jakarta.xml.bind.JAXBElement; +import org.docx4j.XmlUtils; +import org.docx4j.model.structure.HeaderFooterPolicy; +import org.docx4j.model.structure.SectionWrapper; +import org.docx4j.openpackaging.packages.WordprocessingMLPackage; +import org.docx4j.openpackaging.parts.WordprocessingML.FooterPart; +import org.docx4j.openpackaging.parts.WordprocessingML.HeaderPart; +import org.docx4j.wml.*; + +import java.util.List; + +/** + * @author REN + */ +public class SectionPage { + + // 节-上下左右设置页边距 + public static void getSectionPageSettings(P paragraph) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getSectPr() != null) { + SectPr sectPr = pPr.getSectPr(); + SectPr.PgMar pgMar = sectPr.getPgMar(); + + if (pgMar != null) { + System.out.println("---- 节页面设置 ----"); + System.out.println("上边距 (twips): " + pgMar.getTop()); + System.out.println("下边距 (twips): " + pgMar.getBottom()); + System.out.println("左边距 (twips): " + pgMar.getLeft()); + System.out.println("右边距 (twips): " + pgMar.getRight()); + System.out.println("装订线宽度 (twips): " + pgMar.getGutter()); + System.out.println("-------------------"); + } + } + } + // 纸张方向 大小 + public static void getSectionPageSize(P paragraph) { + PPr pPr = paragraph.getPPr(); + if (pPr != null && pPr.getSectPr() != null) { + SectPr sectPr = pPr.getSectPr(); + SectPr.PgSz pgSz = sectPr.getPgSz(); + + if (pgSz != null) { + System.out.println("---- 节页面大小设置 ----"); + + // 页面宽度 & 高度(单位是 twips:1/20 pt) + System.out.println("页面宽度 (twips): " + pgSz.getW()); + System.out.println("页面高度 (twips): " + pgSz.getH()); + double widthMM = twipToMM(Integer.parseInt(pgSz.getW().toString())); + double heightMM = twipToMM(Integer.parseInt(pgSz.getH().toString())); + + // 保留一位小数误差范围 + double w = Math.min(widthMM, heightMM); + double h = Math.max(widthMM, heightMM); + String pageSize = ""; + if (Math.abs(w - 210) < 5 && Math.abs(h - 297) < 5) { + pageSize = "A4"; + } + if (Math.abs(w - 297) < 5 && Math.abs(h - 420) < 5) { + pageSize = "A3"; + } + if (Math.abs(w - 216) < 5 && Math.abs(h - 279) < 5) { + pageSize = "Letter"; + } + if (Math.abs(w - 216) < 5 && Math.abs(h - 356) < 5) { + pageSize = "Legal"; + } + if (Math.abs(w - 176) < 5 && Math.abs(h - 250) < 5) { + pageSize = "B5"; + } + if (Math.abs(w - 184) < 5 && Math.abs(h - 267) < 5) { + pageSize = "Executive"; + } + System.out.println("纸型: " + pageSize); + // 页面方向 + STPageOrientation orientation = pgSz.getOrient(); + if (orientation != null) { + System.out.println("纸张方向: " + ("landscape".equals(orientation.value()) ? "横向" : "纵向")); + } else { + System.out.println("纸张方向: 默认纵向"); + } + + System.out.println("----------------------"); + } + } + } + public static double twipToMM(int twips) { + return twips * 0.0176389; + } + // 水印 + public static void getSectionPageWatermark(R run) { + // 检查是否包含 Drawing(图片水印) + if (XmlUtils.marshaltoString(run, true).contains(" sections, WordprocessingMLPackage wordMLPackage) { + for (int i = 0; i < sections.size(); i++) { + SectionWrapper section = sections.get(i); + HeaderFooterPolicy hfp = section.getHeaderFooterPolicy(); + System.out.println("📄 节 " + (i + 1)); + + if (hfp.getDefaultHeader() != null) { + System.out.println("【默认/奇数页 页眉】"); + extractTextFromHeader(hfp.getDefaultHeader(), wordMLPackage); + } + + if (hfp.getEvenHeader() != null) { + System.out.println("【偶数页 页眉】"); + extractTextFromHeader(hfp.getEvenHeader(), wordMLPackage); + } + + if (hfp.getFirstHeader() != null) { + System.out.println("【首页 页眉】"); + extractTextFromHeader(hfp.getFirstHeader(), wordMLPackage); + } + + System.out.println("----------------------------"); + } + } + + // 页脚 + public static void getSectionPageFooter(List sections, WordprocessingMLPackage wordMLPackage) { + for (int i = 0; i < sections.size(); i++) { + SectionWrapper section = sections.get(i); + HeaderFooterPolicy hfp = section.getHeaderFooterPolicy(); + System.out.println("📄 节 " + (i + 1)); + + if (hfp.getDefaultFooter() != null) { + System.out.println("【默认/奇数页 页脚】"); + extractTextFromFooter(hfp.getDefaultFooter(), wordMLPackage); + } + + if (hfp.getEvenFooter() != null) { + System.out.println("【偶数页 页脚】"); + extractTextFromFooter(hfp.getEvenFooter(), wordMLPackage); + } + + if (hfp.getFirstFooter() != null) { + System.out.println("【首页 页脚】"); + extractTextFromFooter(hfp.getFirstFooter(), wordMLPackage); + } + + System.out.println("----------------------------"); + } + } + + public static void extractTextFromHeader(HeaderPart headerPart, WordprocessingMLPackage wordMLPackage) { + List contents = headerPart.getContent(); + for (Object obj : contents) { + if (obj instanceof P) { + P paragraph = (P) obj; + StringBuilder text = new StringBuilder(); + String font = null; + Double fontSizePt = null; + String colorHex = null; + String alignment = null; + + // 获取对齐方式 + if (paragraph.getPPr() != null && paragraph.getPPr().getJc() != null) { + alignment = paragraph.getPPr().getJc().getVal().value(); + } + + // 获取文本与样式 + for (Object o : paragraph.getContent()) { + if (o instanceof R) { + R run = (R) o; + + // 样式信息 + RPr rPr = run.getRPr(); + if (rPr != null) { + if (rPr.getRFonts() != null) { + font = rPr.getRFonts().getAscii(); + } + if (rPr.getSz() != null) { + fontSizePt = rPr.getSz().getVal().intValue() / 2.0; + } + if (rPr.getColor() != null) { + colorHex = rPr.getColor().getVal(); + } + } + + // 文本信息 + for (Object rContent : run.getContent()) { + Object val = (rContent instanceof JAXBElement) + ? ((JAXBElement) rContent).getValue() + : rContent; + + if (val instanceof Text) { + text.append(((Text) val).getValue()); + } + } + } + } + + // 输出页眉段落信息 + if (text.length() > 0) { + System.out.println("页眉文本: " + text); + System.out.println("字体: " + font); + System.out.println("字号: " + fontSizePt + " pt"); + System.out.println("颜色: " + colorHex); + System.out.println("对齐方式: " + alignment); + } + } + } + + // 提取页边距(节属性中的 header/left/right) + SectPr sectPr = wordMLPackage.getMainDocumentPart().getJaxbElement().getBody().getSectPr(); + if (sectPr != null && sectPr.getPgMar() != null) { + SectPr.PgMar pgMar = sectPr.getPgMar(); + if (pgMar.getHeader() != null) { + System.out.println("页眉距顶端: " + (pgMar.getHeader().intValue() / 20.0) + " pt"); + } + if (pgMar.getLeft() != null) { + System.out.println("左边距: " + (pgMar.getLeft().intValue() / 20.0) + " pt"); + } + if (pgMar.getRight() != null) { + System.out.println("右边距: " + (pgMar.getRight().intValue() / 20.0) + " pt"); + } + } + } + + public static void extractTextFromFooter(FooterPart footerPart, WordprocessingMLPackage wordMLPackage) { + List contents = footerPart.getContent(); + for (Object obj : contents) { + if (obj instanceof P) { + P paragraph = (P) obj; + StringBuilder text = new StringBuilder(); + String font = null; + Double fontSizePt = null; + String colorHex = null; + String alignment = null; + + // 获取对齐方式 + if (paragraph.getPPr() != null && paragraph.getPPr().getJc() != null) { + alignment = paragraph.getPPr().getJc().getVal().value(); + } + + // 遍历 run 内容 + for (Object o : paragraph.getContent()) { + if (o instanceof R) { + R run = (R) o; + + // 获取样式 + RPr rPr = run.getRPr(); + if (rPr != null) { + if (rPr.getRFonts() != null) { + font = rPr.getRFonts().getAscii(); + } + if (rPr.getSz() != null) { + fontSizePt = rPr.getSz().getVal().intValue() / 2.0; + } + if (rPr.getColor() != null) { + colorHex = rPr.getColor().getVal(); + } + } + + // 提取文本 + for (Object rContent : run.getContent()) { + Object val = rContent instanceof JAXBElement ? ((JAXBElement) rContent).getValue() : rContent; + if (val instanceof Text) { + text.append(((Text) val).getValue()); + } + } + } + } + + // 打印段落信息 + if (text.length() > 0) { + System.out.println("页脚文本: " + text); + System.out.println("字体: " + font); + System.out.println("字号: " + fontSizePt + " pt"); + System.out.println("颜色: " + colorHex); + System.out.println("对齐方式: " + alignment); + } + } + } + + // 获取 section 页边距信息 + SectPr sectPr = wordMLPackage.getMainDocumentPart().getJaxbElement().getBody().getSectPr(); + if (sectPr != null && sectPr.getPgMar() != null) { + SectPr.PgMar pgMar = sectPr.getPgMar(); + if (pgMar.getFooter() != null) { + System.out.println("页脚距底端: " + (pgMar.getFooter().intValue() / 20.0) + " pt"); + } + if (pgMar.getLeft() != null) { + System.out.println("左边距: " + (pgMar.getLeft().intValue() / 20.0) + " pt"); + } + if (pgMar.getRight() != null) { + System.out.println("右边距: " + (pgMar.getRight().intValue() / 20.0) + " pt"); + } + } + } + + +} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/text/TextInfo.java b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/text/TextInfo.java new file mode 100644 index 0000000..61df678 --- /dev/null +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/text/TextInfo.java @@ -0,0 +1,133 @@ +package com.example.exam.exam.service.wpsword.docx4j.text; + +import com.example.exam.exam.service.wpsword.docx4j.DocxSetInfo; +import com.example.exam.exam.service.wpsword.docx4j.paragraph.Convert; +import com.example.exam.exam.service.wpsword.docx4j.vo.JudgementWordsVO; +import jakarta.xml.bind.JAXBElement; +import jakarta.xml.bind.JAXBException; +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.wordprocessingDrawing.Anchor; +import org.docx4j.jaxb.Context; +import org.docx4j.wml.R; + +import java.util.List; + +public class TextInfo { + + // 文本内容 + public static List getTextInfo(List judgementWordsVOS, Anchor anchor, int betoLong, String firstId){ + String name = "【第" + betoLong + "个图形】【文本框】"; + String parName = "【文本】"; + String neiRongName = parName + "【文本内容】"; + String fangXiangName = parName + "【文字方向】"; + String parentId = Convert.getStringRandom(); + Object graphicData = anchor.getGraphic().getGraphicData().getAny().get(0); + // 正确处理 JAXBElement + if (graphicData instanceof JAXBElement) { + JAXBElement jaxbElement = (JAXBElement) graphicData; + Object value = jaxbElement.getValue(); + + // 现在可以尝试转换为实际类型 + if (value.getClass().getName().contains("CTWordprocessingShape")) { + if (value instanceof CTWordprocessingShape) { + // 文本内容 + CTWordprocessingShape textInfo = (CTWordprocessingShape) value; + String textValue = textInfo.getTxbx().getTxbxContent().getContent().toString(); + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + neiRongName + textValue, name + neiRongName + textValue, name); + // 文字方向 + String vertValue = textInfo.getBodyPr().getVert().value(); + vertValue = getTextDirection(vertValue); + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + fangXiangName + vertValue, name + fangXiangName + vertValue, name); + String tianChongName = parName + "【填充方式】"; + // 文本填充-填充方式 TODO 存在标签对不上的问题 + if (textInfo.getSpPr().getSolidFill() != null) { + // 纯色填充 + String typeValue = "纯色填充"; + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + tianChongName + typeValue, name + tianChongName + typeValue, name); + + if (textInfo.getSpPr().getSolidFill().getSrgbClr() != null) { + // 纯色填充-颜色 + String color = textInfo.getSpPr().getSolidFill().getSrgbClr().getVal(); + tianChongName = tianChongName + "【颜色】"; + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + tianChongName + color, name + tianChongName + color, name); + } else if (textInfo.getSpPr().getSolidFill().getSchemeClr() != null) { + // 纯色填充-颜色 + String color = textInfo.getSpPr().getSolidFill().getSrgbClr().getVal(); + tianChongName = tianChongName + "【颜色】"; + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + tianChongName + color, name + tianChongName + color, name); + } + } + if (textInfo.getSpPr().getBlipFill() != null) { + // 图片填充 + tianChongName = tianChongName + "图片填充"; + } + if (textInfo.getSpPr().getGradFill() != null) { + // 渐变填充 + tianChongName = tianChongName + "渐变填充"; + } + } + } + if (value.getClass().getName().contains("shadow")) { + System.out.println(11); + System.out.println(11); + } + } + // 属性直接在wp:anchor 下面的 + String layoutHuanRaoName = "【布局】【环绕方式】"; + String layoutHuanRaoValue = anchor.getWrapSquare().getWrapText().value(); + layoutHuanRaoValue = getTextDirection(layoutHuanRaoValue); + judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + layoutHuanRaoName + layoutHuanRaoValue, name + layoutHuanRaoName + layoutHuanRaoValue, name); + + + + return judgementWordsVOS; + } + + public static List getTextFillFromRun(List judgementWordsVOS, R run, int betoLong, String firstId) throws JAXBException, XmlException { + String xml = XmlUtils.marshaltoString(run, true); + org.w3c.dom.Node node = Context.jc.createMarshaller().getNode(run); + XmlCursor xmlCursor = XmlObject.Factory.parse(node).newCursor(); + + System.out.println(xmlCursor.xmlText()); + return judgementWordsVOS; + } + + + private static String getWrapTextDescription(String wrapText) { + if (wrapText == null) return "两侧"; + + switch (wrapText) { + case "bothSides": return "两侧"; + case "left": return "只在左侧"; + case "right": return "只在右侧"; + case "largest": return "最大边"; + default: return "无"; + } + } + public static String getTextDirection(String vert) { + if (vert == null) return "水平(默认)"; + + switch (vert) { + case "horz": + return "水平"; + case "vert": + return "垂直"; + case "vert270": + return "垂直(270度)"; + case "eaVert": + return "东亚垂直"; + case "mongolianVert": + return "蒙古文垂直"; + case "wordArtVert": + return "艺术字垂直"; + case "wordArtVertRtl": + return "艺术字垂直(从右到左)"; + default: + return "水平(默认)"; + } + } +} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/vo/DocxDataInfoVO.java b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/vo/DocxDataInfoVO.java new file mode 100644 index 0000000..44d00ea --- /dev/null +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/vo/DocxDataInfoVO.java @@ -0,0 +1,19 @@ +package com.example.exam.exam.service.wpsword.docx4j.vo; + +import lombok.Data; + +@Data +public class DocxDataInfoVO { + + private String name; + + private String type; + + private String id; + + private String parentId; + + private String index; + + private boolean isClick; +} diff --git a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/vo/JudgementWordsVO.java b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/vo/JudgementWordsVO.java new file mode 100644 index 0000000..1791c8a --- /dev/null +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/vo/JudgementWordsVO.java @@ -0,0 +1,17 @@ +package com.example.exam.exam.service.wpsword.docx4j.vo; + +import lombok.Data; + +/** + * @author REN + */ +@Data +public class JudgementWordsVO { + + // 考点 + private String content; + + // 考点名称 + private String contentIn; + +} 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 new file mode 100644 index 0000000..6d3e774 --- /dev/null +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/vo/WpsDocxInfoVo.java @@ -0,0 +1,25 @@ +package com.example.exam.exam.service.wpsword.docx4j.vo; + +import lombok.Data; + +@Data +public class WpsDocxInfoVo { + + // 大类名称 + private String firstName; + + // 序号 + private String index; + + // 方法名称 + private String function; + + // 考点名称 + private String examName; + + // 考点代码 + private String examCode; + + // 方式方法 + private String method; +} diff --git a/src/main/java/com/example/exam/exam/utils/CustomMultipartFile.java b/src/main/java/com/example/exam/exam/utils/CustomMultipartFile.java new file mode 100644 index 0000000..ad0bb6c --- /dev/null +++ b/src/main/java/com/example/exam/exam/utils/CustomMultipartFile.java @@ -0,0 +1,57 @@ +package com.example.exam.exam.utils; + +import org.springframework.web.multipart.MultipartFile; +import java.io.*; +import java.nio.file.Files; + +public class CustomMultipartFile implements MultipartFile { + private final byte[] fileContent; + private final String fileName; + private final String contentType; + + public CustomMultipartFile(File file) throws IOException { + this.fileName = file.getName(); + this.fileContent = Files.readAllBytes(file.toPath()); + this.contentType = "multipart/form-data"; // 或根据文件类型设置 + } + + @Override + public String getName() { + return fileName; + } + + @Override + public String getOriginalFilename() { + return fileName; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public boolean isEmpty() { + return fileContent == null || fileContent.length == 0; + } + + @Override + public long getSize() { + return fileContent.length; + } + + @Override + public byte[] getBytes() throws IOException { + return fileContent; + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(fileContent); + } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + Files.write(dest.toPath(), fileContent); + } +} \ No newline at end of file