【新增】 word考点基于docx4j 第一版

This commit is contained in:
DESKTOP-932OMT8\REN
2025-07-01 21:15:58 +08:00
parent c64e79d37b
commit f4c9c1c9e7
12 changed files with 1161 additions and 26 deletions

View File

@@ -153,6 +153,37 @@
<version>2.4.2-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-core</artifactId>
<version>11.5.3</version> <!-- 或使用最新版本 -->
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-MOXy</artifactId>
<version>11.5.3</version>
</dependency>
<!-- 必须加这个,不然 marshaltoString 会报错 -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-Internal</artifactId>
<version>8.3.9</version>
</dependency>
<!-- MOXy 实现 -->
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.moxy</artifactId>
<version>3.0.2</version>
</dependency>
<!-- JAXB API -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>
</project>

View File

@@ -45,6 +45,11 @@ public class WpsController {
JudgementWpsPptxService judgementWpsPptxService;
@Resource
JudgementWpsExcelService judgementWpsExcelService;
@GetMapping("/docxMaster")
public CommonResult<String> docxMaster() throws Exception {
judgementWpsWordService.docxMaster("D:\\Project\\Exam\\Software\\Temp\\1.docx");
return CommonResult.success("");
}
/**
* wps word
* @return 判分

View File

@@ -19,6 +19,7 @@ import java.util.List;
*/
public interface JudgementWpsWordService {
void docxMaster(String path) throws Exception;
/**
* 获取word文件内得考点及描述
* @param path minio文件路径

View File

@@ -19,6 +19,7 @@ import pc.exam.pp.module.judgement.service.auto_tools.AutoToolsService;
import pc.exam.pp.module.judgement.service.auto_tools.vo.SourceAndText;
import pc.exam.pp.module.judgement.utils.HtmlAppender;
import pc.exam.pp.module.judgement.utils.wps_word.WpsWordUtils;
import pc.exam.pp.module.judgement.utils.wps_word.docx4j.DocxMaster;
import pc.exam.pp.module.judgement.utils.wps_word.vo.WordInfoReqVo;
import pc.exam.pp.module.judgement.utils.wps_word.vo.WordVO;
import pc.exam.pp.module.system.dal.dataobject.user.AdminUserDO;
@@ -47,6 +48,11 @@ public class JudgementWpsWordServiceImpl implements JudgementWpsWordService {
@Resource
private AdminUserService userService;
@Override
public void docxMaster(String path) throws Exception {
DocxMaster.docxMaster(path);
}
@Override
public List<WordInfoReqVo> programmingWpsWord(String path) throws Exception {
String pathName = "";

View File

@@ -0,0 +1,101 @@
package pc.exam.pp.module.judgement.utils.wps_word.docx4j;
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.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.HeaderPart;
import org.docx4j.openpackaging.parts.WordprocessingML.NumberingDefinitionsPart;
import org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart;
import org.docx4j.wml.*;
import pc.exam.pp.module.judgement.utils.wps_word.docx4j.paragraph.Convert;
import pc.exam.pp.module.judgement.utils.wps_word.docx4j.paragraph.Paragraphs;
import pc.exam.pp.module.judgement.utils.wps_word.docx4j.paragraph.RunText;
import pc.exam.pp.module.judgement.utils.wps_word.docx4j.section.SectionPage;
import pc.exam.pp.module.judgement.utils.wps_word.docx4j.vo.JudgementWordsVO;
import java.io.File;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
/**
* @author REN
*/
public class DocxMaster {
public static void docxMaster(String path) throws Docx4JException {
// 一共分为 段落
// 创建考点数组
List<JudgementWordsVO> judgementWordsVOS = new ArrayList<>();
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new File(path));
List<Object> paragraphs = wordMLPackage.getMainDocumentPart().getContent();
StyleDefinitionsPart stylePart = wordMLPackage.getMainDocumentPart().getStyleDefinitionsPart();
NumberingDefinitionsPart ndp = wordMLPackage.getMainDocumentPart().getNumberingDefinitionsPart();
List<SectionWrapper> sections = wordMLPackage.getDocumentModel().getSections();
int index = 0;
for (Object obj : paragraphs) {
Object realObj = XmlUtils.unwrap(obj);
if (realObj instanceof P) {
index++;
P paragraph = (P) realObj;
PPr pPr = paragraph.getPPr();
// 安全判断pPr 为空或 pPr 中没有 sectPr才认为是普通段落
if (pPr == null || pPr.getSectPr() == null) {
String firstId = Convert.getStringRandom();
// 首先先创建段落
JudgementWordsVO judgementWordsVO = new JudgementWordsVO();
judgementWordsVO.setName("" + index + "");
judgementWordsVO.setParentId("0");
judgementWordsVO.setId(firstId);
judgementWordsVOS.add(judgementWordsVO);
judgementWordsVOS = Paragraphs.getParagraphAlignment(paragraph, stylePart, judgementWordsVOS, index, firstId);
judgementWordsVOS = Paragraphs.getParagraphOutlineLvl(paragraph, stylePart, judgementWordsVOS, index, firstId);
judgementWordsVOS = Paragraphs.getParagraphIndent(paragraph, stylePart, judgementWordsVOS, index, firstId);
judgementWordsVOS = Paragraphs.getParagraphSpacing(paragraph, stylePart, judgementWordsVOS, index, firstId);
judgementWordsVOS = Paragraphs.getParagraphList(paragraph, ndp, judgementWordsVOS, index, firstId);
System.out.println(judgementWordsVOS);
} else {
Paragraphs.getParagraphCols(paragraph, stylePart);
}
// 句子
List<Object> contents = paragraph.getContent();
for (Object content : contents) {
if (content instanceof R) {
R run = (R) content;
String text = getRunText(run);
if (text == null || text.trim().isEmpty()) {
continue;
}
// 句子-字体
RunText.getParagraphAlignment(run, text);
}
}
}
}
SectionPage.getSectionPageHeader(sections, wordMLPackage);
SectionPage.getSectionPageFooter(sections, wordMLPackage);
}
// 提取 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();
}
}

