对Office中Word内容的格式检测——问题解决(仅支持docx文件)

前一篇章描述了一些遇到的问题,链接如下:https://blog.csdn.net/qq_39357812/article/details/90774608.

以下主要是问题的解决方法:

问:一般论文中分为几大部分,即封面、摘要、目录、正文、结论、文献、致谢、附录,那么如何定位到其中的某个部分位置呢?(因为目前只是完成了对正文部分的格式校验,所以下面会详述如何定位正文部分)

答:我是根据大纲视图以及正文部分的特征去定位位置的,每一部分内容的开始都需要设置大纲视图级别,例如目录、结论等需要设置为一级。而对于正文部分中,一级标题都需要设置为一级(例如,第一章 绪论需要设置为一级),获取论文中大纲级别的代码如下:

	/**
	 * Word中的大纲级别,可以通过getPPr().getOutlineLvl()直接提取,但需要注意,Word中段落级别,通过如下三种方式定义:
	 *  1、直接对段落进行定义;
	 *  2、对段落的样式进行定义;
	 *  3、对段落样式的基础样式进行定义。
	 *  因此,在通过“getPPr().getOutlineLvl()”提取时,需要依次在如上三处读取。
	 * @param doc
	 * @param para
	 * @return
	 */
	public static String getTitleLvl(XWPFDocument doc, XWPFParagraph para) {
		String titleLvl = "";
		try {
			// 判断该段落是否设置了大纲级别
			if (para.getCTP().getPPr().getOutlineLvl() != null) {
				return String.valueOf(para.getCTP().getPPr().getOutlineLvl().getVal());
			}
		} catch (Exception e) {
		}
		try {
			// 判断该段落的样式是否设置了大纲级别
			if (doc.getStyles().getStyle(para.getStyle()).getCTStyle().getPPr().getOutlineLvl() != null) {
				return String.valueOf(doc.getStyles().getStyle(para.getStyle()).getCTStyle().getPPr().getOutlineLvl().getVal());
			}
		} catch (Exception e) {

		}
		try {
			// 判断该段落的样式的基础样式是否设置了大纲级别
			if (doc.getStyles().getStyle(doc.getStyles().getStyle(para.getStyle()).getCTStyle().getBasedOn().getVal())
					.getCTStyle().getPPr().getOutlineLvl() != null) {
				String styleName = doc.getStyles().getStyle(para.getStyle()).getCTStyle().getBasedOn().getVal();
				return String.valueOf(doc.getStyles().getStyle(styleName).getCTStyle().getPPr().getOutlineLvl().getVal());
			}
		} catch (Exception e) {
		}
		try {
			if(para.getStyleID()!=null){
				return para.getStyleID();
			}
		} catch (Exception e) {
		}
		return titleLvl;
	}

获取大纲级别为一级的段落后,然后根据正文部分的特征定位位置,我的方法是:段落中的文字包含第一章。当满足以上两个条件,则可以判断该段落为第一章一级标题,并且也是正文部分的开始位置。那么如何确定正文段落的范围呢?我的方法是:正文中的一级标题中的文字中包含标题序号,例如第一章、第二章等等。因此,我是根据大纲级别为一级的段落,并且段落中文字是否包含“第”和“章”这两个字,以上两种条件确定是否属于正文段落范围,当不满足条件时,便不是。

问:如何定位正文部分中各级标题(例如第一章、第二章等一级标题,1.1、1.2等二级标题)?

答:我的方法是:首先定位一级标题的,然后是二级标题,再然后是三级标题(我只对一级、二级以及三级标题进行校验,四级以及五级标题跟正文段落格式相同,不进行校验)。首先获取大纲级别为一级的段落,然后判断这些段落是否满足正文部分一级标题的特征,代码如下:

   // 是否满足一级标题的特征
	public static Boolean isFirstTitle(XWPFParagraph para) {
		if (para.getParagraphText().indexOf("第") != -1 && para.getParagraphText().indexOf("章") != -1) {
			return true;
		}
		return false;
	}

满足以上两个条件,便可以判断为一级标题的段落,同时根据段落的顺序,可以获取正确的标题序号(例如某个段落为第二个一级标题,则该标题序号为第二章)。定位二级以及三级标题与之类似(二级标题段落的大纲级别为二级,三级标题段落的大纲级别为三级)。