View File

@@ -0,0 +1,22 @@
package pc.exam.pp.module.judgement.utils.wps_word.docx4j;
import pc.exam.pp.module.judgement.utils.wps_word.docx4j.vo.JudgementWordsVO;
import java.util.List;
/**
* @author REN
*/
public class DocxSetInfo {
public static List<JudgementWordsVO> setInfo(List<JudgementWordsVO> judgementWordsVOS, String id, String parentId, String contentIn, String content, String name) {
JudgementWordsVO judgementWordsVO = new JudgementWordsVO();
judgementWordsVO.setId(id);
judgementWordsVO.setParentId(parentId);
judgementWordsVO.setContentIn(contentIn);
judgementWordsVO.setContent(content);
judgementWordsVO.setName(name);
judgementWordsVOS.add(judgementWordsVO);
return judgementWordsVOS;
}
}

View File

@@ -0,0 +1,84 @@
package pc.exam.pp.module.judgement.utils.wps_word.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<Numbering.AbstractNum> 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<Object> 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;
}
}

View File

@@ -0,0 +1,381 @@
package pc.exam.pp.module.judgement.utils.wps_word.docx4j.paragraph;
import jakarta.xml.bind.JAXBElement;
import org.docx4j.openpackaging.parts.WordprocessingML.NumberingDefinitionsPart;
import org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart;
import org.docx4j.wml.*;
import pc.exam.pp.module.judgement.utils.wps_word.docx4j.DocxSetInfo;
import pc.exam.pp.module.judgement.utils.wps_word.docx4j.vo.JudgementWordsVO;
import java.math.BigInteger;
import java.util.List;
/**
* @author REN
*/
public class Paragraphs {
/**
* 段落-对齐方式
* @param paragraph
* @param stylePart
* @param judgementWordsVOS
* @param betoLong
* @param firstId
* @return
*/
public static List<JudgementWordsVO> getParagraphAlignment(P paragraph, StyleDefinitionsPart stylePart, List<JudgementWordsVO> judgementWordsVOS, int betoLong, String firstId) {
String name = "【第" + betoLong + "段】";
String parName = "【段落格式(常规)】【对齐方式】";
if (paragraph == null) {
return judgementWordsVOS;
}
// 先查段落自身
PPr pPr = paragraph.getPPr();
String parentId = Convert.getStringRandom();
if (pPr != null && pPr.getJc() != null && pPr.getJc().getVal() != null) {
String value = Convert.convertJc(pPr.getJc().getVal().value());
if (value != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + parName + value, name + parName + value, name);
}
}
// 若段落未定义对齐方式,则尝试从样式中继承
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) {
String value = Convert.convertJc(style.getPPr().getJc().getVal().value());
if (value != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + parName + value, name + parName + value, name);
}
}
}
// 都没有设置,返回默认
return judgementWordsVOS;
}
// 段落-大纲级别
public static List<JudgementWordsVO> getParagraphOutlineLvl(P paragraph, StyleDefinitionsPart stylePart, List<JudgementWordsVO> judgementWordsVOS, int betoLong, String firstId) {
if (paragraph == null) {
return judgementWordsVOS;
}
String name = "【第" + betoLong + "段】";
String parName = "【段落格式(常规)】【大纲级别】";
String parentId = Convert.getStringRandom();
// 先查询自身段落数据
PPr pPr = paragraph.getPPr();
if (pPr != null && pPr.getOutlineLvl() != null && pPr.getOutlineLvl().getVal() != null) {
String value = Convert.convertOutlineLvl(pPr.getOutlineLvl().getVal().toString());
if (value != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + parName + value, name + parName + value, name);
}
}
// 没有设置,从公共方法中获取参数
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) {
String value = Convert.convertJc(style.getPPr().getOutlineLvl().getVal().toString());
if (value != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + parName + value, name + parName + value, name);
}
}
}
return judgementWordsVOS;
}
// 段落-缩进
public static List<JudgementWordsVO> getParagraphIndent(P paragraph, StyleDefinitionsPart stylePart, List<JudgementWordsVO> judgementWordsVOS, int betoLong, String firstId) {
if (paragraph == null) {
return judgementWordsVOS;
}
String name = "【第" + betoLong + "段】";
String parentId = Convert.getStringRandom();
// 先查询自身段落数据
PPr pPr = paragraph.getPPr();
PPrBase.Ind ind = pPr.getInd();
if (ind != null) {
if (ind.getLeft() != null) {
String value = ind.getLeft().intValue() + "";
String parName = "【段落格式(缩进)】【左缩进】";
if (value != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + parName + value, name + parName + value, name);
}
}
if (ind.getRight() != null) {
String value = ind.getRight().intValue() + "";
String parName = "【段落格式(缩进)】【右缩进】";
if (value != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + parName + value, name + parName + value, name);
}
}
if (ind.getFirstLine() != null) {
String value = ind.getFirstLine().intValue() + "";
String parName = "【段落格式(缩进)】【首行缩进】";
if (value != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + parName + value, name + parName + value, name);
}
}
if (ind.getHanging() != null) {
String value = ind.getHanging().intValue() + "";
String parName = "【段落格式(缩进)】【悬挂缩进】";
if (value != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + parName + value, name + parName + value, name);
}
}
}
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) {
String value = indStyle.getLeft().intValue() + "";
String parName = "【段落格式(缩进)】【左缩进】";
if (value != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + parName + value, name + parName + value, name);
}
}
if (indStyle.getRight() != null) {
String value = indStyle.getRight().intValue() + "";
String parName = "【段落格式(缩进)】【右缩进】";
if (value != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + parName + value, name + parName + value, name);
}
}
if (indStyle.getFirstLine() != null) {
String value = indStyle.getFirstLine().intValue() + "";
String parName = "【段落格式(缩进)】【首行缩进】";
if (value != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + parName + value, name + parName + value, name);
}
}
if (indStyle.getHanging() != null) {
String value = indStyle.getHanging().intValue() + "";
String parName = "【段落格式(缩进)】【悬挂缩进】";
if (value != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + parName + value, name + parName + value, name);
}
}
}
}
return judgementWordsVOS;
}
// 段落-间距
public static List<JudgementWordsVO> getParagraphSpacing(P paragraph, StyleDefinitionsPart stylePart, List<JudgementWordsVO> judgementWordsVOS, int betoLong, String firstId) {
if (paragraph == null) {
return judgementWordsVOS;
}
String name = "【第" + betoLong + "段】";
String parentId = Convert.getStringRandom();
// 先查询自身段落数据
PPr pPr = paragraph.getPPr();
if (pPr != null && pPr.getSpacing() != null) {
BigInteger before = pPr.getSpacing().getBefore();
BigInteger after = pPr.getSpacing().getAfter();
BigInteger line = pPr.getSpacing().getLine();
String beforeName = "【段落格式(间距)】【段前】";
if (before != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + beforeName + before, name + beforeName + before, name);
}
String afterName = "【段落格式(间距)】【段后】";
if (after != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + afterName + after, name + afterName + after, name);
}
String lineName = "【段落格式(间距)】【行距】";
if (line != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + lineName + line, name + lineName + line, name);
}
String linesName = "【段落格式(间距)】【行距值】";
if (line != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + linesName + line, name + linesName + line, name);
}
}
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();
BigInteger before = spacing.getBefore();
BigInteger after = spacing.getAfter();
BigInteger line = spacing.getLine();
String beforeName = "【段落格式(间距)】【段前】";
if (before != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + beforeName + before, name + beforeName + before, name);
}
String afterName = "【段落格式(间距)】【段后】";
if (after != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + afterName + after, name + afterName + after, name);
}
String lineName = "【段落格式(间距)】【行距】";
if (line != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + lineName + line, name + lineName + line, name);
}
String linesName = "【段落格式(间距)】【行距值】";
if (line != null) {
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + linesName + line, name + linesName + line, name);
}
}
}
return judgementWordsVOS;
}
// 段落-编号列表
public static List<JudgementWordsVO> getParagraphList(P paragraph, NumberingDefinitionsPart ndp, List<JudgementWordsVO> judgementWordsVOS, int betoLong, String firstId) {
if (paragraph == null) {
return judgementWordsVOS;
}
String name = "【第" + betoLong + "段】";
String parentId = Convert.getStringRandom();
// 先查询自身段落数据
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;
String trueName = "【编号列表】【应用编号列表】存在";
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + trueName, name + trueName, name);
String jiBieName = "【编号列表】【列表级别】" + (ilvl != null ? ilvl.intValue() : "未知");
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + jiBieName, name + jiBieName, name);
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) {
String bianHaoYangShiName = "【编号列表】【编号样式】" + lvl.getNumFmt().getVal();
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + bianHaoYangShiName, name + bianHaoYangShiName, name);
String bianHaoGeShiName = "【编号列表】【编号格式】" + lvl.getLvlText().getVal();
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + bianHaoGeShiName, name + bianHaoGeShiName, name);
if ("bullet".equals(lvl.getNumFmt().getVal().value())) {
String xiangMuFuHaoName = "【编号列表】【项目符号】" + lvl.getLvlText().getVal();
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + xiangMuFuHaoName, name + xiangMuFuHaoName, name);
String lieBiaoName = "【编号列表】【列表类型】无序列表";
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + lieBiaoName, name + lieBiaoName, name);
} else {
String lieBiaoName = "【编号列表】【列表类型】有序列表";
judgementWordsVOS = DocxSetInfo.setInfo(judgementWordsVOS, parentId, firstId, name + lieBiaoName, name + lieBiaoName, name);
}
}
}
}
}
}
return judgementWordsVOS;
}
// 段落-边框
// public static List<JudgementWordsVO> getParagraphBorder(P paragraph, StyleDefinitionsPart stylePart, List<JudgementWordsVO> judgementWordsVOS, int betoLong, String firstId) {
// if (paragraph == null) {
// return judgementWordsVOS;
// }
// String name = "【第" + betoLong + "段】";
// String parentId = Convert.getStringRandom();
// // 先查询自身段落数据
// PPr pPr = paragraph.getPPr();
// if (pPr != null && pPr.getPBdr() != null) {
// PPrBase.PBdr border = pPr.getPBdr();
// String topName = "【】【】";
// Convert.printBorder("上边框", border.getTop());
// Convert.printBorder("下边框", border.getBottom());
// Convert.printBorder("左边框", border.getLeft());
// Convert.printBorder("右边框", border.getRight());
// Convert.printBorder("内横线", border.getBetween());
// Convert.printBorder("内竖线", border.getBar());
// }
// }
// 段落-底纹
public static void getParagraphShd(P paragraph, StyleDefinitionsPart stylePart) {
if (paragraph == null) {
return ;
}
// 先查询自身段落数据
PPr pPr = paragraph.getPPr();
if (pPr != null && pPr.getShd() != null) {
CTShd shd = pPr.getShd();
System.out.println("图案样式 (val): " + shd.getVal()); // 如 clear, solid, nil
System.out.println("图案颜色 (color): " + shd.getColor()); // 图案颜色(文字上的线)
System.out.println("填充颜色 (fill): " + shd.getFill()); // 背景色(段落底色)
}
}
// 段落-首字下沉
public static void getParagraphDropCap(P paragraph, StyleDefinitionsPart stylePart) {
if (paragraph == null) {
return ;
}
PPr pPr = paragraph.getPPr();
if (pPr != null && pPr.getFramePr() != null) {
CTFramePr framePr = pPr.getFramePr();
System.out.println("---- 首字下沉 ----");
System.out.println("存在首字下沉: 是");
if (framePr.getDropCap() != null) {
System.out.println("首字位置 (dropCap): " + framePr.getDropCap().value()); // drop | none | margin
}
if (framePr.getLines() != null) {
System.out.println("下沉行数 (lines): " + framePr.getLines().intValue());
}
if (framePr.getHSpace() != null) {
System.out.println("距正文 (hSpace): " + framePr.getHSpace().intValue()); // twips
}
// 获取首字字体
String fontName = Convert.getFirstRunFont(paragraph);
if (fontName != null) {
System.out.println("字体: " + fontName);
}
} else {
System.out.println("不存在首字下沉");
}
}
// 段落-分栏
public static void getParagraphCols(P paragraph, StyleDefinitionsPart stylePart) {
if (paragraph == null) {
return ;
}
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();
System.out.println("---- 分栏信息 ----");
System.out.println("栏数: " + (cols.getNum() != null ? cols.getNum().intValue() : "未设置"));
System.out.println("是否栏宽相等 (equalWidth): " + (cols.isEqualWidth() ? "" : ""));
System.out.println("是否有分隔线 (sep): " + (cols.isSep() ? "" : ""));
if (cols.getCol() != null && !cols.getCol().isEmpty()) {
int index = 1;
for (CTColumn col : cols.getCol()) {
System.out.println("" + index + " 栏宽度 (twips): " + col.getW());
System.out.println("" + index + " 栏间距 (twips): " + col.getSpace());
index++;
}
}
}
}
}