问:如何对一些格式进行校验(例如字体主题、字体大小以及段落行距等)?

答:以下是对字体属性的校验方法(包括中英文字体大小、字体主题、字体加粗):
1、判断文字是中文还是英文的代码如下:

	// 对空格、符合、数字不区分中英文
	public String removeExtraString(String str){
		return str.replaceAll(" ", "")
				  .replaceAll("[\\pP\\p{Punct}]","")
				  .replaceAll("[0-9]*", "");

	}

	// 判断是否英文
	public Boolean isEnglishFont(XWPFRun xwpfRun) {
		if (this.removeExtraString(xwpfRun.getText(0)).matches("[a-zA-Z]+")){
			return true;
		}
		return false;
	}

	// 判断是否中文
	public Boolean isChinessFont(XWPFRun xwpfRun){
		Pattern pattern = Pattern.compile("[\\u4e00-\\u9fa5]+");
		if (pattern.matcher(this.removeExtraString(xwpfRun.getText(0))).find()){
			return true;
		}
		return false;
	}

2、获取字体大小的代码如下:

	// 获取文档默认字体大小
	public float getDocxDefaultFontSize(XWPFDocument docx) throws Exception {
		if (docx.getStyle().getDocDefaults().getRPrDefault() != null) {
			if (docx.getStyle().getDocDefaults().getRPrDefault().getRPr() != null) {
				CTRPr rPr = docx.getStyle().getDocDefaults().getRPrDefault().getRPr();
				if (rPr.getSz() != null) {
					return (float) rPr.getSz().getVal().longValue()/2;
				} else if (rPr.getSzCs() != null) {
					return (float) rPr.getSzCs().getVal().longValue()/2;
				}
			}
		}
		return 10;
	}

	// 获取字体大小
	public float getFontSize(XWPFDocument docx, XWPFParagraph paragraph, XWPFRun x) throws Exception {

		if (x.getCTR().getRPr() != null) {
			if (x.getCTR().getRPr().getSz() != null) {
				return (float)x.getCTR().getRPr().getSz().getVal().longValue()/2;
			} else if (x.getCTR().getRPr().getSzCs() != null) {
				return (float)x.getCTR().getRPr().getSzCs().getVal().longValue()/2;
			}
		}

		if (docx.getStyles().getStyle(paragraph.getStyleID()) != null) {
			if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getRPr() != null) {
				CTRPr rPr = docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getRPr();
				if (rPr.getSz() != null) return (float)rPr.getSz().getVal().longValue()/2;
				else if (rPr.getSzCs() != null) return (float)rPr.getSzCs().getVal().longValue()/2;
			}
		}

		// 文档默认字体大小
		float fontSize = this.getDocxDefaultFontSize(docx);
		return fontSize;

	}

3、获取字体主题的代码如下:

	// 获取字体的字体主题
	public String getFontTheme(XWPFRun run, XWPFDocument docx, XWPFParagraph paragraph) throws Exception {
		if (run.getCTR().getRPr() != null && run.getCTR().getRPr().getRFonts() != null) {
			CTFonts rFonts = run.getCTR().getRPr().getRFonts();
			if (this.isEnglishFont(run) && rFonts.getAscii() != null) {
				return rFonts.getAscii();
			} else if (this.isChinessFont(run) && rFonts.getEastAsia() != null) {
				return rFonts.getEastAsia();
			}
		}

		if (docx.getStyles().getStyle(paragraph.getStyleID()) != null) {
			if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getRPr() != null){
				if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getRPr().getRFonts() != null) {
					CTFonts rFonts1 = docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getRPr().getRFonts();
					if (this.isEnglishFont(run) && rFonts1.getAscii() != null) return rFonts1.getAscii();
					else if (this.isChinessFont(run) && rFonts1.getEastAsia() != null) return rFonts1.getEastAsia();
				}
			}
		}

		// 默认字体主题
		String fontTheme = "";
		// 如果为英文字体
		if (this.isEnglishFont(run)) {
			fontTheme = docx.getStyle().getDocDefaults().getRPrDefault().getRPr().getRFonts().getAscii();
		} else {
			fontTheme = docx.getStyle().getDocDefaults().getRPrDefault().getRPr().getRFonts().getEastAsia();
		}
		return fontTheme;
	}