View File

@@ -0,0 +1,170 @@
package pc.exam.pp.module.judgement.utils.wps_word.docx4j.paragraph;
import org.docx4j.XmlUtils;
import org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart;
import org.docx4j.wml.*;
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("--------");
}
}

View File

@@ -0,0 +1,309 @@
package pc.exam.pp.module.judgement.utils.wps_word.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("---- 节页面大小设置 ----");
// 页面宽度 & 高度(单位是 twips1/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("<w:drawing")) {
System.out.println("水印类型图片水印Drawing");
return;
}
// 检查是否包含 VML 文字水印WordArt
if (XmlUtils.marshaltoString(run, true).contains("<v:shape") &&
XmlUtils.marshaltoString(run, true).contains("style=")) {
System.out.println("水印类型文字水印WordArt");
return;
}
}
// 页眉
public static void getSectionPageHeader(List<org.docx4j.model.structure.SectionWrapper> sections, WordprocessingMLPackage wordMLPackage) {
for (int i = 0; i < sections.size(); i++) {
org.docx4j.model.structure.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<org.docx4j.model.structure.SectionWrapper> 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<Object> 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<Object> 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");
}
}
}
}

View File

@@ -0,0 +1,25 @@
package pc.exam.pp.module.judgement.utils.wps_word.docx4j.vo;
import lombok.Data;
/**
* @author REN
*/
@Data
public class JudgementWordsVO {
// 名称(相当于类型)
private String name;
// 考点
private String content;
// 考点名称
private String contentIn;
// 父节点
private String parentId;
// 节点id
private String id;
}