4、获取字体加粗状态的代码如下:

	// 获取默认字体加粗
	public Boolean getDocxDefaultFontBold(XWPFDocument docx) throws Exception {
		if (docx.getStyle().getDocDefaults().getRPrDefault() != null) {
			if (docx.getStyle().getDocDefaults().getRPrDefault().getRPr() != null) {
				CTRPr rPr = docx.getStyle().getDocDefaults().getRPrDefault().getRPr();
				if (rPr.getB() != null) {
					if (rPr.getB().isSetVal()) return false;
					else return true;
				} else if (rPr.getBCs() != null) {
					if (rPr.getBCs().isSetVal()) return false;
					else return true;
				}
			}
		}
		return false;
	}

	// 获取字体是否加粗
	public Boolean getFontBold(XWPFDocument docx, XWPFParagraph paragraph, XWPFRun run) throws Exception {
		if (run.getCTR().getRPr() != null) {
			if (run.getCTR().getRPr().getB() != null) {
				if (run.getCTR().getRPr().getB().isSetVal()) return false;
				else return true;
			}
		}

		if (docx.getStyles().getStyle(paragraph.getStyleID()) != null) {
			if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getRPr() != null) {
				CTRPr rPr = docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getRPr();
				if (rPr.getB() != null) {
					if (rPr.getB().isSetVal()) return false;
					else return true;
				} else if (rPr.getBCs() != null) {
					if (rPr.getBCs().isSetVal()) return false;
					else return true;
				}
			}
		}

		// 默认字体加粗状态
		return this.getDocxDefaultFontBold(docx);
	}

以下是对段落属性的校验方法(包括段落行距、首行缩进、段落对齐方式):
1、获取段落首行缩进的代码:

	// 获取文档默认首行缩进
	public Integer getDocxFirstLineChars(XWPFDocument docx) throws Exception {
		if (docx.getStyle().getDocDefaults().getPPrDefault() != null && docx.getStyle().getDocDefaults().getPPrDefault().getPPr() != null) {
			CTPPr pPr = docx.getStyle().getDocDefaults().getPPrDefault().getPPr();
			if (pPr.getInd() != null && pPr.getInd().isSetFirstLineChars()) {
				return pPr.getInd().getFirstLineChars().intValue();
			}
		}
		return -1;
	}

	// 获取段落首行缩进
	public Integer getParaFirstLineChars(XWPFDocument docx, XWPFParagraph paragraph) throws Exception {
		if (paragraph.getCTP().getPPr() != null){
			if (paragraph.getCTP().getPPr().getInd() != null && paragraph.getCTP().getPPr().getInd().isSetFirstLineChars()) {
				return paragraph.getCTP().getPPr().getInd().getFirstLineChars().intValue();
			}
		}

		if (docx.getStyles().getStyle(paragraph.getStyleID()) != null) {
			if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr() != null) {
				CTPPr pPr = docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr();
				if (pPr.getInd() != null && pPr.getInd().isSetFirstLineChars()) {
					return pPr.getInd().getFirstLineChars().intValue();
				}
			}
		}

		return this.getDocxFirstLineChars(docx);
	}

2、获取段前行距的代码(分为段前行数和段前磅数):
① 获取段前行数的代码:

	public Integer getParaSpacingBeforeLines(XWPFParagraph paragraph, XWPFDocument docx) throws Exception {
		if (paragraph.getSpacingBeforeLines() != -1) {
			return paragraph.getSpacingBeforeLines();
		}

		if (docx.getStyles().getStyle(paragraph.getStyleID()) != null) {
			if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr() != null) {
				if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr().getSpacing() != null) {
					if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr().getSpacing().getBeforeLines() != null) {
						return docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr().getSpacing().getBeforeLines().intValue();
					}
				}
			}
		}
		// 文档默认段前行距
		Integer beforeLines = this.getDocxDefaultSpacing(docx, ThesisConstant.SPACING_BEFORE_Line);
		return beforeLines;
	}

② 获取段前磅数的代码:

	public Integer getParaSpacingBefore(XWPFParagraph paragraph, XWPFDocument docx) throws Exception {
		if (paragraph.getSpacingBefore() != -1) {
			return paragraph.getSpacingBefore();
		}

		if (docx.getStyles().getStyle(paragraph.getStyleID()) != null) {
			if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr() != null) {
				if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr().getSpacing() != null) {
					if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr().getSpacing().getBefore() != null) {
						return docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr().getSpacing().getBefore().intValue();
					}
				}
			}
		}

		// 文档默认段前行距
		Integer spacingBefore = this.getDocxDefaultSpacing(docx, ThesisConstant.SPACING_BEFORE);
		return spacingBefore;
	}

3、获取段后行距的代码(分为段后行数和段后磅数):
① 获取段后行数的代码:

	public Integer getParaSpacingAfterLines(XWPFParagraph paragraph, XWPFDocument docx) throws Exception {
		if (paragraph.getSpacingAfterLines() != -1) {
			return paragraph.getSpacingAfterLines();
		}

		if (docx.getStyles().getStyle(paragraph.getStyleID()) != null) {
			if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr() != null) {
				if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr().getSpacing() != null) {
					if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr().getSpacing().getAfterLines() != null) {
						return docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr().getSpacing().getAfterLines().intValue();
					}
				}
			}
		}
		// 文档默认段后行距
		Integer afterLines = this.getDocxDefaultSpacing(docx, ThesisConstant.SPACING_AFTER_LINE);
		return afterLines;
	}

② 获取段后磅数的代码:

	public Integer getParaSpacingAfter(XWPFParagraph paragraph, XWPFDocument docx) throws Exception {
		if (paragraph.getSpacingAfter() != -1) {
			return paragraph.getSpacingAfter();
		}

		if (docx.getStyles().getStyle(paragraph.getStyleID()) != null) {
			if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr() != null) {
				if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr().getSpacing() != null) {
					if (docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr().getSpacing().getAfter() != null) {
						return docx.getStyles().getStyle(paragraph.getStyleID()).getCTStyle().getPPr().getSpacing().getAfter().intValue();
					}
				}
			}
		}

		// 文档默认段后行距
		Integer spacingAfter = this.getDocxDefaultSpacing(docx, ThesisConstant.SPACING_AFTER);
		return spacingAfter;
	}

以上代码获取文档默认段前、后行距的代码如下:

	// 获取文档默认段前、后行距
	public Integer getDocxDefaultSpacing(XWPFDocument docx, String category) throws Exception {
		if (docx.getStyle().getDocDefaults().getPPrDefault().getPPr() != null) {
			if (docx.getStyle().getDocDefaults().getPPrDefault().getPPr().getSpacing() != null) {
				CTSpacing spacing = docx.getStyle().getDocDefaults().getPPrDefault().getPPr().getSpacing();
				if (category.equals(ThesisConstant.SPACING_BEFORE) ){
					if (spacing.getBefore() != null) {
						return spacing.getBefore().intValue();
					}
				} else if (category.equals(ThesisConstant.SPACING_BEFORE_Line)) {
					if (spacing.getBeforeLines() != null) {
						return spacing.getBeforeLines().intValue();
					}
				} else if (category.equals(ThesisConstant.SPACING_AFTER)) {
					if (spacing.getAfter() != null) {
						return spacing.getAfter().intValue();
					}
				} else if (category.equals(ThesisConstant.SPACING_AFTER_LINE)) {
					if (spacing.getAfterLines() != null) {
						return spacing.getAfterLines().intValue();
					}
				}
			}
		}
		return 0;
	}

4、获取段间行距的代码与上述类似,在此就不一一详述。

问:生成的检测报告中,每一条校验信息的生成规则?

答:我的方法是:错误范围+错误具体位置+错误原因+如何改正。例如第一章的1.1章节标题“1.1 项目背景”中的“背景”二字的字体大小出现错误,则在检测报告中会生成这样一条信息:1.1标题中 ‘背景’,【中文字体大小错误】,应改为 小四。通过这样的一个规则,可以让用户快速定位错误并改正。

你可能感兴趣的:(POI,docx,Office,Open,XML,论文格式检测,生成报告)