52
pom.xml
View File

@@ -141,32 +141,32 @@
<!-- 使用 huawei / aliyun 的 Maven 源,提升下载速度 -->
<repositories>
<repository>
<id>huaweicloud</id>
<name>huawei</name>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
</repository>
<repository>
<id>aliyunmaven</id>
<name>aliyun</name>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<!-- <repository>-->
<!-- <id>huaweicloud</id>-->
<!-- <name>huawei</name>-->
<!-- <url>https://mirrors.huaweicloud.com/repository/maven/</url>-->
<!-- </repository>-->
<!-- <repository>-->
<!-- <id>aliyunmaven</id>-->
<!-- <name>aliyun</name>-->
<!-- <url>https://maven.aliyun.com/repository/public</url>-->
<!-- </repository>-->
<!-- <repository>-->
<!-- <id>spring-milestones</id>-->
<!-- <name>Spring Milestones</name>-->
<!-- <url>https://repo.spring.io/milestone</url>-->
<!-- <snapshots>-->
<!-- <enabled>false</enabled>-->
<!-- </snapshots>-->
<!-- </repository>-->
<!-- <repository>-->
<!-- <id>spring-snapshots</id>-->
<!-- <name>Spring Snapshots</name>-->
<!-- <url>https://repo.spring.io/snapshot</url>-->
<!-- <releases>-->
<!-- <enabled>false</enabled>-->
<!-- </releases>-->
<!-- </repository>-->
<repository>
<id>maven-central</id>
<name>Maven Central Repository</name>